@@ -19,20 +19,27 @@ package openstack
19
19
import (
20
20
"context"
21
21
"fmt"
22
+ "io/ioutil"
23
+ "net"
22
24
"regexp"
25
+ "sort"
23
26
"strings"
24
27
25
28
"github.com/gophercloud/gophercloud"
29
+ "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces"
26
30
"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
27
31
"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"
28
35
"k8s.io/klog/v2"
29
36
30
- "k8s.io/api/core/v1"
31
37
"k8s.io/apimachinery/pkg/types"
32
38
"k8s.io/apimachinery/pkg/util/validation"
33
39
cloudprovider "k8s.io/cloud-provider"
34
40
"k8s.io/cloud-provider-openstack/pkg/client"
35
41
"k8s.io/cloud-provider-openstack/pkg/metrics"
42
+ "k8s.io/cloud-provider-openstack/pkg/util"
36
43
"k8s.io/cloud-provider-openstack/pkg/util/errors"
37
44
"k8s.io/cloud-provider-openstack/pkg/util/metadata"
38
45
)
@@ -48,6 +55,8 @@ const (
48
55
instanceShutoff = "SHUTOFF"
49
56
)
50
57
58
+ var _ cloudprovider.Instances = & Instances {}
59
+
51
60
// Instances returns an implementation of Instances for OpenStack.
52
61
func (os * OpenStack ) Instances () (cloudprovider.Instances , bool ) {
53
62
return os .instances ()
@@ -75,6 +84,18 @@ func (os *OpenStack) instances() (*Instances, bool) {
75
84
}, true
76
85
}
77
86
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
+
78
99
// CurrentNodeName implements Instances.CurrentNodeName
79
100
// Note this is *not* necessarily the same as hostname.
80
101
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
221
242
}, nil
222
243
}
223
244
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
-
236
245
// InstanceID returns the cloud provider ID of the specified instance.
237
246
func (i * Instances ) InstanceID (ctx context.Context , name types.NodeName ) (string , error ) {
238
247
srv , err := getServerByName (i .compute , name )
239
248
if err != nil {
240
- if err == ErrNotFound {
249
+ if err == errors . ErrNotFound {
241
250
return "" , cloudprovider .InstanceNotFound
242
251
}
243
252
return "" , err
@@ -371,3 +380,250 @@ func RemoveFromNodeAddresses(addresses *[]v1.NodeAddress, removeAddresses ...v1.
371
380
}
372
381
}
373
382
}
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