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:
359
vendor/github.com/vmware/vic/lib/install/data/data.go
generated
vendored
Normal file
359
vendor/github.com/vmware/vic/lib/install/data/data.go
generated
vendored
Normal file
@@ -0,0 +1,359 @@
|
||||
// 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 data
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"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/config/executor"
|
||||
"github.com/vmware/vic/pkg/ip"
|
||||
)
|
||||
|
||||
// Data wraps all parameters required by value validation
|
||||
type Data struct {
|
||||
*common.Target
|
||||
common.Debug
|
||||
common.Compute
|
||||
common.VCHID
|
||||
common.ContainerConfig
|
||||
|
||||
OpsCredentials common.OpsCredentials
|
||||
Kubelet common.Kubelet
|
||||
|
||||
CertPEM []byte
|
||||
KeyPEM []byte
|
||||
ClientCAs []byte
|
||||
RegistryCAs []byte
|
||||
common.Images
|
||||
|
||||
ImageDatastorePath string `cmd:"image-store"`
|
||||
VolumeLocations map[string]*url.URL `cmd:"volume-store" label:"value-key"`
|
||||
|
||||
BridgeNetworkName string `cmd:"bridge-network"`
|
||||
ClientNetwork NetworkConfig `cmd:"client-network"`
|
||||
PublicNetwork NetworkConfig `cmd:"public-network"`
|
||||
ManagementNetwork NetworkConfig `cmd:"management-network"`
|
||||
DNS []net.IP `cmd:"dns-server"`
|
||||
|
||||
common.ContainerNetworks `cmd:"container-network"`
|
||||
common.ResourceLimits
|
||||
|
||||
BridgeIPRange *net.IPNet `cmd:"bridge-network-range"`
|
||||
|
||||
InsecureRegistries []string `cmd:"insecure-registry"`
|
||||
WhitelistRegistries []string `cmd:"whitelist-registry"`
|
||||
|
||||
HTTPSProxy *url.URL `cmd:"https-proxy"`
|
||||
HTTPProxy *url.URL `cmd:"http-proxy"`
|
||||
ProxyIsSet bool
|
||||
|
||||
NumCPUs int `cmd:"endpoint-cpu"`
|
||||
MemoryMB int `cmd:"endpoint-memory"`
|
||||
|
||||
Timeout time.Duration
|
||||
|
||||
Force bool
|
||||
ResetInProgressFlag bool
|
||||
|
||||
AsymmetricRouting bool `cmd:"asymmetric-routes"`
|
||||
|
||||
ScratchSize string `cmd:"base-image-size"`
|
||||
Rollback bool
|
||||
|
||||
SyslogConfig SyslogConfig `cmd:"syslog"`
|
||||
}
|
||||
|
||||
type SyslogConfig struct {
|
||||
Addr *url.URL `cmd:"address"`
|
||||
Tag string
|
||||
}
|
||||
|
||||
// NetworkConfig is used to set IP addr for each network
|
||||
type NetworkConfig struct {
|
||||
Name string `cmd:"parent"`
|
||||
Destinations []net.IPNet
|
||||
Gateway net.IPNet
|
||||
IP net.IPNet `cmd:"ip"`
|
||||
}
|
||||
|
||||
// Empty determines if ip and gateway are unset
|
||||
func (n *NetworkConfig) Empty() bool {
|
||||
return ip.Empty(n.Gateway) && ip.Empty(n.IP)
|
||||
}
|
||||
|
||||
func (n *NetworkConfig) IsSet() bool {
|
||||
return n.Name != "" || !n.Empty()
|
||||
}
|
||||
|
||||
// InstallerData is used to hold the transient installation configuration that shouldn't be serialized
|
||||
type InstallerData struct {
|
||||
// Virtual Container Host capacity
|
||||
VCHSize config.Resources
|
||||
VCHSizeIsSet bool
|
||||
|
||||
// Appliance capacity
|
||||
ApplianceSize config.Resources
|
||||
|
||||
KeyPEM string
|
||||
CertPEM string
|
||||
|
||||
//FIXME: remove following attributes after port-layer-server read config from guestinfo
|
||||
DatacenterName string
|
||||
ClusterPath string
|
||||
ResourcePoolPath string
|
||||
ApplianceInventoryPath string
|
||||
|
||||
Datacenter types.ManagedObjectReference
|
||||
Cluster types.ManagedObjectReference
|
||||
|
||||
ImageFiles map[string]string
|
||||
|
||||
ApplianceISO string
|
||||
BootstrapISO string
|
||||
ISOVersion string
|
||||
PreUpgradeVersion string
|
||||
Timeout time.Duration
|
||||
|
||||
HTTPSProxy *url.URL
|
||||
HTTPProxy *url.URL
|
||||
}
|
||||
|
||||
func NewData() *Data {
|
||||
d := &Data{
|
||||
Target: common.NewTarget(),
|
||||
ContainerNetworks: common.ContainerNetworks{
|
||||
MappedNetworks: make(map[string]string),
|
||||
MappedNetworksGateways: make(map[string]net.IPNet),
|
||||
MappedNetworksIPRanges: make(map[string][]ip.Range),
|
||||
MappedNetworksDNS: make(map[string][]net.IP),
|
||||
MappedNetworksFirewalls: make(map[string]executor.TrustLevel),
|
||||
},
|
||||
Timeout: 3 * time.Minute,
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// equalIPRanges checks if two ip.Range slices are equal by using
|
||||
// reflect.DeepEqual, with an extra check that returns true when the
|
||||
// lengths of both slices are zero.
|
||||
func equalIPRanges(a, b []ip.Range) bool {
|
||||
if !reflect.DeepEqual(a, b) {
|
||||
return len(a) == 0 && len(b) == 0
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// equalIPSlices checks if two net.IP slices are equal by using
|
||||
// reflect.DeepEqual, with an extra check that returns true when the
|
||||
// lengths of both slices are zero.
|
||||
func equalIPSlices(a, b []net.IP) bool {
|
||||
if !reflect.DeepEqual(a, b) {
|
||||
return len(a) == 0 && len(b) == 0
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// const copyInfoMsg = `Please use vic-machine inspect config to find existing
|
||||
// container network settings and supply them along with new container networks`
|
||||
const copyInfoMsg = `Please use vic-machine inspect config to find existing
|
||||
%s settings and supply them along with new %s`
|
||||
|
||||
// copyContainerNetworks checks that existing container networks (in d) are present
|
||||
// in the specified networks (src) and then copies new networks into d. It does not
|
||||
// overwrite data for existing networks.
|
||||
func (d *Data) copyContainerNetworks(src *Data) error {
|
||||
// Any existing container networks and their related options must be specified
|
||||
// while performing a configure operation.
|
||||
errMsg := "Existing container-network %s:%s not specified in configure command"
|
||||
var netsMissing, netsChanged bool
|
||||
for vicNet, vmNet := range d.ContainerNetworks.MappedNetworks {
|
||||
if _, ok := src.ContainerNetworks.MappedNetworks[vicNet]; !ok {
|
||||
netsMissing = true
|
||||
log.Errorf(fmt.Sprintf(errMsg, vmNet, vicNet))
|
||||
continue
|
||||
}
|
||||
|
||||
// If an existing container network is specified, ensure that all existing settings match the specified settings.
|
||||
if !reflect.DeepEqual(d.ContainerNetworks.MappedNetworks[vicNet], src.ContainerNetworks.MappedNetworks[vicNet]) ||
|
||||
!reflect.DeepEqual(d.ContainerNetworks.MappedNetworksGateways[vicNet], src.ContainerNetworks.MappedNetworksGateways[vicNet]) ||
|
||||
!equalIPRanges(d.ContainerNetworks.MappedNetworksIPRanges[vicNet], src.ContainerNetworks.MappedNetworksIPRanges[vicNet]) ||
|
||||
!equalIPSlices(d.ContainerNetworks.MappedNetworksDNS[vicNet], src.ContainerNetworks.MappedNetworksDNS[vicNet]) ||
|
||||
!reflect.DeepEqual(d.ContainerNetworks.MappedNetworksFirewalls[vicNet], src.ContainerNetworks.MappedNetworksFirewalls[vicNet]) {
|
||||
|
||||
netsChanged = true
|
||||
log.Errorf("Found changes to existing container network %s", vicNet)
|
||||
}
|
||||
}
|
||||
|
||||
if netsMissing {
|
||||
log.Info(fmt.Sprintf(copyInfoMsg, "container network", "container networks"))
|
||||
return fmt.Errorf("all existing container networks must also be specified")
|
||||
}
|
||||
if netsChanged {
|
||||
log.Info(fmt.Sprintf(copyInfoMsg, "container network", "container networks"))
|
||||
return fmt.Errorf("changes to existing container networks are not supported")
|
||||
}
|
||||
|
||||
// Copy data only for new container networks.
|
||||
for vicNet, vmNet := range src.ContainerNetworks.MappedNetworks {
|
||||
if _, ok := d.ContainerNetworks.MappedNetworks[vicNet]; !ok {
|
||||
d.ContainerNetworks.MappedNetworks[vicNet] = vmNet
|
||||
d.ContainerNetworks.MappedNetworksGateways[vicNet] = src.ContainerNetworks.MappedNetworksGateways[vicNet]
|
||||
d.ContainerNetworks.MappedNetworksIPRanges[vicNet] = src.ContainerNetworks.MappedNetworksIPRanges[vicNet]
|
||||
d.ContainerNetworks.MappedNetworksDNS[vicNet] = src.ContainerNetworks.MappedNetworksDNS[vicNet]
|
||||
d.ContainerNetworks.MappedNetworksFirewalls[vicNet] = src.ContainerNetworks.MappedNetworksFirewalls[vicNet]
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// copyVolumeStores checks that existing volume stores (in d) are present in the
|
||||
// specified volume stores (src) and then copies new volume stores into d. It
|
||||
// does not overwrite data for existing volume stores.
|
||||
func (d *Data) copyVolumeStores(src *Data) error {
|
||||
// Any existing volume stores must be specified while performing a configure operation.
|
||||
errMsg := "Existing volume store %s not specified in configure command"
|
||||
var storesMissing, storesChanged bool
|
||||
for label, url := range d.VolumeLocations {
|
||||
srcURL, ok := src.VolumeLocations[label]
|
||||
if !ok {
|
||||
storesMissing = true
|
||||
log.Errorf(fmt.Sprintf(errMsg, label))
|
||||
continue
|
||||
}
|
||||
|
||||
dURLString := fmt.Sprintf("%s://%s", url.Scheme, filepath.Join(strings.Trim(url.Host, "/"), strings.Trim(url.Path, "/")))
|
||||
srcURLString := fmt.Sprintf("%s://%s", srcURL.Scheme, filepath.Join(strings.Trim(srcURL.Host, "/"), strings.Trim(srcURL.Path, "/")))
|
||||
if dURLString != srcURLString {
|
||||
storesChanged = true
|
||||
log.Errorf("Found changes to existing volume store %s", label)
|
||||
}
|
||||
}
|
||||
|
||||
if storesMissing {
|
||||
log.Info(fmt.Sprintf(copyInfoMsg, "volume store", "volume stores"))
|
||||
return fmt.Errorf("all existing volume stores must also be specified")
|
||||
}
|
||||
if storesChanged {
|
||||
log.Info(fmt.Sprintf(copyInfoMsg, "volume store", "volume stores"))
|
||||
return fmt.Errorf("changes to existing volume stores are not supported")
|
||||
}
|
||||
|
||||
// Copy data only for new volume stores.
|
||||
for label, url := range src.VolumeLocations {
|
||||
if _, ok := d.VolumeLocations[label]; !ok {
|
||||
d.VolumeLocations[label] = url
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyNonEmpty will shallow copy src value to override existing value if the value is set.
|
||||
// This copy will take care of relationship between variables, that means if any variable
|
||||
// in NetworkConfig is not empty, the whole NetworkConfig will be copied. However, for
|
||||
// container networks, changes to existing networks will not be overwritten.
|
||||
func (d *Data) CopyNonEmpty(src *Data) error {
|
||||
// TODO: Add data copy here for each reconfigure items, to make sure specified variables present in the Data object.
|
||||
|
||||
if src.ClientNetwork.IsSet() {
|
||||
d.ClientNetwork = src.ClientNetwork
|
||||
}
|
||||
if src.PublicNetwork.IsSet() {
|
||||
d.PublicNetwork = src.PublicNetwork
|
||||
}
|
||||
if src.ManagementNetwork.IsSet() {
|
||||
d.ManagementNetwork = src.ManagementNetwork
|
||||
}
|
||||
|
||||
if src.ProxyIsSet {
|
||||
d.HTTPProxy = src.HTTPProxy
|
||||
d.HTTPSProxy = src.HTTPSProxy
|
||||
}
|
||||
|
||||
if src.Debug.Debug != nil {
|
||||
d.Debug = src.Debug
|
||||
}
|
||||
|
||||
if src.ContainerNetworks.IsSet() {
|
||||
if err := d.copyContainerNetworks(src); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(src.VolumeLocations) > 0 {
|
||||
if err := d.copyVolumeStores(src); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if src.OpsCredentials.IsSet {
|
||||
d.OpsCredentials = src.OpsCredentials
|
||||
}
|
||||
|
||||
if src.Target.Thumbprint != "" {
|
||||
d.Target.Thumbprint = src.Target.Thumbprint
|
||||
}
|
||||
|
||||
resourceIsSet := false
|
||||
if src.VCHCPULimitsMHz != nil {
|
||||
d.VCHCPULimitsMHz = src.VCHCPULimitsMHz
|
||||
resourceIsSet = true
|
||||
}
|
||||
if src.VCHCPUReservationsMHz != nil {
|
||||
d.VCHCPUReservationsMHz = src.VCHCPUReservationsMHz
|
||||
resourceIsSet = true
|
||||
}
|
||||
if src.VCHCPUShares != nil {
|
||||
d.VCHCPUShares = src.VCHCPUShares
|
||||
resourceIsSet = true
|
||||
}
|
||||
|
||||
if src.VCHMemoryLimitsMB != nil {
|
||||
d.VCHMemoryLimitsMB = src.VCHMemoryLimitsMB
|
||||
resourceIsSet = true
|
||||
}
|
||||
if src.VCHMemoryReservationsMB != nil {
|
||||
d.VCHMemoryReservationsMB = src.VCHMemoryReservationsMB
|
||||
resourceIsSet = true
|
||||
}
|
||||
if src.VCHMemoryShares != nil {
|
||||
d.VCHMemoryShares = src.VCHMemoryShares
|
||||
resourceIsSet = true
|
||||
}
|
||||
d.ResourceLimits.IsSet = resourceIsSet
|
||||
|
||||
d.Timeout = src.Timeout
|
||||
|
||||
d.DNS = src.DNS
|
||||
|
||||
d.RegistryCAs = src.RegistryCAs
|
||||
|
||||
d.ContainerConfig.ContainerNameConvention = src.ContainerConfig.ContainerNameConvention
|
||||
|
||||
return nil
|
||||
}
|
||||
174
vendor/github.com/vmware/vic/lib/install/data/data_test.go
generated
vendored
Normal file
174
vendor/github.com/vmware/vic/lib/install/data/data_test.go
generated
vendored
Normal file
@@ -0,0 +1,174 @@
|
||||
// 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 data
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/cmd/vic-machine/common"
|
||||
"github.com/vmware/vic/lib/config/executor"
|
||||
"github.com/vmware/vic/pkg/ip"
|
||||
)
|
||||
|
||||
func TestCopyNonEmpty(t *testing.T) {
|
||||
d := NewData()
|
||||
s := NewData()
|
||||
|
||||
ipAddr, mask, _ := net.ParseCIDR("1.1.1.1/32")
|
||||
s.ClientNetwork.IP.IP = ipAddr
|
||||
s.ClientNetwork.IP.Mask = mask.Mask
|
||||
|
||||
d.PublicNetwork.IP.IP = ipAddr
|
||||
d.PublicNetwork.IP.Mask = mask.Mask
|
||||
|
||||
d.CopyNonEmpty(s)
|
||||
assert.Equal(t, s.ClientNetwork.IP.IP, d.ClientNetwork.IP.IP, "client ip is not right")
|
||||
assert.False(t, d.PublicNetwork.IP.IP.Equal(s.PublicNetwork.IP.IP), "public ip should not be changed")
|
||||
assert.Equal(t, d.OpsCredentials, s.OpsCredentials)
|
||||
|
||||
opsUser := "ops"
|
||||
opsPassword := "pass"
|
||||
s.OpsCredentials = common.OpsCredentials{
|
||||
OpsUser: &opsUser,
|
||||
OpsPassword: &opsPassword,
|
||||
}
|
||||
d.CopyNonEmpty(s)
|
||||
assert.NotEqual(t, d.OpsCredentials, s.OpsCredentials)
|
||||
|
||||
s.OpsCredentials.IsSet = true
|
||||
d.CopyNonEmpty(s)
|
||||
assert.Equal(t, d.OpsCredentials, s.OpsCredentials)
|
||||
|
||||
s.Target.Thumbprint = "te:st:th:um:bp:ri:nt"
|
||||
d.CopyNonEmpty(s)
|
||||
assert.Equal(t, d.Target.Thumbprint, s.Target.Thumbprint)
|
||||
}
|
||||
|
||||
func TestEqualIPRanges(t *testing.T) {
|
||||
emptyIPRange := ip.Range{}
|
||||
assert.True(t, equalIPRanges([]ip.Range{emptyIPRange}, []ip.Range{emptyIPRange}))
|
||||
|
||||
fooRange := ip.Range{
|
||||
FirstIP: net.ParseIP("10.10.10.10"),
|
||||
LastIP: net.ParseIP("10.10.10.24"),
|
||||
}
|
||||
barRange := ip.Range{
|
||||
FirstIP: net.ParseIP("10.10.10.10"),
|
||||
LastIP: net.ParseIP("10.10.10.16"),
|
||||
}
|
||||
assert.False(t, equalIPRanges([]ip.Range{fooRange}, []ip.Range{barRange}))
|
||||
}
|
||||
|
||||
func TestEqualIPSlices(t *testing.T) {
|
||||
emptyIP := net.IP{}
|
||||
assert.True(t, equalIPSlices([]net.IP{emptyIP}, []net.IP{emptyIP}))
|
||||
|
||||
fooIP, _, _ := net.ParseCIDR("1.1.1.1/32")
|
||||
barIP, _, _ := net.ParseCIDR("2.2.2.2/32")
|
||||
assert.False(t, equalIPSlices([]net.IP{fooIP}, []net.IP{barIP}))
|
||||
}
|
||||
|
||||
func TestCopyContainerNetworks(t *testing.T) {
|
||||
d := NewData()
|
||||
src := NewData()
|
||||
|
||||
fooLabel := "foo"
|
||||
fooNet := "Foo Network"
|
||||
ipAddr, mask, _ := net.ParseCIDR("1.1.1.1/32")
|
||||
ipRange := ip.Range{
|
||||
FirstIP: net.ParseIP("10.10.10.10"),
|
||||
LastIP: net.ParseIP("10.10.10.24"),
|
||||
}
|
||||
trust := executor.Closed
|
||||
src.ContainerNetworks.MappedNetworks[fooLabel] = fooNet
|
||||
src.ContainerNetworks.MappedNetworksGateways[fooLabel] = *mask
|
||||
src.ContainerNetworks.MappedNetworksIPRanges[fooLabel] = []ip.Range{ipRange}
|
||||
src.ContainerNetworks.MappedNetworksDNS[fooLabel] = []net.IP{ipAddr}
|
||||
src.ContainerNetworks.MappedNetworksFirewalls[fooLabel] = trust
|
||||
|
||||
// Everything in src should be copied to d.
|
||||
err := d.copyContainerNetworks(src)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, d.ContainerNetworks, src.ContainerNetworks)
|
||||
|
||||
barLabel := "bar"
|
||||
barNet := "Bar Network"
|
||||
src.ContainerNetworks.MappedNetworks[barLabel] = barNet
|
||||
src.ContainerNetworks.MappedNetworksGateways[barLabel] = net.IPNet{}
|
||||
src.ContainerNetworks.MappedNetworksIPRanges[barLabel] = []ip.Range{}
|
||||
src.ContainerNetworks.MappedNetworksDNS[barLabel] = []net.IP{}
|
||||
src.ContainerNetworks.MappedNetworksFirewalls[barLabel] = executor.Published
|
||||
|
||||
// The new network should be copied to d.
|
||||
err = d.copyContainerNetworks(src)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, d.ContainerNetworks, src.ContainerNetworks)
|
||||
|
||||
delete(src.ContainerNetworks.MappedNetworks, barLabel)
|
||||
delete(src.ContainerNetworks.MappedNetworksGateways, barLabel)
|
||||
delete(src.ContainerNetworks.MappedNetworksIPRanges, barLabel)
|
||||
delete(src.ContainerNetworks.MappedNetworksDNS, barLabel)
|
||||
delete(src.ContainerNetworks.MappedNetworksFirewalls, barLabel)
|
||||
|
||||
// There should be an error if anything in d is not in src.
|
||||
err = d.copyContainerNetworks(src)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
src.ContainerNetworks.MappedNetworks[barLabel] = barNet
|
||||
src.ContainerNetworks.MappedNetworksGateways[barLabel] = net.IPNet{}
|
||||
src.ContainerNetworks.MappedNetworksIPRanges[barLabel] = []ip.Range{ipRange}
|
||||
src.ContainerNetworks.MappedNetworksDNS[barLabel] = []net.IP{ipAddr}
|
||||
src.ContainerNetworks.MappedNetworksFirewalls[barLabel] = trust
|
||||
|
||||
// There should be an error on an attempt to change an existing network.
|
||||
err = d.copyContainerNetworks(src)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestCopyVolumeStores(t *testing.T) {
|
||||
d := NewData()
|
||||
src := NewData()
|
||||
d.VolumeLocations = make(map[string]*url.URL)
|
||||
src.VolumeLocations = make(map[string]*url.URL)
|
||||
|
||||
var err error
|
||||
src.VolumeLocations["foo"], err = url.Parse("ds://fooDS/dir")
|
||||
assert.NoError(t, err)
|
||||
// Everything in src should be copied to d.
|
||||
err = d.copyVolumeStores(src)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, d.VolumeLocations, src.VolumeLocations)
|
||||
|
||||
src.VolumeLocations["bar"], err = url.Parse("barDS/dir")
|
||||
// The new volume store should be copied to d.
|
||||
err = d.copyVolumeStores(src)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, d.VolumeLocations, src.VolumeLocations)
|
||||
|
||||
delete(src.VolumeLocations, "bar")
|
||||
// There should be an error if anything in d is not in src.
|
||||
err = d.copyVolumeStores(src)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
src.VolumeLocations["bar"], err = url.Parse("barDS/path")
|
||||
assert.NoError(t, err)
|
||||
// There should be an error on an attempt to change an existing volume store.
|
||||
err = d.copyVolumeStores(src)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
34
vendor/github.com/vmware/vic/lib/install/kubelet/kubelet.go
generated
vendored
Normal file
34
vendor/github.com/vmware/vic/lib/install/kubelet/kubelet.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright 2016-2018 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 kubelet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/vmware/vic/lib/config"
|
||||
)
|
||||
|
||||
func ReadKubeletConfigFile(ctx context.Context, configSpec *config.VirtualContainerHostConfigSpec) error {
|
||||
// TODO: this function should perform full validation of the config file
|
||||
pathName := configSpec.KubeletConfigFile
|
||||
data, err := ioutil.ReadFile(pathName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
configSpec.KubeletConfigContent = string(data)
|
||||
return nil
|
||||
}
|
||||
1232
vendor/github.com/vmware/vic/lib/install/management/appliance.go
generated
vendored
Normal file
1232
vendor/github.com/vmware/vic/lib/install/management/appliance.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
60
vendor/github.com/vmware/vic/lib/install/management/appliance_test.go
generated
vendored
Normal file
60
vendor/github.com/vmware/vic/lib/install/management/appliance_test.go
generated
vendored
Normal 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")
|
||||
}
|
||||
559
vendor/github.com/vmware/vic/lib/install/management/configure.go
generated
vendored
Normal file
559
vendor/github.com/vmware/vic/lib/install/management/configure.go
generated
vendored
Normal 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
|
||||
}
|
||||
214
vendor/github.com/vmware/vic/lib/install/management/configure_test.go
generated
vendored
Normal file
214
vendor/github.com/vmware/vic/lib/install/management/configure_test.go
generated
vendored
Normal 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")
|
||||
}
|
||||
269
vendor/github.com/vmware/vic/lib/install/management/create.go
generated
vendored
Normal file
269
vendor/github.com/vmware/vic/lib/install/management/create.go
generated
vendored
Normal 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
|
||||
}
|
||||
254
vendor/github.com/vmware/vic/lib/install/management/create_test.go
generated
vendored
Normal file
254
vendor/github.com/vmware/vic/lib/install/management/create_test.go
generated
vendored
Normal 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.")
|
||||
}
|
||||
}
|
||||
116
vendor/github.com/vmware/vic/lib/install/management/debug.go
generated
vendored
Normal file
116
vendor/github.com/vmware/vic/lib/install/management/debug.go
generated
vendored
Normal 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
|
||||
}
|
||||
344
vendor/github.com/vmware/vic/lib/install/management/delete.go
generated
vendored
Normal file
344
vendor/github.com/vmware/vic/lib/install/management/delete.go
generated
vendored
Normal 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
|
||||
}
|
||||
271
vendor/github.com/vmware/vic/lib/install/management/delete_test.go
generated
vendored
Normal file
271
vendor/github.com/vmware/vic/lib/install/management/delete_test.go
generated
vendored
Normal 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
|
||||
}
|
||||
433
vendor/github.com/vmware/vic/lib/install/management/dispatcher.go
generated
vendored
Normal file
433
vendor/github.com/vmware/vic/lib/install/management/dispatcher.go
generated
vendored
Normal 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)
|
||||
}
|
||||
344
vendor/github.com/vmware/vic/lib/install/management/finder.go
generated
vendored
Normal file
344
vendor/github.com/vmware/vic/lib/install/management/finder.go
generated
vendored
Normal 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
|
||||
}
|
||||
346
vendor/github.com/vmware/vic/lib/install/management/finder_test.go
generated
vendored
Normal file
346
vendor/github.com/vmware/vic/lib/install/management/finder_test.go
generated
vendored
Normal 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
|
||||
}
|
||||
232
vendor/github.com/vmware/vic/lib/install/management/inspect.go
generated
vendored
Normal file
232
vendor/github.com/vmware/vic/lib/install/management/inspect.go
generated
vendored
Normal 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
|
||||
}
|
||||
54
vendor/github.com/vmware/vic/lib/install/management/inspect_test.go
generated
vendored
Normal file
54
vendor/github.com/vmware/vic/lib/install/management/inspect_test.go
generated
vendored
Normal 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"])
|
||||
}
|
||||
143
vendor/github.com/vmware/vic/lib/install/management/network.go
generated
vendored
Normal file
143
vendor/github.com/vmware/vic/lib/install/management/network.go
generated
vendored
Normal 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
|
||||
}
|
||||
160
vendor/github.com/vmware/vic/lib/install/management/resource_pool.go
generated
vendored
Normal file
160
vendor/github.com/vmware/vic/lib/install/management/resource_pool.go
generated
vendored
Normal 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)
|
||||
}
|
||||
385
vendor/github.com/vmware/vic/lib/install/management/store_files.go
generated
vendored
Normal file
385
vendor/github.com/vmware/vic/lib/install/management/store_files.go
generated
vendored
Normal 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
|
||||
|
||||
}
|
||||
91
vendor/github.com/vmware/vic/lib/install/management/update.go
generated
vendored
Normal file
91
vendor/github.com/vmware/vic/lib/install/management/update.go
generated
vendored
Normal 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
|
||||
}
|
||||
99
vendor/github.com/vmware/vic/lib/install/management/virtual_app.go
generated
vendored
Normal file
99
vendor/github.com/vmware/vic/lib/install/management/virtual_app.go
generated
vendored
Normal 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
|
||||
}
|
||||
247
vendor/github.com/vmware/vic/lib/install/opsuser/opsuser.go
generated
vendored
Normal file
247
vendor/github.com/vmware/vic/lib/install/opsuser/opsuser.go
generated
vendored
Normal file
@@ -0,0 +1,247 @@
|
||||
// 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 opsuser
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/vmware/govmomi"
|
||||
"github.com/vmware/govmomi/find"
|
||||
gvsession "github.com/vmware/govmomi/session"
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/lib/config"
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/vsphere/compute"
|
||||
"github.com/vmware/vic/pkg/vsphere/rbac"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
)
|
||||
|
||||
var opsuserRolePrefix = "vic-vch-"
|
||||
|
||||
type RBACManager struct {
|
||||
AuthzManager *rbac.AuthzManager
|
||||
configSpec *config.VirtualContainerHostConfigSpec
|
||||
session *session.Session
|
||||
client *vim25.Client
|
||||
}
|
||||
|
||||
func NewRBACManager(ctx context.Context, client *vim25.Client, session *session.Session, rbacConfig *rbac.Config, configSpec *config.VirtualContainerHostConfigSpec) *RBACManager {
|
||||
RBACOpsuserManager := &RBACManager{
|
||||
configSpec: configSpec,
|
||||
session: session,
|
||||
client: client,
|
||||
}
|
||||
am := rbac.NewAuthzManager(ctx, client)
|
||||
am.InitConfig(configSpec.Connection.Username, opsuserRolePrefix, rbacConfig)
|
||||
RBACOpsuserManager.AuthzManager = am
|
||||
return RBACOpsuserManager
|
||||
}
|
||||
|
||||
func GrantDCReadOnlyPerms(ctx context.Context, session *session.Session, configSpec *config.VirtualContainerHostConfigSpec) error {
|
||||
mgr := NewRBACManager(ctx, session.Vim25(), session, &DCReadOnlyConf, configSpec)
|
||||
_, err := mgr.SetupDCReadOnlyPermissions(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
func GrantOpsUserPerms(ctx context.Context, client *vim25.Client, configSpec *config.VirtualContainerHostConfigSpec) error {
|
||||
mgr := NewRBACManager(ctx, client, nil, &OpsuserRBACConf, configSpec)
|
||||
_, err := mgr.SetupRolesAndPermissions(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
func (mgr *RBACManager) SetupRolesAndPermissions(ctx context.Context) ([]rbac.ResourcePermission, error) {
|
||||
am := mgr.AuthzManager
|
||||
res, err := am.IsPrincipalAnAdministrator(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res {
|
||||
log.Warnf("Skipping ops-user Role/Permissions initialization. The current ops-user (%s) has administrative privileges.", am.Principal)
|
||||
log.Warnf("This occurs when \"%s\" is a member of the \"Administrators\" group or has been granted \"Admin\" role to any of the resources in the system.", am.Principal)
|
||||
return nil, nil
|
||||
}
|
||||
if _, err = am.CreateRoles(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mgr.SetupPermissions(ctx)
|
||||
}
|
||||
|
||||
func (mgr *RBACManager) SetupPermissions(ctx context.Context) ([]rbac.ResourcePermission, error) {
|
||||
return mgr.setupPermissions(ctx)
|
||||
}
|
||||
|
||||
func (mgr *RBACManager) SetupDCReadOnlyPermissions(ctx context.Context) (*rbac.ResourcePermission, error) {
|
||||
am := mgr.AuthzManager
|
||||
res, err := am.IsPrincipalAnAdministrator(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// If administrator skip setting the root Permissions
|
||||
if res {
|
||||
log.Warnf("Cannot perform ops-user Role/Permissions initialization. The current ops-user (%s) has administrative privileges.", am.Principal)
|
||||
log.Warnf("This occurs when \"%s\" is a member of the \"Administrators\" group or has been granted \"Admin\" role to any of the resources in the system.", am.Principal)
|
||||
return nil, errors.Errorf("Cannot grant ops-user permissions as %s has administrative privileges", am.Principal)
|
||||
}
|
||||
return mgr.setupDcReadOnlyPermissions(ctx)
|
||||
}
|
||||
|
||||
func (mgr *RBACManager) setupDcReadOnlyPermissions(ctx context.Context) (*rbac.ResourcePermission, error) {
|
||||
type ResourceDesc struct {
|
||||
rType int8
|
||||
ref types.ManagedObjectReference
|
||||
}
|
||||
|
||||
session := mgr.session
|
||||
am := mgr.AuthzManager
|
||||
datacenter := session.Datacenter.Reference()
|
||||
desc := ResourceDesc{rbac.DatacenterReadOnly, datacenter}
|
||||
|
||||
// Apply permissions
|
||||
resourcePermission, err := am.AddPermission(ctx, desc.ref, desc.rType, false)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("Ops-User: RBACManager, Unable to set top read only permissions on %s, error: %s",
|
||||
desc.ref.String(), err.Error())
|
||||
}
|
||||
|
||||
return resourcePermission, nil
|
||||
}
|
||||
|
||||
func (mgr *RBACManager) setupPermissions(ctx context.Context) ([]rbac.ResourcePermission, error) {
|
||||
type ResourceDesc struct {
|
||||
rType int8
|
||||
ref types.ManagedObjectReference
|
||||
}
|
||||
|
||||
am := mgr.AuthzManager
|
||||
resourceDescs := make([]ResourceDesc, 0, len(am.Config.Resources))
|
||||
|
||||
// Get a reference to the top object
|
||||
finder := find.NewFinder(mgr.client, false)
|
||||
|
||||
root, err := finder.Folder(ctx, "/")
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("Ops-User: RBACManager, Unable to find top object: %s", err.Error())
|
||||
}
|
||||
|
||||
resourceDescs = append(resourceDescs, ResourceDesc{rbac.VCenter, root.Reference()})
|
||||
|
||||
session := session.NewSession(&session.Config{})
|
||||
// Set client
|
||||
session.Client = &govmomi.Client{
|
||||
Client: mgr.client,
|
||||
SessionManager: gvsession.NewManager(mgr.client),
|
||||
}
|
||||
|
||||
// Use the VirtualContainerHostConfigSpec to find the various resources
|
||||
// Start with Resource Pool, Cluster and Datacenter
|
||||
rpRef := mgr.configSpec.ComputeResources[0]
|
||||
rp := compute.NewResourcePool(ctx, session, rpRef)
|
||||
|
||||
datacenter, err := rp.GetDatacenter(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("Ops-User: RBACManager, Unable to find Datacenter: %s", err.Error())
|
||||
}
|
||||
resourceDescs = append(resourceDescs, ResourceDesc{rbac.Datacenter, datacenter.Reference()})
|
||||
|
||||
finder.SetDatacenter(datacenter)
|
||||
|
||||
cluster, err := rp.GetCluster(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("Ops-User: RBACManager, Unable to find Cluster: %s", err.Error())
|
||||
}
|
||||
resourceDescs = append(resourceDescs, ResourceDesc{rbac.Cluster, cluster.Reference()})
|
||||
|
||||
// Find image and volume datastores
|
||||
dsNameToRef := make(rbac.NameToRef)
|
||||
err = mgr.collectDatastores(ctx, finder, dsNameToRef)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("Ops-User: RBACManager, Unable to find Datastores: %s", err.Error())
|
||||
}
|
||||
|
||||
// Loop over Datastores
|
||||
for _, ref := range dsNameToRef {
|
||||
resourceDescs = append(resourceDescs, ResourceDesc{rbac.Datastore, ref})
|
||||
}
|
||||
|
||||
// Loop over Networks
|
||||
for _, network := range mgr.configSpec.Network.ContainerNetworks {
|
||||
netRef := &types.ManagedObjectReference{}
|
||||
netRef.FromString(network.ID)
|
||||
if netRef.Type == "" || netRef.Value == "" {
|
||||
return nil, errors.Errorf("Ops-User: RBACManager, Unable to build Bridged Network MoRef: %s", network.ID)
|
||||
}
|
||||
resourceDescs = append(resourceDescs, ResourceDesc{rbac.Network, *netRef})
|
||||
}
|
||||
|
||||
// Loop over Resource Pools
|
||||
for _, rPoolRef := range mgr.configSpec.ComputeResources {
|
||||
resourceDescs = append(resourceDescs, ResourceDesc{rbac.Endpoint, rPoolRef})
|
||||
}
|
||||
|
||||
resourcePermissions := make([]rbac.ResourcePermission, 0, len(am.Config.Resources))
|
||||
// Apply permissions
|
||||
for _, desc := range resourceDescs {
|
||||
resourcePermission, err := am.AddPermission(ctx, desc.ref, desc.rType, false)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("Ops-User: RBACManager, Unable to set permissions on %s, error: %s",
|
||||
desc.ref.String(), err.Error())
|
||||
}
|
||||
if resourcePermission != nil {
|
||||
resourcePermissions = append(resourcePermissions, *resourcePermission)
|
||||
}
|
||||
}
|
||||
|
||||
return resourcePermissions, nil
|
||||
}
|
||||
|
||||
func (mgr *RBACManager) collectDatastores(ctx context.Context, finder *find.Finder, dsNameToRef rbac.NameToRef) error {
|
||||
err := mgr.findDatastores(ctx, finder, mgr.configSpec.Storage.ImageStores, dsNameToRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
volumeLocations := make([]url.URL, 0, len(mgr.configSpec.Storage.VolumeLocations))
|
||||
for _, volumeLocation := range mgr.configSpec.Storage.VolumeLocations {
|
||||
// Only apply changes to datastores managed by vSphere
|
||||
if volumeLocation.Scheme != "ds" {
|
||||
continue
|
||||
}
|
||||
volumeLocations = append(volumeLocations, *volumeLocation)
|
||||
}
|
||||
if err = mgr.findDatastores(ctx, finder, volumeLocations, dsNameToRef); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mgr *RBACManager) findDatastores(ctx context.Context, finder *find.Finder,
|
||||
storeURLs []url.URL, dsNameToRef rbac.NameToRef) error {
|
||||
for _, storeURL := range storeURLs {
|
||||
dsName := storeURL.Host
|
||||
// Skip if we already have one
|
||||
if _, ok := dsNameToRef[dsName]; ok {
|
||||
continue
|
||||
}
|
||||
ds, err := finder.Datastore(ctx, dsName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dsNameToRef[dsName] = ds.Reference()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
160
vendor/github.com/vmware/vic/lib/install/opsuser/opsuser_conf.go
generated
vendored
Normal file
160
vendor/github.com/vmware/vic/lib/install/opsuser/opsuser_conf.go
generated
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
// 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 opsuser
|
||||
|
||||
import (
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/pkg/vsphere/rbac"
|
||||
)
|
||||
|
||||
var vchRolePrefix = "vic-vch-"
|
||||
|
||||
// Pre-existing ReadOnly Role, no need to specify the privileges
|
||||
var RoleReadOnly = types.AuthorizationRole{
|
||||
Name: "ReadOnly",
|
||||
Privilege: []string{},
|
||||
}
|
||||
|
||||
var RoleVCenter = types.AuthorizationRole{
|
||||
Name: "vcenter",
|
||||
Privilege: []string{
|
||||
"Datastore.Config",
|
||||
},
|
||||
}
|
||||
|
||||
var RoleDataCenter = types.AuthorizationRole{
|
||||
Name: "datacenter",
|
||||
Privilege: []string{
|
||||
"Datastore.Config",
|
||||
"Datastore.FileManagement",
|
||||
"VirtualMachine.Config.AddNewDisk",
|
||||
"VirtualMachine.Config.AdvancedConfig",
|
||||
"VirtualMachine.Config.RemoveDisk",
|
||||
"VirtualMachine.Inventory.Create",
|
||||
"VirtualMachine.Inventory.Delete",
|
||||
},
|
||||
}
|
||||
|
||||
var RoleCluster = types.AuthorizationRole{
|
||||
Name: "cluster",
|
||||
Privilege: []string{
|
||||
"Datastore.AllocateSpace",
|
||||
"Datastore.Browse",
|
||||
"Datastore.Config",
|
||||
"Datastore.DeleteFile",
|
||||
"Datastore.FileManagement",
|
||||
"Host.Config.SystemManagement",
|
||||
},
|
||||
}
|
||||
|
||||
var RoleDataStore = types.AuthorizationRole{
|
||||
Name: "datastore",
|
||||
Privilege: []string{
|
||||
"Datastore.AllocateSpace",
|
||||
"Datastore.Browse",
|
||||
"Datastore.Config",
|
||||
"Datastore.DeleteFile",
|
||||
"Datastore.FileManagement",
|
||||
"Host.Config.SystemManagement",
|
||||
},
|
||||
}
|
||||
|
||||
var RoleNetwork = types.AuthorizationRole{
|
||||
Name: "network",
|
||||
Privilege: []string{
|
||||
"Network.Assign",
|
||||
},
|
||||
}
|
||||
|
||||
var RoleEndpoint = types.AuthorizationRole{
|
||||
Name: "endpoint",
|
||||
Privilege: []string{
|
||||
"DVPortgroup.Modify",
|
||||
"DVPortgroup.PolicyOp",
|
||||
"DVPortgroup.ScopeOp",
|
||||
"Resource.AssignVMToPool",
|
||||
"VirtualMachine.Config.AddExistingDisk",
|
||||
"VirtualMachine.Config.AddNewDisk",
|
||||
"VirtualMachine.Config.AddRemoveDevice",
|
||||
"VirtualMachine.Config.AdvancedConfig",
|
||||
"VirtualMachine.Config.EditDevice",
|
||||
"VirtualMachine.Config.RemoveDisk",
|
||||
"VirtualMachine.Config.Rename",
|
||||
"VirtualMachine.GuestOperations.Execute",
|
||||
"VirtualMachine.Interact.DeviceConnection",
|
||||
"VirtualMachine.Interact.PowerOff",
|
||||
"VirtualMachine.Interact.PowerOn",
|
||||
"VirtualMachine.Inventory.Create",
|
||||
"VirtualMachine.Inventory.Delete",
|
||||
"VirtualMachine.Inventory.Register",
|
||||
"VirtualMachine.Inventory.Unregister",
|
||||
},
|
||||
}
|
||||
|
||||
var DCReadOnlyConf = rbac.Config{
|
||||
Resources: []rbac.Resource{
|
||||
{
|
||||
Type: rbac.DatacenterReadOnly,
|
||||
Propagate: false,
|
||||
Role: RoleReadOnly,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Configuration for the ops-user
|
||||
var OpsuserRBACConf = rbac.Config{
|
||||
Resources: []rbac.Resource{
|
||||
{
|
||||
Type: rbac.VCenter,
|
||||
Propagate: false,
|
||||
Role: RoleVCenter,
|
||||
},
|
||||
{
|
||||
Type: rbac.Datacenter,
|
||||
Propagate: true,
|
||||
Role: RoleDataCenter,
|
||||
},
|
||||
{
|
||||
Type: rbac.Cluster,
|
||||
Propagate: true,
|
||||
Role: RoleDataStore,
|
||||
},
|
||||
{
|
||||
Type: rbac.DatastoreFolder,
|
||||
Propagate: true,
|
||||
Role: RoleDataStore,
|
||||
},
|
||||
{
|
||||
Type: rbac.Datastore,
|
||||
Propagate: false,
|
||||
Role: RoleDataStore,
|
||||
},
|
||||
{
|
||||
Type: rbac.VSANDatastore,
|
||||
Propagate: false,
|
||||
Role: RoleDataStore,
|
||||
},
|
||||
{
|
||||
Type: rbac.Network,
|
||||
Propagate: true,
|
||||
Role: RoleNetwork,
|
||||
},
|
||||
{
|
||||
Type: rbac.Endpoint,
|
||||
Propagate: true,
|
||||
Role: RoleEndpoint,
|
||||
},
|
||||
},
|
||||
}
|
||||
173
vendor/github.com/vmware/vic/lib/install/opsuser/opsuser_test.go
generated
vendored
Normal file
173
vendor/github.com/vmware/vic/lib/install/opsuser/opsuser_test.go
generated
vendored
Normal file
@@ -0,0 +1,173 @@
|
||||
// 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 opsuser
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/vmware/vic/lib/config"
|
||||
|
||||
"github.com/vmware/govmomi/find"
|
||||
"github.com/vmware/govmomi/simulator"
|
||||
"github.com/vmware/vic/pkg/vsphere/rbac"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
"github.com/vmware/vic/pkg/vsphere/test/env"
|
||||
)
|
||||
|
||||
var opsuser = "ops-user@vsphere.local"
|
||||
var rolePrefix = "vic-vch-"
|
||||
|
||||
func TestOpsUserRolesSimulatorVPX(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
m := simulator.VPX()
|
||||
defer m.Remove()
|
||||
|
||||
err := m.Create()
|
||||
require.NoError(t, err, "Cannot create VPX Simulator")
|
||||
|
||||
s := m.Service.NewServer()
|
||||
defer s.Close()
|
||||
|
||||
config := &session.Config{
|
||||
Service: s.URL.String(),
|
||||
Insecure: true,
|
||||
Keepalive: time.Duration(5) * time.Minute,
|
||||
}
|
||||
|
||||
sess, err := session.NewSession(config).Connect(ctx)
|
||||
require.NoError(t, err, "Cannot connect to VPX Simulator")
|
||||
|
||||
am := rbac.NewAuthzManager(ctx, sess.Vim25())
|
||||
am.InitConfig(opsuser, rolePrefix, &OpsuserRBACConf)
|
||||
|
||||
var testRoleNames = []string{
|
||||
"datastore",
|
||||
"endpoint",
|
||||
}
|
||||
|
||||
var testRolePrivileges = []string{
|
||||
"Datastore.DeleteFile",
|
||||
"VirtualMachine.Config.AddNewDisk",
|
||||
}
|
||||
|
||||
rbac.DoTestRoles(ctx, t, am, testRoleNames, testRolePrivileges)
|
||||
}
|
||||
|
||||
func TestOpsUserRolesVCenter(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
config := &session.Config{
|
||||
Service: env.URL(t),
|
||||
Insecure: true,
|
||||
Keepalive: time.Duration(5) * time.Minute,
|
||||
}
|
||||
|
||||
sess, err := session.NewSession(config).Connect(ctx)
|
||||
if err != nil {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
am := rbac.NewAuthzManager(ctx, sess.Vim25())
|
||||
am.InitConfig(opsuser, rolePrefix, &OpsuserRBACConf)
|
||||
|
||||
var testRoleNames = []string{
|
||||
"datastore",
|
||||
"endpoint",
|
||||
}
|
||||
|
||||
var testRolePrivileges = []string{
|
||||
"Datastore.DeleteFile",
|
||||
"VirtualMachine.Config.AddNewDisk",
|
||||
}
|
||||
|
||||
rbac.DoTestRoles(ctx, t, am, testRoleNames, testRolePrivileges)
|
||||
}
|
||||
|
||||
func TestOpsUserPermsSimulatorVPX(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
m := simulator.VPX()
|
||||
|
||||
defer m.Remove()
|
||||
|
||||
err := m.Create()
|
||||
require.NoError(t, err)
|
||||
|
||||
s := m.Service.NewServer()
|
||||
defer s.Close()
|
||||
|
||||
fmt.Println(s.URL.String())
|
||||
|
||||
sessionConfig := &session.Config{
|
||||
Service: s.URL.String(),
|
||||
Insecure: true,
|
||||
Keepalive: time.Duration(5) * time.Minute,
|
||||
}
|
||||
|
||||
sess, err := session.NewSession(sessionConfig).Connect(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
configSpec := &config.VirtualContainerHostConfigSpec{
|
||||
Connection: config.Connection{
|
||||
Username: "ops-user@vsphere.local",
|
||||
},
|
||||
}
|
||||
|
||||
mgr := NewRBACManager(ctx, sess.Vim25(), nil, &OpsuserRBACConf, configSpec)
|
||||
am := mgr.AuthzManager
|
||||
|
||||
var roleCount = len(am.TargetRoles)
|
||||
count := rbac.InitRoles(ctx, t, am)
|
||||
|
||||
defer rbac.Cleanup(ctx, t, am, true)
|
||||
require.Equal(t, roleCount, count, "Incorrect number of roles: expected %d, actual %d", roleCount, count)
|
||||
|
||||
c := sess.Client
|
||||
// Find the Datacenter
|
||||
finder := find.NewFinder(c.Client, false)
|
||||
|
||||
dcList, err := finder.DatacenterList(ctx, "/*")
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, 0, len(dcList))
|
||||
|
||||
dc := dcList[0]
|
||||
finder.SetDatacenter(dc)
|
||||
|
||||
resourcePermission, err := am.AddPermission(ctx, dc.Reference(), rbac.Datacenter, false)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resourcePermission)
|
||||
|
||||
// Get permission back
|
||||
permissions, err := am.GetPermissions(ctx, dc.Reference())
|
||||
|
||||
if err != nil || len(permissions) == 0 {
|
||||
t.Fatalf("Failed to get permissions for Datacenter")
|
||||
}
|
||||
|
||||
foundPermission := permissions[0]
|
||||
|
||||
permission := &resourcePermission.Permission
|
||||
|
||||
if foundPermission.Principal != permission.Principal ||
|
||||
foundPermission.RoleId != permission.RoleId ||
|
||||
foundPermission.Propagate != permission.Propagate ||
|
||||
foundPermission.Group != permission.Group {
|
||||
t.Fatalf("Permission mismatch, exp: %v, found: %v", permission, foundPermission)
|
||||
}
|
||||
}
|
||||
135
vendor/github.com/vmware/vic/lib/install/ova/configure.go
generated
vendored
Normal file
135
vendor/github.com/vmware/vic/lib/install/ova/configure.go
generated
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
// 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 ova
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/url"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/lib/config/dynamic/admiral"
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
"github.com/vmware/vic/pkg/vsphere/tasks"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
const (
|
||||
// ManagedByKey defines the extension key to use in the ManagedByInfo of the OVA
|
||||
ManagedByKey = "com.vmware.vic"
|
||||
// ManagedByType defines the type to use in the ManagedByInfo of the OVA
|
||||
ManagedByType = "VicApplianceVM"
|
||||
)
|
||||
|
||||
// ConfigureManagedByInfo takes sets the ManagedBy field for the VM specified by ovaURL
|
||||
func ConfigureManagedByInfo(ctx context.Context, config *session.Config, ovaURL string) error {
|
||||
sess := session.NewSession(config)
|
||||
sess, err := sess.Connect(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v, err := getOvaVMByTag(ctx, sess, ovaURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("Attempting to configure ManagedByInfo")
|
||||
err = configureManagedByInfo(ctx, sess, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("Successfully configured ManagedByInfo")
|
||||
return nil
|
||||
}
|
||||
|
||||
func configureManagedByInfo(ctx context.Context, sess *session.Session, v *vm.VirtualMachine) error {
|
||||
spec := types.VirtualMachineConfigSpec{
|
||||
ManagedBy: &types.ManagedByInfo{
|
||||
ExtensionKey: ManagedByKey,
|
||||
Type: ManagedByType,
|
||||
},
|
||||
}
|
||||
|
||||
info, err := v.WaitForResult(ctx, func(ctx context.Context) (tasks.Task, error) {
|
||||
return v.Reconfigure(ctx, spec)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Error while setting ManagedByInfo: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if info.State != types.TaskInfoStateSuccess {
|
||||
log.Errorf("Setting ManagedByInfo reported: %s", info.Error.LocalizedMessage)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getOvaVMByTag(ctx context.Context, sess *session.Session, u string) (*vm.VirtualMachine, error) {
|
||||
ovaURL, err := url.Parse(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
host := ovaURL.Hostname()
|
||||
|
||||
log.Debugf("Looking up host %s", host)
|
||||
ips, err := net.LookupIP(host)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("IP lookup failed: %s", err)
|
||||
}
|
||||
|
||||
log.Debugf("found %d IP(s) from hostname lookup on %s:", len(ips), host)
|
||||
var ip string
|
||||
for _, i := range ips {
|
||||
log.Debugf(i.String())
|
||||
if i.To4() != nil {
|
||||
ip = i.String()
|
||||
}
|
||||
}
|
||||
|
||||
if ip == "" {
|
||||
return nil, errors.Errorf("IPV6 support not yet implemented")
|
||||
}
|
||||
|
||||
vms, err := admiral.DefaultDiscovery.Discover(ctx, sess)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("failed to discover OVA vm(s): %s", err)
|
||||
}
|
||||
|
||||
log.Infof("Found %d VM(s) tagged as OVA", len(vms))
|
||||
for i, v := range vms {
|
||||
log.Debugf("Checking IP for %s", v.Reference().Value)
|
||||
vmIP, err := v.WaitForIP(ctx)
|
||||
if err != nil && i == len(vms)-1 {
|
||||
return nil, errors.Errorf("failed to get VM IP: %s", err)
|
||||
}
|
||||
|
||||
// verify the tagged vm has the IP we expect
|
||||
if vmIP == ip {
|
||||
log.Debugf("Found OVA with matching IP: %s", ip)
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.Errorf("no VM(s) found with OVA tag")
|
||||
}
|
||||
106
vendor/github.com/vmware/vic/lib/install/ova/configure_test.go
generated
vendored
Normal file
106
vendor/github.com/vmware/vic/lib/install/ova/configure_test.go
generated
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
// 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 ova
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
)
|
||||
|
||||
func TestGetOvaVMByTagBadURL(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
bogusURL := "foo/bar.url://what-is-this"
|
||||
vm, err := getOvaVMByTag(ctx, nil, bogusURL)
|
||||
assert.Nil(t, vm)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestGetOvaVMByTag(t *testing.T) {
|
||||
username := os.Getenv("TEST_VC_USERNAME")
|
||||
password := os.Getenv("TEST_VC_PASSWORD")
|
||||
vcURL := os.Getenv("TEST_VC_URL")
|
||||
ovaURL := os.Getenv("TEST_OVA_URL")
|
||||
|
||||
if vcURL == "" || ovaURL == "" {
|
||||
log.Infof("Skipping TestGetOvaVMByTag")
|
||||
t.Skipf("This test should only run against a VC with a deployed OVA")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
vc, err := url.Parse(vcURL)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to parse VC url: %s", err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
vc.User = url.UserPassword(username, password)
|
||||
|
||||
var cert object.HostCertificateInfo
|
||||
if err = cert.FromURL(vc, new(tls.Config)); err != nil {
|
||||
log.Error(err.Error())
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
if cert.Err != nil {
|
||||
log.Errorf("Failed to verify certificate for target=%s (thumbprint=%s)", vc.Host, cert.ThumbprintSHA1)
|
||||
log.Error(cert.Err.Error())
|
||||
}
|
||||
|
||||
tp := cert.ThumbprintSHA1
|
||||
log.Infof("Accepting host %q thumbprint %s", vc.Host, tp)
|
||||
|
||||
sessionConfig := &session.Config{
|
||||
Thumbprint: tp,
|
||||
Service: vc.String(),
|
||||
DatacenterPath: "/ha-datacenter",
|
||||
DatastorePath: "datastore1",
|
||||
User: vc.User,
|
||||
Insecure: true,
|
||||
}
|
||||
|
||||
s := session.NewSession(sessionConfig)
|
||||
sess, err := s.Connect(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("Error connecting: %s", err.Error())
|
||||
}
|
||||
defer sess.Logout(ctx)
|
||||
|
||||
sess, err = sess.Populate(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("Error populating: %s", err.Error())
|
||||
}
|
||||
|
||||
vm, err := getOvaVMByTag(ctx, sess, ovaURL)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting OVA by tag: %s", err.Error())
|
||||
}
|
||||
if vm == nil {
|
||||
log.Errorf("No VM found")
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
log.Infof("%s", vm.String())
|
||||
}
|
||||
262
vendor/github.com/vmware/vic/lib/install/plugin/register.go
generated
vendored
Normal file
262
vendor/github.com/vmware/vic/lib/install/plugin/register.go
generated
vendored
Normal file
@@ -0,0 +1,262 @@
|
||||
// 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 plugin
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
|
||||
"context"
|
||||
)
|
||||
|
||||
type Info struct {
|
||||
*ManagedEntityInfo
|
||||
|
||||
Company string
|
||||
Key string
|
||||
Name string
|
||||
ServerThumbprint string
|
||||
ShowInSolutionManager bool
|
||||
Summary string
|
||||
Type string
|
||||
URL string
|
||||
Version string
|
||||
}
|
||||
|
||||
type ManagedEntityInfo struct {
|
||||
Description string
|
||||
IconURL string
|
||||
SmallIconURL string
|
||||
EntityType string
|
||||
}
|
||||
|
||||
type Pluginator struct {
|
||||
Session *session.Session
|
||||
ExtensionManager *object.ExtensionManager
|
||||
Context context.Context
|
||||
|
||||
info *Info
|
||||
|
||||
tURL *url.URL
|
||||
tThumbprint string
|
||||
connected bool
|
||||
}
|
||||
|
||||
func NewPluginator(ctx context.Context, target *url.URL, thumbprint string, i *Info) (*Pluginator, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
p := &Pluginator{
|
||||
tURL: target,
|
||||
tThumbprint: thumbprint,
|
||||
info: i,
|
||||
}
|
||||
p.Context = ctx
|
||||
|
||||
err := p.connect()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *Pluginator) disconnect() error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
if p.Session != nil {
|
||||
err := p.Session.Client.Logout(p.Context)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to disconnect: %s", err)
|
||||
}
|
||||
}
|
||||
p.connected = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Pluginator) connect() error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
var err error
|
||||
|
||||
if p.tURL.Scheme == "https" && p.tThumbprint == "" {
|
||||
var cert object.HostCertificateInfo
|
||||
if err = cert.FromURL(p.tURL, new(tls.Config)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cert.Err != nil {
|
||||
log.Errorf("Failed to verify certificate for target=%s (thumbprint=%s)",
|
||||
p.tURL.Host, cert.ThumbprintSHA1)
|
||||
return cert.Err
|
||||
}
|
||||
|
||||
p.tThumbprint = cert.ThumbprintSHA1
|
||||
log.Debugf("Accepting host %q thumbprint %s", p.tURL.Host, p.tThumbprint)
|
||||
}
|
||||
|
||||
sessionconfig := &session.Config{
|
||||
Thumbprint: p.tThumbprint,
|
||||
}
|
||||
sessionconfig.Service = p.tURL.String()
|
||||
|
||||
p.Session = session.NewSession(sessionconfig)
|
||||
p.Session, err = p.Session.Connect(p.Context)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect: %s", err)
|
||||
}
|
||||
|
||||
// #nosec: Errors unhandled.
|
||||
p.Session.Populate(p.Context)
|
||||
|
||||
em, err := object.GetExtensionManager(p.Session.Client.Client)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get extension manager: %s", err)
|
||||
}
|
||||
p.ExtensionManager = em
|
||||
|
||||
p.connected = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Register installs an extension to the target
|
||||
func (p *Pluginator) Register() error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
var err error
|
||||
if !p.connected {
|
||||
return errors.New("not connected")
|
||||
}
|
||||
|
||||
desc := types.Description{
|
||||
Label: p.info.Name,
|
||||
Summary: p.info.Summary,
|
||||
}
|
||||
|
||||
e := types.Extension{
|
||||
Key: p.info.Key,
|
||||
Version: p.info.Version,
|
||||
Company: p.info.Company,
|
||||
Description: &desc,
|
||||
}
|
||||
|
||||
if p.info.ManagedEntityInfo != nil {
|
||||
e.Type = p.info.EntityType
|
||||
}
|
||||
|
||||
eci := types.ExtensionClientInfo{
|
||||
Version: p.info.Version,
|
||||
Company: p.info.Company,
|
||||
Description: &desc,
|
||||
Type: p.info.Type,
|
||||
Url: p.info.URL,
|
||||
}
|
||||
e.Client = append(e.Client, eci)
|
||||
|
||||
d := types.KeyValue{
|
||||
Key: "name",
|
||||
Value: p.info.Name,
|
||||
}
|
||||
|
||||
eri := types.ExtensionResourceInfo{
|
||||
Locale: "en_US",
|
||||
Module: "name",
|
||||
}
|
||||
|
||||
if p.info.ManagedEntityInfo != nil {
|
||||
mei := types.ExtManagedEntityInfo{
|
||||
Description: p.info.ManagedEntityInfo.Description,
|
||||
Type: p.info.ManagedEntityInfo.EntityType,
|
||||
}
|
||||
e.ManagedEntityInfo = append(e.ManagedEntityInfo, mei)
|
||||
}
|
||||
|
||||
eri.Data = append(eri.Data, d)
|
||||
|
||||
e.ResourceList = append(e.ResourceList, eri)
|
||||
|
||||
// HTTPS requires extension server info
|
||||
if strings.HasPrefix(strings.ToLower(p.info.URL), "https://") {
|
||||
esi := types.ExtensionServerInfo{
|
||||
Url: p.info.URL,
|
||||
Description: &desc,
|
||||
Company: p.info.Company,
|
||||
Type: "HTTPS",
|
||||
AdminEmail: []string{"noreply@vmware.com"},
|
||||
ServerThumbprint: p.info.ServerThumbprint,
|
||||
}
|
||||
e.Server = append(e.Server, esi)
|
||||
}
|
||||
|
||||
e.ShownInSolutionManager = &p.info.ShowInSolutionManager
|
||||
|
||||
e.LastHeartbeatTime = time.Now().UTC()
|
||||
|
||||
err = p.ExtensionManager.Register(p.Context, e)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unregister removes an extension from the target
|
||||
func (p *Pluginator) Unregister(key string) error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
if !p.connected {
|
||||
return errors.New("not connected")
|
||||
}
|
||||
|
||||
if err := p.ExtensionManager.Unregister(p.Context, key); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsRegistered checks for presence of an extension on the target
|
||||
func (p *Pluginator) IsRegistered(key string) (bool, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
if !p.connected {
|
||||
return false, errors.New("not connected")
|
||||
}
|
||||
|
||||
e, err := p.ExtensionManager.Find(p.Context, key)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if e != nil {
|
||||
log.Debugf("%q is registered", key)
|
||||
return true, nil
|
||||
}
|
||||
log.Debugf("%q is not registered", key)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// IsRegistered checks for presence of an extension on the target
|
||||
func (p *Pluginator) GetPlugin(key string) (*types.Extension, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
if !p.connected {
|
||||
return nil, errors.New("not connected")
|
||||
}
|
||||
|
||||
return p.ExtensionManager.Find(p.Context, key)
|
||||
}
|
||||
212
vendor/github.com/vmware/vic/lib/install/validate/compute.go
generated
vendored
Normal file
212
vendor/github.com/vmware/vic/lib/install/validate/compute.go
generated
vendored
Normal file
@@ -0,0 +1,212 @@
|
||||
// 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 validate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/govmomi/find"
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"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"
|
||||
)
|
||||
|
||||
func (v *Validator) compute(op trace.Operation, input *data.Data, conf *config.VirtualContainerHostConfigSpec) {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
// ComputeResourcePath should resolve to a ComputeResource, ClusterComputeResource or ResourcePool
|
||||
|
||||
pool, err := v.ResourcePoolHelper(op, input.ComputeResourcePath)
|
||||
v.NoteIssue(err)
|
||||
if pool == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: for vApp creation assert that the name doesn't exist
|
||||
// TODO: for RP creation assert whatever we decide about the pool - most likely that it's empty
|
||||
}
|
||||
|
||||
func (v *Validator) inventoryPath(op trace.Operation, obj object.Reference) string {
|
||||
elt, err := v.Session.Finder.Element(op, obj.Reference())
|
||||
if err != nil {
|
||||
op.Warnf("failed to get inventory path for %s: %s", obj.Reference(), err)
|
||||
return ""
|
||||
}
|
||||
|
||||
return elt.Path
|
||||
}
|
||||
|
||||
// ResourcePoolHelper finds a resource pool from the input compute path and shows
|
||||
// suggestions if unable to do so when the path is ambiguous.
|
||||
func (v *Validator) ResourcePoolHelper(ctx context.Context, path string) (*object.ResourcePool, error) {
|
||||
op := trace.FromContext(ctx, "DatastoreHelper")
|
||||
defer trace.End(trace.Begin(path, op))
|
||||
|
||||
// if compute-resource is unspecified is there a default
|
||||
if path == "" {
|
||||
if v.Session.Pool != nil {
|
||||
op.Debugf("Using default resource pool for compute resource: %q", v.Session.Pool.InventoryPath)
|
||||
return v.Session.Pool, nil
|
||||
}
|
||||
|
||||
// if no path specified and no default available the show all
|
||||
v.suggestComputeResource(op)
|
||||
return nil, errors.New("No unambiguous default compute resource available: --compute-resource must be specified")
|
||||
}
|
||||
|
||||
pool, err := v.Session.Finder.ResourcePool(op, path)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *find.NotFoundError:
|
||||
// fall through to ComputeResource check
|
||||
case *find.MultipleFoundError:
|
||||
op.Errorf("Failed to use --compute-resource=%q as resource pool: %s", path, err)
|
||||
v.suggestResourcePool(op, path)
|
||||
return nil, err
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var compute *object.ComputeResource
|
||||
|
||||
if pool == nil {
|
||||
// check if its a ComputeResource or ClusterComputeResource
|
||||
compute, err = v.Session.Finder.ComputeResource(op, path)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *find.NotFoundError, *find.MultipleFoundError:
|
||||
v.suggestComputeResource(op)
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Use the default pool
|
||||
pool, err = compute.ResourcePool(op)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pool.InventoryPath = v.inventoryPath(op, pool.Reference())
|
||||
} else {
|
||||
// TODO: add an object.ResourcePool.Owner method (see compute.ResourcePool.GetCluster)
|
||||
var p mo.ResourcePool
|
||||
|
||||
if err = pool.Properties(op, pool.Reference(), []string{"owner"}, &p); err != nil {
|
||||
op.Errorf("unable to get cluster of resource pool %s: %s", pool.Name(), err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
compute = object.NewComputeResource(pool.Client(), p.Owner)
|
||||
compute.InventoryPath = v.inventoryPath(op, compute.Reference())
|
||||
}
|
||||
|
||||
// stash the pool for later use
|
||||
v.ResourcePoolPath = pool.InventoryPath
|
||||
|
||||
// some hoops for while we're still using session package
|
||||
v.Session.Pool = pool
|
||||
v.Session.PoolPath = pool.InventoryPath
|
||||
|
||||
v.Session.Cluster = compute
|
||||
v.Session.ClusterPath = compute.InventoryPath
|
||||
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
func (v *Validator) ListComputeResource() ([]string, error) {
|
||||
compute, err := v.Session.Finder.ComputeResourceList(v.Context, "*")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to list compute resource: %s", err)
|
||||
}
|
||||
|
||||
if len(compute) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
matches := make([]string, len(compute))
|
||||
for i, c := range compute {
|
||||
matches[i] = c.Name()
|
||||
}
|
||||
return matches, nil
|
||||
}
|
||||
|
||||
func (v *Validator) suggestComputeResource(op trace.Operation) {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
compute, err := v.ListComputeResource()
|
||||
if err != nil {
|
||||
op.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
op.Info("Suggested values for --compute-resource:")
|
||||
for _, c := range compute {
|
||||
op.Infof(" %q", c)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Validator) ListResourcePool(path string) ([]string, error) {
|
||||
pools, err := v.Session.Finder.ResourcePoolList(v.Context, path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to list resource pool: %s", err)
|
||||
}
|
||||
|
||||
if len(pools) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
matches := make([]string, len(pools))
|
||||
for i, p := range pools {
|
||||
matches[i] = p.InventoryPath
|
||||
}
|
||||
return matches, nil
|
||||
}
|
||||
|
||||
func (v *Validator) suggestResourcePool(op trace.Operation, path string) {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
pools, err := v.ListResourcePool(path)
|
||||
if err != nil {
|
||||
op.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
op.Info("Suggested resource pool values for --compute-resource:")
|
||||
for _, c := range pools {
|
||||
p := strings.TrimPrefix(c, v.DatacenterPath+"/host/")
|
||||
op.Infof(" %q", p)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Validator) ValidateCompute(ctx context.Context, input *data.Data, computeRequired bool) (*config.VirtualContainerHostConfigSpec, error) {
|
||||
op := trace.FromContext(ctx, "ValidateCompute")
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
conf := &config.VirtualContainerHostConfigSpec{}
|
||||
|
||||
if input.ComputeResourcePath == "" && !computeRequired {
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
op.Info("Validating compute resource")
|
||||
v.compute(op, input, conf)
|
||||
return conf, v.ListIssues(ctx)
|
||||
}
|
||||
598
vendor/github.com/vmware/vic/lib/install/validate/config.go
generated
vendored
Normal file
598
vendor/github.com/vmware/vic/lib/install/validate/config.go
generated
vendored
Normal file
@@ -0,0 +1,598 @@
|
||||
// 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 validate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/vmware/govmomi/govc/host/esxcli"
|
||||
"github.com/vmware/govmomi/license"
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"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/optmanager"
|
||||
)
|
||||
|
||||
const persistNetworkBackingKey = "config.vpxd.SerialPort.PersistNetworkBacking"
|
||||
|
||||
type FirewallStatus struct {
|
||||
Rule types.HostFirewallRule
|
||||
MisconfiguredEnabled []string
|
||||
MisconfiguredDisabled []string
|
||||
UnknownEnabled []string
|
||||
UnknownDisabled []string
|
||||
MisconfiguredAllowedIPEnabled []string
|
||||
Correct []string
|
||||
}
|
||||
|
||||
type FirewallConfigUnavailableError struct {
|
||||
Host string
|
||||
}
|
||||
|
||||
func (e *FirewallConfigUnavailableError) Error() string {
|
||||
return fmt.Sprintf("Firewall configuration unavailable on %q", e.Host)
|
||||
}
|
||||
|
||||
type FirewallMisconfiguredError struct {
|
||||
Host string
|
||||
Rule types.HostFirewallRule
|
||||
}
|
||||
|
||||
func (e *FirewallMisconfiguredError) Error() string {
|
||||
return fmt.Sprintf("Firewall configuration on %q does not permit %s %d/%s %s",
|
||||
e.Host, e.Rule.PortType, e.Rule.Port, e.Rule.Protocol, e.Rule.Direction)
|
||||
}
|
||||
|
||||
type FirewallUnknownDHCPAllowedIPError struct {
|
||||
AllowedIPs []string
|
||||
Host string
|
||||
Rule types.HostFirewallRule
|
||||
TargetIP net.IPNet
|
||||
}
|
||||
|
||||
func (e *FirewallUnknownDHCPAllowedIPError) Error() string {
|
||||
return fmt.Sprintf("Firewall configuration on %q may prevent connection on %s %d/%s %s with allowed IPs: %s",
|
||||
e.Host, e.Rule.PortType, e.Rule.Port, e.Rule.Protocol, e.Rule.Direction, e.AllowedIPs)
|
||||
}
|
||||
|
||||
type FirewallMisconfiguredAllowedIPError struct {
|
||||
AllowedIPs []string
|
||||
Host string
|
||||
Rule types.HostFirewallRule
|
||||
TargetIP net.IPNet
|
||||
}
|
||||
|
||||
func (e *FirewallMisconfiguredAllowedIPError) Error() string {
|
||||
return fmt.Sprintf("Firewall configuration on %q does not permit %s %d/%s %s for %s with allowed IPs: %s",
|
||||
e.Host, e.Rule.PortType, e.Rule.Port, e.Rule.Protocol, e.Rule.Direction, e.TargetIP.IP, e.AllowedIPs)
|
||||
}
|
||||
|
||||
// CheckFirewall verifies that host firewall configuration allows tether traffic and outputs results
|
||||
func (v *Validator) CheckFirewall(ctx context.Context, conf *config.VirtualContainerHostConfigSpec) {
|
||||
op := trace.FromContext(ctx, "CheckFirewall")
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
mgmtIP := v.GetMgmtIP(conf)
|
||||
op.Debugf("Checking firewall with management network IP %s", mgmtIP)
|
||||
fwStatus := v.CheckFirewallForTether(op, mgmtIP)
|
||||
|
||||
// log the results
|
||||
v.FirewallCheckOutput(op, fwStatus)
|
||||
}
|
||||
|
||||
// CheckFirewallForTether which host firewalls are configured to allow tether traffic
|
||||
func (v *Validator) CheckFirewallForTether(ctx context.Context, mgmtIP net.IPNet) FirewallStatus {
|
||||
op := trace.FromContext(ctx, "CheckFirewallForTether")
|
||||
|
||||
var hosts []*object.HostSystem
|
||||
var err error
|
||||
|
||||
requiredRule := types.HostFirewallRule{
|
||||
Port: constants.SerialOverLANPort,
|
||||
PortType: types.HostFirewallRulePortTypeDst,
|
||||
Protocol: string(types.HostFirewallRuleProtocolTcp),
|
||||
Direction: types.HostFirewallRuleDirectionOutbound,
|
||||
}
|
||||
|
||||
status := FirewallStatus{Rule: requiredRule}
|
||||
|
||||
errMsg := "Firewall check SKIPPED"
|
||||
if !v.sessionValid(op, errMsg) {
|
||||
return status
|
||||
}
|
||||
|
||||
if hosts, err = v.Session.Datastore.AttachedClusterHosts(op, v.Session.Cluster); err != nil {
|
||||
op.Errorf("Unable to get the list of hosts attached to given storage: %s", err)
|
||||
v.NoteIssue(err)
|
||||
return status
|
||||
}
|
||||
|
||||
for _, host := range hosts {
|
||||
firewallEnabled, err := v.firewallEnabled(op, host)
|
||||
if err != nil {
|
||||
v.NoteIssue(err)
|
||||
break
|
||||
}
|
||||
|
||||
mgmtAllowed, err := v.ManagementNetAllowed(op, mgmtIP, host, requiredRule)
|
||||
if mgmtAllowed && err == nil {
|
||||
status.Correct = append(status.Correct, host.InventoryPath)
|
||||
}
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *FirewallMisconfiguredError:
|
||||
if firewallEnabled {
|
||||
op.Debugf("fw misconfigured with fw enabled %q", host.InventoryPath)
|
||||
status.MisconfiguredEnabled = append(status.MisconfiguredEnabled, host.InventoryPath)
|
||||
} else {
|
||||
op.Debugf("fw misconfigured with fw disabled %q", host.InventoryPath)
|
||||
status.MisconfiguredDisabled = append(status.MisconfiguredDisabled, host.InventoryPath)
|
||||
}
|
||||
case *FirewallUnknownDHCPAllowedIPError:
|
||||
if firewallEnabled {
|
||||
op.Debugf("fw unknown (dhcp) with fw enabled %q", host.InventoryPath)
|
||||
status.UnknownEnabled = append(status.UnknownEnabled, host.InventoryPath)
|
||||
} else {
|
||||
op.Debugf("fw unknown (dhcp) with fw disabled %q", host.InventoryPath)
|
||||
status.UnknownDisabled = append(status.UnknownDisabled, host.InventoryPath)
|
||||
}
|
||||
op.Warn(err)
|
||||
case *FirewallMisconfiguredAllowedIPError:
|
||||
if firewallEnabled {
|
||||
op.Debugf("fw misconfigured allowed IP with fw enabled %q", host.InventoryPath)
|
||||
status.MisconfiguredAllowedIPEnabled = append(status.MisconfiguredAllowedIPEnabled, host.InventoryPath)
|
||||
op.Error(err)
|
||||
} else {
|
||||
op.Debugf("fw misconfigured allowed IP with fw disabled %q", host.InventoryPath)
|
||||
status.MisconfiguredDisabled = append(status.MisconfiguredDisabled, host.InventoryPath)
|
||||
op.Warn(err)
|
||||
}
|
||||
case *FirewallConfigUnavailableError:
|
||||
if firewallEnabled {
|
||||
op.Debugf("fw configuration unavailable %q", host.InventoryPath)
|
||||
status.UnknownEnabled = append(status.UnknownEnabled, host.InventoryPath)
|
||||
op.Error(err)
|
||||
} else {
|
||||
op.Debugf("fw configuration unavailable %q", host.InventoryPath)
|
||||
status.UnknownDisabled = append(status.UnknownDisabled, host.InventoryPath)
|
||||
op.Warn(err)
|
||||
}
|
||||
default:
|
||||
v.NoteIssue(err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
// FirewallCheckOutput outputs firewall status messages associated
|
||||
// with the hosts in each of the status categories
|
||||
func (v *Validator) FirewallCheckOutput(ctx context.Context, status FirewallStatus) {
|
||||
op := trace.FromContext(ctx, "FirewallCheckOutput")
|
||||
|
||||
var err error
|
||||
// TODO: when we can intelligently place containerVMs on hosts with proper config, install
|
||||
// can proceed if there is at least one host properly configured. For now this prevents install.
|
||||
if len(status.Correct) > 0 {
|
||||
op.Info("Firewall configuration OK on hosts:")
|
||||
for _, h := range status.Correct {
|
||||
op.Infof("\t%q", h)
|
||||
}
|
||||
}
|
||||
if len(status.MisconfiguredEnabled) > 0 {
|
||||
op.Error("Firewall configuration incorrect on hosts:")
|
||||
for _, h := range status.MisconfiguredEnabled {
|
||||
op.Errorf("\t%q", h)
|
||||
}
|
||||
err = fmt.Errorf("Firewall must permit %s %d/%s %s to the VCH management interface",
|
||||
status.Rule.PortType, status.Rule.Port, status.Rule.Protocol, status.Rule.Direction)
|
||||
op.Error(err)
|
||||
v.NoteIssue(err)
|
||||
}
|
||||
if len(status.MisconfiguredAllowedIPEnabled) > 0 {
|
||||
op.Error("Firewall configuration incorrect due to allowed IP restrictions on hosts:")
|
||||
for _, h := range status.MisconfiguredAllowedIPEnabled {
|
||||
op.Errorf("\t%q", h)
|
||||
}
|
||||
err = fmt.Errorf("Firewall must permit %s %d/%s %s to the VCH management interface",
|
||||
status.Rule.PortType, status.Rule.Port, status.Rule.Protocol, status.Rule.Direction)
|
||||
op.Error(err)
|
||||
v.NoteIssue(err)
|
||||
}
|
||||
if len(status.MisconfiguredDisabled) > 0 {
|
||||
op.Warn("Firewall configuration will be incorrect if firewall is reenabled on hosts:")
|
||||
for _, h := range status.MisconfiguredDisabled {
|
||||
op.Warnf("\t%q", h)
|
||||
}
|
||||
op.Infof("Firewall must permit %s %d/%s %s to VCH management interface if firewall is reenabled",
|
||||
status.Rule.PortType, status.Rule.Port, status.Rule.Protocol, status.Rule.Direction)
|
||||
}
|
||||
|
||||
preMsg := "Firewall allowed IP configuration may prevent required connection on hosts:"
|
||||
postMsg := fmt.Sprintf("Firewall must permit %s %d/%s %s to the VCH management interface",
|
||||
status.Rule.PortType, status.Rule.Port, status.Rule.Protocol, status.Rule.Direction)
|
||||
v.firewallCheckDHCPMessage(op, status.UnknownEnabled, preMsg, postMsg)
|
||||
|
||||
preMsg = "Firewall configuration may be incorrect if firewall is reenabled on hosts:"
|
||||
postMsg = fmt.Sprintf("Firewall must permit %s %d/%s %s to the VCH management interface if firewall is reenabled",
|
||||
status.Rule.PortType, status.Rule.Port, status.Rule.Protocol, status.Rule.Direction)
|
||||
v.firewallCheckDHCPMessage(op, status.UnknownDisabled, preMsg, postMsg)
|
||||
}
|
||||
|
||||
// firewallCheckDHCPMessage outputs warning message when we are unable to check
|
||||
// that the management interface is allowed by the host firewall allowed IP rules due to DHCP
|
||||
func (v *Validator) firewallCheckDHCPMessage(op trace.Operation, hosts []string, preMsg, postMsg string) {
|
||||
if len(hosts) > 0 {
|
||||
op.Warn("Unable to fully verify firewall configuration due to DHCP use on management network")
|
||||
op.Warn("VCH management interface IP assigned by DHCP must be permitted by allowed IP settings")
|
||||
op.Warn(preMsg)
|
||||
for _, h := range hosts {
|
||||
op.Warnf("\t%q", h)
|
||||
}
|
||||
op.Info(postMsg)
|
||||
}
|
||||
}
|
||||
|
||||
// CheckIPInNets checks that an IP is within allowedIPs or allowedNets
|
||||
func (v *Validator) CheckIPInNets(checkIP net.IPNet, allowedIPs []string, allowedNets []string) bool {
|
||||
for _, a := range allowedIPs {
|
||||
aIP := net.ParseIP(a)
|
||||
if aIP != nil && checkIP.IP.Equal(aIP) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for _, n := range allowedNets {
|
||||
_, aNet, err := net.ParseCIDR(n)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if aNet.Contains(checkIP.IP) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isMethodNotFoundError(err error) bool {
|
||||
if soap.IsSoapFault(err) {
|
||||
_, ok := soap.ToSoapFault(err).VimFault().(types.MethodNotFound)
|
||||
return ok
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// FirewallEnabled checks if the host firewall is enabled
|
||||
func (v *Validator) firewallEnabled(op trace.Operation, host *object.HostSystem) (bool, error) {
|
||||
esxfw, err := esxcli.GetFirewallInfo(host)
|
||||
if err != nil {
|
||||
if isMethodNotFoundError(err) {
|
||||
return true, nil // vcsim does not support esxcli; assume firewall is enabled in this case
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
if esxfw.Enabled {
|
||||
op.Infof("Firewall status: ENABLED on %q", host.InventoryPath)
|
||||
return true, nil
|
||||
}
|
||||
op.Infof("Firewall status: DISABLED on %q", host.InventoryPath)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// GetMgmtIP finds the management network IP in config
|
||||
func (v *Validator) GetMgmtIP(conf *config.VirtualContainerHostConfigSpec) net.IPNet {
|
||||
var mgmtIP net.IPNet
|
||||
if conf != nil {
|
||||
n := conf.ExecutorConfig.Networks[config.ManagementNetworkName]
|
||||
if n != nil && n.Network.Common.Name == config.ManagementNetworkName {
|
||||
if n.IP != nil {
|
||||
mgmtIP = *n.IP
|
||||
}
|
||||
return mgmtIP
|
||||
}
|
||||
}
|
||||
return mgmtIP
|
||||
}
|
||||
|
||||
// ManagementNetAllowed checks if the management network is allowed based
|
||||
// on the host firewall's allowed IP settings
|
||||
func (v *Validator) ManagementNetAllowed(ctx context.Context, mgmtIP net.IPNet,
|
||||
host *object.HostSystem, requiredRule types.HostFirewallRule) (bool, error) {
|
||||
op := trace.FromContext(ctx, "ManagementNetAllowed")
|
||||
|
||||
fs, err := host.ConfigManager().FirewallSystem(op)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
info, err := fs.Info(op)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// we've seen cases where the firewall config isn't available
|
||||
if info == nil {
|
||||
return false, &FirewallConfigUnavailableError{Host: host.InventoryPath}
|
||||
}
|
||||
|
||||
rs := object.HostFirewallRulesetList(info.Ruleset)
|
||||
filteredRules, err := rs.EnabledByRule(requiredRule, true) // find matching rules that are enabled
|
||||
if err != nil { // rule not enabled (fw is misconfigured)
|
||||
return false, &FirewallMisconfiguredError{Host: host.InventoryPath, Rule: requiredRule}
|
||||
}
|
||||
op.Debugf("filtered rules: %v", filteredRules)
|
||||
|
||||
// check that allowed IPs permit management IP
|
||||
var allowedIPs []string
|
||||
var allowedNets []string
|
||||
for _, r := range filteredRules {
|
||||
op.Debugf("filtered IPs: %v networks: %v allIP: %v rule: %v",
|
||||
r.AllowedHosts.IpAddress, r.AllowedHosts.IpNetwork, r.AllowedHosts.AllIp, r.Key)
|
||||
|
||||
if r.AllowedHosts.AllIp { // this rule allows all hosts
|
||||
return true, nil
|
||||
}
|
||||
for _, h := range r.AllowedHosts.IpAddress {
|
||||
allowedIPs = append(allowedIPs, h)
|
||||
}
|
||||
for _, n := range r.AllowedHosts.IpNetwork {
|
||||
s := fmt.Sprintf("%s/%d", n.Network, n.PrefixLength)
|
||||
allowedNets = append(allowedNets, s)
|
||||
}
|
||||
}
|
||||
|
||||
if mgmtIP.IP == nil { // DHCP
|
||||
if len(allowedIPs) > 0 || len(allowedNets) > 0 {
|
||||
return false, &FirewallUnknownDHCPAllowedIPError{AllowedIPs: append(allowedNets, allowedIPs...),
|
||||
Host: host.InventoryPath,
|
||||
Rule: requiredRule,
|
||||
TargetIP: mgmtIP}
|
||||
}
|
||||
// no allowed IPs
|
||||
return false, &FirewallMisconfiguredError{Host: host.InventoryPath, Rule: requiredRule}
|
||||
}
|
||||
|
||||
// static management IP, check that it is allowed
|
||||
mgmtAllowed := v.CheckIPInNets(mgmtIP, allowedIPs, allowedNets)
|
||||
if mgmtAllowed {
|
||||
return true, nil
|
||||
}
|
||||
return false, &FirewallMisconfiguredAllowedIPError{AllowedIPs: append(allowedNets, allowedIPs...),
|
||||
Host: host.InventoryPath,
|
||||
Rule: requiredRule,
|
||||
TargetIP: mgmtIP}
|
||||
}
|
||||
|
||||
func (v *Validator) CheckLicense(ctx context.Context) {
|
||||
op := trace.FromContext(ctx, "CheckLicense")
|
||||
|
||||
var err error
|
||||
|
||||
errMsg := "License check SKIPPED"
|
||||
if !v.sessionValid(op, errMsg) {
|
||||
return
|
||||
}
|
||||
|
||||
if v.IsVC() {
|
||||
if err = v.checkAssignedLicenses(op); err != nil {
|
||||
v.NoteIssue(err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err = v.checkLicense(op); err != nil {
|
||||
v.NoteIssue(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Validator) assignedLicenseHasFeature(la []types.LicenseAssignmentManagerLicenseAssignment, feature string) bool {
|
||||
for _, a := range la {
|
||||
if license.HasFeature(a.AssignedLicense, feature) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (v *Validator) checkAssignedLicenses(op trace.Operation) error {
|
||||
var hosts []*object.HostSystem
|
||||
var invalidLic []string
|
||||
var validLic []string
|
||||
var err error
|
||||
client := v.Session.Client.Client
|
||||
|
||||
if hosts, err = v.Session.Datastore.AttachedClusterHosts(op, v.Session.Cluster); err != nil {
|
||||
op.Errorf("Unable to get the list of hosts attached to given storage: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
lm := license.NewManager(client)
|
||||
|
||||
am, err := lm.AssignmentManager(op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
features := []string{"serialuri", "dvs"}
|
||||
|
||||
for _, host := range hosts {
|
||||
valid := true
|
||||
la, err := am.QueryAssigned(op, host.Reference().Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, feature := range features {
|
||||
if !v.assignedLicenseHasFeature(la, feature) {
|
||||
valid = false
|
||||
msg := fmt.Sprintf("%q - license missing feature %q", host.InventoryPath, feature)
|
||||
invalidLic = append(invalidLic, msg)
|
||||
}
|
||||
}
|
||||
|
||||
if valid == true {
|
||||
validLic = append(validLic, host.InventoryPath)
|
||||
}
|
||||
}
|
||||
|
||||
if len(validLic) > 0 {
|
||||
op.Info("License check OK on hosts:")
|
||||
for _, h := range validLic {
|
||||
op.Infof(" %q", h)
|
||||
}
|
||||
}
|
||||
if len(invalidLic) > 0 {
|
||||
op.Error("License check FAILED on hosts:")
|
||||
for _, h := range invalidLic {
|
||||
op.Errorf(" %q", h)
|
||||
}
|
||||
msg := "License does not meet minimum requirements to use VIC"
|
||||
return errors.New(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *Validator) checkLicense(op trace.Operation) error {
|
||||
var invalidLic []string
|
||||
client := v.Session.Client.Client
|
||||
|
||||
lm := license.NewManager(client)
|
||||
licenses, err := lm.List(op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.checkEvalLicense(op, licenses)
|
||||
|
||||
features := []string{"serialuri"}
|
||||
|
||||
for _, feature := range features {
|
||||
if len(licenses.WithFeature(feature)) == 0 {
|
||||
msg := fmt.Sprintf("Host license missing feature %q", feature)
|
||||
invalidLic = append(invalidLic, msg)
|
||||
}
|
||||
}
|
||||
|
||||
if len(invalidLic) > 0 {
|
||||
op.Error("License check FAILED:")
|
||||
for _, h := range invalidLic {
|
||||
op.Errorf(" %q", h)
|
||||
}
|
||||
msg := "License does not meet minimum requirements to use VIC"
|
||||
return errors.New(msg)
|
||||
}
|
||||
op.Info("License check OK")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *Validator) checkEvalLicense(op trace.Operation, licenses []types.LicenseManagerLicenseInfo) {
|
||||
for _, l := range licenses {
|
||||
if l.EditionKey == "eval" {
|
||||
op.Warn("Evaluation license detected. VIC may not function if evaluation expires or insufficient license is later assigned.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// isStandaloneHost checks if host is ESX or vCenter with single host
|
||||
func (v *Validator) isStandaloneHost() bool {
|
||||
cl := v.Session.Cluster.Reference()
|
||||
|
||||
if cl.Type != "ClusterComputeResource" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// drs checks that DRS is enabled
|
||||
func (v *Validator) CheckDrs(ctx context.Context) {
|
||||
if v.DisableDRSCheck {
|
||||
return
|
||||
}
|
||||
op := trace.FromContext(ctx, "CheckDrs")
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
errMsg := "DRS check SKIPPED"
|
||||
if !v.sessionValid(op, errMsg) {
|
||||
return
|
||||
}
|
||||
|
||||
cl := v.Session.Cluster
|
||||
ref := cl.Reference()
|
||||
|
||||
if v.isStandaloneHost() {
|
||||
op.Info("DRS check SKIPPED - target is standalone host")
|
||||
return
|
||||
}
|
||||
|
||||
var ccr mo.ClusterComputeResource
|
||||
|
||||
err := cl.Properties(op, ref, []string{"configurationEx"}, &ccr)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Failed to validate DRS config: %s", err)
|
||||
v.NoteIssue(errors.New(msg))
|
||||
return
|
||||
}
|
||||
|
||||
z := ccr.ConfigurationEx.(*types.ClusterConfigInfoEx).DrsConfig
|
||||
|
||||
if !(*z.Enabled) {
|
||||
op.Error("DRS check FAILED")
|
||||
op.Errorf(" DRS must be enabled on cluster %q", v.Session.Cluster.InventoryPath)
|
||||
v.NoteIssue(errors.New("DRS must be enabled to use VIC"))
|
||||
return
|
||||
}
|
||||
op.Info("DRS check OK on:")
|
||||
op.Infof(" %q", v.Session.Cluster.InventoryPath)
|
||||
}
|
||||
|
||||
// check that PersistNetworkBacking is set
|
||||
func (v *Validator) CheckPersistNetworkBacking(ctx context.Context, quiet bool) bool {
|
||||
op := trace.FromContext(ctx, "Check vCenter serial port backing")
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
errMsg := "vCenter settings check SKIPPED"
|
||||
if !v.sessionValid(op, errMsg) {
|
||||
return false
|
||||
}
|
||||
if !v.IsVC() {
|
||||
op.Debug(errMsg)
|
||||
return true
|
||||
}
|
||||
|
||||
val, err := optmanager.QueryOptionValue(ctx, v.Session, persistNetworkBackingKey)
|
||||
if err != nil {
|
||||
// the key is not set
|
||||
val = "false"
|
||||
}
|
||||
if val != "true" {
|
||||
if !quiet {
|
||||
op.Errorf("vCenter settings check FAILED")
|
||||
msg := fmt.Sprintf("vCenter advanced option %s=true must be set", persistNetworkBackingKey)
|
||||
v.NoteIssue(errors.New(msg))
|
||||
}
|
||||
return false
|
||||
}
|
||||
op.Infof("vCenter settings check OK")
|
||||
return true
|
||||
}
|
||||
380
vendor/github.com/vmware/vic/lib/install/validate/config_to_data.go
generated
vendored
Normal file
380
vendor/github.com/vmware/vic/lib/install/validate/config_to_data.go
generated
vendored
Normal file
@@ -0,0 +1,380 @@
|
||||
// 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 validate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/go-units"
|
||||
|
||||
"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/config/executor"
|
||||
"github.com/vmware/vic/lib/install/data"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
const (
|
||||
httpProxy = "HTTP_PROXY"
|
||||
httpsProxy = "HTTPS_PROXY"
|
||||
)
|
||||
|
||||
// Finder is defined for easy to test
|
||||
type Finder interface {
|
||||
ObjectReference(ctx context.Context, ref types.ManagedObjectReference) (object.Reference, error)
|
||||
}
|
||||
|
||||
// SetDataFromVM set value based on VCH VM properties
|
||||
func SetDataFromVM(ctx context.Context, finder Finder, vm *vm.VirtualMachine, d *data.Data) error {
|
||||
op := trace.FromContext(ctx, "SetDataFromVM")
|
||||
|
||||
// display name
|
||||
name, err := vm.Name(op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.DisplayName = name
|
||||
|
||||
// compute resource
|
||||
parent, err := vm.ResourcePool(op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var mrp mo.ResourcePool
|
||||
if err = parent.Properties(op, parent.Reference(), []string{"parent"}, &mrp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if mrp.Parent == nil {
|
||||
return fmt.Errorf("Failed to get parent resource pool")
|
||||
}
|
||||
or, err := finder.ObjectReference(op, *mrp.Parent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rp, ok := or.(*object.ResourcePool)
|
||||
if !ok {
|
||||
return fmt.Errorf("parent resource %s is not resource pool", mrp.Parent)
|
||||
}
|
||||
d.ComputeResourcePath = rp.InventoryPath
|
||||
|
||||
// Set VCH resource limits and VCH endpoint VM resource limits
|
||||
setVCHResources(op, parent, d)
|
||||
setApplianceResources(op, vm, d)
|
||||
return nil
|
||||
}
|
||||
|
||||
func setApplianceResources(op trace.Operation, vm *vm.VirtualMachine, d *data.Data) error {
|
||||
var m mo.VirtualMachine
|
||||
ps := []string{"config.hardware.numCPU", "config.hardware.memoryMB"}
|
||||
|
||||
if err := vm.Properties(op, vm.Reference(), ps, &m); err != nil {
|
||||
return err
|
||||
}
|
||||
if m.Config != nil {
|
||||
d.NumCPUs = int(m.Config.Hardware.NumCPU)
|
||||
d.MemoryMB = int(m.Config.Hardware.MemoryMB)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// setVCHResources will populate the configuration data based on the deployed VCH config
|
||||
func setVCHResources(op trace.Operation, vch *object.ResourcePool, d *data.Data) error {
|
||||
var p mo.ResourcePool
|
||||
ps := []string{"config.cpuAllocation", "config.memoryAllocation"}
|
||||
|
||||
if err := vch.Properties(op, vch.Reference(), ps, &p); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cpu := p.Config.CpuAllocation
|
||||
// only set if we have a limit set. -1 == no limit
|
||||
if cpu.Limit != nil && *cpu.Limit != -1 {
|
||||
currentCPULimit := int(*cpu.Limit)
|
||||
d.VCHCPULimitsMHz = ¤tCPULimit
|
||||
}
|
||||
if cpu.Reservation != nil {
|
||||
currentCPUReserve := int(*cpu.Reservation)
|
||||
d.VCHCPUReservationsMHz = ¤tCPUReserve
|
||||
}
|
||||
d.VCHCPUShares = cpu.Shares
|
||||
|
||||
memory := p.Config.MemoryAllocation
|
||||
// only set if we have a limit set. -1 == no limit
|
||||
if memory.Limit != nil && *memory.Limit != -1 {
|
||||
currentMemLimit := int(*memory.Limit)
|
||||
d.VCHMemoryLimitsMB = ¤tMemLimit
|
||||
}
|
||||
if memory.Reservation != nil {
|
||||
currentMemReserve := int(*memory.Reservation)
|
||||
d.VCHMemoryReservationsMB = ¤tMemReserve
|
||||
}
|
||||
d.VCHMemoryShares = memory.Shares
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewDataFromConfig converts VirtualContainerHostConfigSpec back to data.Data object
|
||||
// This method does not touch any configuration for VCH VM or resource pool, which should be retrieved from VM attributes
|
||||
func NewDataFromConfig(ctx context.Context, finder Finder, conf *config.VirtualContainerHostConfigSpec) (d *data.Data, err error) {
|
||||
op := trace.FromContext(ctx, "NewDataFromConfig")
|
||||
|
||||
if conf == nil {
|
||||
err = fmt.Errorf("configuration is empty")
|
||||
return
|
||||
}
|
||||
|
||||
d = data.NewData()
|
||||
if d.Target.URL, err = url.Parse(conf.Connection.Target); err != nil {
|
||||
return
|
||||
}
|
||||
d.OpsCredentials.OpsUser = &conf.Connection.Username
|
||||
d.OpsCredentials.OpsPassword = &conf.Connection.Token
|
||||
d.Thumbprint = conf.Connection.TargetThumbprint
|
||||
|
||||
d.AsymmetricRouting = conf.AsymmetricRouting
|
||||
|
||||
if err = setBridgeNetwork(op, finder, d, conf); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if conf.Certificate.HostCertificate != nil {
|
||||
d.CertPEM = conf.Certificate.HostCertificate.Cert
|
||||
d.KeyPEM = conf.Certificate.HostCertificate.Key
|
||||
}
|
||||
d.ClientCAs = conf.Certificate.CertificateAuthorities
|
||||
d.RegistryCAs = conf.RegistryCertificateAuthorities
|
||||
|
||||
clientNet, err := getNetworkConfig(op, finder, conf.ExecutorConfig.Networks[config.ClientNetworkName])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
d.ClientNetwork = *clientNet
|
||||
publicNet, err := getNetworkConfig(op, finder, conf.ExecutorConfig.Networks[config.PublicNetworkName])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
d.PublicNetwork = *publicNet
|
||||
mgmtNet, err := getNetworkConfig(op, finder, conf.ExecutorConfig.Networks[config.ManagementNetworkName])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
d.ManagementNetwork = *mgmtNet
|
||||
|
||||
// remove duplicate network config
|
||||
if d.ManagementNetwork.Name == d.ClientNetwork.Name {
|
||||
d.ManagementNetwork = data.NetworkConfig{}
|
||||
}
|
||||
if d.ClientNetwork.Name == d.PublicNetwork.Name {
|
||||
// revert client network settings
|
||||
d.ClientNetwork = data.NetworkConfig{}
|
||||
}
|
||||
|
||||
if err = setContainerNetworks(op, finder, d, conf.Network.ContainerNetworks, conf.BridgeNetwork); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
d.Debug.Debug = &conf.Diagnostics.DebugLevel
|
||||
if conf.ExecutorConfig.Networks[config.PublicNetworkName] != nil {
|
||||
d.DNS = conf.ExecutorConfig.Networks[config.PublicNetworkName].Network.Nameservers
|
||||
}
|
||||
|
||||
if err = setHTTPProxies(d, conf); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = setImageStore(d, conf); err != nil {
|
||||
return
|
||||
}
|
||||
setVolumeLocations(op, d, conf)
|
||||
d.InsecureRegistries = conf.InsecureRegistries
|
||||
d.WhitelistRegistries = conf.RegistryWhitelist
|
||||
if d.ScratchSize, err = getHumanSize(conf.ScratchSize, "KB"); err != nil {
|
||||
return
|
||||
}
|
||||
if conf.Diagnostics.SysLogConfig != nil {
|
||||
if d.SyslogConfig.Addr, err = url.Parse(fmt.Sprintf("%s://%s", conf.Diagnostics.SysLogConfig.Network, conf.Diagnostics.SysLogConfig.RAddr)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
d.ContainerNameConvention = conf.ContainerNameConvention
|
||||
return
|
||||
}
|
||||
|
||||
func getHumanSize(size int64, unit string) (string, error) {
|
||||
is, err := units.FromHumanSize(fmt.Sprintf("%d%s", size, unit))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
hsize := units.HumanSize(float64(is))
|
||||
hsize = strings.Replace(hsize, " ", "", -1)
|
||||
return hsize, nil
|
||||
}
|
||||
|
||||
func setImageStore(d *data.Data, conf *config.VirtualContainerHostConfigSpec) error {
|
||||
if len(conf.ImageStores) == 0 {
|
||||
return fmt.Errorf("no image store configured")
|
||||
}
|
||||
if len(conf.ImageStores) > 1 {
|
||||
return fmt.Errorf("%d image stores configured", len(conf.ImageStores))
|
||||
}
|
||||
imageURL := conf.ImageStores[0]
|
||||
if imageURL.Path != "" {
|
||||
path := strings.Split(imageURL.Path, "/")
|
||||
if len(path) > 1 && path[len(path)-1] != "" {
|
||||
imageURL.Path = strings.Join(path[:len(path)-1], "/")
|
||||
}
|
||||
if imageURL.Scheme != "" && len(path) == 1 {
|
||||
imageURL.Path = ""
|
||||
}
|
||||
}
|
||||
d.ImageDatastorePath = urlString(imageURL)
|
||||
return nil
|
||||
}
|
||||
|
||||
func setVolumeLocations(op trace.Operation, d *data.Data, conf *config.VirtualContainerHostConfigSpec) {
|
||||
d.VolumeLocations = make(map[string]*url.URL, len(conf.VolumeLocations))
|
||||
|
||||
var dsURL object.DatastorePath
|
||||
for k, v := range conf.VolumeLocations {
|
||||
if ok := dsURL.FromString(v.Path); !ok {
|
||||
op.Debugf("%s is not datastore path", v.Path)
|
||||
d.VolumeLocations[k] = v
|
||||
continue
|
||||
}
|
||||
u := *v
|
||||
u.Path = path.Join(dsURL.Datastore, dsURL.Path)
|
||||
u.Host = ""
|
||||
d.VolumeLocations[k] = &u
|
||||
}
|
||||
}
|
||||
|
||||
func urlString(u url.URL) string {
|
||||
if u.Scheme == "" {
|
||||
if u.Path == "" {
|
||||
return u.Host
|
||||
}
|
||||
return fmt.Sprintf("%s/%s", u.Host, u.Path)
|
||||
}
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func setHTTPProxies(d *data.Data, conf *config.VirtualContainerHostConfigSpec) error {
|
||||
persona := conf.Sessions[config.PersonaService]
|
||||
if persona == nil {
|
||||
return nil
|
||||
}
|
||||
for _, env := range persona.Cmd.Env {
|
||||
if !strings.HasPrefix(env, httpProxy) && !strings.HasPrefix(env, httpsProxy) {
|
||||
continue
|
||||
}
|
||||
|
||||
strs := strings.Split(env, "=")
|
||||
if len(strs) != 2 {
|
||||
return fmt.Errorf("wrong env format: %s", env)
|
||||
}
|
||||
url, err := url.Parse(strs[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strs[0] == httpProxy {
|
||||
d.HTTPProxy = url
|
||||
} else {
|
||||
d.HTTPSProxy = url
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setContainerNetworks(op trace.Operation, finder Finder, d *data.Data, containerNetworks map[string]*executor.ContainerNetwork, bridge string) error {
|
||||
for k, v := range containerNetworks {
|
||||
if k == bridge {
|
||||
// bridge network is persisted in executor network as well, skip it here
|
||||
continue
|
||||
}
|
||||
name, err := getNameFromID(op, finder, v.Common.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.ContainerNetworks.MappedNetworks[k] = name
|
||||
d.ContainerNetworks.MappedNetworksGateways[k] = v.Gateway
|
||||
d.ContainerNetworks.MappedNetworksDNS[k] = v.Nameservers
|
||||
d.ContainerNetworks.MappedNetworksIPRanges[k] = v.Pools
|
||||
d.ContainerNetworks.MappedNetworksFirewalls[k] = v.TrustLevel
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getNetworkConfig(op trace.Operation, finder Finder, conf *executor.NetworkEndpoint) (net *data.NetworkConfig, err error) {
|
||||
net = &data.NetworkConfig{}
|
||||
if conf == nil {
|
||||
return
|
||||
}
|
||||
if net.Name, err = getNameFromID(op, finder, conf.Network.ID); err != nil {
|
||||
return
|
||||
}
|
||||
net.Destinations = conf.Network.Destinations
|
||||
net.Gateway = conf.Network.Gateway
|
||||
if conf.IP != nil {
|
||||
net.IP = *conf.IP
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func setBridgeNetwork(op trace.Operation, finder Finder, d *data.Data, conf *config.VirtualContainerHostConfigSpec) error {
|
||||
bridgeNet := conf.ExecutorConfig.Networks[conf.Network.BridgeNetwork]
|
||||
name, err := getNameFromID(op, finder, bridgeNet.Network.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.BridgeNetworkName = name
|
||||
d.BridgeIPRange = conf.Network.BridgeIPRange
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getNameFromID(op trace.Operation, finder Finder, mobID string) (string, error) {
|
||||
moref := new(types.ManagedObjectReference)
|
||||
ok := moref.FromString(mobID)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("could not restore serialized managed object reference: %s", mobID)
|
||||
}
|
||||
|
||||
if finder == nil {
|
||||
return "", fmt.Errorf("finder is not set")
|
||||
}
|
||||
|
||||
obj, err := finder.ObjectReference(op, *moref)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// We can use Name() directly since InventoryPath is set
|
||||
type common interface {
|
||||
Name() string
|
||||
}
|
||||
name := obj.(common).Name()
|
||||
|
||||
op.Debugf("%s name: %s", mobID, name)
|
||||
return name, nil
|
||||
}
|
||||
664
vendor/github.com/vmware/vic/lib/install/validate/network.go
generated
vendored
Normal file
664
vendor/github.com/vmware/vic/lib/install/validate/network.go
generated
vendored
Normal file
@@ -0,0 +1,664 @@
|
||||
// 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 validate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"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/config/executor"
|
||||
"github.com/vmware/vic/lib/install/data"
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/ip"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
func (v *Validator) getEndpoint(op trace.Operation, conf *config.VirtualContainerHostConfigSpec, network data.NetworkConfig, epName, contNetName string, def bool, ns []net.IP) (*executor.NetworkEndpoint, error) {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
var gw net.IPNet
|
||||
var dest []net.IPNet
|
||||
var staticIP *net.IPNet
|
||||
|
||||
if !network.Empty() {
|
||||
op.Debugf("Setting static IP for %q on port group %q", contNetName, network.Name)
|
||||
gw = network.Gateway
|
||||
dest = network.Destinations
|
||||
staticIP = &network.IP
|
||||
}
|
||||
|
||||
moid, err := v.networkHelper(op, network.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
e := &executor.NetworkEndpoint{
|
||||
Common: executor.Common{
|
||||
Name: epName,
|
||||
},
|
||||
Network: executor.ContainerNetwork{
|
||||
Common: executor.Common{
|
||||
Name: contNetName,
|
||||
ID: moid,
|
||||
},
|
||||
Default: def,
|
||||
Destinations: dest,
|
||||
Gateway: gw,
|
||||
Nameservers: ns,
|
||||
},
|
||||
IP: staticIP,
|
||||
}
|
||||
if staticIP != nil {
|
||||
e.Static = true
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (v *Validator) checkNetworkConflict(bridgeNetName, otherNetName, otherNetType string) {
|
||||
if bridgeNetName == otherNetName {
|
||||
v.NoteIssue(errors.Errorf("the bridge network must not be shared with another network role - %s also uses %q", otherNetType, bridgeNetName))
|
||||
}
|
||||
}
|
||||
|
||||
// portGroupConfig gets the input config for all networks
|
||||
// for use in checking that the config is valid
|
||||
func (v *Validator) portGroupConfig(op trace.Operation, input *data.Data, ips map[string][]data.NetworkConfig) {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
if input.ManagementNetwork.Name != "" {
|
||||
if !input.ManagementNetwork.Empty() {
|
||||
ips[input.ManagementNetwork.Name] = append(ips[input.ManagementNetwork.Name], input.ManagementNetwork)
|
||||
}
|
||||
}
|
||||
if input.ClientNetwork.Name != "" {
|
||||
if !input.ClientNetwork.Empty() {
|
||||
ips[input.ClientNetwork.Name] = append(ips[input.ClientNetwork.Name], input.ClientNetwork)
|
||||
}
|
||||
}
|
||||
if input.PublicNetwork.Name != "" {
|
||||
if !input.PublicNetwork.Empty() {
|
||||
ips[input.PublicNetwork.Name] = append(ips[input.PublicNetwork.Name], input.PublicNetwork)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkPortGroups checks that network config is valid
|
||||
// enforce that networks that share a port group with public are configured via the pubic args
|
||||
// prevent assigning > 1 static IP to the same port group
|
||||
// warn if assigning addresses in the same subnet to > 1 port group
|
||||
func (v *Validator) checkPortGroups(op trace.Operation, input *data.Data, ips map[string][]data.NetworkConfig) error {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
networks := make(map[string]string)
|
||||
|
||||
shared := false
|
||||
// check for networks that share port group with public
|
||||
for nn, n := range map[string]*data.NetworkConfig{
|
||||
config.ClientNetworkName: &input.ClientNetwork,
|
||||
config.ManagementNetworkName: &input.ManagementNetwork,
|
||||
} {
|
||||
if n.Name == input.PublicNetwork.Name && !n.Empty() {
|
||||
op.Errorf("%s network shares port group with public network, but has static IP configuration", nn)
|
||||
op.Errorf("To resolve this, configure static IP for public network and assign %s network to same port group", nn)
|
||||
op.Error("The static IP will be automatically configured for networks sharing the port group")
|
||||
shared = true
|
||||
}
|
||||
}
|
||||
|
||||
if shared {
|
||||
return fmt.Errorf("Static IP on network sharing port group with public network - Configuration ONLY allowed through public network options")
|
||||
}
|
||||
|
||||
for pg, config := range ips {
|
||||
if len(config) > 1 {
|
||||
var msgIPs []string
|
||||
for _, v := range config {
|
||||
msgIPs = append(msgIPs, v.IP.IP.String())
|
||||
}
|
||||
op.Errorf("Port group %q is configured for networks with more than one static IP: %s", pg, msgIPs)
|
||||
op.Error("All VCH networks on the same port group must have the same IP address")
|
||||
op.Error("To resolve this, configure static IP for one network and assign other networks to same port group")
|
||||
op.Error("The static IP will be automatically configured for networks sharing the port group")
|
||||
return fmt.Errorf("Incorrect static IP configuration for networks on port group %q", pg)
|
||||
}
|
||||
|
||||
// check if same subnet assigned to multiple portgroups - this can cause routing problems
|
||||
// #nosec: Errors unhandled.
|
||||
_, net, _ := net.ParseCIDR(config[0].IP.String())
|
||||
netAddr := net.String()
|
||||
if networks[netAddr] != "" {
|
||||
op.Warnf("Unsupported static IP configuration: Same subnet %q is assigned to multiple port groups %q and %q", netAddr, networks[netAddr], pg)
|
||||
} else {
|
||||
networks[netAddr] = pg
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// configureSharedPortGroups sets VCH static IP for networks that share a
|
||||
// portgroup with another network that has a configured static IP
|
||||
func (v *Validator) configureSharedPortGroups(op trace.Operation, input *data.Data, ips map[string][]data.NetworkConfig) error {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
// find other networks using same portgroup and copy the NetworkConfig to them
|
||||
for name, config := range ips {
|
||||
if len(config) != 1 {
|
||||
return fmt.Errorf("Failed to configure static IP for additional networks using port group %q", name)
|
||||
}
|
||||
op.Infof("Configuring static IP for additional networks using port group %q", name)
|
||||
if input.ClientNetwork.Name == name && input.ClientNetwork.Empty() {
|
||||
input.ClientNetwork = config[0]
|
||||
}
|
||||
if input.PublicNetwork.Name == name && input.PublicNetwork.Empty() {
|
||||
input.PublicNetwork = config[0]
|
||||
}
|
||||
if input.ManagementNetwork.Name == name && input.ManagementNetwork.Empty() {
|
||||
input.ManagementNetwork = config[0]
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *Validator) network(op trace.Operation, input *data.Data, conf *config.VirtualContainerHostConfigSpec) {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
var e *executor.NetworkEndpoint
|
||||
var err error
|
||||
|
||||
// set default portgroup if user input not provided
|
||||
if input.ClientNetwork.Name == "" {
|
||||
input.ClientNetwork.Name = input.PublicNetwork.Name
|
||||
}
|
||||
if input.ManagementNetwork.Name == "" {
|
||||
input.ManagementNetwork.Name = input.ClientNetwork.Name
|
||||
}
|
||||
|
||||
i := make(map[string][]data.NetworkConfig) // user configured IPs for portgroup
|
||||
v.portGroupConfig(op, input, i)
|
||||
|
||||
err = v.checkPortGroups(op, input, i)
|
||||
v.NoteIssue(err)
|
||||
|
||||
err = v.configureSharedPortGroups(op, input, i)
|
||||
v.NoteIssue(err)
|
||||
|
||||
// client and management networks need to have at least one
|
||||
// routing destination if gateway was specified
|
||||
for nn, n := range map[string]*data.NetworkConfig{
|
||||
config.ClientNetworkName: &input.ClientNetwork,
|
||||
config.ManagementNetworkName: &input.ManagementNetwork,
|
||||
} {
|
||||
if n.Name == input.PublicNetwork.Name {
|
||||
// no Destinations required if sharing with PublicNetwork
|
||||
continue
|
||||
}
|
||||
if !ip.IsUnspecifiedIP(n.Gateway.IP) && len(n.Destinations) == 0 {
|
||||
v.NoteIssue(fmt.Errorf("%s network gateway specified without at least one routing destination", nn))
|
||||
}
|
||||
}
|
||||
|
||||
// if static ip is specified for public network, gateway must be specified
|
||||
if !ip.IsUnspecifiedIP(input.PublicNetwork.IP.IP) && ip.IsUnspecifiedIP(input.PublicNetwork.Gateway.IP) {
|
||||
v.NoteIssue(errors.New("public network must have both static IP and gateway specified"))
|
||||
}
|
||||
|
||||
// public network should not have any routing destinations specified
|
||||
// if a gateway was specified
|
||||
if !ip.IsUnspecifiedIP(input.PublicNetwork.Gateway.IP) && len(input.PublicNetwork.Destinations) > 0 {
|
||||
v.NoteIssue(errors.New("public network has the default route and must not have any routing destinations specified for gateway"))
|
||||
}
|
||||
|
||||
// check if static IP on all networks and no user provided DNS servers
|
||||
specifiedDNS := len(input.DNS) > 0
|
||||
usingDHCP := ip.IsUnspecifiedIP(input.ClientNetwork.IP.IP) || ip.IsUnspecifiedIP(input.PublicNetwork.IP.IP) || ip.IsUnspecifiedIP(input.ManagementNetwork.IP.IP)
|
||||
|
||||
if !usingDHCP && !specifiedDNS { // Set default DNS servers
|
||||
op.Debugf("Setting default DNS servers 8.8.8.8 and 8.8.4.4")
|
||||
input.DNS = []net.IP{net.ParseIP("8.8.8.8"), net.ParseIP("8.8.4.4")}
|
||||
}
|
||||
|
||||
// Public net
|
||||
// public network is default for appliance
|
||||
e, err = v.getEndpoint(op, conf, input.PublicNetwork, config.PublicNetworkName, config.PublicNetworkName, true, input.DNS)
|
||||
if err != nil {
|
||||
v.NoteIssue(fmt.Errorf("Error checking network for --public-network: %s", err))
|
||||
v.suggestNetwork(op, "--public-network", true)
|
||||
}
|
||||
// Bridge network should be different than all other networks
|
||||
v.checkNetworkConflict(input.BridgeNetworkName, input.PublicNetwork.Name, config.PublicNetworkName)
|
||||
conf.AddNetwork(e)
|
||||
|
||||
// Client net - defaults to connect to same portgroup as public
|
||||
e, err = v.getEndpoint(op, conf, input.ClientNetwork, config.ClientNetworkName, config.ClientNetworkName, false, input.DNS)
|
||||
if err != nil {
|
||||
v.NoteIssue(fmt.Errorf("Error checking network for --client-network: %s", err))
|
||||
v.suggestNetwork(op, "--client-network", true)
|
||||
}
|
||||
v.checkNetworkConflict(input.BridgeNetworkName, input.ClientNetwork.Name, config.ClientNetworkName)
|
||||
conf.AddNetwork(e)
|
||||
|
||||
// Management net - defaults to connect to the same portgroup as client
|
||||
e, err = v.getEndpoint(op, conf, input.ManagementNetwork, "", config.ManagementNetworkName, false, input.DNS)
|
||||
if err != nil {
|
||||
v.NoteIssue(fmt.Errorf("Error checking network for --management-network: %s", err))
|
||||
v.suggestNetwork(op, "--management-network", true)
|
||||
}
|
||||
v.checkNetworkConflict(input.BridgeNetworkName, input.ManagementNetwork.Name, config.ManagementNetworkName)
|
||||
conf.AddNetwork(e)
|
||||
|
||||
// Bridge net -
|
||||
// vCenter: must exist and must be a DPG
|
||||
// ESX: doesn't need to exist - we will create with default value
|
||||
//
|
||||
// for now we're hardcoded to "bridge" for the container host name
|
||||
conf.BridgeNetwork = "bridge"
|
||||
endpointMoref, err := v.dpgHelper(op, input.BridgeNetworkName)
|
||||
|
||||
var bridgeID, netMoid string
|
||||
if err != nil {
|
||||
bridgeID = ""
|
||||
netMoid = ""
|
||||
} else {
|
||||
bridgeID = endpointMoref.String()
|
||||
netMoid = endpointMoref.String()
|
||||
}
|
||||
|
||||
checkBridgeVDS := true
|
||||
if err != nil {
|
||||
if _, ok := err.(*find.NotFoundError); !ok || v.IsVC() {
|
||||
v.NoteIssue(fmt.Errorf("An existing distributed port group must be specified for bridge network on vCenter: %s", err))
|
||||
v.suggestNetwork(op, "--bridge-network", false)
|
||||
checkBridgeVDS = false // prevent duplicate error output
|
||||
}
|
||||
|
||||
// this allows the dispatcher to create the network with corresponding name
|
||||
// if BridgeNetworkName doesn't already exist then we set the ContainerNetwork
|
||||
// ID to the name, but leaving the NetworkEndpoint moref as ""
|
||||
netMoid = input.BridgeNetworkName
|
||||
}
|
||||
|
||||
bridgeNet := &executor.NetworkEndpoint{
|
||||
Common: executor.Common{
|
||||
Name: "bridge",
|
||||
ID: bridgeID,
|
||||
},
|
||||
Static: true,
|
||||
IP: &net.IPNet{IP: net.IPv4zero}, // static but managed externally
|
||||
Network: executor.ContainerNetwork{
|
||||
Common: executor.Common{
|
||||
Name: "bridge",
|
||||
ID: netMoid,
|
||||
},
|
||||
Type: "bridge",
|
||||
},
|
||||
}
|
||||
// we need to have the bridge network identified as an available container network
|
||||
conf.AddContainerNetwork(&bridgeNet.Network)
|
||||
// we also need to have the appliance attached to the bridge network to allow
|
||||
// port forwarding
|
||||
conf.AddNetwork(bridgeNet)
|
||||
|
||||
// make sure that the bridge IP pool is large enough for bridge networks
|
||||
err = v.checkBridgeIPRange(input.BridgeIPRange)
|
||||
if err != nil {
|
||||
v.NoteIssue(err)
|
||||
}
|
||||
conf.BridgeIPRange = input.BridgeIPRange
|
||||
|
||||
op.Debug("Network configuration:")
|
||||
for net, val := range conf.ExecutorConfig.Networks {
|
||||
op.Debugf("\tNetwork: %s NetworkEndpoint: %v", net, val)
|
||||
}
|
||||
|
||||
err = v.checkVDSMembership(op, endpointMoref, input.BridgeNetworkName)
|
||||
if err != nil && checkBridgeVDS {
|
||||
v.NoteIssue(fmt.Errorf("Unable to check hosts in vDS for %q: %s", input.BridgeNetworkName, err))
|
||||
}
|
||||
|
||||
// add mapped networks (from --container-network)
|
||||
// these should be a distributed port groups in vCenter
|
||||
suggestedMapped := false // only suggest mapped nets once
|
||||
for name, net := range input.MappedNetworks {
|
||||
checkMappedVDS := true
|
||||
// "bridge" is reserved
|
||||
if name == "bridge" {
|
||||
v.NoteIssue(fmt.Errorf("Cannot use reserved name \"bridge\" for container network"))
|
||||
continue
|
||||
}
|
||||
|
||||
gw := input.MappedNetworksGateways[name]
|
||||
pools := input.MappedNetworksIPRanges[name]
|
||||
dns := input.MappedNetworksDNS[name]
|
||||
trust := input.MappedNetworksFirewalls[name]
|
||||
|
||||
if len(pools) != 0 && ip.IsUnspecifiedSubnet(&gw) {
|
||||
v.NoteIssue(fmt.Errorf("IP range specified without gateway for container network %q", name))
|
||||
continue
|
||||
}
|
||||
|
||||
if !ip.IsUnspecifiedSubnet(&gw) && !ip.IsRoutableIP(gw.IP, &gw) {
|
||||
v.NoteIssue(fmt.Errorf("Gateway %s is not a routable address", gw.IP))
|
||||
continue
|
||||
}
|
||||
|
||||
err = nil
|
||||
// verify ip ranges are within subnet,
|
||||
// and don't overlap with each other
|
||||
for i, r := range pools {
|
||||
if !gw.Contains(r.FirstIP) || !gw.Contains(r.LastIP) {
|
||||
err = fmt.Errorf("IP range %q is not in subnet %q", r, gw)
|
||||
break
|
||||
}
|
||||
|
||||
for _, r2 := range pools[i+1:] {
|
||||
if r2.Overlaps(r) {
|
||||
err = fmt.Errorf("Overlapping ip ranges: %q %q", r2, r)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
v.NoteIssue(err)
|
||||
continue
|
||||
}
|
||||
|
||||
moref, err := v.dpgHelper(op, net)
|
||||
if err != nil {
|
||||
v.NoteIssue(fmt.Errorf("Error adding container network %q: %s", name, err))
|
||||
checkMappedVDS = false
|
||||
if !suggestedMapped {
|
||||
v.suggestNetwork(op, "--container-network", true)
|
||||
suggestedMapped = true
|
||||
}
|
||||
}
|
||||
mappedNet := &executor.ContainerNetwork{
|
||||
Common: executor.Common{
|
||||
Name: name,
|
||||
ID: moref.String(),
|
||||
},
|
||||
Type: "external",
|
||||
Gateway: gw,
|
||||
Nameservers: dns,
|
||||
Pools: pools,
|
||||
TrustLevel: trust,
|
||||
}
|
||||
if input.BridgeNetworkName == net {
|
||||
v.NoteIssue(errors.Errorf("the bridge network must not be shared with another network role - %q also mapped as container network %q", input.BridgeNetworkName, name))
|
||||
}
|
||||
|
||||
err = v.checkVDSMembership(op, moref, net)
|
||||
if err != nil && checkMappedVDS {
|
||||
v.NoteIssue(fmt.Errorf("Unable to check hosts in vDS for %q: %s", net, err))
|
||||
}
|
||||
|
||||
conf.AddContainerNetwork(mappedNet)
|
||||
}
|
||||
|
||||
conf.AsymmetricRouting = input.AsymmetricRouting
|
||||
}
|
||||
|
||||
// generateBridgeName returns a name that can be used to create a switch/pg pair on ESX
|
||||
func (v *Validator) generateBridgeName(op trace.Operation, input *data.Data, conf *config.VirtualContainerHostConfigSpec) string {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
return input.DisplayName
|
||||
}
|
||||
|
||||
// checkBridgeIPRange verifies that the bridge network pool is large enough
|
||||
// port layer currently defaults to /16 for bridge network so pool must be >= /16
|
||||
func (v *Validator) checkBridgeIPRange(bridgeIPRange *net.IPNet) error {
|
||||
if bridgeIPRange == nil {
|
||||
return nil
|
||||
}
|
||||
ones, _ := bridgeIPRange.Mask.Size()
|
||||
if ones > 16 {
|
||||
return fmt.Errorf("Specified bridge network range is not large enough for the default bridge network size. --bridge-network-range must be /16 or larger network.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getNetwork gets a moref based on the network name
|
||||
func (v *Validator) getNetwork(op trace.Operation, name string) (object.NetworkReference, error) {
|
||||
defer trace.End(trace.Begin(name, op))
|
||||
|
||||
nets, err := v.Session.Finder.NetworkList(op, name)
|
||||
if err != nil {
|
||||
op.Debugf("no such network %q", name)
|
||||
// TODO: error message about no such match and how to get a network list
|
||||
// we return err directly here so we can check the type
|
||||
return nil, err
|
||||
}
|
||||
if len(nets) > 1 {
|
||||
// TODO: error about required disabmiguation and list entries in nets
|
||||
return nil, errors.New("ambiguous network " + name)
|
||||
}
|
||||
return nets[0], nil
|
||||
}
|
||||
|
||||
// networkHelper gets a moid based on the network name
|
||||
func (v *Validator) networkHelper(op trace.Operation, name string) (string, error) {
|
||||
defer trace.End(trace.Begin(name, op))
|
||||
|
||||
net, err := v.getNetwork(op, name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
moref := net.Reference()
|
||||
return moref.String(), nil
|
||||
}
|
||||
|
||||
func (v *Validator) dpgMorefHelper(op trace.Operation, ref string) (string, error) {
|
||||
defer trace.End(trace.Begin(ref, op))
|
||||
|
||||
moref := new(types.ManagedObjectReference)
|
||||
ok := moref.FromString(ref)
|
||||
if !ok {
|
||||
// TODO: error message about no such match and how to get a network list
|
||||
return "", errors.New("could not restore serialized managed object reference: " + ref)
|
||||
}
|
||||
|
||||
net, err := v.Session.Finder.ObjectReference(op, *moref)
|
||||
if err != nil {
|
||||
// TODO: error message about no such match and how to get a network list
|
||||
return "", errors.New("unable to locate network from moref: " + ref)
|
||||
}
|
||||
|
||||
// ensure that the type of the network is a Distributed Port Group if the target is a vCenter
|
||||
// if it's not then any network suffices
|
||||
if v.IsVC() {
|
||||
_, dpg := net.(*object.DistributedVirtualPortgroup)
|
||||
if !dpg {
|
||||
return "", fmt.Errorf("%q is not a Distributed Port Group", ref)
|
||||
}
|
||||
}
|
||||
|
||||
return ref, nil
|
||||
}
|
||||
|
||||
func (v *Validator) dpgHelper(op trace.Operation, path string) (types.ManagedObjectReference, error) {
|
||||
defer trace.End(trace.Begin(path, op))
|
||||
|
||||
net, err := v.getNetwork(op, path)
|
||||
if err != nil {
|
||||
return types.ManagedObjectReference{}, err
|
||||
}
|
||||
|
||||
// ensure that the type of the network is a Distributed Port Group if the target is a vCenter
|
||||
// if it's not then any network suffices
|
||||
if v.IsVC() {
|
||||
_, dpg := net.(*object.DistributedVirtualPortgroup)
|
||||
if !dpg {
|
||||
return types.ManagedObjectReference{}, fmt.Errorf("%q is not a Distributed Port Group", path)
|
||||
}
|
||||
}
|
||||
|
||||
return net.Reference(), nil
|
||||
}
|
||||
|
||||
// inDVP checks if the host is in the distributed virtual portgroup (dvpHosts)
|
||||
func (v *Validator) inDVP(op trace.Operation, host types.ManagedObjectReference, dvpHosts []types.ManagedObjectReference) bool {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
for _, h := range dvpHosts {
|
||||
if host == h {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// checkVDSMembership verifes all hosts in the vCenter are connected to the vDS
|
||||
func (v *Validator) checkVDSMembership(op trace.Operation, network types.ManagedObjectReference, netName string) error {
|
||||
defer trace.End(trace.Begin(network.Value, op))
|
||||
|
||||
var dvp mo.DistributedVirtualPortgroup
|
||||
var nonMembers []string
|
||||
|
||||
if !v.IsVC() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if v.Session.Cluster == nil {
|
||||
return errors.New("Invalid cluster. Check --compute-resource")
|
||||
}
|
||||
|
||||
clusterHosts, err := v.Session.Cluster.Hosts(op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r := object.NewDistributedVirtualPortgroup(v.Session.Client.Client, network)
|
||||
if err := r.Properties(op, r.Reference(), []string{"name", "host"}, &dvp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, h := range clusterHosts {
|
||||
if !v.inDVP(op, h.Reference(), dvp.Host) {
|
||||
nonMembers = append(nonMembers, h.InventoryPath)
|
||||
}
|
||||
}
|
||||
|
||||
if len(nonMembers) > 0 {
|
||||
op.Errorf("vDS configuration incorrect on %q. All cluster hosts must be in the vDS.", netName)
|
||||
op.Errorf(" %q is missing hosts:", netName)
|
||||
for _, hs := range nonMembers {
|
||||
op.Errorf(" %q", hs)
|
||||
}
|
||||
|
||||
errMsg := fmt.Sprintf("All cluster hosts must be in the vDS. %q is missing hosts: %s", netName, nonMembers)
|
||||
v.NoteIssue(errors.New(errMsg))
|
||||
} else {
|
||||
op.Infof("vDS configuration OK on %q", netName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListNetworks returns the InventoryPath of all networks (excluding DVS uplinks) or
|
||||
// all networks excluding standard networks
|
||||
func (v *Validator) listNetworks(op trace.Operation, incStdNets bool) ([]string, error) {
|
||||
var selectedNets []string
|
||||
|
||||
nets, err := v.Session.Finder.NetworkList(v.Context, "*")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to list networks: %s", err)
|
||||
}
|
||||
|
||||
if len(nets) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
for _, net := range nets {
|
||||
switch o := net.(type) {
|
||||
case *object.DistributedVirtualPortgroup:
|
||||
// Filter out DVS uplink
|
||||
if !v.isDVSUplink(op, net.Reference()) {
|
||||
selectedNets = append(selectedNets, o.InventoryPath)
|
||||
}
|
||||
case *object.Network:
|
||||
if incStdNets {
|
||||
selectedNets = append(selectedNets, o.InventoryPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return selectedNets, nil
|
||||
}
|
||||
|
||||
// suggestNetwork suggests all networks
|
||||
// incStdNets includes standard Networks in addition to DPGs
|
||||
func (v *Validator) suggestNetwork(op trace.Operation, flag string, incStdNets bool) {
|
||||
defer trace.End(trace.Begin(flag, op))
|
||||
|
||||
nets, err := v.listNetworks(op, incStdNets)
|
||||
if err != nil {
|
||||
op.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(nets) == 0 {
|
||||
op.Info("No networks found")
|
||||
return
|
||||
}
|
||||
|
||||
op.Infof("Suggested values for %s:", flag)
|
||||
for _, n := range nets {
|
||||
if v.isNetworkNameValid(n, flag) {
|
||||
op.Infof(" %q", path.Base(n))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// isDVSUplink determines if the DVP is an uplink
|
||||
func (v *Validator) isDVSUplink(op trace.Operation, ref types.ManagedObjectReference) bool {
|
||||
defer trace.End(trace.Begin(ref.Value, op))
|
||||
|
||||
var dvp mo.DistributedVirtualPortgroup
|
||||
|
||||
r := object.NewDistributedVirtualPortgroup(v.Session.Client.Client, ref)
|
||||
if err := r.Properties(v.Context, r.Reference(), []string{"tag"}, &dvp); err != nil {
|
||||
op.Errorf("Unable to check tags on %q: %s", ref, err)
|
||||
return false
|
||||
}
|
||||
for _, t := range dvp.Tag {
|
||||
if strings.Contains(t.Key, "UPLINKPG") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isNetworkNameValid determines if the network name in inventoryPath is
|
||||
// not a reserved name
|
||||
func (v *Validator) isNetworkNameValid(inventoryPath, flag string) bool {
|
||||
n := path.Base(inventoryPath)
|
||||
if flag != "--bridge-network" && n == "bridge" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
224
vendor/github.com/vmware/vic/lib/install/validate/storage.go
generated
vendored
Normal file
224
vendor/github.com/vmware/vic/lib/install/validate/storage.go
generated
vendored
Normal file
@@ -0,0 +1,224 @@
|
||||
// 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 validate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/vic/cmd/vic-machine/common"
|
||||
"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/datastore"
|
||||
)
|
||||
|
||||
func (v *Validator) storage(op trace.Operation, input *data.Data, conf *config.VirtualContainerHostConfigSpec) {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
// Image Store
|
||||
imageDSpath, ds, err := v.DatastoreHelper(op, input.ImageDatastorePath, "", "--image-store")
|
||||
|
||||
if err != nil {
|
||||
v.NoteIssue(err)
|
||||
return
|
||||
}
|
||||
|
||||
// provide a default path if only a DS name is provided
|
||||
if imageDSpath.Path == "" {
|
||||
op.Debug("No path element specified for image store - will use default")
|
||||
}
|
||||
|
||||
if ds != nil {
|
||||
v.SetDatastore(ds, imageDSpath)
|
||||
conf.AddImageStore(imageDSpath)
|
||||
}
|
||||
|
||||
if conf.VolumeLocations == nil {
|
||||
conf.VolumeLocations = make(map[string]*url.URL)
|
||||
}
|
||||
|
||||
for label, targetURL := range input.VolumeLocations {
|
||||
var vsErr error
|
||||
switch targetURL.Scheme {
|
||||
case common.NfsScheme:
|
||||
vsErr := validateNFSTarget(targetURL)
|
||||
v.NoteIssue(vsErr)
|
||||
case common.DsScheme:
|
||||
// TODO: change v.DatastoreHelper to take url struct instead of string and modify tests.
|
||||
targetURL, _, vsErr = v.DatastoreHelper(op, targetURL.Path, label, "--volume-store")
|
||||
v.NoteIssue(vsErr)
|
||||
default:
|
||||
// We should not reach here, if we do we will attempt to treat this as a vsphere datastore
|
||||
targetURL, _, vsErr = v.DatastoreHelper(op, targetURL.String(), label, "--volume-store")
|
||||
v.NoteIssue(vsErr)
|
||||
}
|
||||
|
||||
// skip adding volume stores that we know will fail
|
||||
if vsErr != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
conf.VolumeLocations[label] = targetURL
|
||||
}
|
||||
}
|
||||
|
||||
func validateNFSTarget(nfsURL *url.URL) error {
|
||||
if nfsURL.Host == "" {
|
||||
return fmt.Errorf("volume store target (%s) is missing the host field. format: <nfs://<user>:<password>@<host>/<share point path>:label", nfsURL.String())
|
||||
}
|
||||
|
||||
if nfsURL.Path == "" {
|
||||
return fmt.Errorf("volume store target (%s) is missing the path field. format: <nfs://<host>/<share point path>?<mount options as query vars>:label", nfsURL.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *Validator) DatastoreHelper(ctx context.Context, path string, label string, flag string) (*url.URL, *object.Datastore, error) {
|
||||
op := trace.FromContext(ctx, "DatastoreHelper")
|
||||
defer trace.End(trace.Begin(path, op))
|
||||
|
||||
stripRawTarget := path
|
||||
|
||||
if strings.HasPrefix(stripRawTarget, common.DsScheme+"://") {
|
||||
stripRawTarget = strings.Replace(path, common.DsScheme+"://", "", -1)
|
||||
}
|
||||
|
||||
// #nosec: Errors unhandled.
|
||||
stripRawTarget, _ = url.PathUnescape(stripRawTarget)
|
||||
dsURL, dsErr := url.Parse(stripRawTarget)
|
||||
if dsErr != nil {
|
||||
return nil, nil, errors.Errorf("error parsing datastore path: %s", dsErr)
|
||||
}
|
||||
|
||||
path = stripRawTarget
|
||||
|
||||
// url scheme does not contain ://, so remove it to make url work
|
||||
if dsURL.Scheme != "" && dsURL.Scheme != "ds" {
|
||||
return nil, nil, errors.Errorf("bad scheme %q provided for datastore", dsURL.Scheme)
|
||||
}
|
||||
|
||||
dsURL.Scheme = common.DsScheme
|
||||
|
||||
// if a datastore name (e.g. "datastore1") is specified with no decoration then this
|
||||
// is interpreted as the Path
|
||||
if dsURL.Host == "" && dsURL.Path != "" {
|
||||
pathElements := strings.SplitN(path, "/", 2)
|
||||
dsURL.Host = pathElements[0]
|
||||
if len(pathElements) > 1 {
|
||||
dsURL.Path = pathElements[1]
|
||||
} else {
|
||||
dsURL.Path = ""
|
||||
}
|
||||
}
|
||||
|
||||
if dsURL.Host == "" {
|
||||
// see if we can find a default datastore
|
||||
store, err := v.Session.Finder.DatastoreOrDefault(op, "*")
|
||||
if err != nil {
|
||||
v.suggestDatastore(op, "*", label, flag)
|
||||
return nil, nil, errors.New("datastore empty")
|
||||
}
|
||||
|
||||
dsURL.Host = store.Name()
|
||||
op.Infof("Using default datastore: %s", dsURL.Host)
|
||||
}
|
||||
|
||||
stores, err := v.Session.Finder.DatastoreList(op, dsURL.Host)
|
||||
if err != nil {
|
||||
op.Debugf("no such datastore %#v", dsURL)
|
||||
v.suggestDatastore(op, path, label, flag)
|
||||
// TODO: error message about no such match and how to get a datastore list
|
||||
// we return err directly here so we can check the type
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(stores) > 1 {
|
||||
// TODO: error about required disabmiguation and list entries in stores
|
||||
v.suggestDatastore(op, path, label, flag)
|
||||
return nil, nil, errors.New("ambiguous datastore " + dsURL.Host)
|
||||
}
|
||||
|
||||
// temporary until session is extracted
|
||||
// FIXME: commented out until components can consume moid
|
||||
// dsURL.Host = stores[0].Reference().Value
|
||||
|
||||
// make sure the vsphere ds format fits the right format
|
||||
if _, err := datastore.ToURL(fmt.Sprintf("[%s] %s", dsURL.Host, dsURL.Path)); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return dsURL, stores[0], nil
|
||||
}
|
||||
|
||||
func (v *Validator) SetDatastore(ds *object.Datastore, path *url.URL) {
|
||||
v.Session.Datastore = ds
|
||||
v.Session.DatastorePath = path.Host
|
||||
}
|
||||
|
||||
func (v *Validator) ListDatastores() ([]string, error) {
|
||||
dss, err := v.Session.Finder.DatastoreList(v.Context, "*")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to list datastores: %s", err)
|
||||
}
|
||||
|
||||
if len(dss) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
matches := make([]string, len(dss))
|
||||
for i, d := range dss {
|
||||
matches[i] = d.Name()
|
||||
}
|
||||
return matches, nil
|
||||
}
|
||||
|
||||
// suggestDatastore suggests all datastores present on target in datastore:label format if applicable
|
||||
func (v *Validator) suggestDatastore(op trace.Operation, path string, label string, flag string) {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
var val string
|
||||
if label != "" {
|
||||
val = fmt.Sprintf("%s:%s", path, label)
|
||||
} else {
|
||||
val = path
|
||||
}
|
||||
op.Infof("Suggesting valid values for %s based on %q", flag, val)
|
||||
|
||||
dss, err := v.ListDatastores()
|
||||
if err != nil {
|
||||
op.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(dss) == 0 {
|
||||
op.Info("No datastores found")
|
||||
return
|
||||
}
|
||||
|
||||
if dss != nil {
|
||||
op.Infof("Suggested values for %s:", flag)
|
||||
for _, d := range dss {
|
||||
if label != "" {
|
||||
d = fmt.Sprintf("%s:%s", d, label)
|
||||
}
|
||||
op.Infof(" %q", d)
|
||||
}
|
||||
}
|
||||
}
|
||||
93
vendor/github.com/vmware/vic/lib/install/validate/update.go
generated
vendored
Normal file
93
vendor/github.com/vmware/vic/lib/install/validate/update.go
generated
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
// 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 validate
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/vmware/vic/lib/config"
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/version"
|
||||
)
|
||||
|
||||
// MigrateConfig migrate old VCH configuration to new version. Currently check required fields only
|
||||
func (v *Validator) ValidateMigratedConfig(ctx context.Context, conf *config.VirtualContainerHostConfigSpec) (*config.VirtualContainerHostConfigSpec, error) {
|
||||
defer trace.End(trace.Begin(conf.Name, ctx))
|
||||
|
||||
v.assertTarget(ctx, conf)
|
||||
v.assertDatastore(ctx, conf)
|
||||
v.assertNetwork(ctx, conf)
|
||||
if err := v.ListIssues(ctx); err != nil {
|
||||
return conf, err
|
||||
}
|
||||
return v.migrateData(ctx, conf)
|
||||
}
|
||||
|
||||
func (v *Validator) migrateData(ctx context.Context, conf *config.VirtualContainerHostConfigSpec) (*config.VirtualContainerHostConfigSpec, error) {
|
||||
conf.Version = version.GetBuild()
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
func (v *Validator) assertNetwork(ctx context.Context, conf *config.VirtualContainerHostConfigSpec) {
|
||||
// minimum network configuration check
|
||||
}
|
||||
|
||||
// assertDatastore check required datastore configuration only
|
||||
func (v *Validator) assertDatastore(ctx context.Context, conf *config.VirtualContainerHostConfigSpec) {
|
||||
defer trace.End(trace.Begin("", ctx))
|
||||
if len(conf.ImageStores) == 0 {
|
||||
v.NoteIssue(errors.New("Image store is not set"))
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Validator) assertTarget(ctx context.Context, conf *config.VirtualContainerHostConfigSpec) {
|
||||
defer trace.End(trace.Begin("", ctx))
|
||||
|
||||
if conf.Target == "" {
|
||||
v.NoteIssue(errors.New("target is not set"))
|
||||
}
|
||||
|
||||
if conf.Username == "" {
|
||||
v.NoteIssue(errors.New("target username is not set"))
|
||||
}
|
||||
|
||||
if conf.Token == "" {
|
||||
v.NoteIssue(errors.New("target token is not set"))
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Validator) AssertVersion(ctx context.Context, conf *config.VirtualContainerHostConfigSpec) (err error) {
|
||||
defer trace.End(trace.Begin("", ctx))
|
||||
defer func() {
|
||||
err = v.ListIssues(ctx)
|
||||
}()
|
||||
|
||||
if conf.Version == nil {
|
||||
v.NoteIssue(errors.Errorf("Unknown version of VCH %q", conf.Name))
|
||||
return err
|
||||
}
|
||||
var older bool
|
||||
installerBuild := version.GetBuild()
|
||||
if older, err = conf.Version.IsOlder(installerBuild); err != nil {
|
||||
v.NoteIssue(errors.Errorf("Failed to compare VCH version %q with installer version %q: %s", conf.Version.ShortVersion(), installerBuild.ShortVersion(), err))
|
||||
return err
|
||||
}
|
||||
if !older {
|
||||
v.NoteIssue(errors.Errorf("%q has same or newer version %s than installer version %s. No upgrade is available.", conf.Name, conf.Version.ShortVersion(), installerBuild.ShortVersion()))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
982
vendor/github.com/vmware/vic/lib/install/validate/validator.go
generated
vendored
Normal file
982
vendor/github.com/vmware/vic/lib/install/validate/validator.go
generated
vendored
Normal file
@@ -0,0 +1,982 @@
|
||||
// Copyright 2016-2018 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 validate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
units "github.com/docker/go-units"
|
||||
|
||||
"github.com/vmware/govmomi"
|
||||
"github.com/vmware/govmomi/find"
|
||||
"github.com/vmware/govmomi/object"
|
||||
vmomisession "github.com/vmware/govmomi/session"
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
"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/config/dynamic"
|
||||
"github.com/vmware/vic/lib/config/executor"
|
||||
"github.com/vmware/vic/lib/constants"
|
||||
"github.com/vmware/vic/lib/install/data"
|
||||
"github.com/vmware/vic/lib/install/kubelet"
|
||||
"github.com/vmware/vic/lib/install/opsuser"
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/registry"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/version"
|
||||
"github.com/vmware/vic/pkg/vsphere/optmanager"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
)
|
||||
|
||||
const defaultSyslogPort = 514
|
||||
const registryValidationTime = 10 * time.Second
|
||||
|
||||
type Validator struct {
|
||||
TargetPath string
|
||||
DatacenterPath string
|
||||
ClusterPath string
|
||||
ResourcePoolPath string
|
||||
ImageStorePath string
|
||||
PublicNetworkPath string
|
||||
BridgeNetworkPath string
|
||||
BridgeNetworkName string
|
||||
|
||||
Session *session.Session
|
||||
Context context.Context
|
||||
|
||||
isVC bool
|
||||
issues []error
|
||||
|
||||
DisableDRSCheck bool
|
||||
allowEmptyDC bool
|
||||
}
|
||||
|
||||
func CreateFromVCHConfig(ctx context.Context, vch *config.VirtualContainerHostConfigSpec, sess *session.Session) (*Validator, error) {
|
||||
defer trace.End(trace.Begin("", ctx))
|
||||
|
||||
v := &Validator{}
|
||||
v.Session = sess
|
||||
v.Context = ctx
|
||||
v.isVC = v.Session.IsVC()
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func NewValidator(ctx context.Context, input *data.Data) (*Validator, error) {
|
||||
op := trace.FromContext(ctx, "NewValidator")
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
var err error
|
||||
|
||||
v := &Validator{}
|
||||
v.Context = ctx
|
||||
tURL := input.URL
|
||||
|
||||
// normalize the path - strip trailing /
|
||||
input.URL.Path = strings.TrimSuffix(input.URL.Path, "/")
|
||||
|
||||
// default to https scheme
|
||||
if tURL.Scheme == "" {
|
||||
tURL.Scheme = "https"
|
||||
}
|
||||
|
||||
// if they specified only an IP address the parser for some reason considers that a path
|
||||
if tURL.Host == "" {
|
||||
tURL.Host = tURL.Path
|
||||
tURL.Path = ""
|
||||
}
|
||||
|
||||
if tURL.Scheme == "https" && input.Thumbprint == "" {
|
||||
var cert object.HostCertificateInfo
|
||||
if err = cert.FromURL(tURL, new(tls.Config)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cert.Err != nil {
|
||||
if !input.Force {
|
||||
// TODO: prompt user / check ./known_hosts
|
||||
op.Errorf("Failed to verify certificate for target=%s (thumbprint=%s)",
|
||||
tURL.Host, cert.ThumbprintSHA1)
|
||||
return nil, cert.Err
|
||||
}
|
||||
}
|
||||
|
||||
input.Thumbprint = cert.ThumbprintSHA1
|
||||
op.Debugf("Accepting host %q thumbprint %s", tURL.Host, input.Thumbprint)
|
||||
}
|
||||
|
||||
sessionconfig := &session.Config{
|
||||
Thumbprint: input.Thumbprint,
|
||||
Insecure: input.Force,
|
||||
}
|
||||
|
||||
// if a datacenter was specified, set it
|
||||
v.DatacenterPath = tURL.Path
|
||||
if v.DatacenterPath != "" {
|
||||
v.DatacenterPath = strings.TrimPrefix(v.DatacenterPath, "/")
|
||||
sessionconfig.DatacenterPath = v.DatacenterPath
|
||||
// path needs to be stripped before we can use it as a service url
|
||||
tURL.Path = ""
|
||||
}
|
||||
|
||||
sessionconfig.Service = tURL.String()
|
||||
|
||||
sessionconfig.CloneTicket = input.CloneTicket
|
||||
|
||||
v.Session = session.NewSession(sessionconfig)
|
||||
v.Session.UserAgent = version.UserAgent("vic-machine")
|
||||
v.Session, err = v.Session.Connect(v.Context)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// cached here to allow a modicum of testing while session is still in use.
|
||||
v.isVC = v.Session.IsVC()
|
||||
finder := find.NewFinder(v.Session.Client.Client, false)
|
||||
v.Session.Finder = finder
|
||||
|
||||
// Intentionally ignore any error returned by Populate
|
||||
_, err = v.Session.Populate(ctx)
|
||||
if err != nil {
|
||||
op.Debugf("new validator Session.Populate: %s", err)
|
||||
}
|
||||
|
||||
if strings.Contains(sessionconfig.DatacenterPath, "/") {
|
||||
detail := "--target should only specify datacenter in the path (e.g. https://addr/datacenter) - specify cluster, resource pool, or folder with --compute-resource"
|
||||
op.Error(detail)
|
||||
v.suggestDatacenter(op)
|
||||
return nil, errors.New(detail)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
var schemeMatch = regexp.MustCompile(`^\w+://`)
|
||||
|
||||
// Starting from Go 1.8 the URL parser does not
|
||||
// work properly with URLs with no Scheme,
|
||||
// this function adds "https" as Scheme if necessary
|
||||
func ParseURL(s string) (*url.URL, error) {
|
||||
var err error
|
||||
var u *url.URL
|
||||
|
||||
if s != "" {
|
||||
// Default the scheme to https
|
||||
if !schemeMatch.MatchString(s) {
|
||||
s = "https://" + s
|
||||
}
|
||||
|
||||
u, err = url.Parse(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (v *Validator) AllowEmptyDC() {
|
||||
v.allowEmptyDC = true
|
||||
}
|
||||
|
||||
func (v *Validator) datacenter(op trace.Operation) error {
|
||||
if v.allowEmptyDC && v.DatacenterPath == "" {
|
||||
return nil
|
||||
}
|
||||
if v.Session.Datacenter != nil {
|
||||
v.DatacenterPath = v.Session.Datacenter.InventoryPath
|
||||
return nil
|
||||
}
|
||||
var detail string
|
||||
if v.DatacenterPath != "" {
|
||||
detail = fmt.Sprintf("Datacenter %q in --target is not found", path.Base(v.DatacenterPath))
|
||||
} else {
|
||||
// this means multiple datacenter exists, but user did not specify it in --target
|
||||
detail = "Datacenter must be specified in --target (e.g. https://addr/datacenter)"
|
||||
}
|
||||
op.Error(detail)
|
||||
v.suggestDatacenter(op)
|
||||
return errors.New(detail)
|
||||
}
|
||||
|
||||
func (v *Validator) ListDatacenters() ([]string, error) {
|
||||
dcs, err := v.Session.Finder.DatacenterList(v.Context, "*")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to list datacenters: %s", err)
|
||||
}
|
||||
|
||||
if len(dcs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
matches := make([]string, len(dcs))
|
||||
for i, d := range dcs {
|
||||
matches[i] = d.Name()
|
||||
}
|
||||
return matches, nil
|
||||
}
|
||||
|
||||
// suggestDatacenter suggests all datacenters on the target
|
||||
func (v *Validator) suggestDatacenter(op trace.Operation) {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
op.Info("Suggesting valid values for datacenter in --target")
|
||||
|
||||
dcs, err := v.ListDatacenters()
|
||||
if err != nil {
|
||||
op.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(dcs) == 0 {
|
||||
op.Info("No datacenters found")
|
||||
return
|
||||
}
|
||||
|
||||
op.Info("Suggested datacenters:")
|
||||
for _, d := range dcs {
|
||||
op.Infof(" %q", d)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (v *Validator) NoteIssue(err error) {
|
||||
if err != nil {
|
||||
v.issues = append(v.issues, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Validator) ListIssues(ctx context.Context) error {
|
||||
op := trace.FromContext(ctx, "ListIssues")
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
if len(v.issues) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
op.Error("--------------------")
|
||||
for _, err := range v.issues {
|
||||
op.Error(err)
|
||||
}
|
||||
|
||||
return errors.New("validation of configuration failed")
|
||||
}
|
||||
|
||||
func (v *Validator) GetIssues() []error {
|
||||
return v.issues
|
||||
}
|
||||
|
||||
func (v *Validator) ClearIssues() {
|
||||
v.issues = []error{}
|
||||
}
|
||||
|
||||
// Validate runs through various validations, starting with basics such as naming, moving onto vSphere entities
|
||||
// and then the compatibility between those entities. It assembles a set of issues that are found for reporting.
|
||||
func (v *Validator) Validate(ctx context.Context, input *data.Data) (*config.VirtualContainerHostConfigSpec, error) {
|
||||
op := trace.FromContext(ctx, "Validate")
|
||||
defer trace.End(trace.Begin("", op))
|
||||
op.Info("Validating supplied configuration")
|
||||
|
||||
conf := &config.VirtualContainerHostConfigSpec{}
|
||||
|
||||
if err := v.datacenter(op); err != nil {
|
||||
return conf, err
|
||||
}
|
||||
|
||||
v.basics(op, input, conf)
|
||||
|
||||
v.target(op, input, conf)
|
||||
v.credentials(op, input, conf)
|
||||
v.compute(op, input, conf)
|
||||
v.storage(op, input, conf)
|
||||
v.network(op, input, conf)
|
||||
// FIXME ATC DEBT setting this value needs to be moved to Dispatcher
|
||||
// https://github.com/vmware/vic/issues/6803
|
||||
ok := v.CheckPersistNetworkBacking(op, true)
|
||||
if !ok {
|
||||
err := v.ConfigureVCenter(op)
|
||||
if err != nil {
|
||||
op.Errorf("%s", err)
|
||||
op.Errorf("vCenter settings update FAILED")
|
||||
}
|
||||
}
|
||||
v.CheckFirewall(op, conf)
|
||||
v.CheckPersistNetworkBacking(op, false)
|
||||
v.CheckLicense(op)
|
||||
v.CheckDrs(op)
|
||||
|
||||
v.certificate(op, input, conf)
|
||||
v.certificateAuthorities(op, input, conf)
|
||||
v.registries(op, input, conf)
|
||||
|
||||
// Perform the higher level compatibility and consistency checks
|
||||
v.compatibility(op, conf)
|
||||
|
||||
v.syslog(op, conf, input)
|
||||
|
||||
// Kubelet
|
||||
v.kubelet(op, conf, input)
|
||||
|
||||
// TODO: determine if this is where we should turn the noted issues into message
|
||||
return conf, v.ListIssues(op)
|
||||
|
||||
}
|
||||
|
||||
func (v *Validator) ValidateTarget(ctx context.Context, input *data.Data) (*config.VirtualContainerHostConfigSpec, error) {
|
||||
op := trace.FromContext(ctx, "ValidateTarget")
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
conf := &config.VirtualContainerHostConfigSpec{}
|
||||
|
||||
op.Info("Validating target")
|
||||
if err := v.datacenter(op); err != nil {
|
||||
return conf, err
|
||||
}
|
||||
v.target(op, input, conf)
|
||||
return conf, v.ListIssues(op)
|
||||
}
|
||||
|
||||
func (v *Validator) basics(op trace.Operation, input *data.Data, conf *config.VirtualContainerHostConfigSpec) {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
// TODO: ensure that displayname doesn't violate constraints (length, characters, etc)
|
||||
conf.SetName(input.DisplayName)
|
||||
|
||||
if input.Debug.Debug != nil {
|
||||
conf.SetDebug(*input.Debug.Debug)
|
||||
}
|
||||
|
||||
conf.Name = input.DisplayName
|
||||
conf.Version = version.GetBuild()
|
||||
|
||||
scratchSize, err := units.FromHumanSize(input.ScratchSize)
|
||||
if err != nil { // TODO set minimum size of scratch disk
|
||||
v.NoteIssue(errors.Errorf("Invalid default image size %s provided; error from parser: %s", input.ScratchSize, err.Error()))
|
||||
} else {
|
||||
conf.ScratchSize = scratchSize / units.KB
|
||||
op.Debugf("Setting scratch image size to %d KB in VCHConfig", conf.ScratchSize)
|
||||
}
|
||||
|
||||
if input.ContainerNameConvention != "" {
|
||||
// ensure token is present
|
||||
if !strings.Contains(input.ContainerNameConvention, string(config.IDToken)) && !strings.Contains(input.ContainerNameConvention, string(config.NameToken)) {
|
||||
v.NoteIssue(errors.Errorf("Container name convention must include %s or %s token", config.IDToken, config.NameToken))
|
||||
}
|
||||
|
||||
// coarse check - only enforce that there's enough capcity for a shortID
|
||||
// name lengths are many and vary significantly so much harder to provide sanity checks for - they will truncate when convention is applied.
|
||||
if len(input.ContainerNameConvention)-len(config.IDToken) >= constants.MaxVMNameLength-constants.ShortIDLen {
|
||||
v.NoteIssue(errors.Errorf("Container name convetion exceeds maximum length (%d, discounting tokens)", constants.MaxVMNameLength-constants.ShortIDLen))
|
||||
}
|
||||
}
|
||||
|
||||
conf.ContainerNameConvention = input.ContainerNameConvention
|
||||
}
|
||||
|
||||
func (v *Validator) checkSessionSet() []string {
|
||||
var errs []string
|
||||
|
||||
if v.Session.Datastore == nil {
|
||||
errs = append(errs, "datastore not set")
|
||||
}
|
||||
if v.Session.Cluster == nil {
|
||||
errs = append(errs, "cluster not set")
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func (v *Validator) sessionValid(op trace.Operation, errMsg string) bool {
|
||||
if c := v.checkSessionSet(); len(c) > 0 {
|
||||
op.Error(errMsg)
|
||||
for _, e := range c {
|
||||
op.Errorf(" %s", e)
|
||||
}
|
||||
v.NoteIssue(errors.New(errMsg))
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (v *Validator) target(op trace.Operation, input *data.Data, conf *config.VirtualContainerHostConfigSpec) {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
// check if host is managed by VC
|
||||
v.managedbyVC(op)
|
||||
}
|
||||
|
||||
func (v *Validator) managedbyVC(op trace.Operation) {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
if v.IsVC() {
|
||||
return
|
||||
}
|
||||
host, err := v.Session.Finder.DefaultHostSystem(op)
|
||||
if err != nil {
|
||||
v.NoteIssue(fmt.Errorf("Failed to get host system: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
var mh mo.HostSystem
|
||||
|
||||
if err = host.Properties(op, host.Reference(), []string{"summary.managementServerIp"}, &mh); err != nil {
|
||||
v.NoteIssue(fmt.Errorf("Failed to get host properties: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
if ip := mh.Summary.ManagementServerIp; ip != "" {
|
||||
v.NoteIssue(fmt.Errorf("Target is managed by vCenter server %q, please change --target to vCenter server address or select a standalone ESXi", ip))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (v *Validator) credentials(op trace.Operation, input *data.Data, conf *config.VirtualContainerHostConfigSpec) {
|
||||
// empty string for password is horrific, but a legitimate scenario especially in isolated labs
|
||||
if input.OpsCredentials.OpsUser == nil || input.OpsCredentials.OpsPassword == nil {
|
||||
v.NoteIssue(errors.New("User/password for the operations user has not been set"))
|
||||
return
|
||||
}
|
||||
|
||||
// check target with ops credentials
|
||||
u := input.Target.URL
|
||||
|
||||
conf.Username = *input.OpsCredentials.OpsUser
|
||||
conf.Token = *input.OpsCredentials.OpsPassword
|
||||
conf.TargetThumbprint = input.Thumbprint
|
||||
|
||||
// If Grant Perms has been explicitly requested (either true or false)
|
||||
// set it to the new value. Otherwise leave the value in conf as it is
|
||||
if input.OpsCredentials.GrantPerms != nil {
|
||||
if *input.OpsCredentials.GrantPerms {
|
||||
// Set Grant Permissions level
|
||||
conf.SetGrantPerms()
|
||||
} else {
|
||||
conf.ClearGrantPerms()
|
||||
}
|
||||
}
|
||||
|
||||
// If Grant Perms is set trying adding ReadOnly role to the Datacenter
|
||||
// for the ops-user. This is necessary since the Login operation below
|
||||
// fails if the ops-user has no permissions.
|
||||
//
|
||||
// FIXME DEBT.
|
||||
// https://github.com/vmware/vic/issues/6870
|
||||
// Notice that this operation should not be performed from the Validator.
|
||||
// Eventually, this must be moved to the Dispatcher as the Validator
|
||||
// should not modify VC configuration.
|
||||
if conf.ShouldGrantPerms() {
|
||||
err := opsuser.GrantDCReadOnlyPerms(v.Context, v.Session, conf)
|
||||
if err != nil {
|
||||
v.NoteIssue(fmt.Errorf("Failed to validate operations credentials: %s", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Discard anything other than these URL fields for the target
|
||||
stripped := &url.URL{
|
||||
Scheme: u.Scheme,
|
||||
Host: u.Host,
|
||||
Path: u.Path,
|
||||
}
|
||||
conf.Target = stripped.String()
|
||||
|
||||
// validate that the provided operations credentials are valid
|
||||
stripped.Path = "/sdk"
|
||||
|
||||
var soapClient *soap.Client
|
||||
|
||||
if input.Thumbprint != "" {
|
||||
// if any thumprint is specified, then object if there's a mismatch
|
||||
soapClient = soap.NewClient(stripped, false)
|
||||
soapClient.SetThumbprint(stripped.Host, conf.TargetThumbprint)
|
||||
} else {
|
||||
soapClient = soap.NewClient(stripped, input.Force)
|
||||
}
|
||||
soapClient.UserAgent = "vice-validator"
|
||||
|
||||
vimClient, err := vim25.NewClient(op, soapClient)
|
||||
if err != nil {
|
||||
v.NoteIssue(fmt.Errorf("Failed to create client for validation of operations credentials: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
client := &govmomi.Client{
|
||||
Client: vimClient,
|
||||
SessionManager: vmomisession.NewManager(vimClient),
|
||||
}
|
||||
|
||||
err = client.Login(op, url.UserPassword(conf.Username, conf.Token))
|
||||
if err != nil {
|
||||
v.NoteIssue(fmt.Errorf("Failed to validate operations credentials: %s", err))
|
||||
return
|
||||
}
|
||||
client.Logout(op)
|
||||
|
||||
// confirm the RBAC configuration of the provided user
|
||||
// TODO: this can be dropped once we move to configuration the RBAC during creation
|
||||
}
|
||||
|
||||
func (v *Validator) certificate(op trace.Operation, input *data.Data, conf *config.VirtualContainerHostConfigSpec) {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
if len(input.CertPEM) == 0 && len(input.KeyPEM) == 0 {
|
||||
// if there's no data supplied then we're configuring without TLS
|
||||
op.Debug("Configuring without TLS due to empty key and cert buffers")
|
||||
return
|
||||
}
|
||||
|
||||
// check the cert can be loaded
|
||||
_, err := tls.X509KeyPair(input.CertPEM, input.KeyPEM)
|
||||
v.NoteIssue(err)
|
||||
|
||||
conf.HostCertificate = &config.RawCertificate{
|
||||
Key: input.KeyPEM,
|
||||
Cert: input.CertPEM,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Validator) certificateAuthorities(op trace.Operation, input *data.Data, conf *config.VirtualContainerHostConfigSpec) {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
if len(input.ClientCAs) == 0 {
|
||||
// if there's no data supplied then we're configuring without client verification
|
||||
op.Debug("Configuring without client verification due to empty certificate authorities")
|
||||
return
|
||||
}
|
||||
|
||||
// ensure TLS is configurable
|
||||
if len(input.CertPEM) == 0 {
|
||||
v.NoteIssue(errors.New("Certificate authority specified, but no TLS certificate provided"))
|
||||
return
|
||||
}
|
||||
|
||||
// check a CA can be loaded
|
||||
pool := x509.NewCertPool()
|
||||
if !pool.AppendCertsFromPEM(input.ClientCAs) {
|
||||
v.NoteIssue(errors.New("Unable to load certificate authority data"))
|
||||
return
|
||||
}
|
||||
|
||||
conf.CertificateAuthorities = input.ClientCAs
|
||||
}
|
||||
|
||||
func (v *Validator) registries(op trace.Operation, input *data.Data, conf *config.VirtualContainerHostConfigSpec) {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
// Check if CAs can be loaded
|
||||
pool := x509.NewCertPool()
|
||||
if len(input.RegistryCAs) > 0 {
|
||||
if !pool.AppendCertsFromPEM(input.RegistryCAs) {
|
||||
v.NoteIssue(errors.New("Unable to load certificate authority data for registry"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
conf.RegistryCertificateAuthorities = input.RegistryCAs
|
||||
|
||||
// test reachability
|
||||
insecureRegistries, whitelistRegistries, err := v.reachableRegistries(op, input, pool)
|
||||
if err != nil {
|
||||
v.NoteIssue(err)
|
||||
return
|
||||
}
|
||||
|
||||
// copy the list of insecure registries
|
||||
conf.InsecureRegistries = insecureRegistries
|
||||
|
||||
// copy the list of whitelist registries
|
||||
conf.RegistryWhitelist = whitelistRegistries
|
||||
|
||||
// create vic-machine info message
|
||||
msg := v.friendlyRegistryList("Insecure registries", conf.InsecureRegistries)
|
||||
if msg != "" {
|
||||
op.Info(msg)
|
||||
}
|
||||
msg = v.friendlyRegistryList("Whitelist registries", conf.RegistryWhitelist)
|
||||
if msg != "" {
|
||||
op.Info(msg)
|
||||
}
|
||||
|
||||
if len(input.RegistryCAs) == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Validator) friendlyRegistryList(registryType string, registryList []string) string {
|
||||
if len(registryList) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return registryType + " = " + strings.Join(registryList, ", ")
|
||||
}
|
||||
|
||||
// Validate registries are reachable. Secure registries that are not specified as insecure are validated with the
|
||||
// CA certs passed into vic-machine.
|
||||
func (v *Validator) reachableRegistries(op trace.Operation, input *data.Data, pool *x509.CertPool) (insecureRegistries []string, whitelistRegistries []string, err error) {
|
||||
secureRegistriesSet, err := dynamic.ParseRegistries(input.WhitelistRegistries)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
insecureRegistriesSet, err := dynamic.ParseRegistries(input.InsecureRegistries)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Test insecure registries' reachability
|
||||
for _, r := range insecureRegistriesSet {
|
||||
r, ok := r.(registry.URLEntry)
|
||||
if !ok {
|
||||
err = fmt.Errorf("invalid insecure registry entry: %s", r)
|
||||
v.NoteIssue(err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Remove intersection between insecure registries and whitelist registries from whitelist set so
|
||||
// we can ensure we test the exclusion set with certs
|
||||
for idx, s := range secureRegistriesSet {
|
||||
if s.IsURL() && r.Match(s.String()) {
|
||||
// remove the insecure registry from list of registries to get validated against certs
|
||||
secureRegistriesSet = append(secureRegistriesSet[:idx], secureRegistriesSet[idx+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure address is not a wildcard domain or CIDR. If it is, do not validate.
|
||||
if strings.HasPrefix(r.URL().Host, "*") {
|
||||
op.Debugf("Skipping registry validation for %s", r)
|
||||
continue
|
||||
}
|
||||
|
||||
schemes := []string{""}
|
||||
if r.URL().Scheme == "" {
|
||||
schemes = []string{"https", "http"}
|
||||
}
|
||||
|
||||
rs := r.String()
|
||||
for _, s := range schemes {
|
||||
if _, err = registry.Reachable(rs, s, "", "", nil, registryValidationTime, true); err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
op.Warnf("Unable to confirm insecure registry %s is a valid registry at this time.", r)
|
||||
} else {
|
||||
op.Debugf("Insecure registry %s confirmed.", r)
|
||||
}
|
||||
}
|
||||
|
||||
// Test secure registries' reachability
|
||||
for _, w := range secureRegistriesSet {
|
||||
// Make sure address is not a wildcard domain or CIDR. If it is, do not validate.
|
||||
if w.IsCIDR() {
|
||||
op.Debugf("Skipping registry validation for %s", w)
|
||||
continue
|
||||
}
|
||||
|
||||
w, ok := w.(registry.URLEntry)
|
||||
if !ok {
|
||||
op.Debugf("Skipping registry validation for %s", w)
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(w.URL().Host, "*") {
|
||||
op.Debugf("Skipping registry validation for %s", w)
|
||||
continue
|
||||
}
|
||||
|
||||
scheme := w.URL().Scheme
|
||||
if scheme == "" {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
if _, err = registry.Reachable(w.String(), scheme, "", "", pool, registryValidationTime, false); err != nil {
|
||||
op.Warnf("Unable to confirm secure registry %s is a valid registry at this time.", w)
|
||||
} else {
|
||||
op.Debugf("Secure registry %s confirmed.", w)
|
||||
}
|
||||
}
|
||||
|
||||
// Return output
|
||||
insecureRegistries = input.InsecureRegistries
|
||||
// If vic-machine had whitelist registry specified
|
||||
if len(input.WhitelistRegistries) > 0 {
|
||||
// ignoring error since default merge policy is union, so should never return
|
||||
// an error
|
||||
// #nosec: Errors unhandled.
|
||||
m, _ := secureRegistriesSet.Merge(insecureRegistriesSet, nil)
|
||||
whitelistRegistries = m.Strings()
|
||||
}
|
||||
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
|
||||
func (v *Validator) compatibility(op trace.Operation, conf *config.VirtualContainerHostConfigSpec) {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
// TODO: add checks such as datastore is acessible from target cluster
|
||||
errMsg := "Compatibility check SKIPPED"
|
||||
if !v.sessionValid(op, errMsg) {
|
||||
return
|
||||
}
|
||||
|
||||
// check session's datastore(s) exist
|
||||
_, err := v.Session.Datastore.AttachedClusterHosts(v.Context, v.Session.Cluster)
|
||||
v.NoteIssue(err)
|
||||
|
||||
v.checkDatastoresAreWriteable(op, conf)
|
||||
}
|
||||
|
||||
// looks up a datastore and adds it to the set
|
||||
func (v *Validator) getDatastore(op trace.Operation, u *url.URL, datastoreSet map[types.ManagedObjectReference]*object.Datastore) map[types.ManagedObjectReference]*object.Datastore {
|
||||
if datastoreSet == nil {
|
||||
datastoreSet = make(map[types.ManagedObjectReference]*object.Datastore)
|
||||
}
|
||||
|
||||
datastores, err := v.Session.Finder.DatastoreList(op, u.Host)
|
||||
v.NoteIssue(err)
|
||||
|
||||
if len(datastores) != 1 {
|
||||
v.NoteIssue(errors.Errorf("Looking up datastore %s returned %d results.", u.String(), len(datastores)))
|
||||
}
|
||||
for _, d := range datastores {
|
||||
datastoreSet[d.Reference()] = d
|
||||
}
|
||||
return datastoreSet
|
||||
}
|
||||
|
||||
// populates the v.datastoreSet "set" with datastore references generated from config
|
||||
func (v *Validator) getAllDatastores(op trace.Operation, conf *config.VirtualContainerHostConfigSpec) map[types.ManagedObjectReference]*object.Datastore {
|
||||
// note that ImageStores, ContainerStores, and VolumeLocations
|
||||
// have just-different-enough types/structures that this cannot be made more succinct
|
||||
var datastoreSet map[types.ManagedObjectReference]*object.Datastore
|
||||
for _, u := range conf.ImageStores {
|
||||
datastoreSet = v.getDatastore(op, &u, datastoreSet)
|
||||
}
|
||||
for _, u := range conf.ContainerStores {
|
||||
datastoreSet = v.getDatastore(op, &u, datastoreSet)
|
||||
}
|
||||
for _, u := range conf.VolumeLocations {
|
||||
//skip non datastore volume stores
|
||||
if u.Scheme != common.DsScheme {
|
||||
continue
|
||||
}
|
||||
datastoreSet = v.getDatastore(op, u, datastoreSet)
|
||||
}
|
||||
return datastoreSet
|
||||
}
|
||||
|
||||
func (v *Validator) checkDatastoresAreWriteable(op trace.Operation, conf *config.VirtualContainerHostConfigSpec) {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
// gather compute host references
|
||||
clusterDatastores, err := v.Session.Cluster.Datastores(op)
|
||||
v.NoteIssue(err)
|
||||
|
||||
// check that the cluster can see all of the datastores in question
|
||||
requestedDatastores := v.getAllDatastores(op, conf)
|
||||
validStores := make(map[types.ManagedObjectReference]*object.Datastore)
|
||||
// remove any found datastores from requested datastores
|
||||
for _, cds := range clusterDatastores {
|
||||
if requestedDatastores[cds.Reference()] != nil {
|
||||
delete(requestedDatastores, cds.Reference())
|
||||
validStores[cds.Reference()] = cds
|
||||
}
|
||||
}
|
||||
// if requestedDatastores is not empty, some requested datastores are not writable
|
||||
for _, store := range requestedDatastores {
|
||||
v.NoteIssue(errors.Errorf("Datastore %q is not accessible by the compute resource", store.Name()))
|
||||
}
|
||||
|
||||
clusterHosts, err := v.Session.Cluster.Hosts(op)
|
||||
justOneHost := len(clusterHosts) == 1
|
||||
v.NoteIssue(err)
|
||||
|
||||
for _, store := range validStores {
|
||||
aHosts, err := store.AttachedHosts(op)
|
||||
v.NoteIssue(err)
|
||||
clusterHosts = intersect(clusterHosts, aHosts)
|
||||
}
|
||||
|
||||
if len(clusterHosts) == 0 {
|
||||
v.NoteIssue(errors.New("No single host can access all of the requested datastores. Installation cannot continue."))
|
||||
}
|
||||
|
||||
if len(clusterHosts) == 1 && v.Session.IsVC() && !justOneHost {
|
||||
// if we have a cluster with >1 host to begin with, on VC, and only one host can talk to all the datastores, warn
|
||||
// on ESX and clusters with only one host to begin with, this warning would be redundant/irrelevant
|
||||
op.Warn("Only one host can access all of the image/container/volume datastores. This may be a point of contention/performance degradation and HA/DRS may not work as intended.")
|
||||
}
|
||||
}
|
||||
|
||||
// finds the intersection between two sets of HostSystem objects
|
||||
func intersect(one []*object.HostSystem, two []*object.HostSystem) []*object.HostSystem {
|
||||
var result []*object.HostSystem
|
||||
for _, o := range one {
|
||||
for _, t := range two {
|
||||
if o.Reference() == t.Reference() {
|
||||
result = append(result, o)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (v *Validator) IsVC() bool {
|
||||
return v.isVC
|
||||
}
|
||||
|
||||
func (v *Validator) AddDeprecatedFields(ctx context.Context, conf *config.VirtualContainerHostConfigSpec, input *data.Data) *data.InstallerData {
|
||||
op := trace.FromContext(ctx, "AddDeprecatedFields")
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
dconfig := data.InstallerData{}
|
||||
|
||||
cpuLimit := int64(input.NumCPUs)
|
||||
memLimit := int64(input.MemoryMB)
|
||||
dconfig.ApplianceSize.CPU.Limit = &cpuLimit
|
||||
dconfig.ApplianceSize.Memory.Limit = &memLimit
|
||||
|
||||
if v.Session.Datacenter != nil {
|
||||
dconfig.Datacenter = v.Session.Datacenter.Reference()
|
||||
dconfig.DatacenterName = v.Session.Datacenter.Name()
|
||||
} else {
|
||||
op.Debug("session datacenter is nil")
|
||||
}
|
||||
|
||||
if v.Session.Cluster != nil {
|
||||
dconfig.Cluster = v.Session.Cluster.Reference()
|
||||
dconfig.ClusterPath = v.Session.Cluster.InventoryPath
|
||||
} else {
|
||||
op.Debug("session cluster is nil")
|
||||
}
|
||||
|
||||
dconfig.ResourcePoolPath = v.ResourcePoolPath
|
||||
|
||||
op.Debugf("Datacenter: %q, Cluster: %q, Resource Pool: %q", dconfig.DatacenterName, dconfig.Cluster, dconfig.ResourcePoolPath)
|
||||
|
||||
if input.VCHCPUReservationsMHz != nil {
|
||||
cpuReserve := int64(*input.VCHCPUReservationsMHz)
|
||||
dconfig.VCHSize.CPU.Reservation = &cpuReserve
|
||||
}
|
||||
if input.VCHCPULimitsMHz != nil {
|
||||
cpuLimit := int64(*input.VCHCPULimitsMHz)
|
||||
dconfig.VCHSize.CPU.Limit = &cpuLimit
|
||||
}
|
||||
dconfig.VCHSize.CPU.Shares = input.VCHCPUShares
|
||||
|
||||
if input.VCHMemoryReservationsMB != nil {
|
||||
memReserve := int64(*input.VCHMemoryReservationsMB)
|
||||
dconfig.VCHSize.Memory.Reservation = &memReserve
|
||||
}
|
||||
if input.VCHMemoryLimitsMB != nil {
|
||||
memLimit := int64(*input.VCHMemoryLimitsMB)
|
||||
dconfig.VCHSize.Memory.Limit = &memLimit
|
||||
}
|
||||
dconfig.VCHSize.Memory.Shares = input.VCHMemoryShares
|
||||
|
||||
return &dconfig
|
||||
}
|
||||
|
||||
func (v *Validator) syslog(op trace.Operation, conf *config.VirtualContainerHostConfigSpec, input *data.Data) {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
if input.SyslogConfig.Addr == nil {
|
||||
return
|
||||
}
|
||||
|
||||
u := input.SyslogConfig.Addr
|
||||
network := u.Scheme
|
||||
if len(network) == 0 {
|
||||
v.NoteIssue(errors.New("syslog address does not have network specified"))
|
||||
return
|
||||
}
|
||||
|
||||
switch network {
|
||||
case "udp", "tcp":
|
||||
default:
|
||||
v.NoteIssue(fmt.Errorf("syslog address transport should be udp or tcp"))
|
||||
return
|
||||
}
|
||||
|
||||
host := u.Host
|
||||
if len(host) == 0 {
|
||||
v.NoteIssue(errors.New("syslog address host not specified"))
|
||||
return
|
||||
}
|
||||
|
||||
if u.Port() == "" {
|
||||
host += fmt.Sprintf(":%d", defaultSyslogPort)
|
||||
}
|
||||
|
||||
conf.Diagnostics.SysLogConfig = &executor.SysLogConfig{
|
||||
Network: network,
|
||||
RAddr: host,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Validator) kubelet(op trace.Operation, conf *config.VirtualContainerHostConfigSpec, input *data.Data) {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
if input.Kubelet.ServerAddress == nil || input.Kubelet.ConfigFile == nil {
|
||||
return
|
||||
}
|
||||
|
||||
conf.KubernetesServerAddress = *input.Kubelet.ServerAddress
|
||||
conf.KubeletConfigFile = *input.Kubelet.ConfigFile
|
||||
|
||||
err := kubelet.ReadKubeletConfigFile(op, conf)
|
||||
if err != nil {
|
||||
v.NoteIssue(fmt.Errorf("Failed to load K8s config file: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME ATC DEBT setting this value needs to be moved to Dispatcher
|
||||
// https://github.com/vmware/vic/issues/6803
|
||||
// set PersistNetworkBacking key to "true"
|
||||
func (v *Validator) ConfigureVCenter(ctx context.Context) error {
|
||||
op := trace.FromContext(ctx, "Set vCenter serial port backing")
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
errMsg := "Set vCenter settings SKIPPED"
|
||||
if !v.sessionValid(op, errMsg) {
|
||||
return nil
|
||||
}
|
||||
if !v.IsVC() {
|
||||
op.Debug(errMsg)
|
||||
return nil
|
||||
}
|
||||
|
||||
err := optmanager.UpdateOptionValue(ctx, v.Session, persistNetworkBackingKey, "true")
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Failed to set required value \"true\" for %s: %s", persistNetworkBackingKey, err)
|
||||
return errors.New(msg)
|
||||
}
|
||||
op.Infof("Set vCenter settings OK")
|
||||
return nil
|
||||
}
|
||||
1018
vendor/github.com/vmware/vic/lib/install/validate/validator_test.go
generated
vendored
Normal file
1018
vendor/github.com/vmware/vic/lib/install/validate/validator_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
223
vendor/github.com/vmware/vic/lib/install/validate/validator_test_sim_util.go
generated
vendored
Normal file
223
vendor/github.com/vmware/vic/lib/install/validate/validator_test_sim_util.go
generated
vendored
Normal file
@@ -0,0 +1,223 @@
|
||||
// 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 validate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/url"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/vmware/vic/cmd/vic-machine/common"
|
||||
"github.com/vmware/vic/lib/config"
|
||||
"github.com/vmware/vic/lib/config/executor"
|
||||
"github.com/vmware/vic/lib/install/data"
|
||||
"github.com/vmware/vic/pkg/ip"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
var (
|
||||
inputConfigAdminPassword = "Admin!23"
|
||||
inputConfigOpsUser = "ops-user@vsphere.local"
|
||||
inputConfigOpsPassword = "ops-user-password"
|
||||
)
|
||||
|
||||
var testInputConfigVPX = data.Data{
|
||||
Target: &common.Target{
|
||||
URL: &url.URL{
|
||||
Scheme: "http",
|
||||
Opaque: "",
|
||||
User: url.UserPassword("administrator@vsphere.local", "Admin!23"),
|
||||
Host: "", // This is set after the simulator starts
|
||||
Path: "/DC0",
|
||||
RawPath: "",
|
||||
ForceQuery: false,
|
||||
RawQuery: "",
|
||||
Fragment: "",
|
||||
},
|
||||
User: "administrator@vsphere.local",
|
||||
Password: &inputConfigAdminPassword,
|
||||
Thumbprint: "",
|
||||
},
|
||||
Debug: common.Debug{},
|
||||
Compute: common.Compute{ComputeResourcePath: "/DC0/host/DC0_C0/Resources/DC0_C0_RP1", DisplayName: "vch-test-1787"},
|
||||
VCHID: common.VCHID{},
|
||||
ContainerConfig: common.ContainerConfig{},
|
||||
OpsCredentials: common.OpsCredentials{
|
||||
OpsUser: &inputConfigOpsUser,
|
||||
OpsPassword: &inputConfigOpsPassword,
|
||||
IsSet: false,
|
||||
},
|
||||
CertPEM: nil,
|
||||
KeyPEM: nil,
|
||||
ClientCAs: nil,
|
||||
RegistryCAs: nil,
|
||||
Images: common.Images{ApplianceISO: "V1.2.0-RC1-100000-DD73850-appliance.iso",
|
||||
BootstrapISO: "V1.2.0-RC1-100000-DD73850-bootstrap.iso", OSType: "linux"},
|
||||
ImageDatastorePath: "LocalDS_0",
|
||||
VolumeLocations: map[string]*url.URL{
|
||||
"default": {
|
||||
Scheme: "ds",
|
||||
Opaque: "",
|
||||
User: (*url.Userinfo)(nil),
|
||||
Host: "",
|
||||
Path: "LocalDS_0/volumes",
|
||||
RawPath: "",
|
||||
ForceQuery: false,
|
||||
RawQuery: "",
|
||||
Fragment: "",
|
||||
},
|
||||
"local": {
|
||||
Scheme: "ds",
|
||||
Opaque: "",
|
||||
User: (*url.Userinfo)(nil),
|
||||
Host: "",
|
||||
Path: "LocalDS_0/volumes_local",
|
||||
RawPath: "",
|
||||
ForceQuery: false,
|
||||
RawQuery: "",
|
||||
Fragment: "",
|
||||
},
|
||||
"nfs": {
|
||||
Scheme: "nfs",
|
||||
Opaque: "",
|
||||
User: (*url.Userinfo)(nil),
|
||||
Host: "nfs-host",
|
||||
Path: "vic-volumes:nas",
|
||||
RawPath: "",
|
||||
ForceQuery: false,
|
||||
RawQuery: "",
|
||||
Fragment: "",
|
||||
},
|
||||
},
|
||||
BridgeNetworkName: "DC0_DVPG0",
|
||||
ClientNetwork: data.NetworkConfig{
|
||||
Name: "VM Network",
|
||||
Destinations: nil,
|
||||
Gateway: net.IPNet{},
|
||||
IP: net.IPNet{},
|
||||
},
|
||||
PublicNetwork: data.NetworkConfig{
|
||||
Name: "VM Network",
|
||||
Destinations: nil,
|
||||
Gateway: net.IPNet{},
|
||||
IP: net.IPNet{},
|
||||
},
|
||||
ManagementNetwork: data.NetworkConfig{
|
||||
Name: "VM Network",
|
||||
Destinations: nil,
|
||||
Gateway: net.IPNet{},
|
||||
IP: net.IPNet{},
|
||||
},
|
||||
DNS: nil,
|
||||
ContainerNetworks: common.ContainerNetworks{
|
||||
MappedNetworks: map[string]string{},
|
||||
MappedNetworksGateways: map[string]net.IPNet{},
|
||||
MappedNetworksIPRanges: map[string][]ip.Range{},
|
||||
MappedNetworksDNS: map[string][]net.IP{},
|
||||
MappedNetworksFirewalls: map[string]executor.TrustLevel{},
|
||||
},
|
||||
ResourceLimits: common.ResourceLimits{},
|
||||
BridgeIPRange: &net.IPNet{
|
||||
IP: []byte{0xac, 0x10, 0x0, 0x0},
|
||||
Mask: []byte{0xff, 0xf0, 0x0, 0x0},
|
||||
},
|
||||
InsecureRegistries: nil,
|
||||
WhitelistRegistries: nil,
|
||||
HTTPSProxy: (*url.URL)(nil),
|
||||
HTTPProxy: (*url.URL)(nil),
|
||||
ProxyIsSet: false,
|
||||
NumCPUs: 1,
|
||||
MemoryMB: 2048,
|
||||
Timeout: 180000000000,
|
||||
Force: true,
|
||||
ResetInProgressFlag: false,
|
||||
AsymmetricRouting: false,
|
||||
ScratchSize: "8GB",
|
||||
Rollback: false,
|
||||
SyslogConfig: data.SyslogConfig{},
|
||||
}
|
||||
|
||||
func GetVcsimInputConfig(ctx context.Context, URL *url.URL) *data.Data {
|
||||
localInputConfig := testInputConfigVPX
|
||||
// Fix the URL to point to vcsim
|
||||
if URL != nil {
|
||||
// Update the Host from the URL
|
||||
localInputConfig.Target.URL.Host = URL.Host
|
||||
}
|
||||
// Copy the URL pointer
|
||||
localInputConfig.Target = common.NewTarget()
|
||||
*localInputConfig.Target = *testInputConfigVPX.Target
|
||||
localInputConfig.Target.URL = new(url.URL)
|
||||
*localInputConfig.Target.URL = *testInputConfigVPX.Target.URL
|
||||
return &localInputConfig
|
||||
}
|
||||
|
||||
// This method allows to perform validation of a configuration when
|
||||
// interacting with GO vmomi simulator, it skips some of the tests
|
||||
// that otherwise would fail (e.g. Firewall)
|
||||
func (v *Validator) VcsimValidate(ctx context.Context, localInputConfig *data.Data) (*config.VirtualContainerHostConfigSpec, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
op := trace.FromContext(ctx, "validateForSim")
|
||||
log.Infof("Validating supplied configuration")
|
||||
|
||||
conf := &config.VirtualContainerHostConfigSpec{}
|
||||
|
||||
if err := v.datacenter(op); err != nil {
|
||||
return conf, err
|
||||
}
|
||||
|
||||
v.basics(op, localInputConfig, conf)
|
||||
|
||||
v.target(op, localInputConfig, conf)
|
||||
v.credentials(op, localInputConfig, conf)
|
||||
v.compute(op, localInputConfig, conf)
|
||||
v.storage(op, localInputConfig, conf)
|
||||
v.network(op, localInputConfig, conf)
|
||||
v.CheckLicense(op)
|
||||
v.CheckDrs(op)
|
||||
|
||||
// fmt.Printf("Config: %# v\n", pretty.Formatter(conf))
|
||||
|
||||
// Perform the higher level compatibility and consistency checks
|
||||
v.compatibility(op, conf)
|
||||
|
||||
v.syslog(op, conf, localInputConfig)
|
||||
|
||||
pool, err := v.ResourcePoolHelper(op, localInputConfig.ComputeResourcePath)
|
||||
v.NoteIssue(err)
|
||||
|
||||
if pool == nil {
|
||||
return conf, v.ListIssues(op)
|
||||
}
|
||||
|
||||
// Add the resource pool
|
||||
conf.ComputeResources = append(conf.ComputeResources, pool.Reference())
|
||||
|
||||
// Add the VM
|
||||
vm, err := v.Session.Finder.VirtualMachine(op, "/DC0/vm/DC0_C0_RP0_VM0")
|
||||
v.NoteIssue(err)
|
||||
|
||||
if vm == nil {
|
||||
return conf, v.ListIssues(op)
|
||||
}
|
||||
|
||||
vmRef := vm.Reference()
|
||||
conf.SetMoref(&vmRef)
|
||||
|
||||
// TODO: determine if this is where we should turn the noted issues into message
|
||||
return conf, v.ListIssues(op)
|
||||
}
|
||||
82
vendor/github.com/vmware/vic/lib/install/vchlog/buffered_pipe.go
generated
vendored
Normal file
82
vendor/github.com/vmware/vic/lib/install/vchlog/buffered_pipe.go
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
// 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 vchlog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// BufferedPipe struct implements a pipe readwriter with buffer
|
||||
type BufferedPipe struct {
|
||||
// buffer: the internal buffer to hold data
|
||||
buffer *bytes.Buffer
|
||||
// c: the sync locker to manage concurrent reads and writes
|
||||
c *sync.Cond
|
||||
// writeClosed: boolean indicating if write end of the pipe is closed
|
||||
writeClosed bool
|
||||
}
|
||||
|
||||
// NewBufferedPipe returns a new buffered pipe instance
|
||||
// the internal buffer is initialized to default size.
|
||||
// Since internal memory is used, need to make sure that buffered data is bounded.
|
||||
func NewBufferedPipe() *BufferedPipe {
|
||||
var m sync.Mutex
|
||||
c := sync.NewCond(&m)
|
||||
return &BufferedPipe{
|
||||
buffer: bytes.NewBuffer(nil),
|
||||
c: c,
|
||||
writeClosed: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Read is blocked until a writer in the queue is done writing (until data is available)
|
||||
func (bp *BufferedPipe) Read(data []byte) (n int, err error) {
|
||||
bp.c.L.Lock()
|
||||
defer bp.c.L.Unlock()
|
||||
defer bp.c.Broadcast()
|
||||
|
||||
for bp.buffer.Len() == 0 && !bp.writeClosed {
|
||||
bp.c.Wait()
|
||||
}
|
||||
if bp.buffer.Len() == 0 && bp.writeClosed {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
return bp.buffer.Read(data)
|
||||
}
|
||||
|
||||
// Write writes to the internal buffer, and signals one of the reader in queue to start reading.
|
||||
func (bp *BufferedPipe) Write(data []byte) (n int, err error) {
|
||||
bp.c.L.Lock()
|
||||
defer bp.c.L.Unlock()
|
||||
defer bp.c.Broadcast()
|
||||
|
||||
if bp.writeClosed {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
return bp.buffer.Write(data)
|
||||
}
|
||||
|
||||
// Close closes the pipe
|
||||
func (bp *BufferedPipe) Close() (err error) {
|
||||
bp.c.L.Lock()
|
||||
defer bp.c.L.Unlock()
|
||||
defer bp.c.Broadcast()
|
||||
bp.writeClosed = true
|
||||
return nil
|
||||
}
|
||||
230
vendor/github.com/vmware/vic/lib/install/vchlog/buffered_pipe_test.go
generated
vendored
Normal file
230
vendor/github.com/vmware/vic/lib/install/vchlog/buffered_pipe_test.go
generated
vendored
Normal file
@@ -0,0 +1,230 @@
|
||||
// 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 vchlog
|
||||
|
||||
import (
|
||||
. "io"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Part 1. Test basic pipe functionality
|
||||
// (Referred to: https://golang.org/src/io/pipe_test.go)
|
||||
|
||||
// Test a single read and write
|
||||
func TestSingleReadWrite(t *testing.T) {
|
||||
testByte := []byte("hello world")
|
||||
toRead := make([]byte, 64)
|
||||
bp := NewBufferedPipe()
|
||||
writeDone := make(chan int)
|
||||
|
||||
// write to the pipe
|
||||
go write(t, bp, testByte, len(testByte), writeDone)
|
||||
|
||||
// read from the pipe
|
||||
read(t, bp, toRead, len(testByte), nil)
|
||||
assert.Equal(t, string(testByte), string(toRead[0:len(testByte)]),
|
||||
"expected %s, read %s", string(testByte), string(toRead[0:len(testByte)]))
|
||||
|
||||
<-writeDone
|
||||
bp.Close()
|
||||
}
|
||||
|
||||
// Test a sequence of reads and writes
|
||||
func readSequence(t *testing.T, r Reader, c chan int) {
|
||||
buf := make([]byte, 64)
|
||||
for {
|
||||
n, err := r.Read(buf)
|
||||
if err == EOF {
|
||||
c <- 0
|
||||
break
|
||||
}
|
||||
assert.Nil(t, err, "read error: %v", err)
|
||||
c <- n
|
||||
}
|
||||
}
|
||||
|
||||
func TestSequenceReadsWrites(t *testing.T) {
|
||||
readDone := make(chan int)
|
||||
bp := NewBufferedPipe()
|
||||
buf := make([]byte, 64)
|
||||
|
||||
// fire the reader
|
||||
go readSequence(t, bp, readDone)
|
||||
|
||||
// write to the pipe
|
||||
for i := 0; i < 5; i++ {
|
||||
l := 5 + i*10
|
||||
toWrite := buf[0:l]
|
||||
write(t, bp, toWrite, l, nil)
|
||||
|
||||
n := <-readDone
|
||||
assert.Equal(t, l, n, "wrote %d bytes, read %d bytes", l, n)
|
||||
}
|
||||
|
||||
bp.Close()
|
||||
n := <-readDone
|
||||
assert.Equal(t, 0, n, "final read should be 0, got %d", n)
|
||||
}
|
||||
|
||||
// Part 2. Test buffered pipe functionalities
|
||||
|
||||
// Test write buffering
|
||||
func TestWriteBuffering(t *testing.T) {
|
||||
bp := NewBufferedPipe()
|
||||
|
||||
// write first chunk of data
|
||||
firstChunk := make([]byte, 16)
|
||||
write(t, bp, firstChunk, len(firstChunk), nil)
|
||||
|
||||
// fire the reader
|
||||
readDone := make(chan int)
|
||||
go func() {
|
||||
buf := make([]byte, 64)
|
||||
for i := 0; i < 2; i++ {
|
||||
l := 8 * (i + 2)
|
||||
read(t, bp, buf, l, nil)
|
||||
}
|
||||
readDone <- 0
|
||||
}()
|
||||
|
||||
// sleep before the next write
|
||||
time.Sleep(time.Second * 5)
|
||||
|
||||
// write second chunk of data
|
||||
secondChunk := make([]byte, 24)
|
||||
write(t, bp, secondChunk, len(secondChunk), nil)
|
||||
|
||||
<-readDone
|
||||
bp.Close()
|
||||
}
|
||||
|
||||
// Test concurrent writers
|
||||
func writeSequence(t *testing.T, w Writer, l int, c chan int) {
|
||||
for i := 0; i < l; i++ {
|
||||
write(t, w, []byte(strconv.Itoa(i)), 1, nil)
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
c <- 0
|
||||
}
|
||||
|
||||
func TestConcurrentWriter(t *testing.T) {
|
||||
bp := NewBufferedPipe()
|
||||
l := 10
|
||||
writersNum := 5
|
||||
total := writersNum * l
|
||||
|
||||
// fire 5 concurrent writers
|
||||
chans := make([]chan int, writersNum)
|
||||
for i := 0; i < writersNum; i++ {
|
||||
chans[i] = make(chan int)
|
||||
go writeSequence(t, bp, l, chans[i])
|
||||
}
|
||||
|
||||
// fire one concurrent reader
|
||||
finalLen := 0
|
||||
readDone := make(chan int)
|
||||
go func() {
|
||||
for {
|
||||
buf := make([]byte, 16)
|
||||
n, err := bp.Read(buf)
|
||||
if err == EOF {
|
||||
break
|
||||
}
|
||||
assert.Nil(t, err, "read error: %v", err)
|
||||
finalLen += n
|
||||
}
|
||||
readDone <- 0
|
||||
}()
|
||||
|
||||
// wait for writers to finish
|
||||
for i := 0; i < writersNum; i++ {
|
||||
<-chans[i]
|
||||
}
|
||||
bp.Close()
|
||||
|
||||
// check if the output has all the bytes
|
||||
<-readDone
|
||||
assert.Equal(t, total, finalLen,
|
||||
"%d concurrent writers wrote %d bytes total, got %d bytes", writersNum, total, finalLen)
|
||||
}
|
||||
|
||||
// Part 3. Edge cases
|
||||
|
||||
// Test read on closed pipe during read
|
||||
func delayClose(t *testing.T, cl Closer, c chan int) {
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
err := cl.Close()
|
||||
assert.Nil(t, err, "close error: %v", err)
|
||||
c <- 0
|
||||
}
|
||||
|
||||
func TestPipeReadClose(t *testing.T) {
|
||||
bp := NewBufferedPipe()
|
||||
c := make(chan int)
|
||||
|
||||
// delay closer
|
||||
go delayClose(t, bp, c)
|
||||
|
||||
// read is expected to block until the pipe is closed
|
||||
buf := make([]byte, 64)
|
||||
n, err := bp.Read(buf)
|
||||
<-c
|
||||
|
||||
assert.Equal(t, EOF, err, "read from closed pipe: %v want %v", err, EOF)
|
||||
assert.Equal(t, 0, n, "read on closed pipe returned %d bytes", n)
|
||||
}
|
||||
|
||||
// Test write on closed pipe during write
|
||||
func TestPipeWriteClose(t *testing.T) {
|
||||
bp := NewBufferedPipe()
|
||||
|
||||
// close pipe
|
||||
bp.Close()
|
||||
|
||||
// write
|
||||
buf := make([]byte, 64)
|
||||
n, err := bp.Write(buf)
|
||||
|
||||
assert.Equal(t, ErrUnexpectedEOF, err, "write to closed pipe: %v want %v", err, ErrUnexpectedEOF)
|
||||
assert.Equal(t, 0, n, "write on closed pipe returned %d bytes", n)
|
||||
}
|
||||
|
||||
// Helper Functions
|
||||
|
||||
// write writes the data and report any error
|
||||
func write(t *testing.T, w Writer, data []byte, expected int, c chan int) {
|
||||
n, err := w.Write(data)
|
||||
assert.Nil(t, err, "write error: %v", err)
|
||||
assert.Equal(t, expected, n, "expected %d bytes, wrote %d", expected, n)
|
||||
|
||||
if c != nil {
|
||||
c <- 0
|
||||
}
|
||||
}
|
||||
|
||||
// read reads to buffer and report any error
|
||||
func read(t *testing.T, r Reader, buf []byte, expected int, c chan int) {
|
||||
n, err := r.Read(buf)
|
||||
assert.Nil(t, err, "read error: %v", err)
|
||||
assert.Equal(t, expected, n, "expected %d bytes, read %d", expected, n)
|
||||
|
||||
if c != nil {
|
||||
c <- 0
|
||||
}
|
||||
}
|
||||
103
vendor/github.com/vmware/vic/lib/install/vchlog/vchlogger.go
generated
vendored
Normal file
103
vendor/github.com/vmware/vic/lib/install/vchlog/vchlogger.go
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
// 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 vchlog
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
// Reminder: When changing these, consider the impact to backwards compatibility.
|
||||
const (
|
||||
LogFilePrefix = "vic-machine" // logFilePrefix is the prefix for file names of all vic-machine log files
|
||||
LogFileSuffix = ".log" // logFileSuffix is the suffix for file names of all vic-machine log files
|
||||
)
|
||||
|
||||
// DatastoreReadySignal serves as a signal struct indicating datastore folder path is available
|
||||
type DatastoreReadySignal struct {
|
||||
// Datastore: the govmomi datastore object
|
||||
Datastore *object.Datastore
|
||||
// Name: vic-machine process name (e.g. "create", "inspect")
|
||||
Name string
|
||||
// Operation: the operation from which the signal is sent
|
||||
Operation trace.Operation
|
||||
// VMPathName: the datastore path
|
||||
VMPathName string
|
||||
// Timestamp: timestamp at which the signal is sent
|
||||
Timestamp time.Time
|
||||
}
|
||||
|
||||
type VCHLogger struct {
|
||||
// pipe: the streaming readwriter pipe to hold log messages
|
||||
pipe *BufferedPipe
|
||||
|
||||
// signalChan: channel for signaling when datastore folder is ready
|
||||
signalChan chan DatastoreReadySignal
|
||||
|
||||
// done: channel indicating if streaming to datastore is finished
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
type Receiver interface {
|
||||
Signal(sig DatastoreReadySignal)
|
||||
}
|
||||
|
||||
// New creates the logger, with the streaming pipe and singaling channel.
|
||||
func New() *VCHLogger {
|
||||
return &VCHLogger{
|
||||
pipe: NewBufferedPipe(),
|
||||
signalChan: make(chan DatastoreReadySignal),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Run waits until the signal arrives and uploads the streaming pipe to datastore
|
||||
func (l *VCHLogger) Run() {
|
||||
sig := <-l.signalChan
|
||||
// suffix the log file name with caller operation ID and timestamp
|
||||
logFileName := LogFilePrefix + "_" + sig.Timestamp.UTC().Format(time.RFC3339) + "_" + sig.Name + "_" + sig.Operation.ID() + LogFileSuffix
|
||||
param := soap.DefaultUpload
|
||||
param.ContentLength = -1
|
||||
sig.Datastore.Upload(sig.Operation.Context, ioutil.NopCloser(l.pipe), path.Join(sig.VMPathName, logFileName), ¶m)
|
||||
close(l.done)
|
||||
}
|
||||
|
||||
// Wait waits for the streaming to VCH datastore to finish, or context times out
|
||||
func (l *VCHLogger) Wait(op trace.Operation) {
|
||||
select {
|
||||
case <-l.done: // done uploading to datastore (possibly error out)
|
||||
case <-op.Done(): // context cancel, timeout
|
||||
}
|
||||
}
|
||||
|
||||
// GetPipe returns the streaming pipe of the vch logger
|
||||
func (l *VCHLogger) GetPipe() *BufferedPipe {
|
||||
return l.pipe
|
||||
}
|
||||
|
||||
// Signal signals the logger that the datastore folder is ready
|
||||
func (l *VCHLogger) Signal(sig DatastoreReadySignal) {
|
||||
l.signalChan <- sig
|
||||
}
|
||||
|
||||
// Close stops the logger by closing the underlying pipe
|
||||
func (l *VCHLogger) Close() error {
|
||||
return l.pipe.Close()
|
||||
}
|
||||
Reference in New Issue
Block a user