Skip to content

Commit 9b8de65

Browse files
authored
Refactor the openstack provider code (kubernetes#1586)
This PR refactors/moves the provider code for better readability.
1 parent 61bdbe5 commit 9b8de65

File tree

5 files changed

+341
-351
lines changed

5 files changed

+341
-351
lines changed

pkg/openstack/instances.go

Lines changed: 270 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,27 @@ package openstack
1919
import (
2020
"context"
2121
"fmt"
22+
"io/ioutil"
23+
"net"
2224
"regexp"
25+
"sort"
2326
"strings"
2427

2528
"github.com/gophercloud/gophercloud"
29+
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces"
2630
"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
2731
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
32+
"github.com/gophercloud/gophercloud/pagination"
33+
"github.com/mitchellh/mapstructure"
34+
v1 "k8s.io/api/core/v1"
2835
"k8s.io/klog/v2"
2936

30-
"k8s.io/api/core/v1"
3137
"k8s.io/apimachinery/pkg/types"
3238
"k8s.io/apimachinery/pkg/util/validation"
3339
cloudprovider "k8s.io/cloud-provider"
3440
"k8s.io/cloud-provider-openstack/pkg/client"
3541
"k8s.io/cloud-provider-openstack/pkg/metrics"
42+
"k8s.io/cloud-provider-openstack/pkg/util"
3643
"k8s.io/cloud-provider-openstack/pkg/util/errors"
3744
"k8s.io/cloud-provider-openstack/pkg/util/metadata"
3845
)
@@ -48,6 +55,8 @@ const (
4855
instanceShutoff = "SHUTOFF"
4956
)
5057

58+
var _ cloudprovider.Instances = &Instances{}
59+
5160
// Instances returns an implementation of Instances for OpenStack.
5261
func (os *OpenStack) Instances() (cloudprovider.Instances, bool) {
5362
return os.instances()
@@ -75,6 +84,18 @@ func (os *OpenStack) instances() (*Instances, bool) {
7584
}, true
7685
}
7786

87+
// InstanceID returns the kubelet's cloud provider ID.
88+
func (os *OpenStack) InstanceID() (string, error) {
89+
if len(os.localInstanceID) == 0 {
90+
id, err := readInstanceID(os.metadataOpts.SearchOrder)
91+
if err != nil {
92+
return "", err
93+
}
94+
os.localInstanceID = id
95+
}
96+
return os.localInstanceID, nil
97+
}
98+
7899
// CurrentNodeName implements Instances.CurrentNodeName
79100
// Note this is *not* necessarily the same as hostname.
80101
func (i *Instances) CurrentNodeName(ctx context.Context, hostname string) (types.NodeName, error) {
@@ -221,23 +242,11 @@ func (i *Instances) InstanceMetadata(ctx context.Context, node *v1.Node) (*cloud
221242
}, nil
222243
}
223244

224-
// InstanceID returns the kubelet's cloud provider ID.
225-
func (os *OpenStack) InstanceID() (string, error) {
226-
if len(os.localInstanceID) == 0 {
227-
id, err := readInstanceID(os.metadataOpts.SearchOrder)
228-
if err != nil {
229-
return "", err
230-
}
231-
os.localInstanceID = id
232-
}
233-
return os.localInstanceID, nil
234-
}
235-
236245
// InstanceID returns the cloud provider ID of the specified instance.
237246
func (i *Instances) InstanceID(ctx context.Context, name types.NodeName) (string, error) {
238247
srv, err := getServerByName(i.compute, name)
239248
if err != nil {
240-
if err == ErrNotFound {
249+
if err == errors.ErrNotFound {
241250
return "", cloudprovider.InstanceNotFound
242251
}
243252
return "", err
@@ -371,3 +380,250 @@ func RemoveFromNodeAddresses(addresses *[]v1.NodeAddress, removeAddresses ...v1.
371380
}
372381
}
373382
}
383+
384+
// mapNodeNameToServerName maps a k8s NodeName to an OpenStack Server Name
385+
// This is a simple string cast.
386+
func mapNodeNameToServerName(nodeName types.NodeName) string {
387+
return string(nodeName)
388+
}
389+
390+
// mapServerToNodeName maps an OpenStack Server to a k8s NodeName
391+
func mapServerToNodeName(server *servers.Server) types.NodeName {
392+
// Node names are always lowercase, and (at least)
393+
// routecontroller does case-sensitive string comparisons
394+
// assuming this
395+
return types.NodeName(strings.ToLower(server.Name))
396+
}
397+
398+
func readInstanceID(searchOrder string) (string, error) {
399+
// First, try to get data from metadata service because local
400+
// data might be changed by accident
401+
md, err := metadata.Get(searchOrder)
402+
if err == nil {
403+
return md.UUID, nil
404+
}
405+
406+
// Try to find instance ID on the local filesystem (created by cloud-init)
407+
const instanceIDFile = "/var/lib/cloud/data/instance-id"
408+
idBytes, err := ioutil.ReadFile(instanceIDFile)
409+
if err == nil {
410+
instanceID := string(idBytes)
411+
instanceID = strings.TrimSpace(instanceID)
412+
klog.V(3).Infof("Got instance id from %s: %s", instanceIDFile, instanceID)
413+
if instanceID != "" && instanceID != "iid-datasource-none" {
414+
return instanceID, nil
415+
}
416+
}
417+
418+
return "", err
419+
}
420+
421+
func getServerByName(client *gophercloud.ServiceClient, name types.NodeName) (*ServerAttributesExt, error) {
422+
opts := servers.ListOpts{
423+
Name: fmt.Sprintf("^%s$", regexp.QuoteMeta(mapNodeNameToServerName(name))),
424+
}
425+
426+
var s []ServerAttributesExt
427+
serverList := make([]ServerAttributesExt, 0, 1)
428+
429+
mc := metrics.NewMetricContext("server", "list")
430+
pager := servers.List(client, opts)
431+
432+
err := pager.EachPage(func(page pagination.Page) (bool, error) {
433+
if err := servers.ExtractServersInto(page, &s); err != nil {
434+
return false, err
435+
}
436+
serverList = append(serverList, s...)
437+
if len(serverList) > 1 {
438+
return false, errors.ErrMultipleResults
439+
}
440+
return true, nil
441+
})
442+
if mc.ObserveRequest(err) != nil {
443+
return nil, err
444+
}
445+
446+
if len(serverList) == 0 {
447+
return nil, errors.ErrNotFound
448+
}
449+
450+
return &serverList[0], nil
451+
}
452+
453+
// IP addresses order:
454+
// * interfaces private IPs
455+
// * access IPs
456+
// * metadata hostname
457+
// * server object Addresses (floating type)
458+
func nodeAddresses(srv *servers.Server, interfaces []attachinterfaces.Interface, networkingOpts NetworkingOpts) ([]v1.NodeAddress, error) {
459+
addrs := []v1.NodeAddress{}
460+
461+
// parse private IP addresses first in an ordered manner
462+
for _, iface := range interfaces {
463+
for _, fixedIP := range iface.FixedIPs {
464+
if iface.PortState == "ACTIVE" {
465+
isIPv6 := net.ParseIP(fixedIP.IPAddress).To4() == nil
466+
if !(isIPv6 && networkingOpts.IPv6SupportDisabled) {
467+
AddToNodeAddresses(&addrs,
468+
v1.NodeAddress{
469+
Type: v1.NodeInternalIP,
470+
Address: fixedIP.IPAddress,
471+
},
472+
)
473+
}
474+
}
475+
}
476+
}
477+
478+
// process public IP addresses
479+
if srv.AccessIPv4 != "" {
480+
AddToNodeAddresses(&addrs,
481+
v1.NodeAddress{
482+
Type: v1.NodeExternalIP,
483+
Address: srv.AccessIPv4,
484+
},
485+
)
486+
}
487+
488+
if srv.AccessIPv6 != "" && !networkingOpts.IPv6SupportDisabled {
489+
AddToNodeAddresses(&addrs,
490+
v1.NodeAddress{
491+
Type: v1.NodeExternalIP,
492+
Address: srv.AccessIPv6,
493+
},
494+
)
495+
}
496+
497+
if srv.Metadata[TypeHostName] != "" {
498+
AddToNodeAddresses(&addrs,
499+
v1.NodeAddress{
500+
Type: v1.NodeHostName,
501+
Address: srv.Metadata[TypeHostName],
502+
},
503+
)
504+
}
505+
506+
// process the rest
507+
type Address struct {
508+
IPType string `mapstructure:"OS-EXT-IPS:type"`
509+
Addr string
510+
}
511+
512+
var addresses map[string][]Address
513+
err := mapstructure.Decode(srv.Addresses, &addresses)
514+
if err != nil {
515+
return nil, err
516+
}
517+
518+
var networks []string
519+
for k := range addresses {
520+
networks = append(networks, k)
521+
}
522+
sort.Strings(networks)
523+
524+
for _, network := range networks {
525+
for _, props := range addresses[network] {
526+
var addressType v1.NodeAddressType
527+
if props.IPType == "floating" {
528+
addressType = v1.NodeExternalIP
529+
} else if util.Contains(networkingOpts.PublicNetworkName, network) {
530+
addressType = v1.NodeExternalIP
531+
// removing already added address to avoid listing it as both ExternalIP and InternalIP
532+
// may happen due to listing "private" network as "public" in CCM's config
533+
RemoveFromNodeAddresses(&addrs,
534+
v1.NodeAddress{
535+
Address: props.Addr,
536+
},
537+
)
538+
} else {
539+
if len(networkingOpts.InternalNetworkName) == 0 || util.Contains(networkingOpts.InternalNetworkName, network) {
540+
addressType = v1.NodeInternalIP
541+
} else {
542+
klog.V(5).Infof("Node '%s' address '%s' ignored due to 'internal-network-name' option", srv.Name, props.Addr)
543+
RemoveFromNodeAddresses(&addrs,
544+
v1.NodeAddress{
545+
Address: props.Addr,
546+
},
547+
)
548+
continue
549+
}
550+
}
551+
552+
isIPv6 := net.ParseIP(props.Addr).To4() == nil
553+
if !(isIPv6 && networkingOpts.IPv6SupportDisabled) {
554+
AddToNodeAddresses(&addrs,
555+
v1.NodeAddress{
556+
Type: addressType,
557+
Address: props.Addr,
558+
},
559+
)
560+
}
561+
}
562+
}
563+
564+
return addrs, nil
565+
}
566+
567+
func getAddressesByName(client *gophercloud.ServiceClient, name types.NodeName, networkingOpts NetworkingOpts) ([]v1.NodeAddress, error) {
568+
srv, err := getServerByName(client, name)
569+
if err != nil {
570+
return nil, err
571+
}
572+
573+
interfaces, err := getAttachedInterfacesByID(client, srv.ID)
574+
if err != nil {
575+
return nil, err
576+
}
577+
578+
return nodeAddresses(&srv.Server, interfaces, networkingOpts)
579+
}
580+
581+
func getAddressByName(client *gophercloud.ServiceClient, name types.NodeName, needIPv6 bool, networkingOpts NetworkingOpts) (string, error) {
582+
if needIPv6 && networkingOpts.IPv6SupportDisabled {
583+
return "", errors.ErrIPv6SupportDisabled
584+
}
585+
586+
addrs, err := getAddressesByName(client, name, networkingOpts)
587+
if err != nil {
588+
return "", err
589+
} else if len(addrs) == 0 {
590+
return "", errors.ErrNoAddressFound
591+
}
592+
593+
for _, addr := range addrs {
594+
isIPv6 := net.ParseIP(addr.Address).To4() == nil
595+
if (addr.Type == v1.NodeInternalIP) && (isIPv6 == needIPv6) {
596+
return addr.Address, nil
597+
}
598+
}
599+
600+
for _, addr := range addrs {
601+
isIPv6 := net.ParseIP(addr.Address).To4() == nil
602+
if (addr.Type == v1.NodeExternalIP) && (isIPv6 == needIPv6) {
603+
return addr.Address, nil
604+
}
605+
}
606+
// It should never return an address from a different IP Address family than the one needed
607+
return "", errors.ErrNoAddressFound
608+
}
609+
610+
// getAttachedInterfacesByID returns the node interfaces of the specified instance.
611+
func getAttachedInterfacesByID(client *gophercloud.ServiceClient, serviceID string) ([]attachinterfaces.Interface, error) {
612+
var interfaces []attachinterfaces.Interface
613+
614+
mc := metrics.NewMetricContext("server_os_interface", "list")
615+
pager := attachinterfaces.List(client, serviceID)
616+
err := pager.EachPage(func(page pagination.Page) (bool, error) {
617+
s, err := attachinterfaces.ExtractInterfaces(page)
618+
if err != nil {
619+
return false, err
620+
}
621+
interfaces = append(interfaces, s...)
622+
return true, nil
623+
})
624+
if mc.ObserveRequest(err) != nil {
625+
return interfaces, err
626+
}
627+
628+
return interfaces, nil
629+
}

0 commit comments

Comments
 (0)