Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

azurerm_netapp_volume - support create_from_snapshot_resource_id #10906

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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