-
Notifications
You must be signed in to change notification settings - Fork 281
Expand file tree
/
Copy pathontap_common.go
More file actions
5590 lines (4815 loc) · 193 KB
/
Copy pathontap_common.go
File metadata and controls
5590 lines (4815 loc) · 193 KB
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
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright 2025 NetApp, Inc. All Rights Reserved.
package ontap
import (
"bytes"
"context"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"html/template"
"maps"
"math/rand"
"net"
"os"
"reflect"
"regexp"
"runtime/debug"
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/RoaringBitmap/roaring/v2"
"github.com/cenkalti/backoff/v4"
"github.com/google/go-cmp/cmp"
"github.com/google/uuid"
tridentconfig "github.com/netapp/trident/config"
"github.com/netapp/trident/internal/fiji"
. "github.com/netapp/trident/logging"
"github.com/netapp/trident/pkg/capacity"
"github.com/netapp/trident/pkg/collection"
"github.com/netapp/trident/pkg/convert"
"github.com/netapp/trident/pkg/locks"
"github.com/netapp/trident/pkg/network"
"github.com/netapp/trident/storage"
sa "github.com/netapp/trident/storage_attribute"
sc "github.com/netapp/trident/storage_class"
drivers "github.com/netapp/trident/storage_drivers"
"github.com/netapp/trident/storage_drivers/ontap/api"
"github.com/netapp/trident/storage_drivers/ontap/api/azgo"
"github.com/netapp/trident/storage_drivers/ontap/api/rest/models"
"github.com/netapp/trident/utils/devices/luks"
"github.com/netapp/trident/utils/errors"
"github.com/netapp/trident/utils/fcp"
"github.com/netapp/trident/utils/filesystem"
"github.com/netapp/trident/utils/iscsi"
tridentmodels "github.com/netapp/trident/utils/models"
"github.com/netapp/trident/utils/version"
)
// //////////////////////////////////////////////////////////////////////////////////////////
// / _____________________
// / | <<Interface>> |
// / | ONTAPI |
// / |____________________|
// / ^ ^
// / Implements | | Implements
// / ____________________ ____________________
// / | ONTAPAPIREST | | ONTAPAPIZAPI |
// / |___________________| |___________________|
// / | +API: RestClient | | +API: *Client |
// / |___________________| |___________________|
// /
// //////////////////////////////////////////////////////////////////////////////////////////
// //////////////////////////////////////////////////////////////////////////////////////////
// Drivers that offer dual support are to call ONTAP REST or ZAPI's
// via abstraction layer (ONTAPI interface)
// //////////////////////////////////////////////////////////////////////////////////////////
const (
MinimumVolumeSizeBytes = 20971520 // 20 MiB
HousekeepingStartupDelay = 10 * time.Second
LUNMetadataBufferMultiplier = 1.1 // 10%
MaximumIgroupNameLength = 96 // 96 characters is the maximum character count for ONTAP igroups.
ADAdminUserPermission = "full_control" // AD admin user permission
DefaultSMBAccessControlUser = "Everyone" // Default SMB access control user
DefaultSMBAccessControlUserType = "windows" // Default SMB access control user type
// Constants for internal pool attributes
Size = "size"
NameTemplate = "nameTemplate"
Region = "region"
Zone = "zone"
Media = "media"
SpaceAllocation = "spaceAllocation"
SnapshotDir = "snapshotDir"
SpaceReserve = "spaceReserve"
SnapshotPolicy = "snapshotPolicy"
SnapshotReserve = "snapshotReserve"
PreserveUnlink = "preserveUnlink"
UnixPermissions = "unixPermissions"
ExportPolicy = "exportPolicy"
SecurityStyle = "securityStyle"
BackendType = "backendType"
Replication = "replication"
Snapshots = "snapshots"
Clones = "clones"
Encryption = "encryption"
LUKSEncryption = "LUKSEncryption"
FileSystemType = "fileSystemType"
FormatOptions = "formatOptions"
ProvisioningType = "provisioningType"
SplitOnClone = "splitOnClone"
TieringPolicy = "tieringPolicy"
SkipRecoveryQueue = "skipRecoveryQueue"
QosPolicy = "qosPolicy"
AdaptiveQosPolicy = "adaptiveQosPolicy"
ADAdminUser = "adAdminUser"
maxFlexGroupCloneWait = 120 * time.Second
maxFlexvolCloneWait = 30 * time.Second
// maxSnapshotDeleteRetry and maxSnapshotDeleteWait should be used
// together to balance snapshot deletion wait times and retries.
maxSnapshotDeleteRetry = 10
// maxSnapshotDeleteWait and maxSnapshotDeleteRetry should be used
// together to balance snapshot deletion wait times and retries.
maxSnapshotDeleteWait = 60 * time.Second
VolTypeRW = "rw" // read-write
VolTypeLS = "ls" // load-sharing
VolTypeDP = "dp" // data-protection
VolTypeDC = "dc" // data-cache
VolTypeTMP = "tmp" // temporary
)
// For legacy reasons, these strings mustn't change
const (
artifactPrefixDocker = "ndvp"
artifactPrefixKubernetes = "trident"
LUNAttributeFSType = "com.netapp.ndvp.fstype"
// Default storage pool name when aggregates are managed automatically
managedStoragePoolName = "managed_storage_pool"
)
// StateReason, Change in these strings require change in test automation.
const (
StateReasonSVMStopped = "SVM is not in 'running' state"
StateReasonDataLIFsDown = "No data LIFs present or all of them are 'down'"
StateReasonSVMUnreachable = "SVM is not reachable"
StateReasonNoAggregates = "SVM does not contain any aggregates."
StateReasonMissingAggregate = "Aggregate defined in the aggregate field of the backend config is not present in the SVM"
StateReasonMissingFlexGroupAggregates = "Some of the aggregates defined in the flexgroupAggregateList field of the backend config are not present in the SVM"
)
var (
volumeCharRegex = regexp.MustCompile(`[^a-zA-Z0-9_]`)
volumeNameRegex = regexp.MustCompile(`\{+.*\.volume.Name[^{a-z]*\}+`)
volumeNameStartWithRegex = regexp.MustCompile(`^[A-Za-z_].*`)
smbShareDeleteACL = map[string]string{DefaultSMBAccessControlUser: DefaultSMBAccessControlUserType}
exportPolicyMutex = locks.NewGCNamedMutex()
// NOTE: The lock order should be lunMutex first, then igroupMutex
lunMutex = locks.NewGCNamedMutex()
igroupMutex = locks.NewGCNamedMutex()
// NOTE: The lock order should be namespaceMutex first, then subsystemMutex
namespaceMutex = locks.NewGCNamedMutex()
subsystemMutex = locks.NewGCNamedMutex()
duringVolCloneAfterSnapCreation1 = fiji.Register("duringVolCloneAfterSnapCreation1", "ontap_common")
duringVolCloneAfterSnapCreation2 = fiji.Register("duringVolCloneAfterSnapCreation2", "ontap_common")
)
// CleanBackendName removes brackets and replaces colons with periods to avoid regex parsing errors.
func CleanBackendName(backendName string) string {
backendName = strings.ReplaceAll(backendName, "[", "")
backendName = strings.ReplaceAll(backendName, "]", "")
return strings.ReplaceAll(backendName, ":", ".")
}
func NewOntapTelemetry(ctx context.Context, d StorageDriver) *Telemetry {
config := d.GetOntapConfig()
t := &Telemetry{
Plugin: d.Name(),
SVM: config.SVM,
StoragePrefix: *config.StoragePrefix,
Driver: d,
done: make(chan struct{}),
}
usageHeartbeat := config.UsageHeartbeat
heartbeatIntervalInHours := 24.0 // default to 24 hours
if usageHeartbeat != "" {
f, err := strconv.ParseFloat(usageHeartbeat, 64)
if err != nil {
Logc(ctx).WithField("interval", usageHeartbeat).Warnf("Invalid heartbeat interval. %v", err)
} else {
heartbeatIntervalInHours = f
}
}
Logc(ctx).WithField("intervalHours", heartbeatIntervalInHours).Debug("Configured EMS heartbeat.")
durationInHours := time.Millisecond * time.Duration(MSecPerHour*heartbeatIntervalInHours)
if durationInHours > 0 {
t.ticker = time.NewTicker(durationInHours)
}
return t
}
// Start starts the flow of ASUP messages for the driver
// These messages can be viewed via filer::> event log show -severity NOTICE.
func (t *Telemetry) Start(ctx context.Context) {
go func() {
time.Sleep(HousekeepingStartupDelay)
EMSHeartbeat(ctx, t.Driver)
for {
select {
case tick := <-t.ticker.C:
Logc(ctx).WithFields(LogFields{
"tick": tick,
"driver": t.Driver.Name(),
}).Debug("Sending EMS heartbeat.")
EMSHeartbeat(ctx, t.Driver)
case <-t.done:
Logc(ctx).WithFields(LogFields{
"driver": t.Driver.Name(),
}).Debugf("Shut down EMS logs for the driver.")
return
}
}
}()
}
func (t *Telemetry) Stop() {
if t.ticker != nil {
t.ticker.Stop()
}
if !t.stopped {
// calling close on an already closed channel causes a panic, guard against that
close(t.done)
t.stopped = true
}
}
// String makes Telemetry satisfy the Stringer interface.
func (t Telemetry) String() (out string) {
defer func() {
if r := recover(); r != nil {
Log().Errorf("Panic in Telemetry#ToString; err: %v", r)
out = "<panic>"
}
}()
elements := reflect.ValueOf(&t).Elem()
var output strings.Builder
for i := 0; i < elements.NumField(); i++ {
fieldName := elements.Type().Field(i).Name
switch fieldName {
case "Driver":
output.WriteString(fmt.Sprintf("%v:%v", "Telemetry.Driver.Name", t.Driver.Name()))
default:
output.WriteString(fmt.Sprintf("%v:%v ", fieldName, elements.Field(i)))
}
}
out = output.String()
return
}
// GoString makes Telemetry satisfy the GoStringer interface.
func (t Telemetry) GoString() string {
return t.String()
}
func deleteExportPolicy(ctx context.Context, policy string, clientAPI api.OntapAPI) error {
err := clientAPI.ExportPolicyDestroy(ctx, policy)
if err != nil {
err = fmt.Errorf("error deleting export policy: %s", err.Error())
}
return err
}
// InitializeOntapConfig parses the ONTAP config, mixing in the specified common config.
func InitializeOntapConfig(
ctx context.Context, driverContext tridentconfig.DriverContext, configJSON string,
commonConfig *drivers.CommonStorageDriverConfig, backendSecret map[string]string,
) (*drivers.OntapStorageDriverConfig, error) {
fields := LogFields{"Method": "InitializeOntapConfig", "Type": "ontap_common"}
Logd(ctx, commonConfig.StorageDriverName,
commonConfig.DebugTraceFlags["method"]).WithFields(fields).Trace(">>>> InitializeOntapConfig")
defer Logd(ctx, commonConfig.StorageDriverName,
commonConfig.DebugTraceFlags["method"]).WithFields(fields).Trace("<<<< InitializeOntapConfig")
commonConfig.DriverContext = driverContext
config := &drivers.OntapStorageDriverConfig{}
config.CommonStorageDriverConfig = commonConfig
// decode configJSON into OntapStorageDriverConfig object
err := json.Unmarshal([]byte(configJSON), &config)
if err != nil {
return nil, fmt.Errorf("could not decode JSON configuration: %v", err)
}
// Inject secret if not empty
if len(backendSecret) != 0 {
err = config.InjectSecrets(backendSecret)
if err != nil {
return nil, fmt.Errorf("could not inject backend secret; err: %v", err)
}
}
// Ensure only one authentication type is specified in the backend config
if config.ClientPrivateKey != "" && config.Username != "" {
return nil, fmt.Errorf("more than one authentication method (username/password and clientPrivateKey)" +
" present in backend config; please ensure only one authentication method is provided")
}
return config, nil
}
func ensureExportPolicyExists(ctx context.Context, policyName string, clientAPI api.OntapAPI) error {
exportPolicyMutex.Lock(policyName)
defer exportPolicyMutex.Unlock(policyName)
return clientAPI.ExportPolicyCreate(ctx, policyName)
}
func destroyExportPolicy(ctx context.Context, policyName string, clientAPI api.OntapAPI) error {
exportPolicyMutex.Lock(policyName)
defer exportPolicyMutex.Unlock(policyName)
return clientAPI.ExportPolicyDestroy(ctx, policyName)
}
func getExportPolicyName(backendUUID string) string {
return fmt.Sprintf("trident-%s", backendUUID)
}
func getEmptyExportPolicyName(storagePrefix string) string {
return fmt.Sprintf("%sempty", storagePrefix)
}
// ensureNodeAccess check to see if the export policy exists and if not it will create it and force a reconcile.
// This should be used during publish to make sure access is available if the policy has somehow been deleted.
// Otherwise we should not need to reconcile, which could be expensive.
func ensureNodeAccess(
ctx context.Context, publishInfo *tridentmodels.VolumePublishInfo, clientAPI api.OntapAPI,
config *drivers.OntapStorageDriverConfig,
) error {
policyName := getExportPolicyName(publishInfo.BackendUUID)
if exists, err := clientAPI.ExportPolicyExists(ctx, policyName); err != nil {
return err
} else if !exists {
Logc(ctx).WithField("exportPolicy", policyName).Debug("Export policy missing, will create it.")
return reconcileNASNodeAccess(ctx, publishInfo.Nodes, config, clientAPI, policyName)
}
Logc(ctx).WithField("exportPolicy", policyName).Debug("Export policy exists.")
return nil
}
func reconcileNASNodeAccess(
ctx context.Context, nodes []*tridentmodels.Node, config *drivers.OntapStorageDriverConfig, clientAPI api.OntapAPI,
policyName string,
) error {
if !config.AutoExportPolicy {
return nil
}
err := ensureExportPolicyExists(ctx, policyName, clientAPI)
if err != nil {
return err
}
desiredRules, err := getDesiredExportPolicyRules(ctx, nodes, config)
if err != nil {
err = fmt.Errorf("unable to determine desired export policy rules; %v", err)
Logc(ctx).Error(err)
return err
}
err = reconcileExportPolicyRules(ctx, policyName, desiredRules, clientAPI, config)
if err != nil {
err = fmt.Errorf("unable to reconcile export policy rules; %v", err)
Logc(ctx).WithField("ExportPolicy", policyName).Error(err)
return err
}
return nil
}
// ensureNodeAccessForPolicy check to see if export policy exists and if not it will create it.
// Add the desired rule(s) to the policy.
func ensureNodeAccessForPolicy(
ctx context.Context, targetNode *tridentmodels.Node, clientAPI api.OntapAPI,
config *drivers.OntapStorageDriverConfig, policyName string,
) error {
fields := LogFields{
"Method": "ensureNodeAccessForPolicy",
"Type": "ontap_common",
"policyName": policyName,
"targetNodeIPs": targetNode.IPs,
}
Logc(ctx).WithFields(fields).Debug(">>>> ensureNodeAccessForPolicy")
defer Logc(ctx).WithFields(fields).Debug("<<<< ensureNodeAccessForPolicy")
exportPolicyMutex.Lock(policyName)
defer exportPolicyMutex.Unlock(policyName)
if exists, err := clientAPI.ExportPolicyExists(ctx, policyName); err != nil {
return err
} else if !exists {
Logc(ctx).WithField("exportPolicy", policyName).Debug("Export policy missing, will create it.")
if err = clientAPI.ExportPolicyCreate(ctx, policyName); err != nil {
return err
}
}
desiredRules, err := network.FilterIPs(ctx, targetNode.IPs, config.AutoExportCIDRs)
if err != nil {
err = fmt.Errorf("unable to determine desired export policy rules; %v", err)
Logc(ctx).Error(err)
return err
}
Logc(ctx).WithField("desiredRules", desiredRules).Debug("Desired export policy rules.")
// first grab all existing rules
existingRules, err := clientAPI.ExportRuleList(ctx, policyName)
if err != nil {
// Could not list rules, just log it, no action required.
Logc(ctx).WithField("error", err).Debug("Export policy rules could not be listed.")
}
Logc(ctx).WithField("existingRules", existingRules).Debug("Existing export policy rules.")
for _, desiredRule := range desiredRules {
desiredRule = strings.TrimSpace(desiredRule)
desiredIP := net.ParseIP(desiredRule)
if desiredIP == nil {
Logc(ctx).WithField("desiredRule", desiredRule).Debug("Invalid desired rule IP")
continue
}
// Loop through the existing rules one by one and compare to make sure we cover the scenario where the
// existing rule is of format "1.1.1.1, 2.2.2.2" and the desired rule is format "1.1.1.1".
// This can happen because of the difference in how ONTAP ZAPI and ONTAP REST creates export rule.
ruleFound := false
for _, existingRule := range existingRules {
existingIPs := strings.Split(existingRule, ",")
for _, ip := range existingIPs {
ip = strings.TrimSpace(ip)
existingIP := net.ParseIP(ip)
if existingIP == nil {
Logc(ctx).WithField("existingRule", existingRule).Debug("Invalid existing rule IP")
continue
}
if existingIP.Equal(desiredIP) {
ruleFound = true
break
}
}
if ruleFound {
break
}
}
// Rule does not exist, so create it
if !ruleFound {
if err = clientAPI.ExportRuleCreate(ctx, policyName, desiredRule, config.NASType); err != nil {
// Check if error is that the export policy rule already exist error
if errors.IsAlreadyExistsError(err) {
Logc(ctx).WithField("desiredRule", desiredRule).WithError(err).Debug(
"Export policy rule already exists")
continue
}
return err
}
}
}
return nil
}
func getDesiredExportPolicyRules(
ctx context.Context, nodes []*tridentmodels.Node, config *drivers.OntapStorageDriverConfig,
) ([]string, error) {
uniqueRules := make(map[string]struct{})
for _, node := range nodes {
// Filter the IPs based on the CIDRs provided by user
filteredIPs, err := network.FilterIPs(ctx, node.IPs, config.AutoExportCIDRs)
if err != nil {
return nil, err
}
for _, ip := range filteredIPs {
uniqueRules[ip] = struct{}{}
}
}
rules := make([]string, 0, len(uniqueRules))
for ip := range uniqueRules {
rules = append(rules, ip)
}
return rules, nil
}
func reconcileExportPolicyRules(
ctx context.Context, policyName string, desiredPolicyRules []string, clientAPI api.OntapAPI,
config *drivers.OntapStorageDriverConfig,
) error {
fields := LogFields{
"Method": "reconcileExportPolicyRules",
"Type": "ontap_common",
"policyName": policyName,
"desiredPolicyRules": desiredPolicyRules,
}
Logc(ctx).WithFields(fields).Debug(">>>> reconcileExportPolicyRules")
defer Logc(ctx).WithFields(fields).Debug("<<<< reconcileExportPolicyRules")
exportPolicyMutex.Lock(policyName)
defer exportPolicyMutex.Unlock(policyName)
// first grab all existing rules
existingRules, err := clientAPI.ExportRuleList(ctx, policyName)
if err != nil {
// Could not extract rules, just log it, no action required.
Logc(ctx).WithField("error", err).Debug("Export policy rules could not be extracted.")
}
Logc(ctx).WithField("existingRules", existingRules).Debug("Existing export policy rules.")
undesiredRules := maps.Clone(existingRules)
for _, desiredRule := range desiredPolicyRules {
desiredRule = strings.TrimSpace(desiredRule)
desiredIP := net.ParseIP(desiredRule)
if desiredIP == nil {
Logc(ctx).WithField("desiredRule", desiredRule).Debug("Invalid desired rule IP")
continue
}
// Loop through the existing rules one by one and compare to make sure we cover the scenario where the
// existing rule is of format "1.1.1.1, 2.2.2.2" and the desired rule is format "1.1.1.1".
// This can happen because of the difference in how ONTAP ZAPI and ONTAP REST creates export rule.
existingRuleIndex := -1
for ruleIndex, rule := range existingRules {
existingIPs := strings.Split(rule, ",")
for _, ip := range existingIPs {
ip = strings.TrimSpace(ip)
existingIP := net.ParseIP(ip)
if existingIP == nil {
Logc(ctx).WithField("existingRule", rule).Debug("Invalid existing rule IP")
continue
}
if existingIP.Equal(desiredIP) {
existingRuleIndex = ruleIndex
break
}
}
if existingRuleIndex != -1 {
break
}
}
if existingRuleIndex != -1 {
// Rule already exists and we want it, so don't create it or delete it
delete(undesiredRules, existingRuleIndex)
} else {
// Rule does not exist, so create it
if err = clientAPI.ExportRuleCreate(ctx, policyName, desiredRule, config.NASType); err != nil {
// Check if error is that the export policy rule already exist error
if errors.IsAlreadyExistsError(err) {
Logc(ctx).WithField("desiredRule", desiredRule).WithError(err).Debug(
"Export policy rule already exists")
continue
}
return err
}
}
}
Logc(ctx).WithField("undesiredRules", undesiredRules).Debug("Undesired export policy rules.")
// Now that the desired rules exists, delete the undesired rules.
// NOTE: With each rule deletion, ONTAP clears and rebuilds the export-policy cache. All I/O operations using the
// outdated cache are paused until it is fully reconstructed. Therefore, an upper limit is set on how many rules
// can be deleted during one reconciliation.
maxDeleteCount := 10
deleted := 0
for ruleIndex := range undesiredRules {
if deleted >= maxDeleteCount {
Logc(ctx).WithField("maxDeleteCount", maxDeleteCount).Info("Maximum export rule delete count reached.")
break
}
if err = clientAPI.ExportRuleDestroy(ctx, policyName, ruleIndex); err != nil {
return err
}
deleted++
}
return nil
}
// getSVMState gets the backend SVM state and reason for offline if any.
// Input:
// protocol - to get the data LIFs of similar service from backend.
// pools - list of known pools to compare with the backend aggregate list and determine the change if any.
func getSVMState(
ctx context.Context, client api.OntapAPI, protocol string, pools []string, configAggrs ...string,
) (string, *roaring.Bitmap) {
changeMap := roaring.New()
svmState, err := client.GetSVMState(ctx)
if err != nil {
// Could not get the SVM info or SVM is unreachable. Just log it.
// Set state offline and reason as unreachable.
Logc(ctx).WithField("error", err).Debug("Error getting SVM information.")
return StateReasonSVMUnreachable, changeMap
}
if svmState != models.SvmStateRunning {
return StateReasonSVMStopped, changeMap
}
// Get Aggregates list and verify if there is any change.
if !client.IsSANOptimized() && client.IsDisaggregated() {
Logc(ctx).Debug("Disaggregated system detected, skipping aggregate checks.")
} else {
aggrList, err := client.GetSVMAggregateNames(ctx)
if err != nil {
Logc(ctx).WithField("error", err).Debug("Error getting the physical pools from backend.")
} else {
if len(aggrList) == 0 {
changeMap.Add(storage.BackendStatePoolsChange)
return StateReasonNoAggregates, changeMap
}
sort.Strings(aggrList)
sort.Strings(pools)
if !cmp.Equal(pools, aggrList) {
changeMap.Add(storage.BackendStatePoolsChange)
// For the case where config.Aggregate is "", but due to configAggrs being a variadic parameter,
// it will be passed as []string{""}.
if strings.Join(configAggrs, "") != "" {
if containsAll, _ := collection.ContainsElements(aggrList, configAggrs); !containsAll {
return StateReasonMissingAggregate, changeMap
}
}
}
}
}
upDataLIFs := make([]string, 0)
if protocol == sa.FCP {
// Check dataLIF for FC protocol
upDataLIFs, err = client.NetFcpInterfaceGetDataLIFs(ctx, protocol)
} else {
// Check dataLIF for iSCSI and NVMe protocols
upDataLIFs, err = client.NetInterfaceGetDataLIFs(ctx, protocol)
}
if err != nil || len(upDataLIFs) == 0 {
if err != nil {
// Log error and keep going.
fields := LogFields{"error": err, "protocol": protocol}
Logc(ctx).WithFields(fields).Warn("Error getting list of data LIFs from backend.")
}
// No data LIFs with state 'up' found.
return StateReasonDataLIFsDown, changeMap
}
// Get ONTAP version
ontapVerCached, err := client.APIVersion(ctx, true)
if err != nil {
// Could not get the ONTAP version. Just log it.
Logc(ctx).WithError(err).Debug("Error getting cached ONTAP version.")
return "", changeMap
}
ontapVerCurrent, err := client.APIVersion(ctx, false)
if err != nil {
// Could not get the ONTAP version. Just log it.
Logc(ctx).WithError(err).Debug("Error getting ONTAP version.")
return "", changeMap
}
var parsedOntapVerCurrent, parsedOntapVerCached *version.Version
switch client.(type) {
// versioning is different for ZAPI, for ex: 1.251 which is equivalent to 9.15.1
case api.OntapAPIZAPI:
parsedOntapVerCached, err = version.ParseMajorMinorVersion(ontapVerCached)
if err != nil {
Logc(ctx).WithField("error", err).Debug("Error parsing cached ONTAP version.")
return "", changeMap
}
parsedOntapVerCurrent, err = version.ParseMajorMinorVersion(ontapVerCurrent)
if err != nil {
Logc(ctx).WithField("error", err).Debug("Error parsing ONTAP version.")
return "", changeMap
}
default:
parsedOntapVerCached, err = version.ParseSemantic(ontapVerCached)
if err != nil {
Logc(ctx).WithField("error", err).Debug("Error parsing cached ONTAP version.")
return "", changeMap
}
parsedOntapVerCurrent, err = version.ParseSemantic(ontapVerCurrent)
if err != nil {
Logc(ctx).WithField("error", err).Debug("Error parsing ONTAP version.")
return "", changeMap
}
}
// Comparing the retrieved ONTAP version with the cached version.
if parsedOntapVerCurrent.GreaterThan(parsedOntapVerCached) || parsedOntapVerCurrent.LessThan(parsedOntapVerCached) {
changeMap.Add(storage.BackendStateAPIVersionChange)
}
return "", changeMap
}
// resizeValidation performs needed validation checks prior to the resize operation.
func resizeValidation(
ctx context.Context,
volConfig *storage.VolumeConfig,
requestedSizeBytes uint64,
volumeExists func(context.Context, string) (bool, error),
volumeSize func(context.Context, string) (uint64, error),
volumeInfo func(context.Context, string) (*api.Volume, error),
) (uint64, error) {
name := volConfig.InternalName
// Ensure the volume exists
volExists, err := volumeExists(ctx, name)
if err != nil {
Logc(ctx).WithField("error", err).Errorf("Error checking for existing volume.")
return 0, fmt.Errorf("error occurred checking for existing volume")
}
if !volExists {
return 0, fmt.Errorf("volume %s does not exist", name)
}
// Lookup the volume's current size on the storage system
volSize, err := volumeSize(ctx, name)
if err != nil {
Logc(ctx).WithField("error", err).Errorf("Error checking volume size.")
return 0, fmt.Errorf("error occurred when checking volume size")
}
volSizeBytes := uint64(volSize)
// Determine original volume size in bytes
volConfigSize, err := capacity.ToBytes(volConfig.Size)
if err != nil {
return 0, fmt.Errorf("could not convert volume size %s: %v", volConfig.Size, err)
}
volConfigSizeBytes, err := strconv.ParseUint(volConfigSize, 10, 64)
if err != nil {
return 0, fmt.Errorf("%v is an invalid volume size: %v", volConfig.Size, err)
}
// Ensure the requested size is greater than the original size that we stored in the vol config
if requestedSizeBytes < volConfigSizeBytes {
return 0, errors.UnsupportedCapacityRangeError(fmt.Errorf("requested size %d is less than previous volume size %d",
requestedSizeBytes, volConfigSizeBytes))
}
if requestedSizeBytes == volConfigSizeBytes {
Logc(ctx).Debugf("Requested volume size %s is the same as current volume size %s.", requestedSizeBytes,
volSizeBytes)
// nothing to do
return 0, nil
}
snapshotReserveInt, err := getSnapshotReserveFromOntap(ctx, name, volumeInfo)
if err != nil {
Logc(ctx).WithField("name", name).Errorf("Could not get the snapshot reserve percentage for volume")
}
// Ensure the final effective volume size is larger than the current volume size
newFlexvolSize := drivers.CalculateVolumeSizeBytes(ctx, name, requestedSizeBytes, snapshotReserveInt)
if newFlexvolSize < volSizeBytes {
return 0, errors.UnsupportedCapacityRangeError(fmt.Errorf("effective volume size %d including any "+
"snapshot reserve is less than the existing volume size %d", newFlexvolSize, volSizeBytes))
}
return newFlexvolSize, nil
}
// reconcileSANNodeAccess ensures unused igroups are removed. Unused igroups are the legacy per-backend igroup and
// per-node igroups without publications. Igroups are not removed if any LUNs are mapped; multiple backends may use
// the same vserver, and existing volumes may still use the per-backend igroup.
func reconcileSANNodeAccess(
ctx context.Context, clientAPI api.OntapAPI, nodes []string, backendUUID, tridentUUID string,
) error {
// List all igroups in backend
igroups, err := clientAPI.IgroupList(ctx)
if err != nil {
return err
}
// Attempt to delete unused igroups
igroups = filterUnusedTridentIgroups(igroups, nodes, backendUUID, tridentUUID)
Logc(ctx).WithFields(LogFields{
"unusedIgroups": igroups,
"backendUUID": backendUUID,
}).Debug("Attempting to delete unused igroups")
for _, igroup := range igroups {
igroupMutex.Lock(igroup)
defer igroupMutex.Unlock(igroup)
if err := DestroyUnmappedIgroup(ctx, clientAPI, igroup); err != nil {
return err
}
}
return nil
}
// filterUnusedTridentIgroups returns Trident-created igroups not in use for backend. Includes per-backend
// igroup and any of the form <node name>-<trident uuid>.
func filterUnusedTridentIgroups(igroups, nodes []string, backendUUID, tridentUUID string) []string {
unusedIgroups := make([]string, 0, len(igroups))
nodeMap := make(map[string]struct{}, len(nodes))
for _, node := range nodes {
nodeMap[node] = struct{}{}
}
backendIgroup := getDefaultIgroupName(tridentconfig.ContextCSI, backendUUID)
for _, igroup := range igroups {
if igroup == backendIgroup {
// Always include deprecated backend igroup
unusedIgroups = append(unusedIgroups, igroup)
} else if strings.HasSuffix(igroup, tridentUUID) {
// Skip igroups without trident uuid
igroupNodeName := strings.TrimSuffix(igroup, "-"+tridentUUID)
if _, ok := nodeMap[igroupNodeName]; !ok {
// Include igroup that is not part of node map
unusedIgroups = append(unusedIgroups, igroup)
}
}
}
return unusedIgroups
}
// GetISCSITargetInfo returns the iSCSI node name and iSCSI interfaces using the provided client's SVM.
func GetISCSITargetInfo(
ctx context.Context, clientAPI api.OntapAPI, config *drivers.OntapStorageDriverConfig,
) (iSCSINodeName string, iSCSIInterfaces []string, returnError error) {
// Get the SVM iSCSI IQN
iSCSINodeName, err := clientAPI.IscsiNodeGetNameRequest(ctx)
if err != nil {
returnError = fmt.Errorf("could not get SVM iSCSI node name: %v", err)
return
}
// Get the SVM iSCSI interface with enabled IQNs
iscsiInterfaces, err := clientAPI.IscsiInterfaceGet(ctx, config.SVM)
if err != nil {
returnError = fmt.Errorf("could not get SVM iSCSI node name: %v", err)
return
}
// Get the IQN
if iscsiInterfaces == nil {
returnError = fmt.Errorf("SVM %s has no active iSCSI interfaces", config.SVM)
return
}
return
}
func GetFCPTargetInfo(
ctx context.Context, clientAPI api.OntapAPI, config *drivers.OntapStorageDriverConfig,
) (FCPNodeName string, FCPInterfaces []string, returnError error) {
// Get the SVM FCP WWPN
FCPNodeName, err := clientAPI.FcpNodeGetNameRequest(ctx)
if err != nil {
returnError = fmt.Errorf("could not get SVM FCP node name: %v", err)
return
}
// Get the SVM FCP interface with enabled WWPNs
FCPInterfaces, err = clientAPI.FcpInterfaceGet(ctx, config.SVM)
if err != nil {
returnError = fmt.Errorf("could not get SVM FCP node name: %v", err)
return
}
// Get the WWPN
if FCPInterfaces == nil {
returnError = fmt.Errorf("SVM %s has no active FCP interfaces", config.SVM)
return
}
return
}
var ontapDriverRedactList = [...]string{"API"}
func GetOntapDriverRedactList() []string {
clone := ontapDriverRedactList
return clone[:]
}
// getNodeSpecificIgroupName generates a distinct igroup name for node name.
// Igroup names may collide if node names are over 59 characters.
func getNodeSpecificIgroupName(nodeName, tridentUUID string) string {
igroupName := fmt.Sprintf("%s-%s", nodeName, tridentUUID)
if len(igroupName) > MaximumIgroupNameLength {
// If the new igroup name is over the igroup character limit, it means the host name is too long.
igroupPrefixLength := MaximumIgroupNameLength - len(tridentUUID) - 1
igroupName = fmt.Sprintf("%s-%s", nodeName[:igroupPrefixLength], tridentUUID)
}
return igroupName
}
// getNodeSpecificFCPIgroupName generates a distinct igroup name for node name.
// Igroup names may collide if node names are over 59 characters.
func getNodeSpecificFCPIgroupName(nodeName, tridentUUID string) string {
igroupName := fmt.Sprintf("%s-fcp-%s", nodeName, tridentUUID)
if len(igroupName) > MaximumIgroupNameLength {
// If the new igroup name is over the igroup character limit, it means the host name is too long.
igroupPrefixLength := MaximumIgroupNameLength - len(tridentUUID) - 5
igroupName = fmt.Sprintf("%s-fcp-%s", nodeName[:igroupPrefixLength], tridentUUID)
}
return igroupName
}
// PublishLUN publishes the volume to the host specified in publishInfo from ontap-san or
// ontap-san-economy. This method may or may not be running on the host where the volume will be
// mounted, so it should limit itself to updating access rules, initiator groups, etc. that require
// some host identity (but not locality) as well as storage controller API access.
// This function assumes that the list of data LIF IP addresses does not change between driver initialization
// and publish
func PublishLUN(
ctx context.Context, clientAPI api.OntapAPI, config *drivers.OntapStorageDriverConfig, ips []string,
publishInfo *tridentmodels.VolumePublishInfo, lunPath, igroupName, nodeName string,
) error {
lunMutex.Lock(lunPath)
defer lunMutex.Unlock(lunPath)
igroupMutex.Lock(igroupName)
defer igroupMutex.Unlock(igroupName)
fields := LogFields{
"Method": "PublishLUN",
"Type": "ontap_common",
"lunPath": lunPath,
"igroup": igroupName,
"nodeName": nodeName,
"publishInfo": publishInfo,
}
Logd(ctx, config.StorageDriverName, config.DebugTraceFlags["method"]).WithFields(fields).Trace(">>>> PublishLUN")
defer Logd(ctx, config.StorageDriverName,
config.DebugTraceFlags["method"]).WithFields(fields).Trace("<<<< PublishLUN")
var iqn string
var err error
if config.SANType == sa.ISCSI {
if publishInfo.Localhost {
// Lookup local host IQNs
iqns, err := iscsi.GetInitiatorIqns(ctx)
if err != nil {
return fmt.Errorf("error determining host initiator IQN: %v", err)
} else if len(iqns) == 0 {
return errors.New("could not determine host initiator IQN")
}
iqn = iqns[0]
} else {
// Host IQN must have been passed in
if len(publishInfo.HostIQN) == 0 {
return errors.New("host initiator IQN not specified")
}
iqn = publishInfo.HostIQN[0]
}
}
// Get the fstype
fstype := drivers.DefaultFileSystemType
lunFSType, err := clientAPI.LunGetFSType(ctx, lunPath)
if err != nil || lunFSType == "" {
if err != nil {
Logc(ctx).Warnf("failed to get fstype for LUN: %v", err)
}
Logc(ctx).WithFields(LogFields{
"LUN": lunPath,
"fstype": fstype,
}).Warn("LUN attribute fstype not found, using default.")
} else {
fstype = lunFSType
}
// Get the format options
// An example of how formatOption may look like:
// "-E stride=256,stripe_width=16 -F -b 2435965"