Files
virtual-kubelet/vendor/github.com/vmware/vic/lib/install/validate/config.go
Loc Nguyen 513cebe7b7 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
2018-06-04 15:41:32 -07:00

599 lines
18 KiB
Go

// 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
}