Skip to content

Commit 3e2deac

Browse files
Merge pull request #147 from shiftstack/merge-bot-master
Merge https://github.com/kubernetes/cloud-provider-openstack:master into master
2 parents 52c1a1c + 813a0ae commit 3e2deac

File tree

10 files changed

+522
-77
lines changed

10 files changed

+522
-77
lines changed

charts/manila-csi-plugin/templates/controllerplugin-statefulset.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ spec:
2626
{{- if $.Values.csimanila.topologyAwarenessEnabled }}
2727
- "--feature-gates=Topology=true"
2828
{{- end }}
29+
{{- if $.Values.controllerplugin.provisioner.extraCreateMetadata }}
30+
- "--extra-create-metadata"
31+
{{- end }}
2932
env:
3033
- name: ADDRESS
3134
value: "unix:///var/lib/kubelet/plugins/{{ printf "%s.%s" .protocolSelector $.Values.driverName | lower }}/csi-controllerplugin.sock"

charts/manila-csi-plugin/values.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ controllerplugin:
7979
tag: v3.0.0
8080
pullPolicy: IfNotPresent
8181
resources: {}
82+
# Whether to pass --extra-create-metadata flag to csi-provisioner.
83+
extraCreateMetadata: false
8284
# CSI external-snapshotter container spec
8385
snapshotter:
8486
image:

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
@@ -166,6 +166,13 @@ The options in `Global` section are used for openstack-cloud-controller-manager
166166
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: ""
167167
* `internal-network-name`
168168
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: ""
169+
* `address-sort-order`
170+
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.
171+
172+
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.
173+
174+
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.
175+
Default: ""
169176
170177
### Load Balancer
171178

pkg/csi/manila/controllerserver.go

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@ type controllerServer struct {
4343
var (
4444
pendingVolumes = sync.Map{}
4545
pendingSnapshots = sync.Map{}
46+
47+
// Recognized volume parameters passed by Kubernetes csi-provisioner sidecar
48+
// when run with --extra-create-metadata flag. These are added to metadata
49+
// of newly created shares if present.
50+
recognizedCSIProvisionerParams = []string{
51+
"csi.storage.k8s.io/pvc/name",
52+
"csi.storage.k8s.io/pvc/namespace",
53+
"csi.storage.k8s.io/pv/name",
54+
}
4655
)
4756

4857
func getVolumeCreator(source *csi.VolumeContentSource, shareOpts *options.ControllerVolumeContext, compatOpts *options.CompatibilityOptions) (volumeCreator, error) {
@@ -92,7 +101,7 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol
92101
return nil, status.Errorf(codes.InvalidArgument, "invalid volume parameters: %v", err)
93102
}
94103

95-
shareMetadata, err := prepareShareMetadata(shareOpts.AppendShareMetadata, cs.d.clusterID)
104+
shareMetadata, err := prepareShareMetadata(shareOpts.AppendShareMetadata, cs.d.clusterID, params)
96105
if err != nil {
97106
return nil, err
98107
}
@@ -485,16 +494,34 @@ func parseStringMapFromJSON(data string) (m map[string]string, err error) {
485494
return
486495
}
487496

488-
func prepareShareMetadata(appendShareMetadata, clusterID string) (map[string]string, error) {
489-
shareMetadata, err := parseStringMapFromJSON(appendShareMetadata)
497+
func prepareShareMetadata(appendShareMetadata, clusterID string, volumeParams map[string]string) (map[string]string, error) {
498+
shareMetadata := make(map[string]string)
499+
500+
// Get extra metadata provided by csi-provisioner sidecar if present.
501+
for _, k := range recognizedCSIProvisionerParams {
502+
if v, ok := volumeParams[k]; ok {
503+
shareMetadata[k] = v
504+
}
505+
}
506+
507+
// Get metadata from appendShareMetadata volume parameter.
508+
// It will not overwrite keys defined in recognizedCSIProvisionerParams.
509+
510+
appendShareMetadataMap, err := parseStringMapFromJSON(appendShareMetadata)
490511
if err != nil {
491512
return nil, status.Errorf(codes.InvalidArgument, "failed to parse appendShareMetadata field: %v", err)
492513
}
493514

494-
if clusterID != "" {
495-
if shareMetadata == nil {
496-
shareMetadata = make(map[string]string)
515+
for k, v := range appendShareMetadataMap {
516+
if existingValue, ok := shareMetadata[k]; ok {
517+
klog.Warningf("skip adding share metadata key %s from appendShareMetadata because it already exists with value %s", k, existingValue)
518+
} else {
519+
shareMetadata[k] = v
497520
}
521+
}
522+
523+
// Get cluster ID.
524+
if clusterID != "" {
498525
if val, ok := shareMetadata[clusterMetadataKey]; ok && val != clusterID {
499526
klog.Warningf("skip adding cluster ID %v to share metadata because appended metadata already defines it as %v", clusterID, val)
500527
} else {

pkg/csi/manila/controllerserver_test.go

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,24 @@ import (
2020

2121
func TestPrepareShareMetadata(t *testing.T) {
2222
ts := []struct {
23+
allVolumeParams map[string]string
2324
appendShareMetadata string
2425
cluster string
2526
expectedResult map[string]string
2627
expectedError bool
2728
}{
2829
{
2930
// Empty metadata and cluster
31+
allVolumeParams: map[string]string{},
3032
appendShareMetadata: "",
3133
cluster: "",
3234
expectedResult: nil,
3335
expectedError: false,
3436
},
3537
{
3638
// Existing metadata and empty cluster
37-
appendShareMetadata: "{\"keyA\": \"valueA\", \"keyB\": \"valueB\"}",
39+
allVolumeParams: map[string]string{"appendShareMetadata": `{"keyA": "valueA", "keyB": "valueB"}`},
40+
appendShareMetadata: `{"keyA": "valueA", "keyB": "valueB"}`,
3841
cluster: "",
3942
expectedResult: map[string]string{"keyA": "valueA", "keyB": "valueB"},
4043
expectedError: false,
@@ -48,29 +51,66 @@ func TestPrepareShareMetadata(t *testing.T) {
4851
},
4952
{
5053
// Both metadata and cluster
54+
allVolumeParams: map[string]string{"appendShareMetadata": `{"keyA": "valueA", "keyB": "valueB"}`},
5155
appendShareMetadata: "{\"keyA\": \"valueA\", \"keyB\": \"valueB\"}",
5256
cluster: "MyCluster",
5357
expectedResult: map[string]string{"keyA": "valueA", "keyB": "valueB", clusterMetadataKey: "MyCluster"},
5458
expectedError: false,
5559
},
5660
{
5761
// Overwrite cluster
62+
allVolumeParams: map[string]string{"appendShareMetadata": "{\"keyA\": \"valueA\", \"" + clusterMetadataKey + "\": \"SomeValue\"}"},
5863
appendShareMetadata: "{\"keyA\": \"valueA\", \"" + clusterMetadataKey + "\": \"SomeValue\"}",
5964
cluster: "MyCluster",
6065
expectedResult: map[string]string{"keyA": "valueA", clusterMetadataKey: "SomeValue"},
6166
expectedError: false,
6267
},
6368
{
6469
// Incorrect metadata
70+
allVolumeParams: map[string]string{"appendShareMetadata": "INVALID"},
6571
appendShareMetadata: "INVALID",
6672
cluster: "MyCluster",
6773
expectedResult: nil,
6874
expectedError: true,
6975
},
76+
{
77+
// csi-provisioner PV/PVC metadata
78+
allVolumeParams: map[string]string{
79+
"csi.storage.k8s.io/pvc/name": "pvc-name",
80+
"csi.storage.k8s.io/pvc/namespace": "pvc-namespace",
81+
"csi.storage.k8s.io/pv/name": "pv-name",
82+
},
83+
cluster: "",
84+
expectedResult: map[string]string{
85+
"csi.storage.k8s.io/pvc/name": "pvc-name",
86+
"csi.storage.k8s.io/pvc/namespace": "pvc-namespace",
87+
"csi.storage.k8s.io/pv/name": "pv-name",
88+
},
89+
appendShareMetadata: "",
90+
expectedError: false,
91+
},
92+
{
93+
// csi-provisioner PV/PVC metadata with conflicting appendShareMetadata
94+
allVolumeParams: map[string]string{
95+
"csi.storage.k8s.io/pvc/name": "pvc-name",
96+
"csi.storage.k8s.io/pvc/namespace": "pvc-namespace",
97+
"csi.storage.k8s.io/pv/name": "pv-name",
98+
"appendShareMetadata": `{"csi.storage.k8s.io/pvc/name": "SomeValue", "keyX": "valueX"}`,
99+
},
100+
appendShareMetadata: `{"csi.storage.k8s.io/pvc/name": "SomeValue", "keyX": "valueX"}`,
101+
cluster: "",
102+
expectedResult: map[string]string{
103+
"csi.storage.k8s.io/pvc/name": "pvc-name",
104+
"csi.storage.k8s.io/pvc/namespace": "pvc-namespace",
105+
"csi.storage.k8s.io/pv/name": "pv-name",
106+
"keyX": "valueX",
107+
},
108+
expectedError: false,
109+
},
70110
}
71111

72112
for i := range ts {
73-
result, err := prepareShareMetadata(ts[i].appendShareMetadata, ts[i].cluster)
113+
result, err := prepareShareMetadata(ts[i].appendShareMetadata, ts[i].cluster, ts[i].allVolumeParams)
74114

75115
if err != nil && !ts[i].expectedError {
76116
t.Errorf("test %d: unexpected error: %v", i, err)

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
"net"
@@ -56,10 +57,79 @@ type Instances struct {
5657
const (
5758
instanceShutoff = "SHUTOFF"
5859
RegionalProviderIDEnv = "OS_CCM_REGIONAL"
60+
noSortPriority = 0
5961
)
6062

6163
var _ cloudprovider.Instances = &Instances{}
6264

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

681+
if networkingOpts.AddressSortOrder != "" {
682+
sortNodeAddresses(addrs, networkingOpts.AddressSortOrder)
683+
}
684+
611685
return addrs, nil
612686
}
613687

0 commit comments

Comments
 (0)