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:
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)
|
||||
}
|
||||
Reference in New Issue
Block a user