Skip to content

Commit

Permalink
Replication job (#242)
Browse files Browse the repository at this point in the history
* added replication-job

* added wait time

* added AT Replication Job

* revert replication-job

* added dell/common-github-actions/go-code-formatter-vetter@main to workflow

* fmt test

* added Golang Validation

* fix uses

* added go static analysis

* dir fix

* Revert Static-Analysis Job

* added port to provisioner connection

* added isDelete Flag

* delete all synciq jobs in destroy

* rename main dir to terraformAT

* idempotency test

* no read refresh

* added delete custom logic

* state remain same on notfound job

* fix test

* added const

* added base doc

* update action doc

* enable synciq service on testing
  • Loading branch information
Krishnan-Priyanshu authored Nov 13, 2024
1 parent 9ea5ea5 commit 7c03b96
Show file tree
Hide file tree
Showing 7 changed files with 526 additions and 2 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/terraform-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,11 @@ jobs:
uses: actions/checkout@v3
- name: Extract go client
run: make clean extract-client
- name: Run the formatter, linter, and vetter
uses: dell/common-github-actions/go-code-formatter-linter-vetter@main
- name: Run the formatter and vetter
uses: dell/common-github-actions/go-code-formatter-vetter@main
with:
directories: ./powerscale/...

sanitize:
name: Check for forbidden words
runs-on: ubuntu-latest
Expand Down
46 changes: 46 additions & 0 deletions docs/resources/synciq_replication_job.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
# Copyright (c) 2024 Dell Inc., or its subsidiaries. All Rights Reserved.
#
# Licensed under the Mozilla Public 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://mozilla.org/MPL/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.

title: "powerscale_synciq_replication_job resource"
linkTitle: "powerscale_synciq_replication_job"
page_title: "powerscale_synciq_replication_job Resource - terraform-provider-powerscale"
subcategory: ""
description: |-
Resource for managing SyncIQReplicationJobResource on OpenManage Enterprise.
---

# powerscale_synciq_replication_job (Resource)

Resource for managing SyncIQReplicationJobResource on OpenManage Enterprise.




<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `action` (String) Action for the job - run, test, resync_prep, allow_write, allow_write_revert
- `id` (String) ID/Name of the policy

### Optional

- `is_paused` (Boolean) change job state to running or paused.
- `wait_time` (Number) Wait Time for the job

Unless specified otherwise, all fields of this resource can be updated.

50 changes: 50 additions & 0 deletions powerscale/helper/synciq_replication_job.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package helper

import (
"context"
powerscale "dell/powerscale-go-client"
"net/http"
"terraform-provider-powerscale/client"
)

// GetSyncIQReplicationJob get syncIQ replication job.
func GetSyncIQReplicationJob(ctx context.Context, client *client.Client, jobID string) (*powerscale.V1SyncJobsExtended, *http.Response, error) {
resp, httpResp, err := client.PscaleOpenAPIClient.SyncApi.GetSyncv1SyncJob(ctx, jobID).Execute()
if err != nil {
return nil, httpResp, err
}
return resp, httpResp, err
}

// CreateSyncIQReplicationJob create syncIQ replication job.
func CreateSyncIQReplicationJob(ctx context.Context, client *client.Client, job powerscale.V1SyncJob) (string, error) {
if job.Action != nil && *job.Action == "run" {
job.Action = nil
}
resp, _, err := client.PscaleOpenAPIClient.SyncApi.CreateSyncv1SyncJob(ctx).V1SyncJob(job).Execute()
if err != nil {
return "", err
}
return resp.Id, nil
}

// UpdateSyncIQReplicationJob update syncIQ replication job.
func UpdateSyncIQReplicationJob(ctx context.Context, client *client.Client, jobID string, job powerscale.V1SyncJobExtendedExtended) (*http.Response, error) {
resp, err := client.PscaleOpenAPIClient.SyncApi.UpdateSyncv1SyncJob(ctx, jobID).V1SyncJob(job).Execute()
if err != nil {
return resp, err
}
return resp, nil
}

// DeleteSyncIQReplicationJob delete syncIQ replication job.
func DeleteSyncIQReplicationJob(ctx context.Context, client *client.Client, jobID string) error {
deleteJob := powerscale.V1SyncJobExtendedExtended{
State: "canceled",
}
resp, err := UpdateSyncIQReplicationJob(ctx, client, jobID, deleteJob)
if resp != nil && resp.StatusCode == http.StatusNotFound {
return nil // already deleted
}
return err
}
8 changes: 8 additions & 0 deletions powerscale/models/synciq_replication_job.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types"
)

// SyncIQReplicationJobResourceModel describes the SyncIQ Replication Job resource data model.
type SyncIQReplicationJobResourceModel struct {
Id types.String `tfsdk:"id"`
Action types.String `tfsdk:"action"`
IsPaused types.Bool `tfsdk:"is_paused"`
WaitTime types.Int64 `tfsdk:"wait_time"`
}

// SyncIQReplicationJobDataSourceModel describes the SyncIQ Replication Job datasource data model.
type SyncIQReplicationJobDataSourceModel struct {
SyncIQReplicationJobs []SyncIQReplicationJobModel `tfsdk:"synciq_jobs"`
Expand Down
1 change: 1 addition & 0 deletions powerscale/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ func (p *PscaleProvider) Resources(ctx context.Context) []func() resource.Resour
NewWriteableSnapshotResource,
NewSnapshotRestoreResource,
NewNfsAliasResource,
NewSyncIQReplicationJobResource,
}
}

Expand Down
264 changes: 264 additions & 0 deletions powerscale/provider/synciq_replication_job_resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
package provider

import (
"context"
powerscale "dell/powerscale-go-client"
"fmt"
"net/http"
"terraform-provider-powerscale/client"
"terraform-provider-powerscale/powerscale/helper"
"terraform-provider-powerscale/powerscale/models"
"time"

"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
)

// Ensure provider defined types fully satisfy framework interfaces.
var (
_ resource.Resource = &synciqPolicyResource{}
_ resource.ResourceWithConfigure = &synciqPolicyResource{}
_ resource.ResourceWithImportState = &synciqPolicyResource{}
)

const (
paused = "paused"
running = "running"
)

// NewSyncIQReplicationJobResource is a helper function to simplify the provider implementation.
func NewSyncIQReplicationJobResource() resource.Resource {
return &SyncIQReplicationJobResource{}
}

// SyncIQReplicationJobResource is the resource implementation.
type SyncIQReplicationJobResource struct {
client *client.Client
}

// Configure implements resource.ResourceWithConfigure.
func (s *SyncIQReplicationJobResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
// Prevent panic if the provider has not been configured.
if req.ProviderData == nil {
return
}

pscaleClient, ok := req.ProviderData.(*client.Client)

if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
)

return
}

s.client = pscaleClient
}

// Metadata returns the resource type name.
func (r *SyncIQReplicationJobResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_synciq_replication_job"
}

// Schema defines the schema for the resource.
func (r *SyncIQReplicationJobResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: "Resource for managing SyncIQReplicationJobResource on OpenManage Enterprise.",
Version: 1,
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Required: true,
Description: "ID/Name of the policy",
MarkdownDescription: "ID/Name of the policy",
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplaceIfConfigured(),
},
},
"action": schema.StringAttribute{
Required: true,
Description: "Action for the job - run, test, resync_prep, allow_write, allow_write_revert",
MarkdownDescription: "Action for the job - run, test, resync_prep, allow_write, allow_write_revert",
Validators: []validator.String{
stringvalidator.OneOf("run", "test", "resync_prep", "allow_write", "allow_write_revert"),
},
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplaceIfConfigured(),
},
},
"is_paused": schema.BoolAttribute{
Optional: true,
Computed: true,
Description: "change job state to running or paused.",
MarkdownDescription: "change job state to running or paused.",
Default: booldefault.StaticBool(false),
},
"wait_time": schema.Int64Attribute{
Optional: true,
Computed: true,
Description: "Wait Time for the job",
MarkdownDescription: "Wait Time for the job",
Default: int64default.StaticInt64(5),
Validators: []validator.Int64{
int64validator.AtLeast(1),
},
},
},
}
}

// Create creates the resource and sets the initial Terraform state.
func (r *SyncIQReplicationJobResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
tflog.Trace(ctx, "resource_SyncIQReplicationJobResource create : Started")
//Get Plan Data
var plan models.SyncIQReplicationJobResourceModel
diags := req.Plan.Get(ctx, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
if plan.IsPaused.ValueBool() {
resp.Diagnostics.AddError("Config Error", "SyncIQ Replication Job cannot be paused befor job creation.")
}

var createJob powerscale.V1SyncJob
// Get param from tf input
err := helper.ReadFromState(ctx, &plan, &createJob)
if err != nil {
resp.Diagnostics.AddError(
"Error reading create plan",
err.Error(),
)
return
}
_, err = helper.CreateSyncIQReplicationJob(ctx, r.client, createJob)
if err != nil {
errStr := "Could not create syncIQ Replication Job with error: "
message := helper.GetErrorString(err, errStr)
resp.Diagnostics.AddError(
"Error creating syncIQ Replication Job",
message,
)
return
}
tflog.Trace(ctx, "resource_SyncIQReplicationJobResource create: updating state finished, saving ...")
// Save into State
diags = resp.State.Set(ctx, &plan)
resp.Diagnostics.Append(diags...)
tflog.Trace(ctx, "resource_SyncIQReplicationJobResource create: finish")
}

// Read refreshes the Terraform state with the latest data.
func (r *SyncIQReplicationJobResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
tflog.Trace(ctx, "resource_SyncIQReplicationJobResource read: started")
var state models.SyncIQReplicationJobResourceModel
diags := req.State.Get(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
time.Sleep(time.Duration(state.WaitTime.ValueInt64()) * time.Second)
tflog.Debug(ctx, "calling get syncIQ Replication Job on powerscale client")
readState, httpResp, err := helper.GetSyncIQReplicationJob(ctx, r.client, state.Id.ValueString())
if err != nil {
if httpResp != nil && httpResp.StatusCode == http.StatusNotFound {
diags = resp.State.Set(ctx, &state)
resp.Diagnostics.Append(diags...)
tflog.Trace(ctx, "resource_SyncIQReplicationJobResource read: finished")
return
}
errStr := "Could not read syncIQ Replication Job with error: "
message := helper.GetErrorString(err, errStr)
resp.Diagnostics.AddError(
"Error reading syncIQ Replication Job",
message,
)
return
}
if len(readState.Jobs) > 0 {
job := readState.Jobs[0]
state.Id = types.StringValue(job.PolicyName)
if job.State == "running" {
state.IsPaused = types.BoolValue(false)
} else if job.State == "paused" {
state.IsPaused = types.BoolValue(true)
}
}

tflog.Trace(ctx, "resource_SyncIQReplicationJobResource read: finished reading state")
//Save into State
diags = resp.State.Set(ctx, &state)
resp.Diagnostics.Append(diags...)
tflog.Trace(ctx, "resource_SyncIQReplicationJobResource read: finished")
}

// Update updates the resource and sets the updated Terraform state on success.
func (r *SyncIQReplicationJobResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
tflog.Trace(ctx, "resource_SyncIQReplicationJobResource update: started")
var state, plan models.SyncIQReplicationJobResourceModel
diags := req.State.Get(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
diags = req.Plan.Get(ctx, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
if !plan.IsPaused.Equal(state.IsPaused) {
isPause := running
if plan.IsPaused.ValueBool() {
isPause = paused
}
updateJob := powerscale.V1SyncJobExtendedExtended{
State: isPause,
}
_, err := helper.UpdateSyncIQReplicationJob(ctx, r.client, state.Id.ValueString(), updateJob)
if err != nil {
errStr := "Could not update syncIQ Replication Job with error: "
message := helper.GetErrorString(err, errStr)
resp.Diagnostics.AddError(
"Error updating syncIQ Replication Job",
message,
)
return
}
diags = resp.State.Set(ctx, &plan)
resp.Diagnostics.Append(diags...)
}
tflog.Trace(ctx, "resource_SyncIQReplicationJobResource update: finished")
}

// Delete deletes the resource and removes the Terraform state on success.
func (r *SyncIQReplicationJobResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
tflog.Trace(ctx, "resource_SyncIQReplicationJobResource delete: started")
var state models.SyncIQReplicationJobResourceModel
diags := req.State.Get(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
err := helper.DeleteSyncIQReplicationJob(ctx, r.client, state.Id.ValueString())
if err != nil {
errStr := "Could not delete syncIQ Replication Job with error: "
message := helper.GetErrorString(err, errStr)
resp.Diagnostics.AddError(
"Error deleting syncIQ Replication Job",
message,
)
}
time.Sleep(time.Duration(state.WaitTime.ValueInt64()) * time.Second)
resp.State.RemoveResource(ctx)
tflog.Trace(ctx, "resource_SyncIQReplicationJobResource delete: finished")
}
Loading

0 comments on commit 7c03b96

Please sign in to comment.