VMware vSphere Integrated Containers provider (#206)
* Add Virtual Kubelet provider for VIC Initial virtual kubelet provider for VMware VIC. This provider currently handles creating and starting of a pod VM via the VIC portlayer and persona server. Image store handling via the VIC persona server. This provider currently requires the feature/wolfpack branch of VIC. * Added pod stop and delete. Also added node capacity. Added the ability to stop and delete pod VMs via VIC. Also retrieve node capacity information from the VCH. * Cleanup and readme file Some file clean up and added a Readme.md markdown file for the VIC provider. * Cleaned up errors, added function comments, moved operation code 1. Cleaned up error handling. Set standard for creating errors. 2. Added method prototype comments for all interface functions. 3. Moved PodCreator, PodStarter, PodStopper, and PodDeleter to a new folder. * Add mocking code and unit tests for podcache, podcreator, and podstarter Used the unit test framework used in VIC to handle assertions in the provider's unit test. Mocking code generated using OSS project mockery, which is compatible with the testify assertion framework. * Vendored packages for the VIC provider Requires feature/wolfpack branch of VIC and a few specific commit sha of projects used within VIC. * Implementation of POD Stopper and Deleter unit tests (#4) * Updated files for initial PR
This commit is contained in:
341
vendor/github.com/vmware/vic/lib/tether/cmd_test.go
generated
vendored
Normal file
341
vendor/github.com/vmware/vic/lib/tether/cmd_test.go
generated
vendored
Normal file
@@ -0,0 +1,341 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tether
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/lib/config/executor"
|
||||
"github.com/vmware/vic/pkg/vsphere/extraconfig"
|
||||
)
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// TestPathLookup constructs the spec for a Session where the binary path must be
|
||||
// resolved from the PATH environment variable - this is a variation from normal
|
||||
// Cmd handling where that is done during creation of Cmd
|
||||
//
|
||||
|
||||
func TestPathLookup(t *testing.T) {
|
||||
_, mocker := testSetup(t)
|
||||
defer testTeardown(t, mocker)
|
||||
|
||||
cfg := executor.ExecutorConfig{
|
||||
ExecutorConfigCommon: executor.ExecutorConfigCommon{
|
||||
ID: "pathlookup",
|
||||
Name: "tether_test_executor",
|
||||
},
|
||||
|
||||
Sessions: map[string]*executor.SessionConfig{
|
||||
"pathlookup": {
|
||||
Common: executor.Common{
|
||||
ID: "pathlookup",
|
||||
Name: "tether_test_session",
|
||||
},
|
||||
Tty: false,
|
||||
Active: true,
|
||||
|
||||
Cmd: executor.Cmd{
|
||||
// test relative path
|
||||
Path: "date",
|
||||
Args: []string{"date", "--reference=/"},
|
||||
Env: []string{"PATH=/bin"},
|
||||
Dir: "/bin",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, src, err := RunTether(t, &cfg, mocker)
|
||||
assert.NoError(t, err, "Didn't expected error from runTether")
|
||||
|
||||
result := ExecutorConfig{}
|
||||
extraconfig.Decode(src, &result)
|
||||
|
||||
assert.Equal(t, "true", result.Sessions["pathlookup"].Started, "Expected command to have been started successfully")
|
||||
assert.Equal(t, 0, result.Sessions["pathlookup"].ExitStatus, "Expected command to have exited cleanly")
|
||||
}
|
||||
|
||||
func TestRelativePath(t *testing.T) {
|
||||
_, mocker := testSetup(t)
|
||||
defer testTeardown(t, mocker)
|
||||
|
||||
cfg := executor.ExecutorConfig{
|
||||
ExecutorConfigCommon: executor.ExecutorConfigCommon{
|
||||
ID: "relpath",
|
||||
Name: "tether_test_executor",
|
||||
},
|
||||
|
||||
Sessions: map[string]*executor.SessionConfig{
|
||||
"relpath": {
|
||||
Common: executor.Common{
|
||||
ID: "relpath",
|
||||
Name: "tether_test_session",
|
||||
},
|
||||
Tty: false,
|
||||
Active: true,
|
||||
|
||||
Cmd: executor.Cmd{
|
||||
// test relative path
|
||||
Path: "./date",
|
||||
Args: []string{"./date", "--reference=/"},
|
||||
Env: []string{"PATH="},
|
||||
Dir: "/bin",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, src, err := RunTether(t, &cfg, mocker)
|
||||
assert.NoError(t, err, "Didn't expected error from RunTether")
|
||||
|
||||
result := ExecutorConfig{}
|
||||
extraconfig.Decode(src, &result)
|
||||
|
||||
assert.Equal(t, "true", result.Sessions["relpath"].Started, "Expected command to have been started successfully")
|
||||
assert.Equal(t, 0, result.Sessions["relpath"].ExitStatus, "Expected command to have exited cleanly")
|
||||
}
|
||||
|
||||
func TestAbsPath(t *testing.T) {
|
||||
_, mocker := testSetup(t)
|
||||
defer testTeardown(t, mocker)
|
||||
|
||||
cfg := executor.ExecutorConfig{
|
||||
ExecutorConfigCommon: executor.ExecutorConfigCommon{
|
||||
ID: "abspath",
|
||||
Name: "tether_test_executor",
|
||||
},
|
||||
|
||||
Sessions: map[string]*executor.SessionConfig{
|
||||
"abspath": {
|
||||
Common: executor.Common{
|
||||
ID: "abspath",
|
||||
Name: "tether_test_session",
|
||||
},
|
||||
Tty: false,
|
||||
Active: true,
|
||||
|
||||
Cmd: executor.Cmd{
|
||||
// test abs path
|
||||
Path: "/bin/date",
|
||||
Args: []string{"date", "--reference=/"},
|
||||
Env: []string{},
|
||||
Dir: "/",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, src, err := RunTether(t, &cfg, mocker)
|
||||
assert.NoError(t, err, "Didn't expected error from RunTether")
|
||||
|
||||
result := ExecutorConfig{}
|
||||
extraconfig.Decode(src, &result)
|
||||
|
||||
assert.Equal(t, "true", result.Sessions["abspath"].Started, "Expected command to have been started successfully")
|
||||
assert.Equal(t, 0, result.Sessions["abspath"].ExitStatus, "Expected command to have exited cleanly")
|
||||
|
||||
// read the output from the session
|
||||
log := mocker.SessionLogBuffer.Bytes()
|
||||
|
||||
// block until tether exits
|
||||
<-mocker.Cleaned
|
||||
|
||||
// run the command directly
|
||||
out, err := exec.Command("/bin/date", "--reference=/").Output()
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to run date for comparison data: %s", err)
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Equal(t, out, log) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestHalt(t *testing.T) {
|
||||
_, mocker := testSetup(t)
|
||||
defer testTeardown(t, mocker)
|
||||
|
||||
cfg := executor.ExecutorConfig{
|
||||
ExecutorConfigCommon: executor.ExecutorConfigCommon{
|
||||
ID: "abspath",
|
||||
Name: "tether_test_executor",
|
||||
},
|
||||
|
||||
Sessions: map[string]*executor.SessionConfig{
|
||||
"abspath": {
|
||||
Common: executor.Common{
|
||||
ID: "abspath",
|
||||
Name: "tether_test_session",
|
||||
},
|
||||
Tty: false,
|
||||
Active: true,
|
||||
|
||||
Cmd: executor.Cmd{
|
||||
// test abs path
|
||||
Path: "/bin/date",
|
||||
Args: []string{"date", "--reference=/"},
|
||||
Env: []string{},
|
||||
Dir: "/",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, src, err := RunTether(t, &cfg, mocker)
|
||||
assert.NoError(t, err, "Didn't expected error from RunTether")
|
||||
|
||||
// block until tether exits
|
||||
<-mocker.Cleaned
|
||||
|
||||
result := ExecutorConfig{}
|
||||
extraconfig.Decode(src, &result)
|
||||
|
||||
assert.Equal(t, "true", result.Sessions["abspath"].Started, "Expected command to have been started successfully")
|
||||
assert.Equal(t, 0, result.Sessions["abspath"].ExitStatus, "Expected command to have exited cleanly")
|
||||
|
||||
// read the output from the session
|
||||
log := mocker.SessionLogBuffer.Bytes()
|
||||
|
||||
// run the command directly
|
||||
out, err := exec.Command("/bin/date", "--reference=/").Output()
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to run date for comparison data: %s", err)
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Equal(t, out, log) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestAbsPathRepeat(t *testing.T) {
|
||||
log.SetLevel(log.WarnLevel)
|
||||
|
||||
for i := 0; i < 2000 && !t.Failed(); i++ {
|
||||
TestAbsPath(t)
|
||||
}
|
||||
|
||||
defer log.SetLevel(log.DebugLevel)
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// TestMissingBinaryConfig constructs the spec for a Session with invalid binary path
|
||||
//
|
||||
|
||||
func TestMissingBinary(t *testing.T) {
|
||||
_, mocker := testSetup(t)
|
||||
defer testTeardown(t, mocker)
|
||||
|
||||
cfg := executor.ExecutorConfig{
|
||||
ExecutorConfigCommon: executor.ExecutorConfigCommon{
|
||||
ID: "missing",
|
||||
Name: "tether_test_executor",
|
||||
},
|
||||
|
||||
Sessions: map[string]*executor.SessionConfig{
|
||||
"missing": {
|
||||
Common: executor.Common{
|
||||
ID: "missing",
|
||||
Name: "tether_test_session",
|
||||
},
|
||||
Tty: false,
|
||||
Active: true,
|
||||
|
||||
Cmd: executor.Cmd{
|
||||
// test relative path
|
||||
Path: "/not/there",
|
||||
Args: []string{"/not/there"},
|
||||
Env: []string{"PATH=/not"},
|
||||
Dir: "/",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, src, err := RunTether(t, &cfg, mocker)
|
||||
assert.Error(t, err, "Expected error from RunTether")
|
||||
|
||||
// refresh the cfg with current data
|
||||
extraconfig.Decode(src, &cfg)
|
||||
|
||||
// check the launch status was failed
|
||||
status := cfg.Sessions["missing"].Started
|
||||
|
||||
assert.Equal(t, "stat /not/there: no such file or directory", status, "Expected status to have a command not found error message")
|
||||
}
|
||||
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// TestMissingRelativeBinaryConfig constructs the spec for a Session with invalid binary path
|
||||
//
|
||||
|
||||
func TestMissingRelativeBinary(t *testing.T) {
|
||||
_, mocker := testSetup(t)
|
||||
defer testTeardown(t, mocker)
|
||||
|
||||
cfg := executor.ExecutorConfig{
|
||||
ExecutorConfigCommon: executor.ExecutorConfigCommon{
|
||||
ID: "missing",
|
||||
Name: "tether_test_executor",
|
||||
},
|
||||
|
||||
Sessions: map[string]*executor.SessionConfig{
|
||||
"missing": {
|
||||
Common: executor.Common{
|
||||
ID: "missing",
|
||||
Name: "tether_test_session",
|
||||
},
|
||||
Tty: false,
|
||||
Active: true,
|
||||
|
||||
Cmd: executor.Cmd{
|
||||
// test relative path
|
||||
Path: "notthere",
|
||||
Args: []string{"notthere"},
|
||||
Env: []string{"PATH=/not"},
|
||||
Dir: "/",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, src, err := RunTether(t, &cfg, mocker)
|
||||
assert.Error(t, err, "Expected error from RunTether")
|
||||
|
||||
// refresh the cfg with current data
|
||||
extraconfig.Decode(src, &cfg)
|
||||
|
||||
// check the launch status was failed
|
||||
status := cfg.Sessions["missing"].Started
|
||||
|
||||
assert.Equal(t, "notthere: no such executable in PATH", status, "Expected status to have a command not found error message")
|
||||
}
|
||||
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
184
vendor/github.com/vmware/vic/lib/tether/config.go
generated
vendored
Normal file
184
vendor/github.com/vmware/vic/lib/tether/config.go
generated
vendored
Normal file
@@ -0,0 +1,184 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tether
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
|
||||
"github.com/vmware/vic/lib/config/executor"
|
||||
"github.com/vmware/vic/pkg/dio"
|
||||
"github.com/vmware/vic/pkg/ip"
|
||||
)
|
||||
|
||||
type ExecutorConfig struct {
|
||||
// allow us to lock the maps when config is being updated
|
||||
// subelements tend to have their own locks
|
||||
sync.Mutex
|
||||
|
||||
// The name of the system
|
||||
Name string `vic:"0.1" scope:"read-only" key:"common/name"`
|
||||
|
||||
// ID corresponds to that of the primary session
|
||||
ID string `vic:"0.1" scope:"read-only" key:"common/id"`
|
||||
|
||||
// Debug is a numeric level that controls extent of debugging
|
||||
DebugLevel int `vic:"0.1" scope:"read-only" key:"diagnostics/debug"`
|
||||
|
||||
// Exclusive access to childPidTable
|
||||
pidMutex sync.Mutex `vic:"0.1" scope:"read-only" recurse:"depth=0"`
|
||||
|
||||
// Set of child PIDs created by us.
|
||||
pids map[int]*SessionConfig `vic:"0.1" scope:"read-only" recurse:"depth=0"`
|
||||
|
||||
// Sessions is the set of sessions currently hosted by this executor
|
||||
// These are keyed by session ID
|
||||
Sessions map[string]*SessionConfig `vic:"0.1" scope:"read-only" key:"sessions"`
|
||||
|
||||
// Execs is the set of non-persistent sessions hosted by this executor
|
||||
Execs map[string]*SessionConfig `vic:"0.1" scope:"read-only,non-persistent" key:"execs"`
|
||||
|
||||
// Maps the mount name to the detail mount specification
|
||||
Mounts map[string]executor.MountSpec `vic:"0.1" scope:"read-only" key:"mounts"`
|
||||
|
||||
// This describes an executors presence on a network, and contains sufficient
|
||||
// information to configure the interface in the guest.
|
||||
Networks map[string]*NetworkEndpoint `vic:"0.1" scope:"read-only" key:"networks"`
|
||||
|
||||
// Key is the host key used during communicate back with the Interaction endpoint if any
|
||||
// Used if the in-guest tether is responsible for authenticating the connection
|
||||
Key []byte `vic:"0.1" scope:"read-only" key:"key"`
|
||||
|
||||
// AsymmetricRouting is set to true if the VCH needs to be setup for asymmetric routing
|
||||
AsymmetricRouting bool `vic:"0.1" scope:"read-only" key:"asymrouting"`
|
||||
|
||||
// Hostname and domainname provided by personality
|
||||
Hostname string `vic:"0.1" scope:"read-only" key:"hostname"`
|
||||
Domainname string `vic:"0.1" scope:"read-only" key:"domainname"`
|
||||
}
|
||||
|
||||
// SessionConfig defines the content of a session - this maps to the root of a process tree
|
||||
// inside an executor
|
||||
// This is close to but not perfectly aligned with the new docker/docker/daemon/execdriver/driver:CommonProcessConfig
|
||||
type SessionConfig struct {
|
||||
// Protects the structure
|
||||
sync.Mutex `vic:"0.1" scope:"read-only" recurse:"depth=0"`
|
||||
|
||||
// The primary session may have the same ID as the executor owning it
|
||||
executor.Common `vic:"0.1" scope:"read-only" key:"common"`
|
||||
executor.Detail `vic:"0.1" scope:"read-write" key:"detail"`
|
||||
|
||||
// Diagnostics holds basic diagnostics data
|
||||
Diagnostics executor.Diagnostics `vic:"0.1" scope:"read-write" key:"diagnostics"`
|
||||
|
||||
// The primary process for the session
|
||||
Cmd exec.Cmd `vic:"0.1" scope:"read-only" key:"cmd" recurse:"depth=2,nofollow"`
|
||||
|
||||
// The exit status of the process, if any
|
||||
ExitStatus int `vic:"0.1" scope:"read-write" key:"status"`
|
||||
|
||||
Started string `vic:"0.1" scope:"read-write" key:"started"`
|
||||
|
||||
// Allow attach
|
||||
Attach bool `vic:"0.1" scope:"read-only" key:"attach"`
|
||||
|
||||
OpenStdin bool `vic:"0.1" scope:"read-only" key:"openstdin"`
|
||||
|
||||
// Delay launching the Cmd until an attach request comes
|
||||
RunBlock bool `vic:"0.1" scope:"read-write" key:"runblock"`
|
||||
|
||||
// Should this config be activated or not
|
||||
Active bool `vic:"0.1" scope:"read-only" key:"active"`
|
||||
|
||||
// Allocate a tty or not
|
||||
Tty bool `vic:"0.1" scope:"read-only" key:"tty"`
|
||||
|
||||
// Restart controls whether a process gets relaunched if it exists
|
||||
Restart bool `vic:"0.1" scope:"read-only" key:"restart"`
|
||||
|
||||
// StopSignal is the signal name or number used to stop a container
|
||||
StopSignal string `vic:"0.1" scope:"read-only" key:"stopSignal"`
|
||||
|
||||
// User and group for setuid programs
|
||||
User string `vic:"0.1" scope:"read-only" key:"user"`
|
||||
Group string `vic:"0.1" scope:"read-only" key:"group"`
|
||||
|
||||
// if there's a pty then we need additional management data
|
||||
Pty *os.File `vic:"0.1" scope:"read-only" recurse:"depth=0"`
|
||||
Outwriter dio.DynamicMultiWriter `vic:"0.1" scope:"read-only" recurse:"depth=0"`
|
||||
Errwriter dio.DynamicMultiWriter `vic:"0.1" scope:"read-only" recurse:"depth=0"`
|
||||
Reader dio.DynamicMultiReader `vic:"0.1" scope:"read-only" recurse:"depth=0"`
|
||||
|
||||
StdinPipe io.WriteCloser `vic:"0.1" scope:"read-only" recurse:"depth=0"`
|
||||
StdoutPipe io.ReadCloser `vic:"0.1" scope:"read-only" recurse:"depth=0"`
|
||||
StderrPipe io.ReadCloser `vic:"0.1" scope:"read-only" recurse:"depth=0"`
|
||||
|
||||
// Blocks launching the process.
|
||||
// The channel contains no value; we’re only interested in its closed property.
|
||||
ClearToLaunch chan struct{} `vic:"0.1" scope:"read-only" recurse:"depth=0"`
|
||||
|
||||
wait *sync.WaitGroup
|
||||
|
||||
// record the config prefix associated with this session - allows partial encoding
|
||||
// without hardcoding the sessions parent in the config.
|
||||
// This is basically a workaround for:
|
||||
// a. not having integrated Exec sessions in to the Sessions map
|
||||
// b. having only an implementation of CalculateKeys that requires a prefix instead
|
||||
// of performing a search
|
||||
extraconfigKey string
|
||||
}
|
||||
|
||||
type NetworkEndpoint struct {
|
||||
// Common.Name - the nic alias requested (only one name and one alias possible in linux)
|
||||
// Common.ID - pci slot of the vnic allowing for interface identifcation in-guest
|
||||
executor.Common
|
||||
|
||||
// Whether this endpoint's IP was specified by the client (true if it was)
|
||||
Static bool `vic:"0.1" scope:"read-only" key:"static"`
|
||||
|
||||
// IP address to assign
|
||||
IP *net.IPNet `vic:"0.1" scope:"read-only" key:"ip"`
|
||||
|
||||
// Actual IP address assigned
|
||||
Assigned net.IPNet `vic:"0.1" scope:"read-write" key:"assigned"`
|
||||
|
||||
// The network in which this information should be interpreted. This is embedded directly rather than
|
||||
// as a pointer so that we can ensure the data is consistent
|
||||
Network executor.ContainerNetwork `vic:"0.1" scope:"read-only" key:"network"`
|
||||
|
||||
// DHCP runtime info
|
||||
DHCP *DHCPInfo `vic:"0.1" scope:"read-only" recurse:"depth=0"`
|
||||
|
||||
Ports []string `vic:"0.1" scope:"read-only" key:"ports"`
|
||||
|
||||
// is this endpoint connected to an internal network?
|
||||
Internal bool `vic:"0.1" scope:"read-only" key:"internal"`
|
||||
|
||||
// whether the network config was successfully applied
|
||||
configured bool `vic:"0.1" scope:"read-only" recurse:"depth=0"`
|
||||
}
|
||||
|
||||
func (e *NetworkEndpoint) IsDynamic() bool {
|
||||
return !e.Static && (e.IP == nil || ip.IsUnspecifiedIP(e.IP.IP))
|
||||
}
|
||||
|
||||
type DHCPInfo struct {
|
||||
Assigned net.IPNet
|
||||
Nameservers []net.IP
|
||||
Gateway net.IPNet
|
||||
}
|
||||
101
vendor/github.com/vmware/vic/lib/tether/config_test.go
generated
vendored
Normal file
101
vendor/github.com/vmware/vic/lib/tether/config_test.go
generated
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tether
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/lib/config/executor"
|
||||
"github.com/vmware/vic/pkg/ip"
|
||||
"github.com/vmware/vic/pkg/vsphere/extraconfig"
|
||||
)
|
||||
|
||||
var (
|
||||
localhost, lmask, _ = net.ParseCIDR("127.0.0.2/24")
|
||||
gateway, gmask, _ = net.ParseCIDR("127.0.0.1/24")
|
||||
)
|
||||
|
||||
func TestToExtraConfig(t *testing.T) {
|
||||
exec := executor.ExecutorConfig{
|
||||
ExecutorConfigCommon: executor.ExecutorConfigCommon{
|
||||
ID: "deadbeef",
|
||||
Name: "configtest",
|
||||
},
|
||||
Sessions: map[string]*executor.SessionConfig{
|
||||
"deadbeef": {
|
||||
Cmd: executor.Cmd{
|
||||
Path: "/bin/bash",
|
||||
Args: []string{"/bin/bash", "-c", "echo hello"},
|
||||
Dir: "/",
|
||||
Env: []string{"HOME=/", "PATH=/bin"},
|
||||
},
|
||||
},
|
||||
"beefed": {
|
||||
Cmd: executor.Cmd{
|
||||
Path: "/bin/bash",
|
||||
Args: []string{"/bin/bash", "-c", "echo goodbye"},
|
||||
Dir: "/",
|
||||
Env: []string{"HOME=/", "PATH=/bin"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Networks: map[string]*executor.NetworkEndpoint{
|
||||
"eth0": {
|
||||
Static: true,
|
||||
IP: &net.IPNet{IP: localhost, Mask: lmask.Mask},
|
||||
Network: executor.ContainerNetwork{
|
||||
Common: executor.Common{
|
||||
Name: "notsure",
|
||||
},
|
||||
Gateway: net.IPNet{IP: gateway, Mask: gmask.Mask},
|
||||
Destinations: []net.IPNet{},
|
||||
Nameservers: []net.IP{},
|
||||
Pools: []ip.Range{},
|
||||
Aliases: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
exec.Networks["eth0"].Network.Assigned.Nameservers = []net.IP{}
|
||||
|
||||
// encode exec package's ExecutorConfig
|
||||
encoded := map[string]string{}
|
||||
extraconfig.Encode(extraconfig.MapSink(encoded), exec)
|
||||
|
||||
// decode into this package's ExecutorConfig
|
||||
var decoded ExecutorConfig
|
||||
extraconfig.Decode(extraconfig.MapSource(encoded), &decoded)
|
||||
|
||||
// the source and destination structs are different - we're doing a sparse comparison
|
||||
expectedNet := exec.Networks["eth0"]
|
||||
actualNet := decoded.Networks["eth0"]
|
||||
|
||||
assert.Equal(t, expectedNet.Common, actualNet.Common)
|
||||
assert.Equal(t, expectedNet.Static, actualNet.Static)
|
||||
assert.Equal(t, expectedNet.Assigned, actualNet.Assigned)
|
||||
assert.Equal(t, expectedNet.Network, actualNet.Network)
|
||||
|
||||
expectedSession := exec.Sessions["deadbeef"]
|
||||
actualSession := decoded.Sessions["deadbeef"]
|
||||
|
||||
assert.Equal(t, expectedSession.Cmd.Path, actualSession.Cmd.Path)
|
||||
assert.Equal(t, expectedSession.Cmd.Args, actualSession.Cmd.Args)
|
||||
assert.Equal(t, expectedSession.Cmd.Dir, actualSession.Cmd.Dir)
|
||||
assert.Equal(t, expectedSession.Cmd.Env, actualSession.Cmd.Env)
|
||||
}
|
||||
317
vendor/github.com/vmware/vic/lib/tether/exec_test.go
generated
vendored
Normal file
317
vendor/github.com/vmware/vic/lib/tether/exec_test.go
generated
vendored
Normal file
@@ -0,0 +1,317 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tether
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/lib/config/executor"
|
||||
"github.com/vmware/vic/pkg/vsphere/extraconfig"
|
||||
)
|
||||
|
||||
func TestExec(t *testing.T) {
|
||||
_, mocker := testSetup(t)
|
||||
|
||||
defer testTeardown(t, mocker)
|
||||
|
||||
dir, err := ioutil.TempDir("", "tetherTestExec")
|
||||
if !assert.Nil(t, err, "Unable to create temp file") {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
flagFile := path.Join(dir, "flagfile")
|
||||
defer os.RemoveAll(flagFile)
|
||||
|
||||
cfg := executor.ExecutorConfig{
|
||||
ExecutorConfigCommon: executor.ExecutorConfigCommon{
|
||||
ID: "primary",
|
||||
Name: "tether_test_executor",
|
||||
},
|
||||
|
||||
Sessions: map[string]*executor.SessionConfig{
|
||||
"primary": {
|
||||
Common: executor.Common{
|
||||
ID: "primary",
|
||||
Name: "tether_test_session",
|
||||
},
|
||||
Tty: true,
|
||||
Active: true,
|
||||
OpenStdin: true,
|
||||
|
||||
Cmd: executor.Cmd{
|
||||
// test abs path
|
||||
Path: "/bin/bash",
|
||||
Args: []string{"/bin/bash", "-c", fmt.Sprintf("until /usr/bin/test -e %s;do /bin/sleep 0.1;done", flagFile)},
|
||||
Env: []string{},
|
||||
Dir: "/",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tthr, src, sink := StartTether(t, &cfg, mocker)
|
||||
|
||||
<-mocker.Started
|
||||
|
||||
// at this point the primary process should be up and running, so grab the pid
|
||||
result := ExecutorConfig{}
|
||||
extraconfig.Decode(src, &result)
|
||||
|
||||
for result.Sessions["primary"].Started != "true" {
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
extraconfig.Decode(src, &result)
|
||||
}
|
||||
|
||||
// configure a command to kill the primary
|
||||
cfg.Execs = map[string]*executor.SessionConfig{
|
||||
"touch": {
|
||||
Common: executor.Common{
|
||||
ID: "touch",
|
||||
Name: "tether_test_session",
|
||||
},
|
||||
Tty: false,
|
||||
Active: true,
|
||||
|
||||
Cmd: executor.Cmd{
|
||||
// test abs path
|
||||
Path: "/bin/touch",
|
||||
Args: []string{"touch", flagFile},
|
||||
Env: []string{},
|
||||
Dir: "/",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// update the active config
|
||||
extraconfig.Encode(sink, cfg)
|
||||
|
||||
// trigger tether reload
|
||||
tthr.Reload()
|
||||
|
||||
<-mocker.Reloaded
|
||||
|
||||
// block until tether exits - exit within test timeout is a pass given the pairing of processes in the test
|
||||
<-mocker.Cleaned
|
||||
}
|
||||
|
||||
func TestMissingBinaryNonFatal(t *testing.T) {
|
||||
_, mocker := testSetup(t)
|
||||
|
||||
defer testTeardown(t, mocker)
|
||||
|
||||
dir, err := ioutil.TempDir("", "tetherTestExec")
|
||||
if !assert.Nil(t, err, "Unable to create temp file") {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
flagFile := path.Join(dir, "flagfile")
|
||||
defer os.RemoveAll(flagFile)
|
||||
|
||||
cfg := executor.ExecutorConfig{
|
||||
ExecutorConfigCommon: executor.ExecutorConfigCommon{
|
||||
ID: "primary",
|
||||
Name: "tether_test_executor",
|
||||
},
|
||||
Diagnostics: executor.Diagnostics{
|
||||
DebugLevel: 2,
|
||||
},
|
||||
|
||||
Sessions: map[string]*executor.SessionConfig{
|
||||
"primary": {
|
||||
Common: executor.Common{
|
||||
ID: "primary",
|
||||
Name: "tether_test_session",
|
||||
},
|
||||
Tty: true,
|
||||
Active: true,
|
||||
OpenStdin: true,
|
||||
|
||||
Cmd: executor.Cmd{
|
||||
// test abs path
|
||||
Path: "/bin/bash",
|
||||
Args: []string{"/bin/bash", "-c", fmt.Sprintf("until /usr/bin/test -e %s;do /bin/sleep 0.1;done", flagFile)},
|
||||
Env: []string{},
|
||||
Dir: "/",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tthr, src, sink := StartTether(t, &cfg, mocker)
|
||||
|
||||
<-mocker.Started
|
||||
|
||||
// at this point the primary process should be up and running, so grab the pid
|
||||
extraconfig.Decode(src, &cfg)
|
||||
|
||||
for cfg.Sessions["primary"].Started != "true" {
|
||||
time.Sleep(time.Second)
|
||||
extraconfig.Decode(src, &cfg)
|
||||
}
|
||||
|
||||
// configure a command to kill the primary
|
||||
cfg.Execs = map[string]*executor.SessionConfig{
|
||||
"nonfatal": {
|
||||
Common: executor.Common{
|
||||
ID: "nonfatal",
|
||||
Name: "nonfatal_exec_session",
|
||||
},
|
||||
Tty: false,
|
||||
Active: true,
|
||||
|
||||
Cmd: executor.Cmd{
|
||||
// test abs path
|
||||
Path: "/not/there",
|
||||
Args: []string{"/not/there"},
|
||||
Env: []string{},
|
||||
Dir: "/",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// update the active config
|
||||
extraconfig.Encode(sink, cfg)
|
||||
|
||||
// trigger tether reload
|
||||
tthr.Reload()
|
||||
|
||||
// wait for the reload to be processed
|
||||
<-mocker.Reloaded
|
||||
|
||||
for cfg.Execs["nonfatal"].Started == "" {
|
||||
time.Sleep(time.Second)
|
||||
extraconfig.Decode(src, &cfg)
|
||||
}
|
||||
|
||||
// reconfigure the missing process to make it inactive
|
||||
cfg.Execs["nonfatal"].Active = false
|
||||
|
||||
// configure a command to kill the primary
|
||||
cfg.Execs["touch"] = &executor.SessionConfig{
|
||||
Common: executor.Common{
|
||||
ID: "touch",
|
||||
Name: "tether_test_session",
|
||||
},
|
||||
Tty: false,
|
||||
Active: true,
|
||||
|
||||
Cmd: executor.Cmd{
|
||||
// test abs path
|
||||
Path: "/bin/touch",
|
||||
Args: []string{"touch", flagFile},
|
||||
Env: []string{},
|
||||
Dir: "/",
|
||||
},
|
||||
ExitStatus: -1,
|
||||
}
|
||||
|
||||
// update the active config
|
||||
extraconfig.Encode(sink, cfg)
|
||||
|
||||
// trigger tether reload
|
||||
tthr.Reload()
|
||||
|
||||
// block until tether exits - exit within test timeout is a pass given the pairing of processes in the test
|
||||
<-mocker.Cleaned
|
||||
|
||||
// update config - into existing data
|
||||
extraconfig.Decode(src, &cfg)
|
||||
|
||||
// check that nonfatal error was as expected
|
||||
status := cfg.Execs["nonfatal"].Started
|
||||
assert.Equal(t, "stat /not/there: no such file or directory", status, "Expected nonfatal status to have a command not found error message")
|
||||
status = cfg.Execs["touch"].Started
|
||||
assert.Equal(t, "true", status, "Expected touch status to be clean")
|
||||
exit := cfg.Execs["touch"].ExitStatus
|
||||
assert.Equal(t, 0, exit, "Expected touch exit status to be success")
|
||||
|
||||
}
|
||||
|
||||
func TestExecHalt(t *testing.T) {
|
||||
_, mocker := testSetup(t)
|
||||
|
||||
defer testTeardown(t, mocker)
|
||||
|
||||
dir, err := ioutil.TempDir("", "tetherTestExec")
|
||||
if !assert.Nil(t, err, "Unable to create temp file") {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
flagFile := path.Join(dir, "flagfile")
|
||||
defer os.RemoveAll(flagFile)
|
||||
|
||||
cfg := executor.ExecutorConfig{
|
||||
ExecutorConfigCommon: executor.ExecutorConfigCommon{
|
||||
ID: "primary",
|
||||
Name: "tether_test_executor",
|
||||
},
|
||||
|
||||
Sessions: map[string]*executor.SessionConfig{
|
||||
"primary": {
|
||||
Common: executor.Common{
|
||||
ID: "primary",
|
||||
Name: "tether_test_session",
|
||||
},
|
||||
Tty: true,
|
||||
Active: true,
|
||||
OpenStdin: true,
|
||||
|
||||
Cmd: executor.Cmd{
|
||||
// test abs path
|
||||
Path: "/bin/bash",
|
||||
Args: []string{"/bin/bash", "-c", fmt.Sprintf("until /usr/bin/test -e %s;do /bin/sleep 0.1;done", flagFile)},
|
||||
Env: []string{},
|
||||
Dir: "/",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tthr, src, sink := StartTether(t, &cfg, mocker)
|
||||
|
||||
<-mocker.Started
|
||||
|
||||
// at this point the primary process should be up and running, so grab the pid
|
||||
result := ExecutorConfig{}
|
||||
extraconfig.Decode(src, &result)
|
||||
|
||||
for result.Sessions["primary"].Started != "true" {
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
extraconfig.Decode(src, &result)
|
||||
}
|
||||
|
||||
// reconfigure the primary process to make it inactive
|
||||
cfg.Sessions["primary"].Active = false
|
||||
|
||||
// update the active config
|
||||
extraconfig.Encode(sink, cfg)
|
||||
|
||||
// trigger tether reload
|
||||
tthr.Reload()
|
||||
|
||||
// block until tether exits - exit within test timeout is a pass given we're deactivating the primary
|
||||
<-mocker.Cleaned
|
||||
|
||||
// process should have been killed via signal due to deactivation
|
||||
extraconfig.Decode(src, &result)
|
||||
assert.Equal(t, -1, result.Sessions["primary"].ExitStatus, "Expected exit code for primary to indicate signal death")
|
||||
}
|
||||
78
vendor/github.com/vmware/vic/lib/tether/interfaces.go
generated
vendored
Normal file
78
vendor/github.com/vmware/vic/lib/tether/interfaces.go
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tether
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/vmware/vic/pkg/dio"
|
||||
)
|
||||
|
||||
// UtilityFn is the sigature of a function that can be used to launch a utility process
|
||||
type UtilityFn func() (*os.Process, error)
|
||||
|
||||
// Operations defines the set of operations that Tether depends upon. These are split out for:
|
||||
// * portability
|
||||
// * dependency injection (primarily for testing)
|
||||
// * behavioural control (e.g. what behaviour is required when a session exits)
|
||||
type Operations interface {
|
||||
Setup(Config) error
|
||||
Cleanup() error
|
||||
// Log returns the tether debug log writer
|
||||
Log() (io.Writer, error)
|
||||
|
||||
SetHostname(hostname string, aliases ...string) error
|
||||
SetupFirewall(ctx context.Context, config *ExecutorConfig) error
|
||||
Apply(endpoint *NetworkEndpoint) error
|
||||
MountLabel(ctx context.Context, label, target string) error
|
||||
MountTarget(ctx context.Context, source url.URL, target string, mountOptions string) error
|
||||
CopyExistingContent(source string) error
|
||||
Fork() error
|
||||
// Returns two DynamicMultiWriters for stdout and stderr
|
||||
SessionLog(session *SessionConfig) (dio.DynamicMultiWriter, dio.DynamicMultiWriter, error)
|
||||
// Returns a function to invoke after the session state has been persisted
|
||||
HandleSessionExit(config *ExecutorConfig, session *SessionConfig) func()
|
||||
ProcessEnv(env []string) []string
|
||||
// LaunchUtility starts a process and provides a way to block on completion and retrieve
|
||||
// it's exit code. This is needed to co-exist with a childreaper.
|
||||
LaunchUtility(UtilityFn) (<-chan int, error)
|
||||
// HandleUtilityExit will process the utility exit. If the pid cannot be matched to a launched
|
||||
// utility process then this returns false and does nothing.
|
||||
HandleUtilityExit(pid, exitCode int) bool
|
||||
}
|
||||
|
||||
// Tether presents the consumption interface for code needing to run a tether
|
||||
type Tether interface {
|
||||
Start() error
|
||||
Stop() error
|
||||
Reload()
|
||||
Register(name string, ext Extension)
|
||||
}
|
||||
|
||||
// Extension is a very simple extension interface for supporting code that need to be
|
||||
// notified when the configuration is reloaded.
|
||||
type Extension interface {
|
||||
Start() error
|
||||
Reload(config *ExecutorConfig) error
|
||||
Stop() error
|
||||
}
|
||||
|
||||
type Config interface {
|
||||
UpdateNetworkEndpoint(e *NetworkEndpoint) error
|
||||
Flush() error
|
||||
}
|
||||
113
vendor/github.com/vmware/vic/lib/tether/mocks.go
generated
vendored
Normal file
113
vendor/github.com/vmware/vic/lib/tether/mocks.go
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tether
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/vic/lib/etcconf"
|
||||
)
|
||||
|
||||
type MockSyscall struct{}
|
||||
|
||||
func (m MockSyscall) Mount(source string, target string, fstype string, flags uintptr, data string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m MockSyscall) Sethostname(_ []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m MockSyscall) Symlink(_, _ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m MockSyscall) Unmount(_ string, _ int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type MockHosts struct{}
|
||||
|
||||
func (h MockHosts) Load() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h MockHosts) Save() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h MockHosts) Copy(conf etcconf.Conf) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h MockHosts) SetHost(_ string, _ net.IP) {
|
||||
}
|
||||
|
||||
func (h MockHosts) RemoveHost(_ string) {
|
||||
}
|
||||
|
||||
func (h MockHosts) RemoveAll() {
|
||||
}
|
||||
|
||||
func (h MockHosts) HostIP(_ string) []net.IP {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h MockHosts) Path() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type MockResolvConf struct{}
|
||||
|
||||
func (h MockResolvConf) Load() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h MockResolvConf) Save() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h MockResolvConf) Copy(conf etcconf.Conf) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h MockResolvConf) AddNameservers(...net.IP) {
|
||||
}
|
||||
|
||||
func (h MockResolvConf) RemoveNameservers(...net.IP) {
|
||||
}
|
||||
|
||||
func (h MockResolvConf) Nameservers() []net.IP {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h MockResolvConf) Attempts() uint {
|
||||
return etcconf.DefaultAttempts
|
||||
}
|
||||
|
||||
func (h MockResolvConf) Timeout() time.Duration {
|
||||
return etcconf.DefaultTimeout
|
||||
}
|
||||
|
||||
func (h MockResolvConf) SetAttempts(uint) {
|
||||
}
|
||||
|
||||
func (h MockResolvConf) SetTimeout(time.Duration) {
|
||||
}
|
||||
|
||||
func (h MockResolvConf) Path() string {
|
||||
return ""
|
||||
}
|
||||
170
vendor/github.com/vmware/vic/lib/tether/msgs/messages.go
generated
vendored
Normal file
170
vendor/github.com/vmware/vic/lib/tether/msgs/messages.go
generated
vendored
Normal file
@@ -0,0 +1,170 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package msgs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// All of the messages passed over the ssh channel/global mux are (or will be)
|
||||
// defined here.
|
||||
|
||||
type Message interface {
|
||||
// Returns the message name
|
||||
RequestType() string
|
||||
|
||||
// Marshalled version of the message
|
||||
Marshal() []byte
|
||||
|
||||
// Unmarshal unpacks the message
|
||||
Unmarshal([]byte) error
|
||||
}
|
||||
|
||||
// WindowChangeMsg the RFC4254 struct
|
||||
const WindowChangeReq = "window-change"
|
||||
|
||||
type WindowChangeMsg struct {
|
||||
Columns uint32
|
||||
Rows uint32
|
||||
WidthPx uint32
|
||||
HeightPx uint32
|
||||
}
|
||||
|
||||
func (wc *WindowChangeMsg) RequestType() string {
|
||||
return WindowChangeReq
|
||||
}
|
||||
|
||||
func (wc *WindowChangeMsg) Marshal() []byte {
|
||||
return ssh.Marshal(*wc)
|
||||
}
|
||||
|
||||
func (wc *WindowChangeMsg) Unmarshal(payload []byte) error {
|
||||
return ssh.Unmarshal(payload, wc)
|
||||
}
|
||||
|
||||
var (
|
||||
Signals = map[ssh.Signal]int{
|
||||
ssh.SIGABRT: 6,
|
||||
ssh.SIGALRM: 14,
|
||||
ssh.SIGFPE: 8,
|
||||
ssh.SIGHUP: 1,
|
||||
ssh.SIGILL: 4,
|
||||
ssh.SIGINT: 2,
|
||||
ssh.SIGKILL: 9,
|
||||
ssh.SIGPIPE: 13,
|
||||
ssh.SIGQUIT: 3,
|
||||
ssh.SIGSEGV: 11,
|
||||
ssh.SIGTERM: 15,
|
||||
ssh.SIGUSR1: 10,
|
||||
ssh.SIGUSR2: 12,
|
||||
}
|
||||
)
|
||||
|
||||
// PingMsg
|
||||
const PingReq = "ping"
|
||||
const PingMsg = "Ping"
|
||||
|
||||
const UnblockReq = "unblock"
|
||||
const UnblockMsg = "Unblock"
|
||||
|
||||
// VersionMsg
|
||||
const VersionReq = "version"
|
||||
|
||||
type VersionMsg struct {
|
||||
Version uint32
|
||||
}
|
||||
|
||||
func (s *VersionMsg) RequestType() string {
|
||||
return VersionReq
|
||||
}
|
||||
|
||||
func (s *VersionMsg) Marshal() []byte {
|
||||
return ssh.Marshal(*s)
|
||||
}
|
||||
|
||||
func (s *VersionMsg) Unmarshal(payload []byte) error {
|
||||
return ssh.Unmarshal(payload, s)
|
||||
}
|
||||
|
||||
// SignalMsg
|
||||
const SignalReq = "signal"
|
||||
|
||||
type SignalMsg struct {
|
||||
Signal ssh.Signal
|
||||
}
|
||||
|
||||
func (s *SignalMsg) RequestType() string {
|
||||
return SignalReq
|
||||
}
|
||||
|
||||
func (s *SignalMsg) Marshal() []byte {
|
||||
return ssh.Marshal(*s)
|
||||
}
|
||||
|
||||
func (s *SignalMsg) Unmarshal(payload []byte) error {
|
||||
return ssh.Unmarshal(payload, s)
|
||||
}
|
||||
|
||||
func (s *SignalMsg) Signum() int {
|
||||
return Signals[s.Signal]
|
||||
}
|
||||
|
||||
func (s *SignalMsg) FromString(name string) error {
|
||||
num, err := strconv.Atoi(name)
|
||||
if err == nil {
|
||||
for sig, val := range Signals {
|
||||
if num == val {
|
||||
s.Signal = sig
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
name = strings.TrimPrefix(strings.ToUpper(name), "SIG")
|
||||
|
||||
s.Signal = ssh.Signal(name)
|
||||
_, ok := Signals[s.Signal]
|
||||
if !ok {
|
||||
return fmt.Errorf("unsupported signal name: %q", name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloseStdinMsg
|
||||
const CloseStdinReq = "close-stdin"
|
||||
|
||||
// ContainersMsg
|
||||
const ContainersReq = "container-ids"
|
||||
|
||||
type ContainersMsg struct {
|
||||
IDs []string
|
||||
}
|
||||
|
||||
func (s *ContainersMsg) RequestType() string {
|
||||
return ContainersReq
|
||||
}
|
||||
|
||||
func (s *ContainersMsg) Marshal() []byte {
|
||||
return ssh.Marshal(*s)
|
||||
}
|
||||
|
||||
func (s *ContainersMsg) Unmarshal(payload []byte) error {
|
||||
return ssh.Unmarshal(payload, s)
|
||||
}
|
||||
85
vendor/github.com/vmware/vic/lib/tether/msgs/messages_test.go
generated
vendored
Normal file
85
vendor/github.com/vmware/vic/lib/tether/msgs/messages_test.go
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package msgs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/lib/migration/feature"
|
||||
)
|
||||
|
||||
func TestWindowChange(t *testing.T) {
|
||||
s := &WindowChangeMsg{1, 2, 3, 4}
|
||||
|
||||
assert.Equal(t, s.RequestType(), WindowChangeReq)
|
||||
|
||||
tmp := s.Marshal()
|
||||
out := &WindowChangeMsg{}
|
||||
out.Unmarshal(tmp)
|
||||
|
||||
assert.Equal(t, s, out)
|
||||
}
|
||||
|
||||
func TestSignal(t *testing.T) {
|
||||
s := &SignalMsg{"HUP"}
|
||||
|
||||
assert.Equal(t, s.RequestType(), SignalReq)
|
||||
|
||||
tmp := s.Marshal()
|
||||
out := &SignalMsg{}
|
||||
out.Unmarshal(tmp)
|
||||
|
||||
assert.Equal(t, s, out)
|
||||
|
||||
for _, name := range []string{"SIGQUIT", "QUIT", "quit", "3"} {
|
||||
err := out.FromString(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, name := range []string{"SIGNOPE", "nope", "0", "-1"} {
|
||||
err := out.FromString(name)
|
||||
if err == nil {
|
||||
t.Errorf("expected error parsing %q", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainers(t *testing.T) {
|
||||
s := &ContainersMsg{IDs: []string{"foo", "bar", "baz"}}
|
||||
|
||||
assert.Equal(t, s.RequestType(), ContainersReq)
|
||||
|
||||
tmp := s.Marshal()
|
||||
out := &ContainersMsg{}
|
||||
out.Unmarshal(tmp)
|
||||
|
||||
assert.Equal(t, s, out)
|
||||
}
|
||||
|
||||
func TestVersion(t *testing.T) {
|
||||
s := &VersionMsg{Version: feature.MaxPluginVersion - 1}
|
||||
|
||||
assert.Equal(t, s.RequestType(), VersionReq)
|
||||
|
||||
tmp := s.Marshal()
|
||||
out := &VersionMsg{}
|
||||
out.Unmarshal(tmp)
|
||||
|
||||
assert.Equal(t, s, out)
|
||||
}
|
||||
245
vendor/github.com/vmware/vic/lib/tether/net_linux_test.go
generated
vendored
Normal file
245
vendor/github.com/vmware/vic/lib/tether/net_linux_test.go
generated
vendored
Normal file
@@ -0,0 +1,245 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build linux
|
||||
|
||||
package tether
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/lib/config/executor"
|
||||
"github.com/vmware/vic/lib/etcconf"
|
||||
"github.com/vmware/vic/pkg/vsphere/extraconfig"
|
||||
)
|
||||
|
||||
// Utility method to add an interface to Mocked
|
||||
// This assigns the interface name and returns the "slot" as a string
|
||||
func AddInterface(name string, mocker *Mocker) string {
|
||||
mocker.maxSlot++
|
||||
|
||||
mocker.Interfaces[name] = &Interface{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: name,
|
||||
Index: mocker.maxSlot,
|
||||
},
|
||||
Up: true,
|
||||
}
|
||||
|
||||
return strconv.Itoa(mocker.maxSlot)
|
||||
}
|
||||
|
||||
func TestSetIpAddress(t *testing.T) {
|
||||
_, mocker := testSetup(t)
|
||||
defer testTeardown(t, mocker)
|
||||
|
||||
hFile, err := ioutil.TempFile("", "vic_set_ip_test_hosts")
|
||||
if err != nil {
|
||||
t.Errorf("Failed to create tmp hosts file: %s", err)
|
||||
}
|
||||
rFile, err := ioutil.TempFile("", "vic_set_ip_test_resolv")
|
||||
if err != nil {
|
||||
t.Errorf("Failed to create tmp resolv file: %s", err)
|
||||
}
|
||||
|
||||
// give us a hosts file we can modify
|
||||
defer func(hosts etcconf.Hosts, resolv etcconf.ResolvConf) {
|
||||
Sys.Hosts = hosts
|
||||
Sys.ResolvConf = resolv
|
||||
}(Sys.Hosts, Sys.ResolvConf)
|
||||
|
||||
Sys.Hosts = etcconf.NewHosts(hFile.Name())
|
||||
Sys.ResolvConf = etcconf.NewResolvConf(rFile.Name())
|
||||
|
||||
bridge := AddInterface("eth1", mocker)
|
||||
public := AddInterface("eth2", mocker)
|
||||
|
||||
secondIP, _ := netlink.ParseIPNet("172.16.0.10/24")
|
||||
gwIP, _ := netlink.ParseIPNet("172.16.0.1/24")
|
||||
cfg := executor.ExecutorConfig{
|
||||
ExecutorConfigCommon: executor.ExecutorConfigCommon{
|
||||
ID: "ipconfig",
|
||||
Name: "tether_test_executor",
|
||||
},
|
||||
Networks: map[string]*executor.NetworkEndpoint{
|
||||
"bridge": {
|
||||
Common: executor.Common{
|
||||
ID: bridge,
|
||||
// interface rename
|
||||
Name: "bridge",
|
||||
},
|
||||
Network: executor.ContainerNetwork{
|
||||
Common: executor.Common{
|
||||
Name: "bridge",
|
||||
},
|
||||
Default: true,
|
||||
Gateway: *gwIP,
|
||||
},
|
||||
Static: true,
|
||||
IP: &net.IPNet{
|
||||
IP: localhost,
|
||||
Mask: lmask.Mask,
|
||||
},
|
||||
},
|
||||
"cnet": {
|
||||
Common: executor.Common{
|
||||
ID: bridge,
|
||||
// no interface rename
|
||||
},
|
||||
Network: executor.ContainerNetwork{
|
||||
Common: executor.Common{
|
||||
Name: "cnet",
|
||||
},
|
||||
},
|
||||
Static: true,
|
||||
IP: secondIP,
|
||||
},
|
||||
"public": {
|
||||
Common: executor.Common{
|
||||
ID: public,
|
||||
// interface rename
|
||||
Name: "public",
|
||||
},
|
||||
Network: executor.ContainerNetwork{
|
||||
Common: executor.Common{
|
||||
Name: "public",
|
||||
},
|
||||
},
|
||||
Static: true,
|
||||
IP: &net.IPNet{
|
||||
IP: gateway,
|
||||
Mask: gmask.Mask,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tthr, _, _ := StartTether(t, &cfg, mocker)
|
||||
|
||||
defer func() {
|
||||
// prevent indefinite wait in tether - normally session exit would trigger this
|
||||
tthr.Stop()
|
||||
|
||||
// wait for tether to exit
|
||||
<-mocker.Cleaned
|
||||
}()
|
||||
|
||||
<-mocker.Started
|
||||
|
||||
assert.NotNil(t, mocker.Interfaces["bridge"], "Expected bridge network if endpoints applied correctly")
|
||||
// check addresses
|
||||
bIface, _ := mocker.Interfaces["bridge"].(*Interface)
|
||||
assert.NotNil(t, bIface)
|
||||
|
||||
assert.Equal(t, 2, len(bIface.Addrs), "Expected two addresses on bridge interface")
|
||||
|
||||
eIface, _ := mocker.Interfaces["public"].(*Interface)
|
||||
assert.NotNil(t, eIface)
|
||||
|
||||
assert.Equal(t, 1, len(eIface.Addrs), "Expected one address on public interface")
|
||||
}
|
||||
|
||||
func TestOutboundRuleAndCmd(t *testing.T) {
|
||||
_, mocker := testSetup(t)
|
||||
defer testTeardown(t, mocker)
|
||||
|
||||
// give us a hosts file we can modify
|
||||
defer func(hosts etcconf.Hosts, resolv etcconf.ResolvConf) {
|
||||
Sys.Hosts = hosts
|
||||
Sys.ResolvConf = resolv
|
||||
}(Sys.Hosts, Sys.ResolvConf)
|
||||
|
||||
fmt.Printf("Test root dir: %s, hosts: %s\n", Sys.Root, Sys.Hosts.Path())
|
||||
|
||||
bridge := AddInterface("eth1", mocker)
|
||||
|
||||
gwIP, _ := netlink.ParseIPNet("172.16.0.1/24")
|
||||
|
||||
cfg := executor.ExecutorConfig{
|
||||
ExecutorConfigCommon: executor.ExecutorConfigCommon{
|
||||
ID: "outboundrule",
|
||||
Name: "tether_test_executor",
|
||||
},
|
||||
Diagnostics: executor.Diagnostics{
|
||||
DebugLevel: 3,
|
||||
},
|
||||
Networks: map[string]*executor.NetworkEndpoint{
|
||||
"bridge": {
|
||||
Common: executor.Common{
|
||||
ID: bridge,
|
||||
// interface rename
|
||||
Name: "bridge",
|
||||
},
|
||||
Network: executor.ContainerNetwork{
|
||||
Common: executor.Common{
|
||||
Name: "bridge",
|
||||
},
|
||||
Default: true,
|
||||
Gateway: *gwIP,
|
||||
},
|
||||
Static: true,
|
||||
IP: &net.IPNet{
|
||||
IP: localhost,
|
||||
Mask: lmask.Mask,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Sessions: map[string]*executor.SessionConfig{
|
||||
"outboundrule": {
|
||||
Common: executor.Common{
|
||||
ID: "outboundrule",
|
||||
Name: "tether_test_session",
|
||||
},
|
||||
Tty: false,
|
||||
Active: true,
|
||||
|
||||
Cmd: executor.Cmd{
|
||||
// test relative path
|
||||
Path: "./date",
|
||||
Args: []string{"./date", "--reference=/"},
|
||||
Env: []string{"PATH="},
|
||||
Dir: "/bin",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, src, _ := StartTether(t, &cfg, mocker)
|
||||
|
||||
fmt.Println("Waiting for tether start")
|
||||
<-mocker.Started
|
||||
|
||||
// wait for tether to exit
|
||||
fmt.Println("Waiting for tether exit")
|
||||
<-mocker.Cleaned
|
||||
|
||||
result := ExecutorConfig{}
|
||||
extraconfig.Decode(src, &result)
|
||||
|
||||
// confirm command ran - necessary to detect early exit due to net config error
|
||||
assert.Equal(t, "true", result.Sessions["outboundrule"].Started, "Expected command to have been started successfully")
|
||||
assert.Equal(t, 0, result.Sessions["outboundrule"].ExitStatus, "Expected command to have exited cleanly")
|
||||
|
||||
// confirm outbound rules configured
|
||||
|
||||
}
|
||||
72
vendor/github.com/vmware/vic/lib/tether/net_test.go
generated
vendored
Normal file
72
vendor/github.com/vmware/vic/lib/tether/net_test.go
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tether
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
|
||||
"github.com/vmware/vic/lib/config/executor"
|
||||
)
|
||||
|
||||
func TestSetHostname(t *testing.T) {
|
||||
_, mocker := testSetup(t)
|
||||
defer testTeardown(t, mocker)
|
||||
|
||||
cfg := executor.ExecutorConfig{
|
||||
ExecutorConfigCommon: executor.ExecutorConfigCommon{
|
||||
ID: "sethostname",
|
||||
Name: "tether_test_executor",
|
||||
},
|
||||
}
|
||||
|
||||
tthr, _, _ := StartTether(t, &cfg, mocker)
|
||||
|
||||
<-mocker.Started
|
||||
|
||||
// prevent indefinite wait in tether - normally session exit would trigger this
|
||||
tthr.Stop()
|
||||
|
||||
// wait for tether to exit
|
||||
<-mocker.Cleaned
|
||||
|
||||
expected := stringid.TruncateID(cfg.ID)
|
||||
if mocker.Hostname != expected {
|
||||
t.Errorf("expected: %s, actual: %s", expected, mocker.Hostname)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoNetwork(t *testing.T) {
|
||||
_, mocker := testSetup(t)
|
||||
defer testTeardown(t, mocker)
|
||||
|
||||
cfg := executor.ExecutorConfig{
|
||||
ExecutorConfigCommon: executor.ExecutorConfigCommon{
|
||||
ID: "ipconfig",
|
||||
Name: "tether_test_executor",
|
||||
},
|
||||
}
|
||||
|
||||
tthr, _, _ := StartTether(t, &cfg, mocker)
|
||||
|
||||
<-mocker.Started
|
||||
|
||||
// prevent indefinite wait in tether - normally session exit would trigger this
|
||||
tthr.Stop()
|
||||
|
||||
// wait for tether to exit
|
||||
<-mocker.Cleaned
|
||||
}
|
||||
213
vendor/github.com/vmware/vic/lib/tether/netfilter/netfilter_linux.go
generated
vendored
Normal file
213
vendor/github.com/vmware/vic/lib/tether/netfilter/netfilter_linux.go
generated
vendored
Normal file
@@ -0,0 +1,213 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package netfilter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"syscall"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/vmware/vic/lib/tether"
|
||||
)
|
||||
|
||||
//
|
||||
// # default
|
||||
// # iptables -A INPUT -m state --state ESTABLISHED -i eth0 -j ACCEPT
|
||||
|
||||
// # Expose()
|
||||
// # iptables -A INPUT -p tcp --dport 7 -i eth0 -j ACCEPT
|
||||
//
|
||||
// # default on non-bridge
|
||||
// # iptables -A INPUT -i eth0 -j REJECT
|
||||
//
|
||||
// # iptables --list
|
||||
//
|
||||
// Chain INPUT (policy ACCEPT)
|
||||
// target prot opt source destination
|
||||
// ACCEPT all -- anywhere anywhere state ESTABLISHED
|
||||
// ACCEPT tcp -- anywhere anywhere tcp dpt:echo
|
||||
// REJECT all -- anywhere anywhere reject-with icmp-port-unreachable
|
||||
//
|
||||
// Chain FORWARD (policy ACCEPT)
|
||||
// target prot opt source destination
|
||||
//
|
||||
// Chain OUTPUT (policy ACCEPT)
|
||||
// target prot opt source destination
|
||||
//
|
||||
|
||||
type Chain string
|
||||
type State string
|
||||
type Protocol string
|
||||
type Target string
|
||||
type Table string
|
||||
type ICMPType string
|
||||
|
||||
const (
|
||||
Filter = Table("filter")
|
||||
Nat = Table("nat")
|
||||
Mangle = Table("mangle")
|
||||
Raw = Table("raw")
|
||||
Security = Table("security")
|
||||
|
||||
Prerouting = Chain("PREROUTING")
|
||||
Input = Chain("INPUT")
|
||||
Forward = Chain("FORWARD")
|
||||
Output = Chain("OUTPUT")
|
||||
|
||||
Invalid = State("INVALID")
|
||||
Established = State("ESTABLISHED")
|
||||
New = State("NEW")
|
||||
Related = State("RELATED")
|
||||
Untracked = State("UNTRACKED")
|
||||
|
||||
TCP = Protocol("tcp")
|
||||
UDP = Protocol("udp")
|
||||
ICMP = Protocol("icmp")
|
||||
|
||||
Drop = Target("DROP")
|
||||
Accept = Target("ACCEPT")
|
||||
Queue = Target("QUEUE")
|
||||
NoQueue = Target("NFQUEUE")
|
||||
Reject = Target("REJECT")
|
||||
Redirect = Target("REDIRECT")
|
||||
|
||||
EchoRequest = ICMPType("echo-request")
|
||||
EchoReply = ICMPType("echo-reply")
|
||||
)
|
||||
|
||||
type Rule struct {
|
||||
Table
|
||||
Chain
|
||||
States []State
|
||||
ICMPType
|
||||
|
||||
Protocol
|
||||
Target
|
||||
|
||||
Interface string
|
||||
SourceAddresses []string
|
||||
FromPort string
|
||||
SrcPort string
|
||||
ToPort string
|
||||
}
|
||||
|
||||
func (r *Rule) Commit(ctx context.Context) tether.UtilityFn {
|
||||
return iptables(ctx, r.args())
|
||||
}
|
||||
|
||||
func (r *Rule) args() []string {
|
||||
var args []string
|
||||
|
||||
if r.Table != "" {
|
||||
args = append(args, "-t", string(r.Table))
|
||||
}
|
||||
|
||||
if r.Chain == Input || r.Chain == Output {
|
||||
args = append(args, "-A", "VIC")
|
||||
} else {
|
||||
args = append(args, "-A", string(r.Chain))
|
||||
}
|
||||
|
||||
if r.Protocol != "" {
|
||||
args = append(args, "-p", string(r.Protocol))
|
||||
}
|
||||
|
||||
if r.ICMPType != "" {
|
||||
args = append(args, "--icmp-type", string(r.ICMPType))
|
||||
}
|
||||
|
||||
if len(r.SourceAddresses) > 0 {
|
||||
args = append(args, "-s", strings.Join(r.SourceAddresses, ","))
|
||||
}
|
||||
|
||||
if r.FromPort != "" {
|
||||
args = append(args, "--dport", r.FromPort)
|
||||
}
|
||||
|
||||
if r.SrcPort != "" {
|
||||
args = append(args, "--sport", r.SrcPort)
|
||||
}
|
||||
|
||||
if len(r.States) > 0 {
|
||||
args = append(args, "-m", "state", "--state", joinStates(r.States))
|
||||
}
|
||||
|
||||
if r.Interface != "" {
|
||||
if r.Chain == Output {
|
||||
args = append(args, "-o", r.Interface)
|
||||
} else {
|
||||
args = append(args, "-i", r.Interface)
|
||||
}
|
||||
}
|
||||
|
||||
args = append(args, "-j", string(r.Target))
|
||||
|
||||
if r.ToPort != "" {
|
||||
args = append(args, "--to-port", r.ToPort)
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
func iptables(ctx context.Context, args []string) tether.UtilityFn {
|
||||
logrus.Infof("Execing iptables %q", args)
|
||||
|
||||
// #nosec: Subprocess launching with variable
|
||||
cmd := &exec.Cmd{
|
||||
Path: "/lib64/ld-linux-x86-64.so.2",
|
||||
Dir: "/",
|
||||
Args: append([]string{"/lib64/ld-linux-x86-64.so.2", "/iptables"}, args...),
|
||||
SysProcAttr: &syscall.SysProcAttr{
|
||||
Chroot: "/.tether",
|
||||
},
|
||||
}
|
||||
|
||||
return func() (*os.Process, error) {
|
||||
return os.StartProcess(cmd.Path, cmd.Args, &os.ProcAttr{
|
||||
Dir: cmd.Dir,
|
||||
Sys: cmd.SysProcAttr,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Flush(ctx context.Context, chain string) tether.UtilityFn {
|
||||
args := []string{"-F"}
|
||||
if chain != "" {
|
||||
args = append(args, chain)
|
||||
}
|
||||
|
||||
return iptables(ctx, args)
|
||||
}
|
||||
|
||||
func Return(ctx context.Context, chain string) tether.UtilityFn {
|
||||
return iptables(ctx, []string{"-A", chain, "-j", "RETURN"})
|
||||
}
|
||||
|
||||
func Policy(ctx context.Context, chain Chain, target Target) tether.UtilityFn {
|
||||
return iptables(ctx, []string{"-P", string(chain), string(target)})
|
||||
}
|
||||
|
||||
func joinStates(states []State) string {
|
||||
tmp := make([]string, len(states))
|
||||
for i, v := range states {
|
||||
tmp[i] = string(v)
|
||||
}
|
||||
return strings.Join(tmp, ",")
|
||||
}
|
||||
79
vendor/github.com/vmware/vic/lib/tether/netfilter/netfilter_linux_test.go
generated
vendored
Normal file
79
vendor/github.com/vmware/vic/lib/tether/netfilter/netfilter_linux_test.go
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package netfilter
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type input struct {
|
||||
r *Rule
|
||||
expected string
|
||||
}
|
||||
|
||||
func TestArgs(t *testing.T) {
|
||||
input := []input{
|
||||
{
|
||||
&Rule{
|
||||
Chain: Input,
|
||||
States: []State{Established},
|
||||
Interface: "external",
|
||||
Target: Accept,
|
||||
},
|
||||
"-A VIC -m state --state ESTABLISHED -i external -j ACCEPT",
|
||||
},
|
||||
{
|
||||
&Rule{
|
||||
Chain: Input,
|
||||
Protocol: TCP,
|
||||
FromPort: "7",
|
||||
Interface: "external",
|
||||
Target: Accept,
|
||||
},
|
||||
"-A VIC -p tcp --dport 7 -i external -j ACCEPT",
|
||||
},
|
||||
{
|
||||
&Rule{
|
||||
Chain: Input,
|
||||
Interface: "external",
|
||||
Target: Reject,
|
||||
},
|
||||
"-A VIC -i external -j REJECT",
|
||||
},
|
||||
{
|
||||
&Rule{
|
||||
Table: Nat,
|
||||
Chain: Prerouting,
|
||||
Interface: "external",
|
||||
Protocol: TCP,
|
||||
FromPort: "80",
|
||||
Target: Redirect,
|
||||
ToPort: "8080",
|
||||
},
|
||||
"-t nat -A PREROUTING -p tcp --dport 80 -i external -j REDIRECT --to-port 8080",
|
||||
},
|
||||
}
|
||||
|
||||
for _, rule := range input {
|
||||
args := rule.r.args()
|
||||
|
||||
if !assert.Equal(t, strings.Join(args, " "), rule.expected) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
1271
vendor/github.com/vmware/vic/lib/tether/ops_linux.go
generated
vendored
Normal file
1271
vendor/github.com/vmware/vic/lib/tether/ops_linux.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
308
vendor/github.com/vmware/vic/lib/tether/ops_linux_test.go
generated
vendored
Normal file
308
vendor/github.com/vmware/vic/lib/tether/ops_linux_test.go
generated
vendored
Normal file
@@ -0,0 +1,308 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build linux
|
||||
|
||||
package tether
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/user"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"os"
|
||||
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
type Interface struct {
|
||||
netlink.LinkAttrs
|
||||
Up bool
|
||||
Addrs []netlink.Addr
|
||||
}
|
||||
|
||||
func (t *Interface) Attrs() *netlink.LinkAttrs {
|
||||
return &t.LinkAttrs
|
||||
}
|
||||
|
||||
func (t *Interface) Type() string {
|
||||
return "mocked"
|
||||
}
|
||||
|
||||
func (t *Mocker) LinkByName(name string) (netlink.Link, error) {
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("Getting link by name %s", name)))
|
||||
|
||||
return t.Interfaces[name], nil
|
||||
}
|
||||
|
||||
func (t *Mocker) LinkSetName(link netlink.Link, name string) error {
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("Renaming %s to %s", link.Attrs().Name, name)))
|
||||
|
||||
iface := link.(*Interface)
|
||||
_, ok := t.Interfaces[name]
|
||||
if ok {
|
||||
return fmt.Errorf("Interface with name %s already exists", name)
|
||||
}
|
||||
|
||||
oldName := iface.Name
|
||||
iface.Name = name
|
||||
// make sure there's no period where the interface isn't "present"
|
||||
t.Interfaces[name] = iface
|
||||
delete(t.Interfaces, oldName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Mocker) LinkSetDown(link netlink.Link) error {
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("Bringing %s down", link.Attrs().Name)))
|
||||
|
||||
iface := link.(*Interface)
|
||||
iface.Up = false
|
||||
// TODO: should this drop addresses?
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Mocker) LinkSetUp(link netlink.Link) error {
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("Bringing %s up", link.Attrs().Name)))
|
||||
|
||||
iface := link.(*Interface)
|
||||
iface.Up = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Mocker) LinkSetAlias(link netlink.Link, alias string) error {
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("Adding alias %s to %s", alias, link.Attrs().Name)))
|
||||
|
||||
iface := link.(*Interface)
|
||||
iface.Alias = alias
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Mocker) AddrList(link netlink.Link, family int) ([]netlink.Addr, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
iface := link.(*Interface)
|
||||
return iface.Addrs, nil
|
||||
}
|
||||
|
||||
func (t *Mocker) AddrAdd(link netlink.Link, addr *netlink.Addr) error {
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("Adding %s to %s", addr.String(), link.Attrs().Name)))
|
||||
|
||||
iface := link.(*Interface)
|
||||
|
||||
for _, adr := range iface.Addrs {
|
||||
if addr.IP.String() == adr.IP.String() {
|
||||
return syscall.EEXIST
|
||||
}
|
||||
}
|
||||
|
||||
iface.Addrs = append(iface.Addrs, *addr)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Mocker) AddrDel(link netlink.Link, addr *netlink.Addr) error {
|
||||
iface := link.(*Interface)
|
||||
|
||||
for i, adr := range iface.Addrs {
|
||||
if addr.IP.String() == adr.IP.String() {
|
||||
iface.Addrs = append(iface.Addrs[:i], iface.Addrs[i+1:]...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return syscall.EADDRNOTAVAIL
|
||||
}
|
||||
|
||||
func (t *Mocker) RouteAdd(route *netlink.Route) error {
|
||||
defer trace.End(trace.Begin("no implemented"))
|
||||
|
||||
// currently ignored
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Mocker) RouteDel(route *netlink.Route) error {
|
||||
defer trace.End(trace.Begin("no implemented"))
|
||||
|
||||
// currently ignored
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Mocker) RuleList(int) ([]netlink.Rule, error) {
|
||||
defer trace.End(trace.Begin("not implemented"))
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (t *Mocker) LinkBySlot(slot int32) (netlink.Link, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
id := int(slot)
|
||||
for _, intf := range t.Interfaces {
|
||||
if intf.Attrs().Index == id {
|
||||
return intf, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.New("no such interface")
|
||||
}
|
||||
|
||||
func TestSlotToPciPath(t *testing.T) {
|
||||
var tests = []struct {
|
||||
slot int32
|
||||
p string
|
||||
err error
|
||||
}{
|
||||
{0, path.Join(pciDevPath, "0000:00:00.0"), nil},
|
||||
{32, path.Join(pciDevPath, "0000:00:11.0", "0000:*:00.0"), nil},
|
||||
{33, path.Join(pciDevPath, "0000:00:11.0", "0000:*:01.0"), nil},
|
||||
{192, path.Join(pciDevPath, "0000:00:16.0", "0000:*:00.0"), nil},
|
||||
{1184, path.Join(pciDevPath, "0000:00:15.1", "0000:*:00.0"), nil},
|
||||
{1216, path.Join(pciDevPath, "0000:00:16.1", "0000:*:00.0"), nil},
|
||||
{1248, path.Join(pciDevPath, "0000:00:17.1", "0000:*:00.0"), nil},
|
||||
{1280, path.Join(pciDevPath, "0000:00:18.1", "0000:*:00.0"), nil},
|
||||
}
|
||||
|
||||
for _, te := range tests {
|
||||
p, err := slotToPCIPath(te.slot, 0)
|
||||
if te.err != nil {
|
||||
if err == nil {
|
||||
t.Fatalf("slotToPCIPath(%d) => (%#v, %#v), want (%s, nil)", te.slot, p, err, te.p)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if p != te.p {
|
||||
t.Fatalf("slotToPCIPath(%d) => (%#v, %#v), want (%s, nil)", te.slot, p, err, te.p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUserSysProcAttr(t *testing.T) {
|
||||
curr, err := user.Current()
|
||||
if err != nil {
|
||||
t.Logf("Failed to get current user, skip test")
|
||||
return
|
||||
}
|
||||
currUID, _ := strconv.Atoi(curr.Uid)
|
||||
currGID, _ := strconv.Atoi(curr.Gid)
|
||||
|
||||
moreThanMax := strconv.Itoa(1 << 33)
|
||||
lessThanMin := "-100"
|
||||
|
||||
var tests = []struct {
|
||||
uid string
|
||||
gid string
|
||||
ruid int
|
||||
rgid int
|
||||
err error
|
||||
}{
|
||||
{"", "", 0, 0, nil},
|
||||
{"notexist", "notexist", 0, 0, errors.New("Unable to find user notexist")},
|
||||
{"notexist", curr.Gid, 0, 0, errors.New("Unable to find user notexist")},
|
||||
{"", "notexist", 0, 0, errors.New("Unable to find group notexist")},
|
||||
{"0", "notexist", 0, 0, errors.New("Unable to find group notexist")},
|
||||
{curr.Uid, "notexist", 0, 0, errors.New("Unable to find group notexist")},
|
||||
{"2000000", "notexist", 0, 0, errors.New("Unable to find group notexist")},
|
||||
{curr.Username, "notexist", 0, 0, errors.New("Unable to find group notexist")},
|
||||
{curr.Username, "0", currUID, 0, nil},
|
||||
{curr.Uid, "1000", currUID, 1000, nil},
|
||||
{curr.Uid, curr.Gid, currUID, currGID, nil},
|
||||
{"root", curr.Gid, 0, currGID, nil},
|
||||
{"root", "root", 0, 0, nil},
|
||||
{moreThanMax, "root", 0, 0, errors.New("Uids and gids must be in range 0-2147483647")},
|
||||
{curr.Username, moreThanMax, 0, 0, errors.New("Uids and gids must be in range 0-2147483647")},
|
||||
{lessThanMin, "root", 0, 0, errors.New("Uids and gids must be in range 0-2147483647")},
|
||||
{curr.Username, lessThanMin, 0, 0, errors.New("Uids and gids must be in range 0-2147483647")},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Logf("uid: %s, gid: %s", test.uid, test.gid)
|
||||
user, err := getUserSysProcAttr(test.uid, test.gid)
|
||||
if err != nil {
|
||||
assert.True(t, test.err != nil, fmt.Sprintf("Should not have error %s", err))
|
||||
if !strings.Contains(err.Error(), test.err.Error()) {
|
||||
assert.True(t, false, fmt.Sprintf("error message mismatch, expected %s, actual %s", test.err, err.Error()))
|
||||
}
|
||||
continue
|
||||
}
|
||||
assert.True(t, test.err == nil, fmt.Sprintf("didn't get expected error: %s", test.err))
|
||||
if user == nil {
|
||||
assert.True(t, (test.ruid == 0 && test.rgid == 0), "returned user is nil, but expect not nil result: %d:%d", test.ruid, test.rgid)
|
||||
continue
|
||||
}
|
||||
assert.Equal(t, test.ruid, int(user.Credential.Uid), "returned user id mismatch")
|
||||
assert.Equal(t, test.rgid, int(user.Credential.Gid), "returned group id mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsEmpty(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "testisEmpty")
|
||||
assert.NoError(t, err)
|
||||
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
err = os.MkdirAll(dir+"/lost+found", 0644)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ok, err := isEmpty(dir)
|
||||
assert.True(t, ok)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = os.MkdirAll(dir+"/test1", 0644)
|
||||
assert.NoError(t, err)
|
||||
err = os.MkdirAll(dir+"/test2", 0644)
|
||||
assert.NoError(t, err)
|
||||
err = ioutil.WriteFile(dir+"/test3.txt", []byte{}, 0644)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ok, err = isEmpty(dir)
|
||||
assert.False(t, ok)
|
||||
assert.NoError(t, err)
|
||||
|
||||
}
|
||||
|
||||
func TestCreateBindSrcTarget(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "testCreateBindSrcTarget")
|
||||
assert.NoError(t, err)
|
||||
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// Create a file under an existing directory
|
||||
file := dir + "/test1"
|
||||
err = createBindSrcTarget(map[string]os.FileMode{file: 0644})
|
||||
assert.NoError(t, err, "createBindSrcTarget failed: %s", err)
|
||||
_, err = os.Stat(dir)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create a file under non-existent directory
|
||||
file = dir + "/testDir/test1"
|
||||
err = createBindSrcTarget(map[string]os.FileMode{file: 0644})
|
||||
assert.NoError(t, err, "createBindSrcTarget failed: %s", err)
|
||||
_, err = os.Stat(dir)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create an existing file
|
||||
err = createBindSrcTarget(map[string]os.FileMode{file: 0644})
|
||||
assert.NoError(t, err, "createBindSrcTarget failed: %s", err)
|
||||
}
|
||||
30
vendor/github.com/vmware/vic/lib/tether/shared/constants.go
generated
vendored
Normal file
30
vendor/github.com/vmware/vic/lib/tether/shared/constants.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package shared is intended for isolating constants and minor functions required
|
||||
// to be consistent across packages so as to avoid transitive package includes
|
||||
package shared
|
||||
|
||||
/* Constants used by tether for exchange outside of tether */
|
||||
const (
|
||||
DiskLabelQueryName = "disk-label"
|
||||
FilterSpecQueryName = "filter-spec"
|
||||
SkipRecurseQueryName = "skip-recurse"
|
||||
SkipDataQueryName = "skip-data"
|
||||
|
||||
// This string is referenced in isos/bootstrap/bootstrap and should be kept in sync.
|
||||
// Using a 0011 prefix caused the disk not to present, so have taken the 6000 prefix
|
||||
// that was being generated consistency when uuid left undefined.
|
||||
ScratchUUID = "60002233445566778899aabbccddeeff"
|
||||
)
|
||||
19
vendor/github.com/vmware/vic/lib/tether/shared/constants_linux.go
generated
vendored
Normal file
19
vendor/github.com/vmware/vic/lib/tether/shared/constants_linux.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package shared
|
||||
|
||||
const (
|
||||
pidFilePath = ".tether/run"
|
||||
)
|
||||
27
vendor/github.com/vmware/vic/lib/tether/shared/func_linux.go
generated
vendored
Normal file
27
vendor/github.com/vmware/vic/lib/tether/shared/func_linux.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package shared
|
||||
|
||||
import (
|
||||
"path"
|
||||
|
||||
"github.com/vmware/vic/lib/system"
|
||||
)
|
||||
|
||||
var Sys = system.New()
|
||||
|
||||
func PIDFileDir() string {
|
||||
return path.Join(Sys.Root, pidFilePath)
|
||||
}
|
||||
58
vendor/github.com/vmware/vic/lib/tether/signer.go
generated
vendored
Normal file
58
vendor/github.com/vmware/vic/lib/tether/signer.go
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tether
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type ContainerSigner struct {
|
||||
id string
|
||||
}
|
||||
|
||||
func (c *ContainerSigner) PublicKey() ssh.PublicKey {
|
||||
return *c
|
||||
}
|
||||
|
||||
// we're going to ignore everything for the moment as we're repurposing the host key for the id.
|
||||
// later we may use a genuine host key and an SSH out-of-band request to get the container id.
|
||||
func (c *ContainerSigner) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) {
|
||||
return &ssh.Signature{
|
||||
Format: "container-id",
|
||||
Blob: []byte{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c ContainerSigner) Type() string {
|
||||
return "container-id"
|
||||
}
|
||||
|
||||
func (c ContainerSigner) Marshal() []byte {
|
||||
return []byte(c.id)
|
||||
}
|
||||
|
||||
func (c ContainerSigner) Verify(data []byte, sig *ssh.Signature) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewSigner(id string) *ContainerSigner {
|
||||
signer := &ContainerSigner{
|
||||
id: id,
|
||||
}
|
||||
|
||||
return signer
|
||||
}
|
||||
1104
vendor/github.com/vmware/vic/lib/tether/tether.go
generated
vendored
Normal file
1104
vendor/github.com/vmware/vic/lib/tether/tether.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
403
vendor/github.com/vmware/vic/lib/tether/tether_linux.go
generated
vendored
Normal file
403
vendor/github.com/vmware/vic/lib/tether/tether_linux.go
generated
vendored
Normal file
@@ -0,0 +1,403 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tether
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/kr/pty"
|
||||
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
//https://github.com/golang/go/blob/master/src/syscall/zerrors_linux_arm64.go#L919
|
||||
SetChildSubreaper = 0x24
|
||||
|
||||
// in sync with lib/apiservers/portlayer/handlers/interaction_handler.go
|
||||
// 115200 bps is 14.4 KB/s so use that
|
||||
ioCopyBufferSize = 14 * 1024
|
||||
)
|
||||
|
||||
// Mkdev will hopefully get rolled into go.sys at some point
|
||||
func Mkdev(majorNumber int, minorNumber int) int {
|
||||
return (majorNumber << 8) | (minorNumber & 0xff) | ((minorNumber & 0xfff00) << 12)
|
||||
}
|
||||
|
||||
// ReloadConfig signals the current process, which triggers the signal handler
|
||||
// to reload the config.
|
||||
func ReloadConfig() error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
p, err := os.FindProcess(os.Getpid())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = p.Signal(syscall.SIGHUP); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// childReaper is used to handle events from child processes, including child exit.
|
||||
// If running as pid=1 then this means it handles zombie process reaping for orphaned children
|
||||
// as well as direct child processes.
|
||||
func (t *tether) childReaper() error {
|
||||
signal.Notify(t.incoming, syscall.SIGCHLD)
|
||||
|
||||
/*
|
||||
PR_SET_CHILD_SUBREAPER (since Linux 3.4)
|
||||
If arg2 is nonzero, set the "child subreaper" attribute of the
|
||||
calling process; if arg2 is zero, unset the attribute. When a
|
||||
process is marked as a child subreaper, all of the children
|
||||
that it creates, and their descendants, will be marked as
|
||||
having a subreaper. In effect, a subreaper fulfills the role
|
||||
of init(1) for its descendant processes. Upon termination of
|
||||
a process that is orphaned (i.e., its immediate parent has
|
||||
already terminated) and marked as having a subreaper, the
|
||||
nearest still living ancestor subreaper will receive a SIGCHLD
|
||||
signal and be able to wait(2) on the process to discover its
|
||||
termination status.
|
||||
*/
|
||||
if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, SetChildSubreaper, uintptr(1), 0); err != 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("Started reaping child processes")
|
||||
|
||||
go func() {
|
||||
var status syscall.WaitStatus
|
||||
flag := syscall.WNOHANG | syscall.WUNTRACED | syscall.WCONTINUED
|
||||
|
||||
for range t.incoming {
|
||||
func() {
|
||||
// general resiliency
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Fprintf(os.Stderr, "Recovered in childReaper: %s\n%s", r, debug.Stack())
|
||||
}
|
||||
}()
|
||||
|
||||
// reap until no more children to process
|
||||
for {
|
||||
log.Debugf("Inspecting children with status change")
|
||||
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
log.Warnf("Someone called shutdown, returning from child reaper")
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
pid, err := syscall.Wait4(-1, &status, flag, nil)
|
||||
// pid 0 means no processes wish to report status
|
||||
if pid == 0 || err == syscall.ECHILD {
|
||||
log.Debug("No more child processes to reap")
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("Wait4 got error: %v\n", err)
|
||||
break
|
||||
}
|
||||
|
||||
if !status.Exited() && !status.Signaled() {
|
||||
log.Debugf("Received notifcation about non-exit status change for %d: %d", pid, status)
|
||||
// no reaping or exit handling required
|
||||
continue
|
||||
}
|
||||
|
||||
exitCode := status.ExitStatus()
|
||||
log.Debugf("Reaped process %d, return code: %d", pid, exitCode)
|
||||
|
||||
session, ok := t.removeChildPid(pid)
|
||||
if ok {
|
||||
log.Debugf("Removed child pid: %d", pid)
|
||||
session.Lock()
|
||||
session.ExitStatus = exitCode
|
||||
|
||||
t.handleSessionExit(session)
|
||||
session.Unlock()
|
||||
continue
|
||||
}
|
||||
|
||||
ok = t.ops.HandleUtilityExit(pid, exitCode)
|
||||
if ok {
|
||||
log.Debugf("Remove utility pid: %d", pid)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Infof("Reaped zombie process PID %d", pid)
|
||||
}
|
||||
}()
|
||||
}
|
||||
log.Info("Stopped reaping child processes")
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tether) stopReaper() {
|
||||
defer trace.End(trace.Begin("Shutting down child reaping"))
|
||||
|
||||
// Ordering is important otherwise we may one goroutine closing, and the other goroutine is trying to write afterwards
|
||||
log.Debugf("Removing the signal notifier")
|
||||
signal.Reset(syscall.SIGCHLD)
|
||||
|
||||
// just closing the incoming channel is not going to stop the iteration
|
||||
// so we use the context cancellation to signal it
|
||||
t.cancel()
|
||||
|
||||
log.Debugf("Closing the reapers signal channel")
|
||||
close(t.incoming)
|
||||
}
|
||||
|
||||
func (t *tether) triggerReaper() {
|
||||
defer trace.End(trace.Begin("Triggering child reaping"))
|
||||
|
||||
t.incoming <- syscall.SIGCHLD
|
||||
}
|
||||
|
||||
func findExecutable(file string, chroot string) error {
|
||||
//log.Infof("***** Dumping directory %s", chroot)
|
||||
//listDirectory(chroot)
|
||||
//log.Infof("***** Dumping directory %s", path.Dir(file))
|
||||
//listDirectory(path.Dir(file))
|
||||
log.Infof("*** Stating file [%s], chroot [%s]", file, chroot)
|
||||
d, err := os.Stat(file)
|
||||
if err != nil {
|
||||
log.Infof("*** Stating file [%s] failed with error - %s", file, err.Error())
|
||||
|
||||
if chroot != "" {
|
||||
file = fmt.Sprintf("%s/%s", chroot, file)
|
||||
//log.Infof("***** Dumping directory %s", path.Dir(file))
|
||||
//listDirectory(path.Dir(file))
|
||||
}
|
||||
log.Infof("*** Stating file [%s], chroot [%s]", file, chroot)
|
||||
d, err = os.Stat(file)
|
||||
if err != nil {
|
||||
log.Infof("*** Stating file [%s] failed with error - %s", file, err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
log.Infof("*** Stating file [%s], chroot [%s] succeeded", file, chroot)
|
||||
if m := d.Mode(); !m.IsDir() && m&0111 != 0 {
|
||||
return nil
|
||||
}
|
||||
return os.ErrPermission
|
||||
}
|
||||
|
||||
// listDirectory logs the directory structure
|
||||
func listDirectory(path string) error {
|
||||
log.Infof("*** Reading directory %s", path)
|
||||
files, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
log.Info("*** Reading directory FAILED")
|
||||
return err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if file.IsDir() {
|
||||
log.Infof("*** %s [dir]", file.Name())
|
||||
} else {
|
||||
log.Infof("*** %s [file]", file.Name())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// lookPath searches for an executable binary named file in the directories
|
||||
// specified by the path argument.
|
||||
// This is a direct modification of the unix os/exec core library impl
|
||||
func lookPath(file string, env []string, dir string, chroot string) (string, error) {
|
||||
// if it starts with a ./ or ../ it's a relative path
|
||||
// need to check explicitly to allow execution of .hidden files
|
||||
|
||||
if strings.HasPrefix(file, "./") || strings.HasPrefix(file, "../") {
|
||||
file = fmt.Sprintf("%s%c%s", dir, os.PathSeparator, file)
|
||||
err := findExecutable(file, chroot)
|
||||
if err == nil {
|
||||
return filepath.Clean(file), nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
// check if it's already a path spec
|
||||
if strings.Contains(file, "/") {
|
||||
err := findExecutable(file, chroot)
|
||||
if err == nil {
|
||||
return filepath.Clean(file), nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
// extract path from the env
|
||||
var pathenv string
|
||||
for _, value := range env {
|
||||
if strings.HasPrefix(value, "PATH=") {
|
||||
pathenv = value
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
pathval := strings.TrimPrefix(pathenv, "PATH=")
|
||||
|
||||
dirs := filepath.SplitList(pathval)
|
||||
for _, dir := range dirs {
|
||||
if dir == "" {
|
||||
// Unix shell semantics: path element "" means "."
|
||||
dir = "."
|
||||
}
|
||||
path := dir + "/" + file
|
||||
if err := findExecutable(path, chroot); err == nil {
|
||||
return filepath.Clean(path), nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("%s: no such executable in PATH", file)
|
||||
}
|
||||
|
||||
func establishPty(session *SessionConfig) error {
|
||||
defer trace.End(trace.Begin("initializing pty handling for session " + session.ID))
|
||||
|
||||
// pty.Start creates a process group anyway so no change needed to kill all descendants
|
||||
var err error
|
||||
session.Pty, err = pty.Start(&session.Cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
session.wait.Add(1)
|
||||
go func() {
|
||||
_, gerr := io.CopyBuffer(session.Outwriter, session.Pty, make([]byte, ioCopyBufferSize))
|
||||
log.Debugf("PTY stdout copy: %s", gerr)
|
||||
|
||||
session.wait.Done()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
_, gerr := io.CopyBuffer(session.Pty, session.Reader, make([]byte, ioCopyBufferSize))
|
||||
log.Debugf("PTY stdin copy: %s", gerr)
|
||||
|
||||
// ensure that an EOT is delivered to the process - this makes the behaviour on EOF at this layer
|
||||
// consistent between tty and non-tty cases
|
||||
n, gerr := session.Pty.Write([]byte("\x04"))
|
||||
if n != 1 || gerr != nil {
|
||||
log.Errorf("Failed to write EOT to pty, closing directly: %s", gerr)
|
||||
session.Pty.Close()
|
||||
}
|
||||
log.Debug("Written EOT to pty")
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func establishNonPty(session *SessionConfig) error {
|
||||
defer trace.End(trace.Begin("initializing nonpty handling for session " + session.ID))
|
||||
var err error
|
||||
|
||||
// configure a process group so we can kill any descendants
|
||||
if session.Cmd.SysProcAttr == nil {
|
||||
session.Cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||
}
|
||||
session.Cmd.SysProcAttr.Setsid = true
|
||||
|
||||
if session.OpenStdin {
|
||||
log.Debugf("Setting StdinPipe")
|
||||
if session.StdinPipe, err = session.Cmd.StdinPipe(); err != nil {
|
||||
log.Errorf("StdinPipe failed with %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("Setting StdoutPipe")
|
||||
if session.StdoutPipe, err = session.Cmd.StdoutPipe(); err != nil {
|
||||
log.Errorf("Setting StdoutPipe failed with %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Setting StderrPipe")
|
||||
if session.StderrPipe, err = session.Cmd.StderrPipe(); err != nil {
|
||||
log.Errorf("Setting StderrPipe failed with %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if session.OpenStdin {
|
||||
go func() {
|
||||
_, gerr := io.CopyBuffer(session.StdinPipe, session.Reader, make([]byte, ioCopyBufferSize))
|
||||
log.Debugf("Reader stdin returned: %s", gerr)
|
||||
|
||||
if gerr == nil {
|
||||
if cerr := session.StdinPipe.Close(); cerr != nil {
|
||||
log.Errorf("(stdin): Close StdinPipe failed with %s", cerr)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Add 2 for Std{out|err}
|
||||
session.wait.Add(2)
|
||||
go func() {
|
||||
_, gerr := io.CopyBuffer(session.Outwriter, session.StdoutPipe, make([]byte, ioCopyBufferSize))
|
||||
log.Debugf("Writer goroutine for stdout returned: %s", gerr)
|
||||
|
||||
if session.StdinPipe != nil {
|
||||
log.Debugf("(stdout): Writing zero byte to stdin pipe")
|
||||
n, werr := session.StdinPipe.Write([]byte{})
|
||||
if n == 0 && werr != nil && werr.Error() == "write |1: bad file descriptor" {
|
||||
log.Debugf("(stdout): Closing stdin pipe")
|
||||
if cerr := session.StdinPipe.Close(); cerr != nil {
|
||||
log.Errorf("Close failed with %s", cerr)
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Debugf("Writer goroutine for stdout exiting")
|
||||
|
||||
session.wait.Done()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
_, gerr := io.CopyBuffer(session.Errwriter, session.StderrPipe, make([]byte, ioCopyBufferSize))
|
||||
log.Debugf("Writer goroutine for stderr returned: %s", gerr)
|
||||
|
||||
if session.StdinPipe != nil {
|
||||
log.Debugf("(stderr): Writing zero byte to stdin pipe")
|
||||
n, werr := session.StdinPipe.Write([]byte{})
|
||||
if n == 0 && werr != nil && werr.Error() == "write |1: bad file descriptor" {
|
||||
log.Debugf("(stderr): Closing stdin pipe")
|
||||
if cerr := session.StdinPipe.Close(); cerr != nil {
|
||||
log.Errorf("Close failed with %s", cerr)
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Debugf("Writer goroutine for stderr exiting")
|
||||
|
||||
session.wait.Done()
|
||||
}()
|
||||
|
||||
return session.Cmd.Start()
|
||||
}
|
||||
322
vendor/github.com/vmware/vic/lib/tether/tether_test.go
generated
vendored
Normal file
322
vendor/github.com/vmware/vic/lib/tether/tether_test.go
generated
vendored
Normal file
@@ -0,0 +1,322 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tether
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/vishvananda/netlink"
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/lib/config/executor"
|
||||
"github.com/vmware/vic/lib/etcconf"
|
||||
"github.com/vmware/vic/lib/system"
|
||||
"github.com/vmware/vic/pkg/dio"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/extraconfig"
|
||||
)
|
||||
|
||||
var Tthr Tether
|
||||
|
||||
type Mocker struct {
|
||||
Base BaseOperations
|
||||
|
||||
// allow tests to tell when the tether has finished setup
|
||||
Started chan bool
|
||||
// allow tests to tell when the tether has finished
|
||||
Cleaned chan bool
|
||||
// allow tests to wait for a reload to complete
|
||||
Reloaded chan bool
|
||||
|
||||
// debug output gets logged here
|
||||
LogWriter io.Writer
|
||||
|
||||
// session output gets logged here
|
||||
SessionLogBuffer bytes.Buffer
|
||||
|
||||
// the hostname of the system
|
||||
Hostname string
|
||||
Aliases []string
|
||||
// the maximum slot number returned from this mocker
|
||||
maxSlot int
|
||||
// the interfaces in the system indexed by name
|
||||
Interfaces map[string]netlink.Link
|
||||
// filesystem mounts, indexed by disk label
|
||||
Mounts map[string]string
|
||||
|
||||
WindowCol uint32
|
||||
WindowRow uint32
|
||||
Signal ssh.Signal
|
||||
|
||||
FirewallRules []string
|
||||
}
|
||||
|
||||
// Start implements the extension method
|
||||
func (t *Mocker) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop implements the extension method
|
||||
func (t *Mocker) Stop() error {
|
||||
close(t.Cleaned)
|
||||
|
||||
defer func() {
|
||||
// tolerate closing started again
|
||||
recover()
|
||||
}()
|
||||
close(t.Started)
|
||||
close(t.Reloaded)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reload implements the extension method
|
||||
func (t *Mocker) Reload(config *ExecutorConfig) error {
|
||||
// the tether has definitely finished it's startup by the time we hit this
|
||||
defer func() {
|
||||
// if we trigger another Reload we don't want to panic
|
||||
recover()
|
||||
}()
|
||||
|
||||
t.Reloaded <- true
|
||||
close(t.Started)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Mocker) Cleanup() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Mocker) Setup(c Config) error {
|
||||
return t.Base.Setup(c)
|
||||
}
|
||||
|
||||
func (t *Mocker) Log() (io.Writer, error) {
|
||||
if t.LogWriter != nil {
|
||||
return t.LogWriter, nil
|
||||
}
|
||||
|
||||
return os.Stdout, nil
|
||||
}
|
||||
|
||||
func (t *Mocker) SetupFirewall(ctx context.Context, conf *ExecutorConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Mocker) SessionLog(session *SessionConfig) (dio.DynamicMultiWriter, dio.DynamicMultiWriter, error) {
|
||||
return dio.MultiWriter(&t.SessionLogBuffer), dio.MultiWriter(&t.SessionLogBuffer), nil
|
||||
}
|
||||
|
||||
func (t *Mocker) HandleSessionExit(config *ExecutorConfig, session *SessionConfig) func() {
|
||||
// check for executor behaviour
|
||||
return func() {
|
||||
if session.ID == config.ID {
|
||||
Tthr.Stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Mocker) ProcessEnv(env []string) []string {
|
||||
return t.Base.ProcessEnv(env)
|
||||
}
|
||||
|
||||
// SetHostname sets both the kernel hostname and /etc/hostname to the specified string
|
||||
func (t *Mocker) SetHostname(hostname string, aliases ...string) error {
|
||||
defer trace.End(trace.Begin("mocking hostname to " + hostname))
|
||||
|
||||
// TODO: we could mock at a much finer granularity, only extracting the syscall
|
||||
// that would exercise the file modification paths, however it's much less generalizable
|
||||
t.Hostname = hostname
|
||||
t.Aliases = aliases
|
||||
return nil
|
||||
}
|
||||
|
||||
// Apply takes the network endpoint configuration and applies it to the system
|
||||
func (t *Mocker) Apply(endpoint *NetworkEndpoint) error {
|
||||
return ApplyEndpoint(t, &t.Base, endpoint)
|
||||
}
|
||||
|
||||
// MountLabel performs a mount with the source treated as a disk label
|
||||
// This assumes that /dev/disk/by-label is being populated, probably by udev
|
||||
func (t *Mocker) MountLabel(ctx context.Context, label, target string) error {
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("mocking mounting %s on %s", label, target)))
|
||||
|
||||
if t.Mounts == nil {
|
||||
t.Mounts = make(map[string]string)
|
||||
}
|
||||
|
||||
t.Mounts[label] = target
|
||||
return nil
|
||||
}
|
||||
|
||||
// MountTarget performs a mount with the source treated as an nfs target
|
||||
func (t *Mocker) MountTarget(ctx context.Context, source url.URL, target string, mountOptions string) error {
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("mocking mounting %s on %s", source.String(), target)))
|
||||
|
||||
if t.Mounts == nil {
|
||||
t.Mounts = make(map[string]string)
|
||||
}
|
||||
|
||||
t.Mounts[source.String()] = target
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyExistingContent copies the underlying files shadowed by a mount on a directory
|
||||
// to the volume mounted on the directory
|
||||
func (t *Mocker) CopyExistingContent(source string) error {
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("mocking copyExistingContent from %s", source)))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fork triggers vmfork and handles the necessary pre/post OS level operations
|
||||
func (t *Mocker) Fork() error {
|
||||
defer trace.End(trace.Begin("mocking fork"))
|
||||
return errors.New("Fork test not implemented")
|
||||
}
|
||||
|
||||
// LaunchUtility uses the underlying implementation for launching and tracking utility processes
|
||||
func (t *Mocker) LaunchUtility(fn UtilityFn) (<-chan int, error) {
|
||||
return launchUtility(&t.Base, fn)
|
||||
}
|
||||
|
||||
func (t *Mocker) HandleUtilityExit(pid, exitCode int) bool {
|
||||
return handleUtilityExit(&t.Base, pid, exitCode)
|
||||
}
|
||||
|
||||
// TestMain simply so we have control of debugging level and somewhere to call package wide test setup
|
||||
func TestMain(m *testing.M) {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
trace.Logger = log.StandardLogger()
|
||||
|
||||
// restore what we inherited.
|
||||
defer func(hosts etcconf.Hosts, resolv etcconf.ResolvConf) {
|
||||
Sys.Hosts = hosts
|
||||
Sys.ResolvConf = resolv
|
||||
}(Sys.Hosts, Sys.ResolvConf)
|
||||
|
||||
// replace the Sys variable with a mock
|
||||
r, err := ioutil.TempDir("", "tether-root")
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
log.Fatalf("Failed to create tmpdir for test root filesytem: %s", err)
|
||||
}
|
||||
|
||||
Sys = system.NewWithRoot(r)
|
||||
Sys.Syscall = &MockSyscall{}
|
||||
|
||||
BindSys = system.NewWithRoot(path.Join(Sys.Root, "/.tether"))
|
||||
BindSys.Syscall = &MockSyscall{}
|
||||
|
||||
// ensure that the bindsys root exists
|
||||
// #nosec
|
||||
os.MkdirAll(BindSys.Root, 0744)
|
||||
|
||||
log.Debugf("Configured sys and bindsys to root in %s and %s", Sys.Root, BindSys.Root)
|
||||
|
||||
retCode := m.Run()
|
||||
|
||||
// call with result of m.Run()
|
||||
os.Exit(retCode)
|
||||
}
|
||||
|
||||
func StartTether(t *testing.T, cfg *executor.ExecutorConfig, mocker *Mocker) (Tether, extraconfig.DataSource, extraconfig.DataSink) {
|
||||
store := extraconfig.New()
|
||||
sink := store.Put
|
||||
src := store.Get
|
||||
extraconfig.Encode(sink, cfg)
|
||||
log.Debugf("Test configuration: %#v", sink)
|
||||
|
||||
Tthr = New(src, sink, mocker)
|
||||
Tthr.Register("mocker", mocker)
|
||||
|
||||
// run the tether to service the attach
|
||||
go func() {
|
||||
erR := Tthr.Start()
|
||||
if erR != nil {
|
||||
t.Error(erR)
|
||||
}
|
||||
}()
|
||||
|
||||
return Tthr, src, sink
|
||||
}
|
||||
|
||||
func RunTether(t *testing.T, cfg *executor.ExecutorConfig, mocker *Mocker) (Tether, extraconfig.DataSource, error) {
|
||||
store := extraconfig.New()
|
||||
sink := store.Put
|
||||
src := store.Get
|
||||
extraconfig.Encode(sink, cfg)
|
||||
log.Debugf("Test configuration: %#v", sink)
|
||||
|
||||
Tthr = New(src, sink, mocker)
|
||||
Tthr.Register("Mocker", mocker)
|
||||
|
||||
// run the tether to service the attach
|
||||
erR := Tthr.Start()
|
||||
|
||||
return Tthr, src, erR
|
||||
}
|
||||
|
||||
func OptionValueArrayToString(options []types.BaseOptionValue) string {
|
||||
// create the key/value store from the extraconfig slice for lookups
|
||||
kv := make(map[string]string)
|
||||
for i := range options {
|
||||
k := options[i].GetOptionValue().Key
|
||||
v := options[i].GetOptionValue().Value.(string)
|
||||
kv[k] = v
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%#v", kv)
|
||||
}
|
||||
|
||||
func testSetup(t *testing.T) (string, *Mocker) {
|
||||
pc, _, _, _ := runtime.Caller(1)
|
||||
name := runtime.FuncForPC(pc).Name()
|
||||
|
||||
log.Infof("Started test setup for %s", name)
|
||||
|
||||
// use the mock ops - fresh one each time as tests might apply different mocked calls
|
||||
mocker := &Mocker{
|
||||
Started: make(chan bool, 0),
|
||||
Cleaned: make(chan bool, 0),
|
||||
Reloaded: make(chan bool, 100),
|
||||
Interfaces: make(map[string]netlink.Link, 0),
|
||||
}
|
||||
|
||||
return name, mocker
|
||||
}
|
||||
|
||||
func testTeardown(t *testing.T, mocker *Mocker) {
|
||||
// cleanup
|
||||
// os.RemoveAll(pathPrefix)
|
||||
log.SetOutput(os.Stdout)
|
||||
|
||||
<-mocker.Cleaned
|
||||
|
||||
pc, _, _, _ := runtime.Caller(1)
|
||||
name := runtime.FuncForPC(pc).Name()
|
||||
|
||||
log.Infof("Finished test teardown for %s", name)
|
||||
}
|
||||
422
vendor/github.com/vmware/vic/lib/tether/toolbox.go
generated
vendored
Normal file
422
vendor/github.com/vmware/vic/lib/tether/toolbox.go
generated
vendored
Normal file
@@ -0,0 +1,422 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build !windows,!darwin
|
||||
|
||||
package tether
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
dar "github.com/docker/docker/pkg/archive"
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/vmware/govmomi/toolbox"
|
||||
"github.com/vmware/govmomi/toolbox/hgfs"
|
||||
"github.com/vmware/govmomi/toolbox/vix"
|
||||
"github.com/vmware/vic/lib/archive"
|
||||
"github.com/vmware/vic/lib/tether/msgs"
|
||||
"github.com/vmware/vic/lib/tether/shared"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
// Toolbox is a tether extension that wraps toolbox.Service
|
||||
type Toolbox struct {
|
||||
*toolbox.Service
|
||||
|
||||
sess struct {
|
||||
sync.Mutex
|
||||
session *SessionConfig
|
||||
}
|
||||
|
||||
// IDs that can be used to authenticate
|
||||
authIDs map[string]struct{}
|
||||
|
||||
stop chan struct{}
|
||||
}
|
||||
|
||||
var (
|
||||
defaultArchiveHandler = hgfs.NewArchiveHandler().(*hgfs.ArchiveHandler)
|
||||
baseOp = &BaseOperations{}
|
||||
)
|
||||
|
||||
// NewToolbox returns a tether.Extension that wraps the vsphere/toolbox service
|
||||
func NewToolbox() *Toolbox {
|
||||
in := toolbox.NewBackdoorChannelIn()
|
||||
out := toolbox.NewBackdoorChannelOut()
|
||||
|
||||
service := toolbox.NewService(in, out)
|
||||
service.PrimaryIP = toolbox.DefaultIP
|
||||
|
||||
return &Toolbox{
|
||||
Service: service,
|
||||
authIDs: make(map[string]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Start implementation of the tether.Extension interface
|
||||
func (t *Toolbox) Start() error {
|
||||
t.stop = make(chan struct{})
|
||||
on := make(chan struct{})
|
||||
|
||||
t.Service.Power.PowerOn.Handler = func() error {
|
||||
log.Info("toolbox: service is ready (power on event received)")
|
||||
close(on)
|
||||
return nil
|
||||
}
|
||||
|
||||
err := t.Service.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait for the vmx to send the OS_PowerOn message,
|
||||
// at which point it will be ready to service vix command requests.
|
||||
log.Info("toolbox: waiting for initialization")
|
||||
|
||||
select {
|
||||
case <-on:
|
||||
case <-time.After(time.Second):
|
||||
log.Warn("toolbox: timeout waiting for power on event")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop implementation of the tether.Extension interface
|
||||
func (t *Toolbox) Stop() error {
|
||||
t.Service.Stop()
|
||||
|
||||
t.Service.Wait()
|
||||
|
||||
close(t.stop)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reload implementation of the tether.Extension interface
|
||||
func (t *Toolbox) Reload(config *ExecutorConfig) error {
|
||||
if config != nil && config.Sessions != nil {
|
||||
t.sess.Lock()
|
||||
defer t.sess.Unlock()
|
||||
t.sess.session = config.Sessions[config.ID]
|
||||
}
|
||||
|
||||
// we allow the primary session
|
||||
t.authIDs[config.ID] = struct{}{}
|
||||
// we also allow any device IDs that are attached
|
||||
for _, mspec := range config.Mounts {
|
||||
// mounstpect.source.path is the disk label for vmdks
|
||||
// TODO: this is not the case for other volumes, eg nfs vols.
|
||||
if mspec.Source.Scheme == "label" {
|
||||
t.authIDs[mspec.Source.Path] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InContainer configures the toolbox to run within a container VM
|
||||
func (t *Toolbox) InContainer() *Toolbox {
|
||||
t.Power.Halt.Handler = t.halt
|
||||
|
||||
cmd := t.Service.Command
|
||||
cmd.Authenticate = t.containerAuthenticate
|
||||
cmd.ProcessStartCommand = t.containerStartCommand
|
||||
|
||||
cmd.FileServer.RegisterFileHandler(hgfs.ArchiveScheme, &hgfs.ArchiveHandler{
|
||||
Read: toolboxOverrideArchiveRead,
|
||||
Write: toolboxOverrideArchiveWrite,
|
||||
})
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *Toolbox) session() *SessionConfig {
|
||||
t.sess.Lock()
|
||||
defer t.sess.Unlock()
|
||||
return t.sess.session
|
||||
}
|
||||
|
||||
func (t *Toolbox) kill(_ context.Context, name string) error {
|
||||
session := t.session()
|
||||
if session == nil {
|
||||
return fmt.Errorf("failed to kill container: process not found")
|
||||
}
|
||||
|
||||
session.Lock()
|
||||
defer session.Unlock()
|
||||
return t.killHelper(session, name)
|
||||
}
|
||||
|
||||
func (t *Toolbox) killHelper(session *SessionConfig, name string) error {
|
||||
if name == "" {
|
||||
name = string(ssh.SIGTERM)
|
||||
}
|
||||
|
||||
if session.Cmd.Process == nil {
|
||||
return fmt.Errorf("the session %s hasn't launched yet", session.ID)
|
||||
}
|
||||
|
||||
sig := new(msgs.SignalMsg)
|
||||
err := sig.FromString(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
num := syscall.Signal(sig.Signum())
|
||||
|
||||
log.Infof("sending signal %s (%d) to process group for %s", sig.Signal, num, session.ID)
|
||||
if err := syscall.Kill(-session.Cmd.Process.Pid, num); err != nil {
|
||||
return fmt.Errorf("failed to signal %s group: %s", session.ID, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Toolbox) containerAuthenticate(_ vix.CommandRequestHeader, data []byte) error {
|
||||
var c vix.UserCredentialNamePassword
|
||||
if err := c.UnmarshalBinary(data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
session := t.session()
|
||||
if session == nil {
|
||||
return errors.New("not yet initialized")
|
||||
}
|
||||
|
||||
session.Lock()
|
||||
defer session.Unlock()
|
||||
|
||||
// no authentication yet, just using container ID and device IDs as a sanity check for now
|
||||
if _, ok := t.authIDs[c.Name]; !ok {
|
||||
return errors.New("failed to verify authentication ID")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Toolbox) containerStartCommand(m *toolbox.ProcessManager, r *vix.StartProgramRequest) (int64, error) {
|
||||
var p *toolbox.Process
|
||||
|
||||
switch r.ProgramPath {
|
||||
case "kill":
|
||||
p = toolbox.NewProcessFunc(t.kill)
|
||||
case "reload":
|
||||
p = toolbox.NewProcessFunc(func(_ context.Context, _ string) error {
|
||||
return ReloadConfig()
|
||||
})
|
||||
default:
|
||||
return -1, fmt.Errorf("unknown command %q", r.ProgramPath)
|
||||
}
|
||||
|
||||
return m.Start(r, p)
|
||||
}
|
||||
|
||||
func (t *Toolbox) halt() error {
|
||||
session := t.session()
|
||||
if session == nil {
|
||||
return fmt.Errorf("failed to halt container: not initialized yet")
|
||||
}
|
||||
|
||||
session.Lock()
|
||||
defer session.Unlock()
|
||||
|
||||
if session.Cmd.Process == nil {
|
||||
return fmt.Errorf("the session %s hasn't launched yet", session.ID)
|
||||
}
|
||||
|
||||
log.Infof("stopping %s", session.ID)
|
||||
|
||||
if err := t.killHelper(session, session.StopSignal); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Killing the executor session in the container VM will stop the tether and its extensions.
|
||||
// If that doesn't happen within the timeout, send a SIGKILL.
|
||||
select {
|
||||
case <-t.stop:
|
||||
log.Infof("%s has stopped", session.ID)
|
||||
return nil
|
||||
case <-time.After(time.Second * 10):
|
||||
}
|
||||
|
||||
log.Warnf("killing %s", session.ID)
|
||||
|
||||
return session.Cmd.Process.Kill()
|
||||
}
|
||||
|
||||
// toolboxOverrideArchiveRead is the online DataSink Override Handler
|
||||
func toolboxOverrideArchiveRead(u *url.URL, tr *tar.Reader) error {
|
||||
|
||||
// special behavior when using disk-labels and filterspec
|
||||
diskLabel := u.Query().Get(shared.DiskLabelQueryName)
|
||||
filterSpec := u.Query().Get(shared.FilterSpecQueryName)
|
||||
if diskLabel != "" && filterSpec != "" {
|
||||
op := trace.NewOperation(context.Background(), "ToolboxOnlineDataSink: %s", u.String())
|
||||
op.Debugf("Reading from tar archive to path %s: %s", u.Path, u.String())
|
||||
spec, err := archive.DecodeFilterSpec(op, &filterSpec)
|
||||
if err != nil {
|
||||
op.Errorf(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
diskPath, err := mountDiskLabel(op, diskLabel)
|
||||
if err != nil {
|
||||
op.Errorf(err.Error())
|
||||
return err
|
||||
}
|
||||
defer unmount(op, diskPath)
|
||||
|
||||
// no need to join on u.Path here. u.Path == spec.Rebase, but
|
||||
// Unpack will rebase tar headers for us. :thumbsup:
|
||||
err = archive.InvokeUnpack(op, tr, spec, diskPath)
|
||||
if err != nil {
|
||||
op.Errorf(err.Error())
|
||||
}
|
||||
op.Debugf("Finished reading from tar archive to path %s: %s", u.Path, u.String())
|
||||
return err
|
||||
}
|
||||
return defaultArchiveHandler.Read(u, tr)
|
||||
|
||||
}
|
||||
|
||||
// toolboxOverrideArchiveWrite is the Online DataSource Override Handler
|
||||
func toolboxOverrideArchiveWrite(u *url.URL, tw *tar.Writer) error {
|
||||
|
||||
// special behavior when using disk-labels and filterspec
|
||||
diskLabel := u.Query().Get(shared.DiskLabelQueryName)
|
||||
filterSpec := u.Query().Get(shared.FilterSpecQueryName)
|
||||
|
||||
skiprecurse, _ := strconv.ParseBool(u.Query().Get(shared.SkipRecurseQueryName))
|
||||
skipdata, _ := strconv.ParseBool(u.Query().Get(shared.SkipDataQueryName))
|
||||
|
||||
if diskLabel != "" && filterSpec != "" {
|
||||
op := trace.NewOperation(context.Background(), "ToolboxOnlineDataSource: %s", u.String())
|
||||
op.Debugf("Writing to archive from %s: %s", u.Path, u.String())
|
||||
|
||||
spec, err := archive.DecodeFilterSpec(op, &filterSpec)
|
||||
if err != nil {
|
||||
op.Errorf(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
// get the container fs mount
|
||||
diskPath, err := mountDiskLabel(op, diskLabel)
|
||||
if err != nil {
|
||||
op.Errorf(err.Error())
|
||||
return err
|
||||
}
|
||||
defer unmount(op, diskPath)
|
||||
|
||||
var rc io.ReadCloser
|
||||
if skiprecurse {
|
||||
// we only want a single file - this is a hack while we're abusing Diff, but
|
||||
// accomplish this by generating a single entry ChangeSet
|
||||
changes := []dar.Change{
|
||||
{
|
||||
Kind: dar.ChangeModify,
|
||||
Path: u.Path,
|
||||
},
|
||||
}
|
||||
|
||||
rc, err = archive.Tar(op, diskPath, changes, spec, !skipdata, false)
|
||||
} else {
|
||||
rc, err = archive.Diff(op, diskPath, "", spec, !skipdata, false)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
op.Errorf(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
tr := tar.NewReader(rc)
|
||||
defer rc.Close()
|
||||
for {
|
||||
hdr, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
op.Debugf("Finished writing to archive from %s: %s with error %#v", u.Path, u.String(), err)
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
op.Errorf("error writing tar: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
op.Debugf("Writing header: %#s", *hdr)
|
||||
err = tw.WriteHeader(hdr)
|
||||
if err != nil {
|
||||
op.Errorf("error writing tar header: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(tw, tr)
|
||||
if err != nil {
|
||||
op.Errorf("error writing tar contents: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
return defaultArchiveHandler.Write(u, tw)
|
||||
}
|
||||
|
||||
func mountDiskLabel(op trace.Operation, label string) (string, error) {
|
||||
// We know the vmdk will always be attached at '/'
|
||||
if label == "containerfs" {
|
||||
return "/", nil
|
||||
}
|
||||
|
||||
// otherwise, label represents a volume that needs to be mounted
|
||||
tmpDir, err := ioutil.TempDir("", fmt.Sprintf("toolbox-%s", label))
|
||||
if err != nil {
|
||||
op.Errorf("failed to create mountpoint %s: %s", tmpDir, err)
|
||||
return "", fmt.Errorf("failed to create mountpoint %s: %s", tmpDir, err)
|
||||
}
|
||||
|
||||
err = baseOp.MountLabel(op, label, tmpDir)
|
||||
if err != nil {
|
||||
os.Remove(tmpDir)
|
||||
op.Errorf("failed to mount label %s at %s: %s", label, tmpDir, err)
|
||||
return "", fmt.Errorf("failed to mount label %s at %s: %s", label, tmpDir, err)
|
||||
}
|
||||
|
||||
return tmpDir, nil
|
||||
}
|
||||
|
||||
func unmount(op trace.Operation, unmountPath string) {
|
||||
// don't unmount the root vmdk
|
||||
if unmountPath == "/" {
|
||||
return
|
||||
}
|
||||
|
||||
// unmount the disk from the temporary directory
|
||||
if err := Sys.Syscall.Unmount(unmountPath, syscall.MNT_DETACH); err != nil {
|
||||
op.Errorf("failed to unmount %s: %s", unmountPath, err.Error())
|
||||
}
|
||||
|
||||
// finally, remove the temporary directory
|
||||
os.Remove(unmountPath)
|
||||
}
|
||||
Reference in New Issue
Block a user