Skip to content

Commit 669b225

Browse files
committed
Implement hypercore_vm_replication resource
1 parent 1e3299f commit 669b225

File tree

3 files changed

+382
-33
lines changed

3 files changed

+382
-33
lines changed
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package provider
5+
6+
import (
7+
"context"
8+
"fmt"
9+
10+
"github.com/hashicorp/terraform-plugin-framework/path"
11+
"github.com/hashicorp/terraform-plugin-framework/resource"
12+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
13+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
14+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
15+
"github.com/hashicorp/terraform-plugin-framework/types"
16+
"github.com/hashicorp/terraform-plugin-log/tflog"
17+
"github.com/hashicorp/terraform-provider-hypercore/internal/utils"
18+
)
19+
20+
// Ensure provider defined types fully satisfy framework interfaces.
21+
var _ resource.Resource = &HypercoreVMReplicationResource{}
22+
var _ resource.ResourceWithImportState = &HypercoreVMReplicationResource{}
23+
24+
func NewHypercoreVMReplicationResource() resource.Resource {
25+
return &HypercoreVMReplicationResource{}
26+
}
27+
28+
// HypercoreVMReplicationResource defines the resource implementation.
29+
type HypercoreVMReplicationResource struct {
30+
client *utils.RestClient
31+
}
32+
33+
// HypercoreVMReplicationResourceModel describes the resource data model.
34+
type HypercoreVMReplicationResourceModel struct {
35+
Id types.String `tfsdk:"id"`
36+
VmUUID types.String `tfsdk:"vm_uuid"`
37+
Label types.String `tfsdk:"label"`
38+
ConnectionUUID types.String `tfsdk:"connection_uuid"`
39+
Enable types.Bool `tfsdk:"enable"`
40+
TargetVmUUID types.String `tfsdk:"target_vm_uuid"`
41+
}
42+
43+
func (r *HypercoreVMReplicationResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
44+
resp.TypeName = req.ProviderTypeName + "_vm_replication"
45+
}
46+
47+
func (r *HypercoreVMReplicationResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
48+
resp.Schema = schema.Schema{
49+
// This description is used by the documentation generator and the language server.
50+
MarkdownDescription: "Hypercore VM replication resource to manage VM boot devices' order",
51+
Attributes: map[string]schema.Attribute{
52+
"id": schema.StringAttribute{
53+
Computed: true,
54+
MarkdownDescription: "Replication identifier",
55+
PlanModifiers: []planmodifier.String{
56+
stringplanmodifier.UseStateForUnknown(),
57+
},
58+
},
59+
"target_vm_uuid": schema.StringAttribute{
60+
Computed: true,
61+
MarkdownDescription: "Remote target VM UUID",
62+
PlanModifiers: []planmodifier.String{
63+
stringplanmodifier.UseStateForUnknown(),
64+
},
65+
},
66+
"vm_uuid": schema.StringAttribute{
67+
MarkdownDescription: "VM UUID of which we want to make a replication",
68+
Required: true,
69+
},
70+
"label": schema.StringAttribute{
71+
MarkdownDescription: "Human-readable label describing the replication purpose",
72+
Required: true,
73+
},
74+
"connection_uuid": schema.StringAttribute{
75+
MarkdownDescription: "Remote connection UUID",
76+
Required: true,
77+
},
78+
"enable": schema.BoolAttribute{
79+
MarkdownDescription: "Enable or disable replication",
80+
Required: true,
81+
},
82+
},
83+
}
84+
}
85+
86+
func (r *HypercoreVMReplicationResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
87+
tflog.Info(ctx, "TTRT HypercoreVMReplicationResource CONFIGURE")
88+
// Prevent padisk if the provider has not been configured.
89+
if req.ProviderData == nil {
90+
return
91+
}
92+
93+
restClient, ok := req.ProviderData.(*utils.RestClient)
94+
95+
if !ok {
96+
resp.Diagnostics.AddError(
97+
"Unexpected Resource Configure Type",
98+
fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
99+
)
100+
101+
return
102+
}
103+
104+
r.client = restClient
105+
}
106+
107+
func (r *HypercoreVMReplicationResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
108+
tflog.Info(ctx, "TTRT HypercoreVMReplicationResource CREATE")
109+
var data HypercoreVMReplicationResourceModel
110+
111+
// Read Terraform plan data into the model
112+
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
113+
114+
if r.client == nil {
115+
resp.Diagnostics.AddError(
116+
"Unconfigured HTTP Client",
117+
"Expected configured HTTP client. Please report this issue to the provider developers.",
118+
)
119+
return
120+
}
121+
if resp.Diagnostics.HasError() {
122+
return
123+
}
124+
125+
restClient := *r.client
126+
vmUUID := data.VmUUID.ValueString()
127+
connectionUUID := data.ConnectionUUID.ValueString()
128+
label := data.Label.ValueString()
129+
enable := data.Enable.ValueBool()
130+
131+
tflog.Info(ctx, fmt.Sprintf("TTRT Create: vm_uuid=%s, connection_uuid=%s, label=%s, enable=%t", vmUUID, connectionUUID, label, enable))
132+
133+
replicationUUID, replication := utils.CreateVMReplication(restClient, vmUUID, connectionUUID, label, enable, ctx)
134+
135+
tflog.Info(ctx, fmt.Sprintf("TTRT Created: vm_uuid=%s, connection_uuid=%s, label=%s, enable=%t, replication=%v", vmUUID, connectionUUID, label, enable, replication))
136+
137+
// TODO: Check if HC3 matches TF
138+
// save into the Terraform state.
139+
data.Id = types.StringValue(replicationUUID)
140+
data.TargetVmUUID = types.StringValue(utils.AnyToString(replication["targetDomainUUID"]))
141+
142+
// Write logs using the tflog package
143+
// Documentation: https://terraform.io/plugin/log
144+
tflog.Trace(ctx, "Created a replication")
145+
146+
// Save data into Terraform state
147+
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
148+
}
149+
150+
func (r *HypercoreVMReplicationResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
151+
tflog.Info(ctx, "TTRT HypercoreVMReplicationResource READ")
152+
var data HypercoreVMReplicationResourceModel
153+
// Read Terraform prior state data into the model
154+
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
155+
156+
if resp.Diagnostics.HasError() {
157+
return
158+
}
159+
160+
// Boot Order read ======================================================================
161+
restClient := *r.client
162+
replicationUUID := data.Id.ValueString()
163+
vmUUID := data.VmUUID.ValueString()
164+
165+
tflog.Debug(ctx, fmt.Sprintf("TTRT HypercoreVMReplicationResource Read oldState replicationUUID=%s\n", replicationUUID))
166+
167+
pHc3Replication := utils.GetVMReplicationByUUID(restClient, replicationUUID)
168+
if pHc3Replication == nil {
169+
resp.Diagnostics.AddError("VM not found", fmt.Sprintf("VM replication not found - replicationUUID=%s", replicationUUID))
170+
return
171+
}
172+
hc3Replication := *pHc3Replication
173+
174+
tflog.Info(ctx, fmt.Sprintf("TTRT HypercoreVMReplicationResource: vm_uuid=%s, replication_uuid=%s", vmUUID, replicationUUID))
175+
176+
// save into the Terraform state.
177+
data.Id = types.StringValue(replicationUUID)
178+
data.TargetVmUUID = types.StringValue(utils.AnyToString(hc3Replication["targetDomainUUID"]))
179+
data.VmUUID = types.StringValue(utils.AnyToString(hc3Replication["sourceDomainUUID"]))
180+
data.ConnectionUUID = types.StringValue(utils.AnyToString(hc3Replication["connectionUUID"]))
181+
data.Label = types.StringValue(utils.AnyToString(hc3Replication["label"]))
182+
data.Enable = types.BoolValue(utils.AnyToBool(hc3Replication["enable"]))
183+
184+
// Save updated data into Terraform state
185+
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
186+
}
187+
188+
func (r *HypercoreVMReplicationResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
189+
tflog.Info(ctx, "TTRT HypercoreVMReplicationResource UPDATE")
190+
var data_state HypercoreVMReplicationResourceModel
191+
resp.Diagnostics.Append(req.State.Get(ctx, &data_state)...)
192+
var data HypercoreVMReplicationResourceModel
193+
194+
// Read Terraform plan data into the model
195+
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
196+
197+
if resp.Diagnostics.HasError() {
198+
return
199+
}
200+
201+
restClient := *r.client
202+
replicationUUID := data.Id.ValueString()
203+
vmUUID := data.VmUUID.ValueString()
204+
connectionUUID := data.ConnectionUUID.ValueString()
205+
label := data.Label.ValueString()
206+
enable := data.Enable.ValueBool()
207+
208+
diag := utils.UpdateVMReplication(restClient, replicationUUID, vmUUID, connectionUUID, label, enable, ctx)
209+
if diag != nil {
210+
resp.Diagnostics.AddWarning(diag.Summary(), diag.Detail())
211+
}
212+
213+
// TODO: Check if HC3 matches TF
214+
// Do not trust UpdateVMReplication made what we asked for. Read new power state from HC3.
215+
pHc3Replication := utils.GetVMReplicationByUUID(restClient, replicationUUID)
216+
if pHc3Replication == nil {
217+
msg := fmt.Sprintf("VM replication not found - replicationUUID=%s.", replicationUUID)
218+
resp.Diagnostics.AddError("VM replication not found", msg)
219+
return
220+
}
221+
newHc3Replication := *pHc3Replication
222+
223+
tflog.Info(ctx, fmt.Sprintf("TTRT HypercoreVMReplicationResource: replication_uuid=%s, replication=%v", replicationUUID, newHc3Replication))
224+
225+
// Save updated data into Terraform state
226+
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
227+
}
228+
229+
func (r *HypercoreVMReplicationResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
230+
tflog.Info(ctx, "TTRT HypercoreVMReplicationResource DELETE")
231+
var data HypercoreVMReplicationResourceModel
232+
233+
// Read Terraform prior state data into the model
234+
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
235+
236+
if resp.Diagnostics.HasError() {
237+
return
238+
}
239+
240+
// Extra implementation not needed - VirDomainReplication doesn't have a DELETE endpoint
241+
242+
// If applicable, this is a great opportunity to initialize any necessary
243+
// provider client data and make a call using it.
244+
// httpResp, err := r.client.Do(httpReq)
245+
// if err != nil {
246+
// resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete example, got error: %s", err))
247+
// return
248+
// }
249+
}
250+
251+
func (r *HypercoreVMReplicationResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
252+
tflog.Info(ctx, "TTRT HypercoreVMReplicationResource IMPORT_STATE")
253+
254+
replicationUUID := req.ID
255+
tflog.Info(ctx, fmt.Sprintf("TTRT HypercoreVMReplicationResource: replicationUUID=%s", replicationUUID))
256+
257+
restClient := *r.client
258+
hc3Replication := utils.GetVMReplicationByUUID(restClient, replicationUUID)
259+
260+
if hc3Replication == nil {
261+
msg := fmt.Sprintf("VM Replication import, VM not found - 'replication_uuid'='%s'.", req.ID)
262+
resp.Diagnostics.AddError("VM Replication import error, VM not found", msg)
263+
return
264+
}
265+
266+
vmUUID := utils.AnyToString((*hc3Replication)["sourceDomainUUID"])
267+
targetVmUUID := utils.AnyToString((*hc3Replication)["targetDomainUUID"])
268+
connectionUUID := utils.AnyToString((*hc3Replication)["connectionUUID"])
269+
label := utils.AnyToString((*hc3Replication)["label"])
270+
enable := utils.AnyToBool((*hc3Replication)["enable"])
271+
272+
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), replicationUUID)...)
273+
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("target_vm_uuid"), targetVmUUID)...)
274+
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("vm_uuid"), vmUUID)...)
275+
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("connection_uuid"), connectionUUID)...)
276+
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("label"), label)...)
277+
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("enable"), enable)...)
278+
}

internal/utils/vm_replication.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package utils
5+
6+
import (
7+
"context"
8+
"fmt"
9+
10+
"github.com/hashicorp/terraform-plugin-framework/diag"
11+
"github.com/hashicorp/terraform-plugin-log/tflog"
12+
)
13+
14+
func GetVMReplicationByUUID(
15+
restClient RestClient,
16+
replicationUUID string,
17+
) *map[string]any {
18+
replication := restClient.GetRecord(
19+
fmt.Sprintf("/rest/v1/VirDomainReplication/%s", replicationUUID),
20+
nil,
21+
false,
22+
-1,
23+
)
24+
return replication
25+
}
26+
27+
func CreateVMReplication(
28+
restClient RestClient,
29+
sourceVmUUID string,
30+
connectionUUID string,
31+
label string,
32+
enable bool,
33+
ctx context.Context,
34+
) (string, map[string]any) {
35+
payload := map[string]any{
36+
"sourceDomainUUID": sourceVmUUID,
37+
"connectionUUID": connectionUUID,
38+
"label": label,
39+
"enable": enable,
40+
}
41+
taskTag, _, _ := restClient.CreateRecord(
42+
"/rest/v1/VirDomainReplication",
43+
payload,
44+
-1,
45+
)
46+
taskTag.WaitTask(restClient, ctx)
47+
replicationUUID := taskTag.CreatedUUID
48+
replication := GetVMReplicationByUUID(restClient, replicationUUID)
49+
return replicationUUID, *replication
50+
}
51+
52+
func UpdateVMReplication(
53+
restClient RestClient,
54+
replicationUUID string,
55+
sourceVmUUID string,
56+
connectionUUID string,
57+
label string,
58+
enable bool,
59+
ctx context.Context,
60+
) diag.Diagnostic {
61+
payload := map[string]any{
62+
"sourceDomainUUID": sourceVmUUID,
63+
"connectionUUID": connectionUUID,
64+
"label": label,
65+
"enable": enable,
66+
}
67+
68+
taskTag, err := restClient.UpdateRecord(
69+
fmt.Sprintf("/rest/v1/VirDomainReplication/%s", replicationUUID),
70+
payload,
71+
-1,
72+
ctx,
73+
)
74+
75+
if err != nil {
76+
return diag.NewWarningDiagnostic(
77+
"HC3 is receiving too many requests at the same time.",
78+
fmt.Sprintf("Please retry apply after Terraform finishes it's current operation. HC3 response message: %v", err.Error()),
79+
)
80+
}
81+
82+
taskTag.WaitTask(restClient, ctx)
83+
tflog.Debug(ctx, fmt.Sprintf("TTRT Task Tag: %v\n", taskTag))
84+
85+
return nil
86+
}

0 commit comments

Comments
 (0)