Skip to content

Commit 26923f1

Browse files
authored
Automated cherry pick of kubernetes#1946: Support specifying a sort order for node addresses (kubernetes#2031)
* Support specifying a sort order for node addresses This introduces a configuration key which influences the way the provider reports the node addresses to the Kubernetes node resource. The default order depends on the hard-coded order the provider queries the addresses and what the cloud returns, which does not guarantee a specific order. To override this behavior it is possible to specify a comma separated list of CIDRs. Essentially, this will sort and group all addresses matching a CIDR in a prioritized manner, where the first item having a higher priority than the last. All non-matching addresses will remain in the same order they are already in. For example, this option can be useful when having multiple or dual-stack interfaces attached to a node and needing a user-controlled, deterministic way of sorting the addresses. * Update gofmt for various files
1 parent 055ac5c commit 26923f1

File tree

15 files changed

+397
-38
lines changed

15 files changed

+397
-38
lines changed

docs/openstack-cloud-controller-manager/using-openstack-cloud-controller-manager.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,13 @@ The options in `Global` section are used for openstack-cloud-controller-manager
167167
The name of Neutron external network. openstack-cloud-controller-manager uses this option when getting the external IP of the Kubernetes node. Can be specified multiple times. Specified network names will be ORed. Default: ""
168168
* `internal-network-name`
169169
The name of Neutron internal network. openstack-cloud-controller-manager uses this option when getting the internal IP of the Kubernetes node, this is useful if the node has multiple interfaces. Can be specified multiple times. Specified network names will be ORed. Default: ""
170+
* `address-sort-order`
171+
This configuration key influences the way the provider reports the node addresses to the Kubernetes node resource. The default order depends on the hard-coded order the provider queries the addresses and what the cloud returns, which does not guarantee a specific order.
172+
173+
To override this behavior it is possible to specify a comma separated list of CIDRs. Essentially, this will sort and group all addresses matching a CIDR in a prioritized manner, where the first item having a higher priority than the last. All non-matching addresses will remain in the same order they are already in.
174+
175+
For example, this option can be useful when having multiple or dual-stack interfaces attached to a node and needing a user-controlled, deterministic way of sorting the addresses.
176+
Default: ""
170177
171178
### Load Balancer
172179

pkg/autohealing/cloudprovider/openstack/provider.go

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -251,10 +251,11 @@ func (provider OpenStackCloudProvider) waitForServerDetachVolumes(serverID strin
251251
}
252252

253253
// FirstTimeRepair Handle the first time repair for a node
254-
// 1) If the node is the first time in error, reboot and uncordon it
255-
// 2) If the node is not the first time in error, check if the last reboot time is in provider.Config.RebuildDelayAfterReboot
256-
// That said, if the node has been found in broken status before but has been long time since then, the processed variable
257-
// will be kept as False, which means the node need to be rebuilt to fix it, otherwise it means the has been processed.
254+
// 1. If the node is the first time in error, reboot and uncordon it
255+
// 2. If the node is not the first time in error, check if the last reboot time is in provider.Config.RebuildDelayAfterReboot
256+
// That said, if the node has been found in broken status before but has been long time since then, the processed variable
257+
// will be kept as False, which means the node need to be rebuilt to fix it, otherwise it means the has been processed.
258+
//
258259
// The bool type return value means that if the node has been processed from a first time repair PoV
259260
func (provider OpenStackCloudProvider) firstTimeRepair(n healthcheck.NodeInfo, serverID string, firstTimeRebootNodes map[string]healthcheck.NodeInfo) (bool, error) {
260261
var firstTimeUnhealthy = true
@@ -312,12 +313,14 @@ func (provider OpenStackCloudProvider) firstTimeRepair(n healthcheck.NodeInfo, s
312313
}
313314

314315
// Repair For master nodes: detach etcd and docker volumes, find the root
315-
// volume, then shutdown the VM, marks the both the VM and the root
316-
// volume (heat resource) as "unhealthy" then trigger Heat stack update
317-
// in order to rebuild the node. The information this function needs:
318-
// - Nova VM ID
319-
// - Root volume ID
320-
// - Heat stack ID and resource ID.
316+
//
317+
// volume, then shutdown the VM, marks the both the VM and the root
318+
// volume (heat resource) as "unhealthy" then trigger Heat stack update
319+
// in order to rebuild the node. The information this function needs:
320+
// - Nova VM ID
321+
// - Root volume ID
322+
// - Heat stack ID and resource ID.
323+
//
321324
// For worker nodes: Call Magnum resize API directly.
322325
func (provider OpenStackCloudProvider) Repair(nodes []healthcheck.NodeInfo) error {
323326
if len(nodes) == 0 {

pkg/csi/cinder/openstack/openstack_snapshots.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ func (os *OpenStack) DeleteSnapshot(snapID string) error {
142142
return err
143143
}
144144

145-
//GetSnapshotByID returns snapshot details by id
145+
// GetSnapshotByID returns snapshot details by id
146146
func (os *OpenStack) GetSnapshotByID(snapshotID string) (*snapshots.Snapshot, error) {
147147
s, err := snapshots.Get(os.blockstorage, snapshotID).Extract()
148148
if err != nil {

pkg/csi/cinder/openstack/openstack_volumes.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ func (os *OpenStack) WaitDiskAttached(instanceID string, volumeID string) error
232232
return err
233233
}
234234

235-
//WaitVolumeTargetStatus waits for volume to be in target state
235+
// WaitVolumeTargetStatus waits for volume to be in target state
236236
func (os *OpenStack) WaitVolumeTargetStatus(volumeID string, tStatus []string) error {
237237
backoff := wait.Backoff{
238238
Duration: operationFinishInitDelay,
@@ -367,7 +367,7 @@ func (os *OpenStack) ExpandVolume(volumeID string, status string, newSize int) e
367367
return fmt.Errorf("volume cannot be resized, when status is %s", status)
368368
}
369369

370-
//GetMaxVolLimit returns max vol limit
370+
// GetMaxVolLimit returns max vol limit
371371
func (os *OpenStack) GetMaxVolLimit() int64 {
372372
if os.bsOpts.NodeVolumeAttachLimit > 0 && os.bsOpts.NodeVolumeAttachLimit <= 256 {
373373
return os.bsOpts.NodeVolumeAttachLimit

pkg/csi/manila/validator/validator.go

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,20 @@ import (
2828
// By default, the input data has to contain a value for each struct field.
2929
//
3030
// Available tags:
31-
// * name:"FIELD-NAME" : key of the value in the input map
32-
// * value: modifies value requirements:
33-
// value:"required" : field is required
34-
// value:"optional" : field is optional
35-
// value:"requiredIf:FIELD-NAME=REGEXP-PATTERN" : field is required if the value of FIELD-NAME matches REGEXP-PATTERN
36-
// value:"optionalIf:FIELD-NAME=REGEXP-PATTERN" : field is optional if the value of FIELD-NAME matches REGEXP-PATTERN
37-
// value:"default:VALUE" : field value defaults to VALUE
38-
// * dependsOn:"FIELD-NAMES|,..." : if this field is not empty, the specified fields are required to be present
39-
// operator ',' acts as AND
40-
// operator '|' acts as XOR
41-
// e.g.: dependsOn:"f1|f2|f3,f4,f5" : if this field is not empty, exactly one of {f1,f2,f3} is required to be present,
42-
// and f4 and f5 is required to be present
43-
// * precludes:"FIELD-NAMES,..." : if this field is not empty, all specified fields are required to be empty
44-
// * matches:"REGEXP-PATTERN" : if this field is not empty, it's required to match REGEXP-PATTERN
31+
// - name:"FIELD-NAME" : key of the value in the input map
32+
// - value: modifies value requirements:
33+
// value:"required" : field is required
34+
// value:"optional" : field is optional
35+
// value:"requiredIf:FIELD-NAME=REGEXP-PATTERN" : field is required if the value of FIELD-NAME matches REGEXP-PATTERN
36+
// value:"optionalIf:FIELD-NAME=REGEXP-PATTERN" : field is optional if the value of FIELD-NAME matches REGEXP-PATTERN
37+
// value:"default:VALUE" : field value defaults to VALUE
38+
// - dependsOn:"FIELD-NAMES|,..." : if this field is not empty, the specified fields are required to be present
39+
// operator ',' acts as AND
40+
// operator '|' acts as XOR
41+
// e.g.: dependsOn:"f1|f2|f3,f4,f5" : if this field is not empty, exactly one of {f1,f2,f3} is required to be present,
42+
// and f4 and f5 is required to be present
43+
// - precludes:"FIELD-NAMES,..." : if this field is not empty, all specified fields are required to be empty
44+
// - matches:"REGEXP-PATTERN" : if this field is not empty, it's required to match REGEXP-PATTERN
4545
type Validator struct {
4646
t reflect.Type
4747

pkg/ingress/controller/controller.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1033,7 +1033,6 @@ func privateKeyFromPEM(pemData []byte) (crypto.PrivateKey, error) {
10331033

10341034
// parsePEMBundle parses a certificate bundle from top to bottom and returns
10351035
// a slice of x509 certificates. This function will error if no certificates are found.
1036-
//
10371036
func parsePEMBundle(bundle []byte) ([]*x509.Certificate, error) {
10381037
var certificates []*x509.Certificate
10391038
var certDERBlock *pem.Block

pkg/kms/barbican/barbican.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ type KMSOpts struct {
1515
KeyID string `gcfg:"key-id"`
1616
}
1717

18-
//Config to read config options
18+
// Config to read config options
1919
type Config struct {
2020
Global client.AuthOpts
2121
KeyManager KMSOpts

pkg/openstack/instances.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package openstack
1818

1919
import (
20+
"bytes"
2021
"context"
2122
"fmt"
2223
"io/ioutil"
@@ -53,10 +54,79 @@ type Instances struct {
5354

5455
const (
5556
instanceShutoff = "SHUTOFF"
57+
noSortPriority = 0
5658
)
5759

5860
var _ cloudprovider.Instances = &Instances{}
5961

62+
// buildAddressSortOrderList builds a list containing only valid CIDRs based on the content of addressSortOrder.
63+
//
64+
// It will ignore and warn about invalid sort order items.
65+
func buildAddressSortOrderList(addressSortOrder string) []*net.IPNet {
66+
var list []*net.IPNet
67+
for _, item := range strings.Split(addressSortOrder, ",") {
68+
item = strings.TrimSpace(item)
69+
70+
_, cidr, err := net.ParseCIDR(item)
71+
if err != nil {
72+
klog.Warningf("Ignoring invalid sort order item '%s': %v.", item, err)
73+
continue
74+
}
75+
76+
list = append(list, cidr)
77+
}
78+
79+
return list
80+
}
81+
82+
// getSortPriority returns the priority as int of an address.
83+
//
84+
// The priority depends on the index of the CIDR in the list the address is matching,
85+
// where the first item of the list has higher priority than the last.
86+
//
87+
// If the address does not match any CIDR or is not an IP address the function returns noSortPriority.
88+
func getSortPriority(list []*net.IPNet, address string) int {
89+
parsedAddress := net.ParseIP(address)
90+
if parsedAddress == nil {
91+
return noSortPriority
92+
}
93+
94+
for i, cidr := range list {
95+
if cidr.Contains(parsedAddress) {
96+
fmt.Println(i, cidr, len(list)-i)
97+
return len(list) - i
98+
}
99+
}
100+
101+
return noSortPriority
102+
}
103+
104+
// sortNodeAddresses sorts node addresses based on comma separated list of CIDRs represented by addressSortOrder.
105+
//
106+
// The function only sorts addresses which match the CIDR and leaves the other addresses in the same order they are in.
107+
// Essentially, it will also group the addresses matching a CIDR together and sort them ascending in this group,
108+
// whereas the inter-group sorting depends on the priority.
109+
//
110+
// The priority depends on the order of the item in addressSortOrder, where the first item has higher priority than the last.
111+
func sortNodeAddresses(addresses []v1.NodeAddress, addressSortOrder string) {
112+
list := buildAddressSortOrderList(addressSortOrder)
113+
114+
sort.SliceStable(addresses, func(i int, j int) bool {
115+
addressLeft := addresses[i]
116+
addressRight := addresses[j]
117+
118+
priorityLeft := getSortPriority(list, addressLeft.Address)
119+
priorityRight := getSortPriority(list, addressRight.Address)
120+
121+
// ignore priorities of value 0 since this means the address has noSortPriority and we need to sort by priority
122+
if priorityLeft > noSortPriority && priorityLeft == priorityRight {
123+
return bytes.Compare(net.ParseIP(addressLeft.Address), net.ParseIP(addressRight.Address)) < 0
124+
}
125+
126+
return priorityLeft > priorityRight
127+
})
128+
}
129+
60130
// Instances returns an implementation of Instances for OpenStack.
61131
func (os *OpenStack) Instances() (cloudprovider.Instances, bool) {
62132
return os.instances()
@@ -561,6 +631,10 @@ func nodeAddresses(srv *servers.Server, interfaces []attachinterfaces.Interface,
561631
}
562632
}
563633

634+
if networkingOpts.AddressSortOrder != "" {
635+
sortNodeAddresses(addrs, networkingOpts.AddressSortOrder)
636+
}
637+
564638
return addrs, nil
565639
}
566640

0 commit comments

Comments
 (0)