Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add volume replication support for Google Cloud NetApp Volumes #9816

Merged
merged 41 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
7b9cd18
Initial replication commit
G-NamanGupta Jan 12, 2024
b835ee3
Merge remote-tracking branch 'naman/volumereplication' into volumerep…
okrause Jan 12, 2024
bcae2a2
Cleanup work
okrause Jan 12, 2024
852dde1
Merge branch 'main' into volumereplication
okrause Jan 12, 2024
76bc059
Update example file
okrause Jan 12, 2024
c10c2ef
Updated example file
okrause Jan 15, 2024
8dfb61e
Major updates
okrause Jan 15, 2024
0b64e15
For replication deletion, stop replication first
okrause Jan 15, 2024
469e778
Add support for deleting destination volume on replication delete
okrause Jan 15, 2024
056eb20
Make volumes deletable in presence of snapshots.
okrause Jan 15, 2024
223a764
Improving debug error message
okrause Jan 15, 2024
e2fdc92
yaml check and format fix
okrause Jan 15, 2024
441be1a
Add wait for mirror to initialize.
okrause Jan 15, 2024
ebd10d2
Wait on destroy, not on create
okrause Jan 16, 2024
c7f1bcc
Make deleting a replication more robust
okrause Jan 19, 2024
012e339
adding support for stop/resume
okrause Jan 19, 2024
1aac204
yamlformat and lint
okrause Jan 19, 2024
c26e36b
Add force delete to delete volumes with nested snapshots
okrause Jan 19, 2024
df34ac4
resource test first version
okrause Jan 20, 2024
ffeb5df
More changes to make tests solid
okrause Jan 22, 2024
b791946
Test updates
okrause Jan 22, 2024
10c647f
Merge branch 'main' into netapp-volume
okrause Jan 23, 2024
cbf790f
few cleanups
okrause Jan 23, 2024
e785eb8
Make virtual field verifies happy
okrause Jan 23, 2024
7c1ed3f
Minor test improvements
okrause Jan 23, 2024
39b2412
Merge branch 'main' into volumereplication
okrause Jan 23, 2024
04761c8
Merge branch 'netapp-volume' into volumereplication
okrause Jan 23, 2024
905290d
More fine tuning
okrause Jan 23, 2024
1b206cc
Resource name change as suggested by @slevenick
okrause Jan 23, 2024
9cbc5ec
Remove snapshot code block and fix typo
okrause Jan 24, 2024
04489c9
Detect manual stop/resume actions
okrause Jan 24, 2024
af4168c
Remove ignore_read for deletion_policy
okrause Jan 25, 2024
d091d20
- Made destinationVolumeParameters immutable. It still requires ignor…
okrause Jan 25, 2024
94234c8
Merge branch 'main' into volumereplication
okrause Jan 26, 2024
054f125
destinationVolumeParameters are only evaluated at create. Make the im…
okrause Jan 29, 2024
7340bd7
Merge branch 'main' into volumereplication
okrause Jan 31, 2024
7b7f322
Name cleanups and comment improvements
okrause Jan 31, 2024
f100278
removed comment
okrause Feb 15, 2024
f1a3292
tabs to spaces in resource block
okrause Feb 15, 2024
4fef937
Updates to address review comments
okrause Feb 15, 2024
4da1f5b
Rewording of comments
okrause Feb 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
289 changes: 289 additions & 0 deletions mmv1/products/netapp/VolumeReplication.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
# 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: |
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.
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"
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}}",
]
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"
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:
- !ruby/object:Api::Type::String
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: "volumeName"
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.
required: true
immutable: true
url_param_only: true
properties:
- !ruby/object:Api::Type::Enum
name: "state"
description: |
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: "stateDetails"
description: |
State details of the replication resource.
output: true
- !ruby/object:Api::Type::Enum
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
which volume is the source volume, since it will overwrite changes done to the destination volume.
values:
- :REPLICATION_ROLE_UNSPECIFIED
- :SOURCE
- :DESTINATION
output: true
- !ruby/object:Api::Type::Enum
name: "replicationSchedule"
description: |
Specifies the replication interval.
values:
- :EVERY_10_MINUTES
- :HOURLY
- :DAILY
required: true
- !ruby/object:Api::Type::Enum
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.
values:
- :MIRROR_STATE_UNSPECIFIED
- :PREPARING
- :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"
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: |-
Replication transfer statistics. All statistics are updated every 5 minutes.
output: true
properties:
- !ruby/object:Api::Type::String
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: |
Labels as key value pairs. Example: `{ "owner": "Bob", "department": "finance", "purpose": "testing" }`
- !ruby/object:Api::Type::NestedObject
name: "destinationVolumeParameters"
shuyama1 marked this conversation as resolved.
Show resolved Hide resolved
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
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: |
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: |
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
- !ruby/object:Api::Type::Boolean
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
- !ruby/object:Api::Type::Boolean
name: "wait_for_mirror"
shuyama1 marked this conversation as resolved.
Show resolved Hide resolved
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 on
create/stop/resume operations, set this parameter to true. Default is false.
default_value: false
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
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Custom function to wait for mirrorState target states
func NetAppVolumeReplicationWaitForMirror(d *schema.ResourceData, meta interface{}, targetState string) error {
shuyama1 marked this conversation as resolved.
Show resolved Hide resolved
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 volume replication: %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
}
Original file line number Diff line number Diff line change
@@ -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.
shuyama1 marked this conversation as resolved.
Show resolved Hide resolved
if v == nil {
return v
}

if v.(string) == "STOPPED" {
shuyama1 marked this conversation as resolved.
Show resolved Hide resolved
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
}
Loading
Loading