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:
Loc Nguyen
2018-06-04 15:41:32 -07:00
committed by Ria Bhatia
parent 98a111e8b7
commit 513cebe7b7
6296 changed files with 1123685 additions and 8 deletions

30
vendor/github.com/vmware/vic/cmd/vic-init/config.go generated vendored Normal file
View File

@@ -0,0 +1,30 @@
// 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 main
import "github.com/vmware/vic/lib/config/executor"
type ExecutorConfig struct {
// Diagnostics holds basic diagnostics data
Diagnostics Diagnostics `vic:"0.1" scope:"read-only" key:"diagnostics"`
}
type Diagnostics struct {
// Should debugging be enabled on whatever component this is and at what level
DebugLevel int `vic:"0.1" scope:"read-only" key:"debug"`
// SyslogConfig holds configuration for connecting to a syslog
// server
SysLogConfig *executor.SysLogConfig `vic:"0.1" scope:"read-only" key:"syslog"`
}

203
vendor/github.com/vmware/vic/cmd/vic-init/main_linux.go generated vendored Normal file
View File

@@ -0,0 +1,203 @@
// 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 main
import (
"os"
"runtime/debug"
"syscall"
"time"
log "github.com/Sirupsen/logrus"
"github.com/vishvananda/netlink"
"github.com/vmware/govmomi/toolbox"
"github.com/vmware/vic/lib/tether"
viclog "github.com/vmware/vic/pkg/log"
"github.com/vmware/vic/pkg/log/syslog"
"github.com/vmware/vic/pkg/logmgr"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/vsphere/extraconfig"
)
var (
tthr tether.Tether
config ExecutorConfig
debugLevel int
)
func main() {
defer func() {
if r := recover(); r != nil {
log.Errorf("run time panic: %s : %s", r, debug.Stack())
}
reboot()
}()
// ensure that panics and error output are persisted
logFile, err := os.OpenFile("/dev/ttyS0", os.O_WRONLY|os.O_SYNC, 0)
if err != nil {
log.Errorf("Could not redirect outputs to serial for debugging info, some debug info may be lost! Error reported was %s", err)
}
err = syscall.Dup3(int(logFile.Fd()), int(os.Stderr.Fd()), 0)
if err != nil {
log.Errorf("Could not pipe standard error to logfile: %s", err)
}
_, err = os.Stderr.WriteString("all stderr redirected to debug log")
if err != nil {
log.Errorf("Could not write to Stderr due to error %s", err)
}
err = syscall.Dup3(int(logFile.Fd()), int(os.Stdout.Fd()), 0)
if err != nil {
log.Errorf("Could not pipe standard out to logfile: %s", err)
}
_, err = os.Stderr.WriteString("all stdout redirected to debug log")
if err != nil {
log.Errorf("Could not write to stdout due to error %s", err)
}
src, err := extraconfig.GuestInfoSourceWithPrefix("init")
if err != nil {
log.Fatal(err)
}
extraconfig.Decode(src, &config)
debugLevel = config.Diagnostics.DebugLevel
logcfg := viclog.NewLoggingConfig()
if debugLevel > 0 {
logcfg.Level = log.DebugLevel
trace.Logger.Level = log.DebugLevel
syslog.Logger.Level = log.DebugLevel
}
if config.Diagnostics.SysLogConfig != nil {
logcfg.Syslog = &viclog.SyslogConfig{
Network: config.Diagnostics.SysLogConfig.Network,
RAddr: config.Diagnostics.SysLogConfig.RAddr,
Priority: syslog.Info | syslog.Daemon,
}
}
viclog.Init(logcfg)
if debugLevel > 2 {
enableShell()
}
sink, err := extraconfig.GuestInfoSinkWithPrefix("init")
if err != nil {
log.Fatal(err)
}
// create the tether
tthr = tether.New(src, sink, &operations{})
// register the toolbox extension and configure for appliance
toolbox := configureToolbox(tether.NewToolbox())
toolbox.PrimaryIP = externalIP
tthr.Register("Toolbox", toolbox)
// Check logs every 5 minutes and rotate them if their size exceeds 20MB.
// The history size we keep is 2 previous files in a compressed form.
// TODO: Check available memory to tune log size and history length for log files.
logrotate, err := logmgr.NewLogManager(time.Second * 300)
const maxLogSizeBytes = 20 * 1024 * 1024
if err == nil {
logrotate.AddLogRotate("/var/log/vic/port-layer.log", logmgr.Daily, maxLogSizeBytes, 2, true)
logrotate.AddLogRotate("/var/log/vic/init.log", logmgr.Daily, maxLogSizeBytes, 2, true)
logrotate.AddLogRotate("/var/log/vic/docker-personality.log", logmgr.Daily, maxLogSizeBytes, 2, true)
logrotate.AddLogRotate("/var/log/vic/vicadmin.log", logmgr.Daily, maxLogSizeBytes, 2, true)
tthr.Register("logrotate", logrotate)
} else {
log.Error(err)
}
err = tthr.Start()
if err != nil {
log.Fatal(err)
}
log.Info("Clean exit from init")
}
// exit cleanly shuts down the system
func halt() {
log.Infof("Powering off the system")
if debugLevel > 0 {
log.Info("Squashing power off for debug init")
return
}
syscall.Sync()
syscall.Reboot(syscall.LINUX_REBOOT_CMD_POWER_OFF)
}
func reboot() {
log.Infof("Rebooting the system")
if debugLevel > 0 {
log.Info("Squashing reboot for debug init")
return
}
syscall.Sync()
syscall.Reboot(syscall.LINUX_REBOOT_CMD_RESTART)
}
func configureToolbox(t *tether.Toolbox) *tether.Toolbox {
cmd := t.Service.Command
cmd.ProcessStartCommand = startCommand
return t
}
// externalIP attempts to find an external IP to be reported as the guest IP
func externalIP() string {
l, err := netlink.LinkByName("client")
if err != nil {
log.Debugf("error looking up client interface by name: %s", err)
l, err = netlink.LinkByAlias("client")
if err != nil {
log.Errorf("error looking up client interface by alias: %s", err)
return ""
}
}
addrs, err := netlink.AddrList(l, netlink.FAMILY_V4)
if err != nil {
log.Errorf("error getting address list for client interface: %s", err)
return ""
}
if len(addrs) == 0 {
log.Warnf("no addresses set on client interface")
return ""
}
return addrs[0].IP.String()
}
// defaultIP tries externalIP, falling back to toolbox.DefaultIP()
func defaultIP() string {
ip := externalIP()
if ip != "" {
return ip
}
return toolbox.DefaultIP()
}

32
vendor/github.com/vmware/vic/cmd/vic-init/main_test.go generated vendored Normal file
View File

@@ -0,0 +1,32 @@
// 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 main
import (
"flag"
"testing"
)
var systemTest *bool
func init() {
systemTest = flag.Bool("systemTest", false, "Run system test")
}
func TestSystem(t *testing.T) {
if *systemTest {
main()
}
}

140
vendor/github.com/vmware/vic/cmd/vic-init/ops.go generated vendored Normal file
View File

@@ -0,0 +1,140 @@
// 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 main
import (
"errors"
"fmt"
"io"
"os"
"path"
"syscall"
"time"
log "github.com/Sirupsen/logrus"
"github.com/vmware/vic/lib/pprof"
"github.com/vmware/vic/lib/tether"
"github.com/vmware/vic/pkg/dio"
"github.com/vmware/vic/pkg/trace"
)
// pathPrefix is present to allow the various files referenced by tether to be placed
// in specific directories, primarily for testing.
var pathPrefix string
const (
logDir = "var/log/vic"
initLog = "init.log"
)
type operations struct {
tether.BaseOperations
}
func (t *operations) Setup(sink tether.Config) error {
if err := t.BaseOperations.Setup(sink); err != nil {
return err
}
return pprof.StartPprof("vch-init", pprof.VCHInitPort)
}
func (t *operations) Cleanup() error {
return t.BaseOperations.Cleanup()
}
// HandleSessionExit controls the behaviour on session exit - for the tether if the session exiting
// is the primary session (i.e. SessionID matches ExecutorID) then we exit everything.
func (t *operations) HandleSessionExit(config *tether.ExecutorConfig, session *tether.SessionConfig) func() {
defer trace.End(trace.Begin(""))
// trigger a reload to force relaunch
return func() {
// If executor debug is greater than 1 then suppress the relaunch but leave the executor up
// for diagnostics
if config.DebugLevel > 2 {
log.Warnf("Debug is set to %d so squashing relaunch of exited process", config.DebugLevel)
return
}
// incredibly basic throttle
time.Sleep(3 * time.Second)
tthr.Reload()
log.Info("Triggered reload")
}
}
func (t *operations) SetHostname(name string, aliases ...string) error {
// switch the names around so we get the pretty name and not the ID
return t.BaseOperations.SetHostname(aliases[0])
}
func (t *operations) Apply(endpoint *tether.NetworkEndpoint) error {
return t.BaseOperations.Apply(endpoint)
}
// Log will redirect logging to both a file on disk and to stdout for the process
func (t *operations) Log() (io.Writer, error) {
defer trace.End(trace.Begin("operations.Log"))
// make the logging directory
// #nosec: Expect directory permissions to be 0700 or less
os.MkdirAll(path.Join(pathPrefix, logDir), 0755)
logPath := path.Join(pathPrefix, logDir, initLog)
log.Infof("opening %s for debug log", logPath)
// #nosec: Expect file permissions to be 0600 or less
out, err := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_SYNC|syscall.O_NOCTTY, 0644)
if err != nil {
detail := fmt.Sprintf("failed to open file port for debug log: %s", err)
log.Error(detail)
return nil, errors.New(detail)
}
return io.MultiWriter(out, os.Stdout), nil
}
// sessionLogWriter returns a writer that will persist the session output
func (t *operations) SessionLog(session *tether.SessionConfig) (dio.DynamicMultiWriter, dio.DynamicMultiWriter, error) {
defer trace.End(trace.Begin("configure session log writer"))
name := session.ID
if name == "" {
name = session.Name
}
logPath := path.Join(pathPrefix, logDir, name+".log")
// open SttyS2 for session logging
log.Infof("opening %s for session logging", logPath)
// #nosec: Expect file permissions to be 0600 or less
f, err := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_SYNC|syscall.O_NOCTTY, 0644)
if err != nil {
detail := fmt.Sprintf("failed to open file for session log: %s", err)
log.Error(detail)
return nil, nil, errors.New(detail)
}
// use multi-writer so it goes to both screen and session log
if debugLevel > 0 {
return dio.MultiWriter(f, os.Stdout), dio.MultiWriter(f, os.Stderr), nil
}
// only duplicate stderr
return dio.MultiWriter(f), dio.MultiWriter(f), nil
}

61
vendor/github.com/vmware/vic/cmd/vic-init/ops_linux.go generated vendored Normal file
View File

@@ -0,0 +1,61 @@
// 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 main
import (
"context"
"fmt"
"io/ioutil"
log "github.com/Sirupsen/logrus"
"github.com/docker/libnetwork/iptables"
"github.com/vishvananda/netlink"
"github.com/vmware/vic/lib/tether"
)
const (
publicIfaceName = "public"
)
func (t *operations) SetupFirewall(ctx context.Context, config *tether.ExecutorConfig) error {
// get the public interface name
l, err := netlink.LinkByName(publicIfaceName)
if l == nil {
l, err = netlink.LinkByAlias(publicIfaceName)
if l == nil {
return fmt.Errorf("could not find interface: %s", publicIfaceName)
}
}
if _, err = iptables.Raw(string(iptables.Append), "FORWARD", "-i", "bridge", "-o", l.Attrs().Name, "-j", "ACCEPT"); err != nil {
return err
}
if config.AsymmetricRouting {
// set rp_filter to "loose" mode; see https://en.wikipedia.org/wiki/Reverse_path_forwarding#Loose_mode
//
// this is so the kernel will not drop packets sent to the public interface from an
// address that is reachable by other interfaces on the VCH. specifically, when
// packets from the bridge network are directed to another network (say, management),
// the incoming reply to the VCH can be dropped if rp_filter is set to the default 1
if err = ioutil.WriteFile(fmt.Sprintf("/proc/sys/net/ipv4/conf/%s/rp_filter", l.Attrs().Name), []byte("2"), 0644); err != nil {
// not a fatal error, so just log it here
log.Warnf("error while setting rp_filter for interface %s: %s", l.Attrs().Name, err)
}
}
return nil
}

View File

@@ -0,0 +1,117 @@
// 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 main
import (
"fmt"
"os/exec"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/vmware/vic/lib/config/executor"
"github.com/vmware/vic/lib/tether"
"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 TestRestart(t *testing.T) {
testSetup(t)
defer testTeardown(t)
cfg := executor.ExecutorConfig{
ExecutorConfigCommon: executor.ExecutorConfigCommon{
ID: "pathlookup",
Name: "tether_test_executor",
},
Diagnostics: executor.Diagnostics{
DebugLevel: 2,
},
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",
},
Restart: true,
},
},
}
tthr, src := StartTether(t, &cfg)
// wait for initialization
<-Mocked.Started
result := &tether.ExecutorConfig{}
extraconfig.Decode(src, result)
// Started returns when we reload but that doesn't mean that the process is started
// Try multiple times before giving up
for i := 0; i < 10; i++ {
if result.Sessions["pathlookup"].Started != "" {
break
}
time.Sleep(time.Duration(i) * time.Millisecond)
}
assert.Equal(t, 0, result.Sessions["pathlookup"].ExitStatus, "Expected command to have exited cleanly")
assert.True(t, result.Sessions["pathlookup"].Restart, "Expected command to be configured for restart")
// wait for the resurrection count to max out the channel
for result.Sessions["pathlookup"].Diagnostics.ResurrectionCount < 10 {
result = &tether.ExecutorConfig{}
extraconfig.Decode(src, &result)
assert.Equal(t, 0, result.Sessions["pathlookup"].ExitStatus, "Expected command to have exited cleanly")
// proceed to the next reincarnation
<-Mocked.SessionExit
tthr.Reload()
}
// read the output from the session
log := Mocked.SessionLogBuffer.Bytes()
// the tether has to be stopped before comparison on the reaper may swaller exec.Wait
tthr.Stop()
<-Mocked.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
}
assert.True(t, strings.HasPrefix(string(log), string(out)), "Expected the data to be constant - first invocation doesn't match")
assert.True(t, strings.HasSuffix(string(log), string(out)), "Expected the data to be constant - last invocation doesn't match")
}

View File

@@ -0,0 +1,283 @@
// 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 main
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"net"
"net/url"
"os"
"runtime"
"testing"
log "github.com/Sirupsen/logrus"
"golang.org/x/crypto/ssh"
"github.com/vmware/govmomi/vim25/types"
"github.com/vmware/vic/lib/config/executor"
"github.com/vmware/vic/lib/system"
"github.com/vmware/vic/lib/tether"
"github.com/vmware/vic/pkg/dio"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/vsphere/extraconfig"
)
var Mocked Mocker
type Mocker struct {
Base tether.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
// Session exit
SessionExit chan bool
// debug output gets logged here
LogBuffer bytes.Buffer
// session output gets logged here
SessionLogBuffer bytes.Buffer
// the hostname of the system
Hostname string
// the ip configuration for name index networks
IPs map[string]net.IP
// filesystem mounts, indexed by disk label
Mounts map[string]string
WindowCol uint32
WindowRow uint32
Signal ssh.Signal
}
// 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)
return nil
}
// Reload implements the extension method
func (t *Mocker) Reload(config *tether.ExecutorConfig) error {
// the tether has definitely finished it's startup by the time we hit this
defer func() {
// deal with repeated reloads
recover()
}()
close(t.Started)
return nil
}
func (t *Mocker) Setup(tether.Config) error {
return nil
}
func (t *Mocker) Cleanup() error {
return nil
}
func (t *Mocker) Log() (io.Writer, error) {
return &t.LogBuffer, nil
}
func (t *Mocker) SessionLog(session *tether.SessionConfig) (dio.DynamicMultiWriter, dio.DynamicMultiWriter, error) {
return dio.MultiWriter(&t.SessionLogBuffer), dio.MultiWriter(&t.SessionLogBuffer), nil
}
func (t *Mocker) HandleSessionExit(config *tether.ExecutorConfig, session *tether.SessionConfig) func() {
return func() {
t.SessionExit <- true
}
}
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
return nil
}
func (t *Mocker) SetupFirewall(cxt context.Context, conf *tether.ExecutorConfig) error {
return nil
}
// Apply takes the network endpoint configuration and applies it to the system
func (t *Mocker) Apply(endpoint *tether.NetworkEndpoint) error {
defer trace.End(trace.Begin("mocking endpoint configuration for " + endpoint.Network.Name))
t.IPs[endpoint.Network.Name] = endpoint.Assigned.IP
return nil
}
// 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 tether.UtilityFn) (<-chan int, error) {
return t.Base.LaunchUtility(fn)
}
func (t *Mocker) HandleUtilityExit(pid, exitCode int) bool {
return t.Base.HandleUtilityExit(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()
// replace the Sys variable with a mock
tether.Sys = system.System{
Hosts: &tether.MockHosts{},
ResolvConf: &tether.MockResolvConf{},
Syscall: &tether.MockSyscall{},
Root: os.TempDir(),
}
retCode := m.Run()
// call with result of m.Run()
os.Exit(retCode)
}
func StartTether(t *testing.T, cfg *executor.ExecutorConfig) (tether.Tether, extraconfig.DataSource) {
store := extraconfig.New()
sink := store.Put
src := store.Get
extraconfig.Encode(sink, cfg)
log.Debugf("Test configuration: %#v", sink)
tthr = tether.New(src, sink, &Mocked)
tthr.Register("mocker", &Mocked)
// run the tether to service the attach
go func() {
err := tthr.Start()
if err != nil {
t.Error(err)
}
}()
return tthr, src
}
func RunTether(t *testing.T, cfg *executor.ExecutorConfig) (tether.Tether, extraconfig.DataSource, error) {
store := extraconfig.New()
sink := store.Put
src := store.Get
extraconfig.Encode(sink, cfg)
log.Debugf("Test configuration: %#v", sink)
tthr = tether.New(src, sink, &Mocked)
tthr.Register("Mocker", &Mocked)
// 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) {
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
Mocked = Mocker{
Started: make(chan bool),
Cleaned: make(chan bool),
SessionExit: make(chan bool),
}
}
func testTeardown(t *testing.T) {
// cleanup
<-Mocked.Cleaned
os.RemoveAll(pathPrefix)
log.SetOutput(os.Stdout)
pc, _, _, _ := runtime.Caller(1)
name := runtime.FuncForPC(pc).Name()
log.Infof("Finished test teardown for %s", name)
}

260
vendor/github.com/vmware/vic/cmd/vic-init/toolbox.go generated vendored Normal file
View File

@@ -0,0 +1,260 @@
// 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 main
import (
"bufio"
"bytes"
"context"
"errors"
"fmt"
"io/ioutil"
"os"
"os/exec"
"strings"
"time"
"github.com/vmware/govmomi/toolbox"
"github.com/vmware/govmomi/toolbox/vix"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/vsphere/diag"
log "github.com/Sirupsen/logrus"
)
// startCommand is the switch for the synthetic commands that are permitted within the appliance.
// This is not intended to allow arbitrary commands to be executed.
// returns:
// pid: toolbox ProcessManager Process id
// error
func startCommand(m *toolbox.ProcessManager, r *vix.StartProgramRequest) (int64, error) {
defer trace.End(trace.Begin(r.ProgramPath))
var p *toolbox.Process
switch r.ProgramPath {
case "enable-ssh":
p = toolbox.NewProcessFunc(func(ctx context.Context, args string) error {
err := enableSSH(args)
// #nosec: Errors unhandled.
_ = enableShell()
return err
})
case "passwd":
p = toolbox.NewProcessFunc(func(ctx context.Context, args string) error {
err := passwd(args)
// #nosec: Errors unhandled.
_ = enableShell()
return err
})
case "test-vc-api":
p = toolbox.NewProcessFunc(func(ctx context.Context, args string) error {
rc := diag.CheckAPIAvailability(args)
if rc == diag.VCStatusOK {
return nil
}
return &toolbox.ProcessError{
Err: errors.New(diag.UserReadableVCAPITestDescription(rc)),
ExitCode: int32(rc),
}
})
default:
return -1, os.ErrNotExist
}
return m.Start(r, p)
}
// enableShell changes the root shell from /bin/false to /bin/bash
// We try to ensure the password is not expired via chage, as chsh
// requires an unexpired password to succeed.
func enableShell() error {
defer trace.End(trace.Begin(""))
// if reset fails, try the rest anyway
// #nosec: Errors unhandled.
resetPasswdExpiry()
// #nosec: Subprocess launching should be audited
chsh := exec.Command("/bin/chsh", "-s", "/bin/bash", "root")
err := chsh.Start()
if err != nil {
err := fmt.Errorf("Failed to launch chsh: %s", err)
log.Error(err)
return err
}
// ignore the error - it's likely raced with child reaper, we just want to make sure
// that it's exited by the time we pass this point
// #nosec: Errors unhandled.
chsh.Wait()
// confirm the change
file, err := os.Open("/etc/passwd")
if err != nil {
err := fmt.Errorf("Failed to open file to confirm change: %s", err)
log.Error(err)
return err
}
reader := bufio.NewReader(file)
line, err := reader.ReadString('\n')
if err != nil {
err := fmt.Errorf("Failed to read line from file to confirm change: %s", err)
log.Error(err)
return err
}
// assert that first line is root
if !strings.HasPrefix(line, "root") {
err := fmt.Errorf("Expected line to start with root: %s", line)
log.Error(err)
return err
}
// assert that first line is root
if !strings.HasSuffix(line, "/bin/bash\n") {
err := fmt.Errorf("Expected line to end with /bin/bash: %s", line)
log.Error(err)
return err
}
log.Info("Attempted to enable the shell for root")
return nil
}
// passwd sets the password for the root user to that provided as an argument
func passwd(pass string) error {
defer trace.End(trace.Begin(""))
// #nosec: Subprocess launching should be audited
setPasswd := exec.Command("/sbin/chpasswd")
stdin, err := setPasswd.StdinPipe()
if err != nil {
err := fmt.Errorf("Failed to create stdin pipe for chpasswd: %s", err)
log.Error(err)
return err
}
err = setPasswd.Start()
if err != nil {
err := fmt.Errorf("Failed to launch chpasswd: %s", err)
log.Error(err)
return err
}
_, err = stdin.Write([]byte("root:" + pass))
// so that we're actively waiting when the process exits, or we'll race (and lose) to child reaper
go func() {
// #nosec: Errors unhandled.
setPasswd.Wait()
}()
err = stdin.Close()
if err != nil {
err := fmt.Errorf("Failed to close input to chpasswd: %s", err)
log.Error(err)
// fire and forget as we're already on error path
// #nosec: Errors unhandled.
setPasswd.Process.Kill()
return err
}
log.Info("Attempted to set the password for root")
return nil
}
// enableSSH receives a key as an argument
func enableSSH(key string) error {
defer trace.End(trace.Begin(""))
// basic sanity check for args - we don't bother validating it's a key
if len(key) != 0 {
err := os.MkdirAll("/root/.ssh", 0700)
if err != nil {
err := fmt.Errorf("unable to create path for keys: %s", err)
log.Error(err)
return err
}
err = ioutil.WriteFile("/root/.ssh/authorized_keys", []byte(key), 0600)
if err != nil {
err := fmt.Errorf("unable to create authorized_keys: %s", err)
log.Error(err)
return err
}
}
return startSSH()
}
// startSSH launches the sshd server
func startSSH() error {
// #nosec: Subprocess launching should be audited
c := exec.Command("/usr/bin/systemctl", "start", "sshd")
var b bytes.Buffer
c.Stdout = &b
c.Stderr = &b
if err := c.Start(); err != nil {
return err
}
go func() {
// because init is explicitly reaping child processes we cannot use simple
// exec commands to gather status
// #nosec: Errors unhandled.
_ = c.Wait()
log.Info("Attempted to start ssh service:\n %s", b)
}()
return nil
}
func resetPasswdExpiry() error {
defer trace.End(trace.Begin(""))
// add just enough time for the password not to be expired
// if the user wants more time they can actually change the password
// This will expire in at most 1 day, perhaps sooner depending on local time
// NB: Format is example based - that's the reference time format
expireDate := time.Now().AddDate(0, 0, -1).Format("2006-01-02")
// #nosec: Subprocess launching should be audited
chage := exec.Command("/bin/chage", "-M", "1", "-d", expireDate, "root")
err := chage.Start()
if err != nil {
err := fmt.Errorf("Failed to launch chage: %s", err)
log.Error(err)
return err
}
// ignore the error - it's likely raced with child reaper, we just want to make sure
// that it's exited by the time we pass this point
// #nosec: Errors unhandled.
chage.Wait()
log.Infof("Attempted reset of password expiry: /bin/chage -M 1 -d %s root", expireDate)
return nil
}