@@ -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,7 @@ 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
+ srv , region , err := i .getInstance (ctx , node )
119
139
if err != nil {
120
140
return nil , err
121
141
}
@@ -124,62 +144,138 @@ func (i *InstancesV2) InstanceMetadata(ctx context.Context, node *v1.Node) (*clo
124
144
server = * srv
125
145
}
126
146
127
- instanceType , err := srvInstanceType (ctx , i .compute , & server )
147
+ instanceType , err := srvInstanceType (ctx , i .compute [ region ] , & server )
128
148
if err != nil {
129
149
return nil , err
130
150
}
131
151
132
- ports , err := getAttachedPorts (ctx , i .network , server .ID )
152
+ ports , err := getAttachedPorts (ctx , i .network [ region ] , server .ID )
133
153
if err != nil {
134
154
return nil , err
135
155
}
136
156
137
- addresses , err := nodeAddresses (ctx , & server , ports , i .network , i .networkingOpts )
157
+ addresses , err := nodeAddresses (ctx , & server , ports , i .network [ region ] , i .networkingOpts )
138
158
if err != nil {
139
159
return nil , err
140
160
}
141
161
142
162
availabilityZone := util .SanitizeLabel (server .AvailabilityZone )
143
163
144
164
return & cloudprovider.InstanceMetadata {
145
- ProviderID : i .makeInstanceID (& server ),
165
+ ProviderID : i .makeInstanceID (& server , region ),
146
166
InstanceType : instanceType ,
147
167
NodeAddresses : addresses ,
148
168
Zone : availabilityZone ,
149
- Region : i . region ,
169
+ Region : region ,
150
170
}, nil
151
171
}
152
172
153
- func (i * InstancesV2 ) makeInstanceID (srv * servers.Server ) string {
173
+ func (i * InstancesV2 ) makeInstanceID (srv * servers.Server , region string ) string {
154
174
if i .regionProviderID {
155
- return fmt .Sprintf ("%s://%s/%s" , ProviderName , i . region , srv .ID )
175
+ return fmt .Sprintf ("%s://%s/%s" , ProviderName , region , srv .ID )
156
176
}
157
177
return fmt .Sprintf ("%s:///%s" , ProviderName , srv .ID )
158
178
}
159
179
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 )
163
- }
180
+ func (i * InstancesV2 ) getInstance (ctx context.Context , node * v1.Node ) (* servers.Server , string , error ) {
181
+ klog . V ( 4 ). InfoS ( "openstack.getInstance() called" , "node" , klog . KObj ( node ),
182
+ "providerID" , node .Spec . ProviderID ,
183
+ "region" , node . Labels [ v1 . LabelTopologyRegion ])
164
184
165
185
instanceID , instanceRegion , err := instanceIDFromProviderID (node .Spec .ProviderID )
166
186
if err != nil {
167
- return nil , err
187
+ return nil , "" , err
168
188
}
169
189
170
- if instanceRegion != "" && instanceRegion != i .region {
171
- return nil , fmt .Errorf ("ProviderID \" %s\" didn't match supported region \" %s\" " , node .Spec .ProviderID , i .region )
190
+ if instanceRegion != "" {
191
+ if slices .Contains (i .regions , instanceRegion ) {
192
+ return i .getInstanceByID (ctx , instanceID , []string {instanceRegion })
193
+ }
194
+
195
+ return nil , "" , fmt .Errorf ("getInstance: ProviderID \" %s\" didn't match supported regions \" %s\" " , node .Spec .ProviderID , strings .Join (i .regions , "," ))
172
196
}
173
197
198
+ // At this point we know that ProviderID is not properly set or it doesn't contain region information
199
+ // We need to search for the instance in all regions
200
+ var searchRegions []string
201
+
202
+ // We cannot trust the region label, so we need to check the region
203
+ instanceRegion = node .Labels [v1 .LabelTopologyRegion ]
204
+ if slices .Contains (i .regions , instanceRegion ) {
205
+ searchRegions = []string {instanceRegion }
206
+ }
207
+
208
+ for _ , r := range i .regions {
209
+ if r != instanceRegion {
210
+ searchRegions = append (searchRegions , r )
211
+ }
212
+ }
213
+
214
+ klog .V (6 ).InfoS ("openstack.getInstance() trying to find the instance in regions" , "node" , klog .KObj (node ),
215
+ "instanceID" , instanceID ,
216
+ "regions" , strings .Join (searchRegions , "," ))
217
+
218
+ if instanceID == "" {
219
+ return i .getInstanceByName (ctx , node .Name , searchRegions )
220
+ }
221
+
222
+ return i .getInstanceByID (ctx , instanceID , searchRegions )
223
+ }
224
+
225
+ func (i * InstancesV2 ) getInstanceByID (ctx context.Context , instanceID string , searchRegions []string ) (* servers.Server , string , error ) {
226
+ server := servers.Server {}
227
+
174
228
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
229
+ for _ , r := range searchRegions {
230
+ err := servers .Get (ctx , i .compute [r ], instanceID ).ExtractInto (& server )
231
+ if mc .ObserveRequest (err ) != nil {
232
+ if errors .IsNotFound (err ) {
233
+ continue
234
+ }
235
+
236
+ return nil , "" , err
179
237
}
180
- return nil , err
238
+
239
+ return & server , r , nil
240
+ }
241
+
242
+ return nil , "" , cloudprovider .InstanceNotFound
243
+ }
244
+
245
+ func (i * InstancesV2 ) getInstanceByName (ctx context.Context , name string , searchRegions []string ) (* servers.Server , string , error ) {
246
+ opts := servers.ListOpts {
247
+ Name : fmt .Sprintf ("^%s$" , regexp .QuoteMeta (name )),
248
+ }
249
+
250
+ serverList := make ([]servers.Server , 0 , 1 )
251
+ mc := metrics .NewMetricContext ("server" , "list" )
252
+
253
+ for _ , r := range searchRegions {
254
+ pager := servers .List (i .compute [r ], opts )
255
+
256
+ err := pager .EachPage (ctx , func (_ context.Context , page pagination.Page ) (bool , error ) {
257
+ s , err := servers .ExtractServers (page )
258
+ if err != nil {
259
+ return false , err
260
+ }
261
+ serverList = append (serverList , s ... )
262
+ if len (serverList ) > 1 {
263
+ return false , errors .ErrMultipleResults
264
+ }
265
+ return true , nil
266
+ })
267
+ if mc .ObserveRequest (err ) != nil {
268
+ return nil , "" , err
269
+ }
270
+
271
+ if len (serverList ) == 0 {
272
+ continue
273
+ }
274
+
275
+ return & serverList [0 ], r , nil
181
276
}
182
- return server , nil
277
+
278
+ return nil , "" , cloudprovider .InstanceNotFound
183
279
}
184
280
185
281
func getServerByName (ctx context.Context , client * gophercloud.ServiceClient , name string ) (* servers.Server , error ) {
0 commit comments