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:
30
vendor/github.com/vmware/vic/cmd/vic-init/config.go
generated
vendored
Normal file
30
vendor/github.com/vmware/vic/cmd/vic-init/config.go
generated
vendored
Normal 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
203
vendor/github.com/vmware/vic/cmd/vic-init/main_linux.go
generated
vendored
Normal 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
32
vendor/github.com/vmware/vic/cmd/vic-init/main_test.go
generated
vendored
Normal 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
140
vendor/github.com/vmware/vic/cmd/vic-init/ops.go
generated
vendored
Normal 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
61
vendor/github.com/vmware/vic/cmd/vic-init/ops_linux.go
generated
vendored
Normal 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
|
||||
}
|
||||
117
vendor/github.com/vmware/vic/cmd/vic-init/restart_test.go
generated
vendored
Normal file
117
vendor/github.com/vmware/vic/cmd/vic-init/restart_test.go
generated
vendored
Normal 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")
|
||||
}
|
||||
283
vendor/github.com/vmware/vic/cmd/vic-init/tether_test.go
generated
vendored
Normal file
283
vendor/github.com/vmware/vic/cmd/vic-init/tether_test.go
generated
vendored
Normal 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
260
vendor/github.com/vmware/vic/cmd/vic-init/toolbox.go
generated
vendored
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user