Skip to content

Commit d7762e1

Browse files
committed
Implement hypercore_vm_replication resource
1 parent 1e3299f commit d7762e1

File tree

3 files changed

+377
-33
lines changed

3 files changed

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

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 GetVMReplication(
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 := GetNic(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)