@@ -21,6 +21,7 @@ import (
21
21
"fmt"
22
22
sysos "os"
23
23
"regexp"
24
+ "slices"
24
25
"strings"
25
26
26
27
"github.com/gophercloud/gophercloud/v2"
@@ -46,9 +47,9 @@ const (
46
47
47
48
// InstancesV2 encapsulates an implementation of InstancesV2 for OpenStack.
48
49
type InstancesV2 struct {
49
- compute * gophercloud.ServiceClient
50
- network * gophercloud.ServiceClient
51
- region string
50
+ compute map [ string ] * gophercloud.ServiceClient
51
+ network map [ string ] * gophercloud.ServiceClient
52
+ regions [] string
52
53
regionProviderID bool
53
54
networkingOpts NetworkingOpts
54
55
}
@@ -57,16 +58,25 @@ type InstancesV2 struct {
57
58
func (os * OpenStack ) InstancesV2 () (cloudprovider.InstancesV2 , bool ) {
58
59
klog .V (4 ).Info ("openstack.Instancesv2() called" )
59
60
60
- compute , err := client .NewComputeV2 (os .provider , os .epOpts )
61
- if err != nil {
62
- klog .Errorf ("unable to access compute v2 API : %v" , err )
63
- return nil , false
64
- }
61
+ var err error
62
+ compute := make (map [string ]* gophercloud.ServiceClient , len (os .regions ))
63
+ network := make (map [string ]* gophercloud.ServiceClient , len (os .regions ))
65
64
66
- network , err := client .NewNetworkV2 (os .provider , os .epOpts )
67
- if err != nil {
68
- klog .Errorf ("unable to access network v2 API : %v" , err )
69
- return nil , false
65
+ for _ , region := range os .regions {
66
+ opt := os .epOpts
67
+ opt .Region = region
68
+
69
+ compute [region ], err = client .NewComputeV2 (os .provider , opt )
70
+ if err != nil {
71
+ klog .Errorf ("unable to access compute v2 API : %v" , err )
72
+ return nil , false
73
+ }
74
+
75
+ network [region ], err = client .NewNetworkV2 (os .provider , opt )
76
+ if err != nil {
77
+ klog .Errorf ("unable to access network v2 API : %v" , err )
78
+ return nil , false
79
+ }
70
80
}
71
81
72
82
regionalProviderID := false
@@ -77,17 +87,23 @@ func (os *OpenStack) InstancesV2() (cloudprovider.InstancesV2, bool) {
77
87
return & InstancesV2 {
78
88
compute : compute ,
79
89
network : network ,
80
- region : os .epOpts . Region ,
90
+ regions : os .regions ,
81
91
regionProviderID : regionalProviderID ,
82
92
networkingOpts : os .networkingOpts ,
83
93
}, true
84
94
}
85
95
86
96
// InstanceExists indicates whether a given node exists according to the cloud provider
87
97
func (i * InstancesV2 ) InstanceExists (ctx context.Context , node * v1.Node ) (bool , error ) {
88
- _ , err := i .getInstance (ctx , node )
98
+ klog .V (4 ).InfoS ("openstack.InstanceExists() called" , "node" , klog .KObj (node ),
99
+ "providerID" , node .Spec .ProviderID ,
100
+ "region" , node .Labels [v1 .LabelTopologyRegion ])
101
+
102
+ _ , _ , err := i .getInstance (ctx , node )
89
103
if err == cloudprovider .InstanceNotFound {
90
- klog .V (6 ).Infof ("instance not found for node: %s" , node .Name )
104
+ klog .V (6 ).InfoS ("Node is not found in cloud provider" , "node" , klog .KObj (node ),
105
+ "providerID" , node .Spec .ProviderID ,
106
+ "region" , node .Labels [v1 .LabelTopologyRegion ])
91
107
return false , nil
92
108
}
93
109
@@ -100,7 +116,11 @@ func (i *InstancesV2) InstanceExists(ctx context.Context, node *v1.Node) (bool,
100
116
101
117
// InstanceShutdown returns true if the instance is shutdown according to the cloud provider.
102
118
func (i * InstancesV2 ) InstanceShutdown (ctx context.Context , node * v1.Node ) (bool , error ) {
103
- server , err := i .getInstance (ctx , node )
119
+ klog .V (4 ).InfoS ("openstack.InstanceShutdown() called" , "node" , klog .KObj (node ),
120
+ "providerID" , node .Spec .ProviderID ,
121
+ "region" , node .Labels [v1 .LabelTopologyRegion ])
122
+
123
+ server , _ , err := i .getInstance (ctx , node )
104
124
if err != nil {
105
125
return false , err
106
126
}
@@ -115,7 +135,11 @@ func (i *InstancesV2) InstanceShutdown(ctx context.Context, node *v1.Node) (bool
115
135
116
136
// InstanceMetadata returns the instance's metadata.
117
137
func (i * InstancesV2 ) InstanceMetadata (ctx context.Context , node * v1.Node ) (* cloudprovider.InstanceMetadata , error ) {
118
- srv , err := i .getInstance (ctx , node )
138
+ klog .V (4 ).InfoS ("openstack.InstanceMetadata() called" , "node" , klog .KObj (node ),
139
+ "providerID" , node .Spec .ProviderID ,
140
+ "region" , node .Labels [v1 .LabelTopologyRegion ])
141
+
142
+ srv , region , err := i .getInstance (ctx , node )
119
143
if err != nil {
120
144
return nil , err
121
145
}
@@ -124,62 +148,140 @@ func (i *InstancesV2) InstanceMetadata(ctx context.Context, node *v1.Node) (*clo
124
148
server = * srv
125
149
}
126
150
127
- instanceType , err := srvInstanceType (ctx , i .compute , & server )
151
+ instanceType , err := srvInstanceType (ctx , i .compute [ region ] , & server )
128
152
if err != nil {
129
153
return nil , err
130
154
}
131
155
132
- ports , err := getAttachedPorts (ctx , i .network , server .ID )
156
+ ports , err := getAttachedPorts (ctx , i .network [ region ] , server .ID )
133
157
if err != nil {
134
158
return nil , err
135
159
}
136
160
137
- addresses , err := nodeAddresses (ctx , & server , ports , i .network , i .networkingOpts )
161
+ addresses , err := nodeAddresses (ctx , & server , ports , i .network [ region ] , i .networkingOpts )
138
162
if err != nil {
139
163
return nil , err
140
164
}
141
165
142
166
availabilityZone := util .SanitizeLabel (server .AvailabilityZone )
143
167
144
168
return & cloudprovider.InstanceMetadata {
145
- ProviderID : i .makeInstanceID (& server ),
169
+ ProviderID : i .makeInstanceID (& server , region ),
146
170
InstanceType : instanceType ,
147
171
NodeAddresses : addresses ,
148
172
Zone : availabilityZone ,
149
- Region : i . region ,
173
+ Region : region ,
150
174
}, nil
151
175
}
152
176
153
- func (i * InstancesV2 ) makeInstanceID (srv * servers.Server ) string {
177
+ func (i * InstancesV2 ) makeInstanceID (srv * servers.Server , region string ) string {
154
178
if i .regionProviderID {
155
- return fmt .Sprintf ("%s://%s/%s" , ProviderName , i . region , srv .ID )
179
+ return fmt .Sprintf ("%s://%s/%s" , ProviderName , region , srv .ID )
156
180
}
157
181
return fmt .Sprintf ("%s:///%s" , ProviderName , srv .ID )
158
182
}
159
183
160
- func (i * InstancesV2 ) getInstance (ctx context.Context , node * v1.Node ) (* servers.Server , error ) {
161
- if node .Spec .ProviderID == "" {
162
- return getServerByName (ctx , i .compute , node .Name )
184
+ func (i * InstancesV2 ) getInstance (ctx context.Context , node * v1.Node ) (* servers.Server , string , error ) {
185
+ var instanceID , instanceRegion string
186
+
187
+ if node .Spec .ProviderID != "" {
188
+ var err error
189
+
190
+ instanceID , instanceRegion , err = instanceIDFromProviderID (node .Spec .ProviderID )
191
+ if err != nil {
192
+ return nil , "" , err
193
+ }
163
194
}
164
195
165
- instanceID , instanceRegion , err := instanceIDFromProviderID (node .Spec .ProviderID )
166
- if err != nil {
167
- return nil , err
196
+ if instanceRegion != "" {
197
+ if slices .Contains (i .regions , instanceRegion ) {
198
+ return i .getInstanceByID (ctx , instanceID , []string {instanceRegion })
199
+ }
200
+
201
+ return nil , "" , fmt .Errorf ("getInstance: ProviderID \" %s\" didn't match supported regions \" %s\" " , node .Spec .ProviderID , strings .Join (i .regions , "," ))
202
+ }
203
+
204
+ // At this point we know that ProviderID is not properly set or it doesn't contain region information
205
+ // We need to search for the instance in all regions
206
+ var searchRegions []string
207
+
208
+ // We cannot trust the region label, so we need to check the region
209
+ instanceRegion = node .Labels [v1 .LabelTopologyRegion ]
210
+ if slices .Contains (i .regions , instanceRegion ) {
211
+ searchRegions = []string {instanceRegion }
168
212
}
169
213
170
- if instanceRegion != "" && instanceRegion != i .region {
171
- return nil , fmt .Errorf ("ProviderID \" %s\" didn't match supported region \" %s\" " , node .Spec .ProviderID , i .region )
214
+ for _ , r := range i .regions {
215
+ if r != instanceRegion {
216
+ searchRegions = append (searchRegions , r )
217
+ }
172
218
}
173
219
220
+ klog .V (4 ).InfoS ("openstack.getInstance() trying to find the instance in regions" , "node" , klog .KObj (node ),
221
+ "instanceID" , instanceID ,
222
+ "regions" , strings .Join (searchRegions , "," ))
223
+
224
+ if instanceID == "" {
225
+ return i .getInstanceByName (ctx , node .Name , searchRegions )
226
+ }
227
+
228
+ return i .getInstanceByID (ctx , instanceID , searchRegions )
229
+ }
230
+
231
+ func (i * InstancesV2 ) getInstanceByID (ctx context.Context , instanceID string , searchRegions []string ) (* servers.Server , string , error ) {
232
+ server := servers.Server {}
233
+
174
234
mc := metrics .NewMetricContext ("server" , "get" )
175
- server , err := servers .Get (ctx , i .compute , instanceID ).Extract ()
176
- if mc .ObserveRequest (err ) != nil {
177
- if errors .IsNotFound (err ) {
178
- return nil , cloudprovider .InstanceNotFound
235
+ for _ , r := range searchRegions {
236
+ err := servers .Get (ctx , i .compute [r ], instanceID ).ExtractInto (& server )
237
+ if mc .ObserveRequest (err ) != nil {
238
+ if errors .IsNotFound (err ) {
239
+ continue
240
+ }
241
+
242
+ return nil , "" , err
179
243
}
180
- return nil , err
244
+
245
+ return & server , r , nil
181
246
}
182
- return server , nil
247
+
248
+ return nil , "" , cloudprovider .InstanceNotFound
249
+ }
250
+
251
+ func (i * InstancesV2 ) getInstanceByName (ctx context.Context , name string , searchRegions []string ) (* servers.Server , string , error ) {
252
+ opts := servers.ListOpts {
253
+ Name : fmt .Sprintf ("^%s$" , regexp .QuoteMeta (name )),
254
+ }
255
+
256
+ serverList := make ([]servers.Server , 0 , 1 )
257
+ mc := metrics .NewMetricContext ("server" , "list" )
258
+
259
+ for _ , r := range searchRegions {
260
+ pager := servers .List (i .compute [r ], opts )
261
+
262
+ err := pager .EachPage (ctx , func (_ context.Context , page pagination.Page ) (bool , error ) {
263
+ s , err := servers .ExtractServers (page )
264
+ if err != nil {
265
+ return false , err
266
+ }
267
+ serverList = append (serverList , s ... )
268
+ if len (serverList ) > 1 {
269
+ return false , errors .ErrMultipleResults
270
+ }
271
+ return true , nil
272
+ })
273
+ if mc .ObserveRequest (err ) != nil {
274
+ return nil , "" , err
275
+ }
276
+
277
+ if len (serverList ) == 0 {
278
+ continue
279
+ }
280
+
281
+ return & serverList [0 ], r , nil
282
+ }
283
+
284
+ return nil , "" , cloudprovider .InstanceNotFound
183
285
}
184
286
185
287
func getServerByName (ctx context.Context , client * gophercloud.ServiceClient , name string ) (* servers.Server , error ) {
0 commit comments