diff --git a/CHANGELOG.md b/CHANGELOG.md index ff705a5..0071f3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## 22.12.0 NEW FEATURES: * resource/cvo_volume: support create and delete onPrem volume. +* resource/cvo_volume: support create snapshot policy for AWS, AZURE and GCP if the snapshot policy is not available. ## 22.11.0 NEW FEATURES: diff --git a/cloudmanager/helper.go b/cloudmanager/helper.go index c0e2a09..bff8415 100644 --- a/cloudmanager/helper.go +++ b/cloudmanager/helper.go @@ -56,7 +56,7 @@ type workingEnvironmentOntapClusterPropertiesResponse struct { ReservedSize interface{} `json:"reservedSize"` SaasProperties interface{} `json:"saasProperties"` Schedules interface{} `json:"schedules"` - SnapshotPolicies interface{} `json:"snapshotPolicies"` + SnapshotPolicies []cvoSnapshotPolicy `json:"snapshotPolicies"` Status cvoStatus `json:"status"` SupportRegistrationInformation []interface{} `json:"supportRegistrationInformation"` SupportRegistrationProperties interface{} `json:"supportRegistrationProperties"` @@ -220,6 +220,18 @@ type svmNameModificationRequest struct { SvmName string `structs:"svmName"` } +// snapshotPolicy +type cvoSnapshotPolicy struct { + Name string `json:"name"` + Schedules []policySchedule `json:"schedules"` + Description string `json:"description"` +} + +type policySchedule struct { + Frequency string `json:"frequency"` + Retention int `json:"retention"` +} + // Check HTTP response code, return error if HTTP request is not successed. func apiResponseChecker(statusCode int, response []byte, funcName string) error { diff --git a/cloudmanager/resource_netapp_cloudmanager_volume.go b/cloudmanager/resource_netapp_cloudmanager_volume.go index dc9825c..1023a70 100644 --- a/cloudmanager/resource_netapp_cloudmanager_volume.go +++ b/cloudmanager/resource_netapp_cloudmanager_volume.go @@ -164,6 +164,33 @@ func resourceCVOVolume() *schema.Resource { }, }, }, + "snapshot_policy": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "schedule": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "schedule_type": { + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice([]string{"5min", "8hour", "hourly", "daily", "weekly", "monthly"}, true), + Required: true, + ForceNew: true, + }, + "retention": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + }, + }, + }, + }, + }, + }, }, } } @@ -231,7 +258,21 @@ func resourceCVOVolumeCreate(d *schema.ResourceData, meta interface{}) error { volume.SvmName = svm workingEnvironmentType = weInfo.WorkingEnvironmentType volume.WorkingEnvironmentType = workingEnvironmentType + if workingEnvironmentType != "ON_PREM" { + // Check if snapshot_nolicy_name exists + if !client.findSnapshotPolicy(weInfo.PublicID, quote.SnapshotPolicyName, clientID) { + // If snapshot_policy_name does not exist, create the snapshot policy + if v, ok := d.GetOk("snapshot_policy"); ok { + policy := v.(*schema.Set) + if policy.Len() > 0 { + err := client.createSnapshotPolicy(weInfo.PublicID, quote.SnapshotPolicyName, policy, clientID) + if err != nil { + return err + } + } + } + } quote.WorkingEnvironmentType = workingEnvironmentType quote.WorkingEnvironmentID = weInfo.PublicID quote.SvmName = svm diff --git a/cloudmanager/volume.go b/cloudmanager/volume.go index 246aa1e..73b70d7 100644 --- a/cloudmanager/volume.go +++ b/cloudmanager/volume.go @@ -141,6 +141,17 @@ type initiator struct { WorkingEnvironmentType string `structs:"workingEnvironmentType,omitempty"` } +type createSnapshotPolicyRequest struct { + SnapshotPolicyName string `structs:"snapshotPolicyName"` + Schedules []scheduleReq `structs:"schedules"` + WorkingEnvironmentID string `structs:"workingEnvironmentId"` +} + +type scheduleReq struct { + ScheduleType string `structs:"scheduleType"` + Retention int `structs:"retention"` +} + func (c *Client) createVolume(vol volumeRequest, createAggregateIfNotFound bool, clientID string) error { var id string if vol.FileSystemID != "" { @@ -519,3 +530,68 @@ func (c *Client) setCommonAttributes(WorkingEnvironmentType string, d *schema.Re } return nil } + +// createSnapshotPolicy +func (c *Client) createSnapshotPolicy(workingEnviromentID string, snapshotPolicyName string, set *schema.Set, clientID string) error { + log.Print("createSnapshotPolicy: ", snapshotPolicyName) + snapshotPolicy := createSnapshotPolicyRequest{} + snapshotPolicy.SnapshotPolicyName = snapshotPolicyName + snapshotPolicy.WorkingEnvironmentID = workingEnviromentID + for _, v := range set.List() { + schedules := v.(map[string]interface{}) + scheduleSet := schedules["schedule"].([]interface{}) + scheduleConfigs := make([]scheduleReq, 0, len(scheduleSet)) + for _, x := range scheduleSet { + snapshotPolicySchedule := scheduleReq{} + scheduleConfig := x.(map[string]interface{}) + snapshotPolicySchedule.ScheduleType = scheduleConfig["schedule_type"].(string) + snapshotPolicySchedule.Retention = scheduleConfig["retention"].(int) + + scheduleConfigs = append(scheduleConfigs, snapshotPolicySchedule) + } + snapshotPolicy.Schedules = scheduleConfigs + } + baseURL, _, err := c.getAPIRoot(snapshotPolicy.WorkingEnvironmentID, clientID) + hostType := "CloudManagerHost" + if err != nil { + return err + } + baseURL = fmt.Sprintf("%s/working-environments/%s/snapshot-policy", baseURL, snapshotPolicy.WorkingEnvironmentID) + param := structs.Map(snapshotPolicy) + statusCode, response, onCloudRequestID, err := c.CallAPIMethod("POST", baseURL, param, c.Token, hostType, clientID) + if err != nil { + log.Print("createSnapshotPolicy request failed ", statusCode) + return err + } + responseError := apiResponseChecker(statusCode, response, "createSnapshotPolicy") + if responseError != nil { + return responseError + } + err = c.waitOnCompletion(onCloudRequestID, "snapshotPolicy", "create", 10, 10, clientID) + if err != nil { + return err + } + + if c.findSnapshotPolicy(workingEnviromentID, snapshotPolicyName, clientID) { + return nil + } + return fmt.Errorf("create snapshot policy failed") +} + +// findSnapshotPolicy +func (c *Client) findSnapshotPolicy(workingEnviromentID string, snapshotPolicyName string, clientID string) bool { + resp, err := c.getCVOProperties(workingEnviromentID, clientID) + if err != nil { + log.Print("cannot find working environment ", workingEnviromentID) + return false + } + snapshotPolicies := resp.SnapshotPolicies + for i := range snapshotPolicies { + if snapshotPolicies[i].Name == snapshotPolicyName { + log.Print("found snapshot policy: ", snapshotPolicyName) + return true + } + } + log.Print("cannot find snapshot policy ", snapshotPolicyName) + return false +} diff --git a/website/docs/r/cvo_volume.html.markdown b/website/docs/r/cvo_volume.html.markdown index 367f244..1b85992 100644 --- a/website/docs/r/cvo_volume.html.markdown +++ b/website/docs/r/cvo_volume.html.markdown @@ -28,6 +28,17 @@ resource "netapp-cloudmanager_volume" "cvo-volume-nfs" { export_policy_type = "custom" export_policy_ip = ["0.0.0.0/0"] export_policy_nfs_version = ["nfs4"] + snapshot_policy_name = "sp1" + snapshot_policy { + schedule { + schedule_type = "5min" + retention = 10 + } + schedule { + schedule_type = "hourly" + retention = 5 + } + } working_environment_id = netapp-cloudmanager_cvo_gcp.cvo-gcp.id client_id = netapp-cloudmanager_connector_gcp.cm-gcp.client_id } @@ -109,6 +120,13 @@ The `initiator` block supports: * `alias` (Required) Initiator alias. (iSCSI protocol parameters) * `iqn` (Required) Initiator IQN. (iSCSI protocol parameters) +The `snapshot_policy` block supports: +* `schedule` - (Required) The schedule configuration for creating snapshot policy. When `snapshot_policy_name` does not exist, the snapshot policy will be created with `schedule`(s) and named as `snapshot_policy_name`. It supports the volume creation based on the AWS, AZURE and GCP CVO. + +The `schedule` block supports: +* `schedule_type` - (Required) snapshot policy schedule type. Must be one of '5min', '8hour', 'hourly', 'daily', 'weekly', 'monthly'. +* `retention` - (Required) snapshot policy retention. + ## Attributes Reference The following attributes are exported in addition to the arguments listed above: