Skip to content

Commit

Permalink
New IP address discovery logic
Browse files Browse the repository at this point in the history
  • Loading branch information
zuzzas committed Oct 30, 2019
1 parent 6d76e17 commit 33814d0
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 19 deletions.
14 changes: 10 additions & 4 deletions pkg/cloudprovider/vsphere/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,18 @@ func init() {
if err != nil {
return nil, err
}
return newVSphere(cfg, true)

cpiConfig, err := ReadCPIConfig(config)
if err != nil {
return nil, err
}
return newVSphere(cfg, cpiConfig, true)
})
}

// Creates new Controller node interface and returns
func newVSphere(cfg *vcfg.Config, finalize ...bool) (*VSphere, error) {
vs, err := buildVSphereFromConfig(cfg)
func newVSphere(cfg *vcfg.Config, cpiCfg *CPIConfig, finalize ...bool) (*VSphere, error) {
vs, err := buildVSphereFromConfig(cfg, cpiCfg)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -144,7 +149,7 @@ func (vs *VSphere) HasClusterID() bool {
}

// Initializes vSphere from vSphere CloudProvider Configuration
func buildVSphereFromConfig(cfg *vcfg.Config) (*VSphere, error) {
func buildVSphereFromConfig(cfg *vcfg.Config, cpiCfg *CPIConfig) (*VSphere, error) {
nm := &NodeManager{
nodeNameMap: make(map[string]*NodeInfo),
nodeUUIDMap: make(map[string]*NodeInfo),
Expand All @@ -154,6 +159,7 @@ func buildVSphereFromConfig(cfg *vcfg.Config) (*VSphere, error) {

vs := VSphere{
cfg: cfg,
cpiCfg: cpiCfg,
nodeManager: nm,
instances: newInstances(nm),
zones: newZones(nm, cfg.Labels.Zone, cfg.Labels.Region),
Expand Down
86 changes: 86 additions & 0 deletions pkg/cloudprovider/vsphere/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
Copyright 2018 The Kubernetes Authors.
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 vsphere

import (
"fmt"
"io"
"os"
"strings"

"gopkg.in/gcfg.v1"
)

// CPIConfig is used to read and store information (related only to the CPI) from the cloud configuration file
type CPIConfig struct {
CPI struct {
// address options for each network type (internal and/or external) are mutually inclusive
// e.g., if you specify "internal-network-name" and "internal-network-subnet-cidr"
// only the addresses that match BOTH will match

// VirtualMachine network interfaces backed by the following vSphere Networks
// will be used in respective status.addresses fields.
InternalNetworkName []string `gcfg:"internal-network-name"`
ExternalNetworkName []string `gcfg:"external-network-name"`

// IP addresses on VirtualMachine's network interfaces included in the fields' CIDRs
// will be used in respective status.addresses fields.
InternalNetworkSubnetCIDR []string `gcfg:"internal-network-subnet-cidr"`
ExternalNetworkSubnetCIDR []string `gcfg:"external-network-subnet-cidr"`
}
}

// FromEnv initializes the provided configuratoin object with values
// obtained from environment variables. If an environment variable is set
// for a property that's already initialized, the environment variable's value
// takes precedence.
func (cfg *CPIConfig) FromEnv() {
if v := os.Getenv("VSPHERE_INTERNAL_NETWORK_NAME"); v != "" {
cfg.CPI.InternalNetworkName = strings.Split(v, ",")
}

if v := os.Getenv("VSPHERE_EXTERNAL_NETWORK_NAME"); v != "" {
cfg.CPI.ExternalNetworkName = strings.Split(v, ",")
}

if v := os.Getenv("VSPHERE_INTERNAL_NETWORK_SUBNET_CIDR"); v != "" {
cfg.CPI.InternalNetworkSubnetCIDR = strings.Split(v, ",")
}

if v := os.Getenv("VSPHERE_EXTERNAL_NETWORK_SUBNET_CIDR"); v != "" {
cfg.CPI.ExternalNetworkSubnetCIDR = strings.Split(v, ",")
}
}

// ReadCPIConfig parses vSphere cloud config file and stores it into CPIConfig.
// Environment variables are also checked
func ReadCPIConfig(config io.Reader) (*CPIConfig, error) {
if config == nil {
return nil, fmt.Errorf("no vSphere cloud provider config file given")
}

cfg := &CPIConfig{}

if err := gcfg.FatalOnly(gcfg.ReadInto(cfg, config)); err != nil {
return nil, err
}

// Env Vars should override config file entries if present
cfg.FromEnv()

return cfg, nil
}
115 changes: 104 additions & 11 deletions pkg/cloudprovider/vsphere/nodemanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,22 @@ func returnIPsFromSpecificFamily(family string, ips []string) []string {
return matching
}

func isMatchingSubnet(address net.IP, subnets []*net.IPNet) (matched bool) {
for _, subnet := range subnets {
if subnet.Contains(address) {
matched = true
break
}
}

return
}

func isMatchingName(networkName string, networkNameMap map[string]struct{}) (matched bool) {
_, matched = networkNameMap[networkName]
return
}

// DiscoverNode finds a node's VM using the specified search value and search
// type.
func (nm *NodeManager) DiscoverNode(nodeID string, searchBy cm.FindVM) error {
Expand Down Expand Up @@ -190,6 +206,37 @@ func (nm *NodeManager) DiscoverNode(nodeID string, searchBy cm.FindVM) error {
klog.Warningf("Unable to find vcInstance for %s. Defaulting to ipv4.", tenantRef)
}

internalNetworkNameMap := make(map[string]struct{})
externalNetworkNameMap := make(map[string]struct{})
for _, intName := range nm.cpiCfg.CPI.InternalNetworkName {
internalNetworkNameMap[intName] = struct{}{}
}
for _, extName := range nm.cpiCfg.CPI.ExternalNetworkName {
externalNetworkNameMap[extName] = struct{}{}
}

var internalNetworkSubnets []*net.IPNet
for _, cidrStr := range nm.cpiCfg.CPI.InternalNetworkSubnetCIDR {
_, subnet, err := net.ParseCIDR(cidrStr)
if err != nil {
return err
}
internalNetworkSubnets = append(internalNetworkSubnets, subnet)
}
var externalNetworkSubnets []*net.IPNet
for _, cidrStr := range nm.cpiCfg.CPI.ExternalNetworkSubnetCIDR {
_, subnet, err := net.ParseCIDR(cidrStr)
if err != nil {
return err
}
externalNetworkSubnets = append(externalNetworkSubnets, subnet)
}

var addressMatchingEnabled bool
if len(internalNetworkNameMap) > 0 || len(externalNetworkNameMap) > 0 || len(internalNetworkSubnets) > 0 || len(externalNetworkSubnets) > 0 {
addressMatchingEnabled = true
}

found := false
addrs := []v1.NodeAddress{}
for _, v := range oVM.Guest.Net {
Expand All @@ -198,29 +245,75 @@ func (nm *NodeManager) DiscoverNode(nodeID string, searchBy cm.FindVM) error {
continue
}

if (len(internalNetworkNameMap) > 0) && (len(externalNetworkNameMap) > 0) && v.Network == "" {
klog.Warningln("Skipping device because network name-based IP address detection is enabled and the \"Network\" field is not set on vNIC")
continue
}

// Only return a single IP address based on the preference of IPFamily
// Must break out of loop in the event of ipv6,ipv4 where the NIC does
// contain a valid IPv6 and IPV4 address
for _, family := range ipFamily {
ips := returnIPsFromSpecificFamily(family, v.IpAddress)

for _, ip := range ips {
klog.V(2).Infof("Adding IP: %s", ip)
if addressMatchingEnabled {
klog.V(2).Infof("Adding Hostname")
v1helper.AddToNodeAddresses(&addrs,
v1.NodeAddress{
Type: v1.NodeExternalIP,
Address: ip,
}, v1.NodeAddress{
Type: v1.NodeInternalIP,
Address: ip,
}, v1.NodeAddress{
Type: v1.NodeHostName,
Address: oVM.Guest.HostName,
},
)

found = true
break
for _, ip := range ips {
parsedIP := net.ParseIP(ip)
if parsedIP == nil {
return err
}

internalNameMatched := isMatchingName(v.Network, internalNetworkNameMap)
internalSubnetMatched := isMatchingSubnet(parsedIP, externalNetworkSubnets)
if internalNameMatched || internalSubnetMatched {
klog.V(2).Infof("Adding Internal IP: %s", ip)
v1helper.AddToNodeAddresses(&addrs,
v1.NodeAddress{
Type: v1.NodeInternalIP,
Address: ip,
},
)
found = true
}

externalNameMatched := isMatchingName(v.Network, externalNetworkNameMap)
externalSubnetMatched := isMatchingSubnet(parsedIP, externalNetworkSubnets)
if externalNameMatched || externalSubnetMatched {
klog.V(2).Infof("Adding External IP: %s", ip)
v1helper.AddToNodeAddresses(&addrs,
v1.NodeAddress{
Type: v1.NodeExternalIP,
Address: ip,
},
)
found = true
}
}
} else {
for _, ip := range ips {
klog.V(2).Infof("Adding IP: %s", ip)
v1helper.AddToNodeAddresses(&addrs,
v1.NodeAddress{
Type: v1.NodeExternalIP,
Address: ip,
}, v1.NodeAddress{
Type: v1.NodeInternalIP,
Address: ip,
}, v1.NodeAddress{
Type: v1.NodeHostName,
Address: oVM.Guest.HostName,
},
)
found = true
break
}
}

if found {
Expand Down
4 changes: 4 additions & 0 deletions pkg/cloudprovider/vsphere/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type GRPCServer interface {
// VSphere is an implementation of cloud provider Interface for VSphere.
type VSphere struct {
cfg *vcfg.Config
cpiCfg *CPIConfig
connectionManager *cm.ConnectionManager
nodeManager *NodeManager
informMgr *k8s.InformerManager
Expand Down Expand Up @@ -84,6 +85,9 @@ type NodeManager struct {
// NodeLister to track Node properties
nodeLister clientv1.NodeLister

// Reference to CPI-specific configuration
cpiCfg *CPIConfig

// Mutexes
nodeInfoLock sync.RWMutex
nodeRegInfoLock sync.RWMutex
Expand Down
11 changes: 7 additions & 4 deletions pkg/cloudprovider/vsphere/vsphere_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,12 @@ func configFromEnvOrSim(multiDc bool) (*vcfg.Config, func()) {

func TestNewVSphere(t *testing.T) {
cfg := &vcfg.Config{}
cpiCfg := &CPIConfig{}
if err := cfg.FromEnv(); err != nil {
t.Skipf("No config found in environment")
}

_, err := newVSphere(cfg)
_, err := newVSphere(cfg, cpiCfg)
if err != nil {
t.Fatalf("Failed to construct/authenticate vSphere: %s", err)
}
Expand All @@ -155,9 +156,10 @@ func TestNewVSphere(t *testing.T) {
func TestVSphereLogin(t *testing.T) {
cfg, cleanup := configFromEnvOrSim(false)
defer cleanup()
cpiCfg := &CPIConfig{}

// Create vSphere configuration object
vs, err := newVSphere(cfg)
vs, err := newVSphere(cfg, cpiCfg)
if err != nil {
t.Fatalf("Failed to construct/authenticate vSphere: %s", err)
}
Expand All @@ -184,13 +186,14 @@ func TestVSphereLogin(t *testing.T) {
func TestVSphereLoginByToken(t *testing.T) {
cfg, cleanup := configFromSim(false)
defer cleanup()
cpiCfg := &CPIConfig{}

// Configure for SAML token auth
cfg.Global.User = localhostCert
cfg.Global.Password = localhostKey

// Create vSphere configuration object
vs, err := newVSphere(cfg)
vs, err := newVSphere(cfg, cpiCfg)
if err != nil {
t.Fatalf("Failed to construct/authenticate vSphere: %s", err)
}
Expand Down Expand Up @@ -490,7 +493,7 @@ func TestSecretVSphereConfig(t *testing.T) {
t.Fatalf("readConfig: unexpected error returned: %v", err)
}
}
vs, err = buildVSphereFromConfig(cfg)
vs, err = buildVSphereFromConfig(cfg, &CPIConfig{})
if err != nil { // testcase.expectedError {
t.Fatalf("buildVSphereFromConfig: Should succeed when a valid config is provided: %v", err)
}
Expand Down

0 comments on commit 33814d0

Please sign in to comment.