/
securitygroupunsafe.go
executable file
·470 lines (404 loc) · 15.7 KB
/
securitygroupunsafe.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
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
/*
* Copyright 2018-2022, CS Systemes d'Information, http://csgroup.eu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package operations
import (
"context"
"reflect"
"strings"
"github.com/CS-SI/SafeScale/v21/lib/server/resources"
"github.com/CS-SI/SafeScale/v21/lib/server/resources/abstract"
"github.com/CS-SI/SafeScale/v21/lib/server/resources/enums/networkproperty"
"github.com/CS-SI/SafeScale/v21/lib/server/resources/enums/securitygroupproperty"
propertiesv1 "github.com/CS-SI/SafeScale/v21/lib/server/resources/properties/v1"
"github.com/CS-SI/SafeScale/v21/lib/utils/concurrency"
"github.com/CS-SI/SafeScale/v21/lib/utils/data"
"github.com/CS-SI/SafeScale/v21/lib/utils/data/serialize"
"github.com/CS-SI/SafeScale/v21/lib/utils/debug"
"github.com/CS-SI/SafeScale/v21/lib/utils/fail"
"github.com/CS-SI/SafeScale/v21/lib/utils/strprocess"
"github.com/CS-SI/SafeScale/v21/lib/utils/valid"
)
// delete effectively remove a Security Group
func (instance *SecurityGroup) unsafeDelete(ctx context.Context, force bool) fail.Error {
task, xerr := concurrency.TaskFromContextOrVoid(ctx)
xerr = debug.InjectPlannedFail(xerr)
if xerr != nil {
return xerr
}
if task.Aborted() {
return fail.AbortedError(nil, "aborted")
}
var (
abstractSG *abstract.SecurityGroup
networkID string
)
value := ctx.Value(CurrentNetworkAbstractContextKey)
if value != nil {
castedValue, ok := value.(*abstract.Network)
if !ok {
return fail.InconsistentError("failed to cast value to '*abstract.Network'")
}
networkID = castedValue.ID
}
xerr = instance.Alter(func(clonable data.Clonable, props *serialize.JSONProperties) fail.Error {
var ok bool
abstractSG, ok = clonable.(*abstract.SecurityGroup)
if !ok {
return fail.InconsistentError("'*abstract.SecurityGroup' expected, '%s' provided", reflect.TypeOf(clonable).String())
}
if networkID == "" {
networkID = abstractSG.Network
}
if !force {
// check bonds to hosts
innerXErr := props.Inspect(securitygroupproperty.HostsV1, func(clonable data.Clonable) fail.Error {
hostsV1, ok := clonable.(*propertiesv1.SecurityGroupHosts)
if !ok {
return fail.InconsistentError("'*propertiesv1.SecurityGroupHosts' expected, '%s' provided", reflect.TypeOf(clonable).String())
}
// Do not remove a SecurityGroup used on hosts
hostCount := len(hostsV1.ByName)
if hostCount > 0 {
keys := make([]string, 0, hostCount)
for k := range hostsV1.ByName {
keys = append(keys, k)
}
return fail.NotAvailableError("security group '%s' is currently bound to %d host%s: %s", instance.GetName(), hostCount, strprocess.Plural(uint(hostCount)), strings.Join(keys, ","))
}
// Do not remove a Security Group marked as default for a host
if hostsV1.DefaultFor != "" {
return fail.InvalidRequestError("failed to delete Security Group '%s': is default for host identified by %s", hostsV1.DefaultFor)
}
return nil
})
if innerXErr != nil {
return innerXErr
}
if task.Aborted() {
return fail.AbortedError(nil, "aborted")
}
// check bonds to subnets
innerXErr = props.Inspect(securitygroupproperty.SubnetsV1, func(clonable data.Clonable) fail.Error {
subnetsV1, ok := clonable.(*propertiesv1.SecurityGroupSubnets)
if !ok {
return fail.InconsistentError("'*propertiesv1.SecurityGroupSubnets' expected, '%s' provided", reflect.TypeOf(clonable).String())
}
// Do not remove a SecurityGroup used on subnet(s)
subnetCount := len(subnetsV1.ByID)
if subnetCount > 0 {
keys := make([]string, subnetCount)
for k := range subnetsV1.ByName {
if task.Aborted() {
return fail.AbortedError(nil, "aborted")
}
keys = append(keys, k)
}
return fail.NotAvailableError("security group is currently bound to %d subnet%s: %s", subnetCount, strprocess.Plural(uint(subnetCount)), strings.Join(keys, ","))
}
// Do not remove a Security Group marked as default for a subnet
if subnetsV1.DefaultFor != "" {
return fail.InvalidRequestError("failed to delete SecurityGroup '%s': is default for Subnet identified by '%s'", abstractSG.Name, subnetsV1.DefaultFor)
}
return nil
})
if innerXErr != nil {
return innerXErr
}
}
if task.Aborted() {
return fail.AbortedError(nil, "aborted")
}
// FIXME: how to restore bindings in case of failure or abortion ? This would prevent the use of DisarmAbortSignal here...
// defer task.DisarmAbortSignal()()
// unbind from Subnets (which will unbind from Hosts attached to these Subnets...)
innerXErr := props.Alter(securitygroupproperty.SubnetsV1, func(clonable data.Clonable) fail.Error {
sgnV1, ok := clonable.(*propertiesv1.SecurityGroupSubnets)
if !ok {
return fail.InconsistentError("'*propertiesv1.SecurityGroupSubnets' expected, '%s' provided", reflect.TypeOf(clonable).String())
}
return instance.unbindFromSubnets(ctx, sgnV1)
})
if innerXErr != nil {
return innerXErr
}
// unbind from the Hosts if there are remaining ones
innerXErr = props.Alter(securitygroupproperty.HostsV1, func(clonable data.Clonable) fail.Error {
sghV1, ok := clonable.(*propertiesv1.SecurityGroupHosts)
if !ok {
return fail.InconsistentError("'*propertiesv1.SecurityGroupHosts' expected, '%s' provided", reflect.TypeOf(clonable).String())
}
return instance.unbindFromHosts(ctx, sghV1)
})
if innerXErr != nil {
return innerXErr
}
// delete SecurityGroup resource
return deleteProviderSecurityGroup(instance.Service(), abstractSG)
})
if xerr != nil {
return xerr
}
// delete Security Group metadata
xerr = instance.MetadataCore.Delete()
if xerr != nil {
return xerr
}
// delete Security Groups in Network metadata if the current operation is not to remove this Network (otherwise may deadlock)
removingNetworkAbstract := ctx.Value(CurrentNetworkAbstractContextKey)
if removingNetworkAbstract == nil {
return instance.updateNetworkMetadataOnRemoval(ctx, networkID)
}
return nil
}
// updateNetworkMetadataOnRemoval removes the reference to instance in Network metadata
func (instance *SecurityGroup) updateNetworkMetadataOnRemoval(ctx context.Context, networkID string) fail.Error {
// -- update Security Groups in Network metadata
networkInstance, xerr := LoadNetwork(ctx, instance.Service(), networkID)
if xerr != nil {
return xerr
}
return networkInstance.Alter(func(_ data.Clonable, props *serialize.JSONProperties) fail.Error {
return props.Alter(networkproperty.SecurityGroupsV1, func(clonable data.Clonable) fail.Error {
nsgV1, ok := clonable.(*propertiesv1.NetworkSecurityGroups)
if !ok {
return fail.InconsistentError("'*propertiesv1.NetworkSecurityGroups' expected, '%s' provided", reflect.TypeOf(clonable).String())
}
delete(nsgV1.ByID, instance.GetID())
delete(nsgV1.ByName, instance.GetName())
return nil
})
})
}
// unsafeClear is the non goroutine-safe implementation for Clear, that does the real work faster (no locking, less if no parameter validations)
// Note: must be used wisely
func (instance *SecurityGroup) unsafeClear() fail.Error {
return instance.Alter(func(clonable data.Clonable, props *serialize.JSONProperties) fail.Error {
asg, ok := clonable.(*abstract.SecurityGroup)
if !ok {
return fail.InconsistentError("'*abstract.SecurityGroup' expected, '%s' provided", reflect.TypeOf(clonable).String())
}
_, innerXErr := instance.Service().ClearSecurityGroup(asg)
return innerXErr
})
}
// unsafeAddRule adds a rule to a security group
func (instance *SecurityGroup) unsafeAddRule(rule *abstract.SecurityGroupRule) (ferr fail.Error) {
defer fail.OnPanic(&ferr)
xerr := rule.Validate()
if xerr != nil {
return xerr
}
return instance.Alter(func(clonable data.Clonable, _ *serialize.JSONProperties) fail.Error {
asg, ok := clonable.(*abstract.SecurityGroup)
if !ok {
return fail.InconsistentError("'*abstract.SecurityGroup' expected, '%s' provided", reflect.TypeOf(clonable).String())
}
_, innerXErr := instance.Service().AddRuleToSecurityGroup(asg, rule)
if innerXErr != nil {
return innerXErr
}
// asg.Replace(newAsg)
return nil
})
}
// unsafeUnbindFromSubnet unbinds the security group from a subnet
func (instance *SecurityGroup) unsafeUnbindFromSubnet(ctx context.Context, params taskUnbindFromHostsAttachedToSubnetParams) (ferr fail.Error) {
defer fail.OnPanic(&ferr)
if instance == nil || valid.IsNil(instance) {
return fail.InvalidInstanceError()
}
if ctx == nil {
return fail.InvalidParameterCannotBeNilError("ctx")
}
task, xerr := concurrency.TaskFromContextOrVoid(ctx)
xerr = debug.InjectPlannedFail(xerr)
if xerr != nil {
return xerr
}
if task.Aborted() {
return fail.AbortedError(nil, "aborted")
}
// Unbind Security Group from Hosts attached to Subnet
_, xerr = task.Run(instance.taskUnbindFromHostsAttachedToSubnet, params)
if xerr != nil {
return xerr
}
// Update instance metadata
return instance.Alter(func(_ data.Clonable, props *serialize.JSONProperties) fail.Error {
return props.Alter(securitygroupproperty.SubnetsV1, func(clonable data.Clonable) fail.Error {
sgsV1, ok := clonable.(*propertiesv1.SecurityGroupSubnets)
if !ok {
return fail.InconsistentError("'*securitygroupproperty.SubnetsV1' expected, '%s' provided", reflect.TypeOf(clonable).String())
}
innerXErr := instance.Service().UnbindSecurityGroupFromSubnet(instance.GetID(), params.subnetID)
if innerXErr != nil {
switch innerXErr.(type) {
case *fail.ErrNotFound:
// consider a Security Group not found as a successful unbind, and continue to update metadata
debug.IgnoreError(innerXErr)
default:
return innerXErr
}
}
// updates security group metadata
delete(sgsV1.ByID, params.subnetID)
delete(sgsV1.ByName, params.subnetName)
return nil
})
})
}
// unsafeBindToSubnet binds the security group to a host
// This method is called assuming Subnet resource is locked (so do not used the resource directly to prevent deadlock
func (instance *SecurityGroup) unsafeBindToSubnet(ctx context.Context, abstractSubnet *abstract.Subnet, subnetHosts *propertiesv1.SubnetHosts, enable resources.SecurityGroupActivation, mark resources.SecurityGroupMark) (ferr fail.Error) {
defer fail.OnPanic(&ferr)
if instance == nil || valid.IsNil(instance) {
return fail.InvalidInstanceError()
}
if ctx == nil {
return fail.InvalidParameterCannotBeNilError("ctx")
}
if abstractSubnet == nil {
return fail.InvalidParameterCannotBeNilError("abstractSubnet")
}
if subnetHosts == nil {
return fail.InvalidParameterCannotBeNilError("subnetProps")
}
task, xerr := concurrency.TaskFromContextOrVoid(ctx)
xerr = debug.InjectPlannedFail(xerr)
if xerr != nil {
return xerr
}
if task.Aborted() {
return fail.AbortedError(nil, "aborted")
}
// instance.lock.Lock()
// defer instance.lock.Unlock()
switch enable {
case resources.SecurityGroupEnable:
xerr = instance.enableOnHostsAttachedToSubnet(task, subnetHosts)
case resources.SecurityGroupDisable:
xerr = instance.disableOnHostsAttachedToSubnet(task, subnetHosts)
}
xerr = debug.InjectPlannedFail(xerr)
if xerr != nil {
return xerr
}
return instance.Alter(func(clonable data.Clonable, props *serialize.JSONProperties) fail.Error {
if mark == resources.MarkSecurityGroupAsDefault {
asg, ok := clonable.(*abstract.SecurityGroup)
if !ok {
return fail.InconsistentError("'*abstract.SecurityGroup' expected, '%s' provided", reflect.TypeOf(clonable).String())
}
if asg.DefaultForHost != "" {
return fail.InvalidRequestError("security group is already marked as default for subnet %s", asg.DefaultForSubnet)
}
asg.DefaultForSubnet = abstractSubnet.ID
}
return props.Alter(securitygroupproperty.SubnetsV1, func(clonable data.Clonable) fail.Error {
sgsV1, ok := clonable.(*propertiesv1.SecurityGroupSubnets)
if !ok {
return fail.InconsistentError("'*propertiesv1.SecurityGroupSubnets' expected, '%s' provided", reflect.TypeOf(clonable).String())
}
// First check if subnet is present with the state requested; if present with same state, consider situation as a success
disable := !bool(enable)
if item, ok := sgsV1.ByID[abstractSubnet.ID]; !ok || item.Disabled == disable {
item = &propertiesv1.SecurityGroupBond{
ID: abstractSubnet.ID,
Name: abstractSubnet.Name,
}
sgsV1.ByID[abstractSubnet.ID] = item
sgsV1.ByName[abstractSubnet.Name] = abstractSubnet.ID
}
// updates security group properties
sgsV1.ByID[abstractSubnet.ID].Disabled = disable
return nil
})
})
}
// unsafeBindToHost binds the security group to a host.
// instance is not locked, it must have been done outside to prevent data races
func (instance *SecurityGroup) unsafeBindToHost(ctx context.Context, hostInstance resources.Host, enable resources.SecurityGroupActivation, mark resources.SecurityGroupMark) (ferr fail.Error) {
defer fail.OnPanic(&ferr)
task, xerr := concurrency.TaskFromContextOrVoid(ctx)
xerr = debug.InjectPlannedFail(xerr)
if xerr != nil {
return xerr
}
if task.Aborted() {
return fail.AbortedError(nil, "aborted")
}
return instance.Alter(func(clonable data.Clonable, props *serialize.JSONProperties) fail.Error {
if mark == resources.MarkSecurityGroupAsDefault {
asg, ok := clonable.(*abstract.SecurityGroup)
if !ok {
return fail.InconsistentError("'*abstract.SecurityGroup' expected, '%s' provided", reflect.TypeOf(clonable).String())
}
if asg.DefaultForHost != "" {
return fail.InvalidRequestError("security group is already marked as default for host %s", asg.DefaultForHost)
}
asg.DefaultForHost = hostInstance.GetID()
}
return props.Alter(securitygroupproperty.HostsV1, func(clonable data.Clonable) fail.Error {
sghV1, ok := clonable.(*propertiesv1.SecurityGroupHosts)
if !ok {
return fail.InconsistentError("'*propertiesv1.SecurityGroupHosts' expected, '%s' provided", reflect.TypeOf(clonable).String())
}
// First check if host is present; if not present or state is different, replace the entry
hostID := hostInstance.GetID()
hostName := hostInstance.GetName()
disable := !bool(enable)
if item, ok := sghV1.ByID[hostID]; !ok || item.Disabled == disable {
item = &propertiesv1.SecurityGroupBond{
ID: hostID,
Name: hostName,
}
sghV1.ByID[hostID] = item
sghV1.ByName[hostName] = hostID
}
// update the state
sghV1.ByID[hostID].Disabled = disable
switch enable {
case resources.SecurityGroupEnable:
// In case the security group is already bound, we must consider a "duplicate" error has a success
xerr := instance.Service().BindSecurityGroupToHost(instance.GetID(), hostID)
xerr = debug.InjectPlannedFail(xerr)
if xerr != nil {
switch xerr.(type) {
case *fail.ErrDuplicate:
debug.IgnoreError(xerr)
// continue
default:
return xerr
}
}
case resources.SecurityGroupDisable:
// In case the security group has to be disabled, we must consider a "not found" error has a success
xerr := instance.Service().UnbindSecurityGroupFromHost(instance.GetID(), hostID)
xerr = debug.InjectPlannedFail(xerr)
if xerr != nil {
switch xerr.(type) {
case *fail.ErrNotFound:
debug.IgnoreError(xerr)
// continue
default:
return xerr
}
}
}
return nil
})
})
}