@@ -19,20 +19,27 @@ package openstack
1919import (
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.
5261func (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.
80101func (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.
237246func (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