-
Notifications
You must be signed in to change notification settings - Fork 44
/
instance_utils.go
431 lines (371 loc) · 11.8 KB
/
instance_utils.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
package instance
import (
"encoding/json"
"fmt"
"sync"
"time"
"github.com/scaleway/scaleway-sdk-go/internal/async"
"github.com/scaleway/scaleway-sdk-go/internal/errors"
"github.com/scaleway/scaleway-sdk-go/scw"
)
var (
resourceLock sync.Map
)
// lockResource locks a resource from a specific resourceID
func lockResource(resourceID string) *sync.Mutex {
v, _ := resourceLock.LoadOrStore(resourceID, &sync.Mutex{})
mutex := v.(*sync.Mutex)
mutex.Lock()
return mutex
}
// lockServer locks a server from its zone and its ID
func lockServer(zone scw.Zone, serverID string) *sync.Mutex {
return lockResource(fmt.Sprint("server", zone, serverID))
}
// AttachIPRequest contains the parameters to attach an IP to a server
//
// Deprecated: UpdateIPRequest should be used instead
type AttachIPRequest struct {
Zone scw.Zone `json:"-"`
IP string `json:"-"`
ServerID string `json:"server_id"`
}
// AttachIPResponse contains the updated IP after attaching
//
// Deprecated: UpdateIPResponse should be used instead
type AttachIPResponse struct {
IP *IP
}
// AttachIP attaches an IP to a server.
//
// Deprecated: UpdateIP() should be used instead
func (s *API) AttachIP(req *AttachIPRequest, opts ...scw.RequestOption) (*AttachIPResponse, error) {
ipResponse, err := s.UpdateIP(&UpdateIPRequest{
Zone: req.Zone,
IP: req.IP,
Server: &NullableStringValue{Value: req.ServerID},
})
if err != nil {
return nil, err
}
return &AttachIPResponse{IP: ipResponse.IP}, nil
}
// DetachIPRequest contains the parameters to detach an IP from a server
//
// Deprecated: UpdateIPRequest should be used instead
type DetachIPRequest struct {
Zone scw.Zone `json:"-"`
IP string `json:"-"`
}
// DetachIPResponse contains the updated IP after detaching
//
// Deprecated: UpdateIPResponse should be used instead
type DetachIPResponse struct {
IP *IP
}
// DetachIP detaches an IP from a server.
//
// Deprecated: UpdateIP() should be used instead
func (s *API) DetachIP(req *DetachIPRequest, opts ...scw.RequestOption) (*DetachIPResponse, error) {
ipResponse, err := s.UpdateIP(&UpdateIPRequest{
Zone: req.Zone,
IP: req.IP,
Server: &NullableStringValue{Null: true},
})
if err != nil {
return nil, err
}
return &DetachIPResponse{IP: ipResponse.IP}, nil
}
// AttachVolumeRequest contains the parameters to attach a volume to a server
// Deprecated by AttachServerVolumeRequest
type AttachVolumeRequest struct {
Zone scw.Zone `json:"-"`
ServerID string `json:"-"`
VolumeID string `json:"-"`
}
// AttachVolumeResponse contains the updated server after attaching a volume
// Deprecated by AttachServerVolumeResponse
type AttachVolumeResponse struct {
Server *Server `json:"-"`
}
// volumesToVolumeTemplates converts a map of *Volume to a map of *VolumeTemplate
// so it can be used in a UpdateServer request
func volumesToVolumeTemplates(volumes map[string]*VolumeServer) map[string]*VolumeServerTemplate {
volumeTemplates := map[string]*VolumeServerTemplate{}
for key, volume := range volumes {
volumeTemplate := &VolumeServerTemplate{
ID: &volume.ID,
}
if volume.Name != "" {
volumeTemplate.Name = &volume.Name
}
if volume.VolumeType == VolumeServerVolumeTypeSbsVolume {
volumeTemplate.VolumeType = VolumeVolumeTypeSbsVolume
}
volumeTemplates[key] = volumeTemplate
}
return volumeTemplates
}
// AttachVolume attaches a volume to a server
//
// Note: Implementation is thread-safe.
// Deprecated by AttachServerVolume provided by instance API
func (s *API) AttachVolume(req *AttachVolumeRequest, opts ...scw.RequestOption) (*AttachVolumeResponse, error) {
defer lockServer(req.Zone, req.ServerID).Unlock()
// check where the volume comes from
volume, err := s.getUnknownVolume(&getUnknownVolumeRequest{
Zone: req.Zone,
VolumeID: req.VolumeID,
}, opts...)
if err != nil {
return nil, err
}
attachServerVolumeReq := &AttachServerVolumeRequest{
Zone: req.Zone,
ServerID: req.ServerID,
VolumeID: req.VolumeID,
VolumeType: AttachServerVolumeRequestVolumeType(volume.Type),
}
resp, err := s.AttachServerVolume(attachServerVolumeReq, opts...)
if err != nil {
return nil, err
}
return &AttachVolumeResponse{Server: resp.Server}, nil
}
// DetachVolumeRequest contains the parameters to detach a volume from a server
// Deprecated by DetachServerVolumeRequest
type DetachVolumeRequest struct {
Zone scw.Zone `json:"-"`
VolumeID string `json:"-"`
// IsBlockVolume should be set to true if volume is from block API,
// can be set to false if volume is from instance API,
// if left nil both API will be tried
IsBlockVolume *bool `json:"-"`
}
// DetachVolumeResponse contains the updated server after detaching a volume
// Deprecated by DetachServerVolumeResponse
type DetachVolumeResponse struct {
Server *Server `json:"-"`
}
// DetachVolume detaches a volume from a server
//
// Note: Implementation is thread-safe.
// Deprecated by DetachServerVolume provided by instance API
func (s *API) DetachVolume(req *DetachVolumeRequest, opts ...scw.RequestOption) (*DetachVolumeResponse, error) {
volume, err := s.getUnknownVolume(&getUnknownVolumeRequest{
Zone: req.Zone,
VolumeID: req.VolumeID,
}, opts...)
if err != nil {
return nil, err
}
if volume.ServerID == nil {
return nil, errors.New("volume should be attached to a server")
}
defer lockServer(req.Zone, *volume.ServerID).Unlock()
resp, err := s.DetachServerVolume(&DetachServerVolumeRequest{
Zone: req.Zone,
ServerID: *volume.ServerID,
VolumeID: volume.ID,
}, opts...)
if err != nil {
return nil, err
}
return &DetachVolumeResponse{Server: resp.Server}, nil
}
// UnsafeSetTotalCount should not be used
// Internal usage only
func (r *ListServersResponse) UnsafeSetTotalCount(totalCount int) {
r.TotalCount = uint32(totalCount)
}
// UnsafeSetTotalCount should not be used
// Internal usage only
func (r *ListBootscriptsResponse) UnsafeSetTotalCount(totalCount int) {
r.TotalCount = uint32(totalCount)
}
// UnsafeSetTotalCount should not be used
// Internal usage only
func (r *ListIPsResponse) UnsafeSetTotalCount(totalCount int) {
r.TotalCount = uint32(totalCount)
}
// UnsafeSetTotalCount should not be used
// Internal usage only
func (r *ListSecurityGroupRulesResponse) UnsafeSetTotalCount(totalCount int) {
r.TotalCount = uint32(totalCount)
}
// UnsafeSetTotalCount should not be used
// Internal usage only
func (r *ListSecurityGroupsResponse) UnsafeSetTotalCount(totalCount int) {
r.TotalCount = uint32(totalCount)
}
// UnsafeSetTotalCount should not be used
// Internal usage only
func (r *ListServersTypesResponse) UnsafeSetTotalCount(totalCount int) {
r.TotalCount = uint32(totalCount)
}
// UnsafeSetTotalCount should not be used
// Internal usage only
func (r *ListSnapshotsResponse) UnsafeSetTotalCount(totalCount int) {
r.TotalCount = uint32(totalCount)
}
// UnsafeSetTotalCount should not be used
// Internal usage only
func (r *ListVolumesResponse) UnsafeSetTotalCount(totalCount int) {
r.TotalCount = uint32(totalCount)
}
// UnsafeSetTotalCount should not be used
// Internal usage only
func (r *ListImagesResponse) UnsafeSetTotalCount(totalCount int) {
r.TotalCount = uint32(totalCount)
}
func (v *NullableStringValue) UnmarshalJSON(b []byte) error {
if string(b) == "null" {
v.Null = true
return nil
}
var tmp string
if err := json.Unmarshal(b, &tmp); err != nil {
return err
}
v.Null = false
v.Value = tmp
return nil
}
func (v *NullableStringValue) MarshalJSON() ([]byte, error) {
if v.Null {
return []byte("null"), nil
}
return json.Marshal(v.Value)
}
// WaitForPrivateNICRequest is used by WaitForPrivateNIC method.
type WaitForPrivateNICRequest struct {
ServerID string
PrivateNicID string
Zone scw.Zone
Timeout *time.Duration
RetryInterval *time.Duration
}
// WaitForPrivateNIC wait for the private network to be in a "terminal state" before returning.
// This function can be used to wait for the private network to be attached for example.
func (s *API) WaitForPrivateNIC(req *WaitForPrivateNICRequest, opts ...scw.RequestOption) (*PrivateNIC, error) {
timeout := defaultTimeout
if req.Timeout != nil {
timeout = *req.Timeout
}
retryInterval := defaultRetryInterval
if req.RetryInterval != nil {
retryInterval = *req.RetryInterval
}
terminalStatus := map[PrivateNICState]struct{}{
PrivateNICStateAvailable: {},
PrivateNICStateSyncingError: {},
}
pn, err := async.WaitSync(&async.WaitSyncConfig{
Get: func() (interface{}, bool, error) {
res, err := s.GetPrivateNIC(&GetPrivateNICRequest{
ServerID: req.ServerID,
Zone: req.Zone,
PrivateNicID: req.PrivateNicID,
}, opts...)
if err != nil {
return nil, false, err
}
_, isTerminal := terminalStatus[res.PrivateNic.State]
return res.PrivateNic, isTerminal, err
},
Timeout: timeout,
IntervalStrategy: async.LinearIntervalStrategy(retryInterval),
})
if err != nil {
return nil, errors.Wrap(err, "waiting for server failed")
}
return pn.(*PrivateNIC), nil
}
// WaitForMACAddressRequest is used by WaitForMACAddress method.
type WaitForMACAddressRequest struct {
ServerID string
PrivateNicID string
Zone scw.Zone
Timeout *time.Duration
RetryInterval *time.Duration
}
// WaitForMACAddress wait for the MAC address be assigned on instance before returning.
// This function can be used to wait for the private network to be attached for example.
func (s *API) WaitForMACAddress(req *WaitForMACAddressRequest, opts ...scw.RequestOption) (*PrivateNIC, error) {
timeout := defaultTimeout
if req.Timeout != nil {
timeout = *req.Timeout
}
retryInterval := defaultRetryInterval
if req.RetryInterval != nil {
retryInterval = *req.RetryInterval
}
pn, err := async.WaitSync(&async.WaitSyncConfig{
Get: func() (interface{}, bool, error) {
res, err := s.GetPrivateNIC(&GetPrivateNICRequest{
ServerID: req.ServerID,
Zone: req.Zone,
PrivateNicID: req.PrivateNicID,
}, opts...)
if err != nil {
return nil, false, err
}
if len(res.PrivateNic.MacAddress) > 0 {
return res.PrivateNic, true, err
}
return res.PrivateNic, false, err
},
Timeout: timeout,
IntervalStrategy: async.LinearIntervalStrategy(retryInterval),
})
if err != nil {
return nil, errors.Wrap(err, "waiting for server failed")
}
return pn.(*PrivateNIC), nil
}
// UnsafeSetTotalCount should not be used
// Internal usage only
func (r *GetServerTypesAvailabilityResponse) UnsafeSetTotalCount(totalCount int) {
r.TotalCount = uint32(totalCount)
}
// WaitForServerRDPPasswordRequest is used by WaitForServerRDPPassword method.
type WaitForServerRDPPasswordRequest struct {
ServerID string
Zone scw.Zone
Timeout *time.Duration
RetryInterval *time.Duration
}
// WaitForServerRDPPassword wait for an RDP password to be generated for an instance before returning.
// This function can be used to wait for a windows instance to boot up.
func (s *API) WaitForServerRDPPassword(req *WaitForServerRDPPasswordRequest, opts ...scw.RequestOption) (*Server, error) {
timeout := defaultTimeout
if req.Timeout != nil {
timeout = *req.Timeout
}
retryInterval := defaultRetryInterval
if req.RetryInterval != nil {
retryInterval = *req.RetryInterval
}
server, err := async.WaitSync(&async.WaitSyncConfig{
Get: func() (interface{}, bool, error) {
res, err := s.GetServer(&GetServerRequest{
ServerID: req.ServerID,
Zone: req.Zone,
}, opts...)
if err != nil {
return nil, false, err
}
if res.Server.AdminPasswordEncryptedValue != nil && *res.Server.AdminPasswordEncryptedValue != "" {
return res.Server, true, err
}
return res.Server, false, err
},
Timeout: timeout,
IntervalStrategy: async.LinearIntervalStrategy(retryInterval),
})
if err != nil {
return nil, errors.Wrap(err, "waiting for server failed")
}
return server.(*Server), nil
}