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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,60 @@
// 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 management
import (
"context"
"net/url"
"testing"
"github.com/stretchr/testify/assert"
"github.com/vmware/vic/lib/config"
"github.com/vmware/vic/pkg/trace"
)
func TestConfirmVolumeStores(t *testing.T) {
op := trace.NewOperation(context.Background(), "TestConfirmVolumeStores")
testVolumeLocations := map[string]*url.URL{
"test1": {},
"test2": {},
"volume-store": {},
}
plVolumeStores1 := "test2 volume-store"
plVolumeStores2 := "test1 test2"
plVolumeStores3 := "volume-store"
plVolumeStores4 := "test1 test2 volume-store"
plVolumeStores5 := ""
testconf := &config.VirtualContainerHostConfigSpec{}
testconf.VolumeLocations = testVolumeLocations
result1 := confirmVolumeStores(op, testconf, plVolumeStores1)
assert.False(t, result1, "Failed first confirmVolumeStores check")
result2 := confirmVolumeStores(op, testconf, plVolumeStores2)
assert.False(t, result2, "Failed second confirmVolumeStores check")
result3 := confirmVolumeStores(op, testconf, plVolumeStores3)
assert.False(t, result3, "Failed third confirmVolumeStores check")
result4 := confirmVolumeStores(op, testconf, plVolumeStores4)
assert.True(t, result4, "Failed fourth confirmVolumeStores check")
result5 := confirmVolumeStores(op, testconf, plVolumeStores5)
assert.False(t, result5, "Failed fifth confirmVolumeStores check")
}

View File

@@ -0,0 +1,559 @@
// 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 management
import (
"bufio"
"context"
"fmt"
"io"
"path"
"reflect"
"strings"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/task"
"github.com/vmware/govmomi/vim25/soap"
"github.com/vmware/govmomi/vim25/types"
"github.com/vmware/vic/lib/config"
"github.com/vmware/vic/lib/install/data"
"github.com/vmware/vic/lib/install/opsuser"
"github.com/vmware/vic/pkg/errors"
"github.com/vmware/vic/pkg/retry"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/vsphere/datastore"
"github.com/vmware/vic/pkg/vsphere/extraconfig"
"github.com/vmware/vic/pkg/vsphere/extraconfig/vmomi"
"github.com/vmware/vic/pkg/vsphere/tasks"
"github.com/vmware/vic/pkg/vsphere/vm"
)
const (
// deprecated snapshot name prefix
UpgradePrefix = "upgrade for"
// new snapshot name for upgrade and configure are using same process
ConfigurePrefix = "reconfigure for"
)
var (
errSecretKeyNotFound = fmt.Errorf("unable to find guestinfo secret")
errNilDatastore = fmt.Errorf("session's datastore is not set")
)
// Configure will try to reconfigure vch appliance. If failed will try to roll back to original status.
func (d *Dispatcher) Configure(vch *vm.VirtualMachine, conf *config.VirtualContainerHostConfigSpec, settings *data.InstallerData, isConfigureOp bool) (err error) {
defer trace.End(trace.Begin(conf.Name, d.op))
d.appliance = vch
// update the displayname to the actual folder name used
if d.vmPathName, err = d.appliance.FolderName(d.op); err != nil {
d.op.Errorf("Failed to get canonical name for appliance: %s", err)
return err
}
ds, err := d.session.Finder.Datastore(d.op, conf.ImageStores[0].Host)
if err != nil {
err = errors.Errorf("Failed to find image datastore %q", conf.ImageStores[0].Host)
return err
}
d.session.Datastore = ds
d.setDockerPort(conf, settings)
if len(settings.ImageFiles) > 0 {
// Need to update iso files
if err = d.uploadImages(settings.ImageFiles); err != nil {
return errors.Errorf("Uploading images failed with %s. Exiting...", err)
}
conf.BootstrapImagePath = fmt.Sprintf("[%s] %s/%s", conf.ImageStores[0].Host, d.vmPathName, settings.BootstrapISO)
}
if err = d.updateResourceSettings(conf.Name, settings); err != nil {
err = errors.Errorf("Failed to reconfigure resources: %s", err)
return err
}
defer func() {
if err != nil {
d.rollbackResourceSettings(conf.Name, settings)
}
}()
// ensure that we wait for components to come up
for _, s := range conf.ExecutorConfig.Sessions {
s.Started = ""
}
snapshotName := fmt.Sprintf("%s %s", ConfigurePrefix, conf.Version.BuildNumber)
snapshotName = strings.TrimSpace(snapshotName)
// check for old snapshot
// #nosec: Errors unhandled.
oldSnapshot, _ := d.appliance.GetCurrentSnapshotTree(d.op)
newSnapshotRef, err := d.tryCreateSnapshot(snapshotName, "configure snapshot")
if err != nil {
d.deleteUpgradeImages(ds, settings)
return err
}
err = d.update(conf, settings, isConfigureOp)
// If successful try to grant permissions to the ops-user
if err == nil && conf.ShouldGrantPerms() {
err = opsuser.GrantOpsUserPerms(d.op, d.session.Vim25(), conf)
if err != nil {
// Update error message and fall through to roll back
err = errors.Errorf("Failed to grant permissions to ops-user, failure: %s", err)
}
}
if err != nil {
// Roll back
d.op.Errorf("Failed to upgrade: %s", err)
d.op.Infof("Rolling back upgrade")
if rerr := d.rollback(conf, snapshotName, settings); rerr != nil {
d.op.Errorf("Failed to revert appliance to snapshot: %s", rerr)
return err
}
d.op.Infof("Appliance is rolled back to old version")
d.deleteUpgradeImages(ds, settings)
d.retryDeleteSnapshotByRef(newSnapshotRef, snapshotName, conf.Name)
// return the error message for upgrade
return err
}
// compatible with old version's upgrade snapshot name
if oldSnapshot != nil && (vm.IsConfigureSnapshot(oldSnapshot, ConfigurePrefix) || vm.IsConfigureSnapshot(oldSnapshot, UpgradePrefix)) {
d.retryDeleteSnapshotByRef(&oldSnapshot.Snapshot, oldSnapshot.Name, conf.Name)
}
return nil
}
func (d *Dispatcher) rollbackResourceSettings(poolName string, settings *data.InstallerData) error {
if !settings.VCHSizeIsSet || d.oldVCHResources == nil {
d.op.Debug("VCH resource settings are not changed")
return nil
}
return updateResourcePoolConfig(d.op, d.vchPool, poolName, d.oldVCHResources)
}
func (d *Dispatcher) updateResourceSettings(poolName string, settings *data.InstallerData) error {
if !settings.VCHSizeIsSet {
d.op.Debug("VCH resource settings are not changed")
return nil
}
var err error
// compute resource
d.vchPool, err = d.appliance.ResourcePool(d.op)
if err != nil {
err = errors.Errorf("Failed to get parent resource pool %q: %s", poolName, err)
return err
}
oldSettings, err := d.getPoolResourceSettings(d.vchPool)
if err != nil {
err = errors.Errorf("Failed to get parent resource settings %q: %s", poolName, err)
return err
}
if reflect.DeepEqual(oldSettings, &settings.VCHSize) {
d.op.Debug("VCH resource settings are same as old value")
return nil
}
d.oldVCHResources = oldSettings
return updateResourcePoolConfig(d.op, d.vchPool, poolName, &settings.VCHSize)
}
func (d *Dispatcher) Rollback(vch *vm.VirtualMachine, conf *config.VirtualContainerHostConfigSpec, settings *data.InstallerData) error {
// some setup that is only necessary because we didn't just create a VCH in this case
d.appliance = vch
d.setDockerPort(conf, settings)
// ensure that we wait for components to come up
// TODO this stanza appears in Update too so we need to abstract it into a helper function
for _, s := range conf.ExecutorConfig.Sessions {
s.Started = ""
}
notfound := "A VCH version available from before the last upgrade could not be found."
snapshot, err := d.appliance.GetCurrentSnapshotTree(d.op)
if err != nil {
return errors.Errorf("%s An error was reported while trying to discover it: %s", notfound, err)
}
if snapshot == nil {
return errors.Errorf("%s No error was reported, so it's possible that this VCH has never been upgraded or the saved previous version was removed out-of-band.", notfound)
}
err = d.rollback(conf, snapshot.Name, settings)
if err != nil {
return errors.Errorf("could not complete manual rollback: %s", err)
}
return d.retryDeleteSnapshotByRef(&snapshot.Snapshot, snapshot.Name, conf.Name)
}
// retryDeleteSnapshotByRef will retry to delete snapshot by its reference if there is GenericVmConfigFault returned. This is a workaround for vSAN delete snapshot
func (d *Dispatcher) retryDeleteSnapshotByRef(snapshot *types.ManagedObjectReference, snapshotName, applianceName string) error {
// delete snapshot immediately after snapshot rollback usually fail in vSAN, so have to retry several times
operation := func() error {
return d.deleteSnapshotByRef(snapshot, snapshotName, applianceName)
}
var err error
if err = retry.Do(operation, isSystemError); err != nil {
d.op.Errorf("Failed to clean up appliance upgrade snapshot %q: %s.", snapshotName, err)
d.op.Errorf("Snapshot %q of appliance virtual machine %q MUST be removed manually before upgrade again", snapshotName, applianceName)
}
return err
}
func isSystemError(err error) bool {
if soap.IsSoapFault(err) {
if _, ok := soap.ToSoapFault(err).VimFault().(*types.SystemError); ok {
return true
}
}
if soap.IsVimFault(err) {
if _, ok := soap.ToVimFault(err).(*types.SystemError); ok {
return true
}
}
if terr, ok := err.(task.Error); ok {
if _, ok := terr.Fault().(*types.SystemError); ok {
return true
}
}
return false
}
func (d *Dispatcher) deleteSnapshotByRef(snapshot *types.ManagedObjectReference, snapshotName, applianceName string) error {
defer trace.End(trace.Begin(snapshotName, d.op))
d.op.Infof("Deleting upgrade snapshot %q", snapshotName)
// do clean up aggressively, even the previous operation failed with context deadline exceeded.
op := trace.NewOperationWithLoggerFrom(context.Background(), d.op, "deleteSnapshotByRef cleanup")
if _, err := d.appliance.WaitForResult(op, func(ctx context.Context) (tasks.Task, error) {
consolidate := true
return d.appliance.RemoveSnapshotByRef(ctx, snapshot, false, &consolidate)
}); err != nil {
return err
}
return nil
}
// tryCreateSnapshot try to create upgrade snapshot. It will check if upgrade snapshot already exists. If exists, return error.
// if succeed, return snapshot refID
func (d *Dispatcher) tryCreateSnapshot(name, desc string) (*types.ManagedObjectReference, error) {
defer trace.End(trace.Begin(name, d.op))
// TODO detect whether another upgrade is in progress & bail if it is.
// Use solution from https://github.com/vmware/vic/issues/4069 to do this either as part of 4069 or once it's closed
info, err := d.appliance.WaitForResult(d.op, func(ctx context.Context) (tasks.Task, error) {
return d.appliance.CreateSnapshot(ctx, name, desc, true, false)
})
if err != nil {
return nil, errors.Errorf("Failed to create upgrade snapshot %q: %s.", name, err)
}
return info.Entity, nil
}
func (d *Dispatcher) deleteUpgradeImages(ds *object.Datastore, settings *data.InstallerData) {
defer trace.End(trace.Begin("", d.op))
d.op.Info("Deleting upgrade images")
// do clean up aggressively, even the previous operation failed with context deadline exceeded.
d.op = trace.NewOperation(context.Background(), "deleteUpgradeImages cleanup")
m := ds.NewFileManager(d.session.Datacenter, true)
file := ds.Path(path.Join(d.vmPathName, settings.ApplianceISO))
if err := d.deleteVMFSFiles(m, ds, file); err != nil {
d.op.Warnf("Image file %q is not removed for %s. Use the vSphere UI to delete content", file, err)
}
file = ds.Path(path.Join(d.vmPathName, settings.BootstrapISO))
if err := d.deleteVMFSFiles(m, ds, file); err != nil {
d.op.Warnf("Image file %q is not removed for %s. Use the vSphere UI to delete content", file, err)
}
}
func (d *Dispatcher) update(conf *config.VirtualContainerHostConfigSpec, settings *data.InstallerData, isConfigureOp bool) error {
defer trace.End(trace.Begin(conf.Name, d.op))
power, err := d.appliance.PowerState(d.op)
if err != nil {
d.op.Errorf("Failed to get vm power status %q: %s", d.appliance.Reference(), err)
return err
}
if power != types.VirtualMachinePowerStatePoweredOff {
if _, err = d.appliance.WaitForResult(d.op, func(ctx context.Context) (tasks.Task, error) {
return d.appliance.PowerOff(ctx)
}); err != nil {
d.op.Errorf("Failed to power off appliance: %s", err)
return err
}
}
isoFile := ""
if settings.ApplianceISO != "" {
isoFile = fmt.Sprintf("[%s] %s/%s", conf.ImageStores[0].Host, d.vmPathName, settings.ApplianceISO)
}
// Create volume stores only for a configure operation, where conf has its storage fields validated.
if isConfigureOp {
if err := d.createVolumeStores(conf); err != nil {
return err
}
}
if err = d.reconfigVCH(conf, isoFile); err != nil {
return err
}
if err = d.startAppliance(conf); err != nil {
return err
}
op, cancel := trace.WithTimeout(&d.op, settings.Timeout, "CheckServiceReady during update")
defer cancel()
if err = d.CheckServiceReady(op, conf, nil); err != nil {
if op.Err() == context.DeadlineExceeded {
//context deadline exceeded, replace returned error message
err = errors.Errorf("Upgrading VCH exceeded time limit of %s. Please increase the timeout using --timeout to accommodate for a busy vSphere target", settings.Timeout)
}
d.op.Info("\tAPI may be slow to start - please retry with increased timeout using --timeout")
return err
}
return nil
}
func (d *Dispatcher) rollback(conf *config.VirtualContainerHostConfigSpec, snapshot string, settings *data.InstallerData) error {
defer trace.End(trace.Begin(fmt.Sprintf("old appliance iso: %q, snapshot: %q", d.oldApplianceISO, snapshot), d.op))
// do not power on appliance in this snapshot revert
d.op.Infof("Reverting to snapshot %s", snapshot)
if _, err := d.appliance.WaitForResult(d.op, func(ctx context.Context) (tasks.Task, error) {
return d.appliance.RevertToSnapshot(d.op, snapshot, true)
}); err != nil {
return errors.Errorf("Failed to roll back upgrade: %s.", err)
}
return d.ensureRollbackReady(conf, settings)
}
func (d *Dispatcher) ensureRollbackReady(conf *config.VirtualContainerHostConfigSpec, settings *data.InstallerData) error {
defer trace.End(trace.Begin(conf.Name, d.op))
power, err := d.appliance.PowerState(d.op)
if err != nil {
d.op.Errorf("Failed to get vm power status %q after rollback: %s", d.appliance.Reference(), err)
return err
}
if power == types.VirtualMachinePowerStatePoweredOff {
d.op.Info("Roll back finished - Appliance is kept in powered off status")
return nil
}
if err = d.startAppliance(conf); err != nil {
return err
}
op, cancel := trace.WithTimeout(&d.op, settings.Timeout, "CheckServiceReady during rollback")
defer cancel()
if err = d.CheckServiceReady(op, conf, nil); err != nil {
// do not return error in this case, to make sure clean up continues
d.op.Info("\tAPI may be slow to start - try to connect to API after a few minutes")
}
return nil
}
func (d *Dispatcher) reconfigVCH(conf *config.VirtualContainerHostConfigSpec, isoFile string) error {
defer trace.End(trace.Begin(isoFile, d.op))
spec := &types.VirtualMachineConfigSpec{}
if isoFile != "" {
deviceChange, err := d.switchISO(isoFile)
if err != nil {
return err
}
spec.DeviceChange = deviceChange
}
if conf != nil {
// reset service started attribute
for _, sess := range conf.ExecutorConfig.Sessions {
sess.Started = ""
sess.Active = true
}
if err := d.addExtraConfig(spec, conf); err != nil {
return err
}
}
if spec.DeviceChange == nil && spec.ExtraConfig == nil {
// nothing need to do
d.op.Debug("Nothing changed, no need to reconfigure appliance")
return nil
}
// reconfig
d.op.Info("Setting VM configuration")
info, err := d.appliance.WaitForResult(d.op, func(ctx context.Context) (tasks.Task, error) {
return d.appliance.Reconfigure(ctx, *spec)
})
if err != nil {
d.op.Errorf("Error while reconfiguring appliance: %s", err)
return err
}
if info.State != types.TaskInfoStateSuccess {
d.op.Errorf("Reconfiguring appliance reported: %s", info.Error.LocalizedMessage)
return err
}
return nil
}
func (d *Dispatcher) addExtraConfig(spec *types.VirtualMachineConfigSpec, conf *config.VirtualContainerHostConfigSpec) error {
if conf == nil {
return nil
}
cfg, err := d.encodeConfig(conf)
if err != nil {
return err
}
spec.ExtraConfig = append(spec.ExtraConfig, vmomi.OptionValueFromMap(cfg, true)...)
// get back old configuration, to remove keys not existed in new guestinfo. We don't care about value atm
oldConfig, err := d.GetNoSecretVCHConfig(d.appliance)
if err != nil {
return err
}
old := make(map[string]string)
extraconfig.Encode(extraconfig.MapSink(old), oldConfig)
for k := range old {
if _, ok := cfg[k]; !ok {
// set old key value to empty string, will remove that key from guestinfo
spec.ExtraConfig = append(spec.ExtraConfig, &types.OptionValue{Key: k, Value: ""})
}
}
return nil
}
func (d *Dispatcher) switchISO(filePath string) ([]types.BaseVirtualDeviceConfigSpec, error) {
defer trace.End(trace.Begin(filePath, d.op))
var devices object.VirtualDeviceList
var err error
d.op.Infof("Switching appliance iso to %s", filePath)
devices, err = d.appliance.Device(d.op)
if err != nil {
d.op.Errorf("Failed to get vm devices for appliance: %s", err)
return nil, err
}
// find the single cdrom
cd, err := devices.FindCdrom("")
if err != nil {
d.op.Errorf("Failed to get CD rom device from appliance: %s", err)
return nil, err
}
oldApplianceISO := cd.Backing.(*types.VirtualCdromIsoBackingInfo).FileName
if oldApplianceISO == filePath {
d.op.Debug("Target file name %q is same to old one, no need to change.")
return nil, nil
}
cd = devices.InsertIso(cd, filePath)
changedDevices := object.VirtualDeviceList([]types.BaseVirtualDevice{cd})
deviceChange, err := changedDevices.ConfigSpec(types.VirtualDeviceConfigSpecOperationEdit)
if err != nil {
d.op.Errorf("Failed to create config spec for appliance: %s", err)
return nil, err
}
d.oldApplianceISO = oldApplianceISO
return deviceChange, nil
}
// extractSecretFromFile reads and extracts the GuestInfoSecretKey value from the input.
func extractSecretFromFile(rc io.ReadCloser) (string, error) {
scanner := bufio.NewScanner(rc)
for scanner.Scan() {
line := scanner.Text()
// The line is of the format: key = "value"
if strings.HasPrefix(line, extraconfig.GuestInfoSecretKey) {
tokens := strings.SplitN(line, "=", 2)
if len(tokens) < 2 {
return "", fmt.Errorf("parse error: unexpected token count in line")
}
// Ensure that the key fully matches the secret key
if strings.Trim(tokens[0], ` `) != extraconfig.GuestInfoSecretKey {
continue
}
// Trim double quotes and spaces
return strings.Trim(tokens[1], `" `), nil
}
}
return "", errSecretKeyNotFound
}
// GuestInfoSecret downloads the VCH's .vmx file and returns the GuestInfoSecretKey value.
func (d *Dispatcher) GuestInfoSecret(vchName, vmPath string, ds *object.Datastore) (*extraconfig.SecretKey, error) {
defer trace.End(trace.Begin("", d.op))
if ds == nil {
return nil, errNilDatastore
}
helper, err := datastore.NewHelper(d.op, d.session, ds, vmPath)
if err != nil {
return nil, err
}
// Download the VCH's .vmx file
path := fmt.Sprintf("%s.vmx", vchName)
rc, err := helper.Download(d.op, path)
if err != nil {
return nil, err
}
secret, err := extractSecretFromFile(rc)
if err != nil {
return nil, err
}
secretKey := &extraconfig.SecretKey{}
if err = secretKey.FromString(secret); err != nil {
return nil, err
}
return secretKey, nil
}

View File

@@ -0,0 +1,214 @@
// 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 management
import (
"context"
"fmt"
"io/ioutil"
"path"
"testing"
"github.com/stretchr/testify/assert"
"github.com/vmware/govmomi/simulator"
"github.com/vmware/govmomi/vim25/types"
"github.com/vmware/vic/lib/config"
"github.com/vmware/vic/lib/install/data"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/vsphere/extraconfig"
"github.com/vmware/vic/pkg/vsphere/session"
"github.com/vmware/vic/pkg/vsphere/test"
"github.com/vmware/vic/pkg/vsphere/vm"
)
func TestGuestInfoSecret(t *testing.T) {
op := trace.NewOperation(context.Background(), "TestGuestInfoSecret")
for i, m := range []*simulator.Model{simulator.ESX(), simulator.VPX()} {
defer m.Remove()
err := m.Create()
if err != nil {
t.Fatal(err)
}
server := m.Service.NewServer()
defer server.Close()
var s *session.Session
if i == 0 {
s, err = test.SessionWithESX(op, server.URL.String())
} else {
s, err = test.SessionWithVPX(op, server.URL.String())
}
if err != nil {
t.Fatal(err)
}
name := "my-vm"
vmx := fmt.Sprintf("%s/%s.vmx", name, name)
ds := s.Datastore
secretKey, err := extraconfig.NewSecretKey()
if err != nil {
t.Fatal(err)
}
spec := types.VirtualMachineConfigSpec{
Name: name,
GuestId: string(types.VirtualMachineGuestOsIdentifierOtherGuest),
Files: &types.VirtualMachineFileInfo{
VmPathName: fmt.Sprintf("[%s] %s", ds.Name(), vmx),
},
ExtraConfig: []types.BaseOptionValue{
&types.OptionValue{
Key: extraconfig.GuestInfoSecretKey,
Value: secretKey.String(),
},
},
}
task, err := s.VMFolder.CreateVM(op, spec, s.Pool, nil)
if err != nil {
t.Fatal(err)
}
err = task.Wait(op)
if err != nil {
t.Fatal(err)
}
d := &Dispatcher{
session: s,
op: op,
vmPathName: name,
}
// Attempt to extract the secret without setting the session's datastore
secret, err := d.GuestInfoSecret(name, name, nil)
assert.Nil(t, secret)
assert.Equal(t, err, errNilDatastore)
// Attempt to extract the secret with an empty .vmx file
// TODO: simulator should write ExtraConfig to the .vmx file
secret, err = d.GuestInfoSecret(name, name, ds)
assert.Nil(t, secret)
assert.Equal(t, err, errSecretKeyNotFound)
// Write malformed key-value pairs
dir := simulator.Map.Get(ds.Reference()).(*simulator.Datastore).Info.(*types.LocalDatastoreInfo).Path
text := fmt.Sprintf("foo.bar = \"baz\"\n%s \"%s\"\n", extraconfig.GuestInfoSecretKey, secretKey.String())
if err = ioutil.WriteFile(path.Join(dir, vmx), []byte(text), 0); err != nil {
t.Fatal(err)
}
// Attempt to extract the secret from an incorrectly populated .vmx file
secret, err = d.GuestInfoSecret(name, name, ds)
assert.Nil(t, secret)
assert.Error(t, err)
// Write an invalid key that only prefix-matches the secret key
text = fmt.Sprintf("%s = \"%s\"\n", extraconfig.GuestInfoSecretKey+"1", secretKey.String())
if err = ioutil.WriteFile(path.Join(dir, vmx), []byte(text), 0); err != nil {
t.Fatal(err)
}
// Attempt to extract the secret from an incorrectly populated .vmx file
secret, err = d.GuestInfoSecret(name, name, ds)
assert.Nil(t, secret)
assert.Equal(t, err, errSecretKeyNotFound)
// Write valid key-value pairs
text = fmt.Sprintf("foo.bar = \"baz\"\n%s = \"%s\"\n", extraconfig.GuestInfoSecretKey, secretKey.String())
if err = ioutil.WriteFile(path.Join(dir, vmx), []byte(text), 0); err != nil {
t.Fatal(err)
}
// Extract the secret from a correctly populated .vmx file
secret, err = d.GuestInfoSecret(name, name, ds)
assert.NoError(t, err)
assert.Equal(t, secret.String(), secretKey.String())
}
}
func testUpdateResources(ctx context.Context, sess *session.Session, conf *config.VirtualContainerHostConfigSpec, vConf *data.InstallerData, hasErr bool, t *testing.T) {
op := trace.NewOperation(ctx, "testUpdateResources")
d := &Dispatcher{
session: sess,
op: op,
isVC: sess.IsVC(),
force: false,
}
appliance, err := sess.Finder.VirtualMachine(op, conf.Name)
if err != nil {
t.Errorf("Didn't find appliance vm: %s", err)
}
d.appliance = vm.NewVirtualMachine(op, sess, appliance.Reference())
settings := &data.InstallerData{}
limit := int64(1024)
settings.VCHSize.CPU.Limit = &limit
settings.VCHSize.Memory.Limit = &limit
settings.VCHSizeIsSet = true
if err = d.updateResourceSettings(conf.Name, settings); err != nil {
t.Errorf("Failed to update resources: %s", err)
}
newSettings, err := d.getPoolResourceSettings(d.vchPool)
if err != nil {
t.Errorf("Failed to get pool resources: %s", err)
}
assert.Equal(t, settings.VCHSize.CPU.Limit, newSettings.CPU.Limit, "Cpu limit is not updated")
assert.Equal(t, 0, d.oldVCHResources.CPU.Limit, "Old Cpu limit is not as expected")
d.oldVCHResources = nil
if err = d.updateResourceSettings(conf.Name, settings); err != nil {
t.Errorf("Failed to update resources: %s", err)
}
assert.Equal(t, d.oldVCHResources, nil, "should not update for same resource settings")
settings2 := &data.InstallerData{}
limit = int64(2048)
settings2.VCHSize.CPU.Limit = &limit
settings2.VCHSize.Memory.Limit = &limit
settings2.VCHSizeIsSet = false
if err = d.updateResourceSettings(conf.Name, settings); err != nil {
t.Errorf("Failed to update resources: %s", err)
}
assert.Equal(t, d.oldVCHResources, nil, "should not update if VCH size is not set")
settings2.VCHSizeIsSet = true
if err = d.updateResourceSettings(conf.Name, settings); err != nil {
t.Errorf("Failed to update resources: %s", err)
}
newSettings, err = d.getPoolResourceSettings(d.vchPool)
if err != nil {
t.Errorf("Failed to get pool resources: %s", err)
}
assert.Equal(t, settings2.VCHSize.CPU.Limit, newSettings.CPU.Limit, "Cpu limit is not updated")
if err = d.rollbackResourceSettings(conf.Name, settings); err != nil {
t.Errorf("Rollback failed: %s", err)
}
newSettings, err = d.getPoolResourceSettings(d.vchPool)
if err != nil {
t.Errorf("Failed to get pool resources: %s", err)
}
assert.Equal(t, settings.VCHSize.CPU.Limit, newSettings.CPU.Limit, "Cpu limit is not rollback")
}

View File

@@ -0,0 +1,269 @@
// 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 management
import (
"context"
"fmt"
"path"
"path/filepath"
"time"
"github.com/vmware/govmomi/object"
"github.com/vmware/vic/lib/config"
"github.com/vmware/vic/lib/install/data"
"github.com/vmware/vic/lib/install/opsuser"
"github.com/vmware/vic/lib/install/vchlog"
"github.com/vmware/vic/lib/progresslog"
"github.com/vmware/vic/pkg/errors"
"github.com/vmware/vic/pkg/retry"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/vsphere/tasks"
)
const (
uploadRetryLimit = 5
uploadMaxElapsedTime = 30 * time.Minute
uploadMaxInterval = 1 * time.Minute
uploadInitialInterval = 10 * time.Second
)
func (d *Dispatcher) CreateVCH(conf *config.VirtualContainerHostConfigSpec, settings *data.InstallerData, receiver vchlog.Receiver) error {
defer trace.End(trace.Begin(conf.Name, d.op))
var err error
if err = d.checkExistence(conf, settings); err != nil {
return err
}
if err = d.createPool(conf, settings); err != nil {
return err
}
if err = d.createBridgeNetwork(conf); err != nil {
d.cleanupAfterCreationFailed(conf, false)
return err
}
if err = d.createAppliance(conf, settings); err != nil {
d.cleanupAfterCreationFailed(conf, true)
return errors.Errorf("Creating the appliance failed with %s. Exiting...", err)
}
// send the signal to VCH logger to indicate VCH datastore path is ready
datastoreReadySignal := vchlog.DatastoreReadySignal{
Datastore: d.session.Datastore,
Name: "create",
Operation: d.op,
VMPathName: d.vmPathName,
Timestamp: time.Now(),
}
receiver.Signal(datastoreReadySignal)
if err = d.uploadImages(settings.ImageFiles); err != nil {
return errors.Errorf("Uploading images failed with %s. Exiting...", err)
}
if conf.ShouldGrantPerms() {
err = opsuser.GrantOpsUserPerms(d.op, d.session.Vim25(), conf)
if err != nil {
return errors.Errorf("Cannot init ops-user permissions, failure: %s. Exiting...", err)
}
}
return d.startAppliance(conf)
}
func (d *Dispatcher) createPool(conf *config.VirtualContainerHostConfigSpec, settings *data.InstallerData) error {
defer trace.End(trace.Begin("", d.op))
var err error
if d.vchPool, err = d.createResourcePool(conf, settings); err != nil {
detail := fmt.Sprintf("Creating resource pool failed: %s", err)
return errors.New(detail)
}
return nil
}
func (d *Dispatcher) startAppliance(conf *config.VirtualContainerHostConfigSpec) error {
defer trace.End(trace.Begin("", d.op))
var err error
_, err = d.appliance.WaitForResult(d.op, func(ctx context.Context) (tasks.Task, error) {
return d.appliance.PowerOn(ctx)
})
if err != nil {
return errors.Errorf("Failed to power on appliance %s. Exiting...", err)
}
return nil
}
func (d *Dispatcher) uploadImages(files map[string]string) error {
defer trace.End(trace.Begin("", d.op))
// upload the images
d.op.Info("Uploading images for container")
// Build retry config
backoffConf := retry.NewBackoffConfig()
backoffConf.InitialInterval = uploadInitialInterval
backoffConf.MaxInterval = uploadMaxInterval
backoffConf.MaxElapsedTime = uploadMaxElapsedTime
for key, image := range files {
baseName := filepath.Base(image)
finalMessage := ""
// upload function that is passed to retry
isoTargetPath := path.Join(d.vmPathName, key)
operationForRetry := func() error {
// attempt to delete the iso image first in case of failed upload
dc := d.session.Datacenter
fm := d.session.Datastore.NewFileManager(dc, false)
ds := d.session.Datastore
// check iso first
d.op.Debugf("Checking if file already exists: %s", isoTargetPath)
_, err := ds.Stat(d.op, isoTargetPath)
if err != nil {
switch err.(type) {
case object.DatastoreNoSuchFileError:
d.op.Debug("File not found. Nothing to do.")
case object.DatastoreNoSuchDirectoryError:
d.op.Debug("Directory not found. Nothing to do.")
default:
d.op.Debugf("ISO file already exists, deleting: %s", isoTargetPath)
err := fm.Delete(d.op, isoTargetPath)
if err != nil {
d.op.Debugf("Failed to delete image (%s) with error (%s)", image, err.Error())
return err
}
}
}
d.op.Infof("Uploading %s as %s", baseName, key)
ul := progresslog.NewUploadLogger(d.op.Infof, baseName, time.Second*3)
// need to wait since UploadLogger is asynchronous.
defer ul.Wait()
return d.session.Datastore.UploadFile(d.op, image, path.Join(d.vmPathName, key),
progresslog.UploadParams(ul))
}
// counter for retry decider
retryCount := uploadRetryLimit
// decider for our retry, will retry the upload uploadRetryLimit times
uploadRetryDecider := func(err error) bool {
if err == nil {
return false
}
retryCount--
if retryCount < 0 {
d.op.Warnf("Attempted upload a total of %d times without success, Upload process failed.", uploadRetryLimit)
return false
}
d.op.Warnf("Failed an attempt to upload isos with err (%s), %d retries remain", err.Error(), retryCount)
return true
}
uploadErr := retry.DoWithConfig(operationForRetry, uploadRetryDecider, backoffConf)
if uploadErr != nil {
finalMessage = fmt.Sprintf("\t\tUpload failed for %q: %s\n", image, uploadErr)
if d.force {
finalMessage = fmt.Sprintf("%s\t\tContinuing despite failures (due to --force option)\n", finalMessage)
finalMessage = fmt.Sprintf("%s\t\tNote: The VCH will not function without %q...", finalMessage, image)
}
d.op.Error(finalMessage)
return errors.New("Failed to upload iso images.")
}
}
return nil
}
// cleanupAfterCreationFailed cleans up the dangling resource pool for the failed VCH and any bridge network if there is any.
// The function will not abort and early terminate upon any error during cleanup process. Error details are logged.
func (d *Dispatcher) cleanupAfterCreationFailed(conf *config.VirtualContainerHostConfigSpec, cleanupNetwork bool) {
defer trace.End(trace.Begin(conf.Name, d.op))
var err error
d.op.Debug("Cleaning up dangling VCH resources after VCH creation failure.")
err = d.cleanupEmptyPool(conf)
if err != nil {
d.op.Errorf("Failed to clean up dangling VCH resource pool after VCH creation failure: %s", err)
} else {
d.op.Debug("Successfully cleaned up dangling resource pool.")
}
// only clean up bridge network created if told to
if cleanupNetwork {
err = d.cleanupBridgeNetwork(conf)
if err != nil {
d.op.Errorf("Failed to clean up dangling bridge network after VCH creation failure: %s", err)
} else {
d.op.Debug("Successfully cleaned up dangling bridge network.")
}
}
}
// cleanupEmptyPool cleans up any dangling empty VCH resource pool when creating this VCH. no-op when VCH pool is nonempty.
func (d *Dispatcher) cleanupEmptyPool(conf *config.VirtualContainerHostConfigSpec) error {
defer trace.End(trace.Begin(conf.Name, d.op))
var err error
d.parentResourcepool, err = d.getComputeResource(nil, conf)
if err != nil {
return err
}
defaultrp, err := d.session.Cluster.ResourcePool(d.op)
if err != nil {
return err
}
if d.parentResourcepool != nil && d.parentResourcepool.Reference() == defaultrp.Reference() {
d.op.Info("VCH resource pool is cluster default pool - skipping cleanup")
return nil
}
err = d.destroyResourcePoolIfEmpty(conf)
if err != nil {
return err
}
return nil
}
// cleanupBridgeNetwork cleans up any bridge networks created when creating this VCH. no-op for VCenter environment.
func (d *Dispatcher) cleanupBridgeNetwork(conf *config.VirtualContainerHostConfigSpec) error {
defer trace.End(trace.Begin(conf.Name, d.op))
err := d.removeNetwork(conf)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,254 @@
// 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 management
import (
"context"
"net/url"
"testing"
log "github.com/Sirupsen/logrus"
"github.com/vmware/govmomi/simulator"
"github.com/vmware/vic/lib/config"
"github.com/vmware/vic/lib/install/data"
"github.com/vmware/vic/lib/install/validate"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/vsphere/session"
)
func TestMain(t *testing.T) {
log.SetLevel(log.DebugLevel)
op := trace.NewOperation(context.Background(), "TestMain")
for i, model := range []*simulator.Model{simulator.ESX(), simulator.VPX()} {
t.Logf("%d", i)
defer model.Remove()
err := model.Create()
if err != nil {
t.Fatal(err)
}
s := model.Service.NewServer()
defer s.Close()
s.URL.User = url.UserPassword("user", "pass")
s.URL.Path = ""
t.Logf("server URL: %s", s.URL)
var input *data.Data
if i == 0 {
input = getESXData(s.URL)
} else {
input = getVPXData(s.URL)
}
if err != nil {
t.Fatal(err)
}
installSettings := &data.InstallerData{}
cpu := int64(1)
memory := int64(1024)
installSettings.ApplianceSize.CPU.Limit = &cpu
installSettings.ApplianceSize.Memory.Limit = &memory
validator, err := validate.NewValidator(op, input)
if err != nil {
t.Fatalf("Failed to validator: %s", err)
}
conf, err := validator.Validate(op, input)
if err != nil {
log.Errorf("Failed to validate conf: %s", err)
validator.ListIssues(op)
}
testCreateNetwork(op, validator.Session, conf, t)
testCreateVolumeStores(op, validator.Session, conf, false, t)
testDeleteVolumeStores(op, validator.Session, conf, 1, t)
errConf := &config.VirtualContainerHostConfigSpec{}
*errConf = *conf
errConf.VolumeLocations = make(map[string]*url.URL)
errConf.VolumeLocations["volume-store"], _ = url.Parse("ds://store_not_exist/volumes/test")
testCreateVolumeStores(op, validator.Session, errConf, true, t)
// FIXME: (pull vic/7088) have to make another VCH config from validator for negative test case and cleanup test
// If we re-use the previous validator like we did in the earlier test (*errConf = *conf), it's not a deep copy of conf
// This conf will get modified by appliance creation and cleanup test, and we can't test create appliance in positive case
// The other way around, if we test positive case first, the VCH data and session data are modified, so we are not able to test the negative case
conf2, err := validator.Validate(op, input)
conf2.ImageStores[0].Host = "http://non-exist"
testCreateAppliance(op, validator.Session, conf2, installSettings, true, t)
testCleanup(op, validator.Session, conf2, t)
testCreateAppliance(op, validator.Session, conf, installSettings, false, t)
// cannot run test for func not implemented in vcsim: ResourcePool:resourcepool-24 does not implement: UpdateConfig
// testUpdateResources(ctx, validator.Session, conf, installSettings, false, t)
}
}
func getESXData(esxURL *url.URL) *data.Data {
result := data.NewData()
result.URL = esxURL
result.DisplayName = "test001"
result.ComputeResourcePath = "/ha-datacenter/host/localhost.localdomain/Resources"
result.ImageDatastorePath = "LocalDS_0"
result.BridgeNetworkName = "bridge"
result.ManagementNetwork.Name = "VM Network"
result.PublicNetwork.Name = "VM Network"
result.VolumeLocations = make(map[string]*url.URL)
testURL := &url.URL{
Host: "LocalDS_0",
Path: "volumes/test",
}
result.VolumeLocations["volume-store"] = testURL
return result
}
func getVPXData(vcURL *url.URL) *data.Data {
result := data.NewData()
result.URL = vcURL
result.DisplayName = "test001"
result.ComputeResourcePath = "/DC0/host/DC0_C0/Resources"
result.ImageDatastorePath = "LocalDS_0"
result.PublicNetwork.Name = "VM Network"
result.BridgeNetworkName = "DC0_DVPG0"
result.VolumeLocations = make(map[string]*url.URL)
testURL := &url.URL{
Host: "LocalDS_0",
Path: "volumes/test",
}
result.VolumeLocations["volume-store"] = testURL
return result
}
func testCreateNetwork(op trace.Operation, sess *session.Session, conf *config.VirtualContainerHostConfigSpec, t *testing.T) {
d := &Dispatcher{
session: sess,
op: op,
isVC: sess.IsVC(),
force: false,
}
err := d.createBridgeNetwork(conf)
if err != nil {
t.Error(err)
}
if d.isVC {
bnet := conf.ExecutorConfig.Networks[conf.BridgeNetwork]
delete(conf.ExecutorConfig.Networks, conf.BridgeNetwork)
err = d.createBridgeNetwork(conf)
if err == nil {
t.Error("expected error")
}
conf.ExecutorConfig.Networks[conf.BridgeNetwork] = bnet
}
}
func testCreateVolumeStores(op trace.Operation, sess *session.Session, conf *config.VirtualContainerHostConfigSpec, hasErr bool, t *testing.T) {
d := &Dispatcher{
session: sess,
op: op,
isVC: sess.IsVC(),
force: false,
}
err := d.createVolumeStores(conf)
if hasErr && err != nil {
t.Logf("Got exepcted err: %s", err)
return
}
if hasErr {
t.Errorf("Should have error, but got success")
return
}
if err != nil {
t.Errorf("Unexpected error: %s", err)
}
}
func testDeleteVolumeStores(op trace.Operation, sess *session.Session, conf *config.VirtualContainerHostConfigSpec, numVols int, t *testing.T) {
d := &Dispatcher{
session: sess,
op: op,
isVC: sess.IsVC(),
force: true,
}
if removed := d.deleteVolumeStoreIfForced(conf, nil); removed != numVols {
t.Errorf("Did not successfully remove all specified volumes")
}
}
func testCleanup(op trace.Operation, sess *session.Session, conf *config.VirtualContainerHostConfigSpec, t *testing.T) {
d := &Dispatcher{
session: sess,
op: op,
isVC: sess.IsVC(),
force: true,
}
err := d.cleanupEmptyPool(conf)
if err != nil {
t.Fatal(err)
return
}
err = d.cleanupBridgeNetwork(conf)
if err != nil {
t.Fatal(err)
return
}
}
func testCreateAppliance(op trace.Operation, sess *session.Session, conf *config.VirtualContainerHostConfigSpec, vConf *data.InstallerData, hasErr bool, t *testing.T) {
d := &Dispatcher{
session: sess,
op: op,
isVC: sess.IsVC(),
force: false,
}
err := d.createPool(conf, vConf)
if err != nil {
if hasErr {
t.Logf("Got expected err: %s", err)
} else {
t.Fatal(err)
}
return
}
err = d.createAppliance(conf, vConf)
if err != nil {
if hasErr {
t.Logf("Got expected err: %s", err)
} else {
t.Fatal(err)
}
return
}
if hasErr {
t.Errorf("No error when error is expected.")
}
}

View File

@@ -0,0 +1,116 @@
// 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 management
import (
"context"
"github.com/vmware/govmomi/vim25/types"
"github.com/vmware/vic/lib/config"
"github.com/vmware/vic/pkg/errors"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/vsphere/vm"
)
func (d *Dispatcher) DebugVCH(vch *vm.VirtualMachine, conf *config.VirtualContainerHostConfigSpec, password, authorizedKey string) error {
defer trace.End(trace.Begin(conf.Name, d.op))
op := trace.FromContext(d.op, "enable appliance debug")
err := d.enableSSH(op, vch, password, authorizedKey)
if err != nil {
op.Errorf("Unable to enable ssh on the VCH appliance VM: %s", err)
return err
}
d.sshEnabled = true
return nil
}
func (d *Dispatcher) enableSSH(ctx context.Context, vch *vm.VirtualMachine, password, authorizedKey string) error {
op := trace.FromContext(ctx, "enable ssh in appliance")
state, err := vch.PowerState(op)
if err != nil {
op.Error("Failed to get appliance power state, service might not be available at this moment.")
}
if state != types.VirtualMachinePowerStatePoweredOn {
err = errors.Errorf("VCH appliance is not powered on, state %s", state)
op.Errorf("%s", err)
return err
}
running, err := vch.IsToolsRunning(op)
if err != nil || !running {
err = errors.New("Tools is not running in the appliance, unable to continue")
op.Errorf("%s", err)
return err
}
pm, err := d.opManager(vch)
if err != nil {
err = errors.Errorf("Unable to manage processes in appliance VM: %s", err)
op.Errorf("%s", err)
return err
}
auth := types.NamePasswordAuthentication{}
spec := types.GuestProgramSpec{
ProgramPath: "enable-ssh",
Arguments: string(authorizedKey),
}
pid, err := pm.StartProgram(op, &auth, &spec)
if err != nil {
err = errors.Errorf("Unable to enable SSH in appliance VM: %s", err)
op.Errorf("%s", err)
return err
}
_, err = d.opManagerWait(op, pm, &auth, pid)
if err != nil {
err = errors.Errorf("Unable to check enable SSH status: %s", err)
op.Errorf("%s", err)
return err
}
if password == "" {
return nil
}
// set the password as well
spec = types.GuestProgramSpec{
ProgramPath: "passwd",
Arguments: password,
}
pid, err = pm.StartProgram(op, &auth, &spec)
if err != nil {
err = errors.Errorf("Unable to enable passwd in appliance VM: %s", err)
op.Errorf("%s", err)
return err
}
_, err = d.opManagerWait(op, pm, &auth, pid)
if err != nil {
err = errors.Errorf("Unable to check enable passwd status: %s", err)
op.Errorf("%s", err)
return err
}
return nil
}

View File

@@ -0,0 +1,344 @@
// 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 management
import (
"context"
"strings"
"sync"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/types"
"github.com/vmware/vic/lib/config"
"github.com/vmware/vic/pkg/errors"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/vsphere/compute"
"github.com/vmware/vic/pkg/vsphere/tasks"
"github.com/vmware/vic/pkg/vsphere/vm"
)
type DeleteContainers int
const (
AllContainers DeleteContainers = iota
PoweredOffContainers
)
type DeleteVolumeStores int
const (
AllVolumeStores DeleteVolumeStores = iota
NoVolumeStores
)
func (d *Dispatcher) DeleteVCH(conf *config.VirtualContainerHostConfigSpec, containers *DeleteContainers, volumeStores *DeleteVolumeStores) error {
defer trace.End(trace.Begin(conf.Name, d.op))
var errs []string
var err error
var vmm *vm.VirtualMachine
if vmm, err = d.findApplianceByID(conf); err != nil {
return err
}
if vmm == nil {
return nil
}
d.parentResourcepool, err = d.getComputeResource(vmm, conf)
if err != nil {
d.op.Error(err)
if !d.force {
d.op.Infof("Specify --force to force delete")
return err
}
// Can't find the RP VCH was created in to delete cVMs, continue anyway
d.op.Warnf("No container VMs found, but proceeding with delete of VCH due to --force")
err = nil
}
if d.parentResourcepool != nil {
if err = d.DeleteVCHInstances(vmm, conf, containers); err != nil {
d.op.Error(err)
if !d.force {
// if container delete failed, do not remove anything else
d.op.Infof("Specify --force to force delete")
return err
}
d.op.Warnf("Proceeding with delete of VCH due to --force")
err = nil
}
}
if err = d.deleteImages(conf); err != nil {
errs = append(errs, err.Error())
}
d.deleteVolumeStoreIfForced(conf, volumeStores) // logs errors but doesn't ever bail out if it has an issue
if err = d.deleteNetworkDevices(vmm, conf); err != nil {
errs = append(errs, err.Error())
}
if err = d.removeNetwork(conf); err != nil {
errs = append(errs, err.Error())
}
if len(errs) > 0 {
// stop here, leave vch appliance there for next time delete
return errors.New(strings.Join(errs, "\n"))
}
err = d.deleteVM(vmm, true)
if err != nil {
d.op.Debugf("Error deleting appliance VM %s", err)
return err
}
defaultrp, err := d.session.Cluster.ResourcePool(d.op)
if err != nil {
return err
}
if d.parentResourcepool != nil && d.parentResourcepool.Reference() == defaultrp.Reference() {
d.op.Warnf("VCH resource pool is cluster default pool - skipping delete")
return nil
}
if err = d.destroyResourcePoolIfEmpty(conf); err != nil {
d.op.Warnf("VCH resource pool is not removed: %s", err)
}
return nil
}
func (d *Dispatcher) getComputeResource(vmm *vm.VirtualMachine, conf *config.VirtualContainerHostConfigSpec) (*compute.ResourcePool, error) {
var rpRef types.ManagedObjectReference
var err error
if len(conf.ComputeResources) == 0 {
err = errors.Errorf("Cannot find compute resource from configuration")
return nil, err
}
rpRef = conf.ComputeResources[len(conf.ComputeResources)-1]
ref, err := d.session.Finder.ObjectReference(d.op, rpRef)
if err != nil {
err = errors.Errorf("Failed to get VCH resource pool %q: %s", rpRef, err)
return nil, err
}
switch ref.(type) {
case *object.VirtualApp:
case *object.ResourcePool:
// ok
default:
err = errors.Errorf("Unsupported compute resource %q", rpRef)
return nil, err
}
rp := compute.NewResourcePool(d.op, d.session, ref.Reference())
return rp, nil
}
func (d *Dispatcher) getImageDatastore(vmm *vm.VirtualMachine, conf *config.VirtualContainerHostConfigSpec, force bool) (*object.Datastore, error) {
var err error
if conf == nil || len(conf.ImageStores) == 0 {
if !force {
err = errors.Errorf("Cannot find image stores from configuration")
return nil, err
}
d.op.Debug("Cannot find image stores from configuration; attempting to find from vm datastore")
dss, err := vmm.DatastoreReference(d.op)
if err != nil {
return nil, errors.Errorf("Failed to query vm datastore: %s", err)
}
if len(dss) == 0 {
return nil, errors.New("No VCH datastore found, cannot continue")
}
ds, err := d.session.Finder.ObjectReference(d.op, dss[0])
if err != nil {
return nil, errors.Errorf("Failed to search vm datastore %s: %s", dss[0], err)
}
return ds.(*object.Datastore), nil
}
ds, err := d.session.Finder.Datastore(d.op, conf.ImageStores[0].Host)
if err != nil {
err = errors.Errorf("Failed to find image datastore %q", conf.ImageStores[0].Host)
return nil, err
}
return ds, nil
}
// detach all VMDKs attached to vm
func (d *Dispatcher) detachAttachedDisks(v *vm.VirtualMachine) error {
devices, err := v.Device(d.op)
if err != nil {
d.op.Debugf("Couldn't find any devices to detach: %s", err.Error())
return nil
}
disks := devices.SelectByType(&types.VirtualDisk{})
if disks == nil {
// nothing attached
d.op.Debug("No disks found attached to VM")
return nil
}
config := []types.BaseVirtualDeviceConfigSpec{}
for _, disk := range disks {
config = append(config,
&types.VirtualDeviceConfigSpec{
Device: disk,
Operation: types.VirtualDeviceConfigSpecOperationRemove,
})
}
op := trace.NewOperation(d.op, "detach disks before delete")
_, err = v.WaitForResult(op,
func(ctx context.Context) (tasks.Task, error) {
t, er := v.Reconfigure(ctx,
types.VirtualMachineConfigSpec{DeviceChange: config})
if t != nil {
op.Debugf("Detach reconfigure task=%s", t.Reference())
}
return t, er
})
return err
}
func (d *Dispatcher) DeleteVCHInstances(vmm *vm.VirtualMachine, conf *config.VirtualContainerHostConfigSpec, containers *DeleteContainers) error {
defer trace.End(trace.Begin(conf.Name, d.op))
deletePoweredOnContainers := d.force || (containers != nil && *containers == AllContainers)
ignoreFailureToFindImageStores := d.force
d.op.Info("Removing VMs")
// serializes access to errs
var mu sync.Mutex
var errs []string
var err error
var children []*vm.VirtualMachine
if children, err = d.parentResourcepool.GetChildrenVMs(d.op, d.session); err != nil {
return err
}
if d.session.Datastore, err = d.getImageDatastore(vmm, conf, ignoreFailureToFindImageStores); err != nil {
return err
}
var wg sync.WaitGroup
for _, child := range children {
//Leave VCH appliance there until everything else is removed, cause it has VCH configuration. Then user could retry delete in case of any failure.
ok, err := d.isVCH(child)
if err != nil {
errs = append(errs, err.Error())
continue
}
if ok {
// Do not delete a VCH in the target RP if it is not the target VCH
if child.Reference() != vmm.Reference() {
d.op.Debugf("Skipping VCH in the resource pool that is not the targeted VCH: %s", child)
continue
}
// child is the target vch; detach all attached disks so later removal of images is successful
if err = d.detachAttachedDisks(child); err != nil {
errs = append(errs, err.Error())
}
continue
}
ok, err = d.isContainerVM(child)
if err != nil {
errs = append(errs, err.Error())
continue
}
if !ok {
d.op.Debugf("Skipping VM in the resource pool that is not a container VM: %s", child)
continue
}
wg.Add(1)
go func(child *vm.VirtualMachine) {
defer wg.Done()
if err = d.deleteVM(child, deletePoweredOnContainers); err != nil {
mu.Lock()
errs = append(errs, err.Error())
mu.Unlock()
}
}(child)
}
wg.Wait()
if len(errs) > 0 {
d.op.Debugf("Error deleting container VMs %s", errs)
return errors.New(strings.Join(errs, "\n"))
}
return nil
}
func (d *Dispatcher) deleteNetworkDevices(vmm *vm.VirtualMachine, conf *config.VirtualContainerHostConfigSpec) error {
defer trace.End(trace.Begin(conf.Name, d.op))
d.op.Info("Removing appliance VM network devices")
power, err := vmm.PowerState(d.op)
if err != nil {
d.op.Errorf("Failed to get vm power status %q: %s", vmm.Reference(), err)
return err
}
if power != types.VirtualMachinePowerStatePoweredOff {
if _, err = vmm.WaitForResult(d.op, func(ctx context.Context) (tasks.Task, error) {
return vmm.PowerOff(ctx)
}); err != nil {
d.op.Errorf("Failed to power off existing appliance for %s", err)
return err
}
}
devices, err := d.networkDevices(vmm)
if err != nil {
d.op.Errorf("Unable to get network devices: %s", err)
return err
}
if len(devices) == 0 {
d.op.Info("No network device attached")
return nil
}
// remove devices
return vmm.RemoveDevice(d.op, false, devices...)
}
func (d *Dispatcher) networkDevices(vmm *vm.VirtualMachine) ([]types.BaseVirtualDevice, error) {
defer trace.End(trace.Begin("", d.op))
var err error
vmDevices, err := vmm.Device(d.op)
if err != nil {
d.op.Errorf("Failed to get vm devices for appliance: %s", err)
return nil, err
}
var devices []types.BaseVirtualDevice
for _, device := range vmDevices {
if _, ok := device.(types.BaseVirtualEthernetCard); ok {
devices = append(devices, device)
}
}
return devices, nil
}

View File

@@ -0,0 +1,271 @@
// 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 management
import (
"context"
"io/ioutil"
"net/url"
"os"
"path"
"testing"
log "github.com/Sirupsen/logrus"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/simulator"
"github.com/vmware/govmomi/vim25/types"
"github.com/vmware/vic/lib/config"
"github.com/vmware/vic/lib/install/data"
"github.com/vmware/vic/lib/install/validate"
"github.com/vmware/vic/pkg/errors"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/vsphere/session"
)
func TestDelete(t *testing.T) {
log.SetLevel(log.DebugLevel)
trace.Logger.Level = log.DebugLevel
ctx := context.Background()
op := trace.NewOperation(ctx, "TestDelete")
for i, model := range []*simulator.Model{simulator.ESX(), simulator.VPX()} {
t.Logf("%d", i)
defer model.Remove()
err := model.Create()
if err != nil {
t.Fatal(err)
}
s := model.Service.NewServer()
defer s.Close()
s.URL.User = url.UserPassword("user", "pass")
s.URL.Path = ""
t.Logf("server URL: %s", s.URL)
var input *data.Data
if i == 0 {
input = getESXData(s.URL)
} else {
input = getVPXData(s.URL)
}
if err != nil {
t.Fatal(err)
}
installSettings := &data.InstallerData{}
cpu := int64(1)
memory := int64(1024)
installSettings.ApplianceSize.CPU.Limit = &cpu
installSettings.ApplianceSize.Memory.Limit = &memory
installSettings.ResourcePoolPath = path.Join(input.ComputeResourcePath, input.DisplayName)
validator, err := validate.NewValidator(ctx, input)
if err != nil {
t.Errorf("Failed to validator: %s", err)
}
conf, err := validator.Validate(ctx, input)
if err != nil {
log.Errorf("Failed to validate conf: %s", err)
validator.ListIssues(op)
}
testCreateNetwork(op, validator.Session, conf, t)
createAppliance(ctx, validator.Session, conf, installSettings, false, t)
testNewVCHFromCompute(input.ComputeResourcePath, input.DisplayName, validator, t)
// testUpgrade(input.ComputeResourcePath, input.DisplayName, validator, installSettings, t) TODO: does not implement: CreateSnapshot_Task
testDeleteVCH(validator, conf, t)
testDeleteDatastoreFiles(validator, t)
}
}
func testUpgrade(computePath string, name string, v *validate.Validator, settings *data.InstallerData, t *testing.T) {
// TODO: add tests for rollback after snapshot func is added in vcsim
d := &Dispatcher{
session: v.Session,
op: trace.FromContext(v.Context, "testUpgrade"),
isVC: v.Session.IsVC(),
force: false,
}
vch, err := d.NewVCHFromComputePath(computePath, name, v)
if err != nil {
t.Errorf("Failed to get VCH: %s", err)
return
}
t.Logf("Got VCH %s, path %s", vch, path.Dir(vch.InventoryPath))
conf, err := d.GetVCHConfig(vch)
if err != nil {
t.Errorf("Failed to get vch configuration: %s", err)
}
if err := d.Configure(vch, conf, settings, false); err != nil {
t.Errorf("Failed to upgrade: %s", err)
}
}
func createAppliance(ctx context.Context, sess *session.Session, conf *config.VirtualContainerHostConfigSpec, vConf *data.InstallerData, hasErr bool, t *testing.T) {
var err error
d := &Dispatcher{
session: sess,
op: trace.FromContext(ctx, "createAppliance"),
isVC: sess.IsVC(),
force: false,
}
err = d.createPool(conf, vConf)
if err != nil {
t.Fatal(err)
}
err = d.createAppliance(conf, vConf)
if err != nil {
t.Fatal(err)
}
}
func testNewVCHFromCompute(computePath string, name string, v *validate.Validator, t *testing.T) {
d := &Dispatcher{
session: v.Session,
op: trace.FromContext(v.Context, "testNewVCHFromCompute"),
isVC: v.Session.IsVC(),
force: false,
}
vch, err := d.NewVCHFromComputePath(computePath, name, v)
if err != nil {
t.Errorf("Failed to get VCH: %s", err)
return
}
if d.session.Cluster == nil {
t.Errorf("Failed to set cluster: %s", err)
return
}
t.Logf("Got VCH %s, path %s", vch, path.Dir(vch.InventoryPath))
}
func testDeleteVCH(v *validate.Validator, conf *config.VirtualContainerHostConfigSpec, t *testing.T) {
d := &Dispatcher{
session: v.Session,
op: trace.FromContext(v.Context, "testDeleteVCH"),
isVC: v.Session.IsVC(),
force: false,
}
// failed to get vm FolderName, that will eventually cause panic in simulator to delete empty datastore file
if err := d.DeleteVCH(conf, nil, nil); err != nil {
t.Errorf("Failed to get VCH: %s", err)
return
}
t.Logf("Successfully deleted VCH")
// check images directory is removed
dsPath := "[LocalDS_0] VIC"
_, err := d.lsFolder(v.Session.Datastore, dsPath)
if err != nil {
if !types.IsFileNotFound(err) {
t.Errorf("Failed to browse folder %s: %s", dsPath, errors.ErrorStack(err))
}
t.Logf("Images Folder is not found")
}
// check appliance vm is deleted
vm, err := d.findApplianceByID(conf)
if vm != nil {
t.Errorf("Should not found vm %s", vm.Reference())
}
if err != nil {
t.Errorf("Unexpected error to get appliance VM: %s", err)
}
// delete VM does not clean up resource pool after VM is removed, so resource pool could not be removed
}
func testDeleteDatastoreFiles(v *validate.Validator, t *testing.T) {
d := &Dispatcher{
session: v.Session,
op: trace.FromContext(v.Context, "testDeleteDatastoreFiles"),
isVC: v.Session.IsVC(),
force: false,
}
ds := v.Session.Datastore
m := object.NewFileManager(ds.Client())
err := m.MakeDirectory(v.Context, ds.Path("Test/folder/data"), v.Session.Datacenter, true)
if err != nil {
t.Errorf("Failed to create datastore dir: %s", err)
return
}
err = m.MakeDirectory(v.Context, ds.Path("Test/folder/metadata"), v.Session.Datacenter, true)
if err != nil {
t.Errorf("Failed to create datastore dir: %s", err)
return
}
err = m.MakeDirectory(v.Context, ds.Path("Test/folder/file"), v.Session.Datacenter, true)
if err != nil {
t.Errorf("Failed to create datastore dir: %s", err)
return
}
isVSAN := d.isVSAN(ds)
t.Logf("datastore is vsan: %t", isVSAN)
if err = createDatastoreFiles(d, ds, t); err != nil {
t.Errorf("Failed to upload file: %s", err)
return
}
fm := ds.NewFileManager(d.session.Datacenter, true)
if err = d.deleteFilesIteratively(fm, ds, ds.Path("Test")); err != nil {
t.Errorf("Failed to delete recursively: %s", err)
}
err = m.MakeDirectory(v.Context, ds.Path("Test/folder/data"), v.Session.Datacenter, true)
if err != nil {
t.Errorf("Failed to create datastore dir: %s", err)
return
}
if err = createDatastoreFiles(d, ds, t); err != nil {
t.Errorf("Failed to upload file: %s", err)
return
}
if _, err = d.deleteDatastoreFiles(ds, "Test", true); err != nil {
t.Errorf("Failed to delete recursively: %s", err)
}
}
func createDatastoreFiles(d *Dispatcher, ds *object.Datastore, t *testing.T) error {
tmpfile, err := ioutil.TempFile("", "tempDatastoreFile.vmdk")
if err != nil {
t.Errorf("Failed to create file: %s", err)
return err
}
defer os.Remove(tmpfile.Name()) // clean up
if err = ds.UploadFile(d.op, tmpfile.Name(), "Test/folder/data/temp.vmdk", nil); err != nil {
t.Errorf("Failed to upload file %q: %s", "Test/folder/data/temp.vmdk", err)
return err
}
if err = ds.UploadFile(d.op, tmpfile.Name(), "Test/folder/tempMetadata", nil); err != nil {
t.Errorf("Failed to upload file %q: %s", "Test/folder/tempMetadata", err)
return err
}
return nil
}

View File

@@ -0,0 +1,433 @@
// 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 management
import (
"context"
"crypto/x509"
"errors"
"fmt"
"math"
"net"
"os"
"strings"
"time"
"github.com/vmware/govmomi/guest"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/types"
"github.com/vmware/vic/lib/config"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/vsphere/compute"
"github.com/vmware/vic/pkg/vsphere/diagnostic"
"github.com/vmware/vic/pkg/vsphere/extraconfig"
"github.com/vmware/vic/pkg/vsphere/session"
"github.com/vmware/vic/pkg/vsphere/vm"
)
type Dispatcher struct {
session *session.Session
op trace.Operation
force bool
secret *extraconfig.SecretKey
isVC bool
vchPoolPath string
vmPathName string
dockertlsargs string
DockerPort string
HostIP string
vchPool *object.ResourcePool
vchVapp *object.VirtualApp
appliance *vm.VirtualMachine
oldApplianceISO string
oldVCHResources *config.Resources
sshEnabled bool
parentResourcepool *compute.ResourcePool
}
type diagnosticLog struct {
key string
name string
start int32
host *object.HostSystem
collect bool
}
var diagnosticLogs = make(map[string]*diagnosticLog)
// NewDispatcher creates a dispatcher that can act upon VIC management operations.
// clientCert is an optional client certificate to allow interaction with the Docker API for verification
// force will ignore some errors
func NewDispatcher(ctx context.Context, s *session.Session, conf *config.VirtualContainerHostConfigSpec, force bool) *Dispatcher {
defer trace.End(trace.Begin(""))
isVC := s.IsVC()
e := &Dispatcher{
session: s,
op: trace.FromContext(ctx, "Dispatcher"),
isVC: isVC,
force: force,
}
if conf != nil {
e.InitDiagnosticLogsFromConf(conf)
}
return e
}
// Get the current log header LineEnd of the hostd/vpxd logs based on VCH configuration
// With this we avoid collecting log file data that existed prior to install.
func (d *Dispatcher) InitDiagnosticLogsFromConf(conf *config.VirtualContainerHostConfigSpec) {
defer trace.End(trace.Begin(""))
if d.isVC {
diagnosticLogs[d.session.ServiceContent.About.InstanceUuid] =
&diagnosticLog{"vpxd:vpxd.log", "vpxd.log", 0, nil, true}
}
var err error
// try best to get datastore and cluster, but do not return for any error. The least is to collect VC log only
if d.session.Datastore == nil {
if len(conf.ImageStores) > 0 {
if d.session.Datastore, err = d.session.Finder.DatastoreOrDefault(d.op, conf.ImageStores[0].Host); err != nil {
d.op.Debugf("Failure finding image store from VCH config (%s): %s", conf.ImageStores[0].Host, err.Error())
} else {
d.op.Debugf("Found ds: %s", conf.ImageStores[0].Host)
}
} else {
d.op.Debug("Image datastore is empty")
}
}
// find the host(s) attached to given storage
if d.session.Cluster == nil {
if len(conf.ComputeResources) > 0 {
rp := compute.NewResourcePool(d.op, d.session, conf.ComputeResources[0])
if d.session.Cluster, err = rp.GetCluster(d.op); err != nil {
d.op.Debugf("Unable to get cluster for given resource pool %s: %s", conf.ComputeResources[0], err)
}
} else {
d.op.Debug("Compute resource is empty")
}
}
var hosts []*object.HostSystem
if d.session.Datastore != nil && d.session.Cluster != nil {
hosts, err = d.session.Datastore.AttachedClusterHosts(d.op, d.session.Cluster)
if err != nil {
d.op.Debugf("Unable to get the list of hosts attached to given storage: %s", err)
}
}
if d.session.Host == nil {
// vCenter w/ auto DRS.
// Set collect=false here as we do not want to collect all hosts logs,
// just the hostd log where the VM is placed.
for _, host := range hosts {
diagnosticLogs[host.Reference().Value] =
&diagnosticLog{"hostd", "hostd.log", 0, host, false}
}
} else {
// vCenter w/ manual DRS or standalone ESXi
var host *object.HostSystem
if d.isVC {
host = d.session.Host
}
diagnosticLogs[d.session.Host.Reference().Value] =
&diagnosticLog{"hostd", "hostd.log", 0, host, true}
}
m := diagnostic.NewDiagnosticManager(d.session)
for k, l := range diagnosticLogs {
if l == nil {
continue
}
// get LineEnd without any LineText
h, err := m.BrowseLog(d.op, l.host, l.key, math.MaxInt32, 0)
if err != nil {
d.op.Warnf("Disabling %s %s collection (%s)", k, l.name, err)
diagnosticLogs[k] = nil
continue
}
l.start = h.LineEnd
}
}
// Get the current log header LineEnd of the hostd/vpxd logs based on vch VM hardwares, cause VCH configuration might not be available at this time
// With this we avoid collecting log file data that existed prior to install.
func (d *Dispatcher) InitDiagnosticLogsFromVCH(vch *vm.VirtualMachine) {
defer trace.End(trace.Begin(""))
if d.isVC {
diagnosticLogs[d.session.ServiceContent.About.InstanceUuid] =
&diagnosticLog{"vpxd:vpxd.log", "vpxd.log", 0, nil, true}
}
var err error
// where the VM is running
ds, err := d.getImageDatastore(vch, nil, true)
if err != nil {
d.op.Debugf("Failure finding image store from VCH VM %s: %s", vch.Reference(), err.Error())
}
var hosts []*object.HostSystem
if ds != nil && d.session.Cluster != nil {
hosts, err = ds.AttachedClusterHosts(d.op, d.session.Cluster)
if err != nil {
d.op.Debugf("Unable to get the list of hosts attached to given storage: %s", err)
}
}
for _, host := range hosts {
diagnosticLogs[host.Reference().Value] =
&diagnosticLog{"hostd", "hostd.log", 0, host, false}
}
m := diagnostic.NewDiagnosticManager(d.session)
for k, l := range diagnosticLogs {
if l == nil {
continue
}
// get LineEnd without any LineText
h, err := m.BrowseLog(d.op, l.host, l.key, math.MaxInt32, 0)
if err != nil {
d.op.Warnf("Disabling %s %s collection (%s)", k, l.name, err)
diagnosticLogs[k] = nil
continue
}
l.start = h.LineEnd
}
}
func (d *Dispatcher) CollectDiagnosticLogs() {
defer trace.End(trace.Begin(""))
m := diagnostic.NewDiagnosticManager(d.session)
for k, l := range diagnosticLogs {
if l == nil || !l.collect {
continue
}
d.op.Infof("Collecting %s %s", k, l.name)
var lines []string
start := l.start
for i := 0; i < 2; i++ {
h, err := m.BrowseLog(d.op, l.host, l.key, start, 0)
if err != nil {
d.op.Errorf("Failed to collect %s %s: %s", k, l.name, err)
break
}
lines = h.LineText
if len(lines) != 0 {
break // l.start was still valid, log was not rolled over
}
// log rolled over, start at the beginning.
// TODO: If this actually happens we will have missed some log data,
// it is possible to get data from the previous log too.
start = 0
d.op.Infof("%s %s rolled over", k, l.name)
}
if len(lines) == 0 {
d.op.Warnf("No log data for %s %s", k, l.name)
continue
}
f, err := os.Create(l.name)
if err != nil {
d.op.Errorf("Failed to create local %s: %s", l.name, err)
continue
}
defer f.Close()
for _, line := range lines {
fmt.Fprintln(f, line)
}
}
}
func (d *Dispatcher) opManager(vch *vm.VirtualMachine) (*guest.ProcessManager, error) {
state, err := vch.PowerState(d.op)
if err != nil {
return nil, fmt.Errorf("Failed to get appliance power state, service might not be available at this moment.")
}
if state != types.VirtualMachinePowerStatePoweredOn {
return nil, fmt.Errorf("VCH appliance is not powered on, state %s", state)
}
running, err := vch.IsToolsRunning(d.op)
if err != nil || !running {
return nil, errors.New("Tools are not running in the appliance, unable to continue")
}
manager := guest.NewOperationsManager(d.session.Client.Client, vch.Reference())
processManager, err := manager.ProcessManager(d.op)
if err != nil {
return nil, fmt.Errorf("Unable to manage processes in appliance VM: %s", err)
}
return processManager, nil
}
// opManagerWait polls for state of the process with the given pid, waiting until the process has completed.
// The pid param must be one returned by ProcessManager.StartProgram.
func (d *Dispatcher) opManagerWait(op trace.Operation, pm *guest.ProcessManager, auth types.BaseGuestAuthentication, pid int64) (*types.GuestProcessInfo, error) {
pids := []int64{pid}
for {
select {
case <-time.After(time.Millisecond * 250):
case <-op.Done():
return nil, fmt.Errorf("opManagerWait(%d): %s", pid, op.Err())
}
procs, err := pm.ListProcesses(op, auth, pids)
if err != nil {
return nil, err
}
if len(procs) == 1 && procs[0].EndTime != nil {
return &procs[0], nil
}
}
}
func (d *Dispatcher) CheckAccessToVCAPI(vch *vm.VirtualMachine, target string) (int64, error) {
pm, err := d.opManager(vch)
if err != nil {
return -1, err
}
auth := types.NamePasswordAuthentication{}
spec := types.GuestProgramSpec{
ProgramPath: "test-vc-api",
Arguments: target,
}
pid, err := pm.StartProgram(d.op, &auth, &spec)
if err != nil {
return -1, err
}
info, err := d.opManagerWait(d.op, pm, &auth, pid)
if err != nil {
return -1, err
}
return int64(info.ExitCode), nil
}
// addrToUse given candidateIPs, determines an address in cert that resolves to
// a candidateIP - this address can be used as the remote address to connect to with
// cert to ensure that certificate validation is successful
// if none can be found, return empty string and an err
func addrToUse(op trace.Operation, candidateIPs []net.IP, cert *x509.Certificate, cas []byte) (string, error) {
if cert == nil {
return "", errors.New("unable to determine suitable address with nil certificate")
}
pool, err := x509.SystemCertPool()
if err != nil {
op.Warnf("Failed to load system cert pool: %s. Using empty pool.", err)
pool = x509.NewCertPool()
}
pool.AppendCertsFromPEM(cas)
// update target to use FQDN
for _, ip := range candidateIPs {
names, err := net.LookupAddr(ip.String())
if err != nil {
op.Debugf("Unable to perform reverse lookup of IP address %s: %s", ip, err)
}
// check all the returned names, and lastly the raw IP
for _, n := range append(names, ip.String()) {
opts := x509.VerifyOptions{
Roots: pool,
DNSName: n,
}
_, err := cert.Verify(opts)
if err == nil {
// this identifier will work
op.Debugf("Matched %q for use against host certificate", n)
// trim '.' fqdn suffix if fqdn
return strings.TrimSuffix(n, "."), nil
}
op.Debugf("Checked %q, no match for host certificate", n)
}
}
// no viable address
return "", errors.New("unable to determine viable address")
}
/// viableHostAddresses attempts to determine which possibles addresses in the certificate
// are viable from the current location.
// This will return all IP addresses - it attempts to validate DNS names via resolution.
// This does NOT check connectivity
func viableHostAddress(op trace.Operation, candidateIPs []net.IP, cert *x509.Certificate, cas []byte) (string, error) {
if cert == nil {
return "", fmt.Errorf("unable to determine suitable address with nil certificate")
}
op.Debug("Loading CAs for client auth")
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(cas)
dnsnames := cert.DNSNames
// assemble the common name and alt names
ip := net.ParseIP(cert.Subject.CommonName)
if ip != nil {
candidateIPs = append(candidateIPs, ip)
} else {
// assume it's dns
dnsnames = append([]string{cert.Subject.CommonName}, dnsnames...)
}
// turn the DNS names into IPs
for _, n := range dnsnames {
// see which resolve from here
ips, err := net.LookupIP(n)
if err != nil {
op.Debugf("Unable to perform IP lookup of %q: %s", n, err)
}
// Allow wildcard names for later validation
if len(ips) == 0 && !strings.HasPrefix(n, "*") {
op.Debugf("Discarding name from viable set: %s", n)
continue
}
candidateIPs = append(candidateIPs, ips...)
}
// always add all the altname IPs - we're not checking for connectivity
candidateIPs = append(candidateIPs, cert.IPAddresses...)
return addrToUse(op, candidateIPs, cert, cas)
}

View File

@@ -0,0 +1,344 @@
// 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 management
import (
"fmt"
"path"
"github.com/vmware/govmomi/find"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/types"
"github.com/vmware/vic/lib/config"
"github.com/vmware/vic/lib/install/validate"
"github.com/vmware/vic/lib/migration"
"github.com/vmware/vic/pkg/errors"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/vsphere/compute"
"github.com/vmware/vic/pkg/vsphere/extraconfig"
"github.com/vmware/vic/pkg/vsphere/extraconfig/vmomi"
"github.com/vmware/vic/pkg/vsphere/vm"
)
const (
vchIDType = "VirtualMachine"
)
func (d *Dispatcher) NewVCHFromID(id string) (*vm.VirtualMachine, error) {
defer trace.End(trace.Begin(id, d.op))
var err error
var vmm *vm.VirtualMachine
moref := &types.ManagedObjectReference{
Type: vchIDType,
Value: id,
}
ref, err := d.session.Finder.ObjectReference(d.op, *moref)
if err != nil {
if !isManagedObjectNotFoundError(err) {
err = errors.Errorf("Failed to query appliance (%q): %s", moref, err)
return nil, err
}
d.op.Debug("Appliance is not found")
return nil, fmt.Errorf("id %q could not be found", id)
}
ovm, ok := ref.(*object.VirtualMachine)
if !ok {
d.op.Errorf("Failed to find VM %q: %s", moref, err)
return nil, err
}
vmm = vm.NewVirtualMachine(d.op, d.session, ovm.Reference())
// check if it's VCH
if ok, err = d.isVCH(vmm); err != nil {
d.op.Error(err)
return nil, err
}
if !ok {
err = errors.Errorf("Not a VCH")
d.op.Error(err)
return nil, err
}
pool, err := vmm.ResourcePool(d.op)
if err != nil {
d.op.Errorf("Failed to get VM parent resource pool: %s", err)
return nil, err
}
rp := compute.NewResourcePool(d.op, d.session, pool.Reference())
if d.session.Cluster, err = rp.GetCluster(d.op); err != nil {
d.op.Debugf("Unable to get the cluster for the VCH's resource pool: %s", err)
}
d.InitDiagnosticLogsFromVCH(vmm)
return vmm, nil
}
func (d *Dispatcher) NewVCHFromComputePath(computePath string, name string, v *validate.Validator) (*vm.VirtualMachine, error) {
defer trace.End(trace.Begin(fmt.Sprintf("path %q, name %q", computePath, name), d.op))
var err error
parent, err := v.ResourcePoolHelper(d.op, computePath)
if err != nil {
return nil, err
}
d.vchPoolPath = path.Join(parent.InventoryPath, name)
var vchPool *object.ResourcePool
if d.isVC {
vapp, err := d.findVirtualApp(d.vchPoolPath)
if err != nil {
d.op.Errorf("Failed to get VCH virtual app %q: %s", d.vchPoolPath, err)
return nil, err
}
if vapp != nil {
vchPool = vapp.ResourcePool
}
}
if vchPool == nil {
vchPool, err = d.session.Finder.ResourcePool(d.op, d.vchPoolPath)
if err != nil {
d.op.Errorf("Failed to get VCH resource pool %q: %s", d.vchPoolPath, err)
return nil, err
}
}
rp := compute.NewResourcePool(d.op, d.session, vchPool.Reference())
if d.session.Cluster, err = rp.GetCluster(d.op); err != nil {
d.op.Debugf("Unable to get the cluster for the VCH's resource pool: %s", err)
}
var vmm *vm.VirtualMachine
if vmm, err = rp.GetChildVM(d.op, d.session, name); err != nil {
d.op.Errorf("Failed to get VCH VM: %s", err)
return nil, err
}
if vmm == nil {
err = errors.Errorf("Didn't find VM %q in resource pool %q", name, rp.Reference())
d.op.Error(err)
return nil, err
}
vmm.InventoryPath = path.Join(d.vchPoolPath, name)
// check if it's VCH
var ok bool
if ok, err = d.isVCH(vmm); err != nil {
d.op.Error(err)
return nil, err
}
if !ok {
err = errors.Errorf("Not a VCH")
d.op.Error(err)
return nil, err
}
d.InitDiagnosticLogsFromVCH(vmm)
return vmm, nil
}
// GetVCHConfig queries VCH configuration and decrypts secret information
func (d *Dispatcher) GetVCHConfig(vm *vm.VirtualMachine) (*config.VirtualContainerHostConfigSpec, error) {
defer trace.End(trace.Begin("", d.op))
//this is the appliance vm
mapConfig, err := vm.FetchExtraConfigBaseOptions(d.op)
if err != nil {
err = errors.Errorf("Failed to get VM extra config of %q: %s", vm.Reference(), err)
d.op.Error(err)
return nil, err
}
kv := vmomi.OptionValueMap(mapConfig)
vchConfig, err := d.decryptVCHConfig(vm, kv)
if err != nil {
err = errors.Errorf("Failed to decode VM configuration %q: %s", vm.Reference(), err)
d.op.Error(err)
return nil, err
}
if vchConfig.IsCreating() {
vmRef := vm.Reference()
vchConfig.SetMoref(&vmRef)
}
return vchConfig, nil
}
// GetNoSecretVCHConfig queries vch configure from vm configuration, without decrypting secret information
// this method is used to accommodate old vch version without secret information
func (d *Dispatcher) GetNoSecretVCHConfig(vm *vm.VirtualMachine) (*config.VirtualContainerHostConfigSpec, error) {
defer trace.End(trace.Begin("", d.op))
//this is the appliance vm
mapConfig, err := vm.FetchExtraConfigBaseOptions(d.op)
if err != nil {
err = errors.Errorf("Failed to get VM extra config of %q: %s", vm.Reference(), err)
d.op.Error(err)
return nil, err
}
kv := vmomi.OptionValueMap(mapConfig)
vchConfig := &config.VirtualContainerHostConfigSpec{}
extraconfig.Decode(extraconfig.MapSource(kv), vchConfig)
if vchConfig.IsCreating() {
vmRef := vm.Reference()
vchConfig.SetMoref(&vmRef)
}
return vchConfig, nil
}
// FetchAndMigrateVCHConfig queries VCH guestinfo, and try to migrate older version data to latest if the data is old
func (d *Dispatcher) FetchAndMigrateVCHConfig(vm *vm.VirtualMachine) (*config.VirtualContainerHostConfigSpec, error) {
defer trace.End(trace.Begin("", d.op))
//this is the appliance vm
mapConfig, err := vm.FetchExtraConfigBaseOptions(d.op)
if err != nil {
err = errors.Errorf("Failed to get VM extra config of %q: %s", vm.Reference(), err)
return nil, err
}
kv := vmomi.OptionValueMap(mapConfig)
newMap, migrated, err := migration.MigrateApplianceConfig(d.op, d.session, kv)
if err != nil {
err = errors.Errorf("Failed to migrate config of %q: %s", vm.Reference(), err)
return nil, err
}
if !migrated {
d.op.Debugf("No need to migrate configuration for %q", vm.Reference())
}
return d.decryptVCHConfig(vm, newMap)
}
func (d *Dispatcher) SearchVCHs(computePath string) ([]*vm.VirtualMachine, error) {
defer trace.End(trace.Begin(computePath, d.op))
if computePath != "" {
return d.searchVCHsFromComputePath(computePath)
}
if d.session.Datacenter != nil {
return d.searchVCHsPerDC(d.session.Datacenter)
}
dcs, err := d.session.Finder.DatacenterList(d.op, "*")
if err != nil {
err = errors.Errorf("Failed to get datacenter list: %s", err)
return nil, err
}
var vchs []*vm.VirtualMachine
for _, dc := range dcs {
dcVCHs, err := d.searchVCHsPerDC(dc)
if err != nil {
return nil, err
}
vchs = append(vchs, dcVCHs...)
}
return vchs, nil
}
// searchVCHsFromComputePath searches for VCHs in all child ResourcePools under computePath.
// The computePath can itself be a ResourcePool, ComputeResource or ClusterComputeResource.
func (d *Dispatcher) searchVCHsFromComputePath(computePath string) ([]*vm.VirtualMachine, error) {
defer trace.End(trace.Begin(computePath, d.op))
pools, err := d.session.Finder.ResourcePoolList(d.op, path.Join(computePath, "..."))
if err != nil {
if _, ok := err.(*find.NotFoundError); ok {
return nil, nil
}
}
var vchs []*vm.VirtualMachine
for _, pool := range pools {
children, err := d.getChildVCHs(pool, true)
if err != nil {
return nil, err
}
vchs = append(vchs, children...)
}
return vchs, nil
}
func (d *Dispatcher) searchVCHsPerDC(dc *object.Datacenter) ([]*vm.VirtualMachine, error) {
defer trace.End(trace.Begin(dc.InventoryPath, d.op))
var err error
var pools []*object.ResourcePool
d.session.Datacenter = dc
d.session.Finder.SetDatacenter(dc)
var vchs []*vm.VirtualMachine
if pools, err = d.session.Finder.ResourcePoolList(d.op, "*"); err != nil {
if _, ok := err.(*find.NotFoundError); ok {
return vchs, nil
}
err = errors.Errorf("Failed to search resource pools for datacenter %q: %s", dc.InventoryPath, err)
return nil, err
}
for _, pool := range pools {
children, err := d.getChildVCHs(pool, true)
if err != nil {
return nil, err
}
vchs = append(vchs, children...)
}
return vchs, nil
}
// getVCHs will check vm with same name under this resource pool, to see if that's VCH vm, and it will also check children vApp, to see if that's a VCH.
// eventually return all fond VCH VMs
func (d *Dispatcher) getChildVCHs(pool *object.ResourcePool, searchVapp bool) ([]*vm.VirtualMachine, error) {
defer trace.End(trace.Begin(pool.InventoryPath, d.op))
// check if pool itself contains VCH vm.
var vchs []*vm.VirtualMachine
poolName := pool.Name()
computeResource := compute.NewResourcePool(d.op, d.session, pool.Reference())
vmm, err := computeResource.GetChildVM(d.op, d.session, poolName)
if err != nil {
return nil, errors.Errorf("Failed to query children VM in resource pool %q: %s", pool.InventoryPath, err)
}
if vmm != nil {
vmm.InventoryPath = path.Join(pool.InventoryPath, poolName)
// #nosec: Errors unhandled.
if ok, _ := d.isVCH(vmm); ok {
d.op.Debugf("%q is VCH", vmm.InventoryPath)
vchs = append(vchs, vmm)
}
}
if !searchVapp {
return vchs, nil
}
vappPath := path.Join(pool.InventoryPath, "*")
vapps, err := d.session.Finder.VirtualAppList(d.op, vappPath)
if err != nil {
if _, ok := err.(*find.NotFoundError); ok {
return vchs, nil
}
d.op.Debugf("Failed to query vapp %q: %s", vappPath, err)
}
for _, vapp := range vapps {
childVCHs, err := d.getChildVCHs(vapp.ResourcePool, false)
if err != nil {
return nil, err
}
vchs = append(vchs, childVCHs...)
}
return vchs, nil
}

View File

@@ -0,0 +1,346 @@
// 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 management
import (
"context"
"fmt"
"net/url"
"path"
"testing"
log "github.com/Sirupsen/logrus"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/property"
"github.com/vmware/govmomi/simulator"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
"github.com/vmware/vic/lib/install/data"
"github.com/vmware/vic/lib/install/validate"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/vsphere/extraconfig"
"github.com/vmware/vic/pkg/vsphere/session"
"github.com/vmware/vic/pkg/vsphere/tasks"
)
func TestFinder(t *testing.T) {
log.SetLevel(log.DebugLevel)
trace.Logger.Level = log.DebugLevel
ctx := context.Background()
for i, model := range []*simulator.Model{simulator.ESX(), simulator.VPX()} {
t.Logf("%d", i)
defer model.Remove()
if i == 1 {
model.Datacenter = 2
model.Cluster = 2
model.Host = 2
model.Pool = 0
}
err := model.Create()
if err != nil {
t.Fatal(err)
}
s := model.Service.NewServer()
defer s.Close()
s.URL.User = url.UserPassword("user", "pass")
s.URL.Path = ""
t.Logf("server URL: %s", s.URL)
var input *data.Data
if i == 0 {
input = getESXData(s.URL)
} else {
input = getVPXData(s.URL)
}
if err != nil {
t.Fatal(err)
}
validator, err := validate.NewValidator(ctx, input)
if err != nil {
t.Errorf("Failed to create validator: %s", err)
}
if _, err = validator.ValidateTarget(ctx, input); err != nil {
t.Logf("Got expected error to validate target: %s", err)
}
validator.AllowEmptyDC()
if _, err = validator.ValidateTarget(ctx, input); err != nil {
t.Errorf("Failed to valiate target: %s", err)
}
prefix := fmt.Sprintf("p%d-", i)
if err = createTestData(ctx, validator.Session, prefix); err != nil {
t.Errorf("Failed to create test data: %s", err)
}
found := testSearchVCHs(t, validator, false)
if found != 0 {
t.Errorf("found %d VCHs, expected %d", found, 0)
}
found = testSearchVCHs(t, validator, true)
expect := 1 // 1 VCH per Resource pool
if model.Host != 0 {
expect *= (model.Host + model.Cluster) * 2
}
if found != expect {
t.Errorf("found %d VCHs, expected %d", found, expect)
}
}
}
func testSearchVCHs(t *testing.T, v *validate.Validator, expect bool) int {
d := &Dispatcher{
session: v.Session,
op: trace.FromContext(v.Context, "testSearchVCHs"),
isVC: v.Session.IsVC(),
}
if expect {
// Add guestinfo so isVCH() returns true for all VMs
vms, err := d.session.Finder.VirtualMachineList(d.op, "/...")
if err != nil {
t.Fatal(err)
}
for _, vm := range vms {
ref := vm.Reference()
svm := simulator.Map.Get(ref).(*simulator.VirtualMachine)
svm.Config.ExtraConfig = []types.BaseOptionValue{&types.OptionValue{
Key: extraconfig.DefaultGuestInfoPrefix + "/init/common/id",
Value: ref.String(),
}}
}
} else {
_, err := d.SearchVCHs("enoent") // NotFound
if err != nil {
t.Error(err)
}
n, err := d.SearchVCHs("/")
if err != nil {
t.Error(err)
}
if len(n) != 0 {
t.Errorf("unexpected: %d", len(n))
}
}
vchs, err := d.SearchVCHs("")
if err != nil {
t.Errorf("Failed to search vchs: %s", err)
}
n := len(vchs)
t.Logf("Found %d VCHs without a compute-path", n)
nexpect := 1
if d.isVC {
nexpect = 2 // 1 for the top-level vApp, VC only
}
for _, vm := range vchs {
// Find with --compute-resource
// The VM HostSystem's parent will be a cluster or standalone compute resource
host, err := vm.HostSystem(d.op)
if err != nil {
t.Fatal(err)
}
c := property.DefaultCollector(vm.VirtualMachine.Client())
var me mo.ManagedEntity
err = c.RetrieveOne(d.op, host.Reference(), []string{"parent"}, &me)
if err != nil {
t.Fatal(err)
}
obj, err := d.session.Finder.Element(d.op, *me.Parent)
if err != nil {
t.Fatal(err)
}
name := path.Base(obj.Path)
paths := []string{
obj.Path, // "/dc1/cluster1"
name, // "cluster1"
path.Join(".", name), // "./cluster1"
}
for _, path := range paths {
vchs, err := d.SearchVCHs(path)
if err != nil {
t.Errorf("SearchVCHs(%s): %s", path, err)
}
if len(vchs) != nexpect {
t.Errorf("Found %d VCHs with compute-path=%s", len(vchs), path)
}
}
}
return n
}
func createTestData(ctx context.Context, sess *session.Session, prefix string) error {
dcs, err := sess.Finder.DatacenterList(ctx, "*")
if err != nil {
return err
}
kind := rpNode
if sess.IsVC() {
kind = vappNode
}
for _, dc := range dcs {
sess.Config.DatacenterPath = dc.InventoryPath
sess.Populate(ctx)
resources := &Node{
Kind: rpNode,
Name: prefix + "Root",
Children: []*Node{
{
Kind: rpNode,
Name: prefix + "pool1",
Children: []*Node{
{
Kind: vmNode,
Name: prefix + "pool1",
},
{
Kind: rpNode,
Name: prefix + "pool1-2",
Children: []*Node{
{
Kind: kind,
Name: prefix + "pool1-2-1",
Children: []*Node{
{
Kind: vmNode,
Name: prefix + "vch1-2-1",
},
},
},
},
},
},
},
{
Kind: vmNode,
Name: prefix + "vch2",
},
},
}
if err = createResources(ctx, sess, resources); err != nil {
return err
}
if sess.IsVC() {
// Test with a top-level VApp
vapp := &Node{
Kind: vappNode,
Name: prefix + "VApp",
}
if err = createResources(ctx, sess, vapp); err != nil {
return err
}
}
}
return nil
}
type nodeKind string
const (
vmNode = nodeKind("VM")
rpNode = nodeKind("RP")
vappNode = nodeKind("VAPP")
)
type Node struct {
Kind nodeKind
Name string
Children []*Node
}
func createResources(ctx context.Context, sess *session.Session, node *Node) error {
rootPools, err := sess.Finder.ResourcePoolList(ctx, "Resources")
if err != nil {
return err
}
for _, pool := range rootPools {
base := path.Base(path.Dir(pool.InventoryPath))
log.Debugf("root pool base name %q", base)
if err = createNodes(ctx, sess, pool, node, base); err != nil {
return err
}
}
return nil
}
func createNodes(ctx context.Context, sess *session.Session, pool *object.ResourcePool, node *Node, base string) error {
log.Debugf("create node %+v", node)
if node == nil {
return nil
}
spec := types.DefaultResourceConfigSpec()
node.Name = fmt.Sprintf("%s-%s", base, node.Name)
switch node.Kind {
case rpNode:
child, err := pool.Create(ctx, node.Name, spec)
if err != nil {
return err
}
for _, childNode := range node.Children {
return createNodes(ctx, sess, child, childNode, base)
}
case vappNode:
confSpec := simulator.NewVAppConfigSpec()
vapp, err := pool.CreateVApp(ctx, node.Name, spec, confSpec, nil)
if err != nil {
return err
}
config := types.VirtualMachineConfigSpec{
Name: node.Name,
GuestId: string(types.VirtualMachineGuestOsIdentifierOtherGuest),
Files: &types.VirtualMachineFileInfo{
VmPathName: fmt.Sprintf("[LocalDS_0] %s", node.Name),
},
}
if _, err = tasks.WaitForResult(ctx, func(ctx context.Context) (tasks.Task, error) {
return vapp.CreateChildVM(ctx, config, nil)
}); err != nil {
return err
}
case vmNode:
config := types.VirtualMachineConfigSpec{
Name: node.Name,
GuestId: string(types.VirtualMachineGuestOsIdentifierOtherGuest),
Files: &types.VirtualMachineFileInfo{
VmPathName: fmt.Sprintf("[LocalDS_0] %s", node.Name),
},
}
if _, err := tasks.WaitForResult(ctx, func(ctx context.Context) (tasks.Task, error) {
return sess.VMFolder.CreateVM(ctx, config, pool, nil)
}); err != nil {
return err
}
default:
return nil
}
return nil
}

View File

@@ -0,0 +1,232 @@
// 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 management
import (
"fmt"
"io/ioutil"
"net"
"os"
"os/user"
"path/filepath"
"strings"
"github.com/docker/docker/opts"
"github.com/vmware/govmomi/vim25/types"
"github.com/vmware/vic/lib/config"
"github.com/vmware/vic/lib/constants"
"github.com/vmware/vic/pkg/certificate"
"github.com/vmware/vic/pkg/errors"
"github.com/vmware/vic/pkg/ip"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/vsphere/vm"
)
func (d *Dispatcher) InspectVCH(vch *vm.VirtualMachine, conf *config.VirtualContainerHostConfigSpec, certPath string) error {
defer trace.End(trace.Begin(conf.Name, d.op))
state, err := vch.PowerState(d.op)
if err != nil {
d.op.Error("Failed to get VM power state, service might not be available at this moment.")
}
if state != types.VirtualMachinePowerStatePoweredOn {
err = errors.Errorf("VCH is not powered on, state %s", state)
d.op.Errorf("%s", err)
return err
}
var clientIP net.IP
var publicIP net.IP
clientNet := conf.ExecutorConfig.Networks["client"]
if clientNet != nil {
clientIP = clientNet.Assigned.IP
}
publicNet := conf.ExecutorConfig.Networks["public"]
if publicNet != nil {
publicIP = publicNet.Assigned.IP
}
if ip.IsUnspecifiedIP(clientIP) {
err = errors.Errorf("No client IP address assigned")
d.op.Errorf("%s", err)
return err
}
if ip.IsUnspecifiedIP(publicIP) {
err = errors.Errorf("No public IP address assigned")
d.op.Errorf("%s", err)
return err
}
d.HostIP = clientIP.String()
d.op.Debugf("IP address for client interface: %s", d.HostIP)
if !conf.HostCertificate.IsNil() {
d.DockerPort = fmt.Sprintf("%d", opts.DefaultTLSHTTPPort)
} else {
d.DockerPort = fmt.Sprintf("%d", opts.DefaultHTTPPort)
}
// try looking up preferred name, irrespective of CAs
if cert, err := conf.HostCertificate.X509Certificate(); err == nil {
// #nosec: Errors unhandled.
name, _ := viableHostAddress(d.op, []net.IP{clientIP}, cert, conf.CertificateAuthorities)
if name != "" {
d.op.Debugf("Retrieved proposed name from host certificate: %q", name)
d.op.Debugf("Assigning first name from set: %s", name)
if name != d.HostIP {
d.op.Infof("Using address from host certificate over allocated IP: %s", d.HostIP)
// reassign
d.HostIP = name
}
} else {
d.op.Warn("Unable to identify address acceptable to host certificate")
}
} else {
d.op.Debug("No host certificates provided")
}
// Check for valid client cert for a tls-verify configuration
if len(conf.CertificateAuthorities) > 0 {
possibleCertPaths := findCertPaths(d.op, conf.Name, certPath)
// Check if a valid client cert exists in one of possibleCertPaths
certPath = ""
for _, path := range possibleCertPaths {
certFile := filepath.Join(path, certificate.ClientCert)
keyFile := filepath.Join(path, certificate.ClientKey)
ckp := certificate.NewKeyPair(certFile, keyFile, nil, nil)
if err = ckp.LoadCertificate(); err != nil {
d.op.Debugf("Unable to load client cert in %s: %s", path, err)
continue
}
if _, err = certificate.VerifyClientCert(conf.CertificateAuthorities, ckp); err != nil {
d.op.Debug(err)
continue
}
certPath = path
break
}
}
d.ShowVCH(conf, "", "", "", "", certPath)
return nil
}
// findCertPaths returns candidate paths for client certs depending on whether
// a certPath was specified in the CLI.
func findCertPaths(op trace.Operation, vchName, certPath string) []string {
var possibleCertPaths []string
if certPath != "" {
op.Infof("--tls-cert-path supplied - only checking for certs in %s/", certPath)
possibleCertPaths = append(possibleCertPaths, certPath)
return possibleCertPaths
}
possibleCertPaths = append(possibleCertPaths, vchName, ".")
logMsg := fmt.Sprintf("--tls-cert-path not supplied - checking for certs in current directory, %s/", vchName)
dockerConfPath := ""
user, err := user.Current()
if err == nil {
dockerConfPath = filepath.Join(user.HomeDir, ".docker")
possibleCertPaths = append(possibleCertPaths, dockerConfPath)
logMsg = fmt.Sprintf("%s and %s/", logMsg, dockerConfPath)
}
op.Info(logMsg)
return possibleCertPaths
}
func (d *Dispatcher) ShowVCH(conf *config.VirtualContainerHostConfigSpec, key string, cert string, cacert string, envfile string, certpath string) {
if d.sshEnabled {
d.op.Info("")
d.op.Info("SSH to appliance:")
d.op.Infof("ssh root@%s", d.HostIP)
}
d.op.Info("")
d.op.Info("VCH Admin Portal:")
d.op.Infof("https://%s:%d", d.HostIP, constants.VchAdminPortalPort)
d.op.Info("")
publicIP := conf.ExecutorConfig.Networks["public"].Assigned.IP
d.op.Info("Published ports can be reached at:")
d.op.Infof("%s", publicIP.String())
cmd, env := d.GetDockerAPICommand(conf, key, cert, cacert, certpath)
d.op.Info("")
d.op.Info("Docker environment variables:")
d.op.Info(env)
if envfile != "" {
if err := ioutil.WriteFile(envfile, []byte(env), 0644); err == nil {
d.op.Info("")
d.op.Infof("Environment saved in %s", envfile)
}
}
d.op.Info("")
d.op.Info("Connect to docker:")
d.op.Info(cmd)
}
// GetDockerAPICommand generates values to display for usage of a deployed VCH
func (d *Dispatcher) GetDockerAPICommand(conf *config.VirtualContainerHostConfigSpec, key string, cert string, cacert string, certpath string) (cmd, env string) {
var dEnv []string
tls := ""
if d.HostIP == "" {
return "", ""
}
if !conf.HostCertificate.IsNil() {
// if we're generating then there's no CA currently
if len(conf.CertificateAuthorities) > 0 {
// find the name to use
if key != "" {
tls = fmt.Sprintf(" --tlsverify --tlscacert=%q --tlscert=%q --tlskey=%q", cacert, cert, key)
} else {
tls = fmt.Sprintf(" --tlsverify")
}
dEnv = append(dEnv, "DOCKER_TLS_VERIFY=1")
info, err := os.Stat(certpath)
if err == nil && info.IsDir() {
if abs, err := filepath.Abs(info.Name()); err == nil {
dEnv = append(dEnv, fmt.Sprintf("DOCKER_CERT_PATH=%s", abs))
}
} else {
d.op.Info("")
d.op.Warn("Unable to find valid client certs")
d.op.Warn("DOCKER_CERT_PATH must be provided in environment or certificates specified individually via CLI arguments")
}
} else {
tls = " --tls"
}
}
dEnv = append(dEnv, fmt.Sprintf("DOCKER_HOST=%s:%s", d.HostIP, d.DockerPort))
cmd = fmt.Sprintf("docker -H %s:%s%s info", d.HostIP, d.DockerPort, tls)
env = strings.Join(dEnv, " ")
return cmd, env
}

View File

@@ -0,0 +1,54 @@
// 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 management
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/vmware/vic/pkg/trace"
)
func TestFindCertPaths(t *testing.T) {
op := trace.NewOperation(context.Background(), "TestFindCertPaths")
vchName := "vch-foo"
// NOTE: not checking for dockerConfPath since $HOME is dependent on the user
// running the test
possiblePaths := map[string]bool{
vchName: false,
".": false,
}
// Get paths when an input certPath is not specified
paths := findCertPaths(op, vchName, "")
assert.True(t, len(paths) >= 2)
for i := range paths {
possiblePaths[paths[i]] = true
}
assert.True(t, possiblePaths[vchName])
assert.True(t, possiblePaths["."])
// Get paths when an input certPath is specified
paths = findCertPaths(op, vchName, "foopath")
for i := range paths {
possiblePaths[paths[i]] = true
}
assert.True(t, len(paths) == 1)
assert.True(t, possiblePaths["foopath"])
}

View File

@@ -0,0 +1,143 @@
// 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 management
import (
"fmt"
"github.com/vmware/govmomi/find"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/types"
"github.com/vmware/vic/lib/config"
"github.com/vmware/vic/pkg/errors"
"github.com/vmware/vic/pkg/trace"
)
func (d *Dispatcher) createBridgeNetwork(conf *config.VirtualContainerHostConfigSpec) error {
defer trace.End(trace.Begin("", d.op))
// if the bridge network is already extant there's nothing to do
bnet := conf.ExecutorConfig.Networks[conf.BridgeNetwork]
if bnet != nil && bnet.ID != "" {
return nil
}
// network didn't exist during validation given we don't have a moref, so create it
if d.session.Client.IsVC() {
// double check
return errors.New("bridge network must already exist for vCenter environments")
}
// in this case the name to use is held in container network ID
name := bnet.Network.ID
d.op.Infof("Creating VirtualSwitch")
hostNetSystem, err := d.session.Host.ConfigManager().NetworkSystem(d.op)
if err != nil {
err = errors.Errorf("Failed to retrieve host network system: %s", err)
return err
}
if err = hostNetSystem.AddVirtualSwitch(d.op, name, &types.HostVirtualSwitchSpec{
NumPorts: 1024,
}); err != nil {
err = errors.Errorf("Failed to add virtual switch (%q): %s", name, err)
return err
}
d.op.Infof("Creating Portgroup")
if err = hostNetSystem.AddPortGroup(d.op, types.HostPortGroupSpec{
Name: name,
VlanId: 1, // TODO: expose this for finer grained grouping within the switch
VswitchName: name,
Policy: types.HostNetworkPolicy{},
}); err != nil {
err = errors.Errorf("Failed to add port group (%q): %s", name, err)
return err
}
net, err := d.session.Finder.Network(d.op, name)
if err != nil {
_, ok := err.(*find.NotFoundError)
if !ok {
err = errors.Errorf("Failed to query virtual switch (%q): %s", name, err)
return err
}
}
// assign the moref to the bridge network config on the appliance
bnet.ID = net.Reference().String()
bnet.Network.ID = net.Reference().String()
conf.CreateBridgeNetwork = true
d.op.Debugf("Created portgroup %q: %s", name, net)
return nil
}
func (d *Dispatcher) removeNetwork(conf *config.VirtualContainerHostConfigSpec) error {
defer trace.End(trace.Begin(conf.Name, d.op))
if d.session.IsVC() {
d.op.Debugf("Remove network is not supported for vCenter")
return nil
}
if !conf.CreateBridgeNetwork {
d.op.Infof("Bridge network was not created during VCH deployment, leaving it there")
return nil
}
br := conf.ExecutorConfig.Networks["bridge"]
if br == nil {
return fmt.Errorf("Bridge Network ID is unknown")
}
name := br.Network.ID
d.op.Debugf("Remove bridge network based on %s", name)
moref := types.ManagedObjectReference{}
ok := moref.FromString(name)
if !ok {
return fmt.Errorf("Unable to delete port group - failed to get moref from: %q", name)
}
net, err := d.session.Finder.ObjectReference(d.op, moref)
if err != nil {
return fmt.Errorf("Unable to delete port group - failed to find network from: %q", name)
}
d.op.Debugf("Delete bridge network: %s", net)
netw, ok := net.(*object.Network)
if !ok {
d.op.Errorf("Expected Network Type, got %#v", net)
return fmt.Errorf("Failed to get network for %q", moref)
}
pgName := netw.Name()
hostNetSystem, err := d.session.Host.ConfigManager().NetworkSystem(d.op)
if err != nil {
return err
}
d.op.Infof("Removing Portgroup %q", pgName)
err = hostNetSystem.RemovePortGroup(d.op, pgName)
if err != nil {
return err
}
d.op.Infof("Removing VirtualSwitch %q", pgName)
err = hostNetSystem.RemoveVirtualSwitch(d.op, pgName)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,160 @@
// 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 management
import (
"context"
"fmt"
"path"
"github.com/vmware/govmomi/find"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
"github.com/vmware/vic/lib/config"
"github.com/vmware/vic/lib/install/data"
"github.com/vmware/vic/pkg/errors"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/vsphere/tasks"
"github.com/vmware/vic/pkg/vsphere/vm"
)
func (d *Dispatcher) createResourcePool(conf *config.VirtualContainerHostConfigSpec, settings *data.InstallerData) (*object.ResourcePool, error) {
defer trace.End(trace.Begin("", d.op))
d.vchPoolPath = path.Join(settings.ResourcePoolPath, conf.Name)
rp, err := d.session.Finder.ResourcePool(d.op, d.vchPoolPath)
if err != nil {
// if we didn't find the resource pool then we will create
_, ok := err.(*find.NotFoundError)
if !ok {
err = errors.Errorf("Failed to query compute resource (%q): %q", d.vchPoolPath, err)
return nil, err
}
} else {
conf.ComputeResources = append(conf.ComputeResources, rp.Reference())
return rp, nil
}
d.op.Infof("Creating Resource Pool %q", conf.Name)
resSpec := types.DefaultResourceConfigSpec()
setResources(&resSpec.CpuAllocation, settings.VCHSize.CPU)
setResources(&resSpec.MemoryAllocation, settings.VCHSize.Memory)
rp, err = d.session.Pool.Create(d.op, conf.Name, resSpec)
if err != nil {
d.op.Debugf("Failed to create resource pool %q: %s", d.vchPoolPath, err)
return nil, err
}
conf.ComputeResources = append(conf.ComputeResources, rp.Reference())
return rp, nil
}
// setResources will modify the resourceAllocation spec with the user provided allocation info
func setResources(spec *types.ResourceAllocationInfo, resource types.ResourceAllocationInfo) {
if resource.Limit != nil {
// if no limit is requested then set to -1 for unlimited
if *resource.Limit == int64(0) {
resource.Limit = types.NewInt64(-1)
}
spec.Limit = resource.Limit
}
if resource.Reservation != nil {
spec.Reservation = resource.Reservation
}
if resource.Shares != nil {
// were custom shares specified
if resource.Shares.Shares != 0 {
spec.Shares = resource.Shares
} else {
// resource shares are zero, so set level to anything except custom
if resource.Shares.Level != "custom" {
spec.Shares.Level = resource.Shares.Level
}
}
}
if resource.ExpandableReservation != nil {
spec.ExpandableReservation = resource.ExpandableReservation
}
}
func (d *Dispatcher) destroyResourcePoolIfEmpty(conf *config.VirtualContainerHostConfigSpec) error {
defer trace.End(trace.Begin("", d.op))
d.op.Infof("Removing Resource Pool %q", conf.Name)
if d.parentResourcepool == nil {
d.op.Warn("Did not find parent VCH resource pool")
return nil
}
var vms []*vm.VirtualMachine
var err error
if vms, err = d.parentResourcepool.GetChildrenVMs(d.op, d.session); err != nil {
err = errors.Errorf("Unable to get children vm of resource pool %q: %s", d.parentResourcepool.Name(), err)
return err
}
if len(vms) != 0 {
err = errors.Errorf("Resource pool is not empty: %q", d.parentResourcepool.Name())
return err
}
if _, err := tasks.WaitForResult(d.op, func(ctx context.Context) (tasks.Task, error) {
return d.parentResourcepool.Destroy(ctx)
}); err != nil {
return err
}
return nil
}
func (d *Dispatcher) findResourcePool(path string) (*object.ResourcePool, error) {
defer trace.End(trace.Begin(path, d.op))
rp, err := d.session.Finder.ResourcePool(d.op, path)
if err != nil {
_, ok := err.(*find.NotFoundError)
if !ok {
err = errors.Errorf("Failed to query resource pool %q: %s", path, err)
return nil, err
}
return nil, nil
}
return rp, nil
}
func (d *Dispatcher) getPoolResourceSettings(pool *object.ResourcePool) (*config.Resources, error) {
var p mo.ResourcePool
ps := []string{"config.cpuAllocation", "config.memoryAllocation"}
if err := pool.Properties(d.op, pool.Reference(), ps, &p); err != nil {
return nil, err
}
res := &config.Resources{
CPU: p.Config.CpuAllocation,
Memory: p.Config.MemoryAllocation,
}
return res, nil
}
func updateResourcePoolConfig(ctx context.Context, pool *object.ResourcePool, name string, size *config.Resources) error {
op := trace.FromContext(ctx, "updateResourcePoolConfig")
defer trace.End(trace.Begin(fmt.Sprintf("cpu %#v, memory: %#v", size.CPU, size.Memory), op))
resSpec := types.DefaultResourceConfigSpec()
// update with user provided configuration
setResources(&resSpec.CpuAllocation, size.CPU)
setResources(&resSpec.MemoryAllocation, size.Memory)
return pool.UpdateConfig(op, name, &resSpec)
}

View File

@@ -0,0 +1,385 @@
// 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 management
import (
"bytes"
"fmt"
"path"
"sort"
"strings"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/types"
"github.com/vmware/vic/cmd/vic-machine/common"
"github.com/vmware/vic/lib/config"
"github.com/vmware/vic/lib/constants"
"github.com/vmware/vic/pkg/errors"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/vsphere/datastore"
)
const (
volumeRoot = "volumes"
dsScheme = "ds"
)
func (d *Dispatcher) deleteImages(conf *config.VirtualContainerHostConfigSpec) error {
defer trace.End(trace.Begin("", d.op))
var errs []string
d.op.Info("Removing image stores")
for _, imageDir := range conf.ImageStores {
imageDSes, err := d.session.Finder.DatastoreList(d.op, imageDir.Host)
if err != nil {
errs = append(errs, err.Error())
continue
}
if len(imageDSes) != 1 {
errs = append(errs, fmt.Sprintf("Found %d datastores with provided datastore path %s. Provided datastore path must identify exactly one datastore.",
len(imageDSes),
imageDir.String()))
continue
}
// delete images subfolder
imagePath := path.Join(imageDir.Path, constants.StorageParentDir)
if _, err = d.deleteDatastoreFiles(imageDSes[0], imagePath, true); err != nil {
errs = append(errs, err.Error())
}
// delete kvStores subfolder
kvPath := path.Join(imageDir.Path, constants.KVStoreFolder)
if _, err = d.deleteDatastoreFiles(imageDSes[0], kvPath, true); err != nil {
errs = append(errs, err.Error())
}
dsPath, err := datastore.URLtoDatastore(&imageDir)
if err != nil {
errs = append(errs, err.Error())
continue
}
children, err := d.getChildren(imageDSes[0], dsPath)
if err != nil {
if !types.IsFileNotFound(err) {
errs = append(errs, err.Error())
}
continue
}
if len(children) == 0 {
d.op.Debugf("Removing empty image store parent directory [%s] %s", imageDir.Host, imageDir.Path)
if _, err = d.deleteDatastoreFiles(imageDSes[0], imageDir.Path, true); err != nil {
errs = append(errs, err.Error())
}
} else {
d.op.Debug("Image store parent directory not empty, leaving in place.")
}
}
if len(errs) > 0 {
return errors.New(strings.Join(errs, "\n"))
}
return nil
}
func (d *Dispatcher) deleteParent(ds *object.Datastore, root string) (bool, error) {
defer trace.End(trace.Begin("", d.op))
// alway forcing delete images
return d.deleteDatastoreFiles(ds, root, true)
}
func (d *Dispatcher) deleteDatastoreFiles(ds *object.Datastore, path string, force bool) (bool, error) {
defer trace.End(trace.Begin(fmt.Sprintf("path %q, force %t", path, force), d.op))
if ds == nil {
err := errors.Errorf("No datastore")
return false, err
}
// refuse to delete everything on the datstore, ignore force
if path == "" {
// #nosec: Errors unhandled.
dsn, _ := ds.ObjectName(d.op)
msg := fmt.Sprintf("refusing to remove datastore files for path \"\" on datastore %q", dsn)
return false, errors.New(msg)
}
var empty bool
dsPath := ds.Path(path)
res, err := d.lsFolder(ds, dsPath)
if err != nil {
if !types.IsFileNotFound(err) {
err = errors.Errorf("Failed to browse folder %q: %s", dsPath, err)
return empty, err
}
d.op.Debugf("Folder %q is not found", dsPath)
empty = true
return empty, nil
}
if len(res.File) > 0 && !force {
d.op.Debugf("Folder %q is not empty, leave it there", dsPath)
return empty, nil
}
m := ds.NewFileManager(d.session.Datacenter, true)
if err = d.deleteFilesIteratively(m, ds, dsPath); err != nil {
return empty, err
}
return true, nil
}
func (d *Dispatcher) isVSAN(ds *object.Datastore) bool {
// #nosec: Errors unhandled.
dsType, _ := ds.Type(d.op)
return dsType == types.HostFileSystemVolumeFileSystemTypeVsan
}
func (d *Dispatcher) deleteFilesIteratively(m *object.DatastoreFileManager, ds *object.Datastore, dsPath string) error {
defer trace.End(trace.Begin(dsPath, d.op))
if d.isVSAN(ds) {
// Get sorted result to make sure child files are listed ahead of their parent folder so we empty the folder before deleting it.
// This behaviour is specifically for vSan, as vSan sometimes throws an error when deleting a folder that is not empty.
res, err := d.getSortedChildren(ds, dsPath)
if err != nil {
if !types.IsFileNotFound(err) {
err = errors.Errorf("Failed to browse sub folders %q: %s", dsPath, err)
return err
}
d.op.Debugf("Folder %q is not found", dsPath)
return nil
}
for _, path := range res {
if err = d.deleteVMFSFiles(m, ds, path); err != nil {
return err
}
}
}
return d.deleteVMFSFiles(m, ds, dsPath)
}
func (d *Dispatcher) deleteVMFSFiles(m *object.DatastoreFileManager, ds *object.Datastore, dsPath string) error {
defer trace.End(trace.Begin(dsPath, d.op))
for _, ext := range []string{"-delta.vmdk", "-flat.vmdk"} {
if strings.HasSuffix(dsPath, ext) {
// Skip backing files as Delete() will do so via DeleteVirtualDisk
return nil
}
}
if err := m.Delete(d.op, dsPath); err != nil {
d.op.Debugf("Failed to delete %q: %s", dsPath, err)
}
return nil
}
// getChildren returns all children under datastore path in unsorted order. (see also getSortedChildren)
func (d *Dispatcher) getChildren(ds *object.Datastore, dsPath string) ([]string, error) {
res, err := d.lsSubFolder(ds, dsPath)
if err != nil {
return nil, err
}
var result []string
for _, dir := range res.HostDatastoreBrowserSearchResults {
for _, f := range dir.File {
dsf, ok := f.(*types.FileInfo)
if !ok {
continue
}
result = append(result, path.Join(dir.FolderPath, dsf.Path))
}
}
return result, nil
}
// getSortedChildren returns all children under datastore path in reversed order.
func (d *Dispatcher) getSortedChildren(ds *object.Datastore, dsPath string) ([]string, error) {
result, err := d.getChildren(ds, dsPath)
if err != nil {
return nil, err
}
sort.Sort(sort.Reverse(sort.StringSlice(result)))
return result, nil
}
func (d *Dispatcher) lsSubFolder(ds *object.Datastore, dsPath string) (*types.ArrayOfHostDatastoreBrowserSearchResults, error) {
defer trace.End(trace.Begin(dsPath, d.op))
spec := types.HostDatastoreBrowserSearchSpec{
MatchPattern: []string{"*"},
}
b, err := ds.Browser(d.op)
if err != nil {
return nil, err
}
task, err := b.SearchDatastoreSubFolders(d.op, dsPath, &spec)
if err != nil {
return nil, err
}
info, err := task.WaitForResult(d.op, nil)
if err != nil {
return nil, err
}
res := info.Result.(types.ArrayOfHostDatastoreBrowserSearchResults)
return &res, nil
}
func (d *Dispatcher) lsFolder(ds *object.Datastore, dsPath string) (*types.HostDatastoreBrowserSearchResults, error) {
defer trace.End(trace.Begin(dsPath, d.op))
spec := types.HostDatastoreBrowserSearchSpec{
MatchPattern: []string{"*"},
}
b, err := ds.Browser(d.op)
if err != nil {
return nil, err
}
task, err := b.SearchDatastore(d.op, dsPath, &spec)
if err != nil {
return nil, err
}
info, err := task.WaitForResult(d.op, nil)
if err != nil {
return nil, err
}
res := info.Result.(types.HostDatastoreBrowserSearchResults)
return &res, nil
}
func (d *Dispatcher) createVolumeStores(conf *config.VirtualContainerHostConfigSpec) error {
defer trace.End(trace.Begin("", d.op))
for _, url := range conf.VolumeLocations {
// NFS volumestores need only make it into the config of the vch
if url.Scheme != dsScheme {
d.op.Debugf("Skipping nfs volume store for vic-machine creation operation : (%s)", url.String())
continue
}
ds, err := d.session.Finder.Datastore(d.op, url.Host)
if err != nil {
return errors.Errorf("Could not retrieve datastore with host %q due to error %s", url.Host, err)
}
if url.Path == "/" || url.Path == "" {
url.Path = constants.StorageParentDir
}
nds, err := datastore.NewHelper(d.op, d.session, ds, url.Path)
if err != nil {
return errors.Errorf("Could not create volume store due to error: %s", err)
}
// FIXME: (GitHub Issue #1301) this is not valid URL syntax and should be translated appropriately when time allows
url.Path = nds.RootURL.String()
}
return nil
}
// returns # of removed stores
func (d *Dispatcher) deleteVolumeStoreIfForced(conf *config.VirtualContainerHostConfigSpec, volumeStores *DeleteVolumeStores) (removed int) {
defer trace.End(trace.Begin("", d.op))
removed = 0
deleteVolumeStores := d.force || (volumeStores != nil && *volumeStores == AllVolumeStores)
if !deleteVolumeStores {
if len(conf.VolumeLocations) == 0 {
return 0
}
dsVolumeStores := new(bytes.Buffer)
nfsVolumeStores := new(bytes.Buffer)
for label, url := range conf.VolumeLocations {
switch url.Scheme {
case common.DsScheme:
dsVolumeStores.WriteString(fmt.Sprintf("\t%s: %s\n", label, url.Path))
case common.NfsScheme:
nfsVolumeStores.WriteString(fmt.Sprintf("\t%s: %s\n", label, url.Path))
}
}
d.op.Warnf("Since --force was not specified, the following volume stores will not be removed. Use the vSphere UI or supplied nfs targets to delete content you do not wish to keep.\n vsphere volumestores:\n%s\n NFS volumestores:\n%s\n", dsVolumeStores.String(), nfsVolumeStores.String())
return 0
}
d.op.Info("Removing volume stores")
for label, url := range conf.VolumeLocations {
// NOTE: We cannot remove nfs VolumeStores at vic-machine delete time. We are not guaranteed to be on the correct network for any of the nfs stores.
if url.Scheme != dsScheme {
d.op.Warnf("Cannot delete VolumeStore (%s). It may not be reachable by vic-machine and has been skipped by the delete process.", url.String())
continue
}
// FIXME: url is being encoded by the portlayer incorrectly, so we have to convert url.Path to the right url.URL object
dsURL, err := datastore.ToURL(url.Path)
if err != nil {
d.op.Warnf("Didn't receive an expected volume store path format: %q", url.Path)
continue
}
if dsURL.Path == constants.StorageParentDir {
dsURL.Path = path.Join(dsURL.Path, constants.VolumesDir)
}
d.op.Debugf("Provided datastore URL: %q", url.Path)
d.op.Debugf("Parsed volume store path: %q", dsURL.Path)
d.op.Infof("Deleting volume store %q on Datastore %q at path %q",
label, dsURL.Host, dsURL.Path)
datastores, err := d.session.Finder.DatastoreList(d.op, dsURL.Host)
if err != nil {
d.op.Errorf("Error finding datastore %q: %s", dsURL.Host, err)
continue
}
if len(datastores) > 1 {
foundDatastores := new(bytes.Buffer)
for _, d := range datastores {
foundDatastores.WriteString(fmt.Sprintf("\n%s\n", d.InventoryPath))
}
d.op.Errorf("Ambiguous datastore name (%q) provided. Results were: %q", dsURL.Host, foundDatastores)
continue
}
datastore := datastores[0]
if _, err := d.deleteDatastoreFiles(datastore, dsURL.Path, deleteVolumeStores); err != nil {
d.op.Errorf("Failed to delete volume store %q on Datastore %q at path %q", label, dsURL.Host, dsURL.Path)
} else {
removed++
}
}
return removed
}

View File

@@ -0,0 +1,91 @@
// 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 management
import (
"github.com/vmware/govmomi/object"
"github.com/vmware/vic/pkg/trace"
)
type State int
// ruleset ID from /etc/vmware/firewall/service.xml
const RulesetID string = "vSPC"
const (
enable State = iota
disable
)
func (s State) String() string {
switch s {
case enable:
return "enable"
case disable:
return "disable"
}
return ""
}
// EnableFirewallRuleset enables the ruleset on the target, allowing VIC backchannel traffic
func (d *Dispatcher) EnableFirewallRuleset() error {
defer trace.End(trace.Begin("", d.op))
return d.modifyFirewall(enable)
}
// DisableFirewallRuleset disables the ruleset on the target, denying VIC backchannel traffic
func (d *Dispatcher) DisableFirewallRuleset() error {
defer trace.End(trace.Begin("", d.op))
return d.modifyFirewall(disable)
}
// modifyFirewall sets the state of the firewall ruleset specified by RulesetID
func (d *Dispatcher) modifyFirewall(state State) error {
defer trace.End(trace.Begin("", d.op))
var err error
var hosts []*object.HostSystem
d.op.Debugf("cluster: %s", d.session.Cluster)
hosts, err = d.session.Cluster.Hosts(d.op)
if err != nil {
return err
}
if len(hosts) == 0 {
d.op.Infof("No hosts to modify")
return nil
}
for _, host := range hosts {
fs, err := host.ConfigManager().FirewallSystem(d.op)
if err != nil {
d.op.Errorf("Failed to get firewall system for host %q: %s", host.Name(), err)
return err
}
switch state {
case enable:
err = fs.EnableRuleset(d.op, RulesetID)
case disable:
err = fs.DisableRuleset(d.op, RulesetID)
}
if err != nil {
d.op.Errorf("Failed to %s ruleset %q on host %q: %s", state.String(), RulesetID, host.Name(), err)
return err
}
d.op.Infof("Ruleset %q %sd on host %q", RulesetID, state.String(), host)
}
return nil
}

View File

@@ -0,0 +1,99 @@
// 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 management
import (
"github.com/vmware/govmomi/find"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/types"
"github.com/vmware/vic/lib/config"
"github.com/vmware/vic/lib/install/data"
"github.com/vmware/vic/pkg/errors"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/version"
)
func (d *Dispatcher) createVApp(conf *config.VirtualContainerHostConfigSpec, settings *data.InstallerData) (*object.VirtualApp, error) {
defer trace.End(trace.Begin("", d.op))
var err error
d.op.Infof("Creating virtual app %q", conf.Name)
resSpec := types.DefaultResourceConfigSpec()
if settings.VCHSize.CPU.Limit != nil && *settings.VCHSize.CPU.Limit != 0 {
resSpec.CpuAllocation.Limit = settings.VCHSize.CPU.Limit
}
if settings.VCHSize.CPU.Reservation != nil && *settings.VCHSize.CPU.Reservation != 0 {
resSpec.CpuAllocation.Reservation = settings.VCHSize.CPU.Reservation
}
if settings.VCHSize.CPU.Shares != nil {
resSpec.CpuAllocation.Shares = settings.VCHSize.CPU.Shares
}
if settings.VCHSize.Memory.Limit != nil && *settings.VCHSize.Memory.Limit != 0 {
resSpec.MemoryAllocation.Limit = settings.VCHSize.Memory.Limit
}
if settings.VCHSize.Memory.Reservation != nil && *settings.VCHSize.Memory.Reservation != 0 {
resSpec.MemoryAllocation.Reservation = settings.VCHSize.Memory.Reservation
}
if settings.VCHSize.Memory.Shares != nil {
resSpec.MemoryAllocation.Shares = settings.VCHSize.Memory.Shares
}
prodSpec := types.VAppProductSpec{
Info: &types.VAppProductInfo{
Name: "vSphere Integrated Containers",
Vendor: "VMware",
VendorUrl: "http://www.vmware.com/",
Version: version.Version,
},
ArrayUpdateSpec: types.ArrayUpdateSpec{
Operation: types.ArrayUpdateOperationAdd,
},
}
configSpec := types.VAppConfigSpec{
Annotation: "vSphere Integrated Containers",
VmConfigSpec: types.VmConfigSpec{
Product: []types.VAppProductSpec{prodSpec},
},
}
app, err := d.session.Pool.CreateVApp(d.op, conf.Name, resSpec, configSpec, d.session.VMFolder)
if err != nil {
d.op.Debugf("Failed to create virtual app %q: %s", conf.Name, err)
return nil, err
}
conf.ComputeResources = append(conf.ComputeResources, app.Reference())
return app, nil
}
func (d *Dispatcher) findVirtualApp(path string) (*object.VirtualApp, error) {
defer trace.End(trace.Begin(path, d.op))
vapp, err := d.session.Finder.VirtualApp(d.op, path)
if err != nil {
_, ok := err.(*find.NotFoundError)
if !ok {
err = errors.Errorf("Failed to query virtual app %q: %s", path, err)
return nil, err
}
return nil, nil
}
return vapp, nil
}