Skip to content

Commit ba4cd44

Browse files
torirevillavivakerambrownaaron
authored
Force Detach ONTAP-NAS
Added support for force detach for ONTAP-NAS volumes during Non-Graceful Node Shutdown scenarios. All ONTAP-NAS volumes will now use per-volume export policies. Provided an upgrade path for existing volumes to be switched to the new export policy model on unpublish without impacting active workloads. Co-authored-by: vivake <[email protected]> Co-authored-by: Aaron Brown <[email protected]>
1 parent 0d197d8 commit ba4cd44

9 files changed

+788
-187
lines changed

storage_drivers/ontap/api/ontap_rest.go

+12-1
Original file line numberDiff line numberDiff line change
@@ -4116,7 +4116,18 @@ func (c RestClient) ExportRuleDestroy(
41164116
params.PolicyID = *exportPolicy.ID
41174117
params.Index = int64(ruleIndex)
41184118

4119-
return c.api.Nas.ExportRuleDelete(params, c.authInfo)
4119+
ok, err := c.api.Nas.ExportRuleDelete(params, c.authInfo)
4120+
if err != nil {
4121+
if restErr, extractErr := ExtractErrorResponse(ctx, err); extractErr == nil {
4122+
if restErr.Error != nil && restErr.Error.Code != nil && *restErr.Error.Code != ENTRY_DOESNT_EXIST &&
4123+
restErr.Error.Message != nil {
4124+
return ok, fmt.Errorf(*restErr.Error.Message)
4125+
}
4126+
} else {
4127+
return ok, err
4128+
}
4129+
}
4130+
return ok, nil
41204131
}
41214132

41224133
// ///////////////////////////////////////////////////////////////////////////

storage_drivers/ontap/api/ontap_zapi.go

+6
Original file line numberDiff line numberDiff line change
@@ -2151,6 +2151,12 @@ func (c Client) ExportRuleDestroy(policy string, ruleIndex int) (*azgo.ExportRul
21512151
SetPolicyName(policy).
21522152
SetRuleIndex(ruleIndex).
21532153
ExecuteUsing(c.zr)
2154+
if zerr := azgo.NewZapiError(response); !zerr.IsPassed() {
2155+
// It's not an error if the export rule doesn't exist
2156+
if zerr.Code() == azgo.EOBJECTNOTFOUND {
2157+
return response, nil
2158+
}
2159+
}
21542160
return response, err
21552161
}
21562162

storage_drivers/ontap/ontap_common.go

+110-31
Original file line numberDiff line numberDiff line change
@@ -286,37 +286,6 @@ func ensureExportPolicyExists(ctx context.Context, policyName string, clientAPI
286286
return clientAPI.ExportPolicyCreate(ctx, policyName)
287287
}
288288

289-
// publishShare ensures that the volume has the correct export policy applied.
290-
func publishShare(
291-
ctx context.Context, clientAPI api.OntapAPI, config *drivers.OntapStorageDriverConfig,
292-
publishInfo *tridentmodels.VolumePublishInfo, volumeName string,
293-
ModifyVolumeExportPolicy func(ctx context.Context, volumeName, policyName string) error,
294-
) error {
295-
fields := LogFields{
296-
"Method": "publishFlexVolShare",
297-
"Type": "ontap_common",
298-
"Share": volumeName,
299-
}
300-
Logd(ctx, config.StorageDriverName,
301-
config.DebugTraceFlags["method"]).WithFields(fields).Trace(">>>> publishFlexVolShare")
302-
defer Logd(ctx, config.StorageDriverName,
303-
config.DebugTraceFlags["method"]).WithFields(fields).Trace("<<<< publishFlexVolShare")
304-
305-
if !config.AutoExportPolicy || publishInfo.Unmanaged {
306-
// Nothing to do if we're not configuring export policies automatically or volume is not managed
307-
return nil
308-
}
309-
310-
if err := ensureNodeAccess(ctx, publishInfo, clientAPI, config); err != nil {
311-
return err
312-
}
313-
314-
// Update volume to use the correct export policy
315-
policyName := getExportPolicyName(publishInfo.BackendUUID)
316-
err := ModifyVolumeExportPolicy(ctx, volumeName, policyName)
317-
return err
318-
}
319-
320289
func getExportPolicyName(backendUUID string) string {
321290
return fmt.Sprintf("trident-%s", backendUUID)
322291
}
@@ -369,6 +338,61 @@ func reconcileNASNodeAccess(
369338
return nil
370339
}
371340

341+
// ensureNodeAccessForPolicy check to see if export policy exists and if not it will create it.
342+
// Add the desired rule(s) to the policy.
343+
func ensureNodeAccessForPolicy(
344+
ctx context.Context, targetNode *tridentmodels.Node, clientAPI api.OntapAPI,
345+
config *drivers.OntapStorageDriverConfig, policyName string,
346+
) error {
347+
if exists, err := clientAPI.ExportPolicyExists(ctx, policyName); err != nil {
348+
return err
349+
} else if !exists {
350+
Logc(ctx).WithField("exportPolicy", policyName).Debug("Export policy missing, will create it.")
351+
352+
if err = ensureExportPolicyExists(ctx, policyName, clientAPI); err != nil {
353+
return err
354+
}
355+
}
356+
357+
desiredRules, err := network.FilterIPs(ctx, targetNode.IPs, config.AutoExportCIDRs)
358+
if err != nil {
359+
err = fmt.Errorf("unable to determine desired export policy rules; %v", err)
360+
Logc(ctx).Error(err)
361+
return err
362+
}
363+
364+
// first grab all existing rules
365+
existingRules, err := clientAPI.ExportRuleList(ctx, policyName)
366+
if err != nil {
367+
// Could not list rules, just log it, no action required.
368+
Logc(ctx).WithField("error", err).Debug("Export policy rules could not be listed.")
369+
}
370+
371+
for _, desiredRule := range desiredRules {
372+
373+
// Loop through the existing rules one by one and compare to make sure we cover the scenario where the
374+
// existing rule is of format "10.193.112.26, 10.244.2.0" and the desired rule is format "10.193.112.26".
375+
// This can happen because of the difference in how ONTAP ZAPI and ONTAP REST creates export rule.
376+
377+
ruleFound := false
378+
for existingRule := range existingRules {
379+
if strings.Contains(existingRule, desiredRule) {
380+
ruleFound = true
381+
break
382+
}
383+
}
384+
385+
// Rule does not exist, so create it
386+
if !ruleFound {
387+
if err = clientAPI.ExportRuleCreate(ctx, policyName, desiredRule, config.NASType); err != nil {
388+
return err
389+
}
390+
}
391+
}
392+
393+
return nil
394+
}
395+
372396
func getDesiredExportPolicyRules(
373397
ctx context.Context, nodes []*tridentmodels.Node, config *drivers.OntapStorageDriverConfig,
374398
) ([]string, error) {
@@ -3864,3 +3888,58 @@ func deleteAutomaticSnapshot(
38643888
}
38653889
}
38663890
}
3891+
3892+
// removeExportPolicyRules takes an export policy name,
3893+
// retrieves its rules and matches the rules that exist to the IP addresses from the node.
3894+
// Any matched IP addresses will be removed from the export policy.
3895+
func removeExportPolicyRules(
3896+
ctx context.Context, exportPolicy string, publishInfo *tridentmodels.VolumePublishInfo, clientAPI api.OntapAPI,
3897+
) error {
3898+
var removeRuleIndexes []int
3899+
3900+
nodeIPRules := make(map[string]struct{})
3901+
for _, ip := range publishInfo.HostIP {
3902+
nodeIPRules[ip] = struct{}{}
3903+
}
3904+
// Get export policy rules from given policy
3905+
allExportRules, err := clientAPI.ExportRuleList(ctx, exportPolicy)
3906+
if err != nil {
3907+
return err
3908+
}
3909+
3910+
// Match list of rules to rule index based on clientMatch address
3911+
// ONTAP expects the rule index to delete
3912+
for clientMatch, ruleIndex := range allExportRules {
3913+
// For the policy, match the node IP addresses to the clientMatch to remove the matched items.
3914+
// Example:
3915+
// trident_pvc_123 is attached to node1 and node2. The policy is being unpublished from node1.
3916+
// node1 IP addresses [1.1.1.0, 1.1.1.1] node2 IP addresses [2.2.2.0, 2.2.2.2].
3917+
// export policy "trident_pvc_123" should have the export rules:
3918+
// index 1: "1.1.1.0"
3919+
// index 2: "1.1.1.1"
3920+
// index 3: "2.2.2.0"
3921+
// index 4: "2.2.2.2"
3922+
// When the clientMatch is the same as the node IP that export rule index will be added to the list of
3923+
// indexes to be removed. For this example indexes 1 and 2 will be removed.
3924+
3925+
// Legacy export policies created via ZAPI will have multiple clientMatch IPs for a node in a single rule.
3926+
// index 1: "1.1.1.0, 1.1.1.1"
3927+
// index 2: "2.2.2.0, 2.2.2.2"
3928+
// For this example, index 1 will be removed.
3929+
for _, singleClientMatch := range strings.Split(clientMatch, ",") {
3930+
if _, match := nodeIPRules[singleClientMatch]; match {
3931+
removeRuleIndexes = append(removeRuleIndexes, ruleIndex)
3932+
}
3933+
}
3934+
}
3935+
3936+
// Attempt to remove node IP addresses from export policy rules
3937+
for _, ruleIndex := range removeRuleIndexes {
3938+
if err = clientAPI.ExportRuleDestroy(ctx, exportPolicy, ruleIndex); err != nil {
3939+
Logc(ctx).WithError(err).Error("Error deleting export policy rule.")
3940+
return err
3941+
}
3942+
}
3943+
3944+
return nil
3945+
}

storage_drivers/ontap/ontap_common_test.go

-40
Original file line numberDiff line numberDiff line change
@@ -4122,46 +4122,6 @@ func MockModifyVolumeExportPolicy(ctx context.Context, volName, policyName strin
41224122
return nil
41234123
}
41244124

4125-
func TestPublishShare(t *testing.T) {
4126-
ctx := context.Background()
4127-
mockCtrl := gomock.NewController(t)
4128-
mockAPI := mockapi.NewMockOntapAPI(mockCtrl)
4129-
4130-
commonConfig := &drivers.CommonStorageDriverConfig{
4131-
DebugTraceFlags: map[string]bool{"method": true},
4132-
}
4133-
config := &drivers.OntapStorageDriverConfig{
4134-
CommonStorageDriverConfig: commonConfig,
4135-
SVM: "testSVM",
4136-
AutoExportPolicy: true,
4137-
}
4138-
4139-
publishInfo := &tridentmodels.VolumePublishInfo{
4140-
BackendUUID: "fakeBackendUUID",
4141-
Unmanaged: false,
4142-
}
4143-
policyName := "trident-fakeBackendUUID"
4144-
volumeName := "fakeVolumeName"
4145-
4146-
// Test1: Positive flow
4147-
mockAPI.EXPECT().ExportPolicyExists(ctx, policyName).Return(true, nil)
4148-
4149-
err := publishShare(ctx, mockAPI, config, publishInfo, volumeName, MockModifyVolumeExportPolicy)
4150-
4151-
assert.NoError(t, err)
4152-
4153-
// Test2: Error flow: PolicyDoesn't exist
4154-
ruleList := make(map[string]int)
4155-
ruleList["0.0.0.1/0"] = 0
4156-
mockAPI = mockapi.NewMockOntapAPI(mockCtrl)
4157-
mockAPI.EXPECT().ExportPolicyExists(ctx, policyName).Return(false, nil)
4158-
mockAPI.EXPECT().ExportPolicyCreate(ctx, policyName).Return(fmt.Errorf("Error Creating Policy"))
4159-
4160-
err = publishShare(ctx, mockAPI, config, publishInfo, volumeName, MockModifyVolumeExportPolicy)
4161-
4162-
assert.Error(t, err)
4163-
}
4164-
41654125
func TestAddUniqueIscsiIGroupName(t *testing.T) {
41664126
tests := []struct {
41674127
message string

0 commit comments

Comments
 (0)