From 7b9cd185f7afdc013436e7b61c56180f938c3091 Mon Sep 17 00:00:00 2001 From: G-NamanGupta Date: Fri, 12 Jan 2024 20:13:30 +0530 Subject: [PATCH 01/34] Initial replication commit --- mmv1/products/netapp/volume.yaml | 416 ++++++++++++++++++ mmv1/products/netapp/volumereplication.yaml | 205 +++++++++ .../terraform/examples/volume_basic.tf.erb | 20 + .../examples/volume_replication_create.tf.erb | 36 ++ .../delete_destinition_rep_volume.go.erb | 30 ++ .../pre_delete/stop_replication.go.erb | 30 ++ .../pre_update/volume_mirror_state.go.erb | 29 ++ .../netapp/resource_netapp_volume_test.go | 328 ++++++++++++++ 8 files changed, 1094 insertions(+) create mode 100644 mmv1/products/netapp/volume.yaml create mode 100644 mmv1/products/netapp/volumereplication.yaml create mode 100644 mmv1/templates/terraform/examples/volume_basic.tf.erb create mode 100644 mmv1/templates/terraform/examples/volume_replication_create.tf.erb create mode 100644 mmv1/templates/terraform/post_delete/delete_destinition_rep_volume.go.erb create mode 100644 mmv1/templates/terraform/pre_delete/stop_replication.go.erb create mode 100644 mmv1/templates/terraform/pre_update/volume_mirror_state.go.erb create mode 100644 mmv1/third_party/terraform/services/netapp/resource_netapp_volume_test.go diff --git a/mmv1/products/netapp/volume.yaml b/mmv1/products/netapp/volume.yaml new file mode 100644 index 000000000000..f15009d66e8a --- /dev/null +++ b/mmv1/products/netapp/volume.yaml @@ -0,0 +1,416 @@ +# Copyright 2023 Google Inc. +# 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. + +--- !ruby/object:Api::Resource +name: 'Volume' +description: | + A volume is a file system container in a storage pool that stores application, database, and user data. + + You can create a volume's capacity using the available capacity in the storage pool and you can define and resize the capacity without disruption to any processes. + + Storage pool settings apply to the volumes contained within them automatically. +references: !ruby/object:Api::Resource::ReferenceLinks + guides: + 'Quickstart': 'https://cloud.google.com/netapp/volumes/docs/get-started/quickstarts/create-volume' + 'Documentation': 'https://cloud.google.com/netapp/volumes/docs/configure-and-use/volumes/overview' + api: 'https://cloud.google.com/netapp/volumes/docs/reference/rest/v1/projects.locations.volumes' +base_url: projects/{{project}}/locations/{{location}}/volumes +self_link: projects/{{project}}/locations/{{location}}/volumes/{{name}} +create_url: projects/{{project}}/locations/{{location}}/volumes?volumeId={{name}} +update_url: projects/{{project}}/locations/{{location}}/volumes/{{name}} +update_verb: :PATCH +update_mask: true +autogen_async: true +async: !ruby/object:Api::OpAsync + operation: !ruby/object:Api::OpAsync::Operation + base_url: '{{op_id}}' +id_format: 'projects/{{project}}/locations/{{location}}/volumes/{{name}}' +import_format: ['projects/{{project}}/locations/{{location}}/volumes/{{name}}'] +parameters: + - !ruby/object:Api::Type::String + name: 'location' + required: true + immutable: true + url_param_only: true + description: | + Name of the pool location. Usually a region name, expect for some STANDARD service level pools which require a zone name. + - !ruby/object:Api::Type::String + name: 'name' + description: + The name of the volume. Needs to be unique per location. + required: true + immutable: true + url_param_only: true +examples: + - !ruby/object:Provider::Terraform::Examples + name: 'volume_basic' + primary_resource_id: 'test_volume' + vars: + volume_name: 'test-volume' + pool_name: 'test-pool' + network_name: 'test-network' + test_vars_overrides: + network_name: 'acctest.BootstrapSharedServiceNetworkingConnection(t, "gcnv-network-config-1", acctest.ServiceNetworkWithParentService("netapp.servicenetworking.goog"))' +properties: + - !ruby/object:Api::Type::String + name: 'shareName' + description: | + Share name (SMB) or export path (NFS) of the volume. Needs to be unique per location. + required: true + immutable: true + - !ruby/object:Api::Type::String + name: 'psaRange' + description: | + Name of the Private Service Access allocated range. Inherited from storage pool. + output: true + - !ruby/object:Api::Type::String + name: 'storagePool' + description: | + Name of the storage pool to create the volume in. Pool needs enough spare capacity to accomodate the volume. + required: true + - !ruby/object:Api::Type::String + name: 'network' + description: | + VPC network name with format: `projects/{{project}}/global/networks/{{network}}`. Inherited from storage pool. + output: true + - !ruby/object:Api::Type::Enum + name: 'serviceLevel' + description: | + Service level of the volume. Inherited from storage pool. + values: + - :SERVICE_LEVEL_UNSPECIFIED + - :PREMIUM + - :EXTREME + - :STANDARD + output: true + - !ruby/object:Api::Type::String + name: 'capacityGib' + description: | + Capacity of the volume (in GiB). + required: true + - !ruby/object:Api::Type::NestedObject + name: 'exportPolicy' + description: |- + Export policy of the volume for NFSV3 and/or NFSV4.1 access. + properties: + - !ruby/object:Api::Type::Array + name: 'rules' + description: |- + Export rules (up to 5) control NFS volume access. + required: true + item_type: !ruby/object:Api::Type::NestedObject + properties: + - !ruby/object:Api::Type::String + name: 'allowedClients' + description: |- + Defines the client ingress specification (allowed clients) as a comma seperated list with IPv4 CIDRs or IPv4 host addresses. + - !ruby/object:Api::Type::String + name: 'hasRootAccess' + description: |- + If enabled, the root user (UID = 0) of the specified clients doesn't get mapped to nobody (UID = 65534). This is also known as no_root_squash. + - !ruby/object:Api::Type::Enum + name: 'accessType' + description: | + Defines the access type for clients matching the `allowedClients` specification. + values: + - :READ_ONLY + - :READ_WRITE + - :READ_NONE + - !ruby/object:Api::Type::Boolean + name: 'nfsv3' + description: | + Enable to apply the export rule to NFSV3 clients. + - !ruby/object:Api::Type::Boolean + name: 'nfsv4' + description: | + Enable to apply the export rule to NFSV4.1 clients. + - !ruby/object:Api::Type::Boolean + name: 'kerberos5ReadOnly' + description: | + If enabled (true) the rule defines a read only access for clients matching the 'allowedClients' specification. It enables nfs clients to mount using 'authentication' kerberos security mode. + - !ruby/object:Api::Type::Boolean + name: 'kerberos5ReadWrite' + description: | + If enabled (true) the rule defines read and write access for clients matching the 'allowedClients' specification. It enables nfs clients to mount using 'authentication' kerberos security mode. The 'kerberos5ReadOnly' value is ignored if this is enabled. + - !ruby/object:Api::Type::Boolean + name: 'kerberos5iReadOnly' + description: | + If enabled (true) the rule defines a read only access for clients matching the 'allowedClients' specification. It enables nfs clients to mount using 'integrity' kerberos security mode. + - !ruby/object:Api::Type::Boolean + name: 'kerberos5iReadWrite' + description: | + If enabled (true) the rule defines read and write access for clients matching the 'allowedClients' specification. It enables nfs clients to mount using 'integrity' kerberos security mode. The 'kerberos5iReadOnly' value is ignored if this is enabled. + - !ruby/object:Api::Type::Boolean + name: 'kerberos5pReadOnly' + description: | + If enabled (true) the rule defines a read only access for clients matching the 'allowedClients' specification. It enables nfs clients to mount using 'privacy' kerberos security mode. + - !ruby/object:Api::Type::Boolean + name: 'kerberos5pReadWrite' + description: | + If enabled (true) the rule defines read and write access for clients matching the 'allowedClients' specification. It enables nfs clients to mount using 'privacy' kerberos security mode. The 'kerberos5pReadOnly' value is ignored if this is enabled. + - !ruby/object:Api::Type::Array + name: 'protocols' + description: | + The protocol of the volume. Allowed combinations are `['NFSV3']`, `['NFSV4']`, `['SMB']`, `['NFSV3', 'NFSV4']`, `['SMB', 'NFSV3']` and `['SMB', 'NFSV4']`. + required: true + immutable: true + item_type: !ruby/object:Api::Type::Enum + name: 'protocols' + description: | + Access protocol types. + values: + - :NFSV3 + - :NFSV4 + - :SMB + - !ruby/object:Api::Type::Array + name: 'smbSettings' + description: | + Settings for volumes with SMB access. + item_type: !ruby/object:Api::Type::Enum + name: 'smbSettings' + description: | + Settings for volumes with SMB access. + values: + - :ENCRYPT_DATA + - :BROWSABLE + - :CHANGE_NOTIFY + - :NON_BROWSABLE + - :OPLOCKS + - :SHOW_SNAPSHOT + - :SHOW_PREVIOUS_VERSIONS + - :ACCESS_BASED_ENUMERATION + - :CONTINUOUSLY_AVAILABLE + - !ruby/object:Api::Type::String + name: 'unixPermissions' + description: | + Unix permission the mount point will be created with. Default is 0770. Applicable for UNIX security style volumes only. + default_from_api: true + - !ruby/object:Api::Type::KeyValueLabels + name: 'labels' + description: | + Labels as key value pairs. Example: `{ "owner": "Bob", "department": "finance", "purpose": "testing" }`. + - !ruby/object:Api::Type::String + name: 'description' + description: | + An optional description of this resource. + # We want to get rid of this. Please don't expose. + # - !ruby/object:Api::Type::Integer + # name: 'snapReserve' + # description: | + # Snap_reserve specifies percentage of volume storage reserved for snapshot storage. Default is 0 percent. Use is deprecated. + - !ruby/object:Api::Type::Boolean + name: 'snapshotDirectory' + description: | + If enabled, a NFS volume will contain a read-only .snapshot directory which provides access to each of the volume's snapshots. Will enable "Previous Versions" support for SMB. + - !ruby/object:Api::Type::String + name: 'usedGib' + description: | + Used capacity of the volume (in GiB). This is computed periodically and it does not represent the realtime usage. + output: true + - !ruby/object:Api::Type::Enum + name: 'securityStyle' + description: | + Security Style of the Volume. Use UNIX to use UNIX or NFSV4 ACLs for file permissions. + Use NTFS to use NTFS ACLs for file permissions. Can only be set for volumes which use SMB together with NFS as protocol. + values: + - :NTFS + - :UNIX + immutable: true + default_from_api: true + - !ruby/object:Api::Type::Boolean + name: 'kerberosEnabled' + description: | + Flag indicating if the volume is a kerberos volume or not, export policy rules control kerberos security modes (krb5, krb5i, krb5p). + immutable: true + default_value: false + - !ruby/object:Api::Type::Boolean + name: ldapEnabled + description: | + Flag indicating if the volume is NFS LDAP enabled or not. Inherited from storage pool. + output: true + - !ruby/object:Api::Type::String + name: 'activeDirectory' + description: | + Reports the resource name of the Active Directory policy being used. Inherited from storage pool. + output: true + - !ruby/object:Api::Type::String + name: 'kmsConfig' + description: | + Reports the CMEK policy resurce name being used for volume encryption. Inherited from storage pool. + output: true + - !ruby/object:Api::Type::Enum + name: 'encryptionType' + description: | + Reports the data-at-rest encryption type of the volume. Inherited from storage pool. + values: + - :ENCRYPTION_TYPE_UNSPECIFIED + - :SERVICE_MANAGED + - :CLOUD_KMS + output: true + - !ruby/object:Api::Type::Boolean + name: 'hasReplication' + description: | + Indicates whether the volume is part of a volume replication relationship. + output: true + - !ruby/object:Api::Type::Array + name: 'restrictedActions' + description: | + List of actions that are restricted on this volume. + item_type: !ruby/object:Api::Type::Enum + name: 'restrictedActions' + description: | + Settings for volumes with SMB access. + values: + - :DELETE + - !ruby/object:Api::Type::Array + name: 'mountOptions' + description: Reports mount instructions for this volume. + output: true + item_type: !ruby/object:Api::Type::NestedObject + properties: + - !ruby/object:Api::Type::String + name: 'export' + description: Export path of the volume. + output: true + - !ruby/object:Api::Type::String + name: 'exportFull' + output: true + description: | + Full export path of the volume. + + Format for NFS volumes: `:/` + + Format for SMB volumes: `\\\\netbios_prefix-four_random_hex_letters.domain_name\\shareName` + - !ruby/object:Api::Type::String + name: 'instructions' + description: Human-readable mount instructions. + output: true + - !ruby/object:Api::Type::Enum + name: 'protocol' + description: | + Protocol to mount with. + values: + - :PROTOCOLS_UNSPECIFIED + - :NFSV3 + - :NFSV4 + - :SMB + output: true + - !ruby/object:Api::Type::NestedObject + name: snapshotPolicy + description: |- + Snapshot policy defines the schedule for automatic snapshot creation. + To disable automatic snapshot creation you have to remove the whole snapshot_policy block. + properties: + - !ruby/object:Api::Type::Boolean + name: 'enabled' + description: |- + Enables automated snapshot creation according to defined schedule. Default is false. + To disable automatic snapshot creation you have to remove the whole snapshot_policy block. + default_value: false + send_empty_value: true + - !ruby/object:Api::Type::NestedObject + name: 'hourlySchedule' + description: |- + Hourly schedule policy. + properties: + - !ruby/object:Api::Type::Integer + name: 'snapshotsToKeep' + description: |- + The maximum number of snapshots to keep for the hourly schedule. + required: true + - !ruby/object:Api::Type::Integer + name: 'minute' + description: |- + Set the minute of the hour to create the snapshot (0-59), defaults to the top of the hour (0). + default_value: 0 + - !ruby/object:Api::Type::NestedObject + name: 'dailySchedule' + description: |- + Daily schedule policy. + properties: + - !ruby/object:Api::Type::Integer + name: 'snapshotsToKeep' + description: |- + The maximum number of snapshots to keep for the daily schedule. + required: true + - !ruby/object:Api::Type::Integer + name: 'minute' + description: |- + Set the minute of the hour to create the snapshot (0-59), defaults to the top of the hour (0). + default_value: 0 + - !ruby/object:Api::Type::Integer + name: 'hour' + description: |- + Set the hour to create the snapshot (0-23), defaults to midnight (0). + default_value: 0 + - !ruby/object:Api::Type::NestedObject + name: 'weeklySchedule' + description: |- + Weekly schedule policy. + properties: + - !ruby/object:Api::Type::Integer + name: 'snapshotsToKeep' + description: |- + The maximum number of snapshots to keep for the weekly schedule. + required: true + - !ruby/object:Api::Type::Integer + name: 'minute' + description: |- + Set the minute of the hour to create the snapshot (0-59), defaults to the top of the hour (0). + default_value: 0 + - !ruby/object:Api::Type::Integer + name: 'hour' + description: |- + Set the hour to create the snapshot (0-23), defaults to midnight (0). + default_value: 0 + - !ruby/object:Api::Type::String + name: 'day' + description: |- + Set the day or days of the week to make a snapshot. Accepts a comma separated days of the week. Defaults to 'Sunday'. + default_value: 'Sunday' + - !ruby/object:Api::Type::NestedObject + name: 'monthlySchedule' + description: |- + Monthly schedule policy. + properties: + - !ruby/object:Api::Type::Integer + name: 'snapshotsToKeep' + description: |- + The maximum number of snapshots to keep for the monthly schedule + required: true + - !ruby/object:Api::Type::Integer + name: 'minute' + description: |- + Set the minute of the hour to create the snapshot (0-59), defaults to the top of the hour (0). + default_value: 0 + - !ruby/object:Api::Type::Integer + name: 'hour' + description: |- + Set the hour to create the snapshot (0-23), defaults to midnight (0). + default_value: 0 + - !ruby/object:Api::Type::String + name: 'daysOfMonth' + description: |- + Set the day or days of the month to make a snapshot (1-31). Accepts a comma separated number of days. Defaults to '1'. + default_value: '1' +# This is disabled until we have support for backup resource and can test it. +# - !ruby/object:Api::Type::NestedObject +# name: restoreParameters +# description: Specifies the source information to create a volume from. +# immutable: true +# properties: +# - !ruby/object:Api::Type::String +# name: 'sourceSnapshot' +# description: |- +# Full name of the snapshot resource. Format: `projects/{{project}}/locations/{{location}}/volumes/{{volume}}/snapshots/{{snapshot}}`. +# required: true diff --git a/mmv1/products/netapp/volumereplication.yaml b/mmv1/products/netapp/volumereplication.yaml new file mode 100644 index 000000000000..2b08f2cb105a --- /dev/null +++ b/mmv1/products/netapp/volumereplication.yaml @@ -0,0 +1,205 @@ +# Copyright 2023 Google Inc. +# 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. + +--- !ruby/object:Api::Resource +name: 'volumereplication' +description: | + Replication is a nested resource under Volume, that describes a cross-region replication relationship between 2 volumes in different regions. +references: !ruby/object:Api::Resource::ReferenceLinks + guides: + 'QUICKSTART_TITLE': 'https://cloud.google.com/netapp/volumes/docs/reference/rest/v1/projects.locations.volumes.replications' + api: 'https://cloud.google.com/netapp/volumes/docs/reference/rest/v1/projects.locations.volumes.replications' +base_url: projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/replications +self_link: projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/replications/{{name}} +create_url: projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/replications?replicationId={{name}} +update_url: projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/replications/{{name}} +update_verb: :PATCH +update_mask: true +autogen_async: true +async: !ruby/object:Api::OpAsync + operation: !ruby/object:Api::OpAsync::Operation + base_url: '{{op_id}}' +id_format: 'projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/replications/{{name}}' +import_format: ['projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/replications/{{name}}'] +parameters: + - !ruby/object:Api::Type::String + name: 'location' + required: true + immutable: true + url_param_only: true + description: | + LOCATION_DESCRIPTION + - !ruby/object:Api::Type::String + name: 'volume_name' + description: + 'The unique name of the volume + `[_a-zA-Z0-9][-_.a-zA-Z0-9]*`.' + required: true + immutable: true + url_param_only: true + - !ruby/object:Api::Type::String + name: 'name' + description: + 'The unique name of the replication resource + `[_a-zA-Z0-9][-_.a-zA-Z0-9]*`.' + required: true + immutable: true + url_param_only: true +custom_code: !ruby/object:Provider::Terraform::CustomCode + pre_delete: templates/terraform/pre_delete/stop_replication.go.erb + post_delete: templates/terraform/post_delete/delete_destinition_rep_volume.go.erb + pre_update: templates/terraform/pre_update/volume_mirror_state.go.erb +examples: + - !ruby/object:Provider::Terraform::Examples + name: 'volume_replication_create' + primary_resource_id: 'test_replication' + vars: + pool_name: 'test-pool' + volume_name: 'test-volume' + replication_name: 'test-replication' + rep_vol_name: 'testrep-vol' + rep_share_name: 'rep-share' +virtual_fields: + - !ruby/object:Api::Type::Enum + name: 'delete_destination_volume' + description: | + wheter to delete the destination volume created or not- (Yes, No) + default_value: :No + values: + - :No + - :Yes + - !ruby/object:Api::Type::Enum + name: 'volume_replication_state' + description: | + Change replication state to Stop/resume. + values: + - :stop + - :resume +properties: + - !ruby/object:Api::Type::String + name: 'destinationVolume' + description: | + Full name of destination volume resource. Example : "projects/{project}/locations/{location}/volumes/{volumeId}" + output: true + - !ruby/object:Api::Type::String + name: 'sourceVolume' + description: | + Full name of source volume resource. Example : "projects/{project}/locations/{location}/volumes/{volumeId}" + output: true + - !ruby/object:Api::Type::Enum + name: 'role' + description: | + Indicates whether this points to source or destination. + values: + - :REPLICATION_ROLE_UNSPECIFIED + - :SOURCE + - :DESTINATION + output: true + - !ruby/object:Api::Type::Enum + name: 'replicationSchedule' + description: | + Indicates the schedule for replication. + values: + - :REPLICATION_SCHEDULE_UNSPECIFIED + - :EVERY_10_MINUTES + - :HOURLY + - :DAILY + required: true + - !ruby/object:Api::Type::Enum + name: 'mirrorState' + description: | + Indicates the state of mirroring. + values: + - :MIRROR_STATE_UNSPECIFIED + - :PREPARING + - :MIRRORED + - :STOPPED + - :TRANSFERRING + output: true + - !ruby/object:Api::Type::NestedObject + name: 'transferStats' + description: |- + Replication transfer statistics. + output: true + properties: + - !ruby/object:Api::Type::String + name: 'transferBytes' + description: | + bytes trasferred so far in current transfer. + - !ruby/object:Api::Type::String + name: 'totalTransferDuration' + description: | + Total time taken during transfer. + - !ruby/object:Api::Type::String + name: 'lastTransferBytes' + description: | + Last transfer size in bytes. + - !ruby/object:Api::Type::String + name: 'lastTransferDuration' + description: | + Time taken during last transfer. + - !ruby/object:Api::Type::String + name: 'lagDuration' + description: | + Lag duration indicates the duration by which Destination region volume content lags behind the primary region volume content. + - !ruby/object:Api::Type::String + name: 'updateTime' + description: | + Time when progress was updated last. + - !ruby/object:Api::Type::String + name: 'lastTransferEndTime' + description: | + Time when last transfer completed. + - !ruby/object:Api::Type::String + name: 'lastTransferError' + description: | + A message describing the cause of the last transfer failure. + - !ruby/object:Api::Type::KeyValueLabels + name: 'labels' + description: | + Labels with user-defined metadata. + This field may contain up to 64 entries. Label keys and values may be no + longer than 63 characters. Label keys must begin with a lowercase letter + and may only contain lowercase letters, Integer characters, underscores, + and dashes. + - !ruby/object:Api::Type::Boolean + name: 'healthy' + description: | + Condition of the relationship. Can be one of the following: - true: The replication relationship is healthy. It has not missed the most recent scheduled transfer. - false: The replication relationship is not healthy. It has missed the most recent scheduled transfer. + output: true + - !ruby/object:Api::Type::String + name: 'description' + description: | + A description about this replication relationship. + - !ruby/object:Api::Type::NestedObject + name: 'destinationVolumeParameters' + description: |- + Destination volume parameters. + ignore_read: true + properties: + - !ruby/object:Api::Type::String + name: 'storagePool' + description: | + Existing destination StoragePool name. + - !ruby/object:Api::Type::String + name: 'volumeId' + description: | + Desired destination volume resource id. If not specified, source volume's resource id will be used. This value must start with a lowercase letter followed by up to 62 lowercase letters, numbers, or hyphens, and cannot end with a hyphen. + - !ruby/object:Api::Type::String + name: 'shareName' + description: | + Destination volume's share name. If not specified, source volume's share name will be used. + - !ruby/object:Api::Type::String + name: 'description' + description: | + Description for the destination volume. diff --git a/mmv1/templates/terraform/examples/volume_basic.tf.erb b/mmv1/templates/terraform/examples/volume_basic.tf.erb new file mode 100644 index 000000000000..3f34bf06b56a --- /dev/null +++ b/mmv1/templates/terraform/examples/volume_basic.tf.erb @@ -0,0 +1,20 @@ +resource "google_netapp_storage_pool" "default" { + name = "<%= ctx[:vars]['pool_name'] %>" + location = "us-west2" + service_level = "PREMIUM" + capacity_gib = "2048" + network = data.google_compute_network.default.id +} + +resource "google_netapp_volume" "<%= ctx[:primary_resource_id] %>" { + location = "us-west2" + name = "<%= ctx[:vars]['volume_name'] %>" + capacity_gib = "100" + share_name = "<%= ctx[:vars]['volume_name'] %>" + storage_pool = google_netapp_storage_pool.default.name + protocols = ["NFSV3"] +} + +data "google_compute_network" "default" { + name = "<%= ctx[:vars]['network_name'] %>" +} diff --git a/mmv1/templates/terraform/examples/volume_replication_create.tf.erb b/mmv1/templates/terraform/examples/volume_replication_create.tf.erb new file mode 100644 index 000000000000..f29d8376ee80 --- /dev/null +++ b/mmv1/templates/terraform/examples/volume_replication_create.tf.erb @@ -0,0 +1,36 @@ +resource "google_netapp_storage_pool" "default" { + name = "<%= ctx[:vars]['pool_name'] %>" + location = "us-west2" + service_level = "PREMIUM" + capacity_gib = "2048" + network = data.google_compute_network.default.id +} + +resource "google_netapp_volume" "test_volume" { + location = "us-west2" + name = "<%= ctx[:vars]['volume_name'] %>" + capacity_gib = "100" + share_name = "<%= ctx[:vars]['volume_name'] %>" + storage_pool = google_netapp_storage_pool.default.name + protocols = ["NFSV3"] +} + +resource "google_netapp_volumereplication" "<%= ctx[:primary_resource_id] %>" { + depends_on = [google_netapp_volume.test_volume] + location = "us-central1" + volume_name = google_netapp_volume.test_volume.name + name = "<%= ctx[:vars]['replication_name'] %>" + replication_schedule = "DAILY" + delete_destination_volume = "Yes" + destination_volume_parameters { + storage_pool= google_netapp_storage_pool.default.name + volume_id= "<%= ctx[:vars]['rep_vol_name'] %>" + share_name= "<%= ctx[:vars]['rep_share_name'] %>" + description= "This is a replicated volume" + } + +} + +data "google_compute_network" "default" { + name = "<%= ctx[:vars]['network_name'] %>" +} \ No newline at end of file diff --git a/mmv1/templates/terraform/post_delete/delete_destinition_rep_volume.go.erb b/mmv1/templates/terraform/post_delete/delete_destinition_rep_volume.go.erb new file mode 100644 index 000000000000..ac321de863d6 --- /dev/null +++ b/mmv1/templates/terraform/post_delete/delete_destinition_rep_volume.go.erb @@ -0,0 +1,30 @@ +time.Sleep(20 * time.Second) +log.Printf("[DEBUG] value of destvol %q: %v", reflect.TypeOf(d.Get("destination_volume")), d.Get("destination_volume")) +if d.Get("delete_destination_volume").(string) == "Yes" { + str1 := d.Get("destination_volume").(string) + res1 := str1+"?force=true" + de_url, err := tpgresource.ReplaceVars(d, config, "{{NetappBasePath}}") + if err != nil { + return err + } + var obj1 map[string]interface{} + res2, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "DELETE", + Project: billingProject, + RawURL: de_url+res1, + UserAgent: userAgent, + Body: obj1, + Timeout: d.Timeout(schema.TimeoutDelete), + }) + if err != nil { + return transport_tpg.HandleNotFoundError(err, d, "volume") + } + err = NetappOperationWaitTime( + config, res2, project, "Deleting volume", userAgent, + d.Timeout(schema.TimeoutDelete)) + if err != nil { + return err + } + log.Printf("[DEBUG] Finished deleting volume %q: %#v", d.Id(), res2) + } \ No newline at end of file diff --git a/mmv1/templates/terraform/pre_delete/stop_replication.go.erb b/mmv1/templates/terraform/pre_delete/stop_replication.go.erb new file mode 100644 index 000000000000..5d88c6781435 --- /dev/null +++ b/mmv1/templates/terraform/pre_delete/stop_replication.go.erb @@ -0,0 +1,30 @@ +rawurl, err := tpgresource.ReplaceVars(d, config, "{{NetappBasePath}}projects/{{project}}/locations/{{location}}/volumes/{{volume_id}}/replications/{{name}}:stop") +if err != nil { + return err + } +reso, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "POST", + Project: billingProject, + RawURL: rawurl, + UserAgent: userAgent, + Body: obj, + Timeout: d.Timeout(schema.TimeoutUpdate), +<% if object.error_retry_predicates -%> + ErrorRetryPredicates: []transport_tpg.RetryErrorPredicateFunc{<%= object.error_retry_predicates.join(',') -%>}, +<% end -%> +<% if object.error_abort_predicates -%> + ErrorAbortPredicates: []transport_tpg.RetryErrorPredicateFunc{<%= object.error_abort_predicates.join(',') -%>}, +<% end -%> +}) +if err != nil { + return fmt.Errorf("Error stopping replication %q: %s", d.Id(), err) +} + +err = NetappOperationWaitTime( + config, reso, project, "Deleting volumereplication", userAgent, + d.Timeout(schema.TimeoutDelete)) + if err != nil { + return err + } + \ No newline at end of file diff --git a/mmv1/templates/terraform/pre_update/volume_mirror_state.go.erb b/mmv1/templates/terraform/pre_update/volume_mirror_state.go.erb new file mode 100644 index 000000000000..f40b82e92d90 --- /dev/null +++ b/mmv1/templates/terraform/pre_update/volume_mirror_state.go.erb @@ -0,0 +1,29 @@ +if d.Get("volume_replication_state").(string) != "" { + log.Printf("[DEBUG] value of replication_state : %v", d.Get("volume_replication_state")) + var obj map[string]interface{} + rawurl, err := tpgresource.ReplaceVars(d, config, "{{NetappBasePath}}projects/{{project}}/locations/{{location}}/volumes/{{volume_id}}/replications/{{name}}:") + if err != nil { + return err + } + rawurl = rawurl+d.Get("volume_replication_state").(string) + reso, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "POST", + Project: billingProject, + RawURL: rawurl, + UserAgent: userAgent, + Body: obj, + Timeout: d.Timeout(schema.TimeoutUpdate), + }) + if err != nil { + return fmt.Errorf("Error Changing replication state %q: %s", d.Id(), err) + } + + err = NetappOperationWaitTime( + config, reso, project, "Deleting volumereplication", userAgent, + d.Timeout(schema.TimeoutDelete)) + + if err != nil { + return err + } +} \ No newline at end of file diff --git a/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_test.go b/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_test.go new file mode 100644 index 000000000000..e402dd0d9d9c --- /dev/null +++ b/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_test.go @@ -0,0 +1,328 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: MMv1 *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package netapp_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + "github.com/hashicorp/terraform-provider-google/google/acctest" +) + +func TestAccNetappVolume_volumeBasicExample_update(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "network_name": acctest.BootstrapSharedServiceNetworkingConnection(t, "gcnv-network-config-1", acctest.ServiceNetworkWithParentService("netapp.servicenetworking.goog")), + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckNetappVolumeDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccNetappVolume_volumeBasicExample_basic(context), + }, + { + ResourceName: "google_netapp_volume.test_volume", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location", "name", "labels", "terraform_labels"}, + }, { + Config: testAccNetappVolume_volumeBasicExample_full(context), + }, + { + ResourceName: "google_netapp_volume.test_volume", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location", "name", "labels", "terraform_labels"}, + }, + { + Config: testAccNetappVolume_volumeBasicExample_update(context), + }, + { + ResourceName: "google_netapp_volume.test_volume", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location", "name", "labels", "terraform_labels"}, + }, + { + Config: testAccNetappVolume_volumeBasicExample_updatesnapshot(context), + }, + { + ResourceName: "google_netapp_volume.test_volume", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location", "name", "labels", "terraform_labels"}, + }, + }, + }) +} + +func testAccNetappVolume_volumeBasicExample_basic(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_netapp_storage_pool" "default" { + name = "tf-test-test-pool%{random_suffix}" + location = "us-west2" + service_level = "PREMIUM" + capacity_gib = "2048" + network = data.google_compute_network.default.id +} + +resource "google_netapp_volume" "test_volume" { + location = "us-west2" + name = "tf-test-test-volume%{random_suffix}" + capacity_gib = "100" + share_name = "tf-test-test-volume%{random_suffix}" + storage_pool = google_netapp_storage_pool.default.name + protocols = ["NFSV3"] +} + +data "google_compute_network" "default" { + name = "%{network_name}" +} +`, context) +} + +func testAccNetappVolume_volumeBasicExample_full(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_netapp_storage_pool" "default" { + name = "tf-test-test-pool%{random_suffix}" + location = "us-west2" + service_level = "PREMIUM" + capacity_gib = "2048" + network = data.google_compute_network.default.id +} + +resource "google_netapp_storage_pool" "default2" { + name = "tf-test-pool%{random_suffix}" + location = "us-west2" + service_level = "EXTREME" + capacity_gib = "2048" + network = data.google_compute_network.default.id +} + +resource "google_netapp_volume" "test_volume" { + location = "us-west2" + name = "tf-test-test-volume%{random_suffix}" + capacity_gib = "100" + share_name = "tf-test-test-volume%{random_suffix}" + storage_pool = google_netapp_storage_pool.default.name + protocols = ["NFSV3"] + smb_settings = [] + unix_permissions = "0770" + labels = { + key= "test" + value= "pool" + } + description = "This is a test description" + snapshot_directory = false + security_style = "UNIX" + kerberos_enabled = false + export_policy { + rules { + access_type = "READ_ONLY" + allowed_clients = "0.0.0.0/0" + has_root_access = "false" + kerberos5_read_only = false + kerberos5_read_write = false + kerberos5i_read_only = false + kerberos5i_read_write = false + kerberos5p_read_only = false + kerberos5p_read_write = false + nfsv3 = true + nfsv4 = false + } + rules { + access_type = "READ_WRITE" + allowed_clients = "10.2.3.4,10.2.3.5" + has_root_access = "true" + kerberos5_read_only = false + kerberos5_read_write = false + kerberos5i_read_only = false + kerberos5i_read_write = false + kerberos5p_read_only = false + kerberos5p_read_write = false + nfsv3 = true + nfsv4 = false + } + } + restricted_actions = [] + snapshot_policy { + daily_schedule { + snapshots_to_keep = 2 + } + enabled = true + hourly_schedule { + snapshots_to_keep = 2 + } + monthly_schedule { + snapshots_to_keep = 4 + } + weekly_schedule { + snapshots_to_keep = 2 + } + } +} + +data "google_compute_network" "default" { + name = "%{network_name}" +} + `, context) +} + +func testAccNetappVolume_volumeBasicExample_update(context map[string]interface{}) string { + return acctest.Nprintf(` + +resource "google_netapp_storage_pool" "default" { + name = "tf-test-test-pool%{random_suffix}" + location = "us-west2" + service_level = "PREMIUM" + capacity_gib = "2048" + network = data.google_compute_network.default.id +} + +resource "google_netapp_storage_pool" "default2" { + name = "tf-test-pool%{random_suffix}" + location = "us-west2" + service_level = "EXTREME" + capacity_gib = "2048" + network = data.google_compute_network.default.id +} + +resource "google_netapp_volume" "test_volume" { + location = "us-west2" + name = "tf-test-test-volume%{random_suffix}" + capacity_gib = "200" + share_name = "tf-test-test-volume%{random_suffix}" + storage_pool = google_netapp_storage_pool.default2.name + protocols = ["NFSV3"] + smb_settings = [] + unix_permissions = "0740" + labels = {} + description = "" + snapshot_directory = true + security_style = "UNIX" + kerberos_enabled = false + export_policy { + rules { + access_type = "READ_WRITE" + allowed_clients = "0.0.0.0/0" + has_root_access = "true" + kerberos5_read_only = false + kerberos5_read_write = false + kerberos5i_read_only = false + kerberos5i_read_write = false + kerberos5p_read_only = false + kerberos5p_read_write = false + nfsv3 = true + nfsv4 = false + } + } + restricted_actions = ["DELETE"] + snapshot_policy { + enabled = true + daily_schedule { + hour = 1 + minute = 2 + snapshots_to_keep = 1 + } + hourly_schedule { + minute = 10 + snapshots_to_keep = 1 + } + monthly_schedule { + days_of_month = "2" + hour = 3 + minute = 4 + snapshots_to_keep = 1 + } + weekly_schedule { + day = "Monday" + hour = 5 + minute = 6 + snapshots_to_keep = 1 + } + } +} + +data "google_compute_network" "default" { + name = "%{network_name}" +} + `, context) +} + +func testAccNetappVolume_volumeBasicExample_updatesnapshot(context map[string]interface{}) string { + return acctest.Nprintf(` + +resource "google_netapp_storage_pool" "default" { + name = "tf-test-test-pool%{random_suffix}" + location = "us-west2" + service_level = "PREMIUM" + capacity_gib = "2048" + network = data.google_compute_network.default.id +} + +resource "google_netapp_storage_pool" "default2" { + name = "tf-test-pool%{random_suffix}" + location = "us-west2" + service_level = "EXTREME" + capacity_gib = "2048" + network = data.google_compute_network.default.id +} + +resource "google_netapp_volume" "test_volume" { + location = "us-west2" + name = "tf-test-test-volume%{random_suffix}" + capacity_gib = "200" + share_name = "tf-test-test-volume%{random_suffix}" + storage_pool = google_netapp_storage_pool.default2.name + protocols = ["NFSV3"] + smb_settings = [] + unix_permissions = "0740" + labels = {} + description = "" + snapshot_directory = true + security_style = "UNIX" + kerberos_enabled = false + export_policy { + rules { + access_type = "READ_WRITE" + allowed_clients = "0.0.0.0/0" + has_root_access = "true" + kerberos5_read_only = false + kerberos5_read_write = false + kerberos5i_read_only = false + kerberos5i_read_write = false + kerberos5p_read_only = false + kerberos5p_read_write = false + nfsv3 = true + nfsv4 = false + } + } + restricted_actions = ["DELETE"] + } + +data "google_compute_network" "default" { + name = "%{network_name}" +} + `, context) +} From bcae2a20e59ef9fed46b59afa0f036d6c56a409b Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Fri, 12 Jan 2024 20:39:17 +0100 Subject: [PATCH 02/34] Cleanup work - Renamed a lot of files to make clear which resource the belong to - Updated documentation for resource fields - Renamed a few resource fields and changed some types - Disabled the custom code for now. Needs to be discussed first --- mmv1/products/netapp/volumereplication.yaml | 106 ++++++++++-------- .../netapp_volume_replication_create.tf.erb | 46 ++++++++ .../examples/volume_replication_create.tf.erb | 36 ------ ...lumereplication_delete_destination.go.erb} | 2 +- ...b => netapp_volumereplication_stop.go.erb} | 6 +- ...tapp_volumereplication_mirror_state.go.erb | 32 ++++++ .../pre_update/volume_mirror_state.go.erb | 29 ----- 7 files changed, 139 insertions(+), 118 deletions(-) create mode 100644 mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb delete mode 100644 mmv1/templates/terraform/examples/volume_replication_create.tf.erb rename mmv1/templates/terraform/post_delete/{delete_destinition_rep_volume.go.erb => netapp_volumereplication_delete_destination.go.erb} (95%) rename mmv1/templates/terraform/pre_delete/{stop_replication.go.erb => netapp_volumereplication_stop.go.erb} (94%) create mode 100644 mmv1/templates/terraform/pre_update/netapp_volumereplication_mirror_state.go.erb delete mode 100644 mmv1/templates/terraform/pre_update/volume_mirror_state.go.erb diff --git a/mmv1/products/netapp/volumereplication.yaml b/mmv1/products/netapp/volumereplication.yaml index 2b08f2cb105a..fa418e926f65 100644 --- a/mmv1/products/netapp/volumereplication.yaml +++ b/mmv1/products/netapp/volumereplication.yaml @@ -14,10 +14,14 @@ --- !ruby/object:Api::Resource name: 'volumereplication' description: | - Replication is a nested resource under Volume, that describes a cross-region replication relationship between 2 volumes in different regions. + You can protect your data through cross-location volume replication, which asynchronously replicates + a source volume in one location to a destination volume in a different location. This capability lets + you use the replicated volume for critical application activity in case of a location-wide outage or + disaster. The replicated volume can also be used as a read-only copy while the mirror is enabled, or + as a independent volume if the mirror is stopped. references: !ruby/object:Api::Resource::ReferenceLinks guides: - 'QUICKSTART_TITLE': 'https://cloud.google.com/netapp/volumes/docs/reference/rest/v1/projects.locations.volumes.replications' + 'Documentation': 'https://cloud.google.com/netapp/volumes/docs/protect-data/about-volume-replication' api: 'https://cloud.google.com/netapp/volumes/docs/reference/rest/v1/projects.locations.volumes.replications' base_url: projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/replications self_link: projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/replications/{{name}} @@ -38,63 +42,65 @@ parameters: immutable: true url_param_only: true description: | - LOCATION_DESCRIPTION + Name of region for this resource. The resource needs to be created in the region of the destination volume. - !ruby/object:Api::Type::String name: 'volume_name' description: - 'The unique name of the volume - `[_a-zA-Z0-9][-_.a-zA-Z0-9]*`.' + The name of the existing source volume. required: true immutable: true url_param_only: true - !ruby/object:Api::Type::String name: 'name' description: - 'The unique name of the replication resource - `[_a-zA-Z0-9][-_.a-zA-Z0-9]*`.' + The name of the replication. Needs to be unique per location. required: true immutable: true url_param_only: true -custom_code: !ruby/object:Provider::Terraform::CustomCode - pre_delete: templates/terraform/pre_delete/stop_replication.go.erb - post_delete: templates/terraform/post_delete/delete_destinition_rep_volume.go.erb - pre_update: templates/terraform/pre_update/volume_mirror_state.go.erb +# The following functionality needs to be discussed in review process +# custom_code: !ruby/object:Provider::Terraform::CustomCode +# pre_delete: templates/terraform/pre_delete/netapp_volumereplication_stop.go.erb +# post_delete: templates/terraform/post_delete/netapp_volumereplication_delete_destination.go.erb +# pre_update: templates/terraform/pre_update/netapp_volumereplication_mirror_state.go.erb examples: - !ruby/object:Provider::Terraform::Examples - name: 'volume_replication_create' + name: 'netapp_volume_replication_create' primary_resource_id: 'test_replication' vars: - pool_name: 'test-pool' - volume_name: 'test-volume' + source_pool_name: 'source-pool' + destination_pool_name: 'destination-pool' + volume_name: 'source-volume' replication_name: 'test-replication' rep_vol_name: 'testrep-vol' rep_share_name: 'rep-share' + network_name: "shared-vpc-prod" + test_vars_overrides: + network_name: 'acctest.BootstrapSharedServiceNetworkingConnection(t, "gcnv-network-config-1", acctest.ServiceNetworkWithParentService("netapp.servicenetworking.goog"))' virtual_fields: - - !ruby/object:Api::Type::Enum + - !ruby/object:Api::Type::Boolean name: 'delete_destination_volume' description: | - wheter to delete the destination volume created or not- (Yes, No) - default_value: :No - values: - - :No - - :Yes - - !ruby/object:Api::Type::Enum - name: 'volume_replication_state' + Delete destination volume when replication resource is destroyed? Default is false. + default_value: false + - !ruby/object:Api::Type::Boolean + name: 'replication_enabled' description: | - Change replication state to Stop/resume. - values: - - :stop - - :resume + Set to false to stop/break the mirror. Stopping the mirror makes the destination volume read-write + and act independently from the source volume. + + Set to true to enable/resume the mirror. WARNING: Resuming a mirror overwrites the destination volume with the content + of the source volume and resumes replication updates from source to destination according to replication schedule. + default_value: true properties: - !ruby/object:Api::Type::String name: 'destinationVolume' description: | - Full name of destination volume resource. Example : "projects/{project}/locations/{location}/volumes/{volumeId}" + Full resource name of destination volume with format: `projects/{{project}}/locations/{{location}}/volumes/{{volumeId}}` output: true - !ruby/object:Api::Type::String name: 'sourceVolume' description: | - Full name of source volume resource. Example : "projects/{project}/locations/{location}/volumes/{volumeId}" + Full resource name of source volume with format: `projects/{{project}}/locations/{{location}}/volumes/{{volumeId}}` output: true - !ruby/object:Api::Type::Enum name: 'role' @@ -108,9 +114,8 @@ properties: - !ruby/object:Api::Type::Enum name: 'replicationSchedule' description: | - Indicates the schedule for replication. + Specifies the replication interval. values: - - :REPLICATION_SCHEDULE_UNSPECIFIED - :EVERY_10_MINUTES - :HOURLY - :DAILY @@ -118,7 +123,7 @@ properties: - !ruby/object:Api::Type::Enum name: 'mirrorState' description: | - Indicates the state of mirroring. + Indicates the state of the mirror between source and destination volumes. values: - :MIRROR_STATE_UNSPECIFIED - :PREPARING @@ -129,37 +134,39 @@ properties: - !ruby/object:Api::Type::NestedObject name: 'transferStats' description: |- - Replication transfer statistics. + Replication transfer statistics. All statistics are updated every 5 minutes. output: true properties: - !ruby/object:Api::Type::String name: 'transferBytes' description: | - bytes trasferred so far in current transfer. + Number of bytes transferred so far in current transfer. - !ruby/object:Api::Type::String name: 'totalTransferDuration' description: | - Total time taken during transfer. + Total time taken so far during current transfer. - !ruby/object:Api::Type::String name: 'lastTransferBytes' description: | - Last transfer size in bytes. + Size of last completed transfer in bytes. - !ruby/object:Api::Type::String name: 'lastTransferDuration' description: | - Time taken during last transfer. + Time taken during last completed transfer. - !ruby/object:Api::Type::String name: 'lagDuration' description: | - Lag duration indicates the duration by which Destination region volume content lags behind the primary region volume content. + The elapsed time since the creation of the snapshot on the source volume that was last replicated + to the destination volume. Lag time represents the difference in age of the destination volume + data in relation to the source volume data. - !ruby/object:Api::Type::String name: 'updateTime' description: | - Time when progress was updated last. + Time when progress was updated last. A timestamp in RFC3339 UTC "Zulu" format. Examples: "2023-06-22T09:13:01.617Z". - !ruby/object:Api::Type::String name: 'lastTransferEndTime' description: | - Time when last transfer completed. + Time when last transfer completed. A timestamp in RFC3339 UTC "Zulu" format. Examples: "2023-06-22T09:13:01.617Z". - !ruby/object:Api::Type::String name: 'lastTransferError' description: | @@ -167,20 +174,18 @@ properties: - !ruby/object:Api::Type::KeyValueLabels name: 'labels' description: | - Labels with user-defined metadata. - This field may contain up to 64 entries. Label keys and values may be no - longer than 63 characters. Label keys must begin with a lowercase letter - and may only contain lowercase letters, Integer characters, underscores, - and dashes. + Labels as key value pairs. Example: `{ "owner": "Bob", "department": "finance", "purpose": "testing" }`. - !ruby/object:Api::Type::Boolean name: 'healthy' description: | - Condition of the relationship. Can be one of the following: - true: The replication relationship is healthy. It has not missed the most recent scheduled transfer. - false: The replication relationship is not healthy. It has missed the most recent scheduled transfer. + Condition of the relationship. Can be one of the following: + - true: The replication relationship is healthy. It has not missed the most recent scheduled transfer. + - false: The replication relationship is not healthy. It has missed the most recent scheduled transfer. output: true - !ruby/object:Api::Type::String name: 'description' description: | - A description about this replication relationship. + An description of this resource. - !ruby/object:Api::Type::NestedObject name: 'destinationVolumeParameters' description: |- @@ -190,15 +195,18 @@ properties: - !ruby/object:Api::Type::String name: 'storagePool' description: | - Existing destination StoragePool name. + Name of an extisting storage pool for the destination volume. + required: true - !ruby/object:Api::Type::String name: 'volumeId' description: | - Desired destination volume resource id. If not specified, source volume's resource id will be used. This value must start with a lowercase letter followed by up to 62 lowercase letters, numbers, or hyphens, and cannot end with a hyphen. + Name for the destination volume to be created. If not specified, the name of the source volume will be used. + default_from_api: true - !ruby/object:Api::Type::String name: 'shareName' description: | - Destination volume's share name. If not specified, source volume's share name will be used. + Share name for destination volume. If not specified, name of source volume's share name will be used. + default_from_api: true - !ruby/object:Api::Type::String name: 'description' description: | diff --git a/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb b/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb new file mode 100644 index 000000000000..ef520296d981 --- /dev/null +++ b/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb @@ -0,0 +1,46 @@ + +data "google_compute_network""default"{ + name = "<%= ctx[:vars]['network_name'] %>" +} + +resource "google_netapp_storage_pool""source_pool"{ + name = "<%= ctx[:vars]['source_pool_name'] %>" + location = "us-west2" + service_level = "PREMIUM" + capacity_gib = 2048 + network = data.google_compute_network.default.id +} + +resource "google_netapp_storage_pool""destination_pool"{ + name = "<%= ctx[:vars]['destination_pool_name'] %>" + location = "us-central1" + service_level = "PREMIUM" + capacity_gib = 2048 + network = data.google_compute_network.default.id +} + +resource "google_netapp_volume""source_volume"{ + location = google_netapp_storage_pool.source_pool.location + name = "<%= ctx[:vars]['volume_name'] %>" + capacity_gib = 100 + share_name = "<%= ctx[:vars]['volume_name'] %>" + storage_pool = google_netapp_storage_pool.source_pool.name + protocols = [ + "NFSV3" + ] +} + +resource "google_netapp_volumereplication""<%= ctx[:primary_resource_id] %>"{ + depends_on = [google_netapp_volume.test_volume + ] + location = "us-central1" + volume_name = google_netapp_volume.test_volume.name + name = "<%= ctx[:vars]['replication_name'] %>" + replication_schedule = "DAILY" + destination_volume_parameters { + storage_pool= google_netapp_storage_pool.default.name + volume_id= "<%= ctx[:vars]['rep_vol_name'] %>" + share_name= "<%= ctx[:vars]['rep_share_name'] %>" + description= "This is a replicated volume" + } +} \ No newline at end of file diff --git a/mmv1/templates/terraform/examples/volume_replication_create.tf.erb b/mmv1/templates/terraform/examples/volume_replication_create.tf.erb deleted file mode 100644 index f29d8376ee80..000000000000 --- a/mmv1/templates/terraform/examples/volume_replication_create.tf.erb +++ /dev/null @@ -1,36 +0,0 @@ -resource "google_netapp_storage_pool" "default" { - name = "<%= ctx[:vars]['pool_name'] %>" - location = "us-west2" - service_level = "PREMIUM" - capacity_gib = "2048" - network = data.google_compute_network.default.id -} - -resource "google_netapp_volume" "test_volume" { - location = "us-west2" - name = "<%= ctx[:vars]['volume_name'] %>" - capacity_gib = "100" - share_name = "<%= ctx[:vars]['volume_name'] %>" - storage_pool = google_netapp_storage_pool.default.name - protocols = ["NFSV3"] -} - -resource "google_netapp_volumereplication" "<%= ctx[:primary_resource_id] %>" { - depends_on = [google_netapp_volume.test_volume] - location = "us-central1" - volume_name = google_netapp_volume.test_volume.name - name = "<%= ctx[:vars]['replication_name'] %>" - replication_schedule = "DAILY" - delete_destination_volume = "Yes" - destination_volume_parameters { - storage_pool= google_netapp_storage_pool.default.name - volume_id= "<%= ctx[:vars]['rep_vol_name'] %>" - share_name= "<%= ctx[:vars]['rep_share_name'] %>" - description= "This is a replicated volume" - } - -} - -data "google_compute_network" "default" { - name = "<%= ctx[:vars]['network_name'] %>" -} \ No newline at end of file diff --git a/mmv1/templates/terraform/post_delete/delete_destinition_rep_volume.go.erb b/mmv1/templates/terraform/post_delete/netapp_volumereplication_delete_destination.go.erb similarity index 95% rename from mmv1/templates/terraform/post_delete/delete_destinition_rep_volume.go.erb rename to mmv1/templates/terraform/post_delete/netapp_volumereplication_delete_destination.go.erb index ac321de863d6..e1d8286690a9 100644 --- a/mmv1/templates/terraform/post_delete/delete_destinition_rep_volume.go.erb +++ b/mmv1/templates/terraform/post_delete/netapp_volumereplication_delete_destination.go.erb @@ -1,6 +1,6 @@ time.Sleep(20 * time.Second) log.Printf("[DEBUG] value of destvol %q: %v", reflect.TypeOf(d.Get("destination_volume")), d.Get("destination_volume")) -if d.Get("delete_destination_volume").(string) == "Yes" { +if d.Get("delete_destination_volume").(bool) == true { str1 := d.Get("destination_volume").(string) res1 := str1+"?force=true" de_url, err := tpgresource.ReplaceVars(d, config, "{{NetappBasePath}}") diff --git a/mmv1/templates/terraform/pre_delete/stop_replication.go.erb b/mmv1/templates/terraform/pre_delete/netapp_volumereplication_stop.go.erb similarity index 94% rename from mmv1/templates/terraform/pre_delete/stop_replication.go.erb rename to mmv1/templates/terraform/pre_delete/netapp_volumereplication_stop.go.erb index 5d88c6781435..f72318a37879 100644 --- a/mmv1/templates/terraform/pre_delete/stop_replication.go.erb +++ b/mmv1/templates/terraform/pre_delete/netapp_volumereplication_stop.go.erb @@ -24,7 +24,7 @@ if err != nil { err = NetappOperationWaitTime( config, reso, project, "Deleting volumereplication", userAgent, d.Timeout(schema.TimeoutDelete)) - if err != nil { - return err - } +if err != nil { + return err +} \ No newline at end of file diff --git a/mmv1/templates/terraform/pre_update/netapp_volumereplication_mirror_state.go.erb b/mmv1/templates/terraform/pre_update/netapp_volumereplication_mirror_state.go.erb new file mode 100644 index 000000000000..7a3149b773fa --- /dev/null +++ b/mmv1/templates/terraform/pre_update/netapp_volumereplication_mirror_state.go.erb @@ -0,0 +1,32 @@ +log.Printf("[DEBUG] value of replication_state : %v", d.Get("replication_enabled")) +var obj map[string]interface{} +rawurl, err := tpgresource.ReplaceVars(d, config, "{{NetappBasePath}}projects/{{project}}/locations/{{location}}/volumes/{{volume_id}}/replications/{{name}}:") +if err != nil { + return err +} + +if d.Get("replication_enabled").(bool) == true { + rawurl = rawurl + "resume" +} else { + rawurl = rawurl + "stop" +} +reso, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "POST", + Project: billingProject, + RawURL: rawurl, + UserAgent: userAgent, + Body: obj, + Timeout: d.Timeout(schema.TimeoutUpdate), +}) +if err != nil { + return fmt.Errorf("Error stopping/resuming replication %q: %s", d.Id(), err) +} + +err = NetappOperationWaitTime( + config, reso, project, "Deleting volumereplication", userAgent, + d.Timeout(schema.TimeoutDelete)) + +if err != nil { + return err +} diff --git a/mmv1/templates/terraform/pre_update/volume_mirror_state.go.erb b/mmv1/templates/terraform/pre_update/volume_mirror_state.go.erb deleted file mode 100644 index f40b82e92d90..000000000000 --- a/mmv1/templates/terraform/pre_update/volume_mirror_state.go.erb +++ /dev/null @@ -1,29 +0,0 @@ -if d.Get("volume_replication_state").(string) != "" { - log.Printf("[DEBUG] value of replication_state : %v", d.Get("volume_replication_state")) - var obj map[string]interface{} - rawurl, err := tpgresource.ReplaceVars(d, config, "{{NetappBasePath}}projects/{{project}}/locations/{{location}}/volumes/{{volume_id}}/replications/{{name}}:") - if err != nil { - return err - } - rawurl = rawurl+d.Get("volume_replication_state").(string) - reso, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ - Config: config, - Method: "POST", - Project: billingProject, - RawURL: rawurl, - UserAgent: userAgent, - Body: obj, - Timeout: d.Timeout(schema.TimeoutUpdate), - }) - if err != nil { - return fmt.Errorf("Error Changing replication state %q: %s", d.Id(), err) - } - - err = NetappOperationWaitTime( - config, reso, project, "Deleting volumereplication", userAgent, - d.Timeout(schema.TimeoutDelete)) - - if err != nil { - return err - } -} \ No newline at end of file From 76bc059689cba756c49eeee94abb612287ef6b83 Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Fri, 12 Jan 2024 20:55:14 +0100 Subject: [PATCH 03/34] Update example file --- .../netapp_volume_replication_create.tf.erb | 51 +++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb b/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb index ef520296d981..dd7346110e7b 100644 --- a/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb +++ b/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb @@ -1,46 +1,45 @@ -data "google_compute_network""default"{ +data "google_compute_network" "default" { name = "<%= ctx[:vars]['network_name'] %>" } -resource "google_netapp_storage_pool""source_pool"{ - name = "<%= ctx[:vars]['source_pool_name'] %>" - location = "us-west2" +resource "google_netapp_storage_pool" "source_pool" { + name = "<%= ctx[:vars]['source_pool_name'] %>" + location = "us-west2" service_level = "PREMIUM" - capacity_gib = 2048 - network = data.google_compute_network.default.id + capacity_gib = 2048 + network = data.google_compute_network.default.id } -resource "google_netapp_storage_pool""destination_pool"{ - name = "<%= ctx[:vars]['destination_pool_name'] %>" - location = "us-central1" +resource "google_netapp_storage_pool" "destination_pool" { + name = "<%= ctx[:vars]['destination_pool_name'] %>" + location = "us-central1" service_level = "PREMIUM" - capacity_gib = 2048 - network = data.google_compute_network.default.id + capacity_gib = 2048 + network = data.google_compute_network.default.id } -resource "google_netapp_volume""source_volume"{ - location = google_netapp_storage_pool.source_pool.location - name = "<%= ctx[:vars]['volume_name'] %>" +resource "google_netapp_volume" "source_volume" { + location = google_netapp_storage_pool.source_pool.location + name = "<%= ctx[:vars]['volume_name'] %>" capacity_gib = 100 - share_name = "<%= ctx[:vars]['volume_name'] %>" + share_name = "<%= ctx[:vars]['volume_name'] %>" storage_pool = google_netapp_storage_pool.source_pool.name protocols = [ "NFSV3" ] } -resource "google_netapp_volumereplication""<%= ctx[:primary_resource_id] %>"{ - depends_on = [google_netapp_volume.test_volume - ] - location = "us-central1" - volume_name = google_netapp_volume.test_volume.name - name = "<%= ctx[:vars]['replication_name'] %>" +resource "google_netapp_volumereplication" "my-replication" { + depends_on = [google_netapp_volume.test_volume] + location = "us-central1" + volume_name = google_netapp_volume.test_volume.name + name = "<%= ctx[:vars]['replication_name'] %>" replication_schedule = "DAILY" destination_volume_parameters { - storage_pool= google_netapp_storage_pool.default.name - volume_id= "<%= ctx[:vars]['rep_vol_name'] %>" - share_name= "<%= ctx[:vars]['rep_share_name'] %>" - description= "This is a replicated volume" + storage_pool = google_netapp_storage_pool.default.name + volume_id = "<%= ctx[:vars]['rep_vol_name'] %>" + share_name = "<%= ctx[:vars]['rep_share_name'] %>" + description = "This is a replicated volume" } -} \ No newline at end of file +} From c10c2ef460bbd966309268debf23a385818e6eb5 Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Mon, 15 Jan 2024 11:35:09 +0100 Subject: [PATCH 04/34] Updated example file --- .../netapp_volume_replication_create.tf.erb | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb b/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb index dd7346110e7b..83a6c3c37e71 100644 --- a/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb +++ b/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb @@ -5,7 +5,7 @@ data "google_compute_network" "default" { resource "google_netapp_storage_pool" "source_pool" { name = "<%= ctx[:vars]['source_pool_name'] %>" - location = "us-west2" + location = "us-central1" service_level = "PREMIUM" capacity_gib = 2048 network = data.google_compute_network.default.id @@ -13,7 +13,7 @@ resource "google_netapp_storage_pool" "source_pool" { resource "google_netapp_storage_pool" "destination_pool" { name = "<%= ctx[:vars]['destination_pool_name'] %>" - location = "us-central1" + location = "us-west2" service_level = "PREMIUM" capacity_gib = 2048 network = data.google_compute_network.default.id @@ -31,15 +31,17 @@ resource "google_netapp_volume" "source_volume" { } resource "google_netapp_volumereplication" "my-replication" { - depends_on = [google_netapp_volume.test_volume] - location = "us-central1" - volume_name = google_netapp_volume.test_volume.name + depends_on = [google_netapp_volume.source_volume] + location = google_netapp_volume.source_volume.location + volume_name = google_netapp_volume.source_volume.name name = "<%= ctx[:vars]['replication_name'] %>" - replication_schedule = "DAILY" + replication_schedule = "EVERY_10_MINUTES" destination_volume_parameters { - storage_pool = google_netapp_storage_pool.default.name - volume_id = "<%= ctx[:vars]['rep_vol_name'] %>" - share_name = "<%= ctx[:vars]['rep_share_name'] %>" + storage_pool = google_netapp_storage_pool.destination_pool.id + volume_id = "<%= ctx[:vars]['destination_volume'] %>" + # Keeping the share_name of source and destination the same makes + # simplifies implementing client failover concepts + share_name = "<%= ctx[:vars]['volume_name'] %>" description = "This is a replicated volume" } } From 8dfb61e96dd6fe7818b9836835d14a4a21608f36 Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Mon, 15 Jan 2024 11:37:14 +0100 Subject: [PATCH 05/34] Major updates - Reorganisation of block - Reorganisation of fields to match API documentation - Updated example parameters - Added missing API fields - Improved descriptions - --- mmv1/products/netapp/volumereplication.yaml | 128 ++++++++++++-------- 1 file changed, 77 insertions(+), 51 deletions(-) diff --git a/mmv1/products/netapp/volumereplication.yaml b/mmv1/products/netapp/volumereplication.yaml index fa418e926f65..94f19a895f28 100644 --- a/mmv1/products/netapp/volumereplication.yaml +++ b/mmv1/products/netapp/volumereplication.yaml @@ -14,11 +14,12 @@ --- !ruby/object:Api::Resource name: 'volumereplication' description: | - You can protect your data through cross-location volume replication, which asynchronously replicates + Volume replication creates a copy of our volume byasynchronously replicate a source volume in one location to a destination volume in a different location. This capability lets you use the replicated volume for critical application activity in case of a location-wide outage or disaster. The replicated volume can also be used as a read-only copy while the mirror is enabled, or - as a independent volume if the mirror is stopped. + as a independent volume if the mirror is stopped. A destination volume will also contain the + snapshots of the source volume. references: !ruby/object:Api::Resource::ReferenceLinks guides: 'Documentation': 'https://cloud.google.com/netapp/volumes/docs/protect-data/about-volume-replication' @@ -35,6 +36,20 @@ async: !ruby/object:Api::OpAsync base_url: '{{op_id}}' id_format: 'projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/replications/{{name}}' import_format: ['projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/replications/{{name}}'] +examples: + - !ruby/object:Provider::Terraform::Examples + name: 'netapp_volume_replication_create' + primary_resource_id: 'test_replication' + vars: + source_pool_name: 'source-pool' + destination_pool_name: 'destination-pool' + volume_name: 'source-volume' + replication_name: 'test-replication' + destination_volume: 'destination-volume' + network_name: 'test-network' + test_vars_overrides: + network_name: '"shared-vpc-prod"' + # network_name: 'acctest.BootstrapSharedServiceNetworkingConnection(t, "gcnv-network-config-1", acctest.ServiceNetworkWithParentService("netapp.servicenetworking.goog"))' parameters: - !ruby/object:Api::Type::String name: 'location' @@ -62,50 +77,30 @@ parameters: # pre_delete: templates/terraform/pre_delete/netapp_volumereplication_stop.go.erb # post_delete: templates/terraform/post_delete/netapp_volumereplication_delete_destination.go.erb # pre_update: templates/terraform/pre_update/netapp_volumereplication_mirror_state.go.erb -examples: - - !ruby/object:Provider::Terraform::Examples - name: 'netapp_volume_replication_create' - primary_resource_id: 'test_replication' - vars: - source_pool_name: 'source-pool' - destination_pool_name: 'destination-pool' - volume_name: 'source-volume' - replication_name: 'test-replication' - rep_vol_name: 'testrep-vol' - rep_share_name: 'rep-share' - network_name: "shared-vpc-prod" - test_vars_overrides: - network_name: 'acctest.BootstrapSharedServiceNetworkingConnection(t, "gcnv-network-config-1", acctest.ServiceNetworkWithParentService("netapp.servicenetworking.goog"))' -virtual_fields: - - !ruby/object:Api::Type::Boolean - name: 'delete_destination_volume' - description: | - Delete destination volume when replication resource is destroyed? Default is false. - default_value: false - - !ruby/object:Api::Type::Boolean - name: 'replication_enabled' - description: | - Set to false to stop/break the mirror. Stopping the mirror makes the destination volume read-write - and act independently from the source volume. - - Set to true to enable/resume the mirror. WARNING: Resuming a mirror overwrites the destination volume with the content - of the source volume and resumes replication updates from source to destination according to replication schedule. - default_value: true properties: - - !ruby/object:Api::Type::String - name: 'destinationVolume' + - !ruby/object:Api::Type::Enum + name: 'state' description: | - Full resource name of destination volume with format: `projects/{{project}}/locations/{{location}}/volumes/{{volumeId}}` + Indicates the state of replication resource. State of the mirror itself is indicated in mirrorState. + values: + - :STATE_UNSPECIFIED + - :CREATING + - :READY + - :UPDATING + - :DELETING + - :ERROR output: true - !ruby/object:Api::Type::String - name: 'sourceVolume' + name: 'stateDetails' description: | - Full resource name of source volume with format: `projects/{{project}}/locations/{{location}}/volumes/{{volumeId}}` + State details of the replication resource. output: true - !ruby/object:Api::Type::Enum name: 'role' description: | - Indicates whether this points to source or destination. + Reverting a replication can swap source and destination volume roles. This field indicates if the `location` hosts + the source or destination volume. For resume and revert and resume operations it is critical to understand + which volume is the source volume, since it will overwrite changes done to the destination volume. values: - :REPLICATION_ROLE_UNSPECIFIED - :SOURCE @@ -123,7 +118,7 @@ properties: - !ruby/object:Api::Type::Enum name: 'mirrorState' description: | - Indicates the state of the mirror between source and destination volumes. + Indicates the state of the mirror between source and destination volumes. Updated every 5 minutes. values: - :MIRROR_STATE_UNSPECIFIED - :PREPARING @@ -131,6 +126,16 @@ properties: - :STOPPED - :TRANSFERRING output: true + - !ruby/object:Api::Type::String + name: 'createTime' + description: | + Create time of the active directory. A timestamp in RFC3339 UTC "Zulu" format. Examples: "2023-06-22T09:13:01.617Z". + output: true + - !ruby/object:Api::Type::String + name: 'destinationVolume' + description: | + Full resource name of destination volume with format: `projects/{{project}}/locations/{{location}}/volumes/{{volumeId}}` + output: true - !ruby/object:Api::Type::NestedObject name: 'transferStats' description: |- @@ -174,18 +179,7 @@ properties: - !ruby/object:Api::Type::KeyValueLabels name: 'labels' description: | - Labels as key value pairs. Example: `{ "owner": "Bob", "department": "finance", "purpose": "testing" }`. - - !ruby/object:Api::Type::Boolean - name: 'healthy' - description: | - Condition of the relationship. Can be one of the following: - - true: The replication relationship is healthy. It has not missed the most recent scheduled transfer. - - false: The replication relationship is not healthy. It has missed the most recent scheduled transfer. - output: true - - !ruby/object:Api::Type::String - name: 'description' - description: | - An description of this resource. + Labels as key value pairs. Example: `{ "owner": "Bob", "department": "finance", "purpose": "testing" }` - !ruby/object:Api::Type::NestedObject name: 'destinationVolumeParameters' description: |- @@ -195,7 +189,7 @@ properties: - !ruby/object:Api::Type::String name: 'storagePool' description: | - Name of an extisting storage pool for the destination volume. + Name of an existing storage pool for the destination volume with format: `projects/{{project}}/locations/{{location}}/storagePools/{{poolId}}` required: true - !ruby/object:Api::Type::String name: 'volumeId' @@ -211,3 +205,35 @@ properties: name: 'description' description: | Description for the destination volume. + - !ruby/object:Api::Type::String + name: 'sourceVolume' + description: | + Full resource name of source volume with format: `projects/{{project}}/locations/{{location}}/volumes/{{volumeId}}` + output: true + - !ruby/object:Api::Type::Boolean + name: 'healthy' + description: | + Condition of the relationship. Can be one of the following: + - true: The replication relationship is healthy. It has not missed the most recent scheduled transfer. + - false: The replication relationship is not healthy. It has missed the most recent scheduled transfer. + output: true + - !ruby/object:Api::Type::String + name: 'description' + description: | + An description of this resource. +virtual_fields: + - !ruby/object:Api::Type::Boolean + name: 'delete_destination_volume' + description: | + Delete destination volume when replication resource is destroyed? Default is false. + default_value: false + - !ruby/object:Api::Type::Boolean + # Replications can be stopped and resumed. This requires to call the stop and resume API endpoints. + name: 'replication_enabled' + description: | + Set to false to stop/break the mirror. Stopping the mirror makes the destination volume read-write + and act independently from the source volume. + + Set to true to enable/resume the mirror. WARNING: Resuming a mirror overwrites the destination volume with the content + of the source volume and resumes replication updates from source to destination according to replication schedule. + default_value: true \ No newline at end of file From 0b64e1521e75e83be84138ac7b395b55d1bf5e2d Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Mon, 15 Jan 2024 12:03:31 +0100 Subject: [PATCH 06/34] For replication deletion, stop replication first --- mmv1/products/netapp/volumereplication.yaml | 13 +++++++------ .../pre_delete/netapp_volumereplication_stop.go.erb | 3 +-- .../netapp_volumereplication_mirror_state.go.erb | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/mmv1/products/netapp/volumereplication.yaml b/mmv1/products/netapp/volumereplication.yaml index 94f19a895f28..d9853870f6ae 100644 --- a/mmv1/products/netapp/volumereplication.yaml +++ b/mmv1/products/netapp/volumereplication.yaml @@ -72,11 +72,6 @@ parameters: required: true immutable: true url_param_only: true -# The following functionality needs to be discussed in review process -# custom_code: !ruby/object:Provider::Terraform::CustomCode -# pre_delete: templates/terraform/pre_delete/netapp_volumereplication_stop.go.erb -# post_delete: templates/terraform/post_delete/netapp_volumereplication_delete_destination.go.erb -# pre_update: templates/terraform/pre_update/netapp_volumereplication_mirror_state.go.erb properties: - !ruby/object:Api::Type::Enum name: 'state' @@ -236,4 +231,10 @@ virtual_fields: Set to true to enable/resume the mirror. WARNING: Resuming a mirror overwrites the destination volume with the content of the source volume and resumes replication updates from source to destination according to replication schedule. - default_value: true \ No newline at end of file + default_value: true + +# The following functionality needs to be discussed in review process +custom_code: !ruby/object:Provider::Terraform::CustomCode + pre_delete: templates/terraform/pre_delete/netapp_volumereplication_stop.go.erb +# post_delete: templates/terraform/post_delete/netapp_volumereplication_delete_destination.go.erb +# pre_update: templates/terraform/pre_update/netapp_volumereplication_mirror_state.go.erb \ No newline at end of file diff --git a/mmv1/templates/terraform/pre_delete/netapp_volumereplication_stop.go.erb b/mmv1/templates/terraform/pre_delete/netapp_volumereplication_stop.go.erb index f72318a37879..36b90ed40766 100644 --- a/mmv1/templates/terraform/pre_delete/netapp_volumereplication_stop.go.erb +++ b/mmv1/templates/terraform/pre_delete/netapp_volumereplication_stop.go.erb @@ -1,4 +1,4 @@ -rawurl, err := tpgresource.ReplaceVars(d, config, "{{NetappBasePath}}projects/{{project}}/locations/{{location}}/volumes/{{volume_id}}/replications/{{name}}:stop") +rawurl, err := tpgresource.ReplaceVars(d, config, "{{NetappBasePath}}projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/replications/{{name}}:stop") if err != nil { return err } @@ -27,4 +27,3 @@ err = NetappOperationWaitTime( if err != nil { return err } - \ No newline at end of file diff --git a/mmv1/templates/terraform/pre_update/netapp_volumereplication_mirror_state.go.erb b/mmv1/templates/terraform/pre_update/netapp_volumereplication_mirror_state.go.erb index 7a3149b773fa..d01e6e92df2e 100644 --- a/mmv1/templates/terraform/pre_update/netapp_volumereplication_mirror_state.go.erb +++ b/mmv1/templates/terraform/pre_update/netapp_volumereplication_mirror_state.go.erb @@ -1,6 +1,6 @@ log.Printf("[DEBUG] value of replication_state : %v", d.Get("replication_enabled")) var obj map[string]interface{} -rawurl, err := tpgresource.ReplaceVars(d, config, "{{NetappBasePath}}projects/{{project}}/locations/{{location}}/volumes/{{volume_id}}/replications/{{name}}:") +rawurl, err := tpgresource.ReplaceVars(d, config, "{{NetappBasePath}}projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/replications/{{name}}:") if err != nil { return err } From 469e778cbac805e291a21e7f57caf765d6ca8282 Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Mon, 15 Jan 2024 12:30:19 +0100 Subject: [PATCH 07/34] Add support for deleting destination volume on replication delete --- mmv1/products/netapp/volumereplication.yaml | 7 +++++-- .../examples/netapp_volume_replication_create.tf.erb | 4 ++++ ...app_volumereplication_delete_destination_volume.go.erb} | 0 3 files changed, 9 insertions(+), 2 deletions(-) rename mmv1/templates/terraform/post_delete/{netapp_volumereplication_delete_destination.go.erb => netapp_volumereplication_delete_destination_volume.go.erb} (100%) diff --git a/mmv1/products/netapp/volumereplication.yaml b/mmv1/products/netapp/volumereplication.yaml index d9853870f6ae..24bf38288905 100644 --- a/mmv1/products/netapp/volumereplication.yaml +++ b/mmv1/products/netapp/volumereplication.yaml @@ -113,7 +113,10 @@ properties: - !ruby/object:Api::Type::Enum name: 'mirrorState' description: | - Indicates the state of the mirror between source and destination volumes. Updated every 5 minutes. + Indicates the state of the mirror between source and destination volumes. Depending on the amount of data + in your source volume, PREPARING phase can take hours or days. mirrorState = MIRRORED indicates your baseline + transfer ended and destination volume became accessible read-only. TRANSFERRING means a MIRRORED volume + currently receives an update. Updated every 5 minutes. values: - :MIRROR_STATE_UNSPECIFIED - :PREPARING @@ -236,5 +239,5 @@ virtual_fields: # The following functionality needs to be discussed in review process custom_code: !ruby/object:Provider::Terraform::CustomCode pre_delete: templates/terraform/pre_delete/netapp_volumereplication_stop.go.erb -# post_delete: templates/terraform/post_delete/netapp_volumereplication_delete_destination.go.erb + post_delete: templates/terraform/post_delete/netapp_volumereplication_delete_destination_volume.go.erb # pre_update: templates/terraform/pre_update/netapp_volumereplication_mirror_state.go.erb \ No newline at end of file diff --git a/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb b/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb index 83a6c3c37e71..665884bd3153 100644 --- a/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb +++ b/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb @@ -44,4 +44,8 @@ resource "google_netapp_volumereplication" "my-replication" { share_name = "<%= ctx[:vars]['volume_name'] %>" description = "This is a replicated volume" } + # WARNING: Setting delete_destination_volume to true, will delete the + # destination volume too, if the replication is deleted. This is great + # for testing, but questionable for production! + delete_destination_volume = true } diff --git a/mmv1/templates/terraform/post_delete/netapp_volumereplication_delete_destination.go.erb b/mmv1/templates/terraform/post_delete/netapp_volumereplication_delete_destination_volume.go.erb similarity index 100% rename from mmv1/templates/terraform/post_delete/netapp_volumereplication_delete_destination.go.erb rename to mmv1/templates/terraform/post_delete/netapp_volumereplication_delete_destination_volume.go.erb From 056eb20364a4e4f49acbb45056e94c751768d3f1 Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Mon, 15 Jan 2024 13:21:03 +0100 Subject: [PATCH 08/34] Make volumes deletable in presence of snapshots. This change will be PRed for volume resource independently. Adding it here while it is not in main. --- mmv1/products/netapp/volume.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mmv1/products/netapp/volume.yaml b/mmv1/products/netapp/volume.yaml index f15009d66e8a..81af5791ea99 100644 --- a/mmv1/products/netapp/volume.yaml +++ b/mmv1/products/netapp/volume.yaml @@ -27,6 +27,8 @@ references: !ruby/object:Api::Resource::ReferenceLinks base_url: projects/{{project}}/locations/{{location}}/volumes self_link: projects/{{project}}/locations/{{location}}/volumes/{{name}} create_url: projects/{{project}}/locations/{{location}}/volumes?volumeId={{name}} +# Snapshots are nested resources of volumes. Add force=true to delete volumes with snapshots. +delete_url: 'projects/{{project}}/locations/{{location}}/volumes/{{name}}?force=true' update_url: projects/{{project}}/locations/{{location}}/volumes/{{name}} update_verb: :PATCH update_mask: true From 223a764c6cb7925625527425e08649900fe07e73 Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Mon, 15 Jan 2024 13:28:15 +0100 Subject: [PATCH 09/34] Improving debug error message --- .../terraform/pre_delete/netapp_volumereplication_stop.go.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mmv1/templates/terraform/pre_delete/netapp_volumereplication_stop.go.erb b/mmv1/templates/terraform/pre_delete/netapp_volumereplication_stop.go.erb index 36b90ed40766..87565722df9d 100644 --- a/mmv1/templates/terraform/pre_delete/netapp_volumereplication_stop.go.erb +++ b/mmv1/templates/terraform/pre_delete/netapp_volumereplication_stop.go.erb @@ -18,7 +18,7 @@ reso, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ <% end -%> }) if err != nil { - return fmt.Errorf("Error stopping replication %q: %s", d.Id(), err) + return fmt.Errorf("Error stopping volumereplication %q before deleting it: %s", d.Id(), err) } err = NetappOperationWaitTime( From e2fdc9236b55554b108626cd218c657fb6d8a99a Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Mon, 15 Jan 2024 13:59:23 +0100 Subject: [PATCH 10/34] yaml check and format fix --- mmv1/products/netapp/volumereplication.yaml | 9 ++++----- .../examples/netapp_volume_replication_create.tf.erb | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/mmv1/products/netapp/volumereplication.yaml b/mmv1/products/netapp/volumereplication.yaml index 24bf38288905..cf55fc2829d4 100644 --- a/mmv1/products/netapp/volumereplication.yaml +++ b/mmv1/products/netapp/volumereplication.yaml @@ -18,7 +18,7 @@ description: | a source volume in one location to a destination volume in a different location. This capability lets you use the replicated volume for critical application activity in case of a location-wide outage or disaster. The replicated volume can also be used as a read-only copy while the mirror is enabled, or - as a independent volume if the mirror is stopped. A destination volume will also contain the + as a independent volume if the mirror is stopped. A destination volume will also contain the snapshots of the source volume. references: !ruby/object:Api::Resource::ReferenceLinks guides: @@ -48,8 +48,7 @@ examples: destination_volume: 'destination-volume' network_name: 'test-network' test_vars_overrides: - network_name: '"shared-vpc-prod"' - # network_name: 'acctest.BootstrapSharedServiceNetworkingConnection(t, "gcnv-network-config-1", acctest.ServiceNetworkWithParentService("netapp.servicenetworking.goog"))' + network_name: 'acctest.BootstrapSharedServiceNetworkingConnection(t, "gcnv-network-config-1", acctest.ServiceNetworkWithParentService("netapp.servicenetworking.goog"))' parameters: - !ruby/object:Api::Type::String name: 'location' @@ -128,7 +127,7 @@ properties: name: 'createTime' description: | Create time of the active directory. A timestamp in RFC3339 UTC "Zulu" format. Examples: "2023-06-22T09:13:01.617Z". - output: true + output: true - !ruby/object:Api::Type::String name: 'destinationVolume' description: | @@ -240,4 +239,4 @@ virtual_fields: custom_code: !ruby/object:Provider::Terraform::CustomCode pre_delete: templates/terraform/pre_delete/netapp_volumereplication_stop.go.erb post_delete: templates/terraform/post_delete/netapp_volumereplication_delete_destination_volume.go.erb -# pre_update: templates/terraform/pre_update/netapp_volumereplication_mirror_state.go.erb \ No newline at end of file + # pre_update: templates/terraform/pre_update/netapp_volumereplication_mirror_state.go.erb diff --git a/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb b/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb index 665884bd3153..5fda78c8b582 100644 --- a/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb +++ b/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb @@ -41,8 +41,8 @@ resource "google_netapp_volumereplication" "my-replication" { volume_id = "<%= ctx[:vars]['destination_volume'] %>" # Keeping the share_name of source and destination the same makes # simplifies implementing client failover concepts - share_name = "<%= ctx[:vars]['volume_name'] %>" - description = "This is a replicated volume" + share_name = "<%= ctx[:vars]['volume_name'] %>" + description = "This is a replicated volume" } # WARNING: Setting delete_destination_volume to true, will delete the # destination volume too, if the replication is deleted. This is great From 441be1ab9ee391c8643789749fc3773f4a4385ef Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Mon, 15 Jan 2024 15:01:55 +0100 Subject: [PATCH 11/34] Add wait for mirror to initialize. Required to run destroy shortly after create. --- .../examples/netapp_volume_replication_create.tf.erb | 7 +++++++ .../pre_delete/netapp_volumereplication_stop.go.erb | 1 + 2 files changed, 8 insertions(+) diff --git a/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb b/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb index 5fda78c8b582..80bfe2879a2b 100644 --- a/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb +++ b/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb @@ -49,3 +49,10 @@ resource "google_netapp_volumereplication" "my-replication" { # for testing, but questionable for production! delete_destination_volume = true } + +# Wait for replication to reach mirrorState==MIRRORED +resource "time_sleep" "wait_for_mirrored" { + depends_on = [google_netapp_volumereplication.my-replication] + + create_duration = "6m" +} \ No newline at end of file diff --git a/mmv1/templates/terraform/pre_delete/netapp_volumereplication_stop.go.erb b/mmv1/templates/terraform/pre_delete/netapp_volumereplication_stop.go.erb index 87565722df9d..3b050287f849 100644 --- a/mmv1/templates/terraform/pre_delete/netapp_volumereplication_stop.go.erb +++ b/mmv1/templates/terraform/pre_delete/netapp_volumereplication_stop.go.erb @@ -2,6 +2,7 @@ rawurl, err := tpgresource.ReplaceVars(d, config, "{{NetappBasePath}}projects/{{ if err != nil { return err } +// Consider doing a force stop? reso, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ Config: config, Method: "POST", From ebd10d2d85e7d9b9c9561216b679ce6351da2d44 Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Tue, 16 Jan 2024 15:31:12 +0100 Subject: [PATCH 12/34] Wait on destroy, not on create --- .../terraform/examples/netapp_volume_replication_create.tf.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb b/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb index 80bfe2879a2b..efe3b199de63 100644 --- a/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb +++ b/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb @@ -54,5 +54,5 @@ resource "google_netapp_volumereplication" "my-replication" { resource "time_sleep" "wait_for_mirrored" { depends_on = [google_netapp_volumereplication.my-replication] - create_duration = "6m" + destroy_duration = "6m" } \ No newline at end of file From c7f1bcc25ee87f3c912c129deff87794be2928e0 Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Fri, 19 Jan 2024 12:55:39 +0100 Subject: [PATCH 13/34] Make deleting a replication more robust - doc improvements - started to implement stop/resume. More work required. - renamed a few files to better reflect what they are good for --- mmv1/products/netapp/volumereplication.yaml | 17 +++++- .../netapp_volume_replication_create.tf.erb | 7 ++- ...plication_delete_destination_volume.go.erb | 55 ++++++++++--------- ...tapp_volumereplication_mirror_state.go.erb | 18 +++++- .../netapp_volumereplication_stop.go.erb | 30 ---------- ...olumereplication_stop_before_delete.go.erb | 38 +++++++++++++ 6 files changed, 103 insertions(+), 62 deletions(-) rename mmv1/templates/terraform/{pre_update => post_update}/netapp_volumereplication_mirror_state.go.erb (53%) delete mode 100644 mmv1/templates/terraform/pre_delete/netapp_volumereplication_stop.go.erb create mode 100644 mmv1/templates/terraform/pre_delete/netapp_volumereplication_stop_before_delete.go.erb diff --git a/mmv1/products/netapp/volumereplication.yaml b/mmv1/products/netapp/volumereplication.yaml index cf55fc2829d4..4e011bc98cda 100644 --- a/mmv1/products/netapp/volumereplication.yaml +++ b/mmv1/products/netapp/volumereplication.yaml @@ -20,6 +20,10 @@ description: | disaster. The replicated volume can also be used as a read-only copy while the mirror is enabled, or as a independent volume if the mirror is stopped. A destination volume will also contain the snapshots of the source volume. + + Please note that the destination volume is not a managed resource. As long as the mirror is active + (mirrorState in not STOPPED) configuration changes done to source or destination volumes are automatically + done to both. references: !ruby/object:Api::Resource::ReferenceLinks guides: 'Documentation': 'https://cloud.google.com/netapp/volumes/docs/protect-data/about-volume-replication' @@ -234,9 +238,18 @@ virtual_fields: Set to true to enable/resume the mirror. WARNING: Resuming a mirror overwrites the destination volume with the content of the source volume and resumes replication updates from source to destination according to replication schedule. default_value: true + - !ruby/object:Api::Type::Boolean + name: 'force_stopping' + description: | + Stopping a mirror will fail if a mirror update is currently in progress (mirrorState is PREPARING or TRANSFERRING). + While it is advisable to let updates complete before stopping, this parameter allows a stop action + triggered by replication_enabled=false being forced. The current transfer will be aborted and destination + volume content will remain in the state of the last successful update. + Enabling this parameter is the only way to abort a long running PREPARING phase. Default is false. + default_value: false # The following functionality needs to be discussed in review process custom_code: !ruby/object:Provider::Terraform::CustomCode - pre_delete: templates/terraform/pre_delete/netapp_volumereplication_stop.go.erb + pre_delete: templates/terraform/pre_delete/netapp_volumereplication_stop_before_delete.go.erb post_delete: templates/terraform/post_delete/netapp_volumereplication_delete_destination_volume.go.erb - # pre_update: templates/terraform/pre_update/netapp_volumereplication_mirror_state.go.erb + # post_update: templates/terraform/post_update/netapp_volumereplication_mirror_state.go.erb diff --git a/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb b/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb index efe3b199de63..94d9c8742f44 100644 --- a/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb +++ b/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb @@ -50,9 +50,12 @@ resource "google_netapp_volumereplication" "my-replication" { delete_destination_volume = true } -# Wait for replication to reach mirrorState==MIRRORED +# Wait for replication to reach mirrorState==MIRRORED. For an empty volume +# this takes about a minute. For volumes with a lot of data, it can take +# many hours. +# Since mirrorState is updated every 5 minutes, lets wait 310 seconds resource "time_sleep" "wait_for_mirrored" { depends_on = [google_netapp_volumereplication.my-replication] - destroy_duration = "6m" + create_duration = "310" } \ No newline at end of file diff --git a/mmv1/templates/terraform/post_delete/netapp_volumereplication_delete_destination_volume.go.erb b/mmv1/templates/terraform/post_delete/netapp_volumereplication_delete_destination_volume.go.erb index e1d8286690a9..dc49de58a3cc 100644 --- a/mmv1/templates/terraform/post_delete/netapp_volumereplication_delete_destination_volume.go.erb +++ b/mmv1/templates/terraform/post_delete/netapp_volumereplication_delete_destination_volume.go.erb @@ -1,30 +1,31 @@ time.Sleep(20 * time.Second) log.Printf("[DEBUG] value of destvol %q: %v", reflect.TypeOf(d.Get("destination_volume")), d.Get("destination_volume")) if d.Get("delete_destination_volume").(bool) == true { - str1 := d.Get("destination_volume").(string) - res1 := str1+"?force=true" - de_url, err := tpgresource.ReplaceVars(d, config, "{{NetappBasePath}}") - if err != nil { - return err - } - var obj1 map[string]interface{} - res2, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ - Config: config, - Method: "DELETE", - Project: billingProject, - RawURL: de_url+res1, - UserAgent: userAgent, - Body: obj1, - Timeout: d.Timeout(schema.TimeoutDelete), - }) - if err != nil { - return transport_tpg.HandleNotFoundError(err, d, "volume") - } - err = NetappOperationWaitTime( - config, res2, project, "Deleting volume", userAgent, - d.Timeout(schema.TimeoutDelete)) - if err != nil { - return err - } - log.Printf("[DEBUG] Finished deleting volume %q: %#v", d.Id(), res2) - } \ No newline at end of file + str1 := d.Get("destination_volume").(string) + // Always force volume deletion + res1 := str1 + "?force=true" + de_url, err := tpgresource.ReplaceVars(d, config, "{{NetappBasePath}}") + if err != nil { + return err + } + var obj1 map[string]interface{} + res2, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "DELETE", + Project: billingProject, + RawURL: de_url + res1, + UserAgent: userAgent, + Body: obj1, + Timeout: d.Timeout(schema.TimeoutDelete), + }) + if err != nil { + return transport_tpg.HandleNotFoundError(err, d, "volume") + } + err = NetappOperationWaitTime( + config, res2, project, "Deleting volume", userAgent, + d.Timeout(schema.TimeoutDelete)) + if err != nil { + return err + } + log.Printf("[DEBUG] Finished deleting volume %q: %#v", d.Id(), res2) +} \ No newline at end of file diff --git a/mmv1/templates/terraform/pre_update/netapp_volumereplication_mirror_state.go.erb b/mmv1/templates/terraform/post_update/netapp_volumereplication_mirror_state.go.erb similarity index 53% rename from mmv1/templates/terraform/pre_update/netapp_volumereplication_mirror_state.go.erb rename to mmv1/templates/terraform/post_update/netapp_volumereplication_mirror_state.go.erb index d01e6e92df2e..415102bca910 100644 --- a/mmv1/templates/terraform/pre_update/netapp_volumereplication_mirror_state.go.erb +++ b/mmv1/templates/terraform/post_update/netapp_volumereplication_mirror_state.go.erb @@ -1,4 +1,13 @@ log.Printf("[DEBUG] value of replication_state : %v", d.Get("replication_enabled")) +// This needs more work. We should send stop/resume only when action is required. Add missing state checks +// STATES transitions +// replication_enabled==true, mirrorState!=STOPPED -> NOOP +// replication_enabled==true, mirrorState==STOPPED -> resume +// replication_enabled==false, mirrorState==STOPPED -> NOOP +// replication_enabled==false, mirrorState==MIRRORED -> stop +// replication_enabled==false, mirrorState==TRANSFERRING -> force stop +// replication_enabled==false, mirrorState==PREPARING -> error. cannot be stopped + var obj map[string]interface{} rawurl, err := tpgresource.ReplaceVars(d, config, "{{NetappBasePath}}projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/replications/{{name}}:") if err != nil { @@ -8,7 +17,14 @@ if err != nil { if d.Get("replication_enabled").(bool) == true { rawurl = rawurl + "resume" } else { - rawurl = rawurl + "stop" + // replication_enabled==false, mirrorState==MIRRORED -> stop + rawurl = rawurl + "stop" + // replication_enabled==false, mirrorState==TRANSFERRING -> force stop + if d.Get("force_stopping").(bool) == true { + obj = map[string]interface{}{ + "force": true, + } + } } reso, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ Config: config, diff --git a/mmv1/templates/terraform/pre_delete/netapp_volumereplication_stop.go.erb b/mmv1/templates/terraform/pre_delete/netapp_volumereplication_stop.go.erb deleted file mode 100644 index 3b050287f849..000000000000 --- a/mmv1/templates/terraform/pre_delete/netapp_volumereplication_stop.go.erb +++ /dev/null @@ -1,30 +0,0 @@ -rawurl, err := tpgresource.ReplaceVars(d, config, "{{NetappBasePath}}projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/replications/{{name}}:stop") -if err != nil { - return err - } -// Consider doing a force stop? -reso, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ - Config: config, - Method: "POST", - Project: billingProject, - RawURL: rawurl, - UserAgent: userAgent, - Body: obj, - Timeout: d.Timeout(schema.TimeoutUpdate), -<% if object.error_retry_predicates -%> - ErrorRetryPredicates: []transport_tpg.RetryErrorPredicateFunc{<%= object.error_retry_predicates.join(',') -%>}, -<% end -%> -<% if object.error_abort_predicates -%> - ErrorAbortPredicates: []transport_tpg.RetryErrorPredicateFunc{<%= object.error_abort_predicates.join(',') -%>}, -<% end -%> -}) -if err != nil { - return fmt.Errorf("Error stopping volumereplication %q before deleting it: %s", d.Id(), err) -} - -err = NetappOperationWaitTime( - config, reso, project, "Deleting volumereplication", userAgent, - d.Timeout(schema.TimeoutDelete)) -if err != nil { - return err -} diff --git a/mmv1/templates/terraform/pre_delete/netapp_volumereplication_stop_before_delete.go.erb b/mmv1/templates/terraform/pre_delete/netapp_volumereplication_stop_before_delete.go.erb new file mode 100644 index 000000000000..89fdd4bf0521 --- /dev/null +++ b/mmv1/templates/terraform/pre_delete/netapp_volumereplication_stop_before_delete.go.erb @@ -0,0 +1,38 @@ +// We are about to delete the replication and need to stop the mirror before. +// FYI: Stopping a PREPARING mirror currently doesn't work. User have to wait until +// mirror reaches MIRRORED. +if d.Get("mirror_state") != "STOPPED" { + rawurl, err := tpgresource.ReplaceVars(d, config, "{{NetappBasePath}}projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/replications/{{name}}:stop") + if err != nil { + return err + } + + reso, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "POST", + Project: billingProject, + RawURL: rawurl, + UserAgent: userAgent, + // We delete anyway, so lets always use force stop + Body: map[string]interface{}{ + "force": true, + }, + Timeout: d.Timeout(schema.TimeoutUpdate), + <% if object.error_retry_predicates -%> + ErrorRetryPredicates: []transport_tpg.RetryErrorPredicateFunc{<%= object.error_retry_predicates.join(',') -%>}, + <% end -%> + <% if object.error_abort_predicates -%> + ErrorAbortPredicates: []transport_tpg.RetryErrorPredicateFunc{<%= object.error_abort_predicates.join(',') -%>}, + <% end -%> + }) + if err != nil { + return fmt.Errorf("Error stopping volumereplication %q before deleting it: %s", d.Id(), err) + } + + err = NetappOperationWaitTime( + config, reso, project, "Deleting volumereplication", userAgent, + d.Timeout(schema.TimeoutDelete)) + if err != nil { + return err + } +} \ No newline at end of file From 012e3399a68b058fbb9127d84fe5e64e424055dc Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Fri, 19 Jan 2024 16:09:24 +0100 Subject: [PATCH 14/34] adding support for stop/resume --- mmv1/products/netapp/volumereplication.yaml | 36 +++--- .../netapp_volume_replication_create.tf.erb | 8 +- ...tapp_volumereplication_mirror_state.go.erb | 116 ++++++++++++------ .../netapp_volumereplication_read.tf.erb | 15 +++ 4 files changed, 116 insertions(+), 59 deletions(-) create mode 100644 mmv1/templates/terraform/pre_read/netapp_volumereplication_read.tf.erb diff --git a/mmv1/products/netapp/volumereplication.yaml b/mmv1/products/netapp/volumereplication.yaml index 4e011bc98cda..9bedd3293d0e 100644 --- a/mmv1/products/netapp/volumereplication.yaml +++ b/mmv1/products/netapp/volumereplication.yaml @@ -14,16 +14,19 @@ --- !ruby/object:Api::Resource name: 'volumereplication' description: | - Volume replication creates a copy of our volume byasynchronously replicate - a source volume in one location to a destination volume in a different location. This capability lets - you use the replicated volume for critical application activity in case of a location-wide outage or - disaster. The replicated volume can also be used as a read-only copy while the mirror is enabled, or - as a independent volume if the mirror is stopped. A destination volume will also contain the - snapshots of the source volume. + Volume replication creates an asynchronous mirror of a volume in a different location. This capability + lets you use the replicated volume for critical application activity in case of a location-wide outage + or disaster. + + A new destination volume is created as part of the replication resource. It's content is updated on a + schedule with content of the source volume. It can be used as a read-only copy while the mirror is + enabled, or as an independent read-write volume while the mirror is stopped. A destination volume will + also contain the snapshots of the source volume. Resuming a mirror will overwrite all changes on the + destination volume with the content of the source volume. While is mirror is enabled, all configuration + changes done to source or destination volumes are automatically done to both. Please note that the + destination volume is not a resource managed by Terraform. - Please note that the destination volume is not a managed resource. As long as the mirror is active - (mirrorState in not STOPPED) configuration changes done to source or destination volumes are automatically - done to both. + Reversing the replication direction is not supported through the provider. references: !ruby/object:Api::Resource::ReferenceLinks guides: 'Documentation': 'https://cloud.google.com/netapp/volumes/docs/protect-data/about-volume-replication' @@ -235,21 +238,20 @@ virtual_fields: Set to false to stop/break the mirror. Stopping the mirror makes the destination volume read-write and act independently from the source volume. - Set to true to enable/resume the mirror. WARNING: Resuming a mirror overwrites the destination volume with the content - of the source volume and resumes replication updates from source to destination according to replication schedule. + Set to true to enable/resume the mirror. WARNING: Resuming a mirror overwrites any changes + done to the destination volume with the content of the source volume. default_value: true - !ruby/object:Api::Type::Boolean name: 'force_stopping' description: | - Stopping a mirror will fail if a mirror update is currently in progress (mirrorState is PREPARING or TRANSFERRING). - While it is advisable to let updates complete before stopping, this parameter allows a stop action - triggered by replication_enabled=false being forced. The current transfer will be aborted and destination - volume content will remain in the state of the last successful update. - Enabling this parameter is the only way to abort a long running PREPARING phase. Default is false. + Only replications with mirror_state=MIRRORED can be stopped. A replication in mirror_state=TRANSFERRING + currently receives an update and stopping the update might be undesirable. Set this parameter to true + to stop anyway. All data transferred to the destination will be discarded and content of destination + volume will remain at the state of the last successful update. Default is false. default_value: false # The following functionality needs to be discussed in review process custom_code: !ruby/object:Provider::Terraform::CustomCode pre_delete: templates/terraform/pre_delete/netapp_volumereplication_stop_before_delete.go.erb post_delete: templates/terraform/post_delete/netapp_volumereplication_delete_destination_volume.go.erb - # post_update: templates/terraform/post_update/netapp_volumereplication_mirror_state.go.erb + post_update: templates/terraform/post_update/netapp_volumereplication_mirror_state.go.erb diff --git a/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb b/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb index 94d9c8742f44..92c0501c46d8 100644 --- a/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb +++ b/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb @@ -36,6 +36,7 @@ resource "google_netapp_volumereplication" "my-replication" { volume_name = google_netapp_volume.source_volume.name name = "<%= ctx[:vars]['replication_name'] %>" replication_schedule = "EVERY_10_MINUTES" + description = "This is a replication resource" destination_volume_parameters { storage_pool = google_netapp_storage_pool.destination_pool.id volume_id = "<%= ctx[:vars]['destination_volume'] %>" @@ -51,11 +52,10 @@ resource "google_netapp_volumereplication" "my-replication" { } # Wait for replication to reach mirrorState==MIRRORED. For an empty volume -# this takes about a minute. For volumes with a lot of data, it can take -# many hours. -# Since mirrorState is updated every 5 minutes, lets wait 310 seconds +# this should takes less than a minute. For volumes with a lot of data, +# it can take many hours. resource "time_sleep" "wait_for_mirrored" { depends_on = [google_netapp_volumereplication.my-replication] - create_duration = "310" + create_duration = "60s" } \ No newline at end of file diff --git a/mmv1/templates/terraform/post_update/netapp_volumereplication_mirror_state.go.erb b/mmv1/templates/terraform/post_update/netapp_volumereplication_mirror_state.go.erb index 415102bca910..a5f6f4868ddd 100644 --- a/mmv1/templates/terraform/post_update/netapp_volumereplication_mirror_state.go.erb +++ b/mmv1/templates/terraform/post_update/netapp_volumereplication_mirror_state.go.erb @@ -1,48 +1,88 @@ -log.Printf("[DEBUG] value of replication_state : %v", d.Get("replication_enabled")) -// This needs more work. We should send stop/resume only when action is required. Add missing state checks -// STATES transitions -// replication_enabled==true, mirrorState!=STOPPED -> NOOP -// replication_enabled==true, mirrorState==STOPPED -> resume -// replication_enabled==false, mirrorState==STOPPED -> NOOP -// replication_enabled==false, mirrorState==MIRRORED -> stop -// replication_enabled==false, mirrorState==TRANSFERRING -> force stop -// replication_enabled==false, mirrorState==PREPARING -> error. cannot be stopped - -var obj map[string]interface{} -rawurl, err := tpgresource.ReplaceVars(d, config, "{{NetappBasePath}}projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/replications/{{name}}:") -if err != nil { - return err -} +// Manage stopping and resuming a mirror +var obj2 map[string]interface{} +do_change := false +var action string +// state transitions +// there can be a glitch is a transfer starts/ends between reading mirrorState +// and sending the action. This will be very rare. No workaround. if d.Get("replication_enabled").(bool) == true { - rawurl = rawurl + "resume" + switch d.Get("mirror_state").(string) { + case "STOPPED": + // replication_enabled==true, mirrorState==STOPPED -> resume + action = "resume" + do_change = true + default: + // replication_enabled==true, mirrorState!=STOPPED -> NOOP + do_change = false + } } else { - // replication_enabled==false, mirrorState==MIRRORED -> stop - rawurl = rawurl + "stop" - // replication_enabled==false, mirrorState==TRANSFERRING -> force stop - if d.Get("force_stopping").(bool) == true { - obj = map[string]interface{}{ + switch d.Get("mirror_state").(string) { + case "MIRRORED": + // replication_enabled==false, mirrorState==MIRRORED -> stop + action = "stop" + do_change = true + case "TRANSFERRING": + // replication_enabled==false, mirrorState==TRANSFERRING -> force stop + // User needs to add force_stopping = true, otherwise will receive error + action = "stop" + do_change = true + case "PREPARING": + // replication_enabled==false, mirrorState==PREPARING -> stop + // Currently cannot be stopped. User will receive intended error. + // User needs to wait until mirrorState=MIRRORED + action = "stop" + do_change = true + default: + // replication_enabled==false, mirrorState==STOPPED -> NOOP + do_change = false + } + + if do_change == true && d.Get("force_stopping").(bool) == true { + obj2 = map[string]interface{}{ "force": true, } } } -reso, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ - Config: config, - Method: "POST", - Project: billingProject, - RawURL: rawurl, - UserAgent: userAgent, - Body: obj, - Timeout: d.Timeout(schema.TimeoutUpdate), -}) -if err != nil { - return fmt.Errorf("Error stopping/resuming replication %q: %s", d.Id(), err) -} -err = NetappOperationWaitTime( - config, reso, project, "Deleting volumereplication", userAgent, - d.Timeout(schema.TimeoutDelete)) +if do_change { + // We need to send STOP/RESUME API calls + rawurl, err := tpgresource.ReplaceVars(d, config, "{{NetappBasePath}}projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/replications/{{name}}:"+action) + if err != nil { + return err + } + + res2, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "POST", + Project: billingProject, + RawURL: rawurl, + UserAgent: userAgent, + Body: obj2, + Timeout: d.Timeout(schema.TimeoutUpdate), + }) + if err != nil { + return fmt.Errorf("Error stopping/resuming replication %q: %s", d.Id(), err) + } -if err != nil { - return err + err = NetappOperationWaitTime( + config, res2, project, "Deleting volumereplication", userAgent, + d.Timeout(schema.TimeoutDelete)) + + if err != nil { + // action failed. We need to fix state of virtual replication_enabled field manually. + // This can go away if Read implement state reconciliation for replication_enabled + if action == "resume" { + // resume action failed. Our mirror is still stopped. + if err := d.Set("replication_enabled", false); err != nil { + return fmt.Errorf("Error setting replication_enabled: %s", err) + } + } else { + // stop action failed. Our mirror is still mirrored. + if err := d.Set("replication_enabled", true); err != nil { + return fmt.Errorf("Error setting replication_enabled: %s", err) + } + } + return err + } } diff --git a/mmv1/templates/terraform/pre_read/netapp_volumereplication_read.tf.erb b/mmv1/templates/terraform/pre_read/netapp_volumereplication_read.tf.erb new file mode 100644 index 000000000000..199cc08dc9f7 --- /dev/null +++ b/mmv1/templates/terraform/pre_read/netapp_volumereplication_read.tf.erb @@ -0,0 +1,15 @@ +// User can stop/resume mirrors manually. We need to calculate the actual +// state of our virtual replication_enabled field +// It relies on mirror_state being recent, so it should be in post_read, +// which does not exit ... + +if d.Get("mirror_state").(string) == "STOPPED" { + if err := d.Set("replication_enabled", false); err != nil { + return fmt.Errorf("Error setting replication_enabled: %s", err) + } +} else { + if err := d.Set("replication_enabled", true); err != nil { + return fmt.Errorf("Error setting replication_enabled: %s", err) + } +} +log.Printf("[DEBUG] value of replication_state : %v", d.Get("replication_enabled")) From 1aac20408fffbdbf3b7f1fa4065f911f810ddc72 Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Fri, 19 Jan 2024 16:13:23 +0100 Subject: [PATCH 15/34] yamlformat and lint --- mmv1/products/netapp/volumereplication.yaml | 120 ++++++++++---------- 1 file changed, 61 insertions(+), 59 deletions(-) diff --git a/mmv1/products/netapp/volumereplication.yaml b/mmv1/products/netapp/volumereplication.yaml index 9bedd3293d0e..d7eea4755c3a 100644 --- a/mmv1/products/netapp/volumereplication.yaml +++ b/mmv1/products/netapp/volumereplication.yaml @@ -11,8 +11,9 @@ # See the License for the specific language governing permissions and # limitations under the License. ---- !ruby/object:Api::Resource -name: 'volumereplication' +--- +!ruby/object:Api::Resource +name: "volumereplication" description: | Volume replication creates an asynchronous mirror of a volume in a different location. This capability lets you use the replicated volume for critical application activity in case of a location-wide outage @@ -25,12 +26,12 @@ description: | destination volume with the content of the source volume. While is mirror is enabled, all configuration changes done to source or destination volumes are automatically done to both. Please note that the destination volume is not a resource managed by Terraform. - + Reversing the replication direction is not supported through the provider. references: !ruby/object:Api::Resource::ReferenceLinks guides: - 'Documentation': 'https://cloud.google.com/netapp/volumes/docs/protect-data/about-volume-replication' - api: 'https://cloud.google.com/netapp/volumes/docs/reference/rest/v1/projects.locations.volumes.replications' + "Documentation": "https://cloud.google.com/netapp/volumes/docs/protect-data/about-volume-replication" + api: "https://cloud.google.com/netapp/volumes/docs/reference/rest/v1/projects.locations.volumes.replications" base_url: projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/replications self_link: projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/replications/{{name}} create_url: projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/replications?replicationId={{name}} @@ -40,49 +41,50 @@ update_mask: true autogen_async: true async: !ruby/object:Api::OpAsync operation: !ruby/object:Api::OpAsync::Operation - base_url: '{{op_id}}' -id_format: 'projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/replications/{{name}}' -import_format: ['projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/replications/{{name}}'] + base_url: "{{op_id}}" +id_format: "projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/replications/{{name}}" +import_format: + [ + "projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/replications/{{name}}", + ] examples: - !ruby/object:Provider::Terraform::Examples - name: 'netapp_volume_replication_create' - primary_resource_id: 'test_replication' + name: "netapp_volume_replication_create" + primary_resource_id: "test_replication" vars: - source_pool_name: 'source-pool' - destination_pool_name: 'destination-pool' - volume_name: 'source-volume' - replication_name: 'test-replication' - destination_volume: 'destination-volume' - network_name: 'test-network' + source_pool_name: "source-pool" + destination_pool_name: "destination-pool" + volume_name: "source-volume" + replication_name: "test-replication" + destination_volume: "destination-volume" + network_name: "test-network" test_vars_overrides: network_name: 'acctest.BootstrapSharedServiceNetworkingConnection(t, "gcnv-network-config-1", acctest.ServiceNetworkWithParentService("netapp.servicenetworking.goog"))' parameters: - !ruby/object:Api::Type::String - name: 'location' + name: "location" required: true immutable: true url_param_only: true description: | Name of region for this resource. The resource needs to be created in the region of the destination volume. - !ruby/object:Api::Type::String - name: 'volume_name' - description: - The name of the existing source volume. + name: "volume_name" + description: The name of the existing source volume. required: true immutable: true url_param_only: true - !ruby/object:Api::Type::String - name: 'name' - description: - The name of the replication. Needs to be unique per location. + name: "name" + description: The name of the replication. Needs to be unique per location. required: true immutable: true url_param_only: true properties: - !ruby/object:Api::Type::Enum - name: 'state' + name: "state" description: | - Indicates the state of replication resource. State of the mirror itself is indicated in mirrorState. + Indicates the state of replication resource. State of the mirror itself is indicated in mirrorState. values: - :STATE_UNSPECIFIED - :CREATING @@ -92,12 +94,12 @@ properties: - :ERROR output: true - !ruby/object:Api::Type::String - name: 'stateDetails' + name: "stateDetails" description: | State details of the replication resource. output: true - !ruby/object:Api::Type::Enum - name: 'role' + name: "role" description: | Reverting a replication can swap source and destination volume roles. This field indicates if the `location` hosts the source or destination volume. For resume and revert and resume operations it is critical to understand @@ -108,7 +110,7 @@ properties: - :DESTINATION output: true - !ruby/object:Api::Type::Enum - name: 'replicationSchedule' + name: "replicationSchedule" description: | Specifies the replication interval. values: @@ -117,12 +119,12 @@ properties: - :DAILY required: true - !ruby/object:Api::Type::Enum - name: 'mirrorState' + name: "mirrorState" description: | - Indicates the state of the mirror between source and destination volumes. Depending on the amount of data - in your source volume, PREPARING phase can take hours or days. mirrorState = MIRRORED indicates your baseline - transfer ended and destination volume became accessible read-only. TRANSFERRING means a MIRRORED volume - currently receives an update. Updated every 5 minutes. + Indicates the state of the mirror between source and destination volumes. Depending on the amount of data + in your source volume, PREPARING phase can take hours or days. mirrorState = MIRRORED indicates your baseline + transfer ended and destination volume became accessible read-only. TRANSFERRING means a MIRRORED volume + currently receives an update. Updated every 5 minutes. values: - :MIRROR_STATE_UNSPECIFIED - :PREPARING @@ -131,124 +133,124 @@ properties: - :TRANSFERRING output: true - !ruby/object:Api::Type::String - name: 'createTime' + name: "createTime" description: | Create time of the active directory. A timestamp in RFC3339 UTC "Zulu" format. Examples: "2023-06-22T09:13:01.617Z". output: true - !ruby/object:Api::Type::String - name: 'destinationVolume' + name: "destinationVolume" description: | Full resource name of destination volume with format: `projects/{{project}}/locations/{{location}}/volumes/{{volumeId}}` output: true - !ruby/object:Api::Type::NestedObject - name: 'transferStats' + name: "transferStats" description: |- Replication transfer statistics. All statistics are updated every 5 minutes. output: true properties: - !ruby/object:Api::Type::String - name: 'transferBytes' + name: "transferBytes" description: | Number of bytes transferred so far in current transfer. - !ruby/object:Api::Type::String - name: 'totalTransferDuration' + name: "totalTransferDuration" description: | Total time taken so far during current transfer. - !ruby/object:Api::Type::String - name: 'lastTransferBytes' + name: "lastTransferBytes" description: | Size of last completed transfer in bytes. - !ruby/object:Api::Type::String - name: 'lastTransferDuration' + name: "lastTransferDuration" description: | Time taken during last completed transfer. - !ruby/object:Api::Type::String - name: 'lagDuration' + name: "lagDuration" description: | The elapsed time since the creation of the snapshot on the source volume that was last replicated to the destination volume. Lag time represents the difference in age of the destination volume data in relation to the source volume data. - !ruby/object:Api::Type::String - name: 'updateTime' + name: "updateTime" description: | Time when progress was updated last. A timestamp in RFC3339 UTC "Zulu" format. Examples: "2023-06-22T09:13:01.617Z". - !ruby/object:Api::Type::String - name: 'lastTransferEndTime' + name: "lastTransferEndTime" description: | Time when last transfer completed. A timestamp in RFC3339 UTC "Zulu" format. Examples: "2023-06-22T09:13:01.617Z". - !ruby/object:Api::Type::String - name: 'lastTransferError' + name: "lastTransferError" description: | A message describing the cause of the last transfer failure. - !ruby/object:Api::Type::KeyValueLabels - name: 'labels' + name: "labels" description: | Labels as key value pairs. Example: `{ "owner": "Bob", "department": "finance", "purpose": "testing" }` - !ruby/object:Api::Type::NestedObject - name: 'destinationVolumeParameters' + name: "destinationVolumeParameters" description: |- Destination volume parameters. ignore_read: true properties: - !ruby/object:Api::Type::String - name: 'storagePool' + name: "storagePool" description: | Name of an existing storage pool for the destination volume with format: `projects/{{project}}/locations/{{location}}/storagePools/{{poolId}}` required: true - !ruby/object:Api::Type::String - name: 'volumeId' + name: "volumeId" description: | Name for the destination volume to be created. If not specified, the name of the source volume will be used. default_from_api: true - !ruby/object:Api::Type::String - name: 'shareName' + name: "shareName" description: | Share name for destination volume. If not specified, name of source volume's share name will be used. default_from_api: true - !ruby/object:Api::Type::String - name: 'description' + name: "description" description: | Description for the destination volume. - !ruby/object:Api::Type::String - name: 'sourceVolume' + name: "sourceVolume" description: | Full resource name of source volume with format: `projects/{{project}}/locations/{{location}}/volumes/{{volumeId}}` output: true - !ruby/object:Api::Type::Boolean - name: 'healthy' + name: "healthy" description: | Condition of the relationship. Can be one of the following: - true: The replication relationship is healthy. It has not missed the most recent scheduled transfer. - false: The replication relationship is not healthy. It has missed the most recent scheduled transfer. output: true - !ruby/object:Api::Type::String - name: 'description' + name: "description" description: | An description of this resource. virtual_fields: - !ruby/object:Api::Type::Boolean - name: 'delete_destination_volume' + name: "delete_destination_volume" description: | Delete destination volume when replication resource is destroyed? Default is false. default_value: false - !ruby/object:Api::Type::Boolean # Replications can be stopped and resumed. This requires to call the stop and resume API endpoints. - name: 'replication_enabled' + name: "replication_enabled" description: | Set to false to stop/break the mirror. Stopping the mirror makes the destination volume read-write and act independently from the source volume. - Set to true to enable/resume the mirror. WARNING: Resuming a mirror overwrites any changes - done to the destination volume with the content of the source volume. + Set to true to enable/resume the mirror. WARNING: Resuming a mirror overwrites any changes + done to the destination volume with the content of the source volume. default_value: true - !ruby/object:Api::Type::Boolean - name: 'force_stopping' + name: "force_stopping" description: | Only replications with mirror_state=MIRRORED can be stopped. A replication in mirror_state=TRANSFERRING currently receives an update and stopping the update might be undesirable. Set this parameter to true to stop anyway. All data transferred to the destination will be discarded and content of destination volume will remain at the state of the last successful update. Default is false. - default_value: false + default_value: false # The following functionality needs to be discussed in review process custom_code: !ruby/object:Provider::Terraform::CustomCode From c26e36b7de58c6da534b1810cf842802786ec79f Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Fri, 19 Jan 2024 18:46:01 +0100 Subject: [PATCH 16/34] Add force delete to delete volumes with nested snapshots --- mmv1/products/netapp/volume.yaml | 13 +++++++++++++ .../pre_delete/netapp_volume_force_delete.go.erb | 4 ++++ .../services/netapp/resource_netapp_volume_test.go | 3 ++- 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 mmv1/templates/terraform/pre_delete/netapp_volume_force_delete.go.erb diff --git a/mmv1/products/netapp/volume.yaml b/mmv1/products/netapp/volume.yaml index f15009d66e8a..4e9819fbe1f5 100644 --- a/mmv1/products/netapp/volume.yaml +++ b/mmv1/products/netapp/volume.yaml @@ -414,3 +414,16 @@ properties: # description: |- # Full name of the snapshot resource. Format: `projects/{{project}}/locations/{{location}}/volumes/{{volume}}/snapshots/{{snapshot}}`. # required: true +virtual_fields: + - !ruby/object:Api::Type::Enum + name: 'deletion_policy' + description: | + Policy to determine if the volume should be deleted forcefully. + Volumes may have nested snapshot resources. Deleting such a volume will fail. + Setting this parameter to FORCE will delete volumes including nested snapshots. + values: + - :DEFAULT + - :FORCE + default_value: :DEFAULT +custom_code: !ruby/object:Provider::Terraform::CustomCode + pre_delete: templates/terraform/pre_delete/netapp_volume_force_delete.go.erb \ No newline at end of file diff --git a/mmv1/templates/terraform/pre_delete/netapp_volume_force_delete.go.erb b/mmv1/templates/terraform/pre_delete/netapp_volume_force_delete.go.erb new file mode 100644 index 000000000000..d8fd2927332f --- /dev/null +++ b/mmv1/templates/terraform/pre_delete/netapp_volume_force_delete.go.erb @@ -0,0 +1,4 @@ +// Delete volume even when nested snapshots do exist +if deletionPolicy := d.Get("deletion_policy"); deletionPolicy == "FORCE" { + url = url + "?force=true" +} \ No newline at end of file diff --git a/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_test.go b/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_test.go index e402dd0d9d9c..1571c89591fe 100644 --- a/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_test.go +++ b/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_test.go @@ -319,7 +319,8 @@ resource "google_netapp_volume" "test_volume" { } } restricted_actions = ["DELETE"] - } + deletion_policy = "FORCE" +} data "google_compute_network" "default" { name = "%{network_name}" From df34ac455dbd801e2561c11a6628412521fff04b Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Sat, 20 Jan 2024 18:31:32 +0100 Subject: [PATCH 17/34] resource test first version --- .../resource_netapp_volumereplication_test.go | 300 ++++++++++++++++++ 1 file changed, 300 insertions(+) create mode 100644 mmv1/third_party/terraform/services/netapp/resource_netapp_volumereplication_test.go diff --git a/mmv1/third_party/terraform/services/netapp/resource_netapp_volumereplication_test.go b/mmv1/third_party/terraform/services/netapp/resource_netapp_volumereplication_test.go new file mode 100644 index 000000000000..e51c18cbc091 --- /dev/null +++ b/mmv1/third_party/terraform/services/netapp/resource_netapp_volumereplication_test.go @@ -0,0 +1,300 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package netapp_test + +import ( + "testing" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + "github.com/hashicorp/terraform-provider-google/google/acctest" +) + +func TestAccNetappvolumereplication_netappVolumeReplicationCreateExample_update(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "network_name": acctest.BootstrapSharedServiceNetworkingConnection(t, "gcnv-network-config-1", acctest.ServiceNetworkWithParentService("netapp.servicenetworking.goog")), + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckNetappvolumereplicationDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccNetappvolumereplication_netappVolumeReplicationCreateExample_basic(context), + }, + { + ResourceName: "google_netapp_volumereplication.test_replication", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"destination_volume_parameters", "location", "volume_name", "name", "labels", "terraform_labels"}, + }, + { + Config: testAccNetappvolumereplication_netappVolumeReplicationCreateExample_update(context), + }, + { + ResourceName: "google_netapp_volumereplication.test_replication", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"destination_volume_parameters", "location", "volume_name", "name", "labels", "terraform_labels"}, + }, + { + Config: testAccNetappvolumereplication_netappVolumeReplicationCreateExample_stop(context), + }, + { + ResourceName: "google_netapp_volumereplication.test_replication", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"destination_volume_parameters", "location", "volume_name", "name", "labels", "terraform_labels"}, + }, + { + Config: testAccNetappvolumereplication_netappVolumeReplicationCreateExample_resume(context), + }, + { + ResourceName: "google_netapp_volumereplication.test_replication", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"destination_volume_parameters", "location", "volume_name", "name", "labels", "terraform_labels"}, + }, + }, + }) +} + +// Basic replication +func testAccNetappvolumereplication_netappVolumeReplicationCreateExample_basic(context map[string]interface{}) string { + var result string = acctest.Nprintf(` + +data "google_compute_network" "default" { + name = "%{network_name}" +} + +resource "google_netapp_storage_pool" "source_pool" { + name = "tf-test-source-pool%{random_suffix}" + location = "us-central1" + service_level = "PREMIUM" + capacity_gib = 2048 + network = data.google_compute_network.default.id +} + +resource "google_netapp_storage_pool" "destination_pool" { + name = "tf-test-destination-pool%{random_suffix}" + location = "us-west2" + service_level = "PREMIUM" + capacity_gib = 2048 + network = data.google_compute_network.default.id +} + +resource "google_netapp_volume" "source_volume" { + location = google_netapp_storage_pool.source_pool.location + name = "tf-test-source-volume%{random_suffix}" + capacity_gib = 100 + share_name = "tf-test-source-volume%{random_suffix}" + storage_pool = google_netapp_storage_pool.source_pool.name + protocols = [ + "NFSV3" + ] +} + +resource "google_netapp_volumereplication" "my-replication" { + depends_on = [google_netapp_volume.source_volume] + location = google_netapp_volume.source_volume.location + volume_name = google_netapp_volume.source_volume.name + name = "tf-test-test-replication%{random_suffix}" + replication_schedule = "EVERY_10_MINUTES" + destination_volume_parameters { + storage_pool = google_netapp_storage_pool.destination_pool.id + volume_id = "tf-test-destination-volume%{random_suffix}" + # Keeping the share_name of source and destination the same makes + # simplifies implementing client failover concepts + share_name = "tf-test-source-volume%{random_suffix}" + description = "This is a replicated volume" + } +} +`, context) + // Give mirror some time to reach mirror_state==MIRRORED state + time.Sleep(120 * time.Second) + return result +} + +// Update parameters +func testAccNetappvolumereplication_netappVolumeReplicationCreateExample_update(context map[string]interface{}) string { + return acctest.Nprintf(` + +data "google_compute_network" "default" { + name = "%{network_name}" +} + +resource "google_netapp_storage_pool" "source_pool" { + name = "tf-test-source-pool%{random_suffix}" + location = "us-central1" + service_level = "PREMIUM" + capacity_gib = 2048 + network = data.google_compute_network.default.id +} + +resource "google_netapp_storage_pool" "destination_pool" { + name = "tf-test-destination-pool%{random_suffix}" + location = "us-west2" + service_level = "PREMIUM" + capacity_gib = 2048 + network = data.google_compute_network.default.id +} + +resource "google_netapp_volume" "source_volume" { + location = google_netapp_storage_pool.source_pool.location + name = "tf-test-source-volume%{random_suffix}" + capacity_gib = 100 + share_name = "tf-test-source-volume%{random_suffix}" + storage_pool = google_netapp_storage_pool.source_pool.name + protocols = [ + "NFSV3" + ] +} + +resource "google_netapp_volumereplication" "my-replication" { + depends_on = [google_netapp_volume.source_volume] + location = google_netapp_volume.source_volume.location + volume_name = google_netapp_volume.source_volume.name + name = "tf-test-test-replication%{random_suffix}" + replication_schedule = "HOURLY" + description = "This is a replication resource" + labels { + "foo": "bar", + } + destination_volume_parameters { + storage_pool = google_netapp_storage_pool.destination_pool.id + volume_id = "tf-test-destination-volume%{random_suffix}" + # Keeping the share_name of source and destination the same makes + # simplifies implementing client failover concepts + share_name = "tf-test-source-volume%{random_suffix}" + description = "This is a replicated volume" + } + replication_enabled = true + delete_destination_volume = true + force_stopping = true +} +`, context) +} + +// Stop replication +func testAccNetappvolumereplication_netappVolumeReplicationCreateExample_stop(context map[string]interface{}) string { + return acctest.Nprintf(` + +data "google_compute_network" "default" { + name = "%{network_name}" +} + +resource "google_netapp_storage_pool" "source_pool" { + name = "tf-test-source-pool%{random_suffix}" + location = "us-central1" + service_level = "PREMIUM" + capacity_gib = 2048 + network = data.google_compute_network.default.id +} + +resource "google_netapp_storage_pool" "destination_pool" { + name = "tf-test-destination-pool%{random_suffix}" + location = "us-west2" + service_level = "PREMIUM" + capacity_gib = 2048 + network = data.google_compute_network.default.id +} + +resource "google_netapp_volume" "source_volume" { + location = google_netapp_storage_pool.source_pool.location + name = "tf-test-source-volume%{random_suffix}" + capacity_gib = 100 + share_name = "tf-test-source-volume%{random_suffix}" + storage_pool = google_netapp_storage_pool.source_pool.name + protocols = [ + "NFSV3" + ] +} + +resource "google_netapp_volumereplication" "my-replication" { + depends_on = [google_netapp_volume.source_volume] + location = google_netapp_volume.source_volume.location + volume_name = google_netapp_volume.source_volume.name + name = "tf-test-test-replication%{random_suffix}" + replication_schedule = "HOURLY" + description = "This is a replication resource" + labels { + "foo": "bar", + } + destination_volume_parameters { + storage_pool = google_netapp_storage_pool.destination_pool.id + volume_id = "tf-test-destination-volume%{random_suffix}" + # Keeping the share_name of source and destination the same makes + # simplifies implementing client failover concepts + share_name = "tf-test-source-volume%{random_suffix}" + description = "This is a replicated volume" + } + replication_enabled = false + delete_destination_volume = true + force_stopping = true +`, context) +} + +// resume replication +func testAccNetappvolumereplication_netappVolumeReplicationCreateExample_resume(context map[string]interface{}) string { + return acctest.Nprintf(` + +data "google_compute_network" "default" { + name = "%{network_name}" +} + +resource "google_netapp_storage_pool" "source_pool" { + name = "tf-test-source-pool%{random_suffix}" + location = "us-central1" + service_level = "PREMIUM" + capacity_gib = 2048 + network = data.google_compute_network.default.id +} + +resource "google_netapp_storage_pool" "destination_pool" { + name = "tf-test-destination-pool%{random_suffix}" + location = "us-west2" + service_level = "PREMIUM" + capacity_gib = 2048 + network = data.google_compute_network.default.id +} + +resource "google_netapp_volume" "source_volume" { + location = google_netapp_storage_pool.source_pool.location + name = "tf-test-source-volume%{random_suffix}" + capacity_gib = 100 + share_name = "tf-test-source-volume%{random_suffix}" + storage_pool = google_netapp_storage_pool.source_pool.name + protocols = [ + "NFSV3" + ] +} + +resource "google_netapp_volumereplication" "my-replication" { + depends_on = [google_netapp_volume.source_volume] + location = google_netapp_volume.source_volume.location + volume_name = google_netapp_volume.source_volume.name + name = "tf-test-test-replication%{random_suffix}" + replication_schedule = "HOURLY" + description = "This is a replication resource" + labels { + "foo": "bar", + } + destination_volume_parameters { + storage_pool = google_netapp_storage_pool.destination_pool.id + volume_id = "tf-test-destination-volume%{random_suffix}" + # Keeping the share_name of source and destination the same makes + # simplifies implementing client failover concepts + share_name = "tf-test-source-volume%{random_suffix}" + description = "This is a replicated volume" + } + replication_enabled = true + delete_destination_volume = true + force_stopping = true +`, context) +} From ffeb5df0b1d8872d1934b4c93f53448578bb8c7f Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Mon, 22 Jan 2024 16:02:42 +0100 Subject: [PATCH 18/34] More changes to make tests solid - Introduced new parameter to wait for mirror_status==MIRRORED - more mirror state reconciliation --- mmv1/products/netapp/volumereplication.yaml | 11 +- .../constants/netapp_volumereplication.go.erb | 51 ++++++++ .../netapp_volume_replication_create.tf.erb | 12 +- ...etapp_volumereplication_post_create.go.erb | 7 ++ ...plication_delete_destination_volume.go.erb | 3 +- ...tapp_volumereplication_mirror_state.go.erb | 12 ++ ...olumereplication_stop_before_delete.go.erb | 1 + .../netapp_volumereplication_read.tf.erb | 5 - .../resource_netapp_volumereplication_test.go | 115 ++++++++++++++---- 9 files changed, 175 insertions(+), 42 deletions(-) create mode 100644 mmv1/templates/terraform/constants/netapp_volumereplication.go.erb create mode 100644 mmv1/templates/terraform/post_create/netapp_volumereplication_post_create.go.erb diff --git a/mmv1/products/netapp/volumereplication.yaml b/mmv1/products/netapp/volumereplication.yaml index d7eea4755c3a..c15031523c45 100644 --- a/mmv1/products/netapp/volumereplication.yaml +++ b/mmv1/products/netapp/volumereplication.yaml @@ -234,7 +234,6 @@ virtual_fields: Delete destination volume when replication resource is destroyed? Default is false. default_value: false - !ruby/object:Api::Type::Boolean - # Replications can be stopped and resumed. This requires to call the stop and resume API endpoints. name: "replication_enabled" description: | Set to false to stop/break the mirror. Stopping the mirror makes the destination volume read-write @@ -251,9 +250,17 @@ virtual_fields: to stop anyway. All data transferred to the destination will be discarded and content of destination volume will remain at the state of the last successful update. Default is false. default_value: false - + - !ruby/object:Api::Type::Boolean + name: "wait_for_mirror" + description: | + Replication resource state is independent of mirror_state. With enough data, it can take many hours + for mirror_state to reach MIRRORED. If you want Terraform to wait for the mirror to finish one + create/stop/resume operations, set this parameter to true. Default is false. + default_value: false # The following functionality needs to be discussed in review process custom_code: !ruby/object:Provider::Terraform::CustomCode + constants: templates/terraform/constants/netapp_volumereplication.go.erb + post_create: templates/terraform/post_create/netapp_volumereplication_post_create.go.erb pre_delete: templates/terraform/pre_delete/netapp_volumereplication_stop_before_delete.go.erb post_delete: templates/terraform/post_delete/netapp_volumereplication_delete_destination_volume.go.erb post_update: templates/terraform/post_update/netapp_volumereplication_mirror_state.go.erb diff --git a/mmv1/templates/terraform/constants/netapp_volumereplication.go.erb b/mmv1/templates/terraform/constants/netapp_volumereplication.go.erb new file mode 100644 index 000000000000..6ad8d0c3240a --- /dev/null +++ b/mmv1/templates/terraform/constants/netapp_volumereplication.go.erb @@ -0,0 +1,51 @@ +// Custom function to wait for mirrorState target states +func NetAppVolumeReplicationWaitForMirror(d *schema.ResourceData, meta interface{}, targetState string) error { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + url, err := tpgresource.ReplaceVars(d, config, "{{NetappBasePath}}projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/replications/{{name}}") + if err != nil { + return err + } + + billingProject := "" + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for volumereplication: %s", err) + } + billingProject = project + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + for { + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + }) + if err != nil { + return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("Netappvolumereplication %q", d.Id())) + } + + log.Printf("[DEBUG] waiting for mirrorState. actual: %v, target: %v", res["mirrorState"], targetState) + + if res["mirrorState"] == targetState { + break + } + + time.Sleep(30 * time.Second) + // This method can potentially run for days, e.g. when setting up a replication for a source volume + // with dozens of TiB of data. Timeout handling yes/no? + } + + return nil +} \ No newline at end of file diff --git a/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb b/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb index 92c0501c46d8..4f3e15b280ad 100644 --- a/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb +++ b/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb @@ -30,7 +30,7 @@ resource "google_netapp_volume" "source_volume" { ] } -resource "google_netapp_volumereplication" "my-replication" { +resource "google_netapp_volumereplication" "<%= ctx[:primary_resource_id] %>" { depends_on = [google_netapp_volume.source_volume] location = google_netapp_volume.source_volume.location volume_name = google_netapp_volume.source_volume.name @@ -49,13 +49,5 @@ resource "google_netapp_volumereplication" "my-replication" { # destination volume too, if the replication is deleted. This is great # for testing, but questionable for production! delete_destination_volume = true + wait_for_mirror = true } - -# Wait for replication to reach mirrorState==MIRRORED. For an empty volume -# this should takes less than a minute. For volumes with a lot of data, -# it can take many hours. -resource "time_sleep" "wait_for_mirrored" { - depends_on = [google_netapp_volumereplication.my-replication] - - create_duration = "60s" -} \ No newline at end of file diff --git a/mmv1/templates/terraform/post_create/netapp_volumereplication_post_create.go.erb b/mmv1/templates/terraform/post_create/netapp_volumereplication_post_create.go.erb new file mode 100644 index 000000000000..5a980cbde3e2 --- /dev/null +++ b/mmv1/templates/terraform/post_create/netapp_volumereplication_post_create.go.erb @@ -0,0 +1,7 @@ +if d.Get("wait_for_mirror") == true { + // Wait for mirrorState=MIRRORED before treating the resource as created + err = NetAppVolumeReplicationWaitForMirror(d, meta, "MIRRORED") + if err != nil { + return fmt.Errorf("Error waiting for volumereplication to reach mirror_state==MIRRORED: %s", err) + } +} \ No newline at end of file diff --git a/mmv1/templates/terraform/post_delete/netapp_volumereplication_delete_destination_volume.go.erb b/mmv1/templates/terraform/post_delete/netapp_volumereplication_delete_destination_volume.go.erb index dc49de58a3cc..c8d7f4724010 100644 --- a/mmv1/templates/terraform/post_delete/netapp_volumereplication_delete_destination_volume.go.erb +++ b/mmv1/templates/terraform/post_delete/netapp_volumereplication_delete_destination_volume.go.erb @@ -1,4 +1,5 @@ -time.Sleep(20 * time.Second) +// A replication CREATE also created a destination volume +// A user can chooses to delete the destination volume after deleting the replication log.Printf("[DEBUG] value of destvol %q: %v", reflect.TypeOf(d.Get("destination_volume")), d.Get("destination_volume")) if d.Get("delete_destination_volume").(bool) == true { str1 := d.Get("destination_volume").(string) diff --git a/mmv1/templates/terraform/post_update/netapp_volumereplication_mirror_state.go.erb b/mmv1/templates/terraform/post_update/netapp_volumereplication_mirror_state.go.erb index a5f6f4868ddd..8771853d7f42 100644 --- a/mmv1/templates/terraform/post_update/netapp_volumereplication_mirror_state.go.erb +++ b/mmv1/templates/terraform/post_update/netapp_volumereplication_mirror_state.go.erb @@ -3,6 +3,7 @@ var obj2 map[string]interface{} do_change := false var action string +var targetState string // state transitions // there can be a glitch is a transfer starts/ends between reading mirrorState // and sending the action. This will be very rare. No workaround. @@ -11,6 +12,7 @@ if d.Get("replication_enabled").(bool) == true { case "STOPPED": // replication_enabled==true, mirrorState==STOPPED -> resume action = "resume" + targetState = "MIRRORED" do_change = true default: // replication_enabled==true, mirrorState!=STOPPED -> NOOP @@ -21,17 +23,20 @@ if d.Get("replication_enabled").(bool) == true { case "MIRRORED": // replication_enabled==false, mirrorState==MIRRORED -> stop action = "stop" + targetState = "STOPPED" do_change = true case "TRANSFERRING": // replication_enabled==false, mirrorState==TRANSFERRING -> force stop // User needs to add force_stopping = true, otherwise will receive error action = "stop" + targetState = "STOPPED" do_change = true case "PREPARING": // replication_enabled==false, mirrorState==PREPARING -> stop // Currently cannot be stopped. User will receive intended error. // User needs to wait until mirrorState=MIRRORED action = "stop" + targetState = "STOPPED" do_change = true default: // replication_enabled==false, mirrorState==STOPPED -> NOOP @@ -85,4 +90,11 @@ if do_change { } return err } + + // mirrorState should be okay by now, but we still observe glitches. So let's make sure. + // Wait for mirrorState=targetState before continuing + err = NetAppVolumeReplicationWaitForMirror(d, meta, targetState) + if err != nil { + return fmt.Errorf("Error waiting for volumereplication to reach mirror_state==%s: %s", targetState, err) + } } diff --git a/mmv1/templates/terraform/pre_delete/netapp_volumereplication_stop_before_delete.go.erb b/mmv1/templates/terraform/pre_delete/netapp_volumereplication_stop_before_delete.go.erb index 89fdd4bf0521..727fd13c620d 100644 --- a/mmv1/templates/terraform/pre_delete/netapp_volumereplication_stop_before_delete.go.erb +++ b/mmv1/templates/terraform/pre_delete/netapp_volumereplication_stop_before_delete.go.erb @@ -1,3 +1,4 @@ +// A replication can only be deleted if mirrorState==STOPPED // We are about to delete the replication and need to stop the mirror before. // FYI: Stopping a PREPARING mirror currently doesn't work. User have to wait until // mirror reaches MIRRORED. diff --git a/mmv1/templates/terraform/pre_read/netapp_volumereplication_read.tf.erb b/mmv1/templates/terraform/pre_read/netapp_volumereplication_read.tf.erb index 199cc08dc9f7..2b02b5e2dfcb 100644 --- a/mmv1/templates/terraform/pre_read/netapp_volumereplication_read.tf.erb +++ b/mmv1/templates/terraform/pre_read/netapp_volumereplication_read.tf.erb @@ -1,8 +1,3 @@ -// User can stop/resume mirrors manually. We need to calculate the actual -// state of our virtual replication_enabled field -// It relies on mirror_state being recent, so it should be in post_read, -// which does not exit ... - if d.Get("mirror_state").(string) == "STOPPED" { if err := d.Set("replication_enabled", false); err != nil { return fmt.Errorf("Error setting replication_enabled: %s", err) diff --git a/mmv1/third_party/terraform/services/netapp/resource_netapp_volumereplication_test.go b/mmv1/third_party/terraform/services/netapp/resource_netapp_volumereplication_test.go index e51c18cbc091..ebb248d85d8f 100644 --- a/mmv1/third_party/terraform/services/netapp/resource_netapp_volumereplication_test.go +++ b/mmv1/third_party/terraform/services/netapp/resource_netapp_volumereplication_test.go @@ -5,7 +5,6 @@ package netapp_test import ( "testing" - "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -32,16 +31,16 @@ func TestAccNetappvolumereplication_netappVolumeReplicationCreateExample_update( ResourceName: "google_netapp_volumereplication.test_replication", ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"destination_volume_parameters", "location", "volume_name", "name", "labels", "terraform_labels"}, + ImportStateVerifyIgnore: []string{"transferStats", "destination_volume_parameters", "location", "volume_name", "name", "labels", "terraform_labels", "delete_destination_volume", "force_stopping", "replication_enabled"}, }, { - Config: testAccNetappvolumereplication_netappVolumeReplicationCreateExample_update(context), + Config: testAccNetappvolumereplication_netappVolumeReplicationCreateExample_waitformirror(context), }, { ResourceName: "google_netapp_volumereplication.test_replication", ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"destination_volume_parameters", "location", "volume_name", "name", "labels", "terraform_labels"}, + ImportStateVerifyIgnore: []string{"transferStats", "destination_volume_parameters", "location", "volume_name", "name", "labels", "terraform_labels", "delete_destination_volume", "force_stopping", "replication_enabled"}, }, { Config: testAccNetappvolumereplication_netappVolumeReplicationCreateExample_stop(context), @@ -50,7 +49,7 @@ func TestAccNetappvolumereplication_netappVolumeReplicationCreateExample_update( ResourceName: "google_netapp_volumereplication.test_replication", ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"destination_volume_parameters", "location", "volume_name", "name", "labels", "terraform_labels"}, + ImportStateVerifyIgnore: []string{"transferStats", "destination_volume_parameters", "location", "volume_name", "name", "labels", "terraform_labels", "delete_destination_volume", "force_stopping", "replication_enabled"}, }, { Config: testAccNetappvolumereplication_netappVolumeReplicationCreateExample_resume(context), @@ -59,7 +58,16 @@ func TestAccNetappvolumereplication_netappVolumeReplicationCreateExample_update( ResourceName: "google_netapp_volumereplication.test_replication", ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"destination_volume_parameters", "location", "volume_name", "name", "labels", "terraform_labels"}, + ImportStateVerifyIgnore: []string{"transferStats", "destination_volume_parameters", "location", "volume_name", "name", "labels", "terraform_labels", "delete_destination_volume", "force_stopping", "replication_enabled"}, + }, + { + Config: testAccNetappvolumereplication_netappVolumeReplicationCreateExample_update(context), + }, + { + ResourceName: "google_netapp_volumereplication.test_replication", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"transferStats", "destination_volume_parameters", "location", "volume_name", "name", "labels", "terraform_labels", "delete_destination_volume", "force_stopping", "replication_enabled"}, }, }, }) @@ -67,8 +75,7 @@ func TestAccNetappvolumereplication_netappVolumeReplicationCreateExample_update( // Basic replication func testAccNetappvolumereplication_netappVolumeReplicationCreateExample_basic(context map[string]interface{}) string { - var result string = acctest.Nprintf(` - + return acctest.Nprintf(` data "google_compute_network" "default" { name = "%{network_name}" } @@ -100,7 +107,7 @@ resource "google_netapp_volume" "source_volume" { ] } -resource "google_netapp_volumereplication" "my-replication" { +resource "google_netapp_volumereplication" "test_replication" { depends_on = [google_netapp_volume.source_volume] location = google_netapp_volume.source_volume.location volume_name = google_netapp_volume.source_volume.name @@ -114,11 +121,63 @@ resource "google_netapp_volumereplication" "my-replication" { share_name = "tf-test-source-volume%{random_suffix}" description = "This is a replicated volume" } + delete_destination_volume = true + wait_for_mirror = true } `, context) - // Give mirror some time to reach mirror_state==MIRRORED state - time.Sleep(120 * time.Second) - return result +} + +func testAccNetappvolumereplication_netappVolumeReplicationCreateExample_waitformirror(context map[string]interface{}) string { + return acctest.Nprintf(` + data "google_compute_network" "default" { + name = "%{network_name}" + } + + resource "google_netapp_storage_pool" "source_pool" { + name = "tf-test-source-pool%{random_suffix}" + location = "us-central1" + service_level = "PREMIUM" + capacity_gib = 2048 + network = data.google_compute_network.default.id + } + + resource "google_netapp_storage_pool" "destination_pool" { + name = "tf-test-destination-pool%{random_suffix}" + location = "us-west2" + service_level = "PREMIUM" + capacity_gib = 2048 + network = data.google_compute_network.default.id + } + + resource "google_netapp_volume" "source_volume" { + location = google_netapp_storage_pool.source_pool.location + name = "tf-test-source-volume%{random_suffix}" + capacity_gib = 100 + share_name = "tf-test-source-volume%{random_suffix}" + storage_pool = google_netapp_storage_pool.source_pool.name + protocols = [ + "NFSV3" + ] + } + + resource "google_netapp_volumereplication" "test_replication" { + depends_on = [google_netapp_volume.source_volume] + location = google_netapp_volume.source_volume.location + volume_name = google_netapp_volume.source_volume.name + name = "tf-test-test-replication%{random_suffix}" + replication_schedule = "EVERY_10_MINUTES" + destination_volume_parameters { + storage_pool = google_netapp_storage_pool.destination_pool.id + volume_id = "tf-test-destination-volume%{random_suffix}" + # Keeping the share_name of source and destination the same makes + # simplifies implementing client failover concepts + share_name = "tf-test-source-volume%{random_suffix}" + description = "This is a replicated volume" + } + delete_destination_volume = true + wait_for_mirror = true + } + `, context) } // Update parameters @@ -156,16 +215,17 @@ resource "google_netapp_volume" "source_volume" { ] } -resource "google_netapp_volumereplication" "my-replication" { +resource "google_netapp_volumereplication" "test_replication" { depends_on = [google_netapp_volume.source_volume] location = google_netapp_volume.source_volume.location volume_name = google_netapp_volume.source_volume.name name = "tf-test-test-replication%{random_suffix}" - replication_schedule = "HOURLY" + replication_schedule = "EVERY_10_MINUTES" description = "This is a replication resource" - labels { - "foo": "bar", - } + labels = { + key = "test" + value = "replication" + } destination_volume_parameters { storage_pool = google_netapp_storage_pool.destination_pool.id volume_id = "tf-test-destination-volume%{random_suffix}" @@ -177,6 +237,7 @@ resource "google_netapp_volumereplication" "my-replication" { replication_enabled = true delete_destination_volume = true force_stopping = true + wait_for_mirror = true } `, context) } @@ -216,15 +277,16 @@ resource "google_netapp_volume" "source_volume" { ] } -resource "google_netapp_volumereplication" "my-replication" { +resource "google_netapp_volumereplication" "test_replication" { depends_on = [google_netapp_volume.source_volume] location = google_netapp_volume.source_volume.location volume_name = google_netapp_volume.source_volume.name name = "tf-test-test-replication%{random_suffix}" - replication_schedule = "HOURLY" + replication_schedule = "EVERY_10_MINUTES" description = "This is a replication resource" - labels { - "foo": "bar", + labels = { + key = "test" + value = "replication2" } destination_volume_parameters { storage_pool = google_netapp_storage_pool.destination_pool.id @@ -237,6 +299,8 @@ resource "google_netapp_volumereplication" "my-replication" { replication_enabled = false delete_destination_volume = true force_stopping = true + wait_for_mirror = true +} `, context) } @@ -275,15 +339,16 @@ resource "google_netapp_volume" "source_volume" { ] } -resource "google_netapp_volumereplication" "my-replication" { +resource "google_netapp_volumereplication" "test_replication" { depends_on = [google_netapp_volume.source_volume] location = google_netapp_volume.source_volume.location volume_name = google_netapp_volume.source_volume.name name = "tf-test-test-replication%{random_suffix}" replication_schedule = "HOURLY" description = "This is a replication resource" - labels { - "foo": "bar", + labels = { + key = "test" + value = "replication2" } destination_volume_parameters { storage_pool = google_netapp_storage_pool.destination_pool.id @@ -296,5 +361,7 @@ resource "google_netapp_volumereplication" "my-replication" { replication_enabled = true delete_destination_volume = true force_stopping = true + wait_for_mirror = true +} `, context) } From b791946f859a7914a2426a954f5ed47ac20bd09c Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Mon, 22 Jan 2024 16:03:59 +0100 Subject: [PATCH 19/34] Test updates --- .../resource_netapp_volumereplication_test.go | 62 ------------------- 1 file changed, 62 deletions(-) diff --git a/mmv1/third_party/terraform/services/netapp/resource_netapp_volumereplication_test.go b/mmv1/third_party/terraform/services/netapp/resource_netapp_volumereplication_test.go index ebb248d85d8f..6ce5d3b78c6a 100644 --- a/mmv1/third_party/terraform/services/netapp/resource_netapp_volumereplication_test.go +++ b/mmv1/third_party/terraform/services/netapp/resource_netapp_volumereplication_test.go @@ -33,15 +33,6 @@ func TestAccNetappvolumereplication_netappVolumeReplicationCreateExample_update( ImportStateVerify: true, ImportStateVerifyIgnore: []string{"transferStats", "destination_volume_parameters", "location", "volume_name", "name", "labels", "terraform_labels", "delete_destination_volume", "force_stopping", "replication_enabled"}, }, - { - Config: testAccNetappvolumereplication_netappVolumeReplicationCreateExample_waitformirror(context), - }, - { - ResourceName: "google_netapp_volumereplication.test_replication", - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"transferStats", "destination_volume_parameters", "location", "volume_name", "name", "labels", "terraform_labels", "delete_destination_volume", "force_stopping", "replication_enabled"}, - }, { Config: testAccNetappvolumereplication_netappVolumeReplicationCreateExample_stop(context), }, @@ -127,59 +118,6 @@ resource "google_netapp_volumereplication" "test_replication" { `, context) } -func testAccNetappvolumereplication_netappVolumeReplicationCreateExample_waitformirror(context map[string]interface{}) string { - return acctest.Nprintf(` - data "google_compute_network" "default" { - name = "%{network_name}" - } - - resource "google_netapp_storage_pool" "source_pool" { - name = "tf-test-source-pool%{random_suffix}" - location = "us-central1" - service_level = "PREMIUM" - capacity_gib = 2048 - network = data.google_compute_network.default.id - } - - resource "google_netapp_storage_pool" "destination_pool" { - name = "tf-test-destination-pool%{random_suffix}" - location = "us-west2" - service_level = "PREMIUM" - capacity_gib = 2048 - network = data.google_compute_network.default.id - } - - resource "google_netapp_volume" "source_volume" { - location = google_netapp_storage_pool.source_pool.location - name = "tf-test-source-volume%{random_suffix}" - capacity_gib = 100 - share_name = "tf-test-source-volume%{random_suffix}" - storage_pool = google_netapp_storage_pool.source_pool.name - protocols = [ - "NFSV3" - ] - } - - resource "google_netapp_volumereplication" "test_replication" { - depends_on = [google_netapp_volume.source_volume] - location = google_netapp_volume.source_volume.location - volume_name = google_netapp_volume.source_volume.name - name = "tf-test-test-replication%{random_suffix}" - replication_schedule = "EVERY_10_MINUTES" - destination_volume_parameters { - storage_pool = google_netapp_storage_pool.destination_pool.id - volume_id = "tf-test-destination-volume%{random_suffix}" - # Keeping the share_name of source and destination the same makes - # simplifies implementing client failover concepts - share_name = "tf-test-source-volume%{random_suffix}" - description = "This is a replicated volume" - } - delete_destination_volume = true - wait_for_mirror = true - } - `, context) -} - // Update parameters func testAccNetappvolumereplication_netappVolumeReplicationCreateExample_update(context map[string]interface{}) string { return acctest.Nprintf(` From cbf790f68377bb151d5e786b7481acfb4469f854 Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Tue, 23 Jan 2024 10:56:52 +0100 Subject: [PATCH 20/34] few cleanups --- mmv1/products/netapp/volume.yaml | 5 +++-- mmv1/templates/terraform/examples/volume_basic.tf.erb | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/mmv1/products/netapp/volume.yaml b/mmv1/products/netapp/volume.yaml index 4e9819fbe1f5..b2f1c74b4592 100644 --- a/mmv1/products/netapp/volume.yaml +++ b/mmv1/products/netapp/volume.yaml @@ -203,7 +203,7 @@ properties: name: 'description' description: | An optional description of this resource. - # We want to get rid of this. Please don't expose. + # Use of snapReserve is depricated. We don't expose it intentially. # - !ruby/object:Api::Type::Integer # name: 'snapReserve' # description: | @@ -425,5 +425,6 @@ virtual_fields: - :DEFAULT - :FORCE default_value: :DEFAULT + ignore_read_extra: true custom_code: !ruby/object:Provider::Terraform::CustomCode - pre_delete: templates/terraform/pre_delete/netapp_volume_force_delete.go.erb \ No newline at end of file + pre_delete: templates/terraform/pre_delete/netapp_volume_force_delete.go.erb diff --git a/mmv1/templates/terraform/examples/volume_basic.tf.erb b/mmv1/templates/terraform/examples/volume_basic.tf.erb index 3f34bf06b56a..54702e41c441 100644 --- a/mmv1/templates/terraform/examples/volume_basic.tf.erb +++ b/mmv1/templates/terraform/examples/volume_basic.tf.erb @@ -13,6 +13,7 @@ resource "google_netapp_volume" "<%= ctx[:primary_resource_id] %>" { share_name = "<%= ctx[:vars]['volume_name'] %>" storage_pool = google_netapp_storage_pool.default.name protocols = ["NFSV3"] + deletion_policy = "DEFAULT" } data "google_compute_network" "default" { From e785eb85c9472c3f33dba2565be9c7e20f2cf142 Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Tue, 23 Jan 2024 11:12:32 +0100 Subject: [PATCH 21/34] Make virtual field verifies happy --- mmv1/products/netapp/volume.yaml | 4 +++- .../services/netapp/resource_netapp_volume_test.go | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/mmv1/products/netapp/volume.yaml b/mmv1/products/netapp/volume.yaml index b2f1c74b4592..d07b6ae41dbf 100644 --- a/mmv1/products/netapp/volume.yaml +++ b/mmv1/products/netapp/volume.yaml @@ -59,6 +59,8 @@ examples: volume_name: 'test-volume' pool_name: 'test-pool' network_name: 'test-network' + ignore_read_extra: + - "deletion_policy" test_vars_overrides: network_name: 'acctest.BootstrapSharedServiceNetworkingConnection(t, "gcnv-network-config-1", acctest.ServiceNetworkWithParentService("netapp.servicenetworking.goog"))' properties: @@ -425,6 +427,6 @@ virtual_fields: - :DEFAULT - :FORCE default_value: :DEFAULT - ignore_read_extra: true + ignore_read: true custom_code: !ruby/object:Provider::Terraform::CustomCode pre_delete: templates/terraform/pre_delete/netapp_volume_force_delete.go.erb diff --git a/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_test.go b/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_test.go index 1571c89591fe..27266475c32c 100644 --- a/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_test.go +++ b/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_test.go @@ -45,7 +45,7 @@ func TestAccNetappVolume_volumeBasicExample_update(t *testing.T) { ResourceName: "google_netapp_volume.test_volume", ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"location", "name", "labels", "terraform_labels"}, + ImportStateVerifyIgnore: []string{"location", "name", "labels", "terraform_labels", "deletion_policy"}, }, { Config: testAccNetappVolume_volumeBasicExample_full(context), }, @@ -53,7 +53,7 @@ func TestAccNetappVolume_volumeBasicExample_update(t *testing.T) { ResourceName: "google_netapp_volume.test_volume", ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"location", "name", "labels", "terraform_labels"}, + ImportStateVerifyIgnore: []string{"location", "name", "labels", "terraform_labels", "deletion_policy"}, }, { Config: testAccNetappVolume_volumeBasicExample_update(context), @@ -62,7 +62,7 @@ func TestAccNetappVolume_volumeBasicExample_update(t *testing.T) { ResourceName: "google_netapp_volume.test_volume", ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"location", "name", "labels", "terraform_labels"}, + ImportStateVerifyIgnore: []string{"location", "name", "labels", "terraform_labels", "deletion_policy"}, }, { Config: testAccNetappVolume_volumeBasicExample_updatesnapshot(context), @@ -71,7 +71,7 @@ func TestAccNetappVolume_volumeBasicExample_update(t *testing.T) { ResourceName: "google_netapp_volume.test_volume", ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"location", "name", "labels", "terraform_labels"}, + ImportStateVerifyIgnore: []string{"location", "name", "labels", "terraform_labels", "deletion_policy"}, }, }, }) From 7c1ed3f7542d9a775dc761283a548791b2aa1cec Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Tue, 23 Jan 2024 11:34:48 +0100 Subject: [PATCH 22/34] Minor test improvements --- .../services/netapp/resource_netapp_volume_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_test.go b/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_test.go index 27266475c32c..386e29eb7f42 100644 --- a/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_test.go +++ b/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_test.go @@ -318,10 +318,22 @@ resource "google_netapp_volume" "test_volume" { nfsv4 = false } } + # Delete protection only gets active after an NFS client mounts. + # Setting it here is save, volume can still be deleted. restricted_actions = ["DELETE"] deletion_policy = "FORCE" } +# Add the following snapshot block to the test as soon as snapshot resoruce +# is added to the provider. It will make the test cleanup require +# deletion_policy = "FORCE" on the volume for successful delete. +# resource "google_netapp_volumesnapshot" "test-snapshot" { +# depends_on = [google_netapp_volume.test_volume] +# location = google_netapp_volume.test_volume.location +# volume_name = google_netapp_volume.test_volume.name +# name = "test-snapshot%{random_suffix}" +# } + data "google_compute_network" "default" { name = "%{network_name}" } From 905290d62d5b0387d93870b87cd2b572868b74db Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Tue, 23 Jan 2024 17:07:26 +0100 Subject: [PATCH 23/34] More fine tuning - Remove merge conflict in volume.yaml - make generated test work - make output field work - ignore_read for virtual fields --- mmv1/products/netapp/volume.yaml | 2 -- mmv1/products/netapp/volumereplication.yaml | 17 ++++++++++++ .../netapp_volume_replication_create.tf.erb | 3 ++- ...etapp_volumereplication_post_create.go.erb | 2 +- ...plication_delete_destination_volume.go.erb | 26 ++++++++++--------- ...tapp_volumereplication_mirror_state.go.erb | 2 +- .../resource_netapp_volumereplication_test.go | 20 ++++++++------ 7 files changed, 47 insertions(+), 25 deletions(-) diff --git a/mmv1/products/netapp/volume.yaml b/mmv1/products/netapp/volume.yaml index 3957b90f57c6..d07b6ae41dbf 100644 --- a/mmv1/products/netapp/volume.yaml +++ b/mmv1/products/netapp/volume.yaml @@ -27,8 +27,6 @@ references: !ruby/object:Api::Resource::ReferenceLinks base_url: projects/{{project}}/locations/{{location}}/volumes self_link: projects/{{project}}/locations/{{location}}/volumes/{{name}} create_url: projects/{{project}}/locations/{{location}}/volumes?volumeId={{name}} -# Snapshots are nested resources of volumes. Add force=true to delete volumes with snapshots. -delete_url: 'projects/{{project}}/locations/{{location}}/volumes/{{name}}?force=true' update_url: projects/{{project}}/locations/{{location}}/volumes/{{name}} update_verb: :PATCH update_mask: true diff --git a/mmv1/products/netapp/volumereplication.yaml b/mmv1/products/netapp/volumereplication.yaml index c15031523c45..1903f4904b81 100644 --- a/mmv1/products/netapp/volumereplication.yaml +++ b/mmv1/products/netapp/volumereplication.yaml @@ -58,6 +58,11 @@ examples: replication_name: "test-replication" destination_volume: "destination-volume" network_name: "test-network" + ignore_read_extra: + - "delete_destination_volume" + - "replication_enabled" + - "force_stopping" + - "wait_for_mirror" test_vars_overrides: network_name: 'acctest.BootstrapSharedServiceNetworkingConnection(t, "gcnv-network-config-1", acctest.ServiceNetworkWithParentService("netapp.servicenetworking.goog"))' parameters: @@ -152,36 +157,44 @@ properties: name: "transferBytes" description: | Number of bytes transferred so far in current transfer. + output: true - !ruby/object:Api::Type::String name: "totalTransferDuration" description: | Total time taken so far during current transfer. + output: true - !ruby/object:Api::Type::String name: "lastTransferBytes" description: | Size of last completed transfer in bytes. + output: true - !ruby/object:Api::Type::String name: "lastTransferDuration" description: | Time taken during last completed transfer. + output: true - !ruby/object:Api::Type::String name: "lagDuration" description: | The elapsed time since the creation of the snapshot on the source volume that was last replicated to the destination volume. Lag time represents the difference in age of the destination volume data in relation to the source volume data. + output: true - !ruby/object:Api::Type::String name: "updateTime" description: | Time when progress was updated last. A timestamp in RFC3339 UTC "Zulu" format. Examples: "2023-06-22T09:13:01.617Z". + output: true - !ruby/object:Api::Type::String name: "lastTransferEndTime" description: | Time when last transfer completed. A timestamp in RFC3339 UTC "Zulu" format. Examples: "2023-06-22T09:13:01.617Z". + output: true - !ruby/object:Api::Type::String name: "lastTransferError" description: | A message describing the cause of the last transfer failure. + output: true - !ruby/object:Api::Type::KeyValueLabels name: "labels" description: | @@ -233,6 +246,7 @@ virtual_fields: description: | Delete destination volume when replication resource is destroyed? Default is false. default_value: false + ignore_read: true - !ruby/object:Api::Type::Boolean name: "replication_enabled" description: | @@ -242,6 +256,7 @@ virtual_fields: Set to true to enable/resume the mirror. WARNING: Resuming a mirror overwrites any changes done to the destination volume with the content of the source volume. default_value: true + ignore_read: true - !ruby/object:Api::Type::Boolean name: "force_stopping" description: | @@ -250,6 +265,7 @@ virtual_fields: to stop anyway. All data transferred to the destination will be discarded and content of destination volume will remain at the state of the last successful update. Default is false. default_value: false + ignore_read: true - !ruby/object:Api::Type::Boolean name: "wait_for_mirror" description: | @@ -257,6 +273,7 @@ virtual_fields: for mirror_state to reach MIRRORED. If you want Terraform to wait for the mirror to finish one create/stop/resume operations, set this parameter to true. Default is false. default_value: false + ignore_read: true # The following functionality needs to be discussed in review process custom_code: !ruby/object:Provider::Terraform::CustomCode constants: templates/terraform/constants/netapp_volumereplication.go.erb diff --git a/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb b/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb index 4f3e15b280ad..1b694268ea6b 100644 --- a/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb +++ b/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb @@ -28,6 +28,7 @@ resource "google_netapp_volume" "source_volume" { protocols = [ "NFSV3" ] + deletion_policy = "FORCE" } resource "google_netapp_volumereplication" "<%= ctx[:primary_resource_id] %>" { @@ -40,7 +41,7 @@ resource "google_netapp_volumereplication" "<%= ctx[:primary_resource_id] %>" { destination_volume_parameters { storage_pool = google_netapp_storage_pool.destination_pool.id volume_id = "<%= ctx[:vars]['destination_volume'] %>" - # Keeping the share_name of source and destination the same makes + # Keeping the share_name of source and destination the same # simplifies implementing client failover concepts share_name = "<%= ctx[:vars]['volume_name'] %>" description = "This is a replicated volume" diff --git a/mmv1/templates/terraform/post_create/netapp_volumereplication_post_create.go.erb b/mmv1/templates/terraform/post_create/netapp_volumereplication_post_create.go.erb index 5a980cbde3e2..3989595b068b 100644 --- a/mmv1/templates/terraform/post_create/netapp_volumereplication_post_create.go.erb +++ b/mmv1/templates/terraform/post_create/netapp_volumereplication_post_create.go.erb @@ -1,4 +1,4 @@ -if d.Get("wait_for_mirror") == true { +if d.Get("wait_for_mirror").(bool) == true { // Wait for mirrorState=MIRRORED before treating the resource as created err = NetAppVolumeReplicationWaitForMirror(d, meta, "MIRRORED") if err != nil { diff --git a/mmv1/templates/terraform/post_delete/netapp_volumereplication_delete_destination_volume.go.erb b/mmv1/templates/terraform/post_delete/netapp_volumereplication_delete_destination_volume.go.erb index c8d7f4724010..5a14f1fb407c 100644 --- a/mmv1/templates/terraform/post_delete/netapp_volumereplication_delete_destination_volume.go.erb +++ b/mmv1/templates/terraform/post_delete/netapp_volumereplication_delete_destination_volume.go.erb @@ -1,32 +1,34 @@ // A replication CREATE also created a destination volume // A user can chooses to delete the destination volume after deleting the replication -log.Printf("[DEBUG] value of destvol %q: %v", reflect.TypeOf(d.Get("destination_volume")), d.Get("destination_volume")) if d.Get("delete_destination_volume").(bool) == true { - str1 := d.Get("destination_volume").(string) - // Always force volume deletion - res1 := str1 + "?force=true" - de_url, err := tpgresource.ReplaceVars(d, config, "{{NetappBasePath}}") + log.Printf("[DEBUG] delete_destination_volume is true. Deleting destination volume %v", d.Get("destination_volume")) + destination_volume := d.Get("destination_volume").(string) + del_url, err := tpgresource.ReplaceVars(d, config, "{{NetappBasePath}}"+destination_volume+"?force=true") if err != nil { return err } - var obj1 map[string]interface{} - res2, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + + var obj map[string]interface{} + res_del, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ Config: config, Method: "DELETE", Project: billingProject, - RawURL: de_url + res1, + RawURL: del_url, UserAgent: userAgent, - Body: obj1, + Body: obj, Timeout: d.Timeout(schema.TimeoutDelete), }) if err != nil { - return transport_tpg.HandleNotFoundError(err, d, "volume") + return transport_tpg.HandleNotFoundError(err, d, "Volume") } + err = NetappOperationWaitTime( - config, res2, project, "Deleting volume", userAgent, + config, res_del, project, "Deleting destination volume", userAgent, d.Timeout(schema.TimeoutDelete)) + if err != nil { return err } - log.Printf("[DEBUG] Finished deleting volume %q: %#v", d.Id(), res2) + + log.Printf("[DEBUG] Finished deleting destination Volume %q: %#v", destination_volume, res_del) } \ No newline at end of file diff --git a/mmv1/templates/terraform/post_update/netapp_volumereplication_mirror_state.go.erb b/mmv1/templates/terraform/post_update/netapp_volumereplication_mirror_state.go.erb index 8771853d7f42..39876ff34e4e 100644 --- a/mmv1/templates/terraform/post_update/netapp_volumereplication_mirror_state.go.erb +++ b/mmv1/templates/terraform/post_update/netapp_volumereplication_mirror_state.go.erb @@ -71,7 +71,7 @@ if do_change { } err = NetappOperationWaitTime( - config, res2, project, "Deleting volumereplication", userAgent, + config, res2, project, "volumereplication "+action, userAgent, d.Timeout(schema.TimeoutDelete)) if err != nil { diff --git a/mmv1/third_party/terraform/services/netapp/resource_netapp_volumereplication_test.go b/mmv1/third_party/terraform/services/netapp/resource_netapp_volumereplication_test.go index 6ce5d3b78c6a..95f06176da72 100644 --- a/mmv1/third_party/terraform/services/netapp/resource_netapp_volumereplication_test.go +++ b/mmv1/third_party/terraform/services/netapp/resource_netapp_volumereplication_test.go @@ -31,7 +31,7 @@ func TestAccNetappvolumereplication_netappVolumeReplicationCreateExample_update( ResourceName: "google_netapp_volumereplication.test_replication", ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"transferStats", "destination_volume_parameters", "location", "volume_name", "name", "labels", "terraform_labels", "delete_destination_volume", "force_stopping", "replication_enabled"}, + ImportStateVerifyIgnore: []string{"destination_volume_parameters", "location", "volume_name", "name", "delete_destination_volume", "replication_enabled", "force_stopping", "wait_for_mirror", "labels", "terraform_labels"}, }, { Config: testAccNetappvolumereplication_netappVolumeReplicationCreateExample_stop(context), @@ -40,7 +40,7 @@ func TestAccNetappvolumereplication_netappVolumeReplicationCreateExample_update( ResourceName: "google_netapp_volumereplication.test_replication", ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"transferStats", "destination_volume_parameters", "location", "volume_name", "name", "labels", "terraform_labels", "delete_destination_volume", "force_stopping", "replication_enabled"}, + ImportStateVerifyIgnore: []string{"destination_volume_parameters", "location", "volume_name", "name", "delete_destination_volume", "replication_enabled", "force_stopping", "wait_for_mirror", "labels", "terraform_labels"}, }, { Config: testAccNetappvolumereplication_netappVolumeReplicationCreateExample_resume(context), @@ -49,7 +49,7 @@ func TestAccNetappvolumereplication_netappVolumeReplicationCreateExample_update( ResourceName: "google_netapp_volumereplication.test_replication", ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"transferStats", "destination_volume_parameters", "location", "volume_name", "name", "labels", "terraform_labels", "delete_destination_volume", "force_stopping", "replication_enabled"}, + ImportStateVerifyIgnore: []string{"destination_volume_parameters", "location", "volume_name", "name", "delete_destination_volume", "replication_enabled", "force_stopping", "wait_for_mirror", "labels", "terraform_labels"}, }, { Config: testAccNetappvolumereplication_netappVolumeReplicationCreateExample_update(context), @@ -58,7 +58,7 @@ func TestAccNetappvolumereplication_netappVolumeReplicationCreateExample_update( ResourceName: "google_netapp_volumereplication.test_replication", ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"transferStats", "destination_volume_parameters", "location", "volume_name", "name", "labels", "terraform_labels", "delete_destination_volume", "force_stopping", "replication_enabled"}, + ImportStateVerifyIgnore: []string{"destination_volume_parameters", "location", "volume_name", "name", "delete_destination_volume", "replication_enabled", "force_stopping", "wait_for_mirror", "labels", "terraform_labels"}, }, }, }) @@ -96,6 +96,7 @@ resource "google_netapp_volume" "source_volume" { protocols = [ "NFSV3" ] + deletion_policy = "FORCE" } resource "google_netapp_volumereplication" "test_replication" { @@ -107,7 +108,7 @@ resource "google_netapp_volumereplication" "test_replication" { destination_volume_parameters { storage_pool = google_netapp_storage_pool.destination_pool.id volume_id = "tf-test-destination-volume%{random_suffix}" - # Keeping the share_name of source and destination the same makes + # Keeping the share_name of source and destination the same # simplifies implementing client failover concepts share_name = "tf-test-source-volume%{random_suffix}" description = "This is a replicated volume" @@ -151,6 +152,7 @@ resource "google_netapp_volume" "source_volume" { protocols = [ "NFSV3" ] + deletion_policy = "FORCE" } resource "google_netapp_volumereplication" "test_replication" { @@ -167,7 +169,7 @@ resource "google_netapp_volumereplication" "test_replication" { destination_volume_parameters { storage_pool = google_netapp_storage_pool.destination_pool.id volume_id = "tf-test-destination-volume%{random_suffix}" - # Keeping the share_name of source and destination the same makes + # Keeping the share_name of source and destination the same # simplifies implementing client failover concepts share_name = "tf-test-source-volume%{random_suffix}" description = "This is a replicated volume" @@ -213,6 +215,7 @@ resource "google_netapp_volume" "source_volume" { protocols = [ "NFSV3" ] + deletion_policy = "FORCE" } resource "google_netapp_volumereplication" "test_replication" { @@ -229,7 +232,7 @@ resource "google_netapp_volumereplication" "test_replication" { destination_volume_parameters { storage_pool = google_netapp_storage_pool.destination_pool.id volume_id = "tf-test-destination-volume%{random_suffix}" - # Keeping the share_name of source and destination the same makes + # Keeping the share_name of source and destination the same # simplifies implementing client failover concepts share_name = "tf-test-source-volume%{random_suffix}" description = "This is a replicated volume" @@ -275,6 +278,7 @@ resource "google_netapp_volume" "source_volume" { protocols = [ "NFSV3" ] + deletion_policy = "FORCE" } resource "google_netapp_volumereplication" "test_replication" { @@ -291,7 +295,7 @@ resource "google_netapp_volumereplication" "test_replication" { destination_volume_parameters { storage_pool = google_netapp_storage_pool.destination_pool.id volume_id = "tf-test-destination-volume%{random_suffix}" - # Keeping the share_name of source and destination the same makes + # Keeping the share_name of source and destination the same # simplifies implementing client failover concepts share_name = "tf-test-source-volume%{random_suffix}" description = "This is a replicated volume" From 1b206ccd1f9bd6cf44846f3ad6bc09742c87d9a6 Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Tue, 23 Jan 2024 18:58:26 +0100 Subject: [PATCH 24/34] Resource name change as suggested by @slevenick --- mmv1/products/netapp/volumereplication.yaml | 12 +++---- ...o.erb => netapp_volume_replication.go.erb} | 4 +-- .../netapp_volume_replication_create.tf.erb | 2 +- ...app_volume_replication_post_create.go.erb} | 2 +- ...lication_delete_destination_volume.go.erb} | 0 ...pp_volume_replication_mirror_state.go.erb} | 4 +-- ...ume_replication_stop_before_delete.go.erb} | 4 +-- ... => netapp_volume_replication_read.tf.erb} | 0 ...esource_netapp_volume_replication_test.go} | 36 +++++++++---------- 9 files changed, 32 insertions(+), 32 deletions(-) rename mmv1/templates/terraform/constants/{netapp_volumereplication.go.erb => netapp_volume_replication.go.erb} (92%) rename mmv1/templates/terraform/post_create/{netapp_volumereplication_post_create.go.erb => netapp_volume_replication_post_create.go.erb} (66%) rename mmv1/templates/terraform/post_delete/{netapp_volumereplication_delete_destination_volume.go.erb => netapp_volume_replication_delete_destination_volume.go.erb} (100%) rename mmv1/templates/terraform/post_update/{netapp_volumereplication_mirror_state.go.erb => netapp_volume_replication_mirror_state.go.erb} (94%) rename mmv1/templates/terraform/pre_delete/{netapp_volumereplication_stop_before_delete.go.erb => netapp_volume_replication_stop_before_delete.go.erb} (88%) rename mmv1/templates/terraform/pre_read/{netapp_volumereplication_read.tf.erb => netapp_volume_replication_read.tf.erb} (100%) rename mmv1/third_party/terraform/services/netapp/{resource_netapp_volumereplication_test.go => resource_netapp_volume_replication_test.go} (89%) diff --git a/mmv1/products/netapp/volumereplication.yaml b/mmv1/products/netapp/volumereplication.yaml index 1903f4904b81..f5dbdce25760 100644 --- a/mmv1/products/netapp/volumereplication.yaml +++ b/mmv1/products/netapp/volumereplication.yaml @@ -13,7 +13,7 @@ --- !ruby/object:Api::Resource -name: "volumereplication" +name: "VolumeReplication" description: | Volume replication creates an asynchronous mirror of a volume in a different location. This capability lets you use the replicated volume for critical application activity in case of a location-wide outage @@ -276,8 +276,8 @@ virtual_fields: ignore_read: true # The following functionality needs to be discussed in review process custom_code: !ruby/object:Provider::Terraform::CustomCode - constants: templates/terraform/constants/netapp_volumereplication.go.erb - post_create: templates/terraform/post_create/netapp_volumereplication_post_create.go.erb - pre_delete: templates/terraform/pre_delete/netapp_volumereplication_stop_before_delete.go.erb - post_delete: templates/terraform/post_delete/netapp_volumereplication_delete_destination_volume.go.erb - post_update: templates/terraform/post_update/netapp_volumereplication_mirror_state.go.erb + constants: templates/terraform/constants/netapp_volume_replication.go.erb + post_create: templates/terraform/post_create/netapp_volume_replication_post_create.go.erb + pre_delete: templates/terraform/pre_delete/netapp_volume_replication_stop_before_delete.go.erb + post_delete: templates/terraform/post_delete/netapp_volume_replication_delete_destination_volume.go.erb + post_update: templates/terraform/post_update/netapp_volume_replication_mirror_state.go.erb diff --git a/mmv1/templates/terraform/constants/netapp_volumereplication.go.erb b/mmv1/templates/terraform/constants/netapp_volume_replication.go.erb similarity index 92% rename from mmv1/templates/terraform/constants/netapp_volumereplication.go.erb rename to mmv1/templates/terraform/constants/netapp_volume_replication.go.erb index 6ad8d0c3240a..10ec7b806253 100644 --- a/mmv1/templates/terraform/constants/netapp_volumereplication.go.erb +++ b/mmv1/templates/terraform/constants/netapp_volume_replication.go.erb @@ -15,7 +15,7 @@ func NetAppVolumeReplicationWaitForMirror(d *schema.ResourceData, meta interface project, err := tpgresource.GetProject(d, config) if err != nil { - return fmt.Errorf("Error fetching project for volumereplication: %s", err) + return fmt.Errorf("Error fetching project for volume replication: %s", err) } billingProject = project @@ -33,7 +33,7 @@ func NetAppVolumeReplicationWaitForMirror(d *schema.ResourceData, meta interface UserAgent: userAgent, }) if err != nil { - return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("Netappvolumereplication %q", d.Id())) + return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("NetappVolumeReplication %q", d.Id())) } log.Printf("[DEBUG] waiting for mirrorState. actual: %v, target: %v", res["mirrorState"], targetState) diff --git a/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb b/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb index 1b694268ea6b..cec016d9eb75 100644 --- a/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb +++ b/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb @@ -31,7 +31,7 @@ resource "google_netapp_volume" "source_volume" { deletion_policy = "FORCE" } -resource "google_netapp_volumereplication" "<%= ctx[:primary_resource_id] %>" { +resource "google_netapp_volume_replication" "<%= ctx[:primary_resource_id] %>" { depends_on = [google_netapp_volume.source_volume] location = google_netapp_volume.source_volume.location volume_name = google_netapp_volume.source_volume.name diff --git a/mmv1/templates/terraform/post_create/netapp_volumereplication_post_create.go.erb b/mmv1/templates/terraform/post_create/netapp_volume_replication_post_create.go.erb similarity index 66% rename from mmv1/templates/terraform/post_create/netapp_volumereplication_post_create.go.erb rename to mmv1/templates/terraform/post_create/netapp_volume_replication_post_create.go.erb index 3989595b068b..b2d44a9c063e 100644 --- a/mmv1/templates/terraform/post_create/netapp_volumereplication_post_create.go.erb +++ b/mmv1/templates/terraform/post_create/netapp_volume_replication_post_create.go.erb @@ -2,6 +2,6 @@ if d.Get("wait_for_mirror").(bool) == true { // Wait for mirrorState=MIRRORED before treating the resource as created err = NetAppVolumeReplicationWaitForMirror(d, meta, "MIRRORED") if err != nil { - return fmt.Errorf("Error waiting for volumereplication to reach mirror_state==MIRRORED: %s", err) + return fmt.Errorf("Error waiting for volume replication to reach mirror_state==MIRRORED: %s", err) } } \ No newline at end of file diff --git a/mmv1/templates/terraform/post_delete/netapp_volumereplication_delete_destination_volume.go.erb b/mmv1/templates/terraform/post_delete/netapp_volume_replication_delete_destination_volume.go.erb similarity index 100% rename from mmv1/templates/terraform/post_delete/netapp_volumereplication_delete_destination_volume.go.erb rename to mmv1/templates/terraform/post_delete/netapp_volume_replication_delete_destination_volume.go.erb diff --git a/mmv1/templates/terraform/post_update/netapp_volumereplication_mirror_state.go.erb b/mmv1/templates/terraform/post_update/netapp_volume_replication_mirror_state.go.erb similarity index 94% rename from mmv1/templates/terraform/post_update/netapp_volumereplication_mirror_state.go.erb rename to mmv1/templates/terraform/post_update/netapp_volume_replication_mirror_state.go.erb index 39876ff34e4e..76e64d169497 100644 --- a/mmv1/templates/terraform/post_update/netapp_volumereplication_mirror_state.go.erb +++ b/mmv1/templates/terraform/post_update/netapp_volume_replication_mirror_state.go.erb @@ -71,7 +71,7 @@ if do_change { } err = NetappOperationWaitTime( - config, res2, project, "volumereplication "+action, userAgent, + config, res2, project, "volume replication "+action, userAgent, d.Timeout(schema.TimeoutDelete)) if err != nil { @@ -95,6 +95,6 @@ if do_change { // Wait for mirrorState=targetState before continuing err = NetAppVolumeReplicationWaitForMirror(d, meta, targetState) if err != nil { - return fmt.Errorf("Error waiting for volumereplication to reach mirror_state==%s: %s", targetState, err) + return fmt.Errorf("Error waiting for volume replication to reach mirror_state==%s: %s", targetState, err) } } diff --git a/mmv1/templates/terraform/pre_delete/netapp_volumereplication_stop_before_delete.go.erb b/mmv1/templates/terraform/pre_delete/netapp_volume_replication_stop_before_delete.go.erb similarity index 88% rename from mmv1/templates/terraform/pre_delete/netapp_volumereplication_stop_before_delete.go.erb rename to mmv1/templates/terraform/pre_delete/netapp_volume_replication_stop_before_delete.go.erb index 727fd13c620d..6b33d4b7344c 100644 --- a/mmv1/templates/terraform/pre_delete/netapp_volumereplication_stop_before_delete.go.erb +++ b/mmv1/templates/terraform/pre_delete/netapp_volume_replication_stop_before_delete.go.erb @@ -27,11 +27,11 @@ if d.Get("mirror_state") != "STOPPED" { <% end -%> }) if err != nil { - return fmt.Errorf("Error stopping volumereplication %q before deleting it: %s", d.Id(), err) + return fmt.Errorf("Error stopping volume replication %q before deleting it: %s", d.Id(), err) } err = NetappOperationWaitTime( - config, reso, project, "Deleting volumereplication", userAgent, + config, reso, project, "Deleting volume replication", userAgent, d.Timeout(schema.TimeoutDelete)) if err != nil { return err diff --git a/mmv1/templates/terraform/pre_read/netapp_volumereplication_read.tf.erb b/mmv1/templates/terraform/pre_read/netapp_volume_replication_read.tf.erb similarity index 100% rename from mmv1/templates/terraform/pre_read/netapp_volumereplication_read.tf.erb rename to mmv1/templates/terraform/pre_read/netapp_volume_replication_read.tf.erb diff --git a/mmv1/third_party/terraform/services/netapp/resource_netapp_volumereplication_test.go b/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_replication_test.go similarity index 89% rename from mmv1/third_party/terraform/services/netapp/resource_netapp_volumereplication_test.go rename to mmv1/third_party/terraform/services/netapp/resource_netapp_volume_replication_test.go index 95f06176da72..99b39657a9c6 100644 --- a/mmv1/third_party/terraform/services/netapp/resource_netapp_volumereplication_test.go +++ b/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_replication_test.go @@ -11,7 +11,7 @@ import ( "github.com/hashicorp/terraform-provider-google/google/acctest" ) -func TestAccNetappvolumereplication_netappVolumeReplicationCreateExample_update(t *testing.T) { +func TestAccNetappVolumeReplication_netappVolumeReplicationCreateExample_update(t *testing.T) { t.Parallel() context := map[string]interface{}{ @@ -22,40 +22,40 @@ func TestAccNetappvolumereplication_netappVolumeReplicationCreateExample_update( acctest.VcrTest(t, resource.TestCase{ PreCheck: func() { acctest.AccTestPreCheck(t) }, ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), - CheckDestroy: testAccCheckNetappvolumereplicationDestroyProducer(t), + CheckDestroy: testAccCheckNetappVolumeReplicationDestroyProducer(t), Steps: []resource.TestStep{ { - Config: testAccNetappvolumereplication_netappVolumeReplicationCreateExample_basic(context), + Config: testAccNetappVolumeReplication_netappVolumeReplicationCreateExample_basic(context), }, { - ResourceName: "google_netapp_volumereplication.test_replication", + ResourceName: "google_netapp_volume_replication.test_replication", ImportState: true, ImportStateVerify: true, ImportStateVerifyIgnore: []string{"destination_volume_parameters", "location", "volume_name", "name", "delete_destination_volume", "replication_enabled", "force_stopping", "wait_for_mirror", "labels", "terraform_labels"}, }, { - Config: testAccNetappvolumereplication_netappVolumeReplicationCreateExample_stop(context), + Config: testAccNetappVolumeReplication_netappVolumeReplicationCreateExample_stop(context), }, { - ResourceName: "google_netapp_volumereplication.test_replication", + ResourceName: "google_netapp_volume_replication.test_replication", ImportState: true, ImportStateVerify: true, ImportStateVerifyIgnore: []string{"destination_volume_parameters", "location", "volume_name", "name", "delete_destination_volume", "replication_enabled", "force_stopping", "wait_for_mirror", "labels", "terraform_labels"}, }, { - Config: testAccNetappvolumereplication_netappVolumeReplicationCreateExample_resume(context), + Config: testAccNetappVolumeReplication_netappVolumeReplicationCreateExample_resume(context), }, { - ResourceName: "google_netapp_volumereplication.test_replication", + ResourceName: "google_netapp_volume_replication.test_replication", ImportState: true, ImportStateVerify: true, ImportStateVerifyIgnore: []string{"destination_volume_parameters", "location", "volume_name", "name", "delete_destination_volume", "replication_enabled", "force_stopping", "wait_for_mirror", "labels", "terraform_labels"}, }, { - Config: testAccNetappvolumereplication_netappVolumeReplicationCreateExample_update(context), + Config: testAccNetappVolumeReplication_netappVolumeReplicationCreateExample_update(context), }, { - ResourceName: "google_netapp_volumereplication.test_replication", + ResourceName: "google_netapp_volume_replication.test_replication", ImportState: true, ImportStateVerify: true, ImportStateVerifyIgnore: []string{"destination_volume_parameters", "location", "volume_name", "name", "delete_destination_volume", "replication_enabled", "force_stopping", "wait_for_mirror", "labels", "terraform_labels"}, @@ -65,7 +65,7 @@ func TestAccNetappvolumereplication_netappVolumeReplicationCreateExample_update( } // Basic replication -func testAccNetappvolumereplication_netappVolumeReplicationCreateExample_basic(context map[string]interface{}) string { +func testAccNetappVolumeReplication_netappVolumeReplicationCreateExample_basic(context map[string]interface{}) string { return acctest.Nprintf(` data "google_compute_network" "default" { name = "%{network_name}" @@ -99,7 +99,7 @@ resource "google_netapp_volume" "source_volume" { deletion_policy = "FORCE" } -resource "google_netapp_volumereplication" "test_replication" { +resource "google_netapp_volume_replication" "test_replication" { depends_on = [google_netapp_volume.source_volume] location = google_netapp_volume.source_volume.location volume_name = google_netapp_volume.source_volume.name @@ -120,7 +120,7 @@ resource "google_netapp_volumereplication" "test_replication" { } // Update parameters -func testAccNetappvolumereplication_netappVolumeReplicationCreateExample_update(context map[string]interface{}) string { +func testAccNetappVolumeReplication_netappVolumeReplicationCreateExample_update(context map[string]interface{}) string { return acctest.Nprintf(` data "google_compute_network" "default" { @@ -155,7 +155,7 @@ resource "google_netapp_volume" "source_volume" { deletion_policy = "FORCE" } -resource "google_netapp_volumereplication" "test_replication" { +resource "google_netapp_volume_replication" "test_replication" { depends_on = [google_netapp_volume.source_volume] location = google_netapp_volume.source_volume.location volume_name = google_netapp_volume.source_volume.name @@ -183,7 +183,7 @@ resource "google_netapp_volumereplication" "test_replication" { } // Stop replication -func testAccNetappvolumereplication_netappVolumeReplicationCreateExample_stop(context map[string]interface{}) string { +func testAccNetappVolumeReplication_netappVolumeReplicationCreateExample_stop(context map[string]interface{}) string { return acctest.Nprintf(` data "google_compute_network" "default" { @@ -218,7 +218,7 @@ resource "google_netapp_volume" "source_volume" { deletion_policy = "FORCE" } -resource "google_netapp_volumereplication" "test_replication" { +resource "google_netapp_volume_replication" "test_replication" { depends_on = [google_netapp_volume.source_volume] location = google_netapp_volume.source_volume.location volume_name = google_netapp_volume.source_volume.name @@ -246,7 +246,7 @@ resource "google_netapp_volumereplication" "test_replication" { } // resume replication -func testAccNetappvolumereplication_netappVolumeReplicationCreateExample_resume(context map[string]interface{}) string { +func testAccNetappVolumeReplication_netappVolumeReplicationCreateExample_resume(context map[string]interface{}) string { return acctest.Nprintf(` data "google_compute_network" "default" { @@ -281,7 +281,7 @@ resource "google_netapp_volume" "source_volume" { deletion_policy = "FORCE" } -resource "google_netapp_volumereplication" "test_replication" { +resource "google_netapp_volume_replication" "test_replication" { depends_on = [google_netapp_volume.source_volume] location = google_netapp_volume.source_volume.location volume_name = google_netapp_volume.source_volume.name From 9cbc5eca7e39dc462759edaa2f818ad2fb7ee0bd Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Wed, 24 Jan 2024 13:23:11 +0100 Subject: [PATCH 25/34] Remove snapshot code block and fix typo --- mmv1/products/netapp/volume.yaml | 2 +- .../services/netapp/resource_netapp_volume_test.go | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/mmv1/products/netapp/volume.yaml b/mmv1/products/netapp/volume.yaml index d07b6ae41dbf..cf8fbd777971 100644 --- a/mmv1/products/netapp/volume.yaml +++ b/mmv1/products/netapp/volume.yaml @@ -205,7 +205,7 @@ properties: name: 'description' description: | An optional description of this resource. - # Use of snapReserve is depricated. We don't expose it intentially. + # Use of snapReserve is depricated. We don't expose it intentionally. # - !ruby/object:Api::Type::Integer # name: 'snapReserve' # description: | diff --git a/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_test.go b/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_test.go index 386e29eb7f42..c57fa406543b 100644 --- a/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_test.go +++ b/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_test.go @@ -324,16 +324,6 @@ resource "google_netapp_volume" "test_volume" { deletion_policy = "FORCE" } -# Add the following snapshot block to the test as soon as snapshot resoruce -# is added to the provider. It will make the test cleanup require -# deletion_policy = "FORCE" on the volume for successful delete. -# resource "google_netapp_volumesnapshot" "test-snapshot" { -# depends_on = [google_netapp_volume.test_volume] -# location = google_netapp_volume.test_volume.location -# volume_name = google_netapp_volume.test_volume.name -# name = "test-snapshot%{random_suffix}" -# } - data "google_compute_network" "default" { name = "%{network_name}" } From 04489c9680beda7f9c47e642d016b57e5a020415 Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Wed, 24 Jan 2024 14:30:10 +0100 Subject: [PATCH 26/34] Detect manual stop/resume actions --- ...eplication.yaml => volumeReplication.yaml} | 1 + ...tapp_volume_replicaton_mirror_state.go.erb | 20 +++++++++++++++++++ ...app_volume_replication_mirror_state.go.erb | 13 ------------ .../netapp_volume_replication_read.tf.erb | 10 ---------- 4 files changed, 21 insertions(+), 23 deletions(-) rename mmv1/products/netapp/{volumereplication.yaml => volumeReplication.yaml} (99%) create mode 100644 mmv1/templates/terraform/custom_flatten/netapp_volume_replicaton_mirror_state.go.erb delete mode 100644 mmv1/templates/terraform/pre_read/netapp_volume_replication_read.tf.erb diff --git a/mmv1/products/netapp/volumereplication.yaml b/mmv1/products/netapp/volumeReplication.yaml similarity index 99% rename from mmv1/products/netapp/volumereplication.yaml rename to mmv1/products/netapp/volumeReplication.yaml index f5dbdce25760..bafe489502a7 100644 --- a/mmv1/products/netapp/volumereplication.yaml +++ b/mmv1/products/netapp/volumeReplication.yaml @@ -136,6 +136,7 @@ properties: - :MIRRORED - :STOPPED - :TRANSFERRING + custom_flatten: templates/terraform/custom_flatten/netapp_volume_replicaton_mirror_state.go.erb output: true - !ruby/object:Api::Type::String name: "createTime" diff --git a/mmv1/templates/terraform/custom_flatten/netapp_volume_replicaton_mirror_state.go.erb b/mmv1/templates/terraform/custom_flatten/netapp_volume_replicaton_mirror_state.go.erb new file mode 100644 index 000000000000..90881e839b6e --- /dev/null +++ b/mmv1/templates/terraform/custom_flatten/netapp_volume_replicaton_mirror_state.go.erb @@ -0,0 +1,20 @@ +func flatten<%= prefix -%><%= titlelize_property(property) -%>(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + // Actual state of replication_enabled depends on mirrorState. let's update it. + // This is to pickup manual user STOP/RESUME operations on the replication. + if v == nil { + return v + } + + if v.(string) == "STOPPED" { + if err := d.Set("replication_enabled", false); err != nil { + return fmt.Errorf("Error setting replication_enabled: %s", err) + } + } else { + if err := d.Set("replication_enabled", true); err != nil { + return fmt.Errorf("Error setting replication_enabled: %s", err) + } + } + log.Printf("[DEBUG] value of replication_state : %v", d.Get("replication_enabled")) + + return v +} \ No newline at end of file diff --git a/mmv1/templates/terraform/post_update/netapp_volume_replication_mirror_state.go.erb b/mmv1/templates/terraform/post_update/netapp_volume_replication_mirror_state.go.erb index 76e64d169497..d3a3f4b392f2 100644 --- a/mmv1/templates/terraform/post_update/netapp_volume_replication_mirror_state.go.erb +++ b/mmv1/templates/terraform/post_update/netapp_volume_replication_mirror_state.go.erb @@ -75,19 +75,6 @@ if do_change { d.Timeout(schema.TimeoutDelete)) if err != nil { - // action failed. We need to fix state of virtual replication_enabled field manually. - // This can go away if Read implement state reconciliation for replication_enabled - if action == "resume" { - // resume action failed. Our mirror is still stopped. - if err := d.Set("replication_enabled", false); err != nil { - return fmt.Errorf("Error setting replication_enabled: %s", err) - } - } else { - // stop action failed. Our mirror is still mirrored. - if err := d.Set("replication_enabled", true); err != nil { - return fmt.Errorf("Error setting replication_enabled: %s", err) - } - } return err } diff --git a/mmv1/templates/terraform/pre_read/netapp_volume_replication_read.tf.erb b/mmv1/templates/terraform/pre_read/netapp_volume_replication_read.tf.erb deleted file mode 100644 index 2b02b5e2dfcb..000000000000 --- a/mmv1/templates/terraform/pre_read/netapp_volume_replication_read.tf.erb +++ /dev/null @@ -1,10 +0,0 @@ -if d.Get("mirror_state").(string) == "STOPPED" { - if err := d.Set("replication_enabled", false); err != nil { - return fmt.Errorf("Error setting replication_enabled: %s", err) - } -} else { - if err := d.Set("replication_enabled", true); err != nil { - return fmt.Errorf("Error setting replication_enabled: %s", err) - } -} -log.Printf("[DEBUG] value of replication_state : %v", d.Get("replication_enabled")) From af4168c52790d48c1da28ec6de7d57726cc0d7e1 Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Thu, 25 Jan 2024 10:59:58 +0100 Subject: [PATCH 27/34] Remove ignore_read for deletion_policy --- mmv1/products/netapp/volume.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/mmv1/products/netapp/volume.yaml b/mmv1/products/netapp/volume.yaml index cf8fbd777971..8350c8934c2b 100644 --- a/mmv1/products/netapp/volume.yaml +++ b/mmv1/products/netapp/volume.yaml @@ -427,6 +427,5 @@ virtual_fields: - :DEFAULT - :FORCE default_value: :DEFAULT - ignore_read: true custom_code: !ruby/object:Provider::Terraform::CustomCode pre_delete: templates/terraform/pre_delete/netapp_volume_force_delete.go.erb From d091d206a2e7aeb1795bbbbb5cbe750075a450b0 Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Thu, 25 Jan 2024 15:33:51 +0100 Subject: [PATCH 28/34] - Made destinationVolumeParameters immutable. It still requires ignore_read. - removed ignore_read from virtual_fields --- mmv1/products/netapp/volumeReplication.yaml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/mmv1/products/netapp/volumeReplication.yaml b/mmv1/products/netapp/volumeReplication.yaml index bafe489502a7..25b37a65e705 100644 --- a/mmv1/products/netapp/volumeReplication.yaml +++ b/mmv1/products/netapp/volumeReplication.yaml @@ -204,6 +204,7 @@ properties: name: "destinationVolumeParameters" description: |- Destination volume parameters. + immutable: true ignore_read: true properties: - !ruby/object:Api::Type::String @@ -247,7 +248,6 @@ virtual_fields: description: | Delete destination volume when replication resource is destroyed? Default is false. default_value: false - ignore_read: true - !ruby/object:Api::Type::Boolean name: "replication_enabled" description: | @@ -257,7 +257,6 @@ virtual_fields: Set to true to enable/resume the mirror. WARNING: Resuming a mirror overwrites any changes done to the destination volume with the content of the source volume. default_value: true - ignore_read: true - !ruby/object:Api::Type::Boolean name: "force_stopping" description: | @@ -266,7 +265,6 @@ virtual_fields: to stop anyway. All data transferred to the destination will be discarded and content of destination volume will remain at the state of the last successful update. Default is false. default_value: false - ignore_read: true - !ruby/object:Api::Type::Boolean name: "wait_for_mirror" description: | @@ -274,7 +272,6 @@ virtual_fields: for mirror_state to reach MIRRORED. If you want Terraform to wait for the mirror to finish one create/stop/resume operations, set this parameter to true. Default is false. default_value: false - ignore_read: true # The following functionality needs to be discussed in review process custom_code: !ruby/object:Provider::Terraform::CustomCode constants: templates/terraform/constants/netapp_volume_replication.go.erb From 054f125fa0ac70d0e35268f974c4eba198e46d80 Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Mon, 29 Jan 2024 11:36:59 +0100 Subject: [PATCH 29/34] destinationVolumeParameters are only evaluated at create. Make the immutable. --- mmv1/products/netapp/volumeReplication.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mmv1/products/netapp/volumeReplication.yaml b/mmv1/products/netapp/volumeReplication.yaml index 25b37a65e705..852373538020 100644 --- a/mmv1/products/netapp/volumeReplication.yaml +++ b/mmv1/products/netapp/volumeReplication.yaml @@ -204,28 +204,31 @@ properties: name: "destinationVolumeParameters" description: |- Destination volume parameters. - immutable: true ignore_read: true properties: - !ruby/object:Api::Type::String name: "storagePool" description: | Name of an existing storage pool for the destination volume with format: `projects/{{project}}/locations/{{location}}/storagePools/{{poolId}}` + immutable: true required: true - !ruby/object:Api::Type::String name: "volumeId" description: | Name for the destination volume to be created. If not specified, the name of the source volume will be used. + immutable: true default_from_api: true - !ruby/object:Api::Type::String name: "shareName" description: | Share name for destination volume. If not specified, name of source volume's share name will be used. + immutable: true default_from_api: true - !ruby/object:Api::Type::String name: "description" description: | Description for the destination volume. + immutable: true - !ruby/object:Api::Type::String name: "sourceVolume" description: | From 7b7f322ee63e2548e97c0308556f78ecb675bb47 Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Wed, 31 Jan 2024 12:12:49 +0100 Subject: [PATCH 30/34] Name cleanups and comment improvements --- ...eplication.yaml => VolumeReplication.yaml} | 11 ++++++--- .../netapp_volume_replication_create.tf.erb | 2 +- ...app_volume_replication_mirror_state.go.erb | 3 ++- ...lume_replication_stop_before_delete.go.erb | 24 +++++++------------ 4 files changed, 20 insertions(+), 20 deletions(-) rename mmv1/products/netapp/{volumeReplication.yaml => VolumeReplication.yaml} (95%) diff --git a/mmv1/products/netapp/volumeReplication.yaml b/mmv1/products/netapp/VolumeReplication.yaml similarity index 95% rename from mmv1/products/netapp/volumeReplication.yaml rename to mmv1/products/netapp/VolumeReplication.yaml index 852373538020..c308bf3e97de 100644 --- a/mmv1/products/netapp/volumeReplication.yaml +++ b/mmv1/products/netapp/VolumeReplication.yaml @@ -74,7 +74,7 @@ parameters: description: | Name of region for this resource. The resource needs to be created in the region of the destination volume. - !ruby/object:Api::Type::String - name: "volume_name" + name: "volumeName" description: The name of the existing source volume. required: true immutable: true @@ -249,14 +249,19 @@ virtual_fields: - !ruby/object:Api::Type::Boolean name: "delete_destination_volume" description: | - Delete destination volume when replication resource is destroyed? Default is false. + A destination volume is created as part of replication creation. The destination volume will not became + under Terraform management unless you import it manually. If you delete the replication, this volume + will remain. + Setting this parameter to true will delete the *current* destination volume when destroying the + replication. If you reversed the replication direction, this will be your former source volume! + For production use, it is recommended to keep this parameter false to avoid accidental volume + deletion. Handle with care. Default is false. default_value: false - !ruby/object:Api::Type::Boolean name: "replication_enabled" description: | Set to false to stop/break the mirror. Stopping the mirror makes the destination volume read-write and act independently from the source volume. - Set to true to enable/resume the mirror. WARNING: Resuming a mirror overwrites any changes done to the destination volume with the content of the source volume. default_value: true diff --git a/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb b/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb index cec016d9eb75..987d039104c3 100644 --- a/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb +++ b/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb @@ -47,7 +47,7 @@ resource "google_netapp_volume_replication" "<%= ctx[:primary_resource_id] %>" { description = "This is a replicated volume" } # WARNING: Setting delete_destination_volume to true, will delete the - # destination volume too, if the replication is deleted. This is great + # CURRENT destination volume if the replication is deleted. This is great # for testing, but questionable for production! delete_destination_volume = true wait_for_mirror = true diff --git a/mmv1/templates/terraform/post_update/netapp_volume_replication_mirror_state.go.erb b/mmv1/templates/terraform/post_update/netapp_volume_replication_mirror_state.go.erb index d3a3f4b392f2..7bbc3355f472 100644 --- a/mmv1/templates/terraform/post_update/netapp_volume_replication_mirror_state.go.erb +++ b/mmv1/templates/terraform/post_update/netapp_volume_replication_mirror_state.go.erb @@ -33,7 +33,8 @@ if d.Get("replication_enabled").(bool) == true { do_change = true case "PREPARING": // replication_enabled==false, mirrorState==PREPARING -> stop - // Currently cannot be stopped. User will receive intended error. + // Currently cannot be stopped. User will receive following error: + // Error code 3, message: invalid request error: "Replication in preparing state. Please wait until replication is in 'READY' STATE and try again later.". // User needs to wait until mirrorState=MIRRORED action = "stop" targetState = "STOPPED" diff --git a/mmv1/templates/terraform/pre_delete/netapp_volume_replication_stop_before_delete.go.erb b/mmv1/templates/terraform/pre_delete/netapp_volume_replication_stop_before_delete.go.erb index 6b33d4b7344c..4e5c188acf2a 100644 --- a/mmv1/templates/terraform/pre_delete/netapp_volume_replication_stop_before_delete.go.erb +++ b/mmv1/templates/terraform/pre_delete/netapp_volume_replication_stop_before_delete.go.erb @@ -5,35 +5,29 @@ if d.Get("mirror_state") != "STOPPED" { rawurl, err := tpgresource.ReplaceVars(d, config, "{{NetappBasePath}}projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/replications/{{name}}:stop") if err != nil { - return err - } + return err + } reso, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ - Config: config, - Method: "POST", - Project: billingProject, - RawURL: rawurl, + Config: config, + Method: "POST", + Project: billingProject, + RawURL: rawurl, UserAgent: userAgent, // We delete anyway, so lets always use force stop Body: map[string]interface{}{ "force": true, }, Timeout: d.Timeout(schema.TimeoutUpdate), - <% if object.error_retry_predicates -%> - ErrorRetryPredicates: []transport_tpg.RetryErrorPredicateFunc{<%= object.error_retry_predicates.join(',') -%>}, - <% end -%> - <% if object.error_abort_predicates -%> - ErrorAbortPredicates: []transport_tpg.RetryErrorPredicateFunc{<%= object.error_abort_predicates.join(',') -%>}, - <% end -%> }) if err != nil { return fmt.Errorf("Error stopping volume replication %q before deleting it: %s", d.Id(), err) } err = NetappOperationWaitTime( - config, reso, project, "Deleting volume replication", userAgent, - d.Timeout(schema.TimeoutDelete)) + config, reso, project, "Deleting volume replication", userAgent, + d.Timeout(schema.TimeoutDelete)) if err != nil { - return err + return err } } \ No newline at end of file From f10027813a95547671da539ee8757e27ea327326 Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Thu, 15 Feb 2024 13:01:08 +0100 Subject: [PATCH 31/34] removed comment Co-authored-by: Shuya Ma <87669292+shuyama1@users.noreply.github.com> --- mmv1/products/netapp/VolumeReplication.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/mmv1/products/netapp/VolumeReplication.yaml b/mmv1/products/netapp/VolumeReplication.yaml index c308bf3e97de..f9660a1a0c12 100644 --- a/mmv1/products/netapp/VolumeReplication.yaml +++ b/mmv1/products/netapp/VolumeReplication.yaml @@ -280,7 +280,6 @@ virtual_fields: for mirror_state to reach MIRRORED. If you want Terraform to wait for the mirror to finish one create/stop/resume operations, set this parameter to true. Default is false. default_value: false -# The following functionality needs to be discussed in review process custom_code: !ruby/object:Provider::Terraform::CustomCode constants: templates/terraform/constants/netapp_volume_replication.go.erb post_create: templates/terraform/post_create/netapp_volume_replication_post_create.go.erb From f1a32929dbb8aefb0ca47bd7e5a057c6f4e4c0d0 Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Thu, 15 Feb 2024 13:01:36 +0100 Subject: [PATCH 32/34] tabs to spaces in resource block Co-authored-by: Shuya Ma <87669292+shuyama1@users.noreply.github.com> --- .../netapp/resource_netapp_volume_replication_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_replication_test.go b/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_replication_test.go index 99b39657a9c6..265f748d377e 100644 --- a/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_replication_test.go +++ b/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_replication_test.go @@ -161,11 +161,11 @@ resource "google_netapp_volume_replication" "test_replication" { volume_name = google_netapp_volume.source_volume.name name = "tf-test-test-replication%{random_suffix}" replication_schedule = "EVERY_10_MINUTES" - description = "This is a replication resource" - labels = { - key = "test" - value = "replication" - } + description. = "This is a replication resource" + labels = { + key = "test" + value = "replication" + } destination_volume_parameters { storage_pool = google_netapp_storage_pool.destination_pool.id volume_id = "tf-test-destination-volume%{random_suffix}" From 4fef9372b172b580f78f44fbc817531cf66194fc Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Thu, 15 Feb 2024 14:52:52 +0100 Subject: [PATCH 33/34] Updates to address review comments - make wait_for_mirror also work for stop/resume, additionally to create - convert tabs in test resource blocks to spaces - fix typos --- mmv1/products/netapp/VolumeReplication.yaml | 3 +- ...app_volume_replication_mirror_state.go.erb | 11 ++- ...resource_netapp_volume_replication_test.go | 93 +++++++++---------- 3 files changed, 53 insertions(+), 54 deletions(-) diff --git a/mmv1/products/netapp/VolumeReplication.yaml b/mmv1/products/netapp/VolumeReplication.yaml index f9660a1a0c12..dd7dde9c7f32 100644 --- a/mmv1/products/netapp/VolumeReplication.yaml +++ b/mmv1/products/netapp/VolumeReplication.yaml @@ -204,6 +204,7 @@ properties: name: "destinationVolumeParameters" description: |- Destination volume parameters. + # destinationVolumeParameters is only used on CREATE. Will not be returned on READ. ignore_read: true properties: - !ruby/object:Api::Type::String @@ -277,7 +278,7 @@ virtual_fields: name: "wait_for_mirror" description: | Replication resource state is independent of mirror_state. With enough data, it can take many hours - for mirror_state to reach MIRRORED. If you want Terraform to wait for the mirror to finish one + for mirror_state to reach MIRRORED. If you want Terraform to wait for the mirror to finish on create/stop/resume operations, set this parameter to true. Default is false. default_value: false custom_code: !ruby/object:Provider::Terraform::CustomCode diff --git a/mmv1/templates/terraform/post_update/netapp_volume_replication_mirror_state.go.erb b/mmv1/templates/terraform/post_update/netapp_volume_replication_mirror_state.go.erb index 7bbc3355f472..57a7146702e5 100644 --- a/mmv1/templates/terraform/post_update/netapp_volume_replication_mirror_state.go.erb +++ b/mmv1/templates/terraform/post_update/netapp_volume_replication_mirror_state.go.erb @@ -79,10 +79,11 @@ if do_change { return err } - // mirrorState should be okay by now, but we still observe glitches. So let's make sure. - // Wait for mirrorState=targetState before continuing - err = NetAppVolumeReplicationWaitForMirror(d, meta, targetState) - if err != nil { - return fmt.Errorf("Error waiting for volume replication to reach mirror_state==%s: %s", targetState, err) + // If user specified to wait for mirror operations, wait to reach target state + if d.Get("wait_for_mirror").(bool) == true { + err = NetAppVolumeReplicationWaitForMirror(d, meta, targetState) + if err != nil { + return fmt.Errorf("Error waiting for volume replication to reach mirror_state==%s: %s", targetState, err) + } } } diff --git a/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_replication_test.go b/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_replication_test.go index 265f748d377e..e663c41e7274 100644 --- a/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_replication_test.go +++ b/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_replication_test.go @@ -122,7 +122,6 @@ resource "google_netapp_volume_replication" "test_replication" { // Update parameters func testAccNetappVolumeReplication_netappVolumeReplicationCreateExample_update(context map[string]interface{}) string { return acctest.Nprintf(` - data "google_compute_network" "default" { name = "%{network_name}" } @@ -161,7 +160,7 @@ resource "google_netapp_volume_replication" "test_replication" { volume_name = google_netapp_volume.source_volume.name name = "tf-test-test-replication%{random_suffix}" replication_schedule = "EVERY_10_MINUTES" - description. = "This is a replication resource" + description = "This is a replication resource" labels = { key = "test" value = "replication" @@ -185,7 +184,6 @@ resource "google_netapp_volume_replication" "test_replication" { // Stop replication func testAccNetappVolumeReplication_netappVolumeReplicationCreateExample_stop(context map[string]interface{}) string { return acctest.Nprintf(` - data "google_compute_network" "default" { name = "%{network_name}" } @@ -219,28 +217,28 @@ resource "google_netapp_volume" "source_volume" { } resource "google_netapp_volume_replication" "test_replication" { - depends_on = [google_netapp_volume.source_volume] - location = google_netapp_volume.source_volume.location - volume_name = google_netapp_volume.source_volume.name - name = "tf-test-test-replication%{random_suffix}" - replication_schedule = "EVERY_10_MINUTES" - description = "This is a replication resource" - labels = { - key = "test" - value = "replication2" - } - destination_volume_parameters { - storage_pool = google_netapp_storage_pool.destination_pool.id - volume_id = "tf-test-destination-volume%{random_suffix}" - # Keeping the share_name of source and destination the same - # simplifies implementing client failover concepts - share_name = "tf-test-source-volume%{random_suffix}" - description = "This is a replicated volume" - } - replication_enabled = false - delete_destination_volume = true - force_stopping = true - wait_for_mirror = true + depends_on = [google_netapp_volume.source_volume] + location = google_netapp_volume.source_volume.location + volume_name = google_netapp_volume.source_volume.name + name = "tf-test-test-replication%{random_suffix}" + replication_schedule = "EVERY_10_MINUTES" + description = "This is a replication resource" + labels = { + key = "test" + value = "replication2" + } + destination_volume_parameters { + storage_pool = google_netapp_storage_pool.destination_pool.id + volume_id = "tf-test-destination-volume%{random_suffix}" + # Keeping the share_name of source and destination the same + # simplifies implementing client failover concepts + share_name = "tf-test-source-volume%{random_suffix}" + description = "This is a replicated volume" + } + replication_enabled = false + delete_destination_volume = true + force_stopping = true + wait_for_mirror = true } `, context) } @@ -248,7 +246,6 @@ resource "google_netapp_volume_replication" "test_replication" { // resume replication func testAccNetappVolumeReplication_netappVolumeReplicationCreateExample_resume(context map[string]interface{}) string { return acctest.Nprintf(` - data "google_compute_network" "default" { name = "%{network_name}" } @@ -282,28 +279,28 @@ resource "google_netapp_volume" "source_volume" { } resource "google_netapp_volume_replication" "test_replication" { - depends_on = [google_netapp_volume.source_volume] - location = google_netapp_volume.source_volume.location - volume_name = google_netapp_volume.source_volume.name - name = "tf-test-test-replication%{random_suffix}" - replication_schedule = "HOURLY" - description = "This is a replication resource" - labels = { - key = "test" - value = "replication2" - } - destination_volume_parameters { - storage_pool = google_netapp_storage_pool.destination_pool.id - volume_id = "tf-test-destination-volume%{random_suffix}" - # Keeping the share_name of source and destination the same - # simplifies implementing client failover concepts - share_name = "tf-test-source-volume%{random_suffix}" - description = "This is a replicated volume" - } - replication_enabled = true - delete_destination_volume = true - force_stopping = true - wait_for_mirror = true + depends_on = [google_netapp_volume.source_volume] + location = google_netapp_volume.source_volume.location + volume_name = google_netapp_volume.source_volume.name + name = "tf-test-test-replication%{random_suffix}" + replication_schedule = "HOURLY" + description = "This is a replication resource" + labels = { + key = "test" + value = "replication2" + } + destination_volume_parameters { + storage_pool = google_netapp_storage_pool.destination_pool.id + volume_id = "tf-test-destination-volume%{random_suffix}" + # Keeping the share_name of source and destination the same + # simplifies implementing client failover concepts + share_name = "tf-test-source-volume%{random_suffix}" + description = "This is a replicated volume" + } + replication_enabled = true + delete_destination_volume = true + force_stopping = true + wait_for_mirror = true } `, context) } From 4da1f5be7ff4597a5315f70cb18f288d019306b3 Mon Sep 17 00:00:00 2001 From: Oliver Krause <3621164+okrause@users.noreply.github.com> Date: Wed, 21 Feb 2024 14:07:16 +0100 Subject: [PATCH 34/34] Rewording of comments Co-authored-by: Shuya Ma <87669292+shuyama1@users.noreply.github.com> --- .../examples/netapp_volume_replication_create.tf.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb b/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb index 987d039104c3..07b69c542b2b 100644 --- a/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb +++ b/mmv1/templates/terraform/examples/netapp_volume_replication_create.tf.erb @@ -47,8 +47,8 @@ resource "google_netapp_volume_replication" "<%= ctx[:primary_resource_id] %>" { description = "This is a replicated volume" } # WARNING: Setting delete_destination_volume to true, will delete the - # CURRENT destination volume if the replication is deleted. This is great - # for testing, but questionable for production! + # CURRENT destination volume if the replication is deleted. Omit the field + # or set delete_destination_volume=false to avoid accidental volume deletion. delete_destination_volume = true wait_for_mirror = true }