Skip to content

Commit

Permalink
azurerm_netapp_volume - support create_from_snapshot_resource_id (#10906
Browse files Browse the repository at this point in the history
)

Implemented support for Azure NetApp Files Volume creation from Snapshot.

Fixes: #9393
  • Loading branch information
paulomarquesc authored Mar 12, 2021
1 parent e4a67a6 commit a217406
Show file tree
Hide file tree
Showing 8 changed files with 412 additions and 0 deletions.
73 changes: 73 additions & 0 deletions azurerm/internal/services/netapp/netapp_volume_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ func resourceNetAppVolume() *schema.Resource {
ValidateFunc: azure.ValidateResourceID,
},

"create_from_snapshot_resource_id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ValidateFunc: azure.ValidateResourceID,
},

"protocols": {
Type: schema.TypeSet,
ForceNew: true,
Expand Down Expand Up @@ -281,6 +289,70 @@ func resourceNetAppVolumeCreateUpdate(d *schema.ResourceData, meta interface{})
volumeType = "DataProtection"
}

// Handling volume creation from snapshot case
snapshotResourceID := d.Get("create_from_snapshot_resource_id").(string)
snapshotID := ""
if snapshotResourceID != "" {
// Get snapshot ID GUID value
parsedSnapshotResourceID, err := parse.SnapshotID(snapshotResourceID)
if err != nil {
return fmt.Errorf("Error parsing snapshotResourceID %q: %+v", snapshotResourceID, err)
}

snapshotClient := meta.(*clients.Client).NetApp.SnapshotClient
snapshotResponse, err := snapshotClient.Get(
ctx,
parsedSnapshotResourceID.ResourceGroup,
parsedSnapshotResourceID.NetAppAccountName,
parsedSnapshotResourceID.CapacityPoolName,
parsedSnapshotResourceID.VolumeName,
parsedSnapshotResourceID.Name,
)
if err != nil {
return fmt.Errorf("Error getting snapshot from NetApp Volume %q (Resource Group %q): %+v", parsedSnapshotResourceID.VolumeName, parsedSnapshotResourceID.ResourceGroup, err)
}
snapshotID = *snapshotResponse.SnapshotID

// Validate if properties that cannot be changed matches (protocols, subnet_id, location, resource group, account_name, pool_name, service_level)
sourceVolume, err := client.Get(
ctx,
parsedSnapshotResourceID.ResourceGroup,
parsedSnapshotResourceID.NetAppAccountName,
parsedSnapshotResourceID.CapacityPoolName,
parsedSnapshotResourceID.VolumeName,
)
if err != nil {
return fmt.Errorf("Error getting source NetApp Volume (snapshot's parent resource) %q (Resource Group %q): %+v", parsedSnapshotResourceID.VolumeName, parsedSnapshotResourceID.ResourceGroup, err)
}

parsedVolumeID, _ := parse.VolumeID(*sourceVolume.ID)
propertyMismatch := []string{}
if !ValidateSlicesEquality(*sourceVolume.ProtocolTypes, *utils.ExpandStringSlice(protocols), false) {
propertyMismatch = append(propertyMismatch, "protocols")
}
if !strings.EqualFold(*sourceVolume.SubnetID, subnetID) {
propertyMismatch = append(propertyMismatch, "subnet_id")
}
if !strings.EqualFold(*sourceVolume.Location, location) {
propertyMismatch = append(propertyMismatch, "location")
}
if !strings.EqualFold(string(sourceVolume.ServiceLevel), serviceLevel) {
propertyMismatch = append(propertyMismatch, "service_level")
}
if !strings.EqualFold(parsedVolumeID.ResourceGroup, resourceGroup) {
propertyMismatch = append(propertyMismatch, "resource_group_name")
}
if !strings.EqualFold(parsedVolumeID.NetAppAccountName, accountName) {
propertyMismatch = append(propertyMismatch, "account_name")
}
if !strings.EqualFold(parsedVolumeID.CapacityPoolName, poolName) {
propertyMismatch = append(propertyMismatch, "pool_name")
}
if len(propertyMismatch) > 0 {
return fmt.Errorf("Following NetApp Volume properties on new Volume from Snapshot does not match Snapshot's source Volume %q (Resource Group %q): %+v", name, resourceGroup, propertyMismatch)
}
}

parameters := netapp.Volume{
Location: utils.String(location),
VolumeProperties: &netapp.VolumeProperties{
Expand All @@ -291,6 +363,7 @@ func resourceNetAppVolumeCreateUpdate(d *schema.ResourceData, meta interface{})
UsageThreshold: utils.Int64(storageQuotaInGB),
ExportPolicy: exportPolicyRule,
VolumeType: utils.String(volumeType),
SnapshotID: utils.String(snapshotID),
DataProtection: dataProtectionReplication,
},
Tags: tags.Expand(d.Get("tags").(map[string]interface{})),
Expand Down
73 changes: 73 additions & 0 deletions azurerm/internal/services/netapp/netapp_volume_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,21 @@ func TestAccNetAppVolume_crossRegionReplication(t *testing.T) {
})
}

func TestAccNetAppVolume_nfsv3FromSnapshot(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_netapp_volume", "test_snapshot_vol")
r := NetAppVolumeResource{}

data.ResourceTest(t, r, []resource.TestStep{
{
Config: r.nfsv3FromSnapshot(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep("create_from_snapshot_resource_id"),
})
}

func TestAccNetAppVolume_requiresImport(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_netapp_volume", "test")
r := NetAppVolumeResource{}
Expand Down Expand Up @@ -320,6 +335,64 @@ resource "azurerm_netapp_volume" "test_secondary" {
`, template, data.RandomInteger, "northeurope")
}

func (NetAppVolumeResource) nfsv3FromSnapshot(data acceptance.TestData) string {
template := NetAppVolumeResource{}.template(data)
return fmt.Sprintf(`
%[1]s
resource "azurerm_netapp_volume" "test" {
name = "acctest-NetAppVolume-%[2]d"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
account_name = azurerm_netapp_account.test.name
pool_name = azurerm_netapp_pool.test.name
volume_path = "my-unique-file-path-%[2]d"
service_level = "Standard"
subnet_id = azurerm_subnet.test.id
protocols = ["NFSv3"]
storage_quota_in_gb = 100
export_policy_rule {
rule_index = 1
allowed_clients = ["1.2.3.0/24"]
protocols_enabled = ["NFSv3"]
unix_read_only = false
unix_read_write = true
}
}
resource "azurerm_netapp_snapshot" "test" {
name = "acctest-Snapshot-%[2]d"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
account_name = azurerm_netapp_account.test.name
pool_name = azurerm_netapp_pool.test.name
volume_name = azurerm_netapp_volume.test.name
}
resource "azurerm_netapp_volume" "test_snapshot_vol" {
name = "acctest-NetAppVolume-NewFromSnapshot-%[2]d"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
account_name = azurerm_netapp_account.test.name
pool_name = azurerm_netapp_pool.test.name
volume_path = "my-unique-file-path-snapshot-%[2]d"
service_level = "Standard"
subnet_id = azurerm_subnet.test.id
protocols = ["NFSv3"]
storage_quota_in_gb = 200
create_from_snapshot_resource_id = azurerm_netapp_snapshot.test.id
export_policy_rule {
rule_index = 1
allowed_clients = ["0.0.0.0/0"]
protocols_enabled = ["NFSv3"]
unix_read_write = true
}
}
`, template, data.RandomInteger)
}

func (r NetAppVolumeResource) requiresImport(data acceptance.TestData) string {
return fmt.Sprintf(`
%s
Expand Down
51 changes: 51 additions & 0 deletions azurerm/internal/services/netapp/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package netapp

import (
"fmt"
"reflect"
"regexp"
"strings"
)

func ValidateNetAppAccountName(v interface{}, k string) (warnings []string, errors []error) {
Expand Down Expand Up @@ -54,3 +56,52 @@ func ValidateNetAppSnapshotName(v interface{}, k string) (warnings []string, err

return warnings, errors
}

func ValidateSlicesEquality(source, new []string, caseSensitive bool) bool {
// Fast path
if len(source) != len(new) {
return false
}

if reflect.DeepEqual(source, new) {
return true
}

// Slow path
// Source -> New direction
sourceNewValidatedCount := 0
for _, sourceItem := range source {
for _, newItem := range new {
if caseSensitive {
if sourceItem == newItem {
sourceNewValidatedCount++
}
} else {
if strings.EqualFold(sourceItem, newItem) {
sourceNewValidatedCount++
}
}
}
}

// New -> Source direction
newSourceValidatedCount := 0
for _, newItem := range source {
for _, sourceItem := range new {
if caseSensitive {
if newItem == sourceItem {
newSourceValidatedCount++
}
} else {
if strings.EqualFold(newItem, sourceItem) {
newSourceValidatedCount++
}
}
}
}

lengthValidation := sourceNewValidatedCount == len(source) && newSourceValidatedCount == len(source) && sourceNewValidatedCount == len(new) && newSourceValidatedCount == len(new)
countValidation := sourceNewValidatedCount == newSourceValidatedCount

return lengthValidation && countValidation
}
90 changes: 90 additions & 0 deletions azurerm/internal/services/netapp/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,93 @@ func TestValidateNetAppSnapshotName(t *testing.T) {
}
}
}

func TestValidateSlicesEquality(t *testing.T) {
testData := []struct {
input1 []string
input2 []string
input3 bool
expected bool
}{
{
// Same order, case sensitive
input1: []string{"CIFS", "NFSv3"},
input2: []string{"CIFS", "NFSv3"},
input3: true,
expected: true,
},
{
// Same order, case insensitive
input1: []string{"CIFS", "NFSv3"},
input2: []string{"cifs", "nfsv3"},
input3: false,
expected: true,
},
{
// Reversed order, case sensitive
input1: []string{"CIFS", "NFSv3"},
input2: []string{"NFSv3", "CIFS"},
input3: true,
expected: true,
},
{
// Reversed order, case insensitive
input1: []string{"cifs", "nfsv3"},
input2: []string{"NFSv3", "CIFS"},
input3: false,
expected: true,
},

{
// Different, case sensitive
input1: []string{"CIFS", "NFSv3"},
input2: []string{"NFSv3"},
input3: true,
expected: false,
},
{
// Different, case insensitive
input1: []string{"CIFS", "NFSv3"},
input2: []string{"nfsv3"},
input3: false,
expected: false,
},
{
// Different, single slices, case sensitive
input1: []string{"CIFS"},
input2: []string{"NFSv3"},
input3: true,
expected: false,
},
{
// Different, single slices, case insensitive
input1: []string{"cifs"},
input2: []string{"NFSv3"},
input3: false,
expected: false,
},
{
// Same, single slices, case sensitive
input1: []string{"CIFS"},
input2: []string{"CIFS"},
input3: true,
expected: true,
},
{
// Different, single slices, case insensitive
input1: []string{"cifs"},
input2: []string{"CIFS"},
input3: false,
expected: true,
},
}

for _, v := range testData {
t.Logf("[DEBUG] Testing %+v and %+v for %v where 'caseSensitive' = %v result..", v.input1, v.input2, v.expected, v.input3)

actual := ValidateSlicesEquality(v.input1, v.input2, v.input3)
if v.expected != actual {
t.Fatalf("Expected %t but got %t", v.expected, actual)
}
}
}
5 changes: 5 additions & 0 deletions examples/netapp/volume_from_snapshot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## Example: NetApp Files Volume creation from Snapshot

This example shows how to create an Azure NetApp Files Volume from a Snapshot.

For more information, please refer to [How Azure NetApp Files snapshots work](https://docs.microsoft.com/en-us/azure/azure-netapp-files/snapshots-introduction).
Loading

0 comments on commit a217406

Please sign in to comment.