Skip to content

Commit

Permalink
CP-51659: Create VM from Template with Full Disk Copy (#83)
Browse files Browse the repository at this point in the history
Signed-off-by: xueqingz <[email protected]>
  • Loading branch information
xueqingz authored Jan 9, 2025
1 parent c53d313 commit d92a91d
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 27 deletions.
20 changes: 20 additions & 0 deletions docs/resources/vm.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,23 @@ resource "xenserver_vm" "windows_vm" {
}
}
# Create a Windows 11 VM that is copy from the custom template
resource "xenserver_vm" "windows_vm_copy" {
name_label = "Windows VM Copy From Custom Template"
template_name = "Custom Windows 11 Template"
static_mem_max = 4 * 1024 * 1024 * 1024
vcpus = 4
cores_per_socket = 2
sr_for_full_disk_copy = data.xenserver_sr.sr.data_items[0].uuid
network_interface = [
{
device = "0"
network_uuid = data.xenserver_network.network.data_items[0].uuid,
},
]
}
# Create a Linux VM that is cloned from the custom template
resource "xenserver_vm" "linux_vm" {
name_label = "Linux VM"
Expand Down Expand Up @@ -200,6 +217,9 @@ output "vm_out" {
- `hard_drive` (Attributes Set) A set of hard drive attributes to attach to the virtual machine, default inherited from the template. (see [below for nested schema](#nestedatt--hard_drive))
- `name_description` (String) The description of the virtual machine, default to be `""`.
- `other_config` (Map of String) The additional configuration of the virtual machine, default to be `{}`.
- `sr_for_full_disk_copy` (String) Use storage-level full disk copy. Give a SR uuid or set as `"origin"` to keep use the origin SR of template disks. Only support custom template.

-> **Note:** `sr_for_full_disk_copy` is not allowed to be updated.
- `static_mem_min` (Number) Statically-set (absolute) minimum memory (bytes), default same with `static_mem_max`. The least amount of memory this VM can boot with without crashing.

### Read-Only
Expand Down
17 changes: 17 additions & 0 deletions examples/resources/xenserver_vm/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,23 @@ resource "xenserver_vm" "windows_vm" {
}
}

# Create a Windows 11 VM that is copy from the custom template
resource "xenserver_vm" "windows_vm_copy" {
name_label = "Windows VM Copy From Custom Template"
template_name = "Custom Windows 11 Template"
static_mem_max = 4 * 1024 * 1024 * 1024
vcpus = 4
cores_per_socket = 2
sr_for_full_disk_copy = data.xenserver_sr.sr.data_items[0].uuid

network_interface = [
{
device = "0"
network_uuid = data.xenserver_network.network.data_items[0].uuid,
},
]
}

# Create a Linux VM that is cloned from the custom template
resource "xenserver_vm" "linux_vm" {
name_label = "Linux VM"
Expand Down
38 changes: 30 additions & 8 deletions xenserver/vm_resouce.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,36 @@ func (r *vmResource) Create(ctx context.Context, req resource.CreateRequest, res
)
return
}
tflog.Debug(ctx, "Clone VM from a template")
vmRef, err := xenapi.VM.Clone(r.session, templateRef, plan.NameLabel.ValueString())
if err != nil {
resp.Diagnostics.AddError(
"Unable to clone VM from template",
err.Error(),
)
return

var vmRef xenapi.VMRef
if !plan.SRForFullDiskCopy.IsUnknown() && plan.SRForFullDiskCopy.ValueString() != "" {
srRef, err := checkIfSupportFullCopy(r.session, templateRef, plan.SRForFullDiskCopy.ValueString())
if err != nil {
resp.Diagnostics.AddError(
"Use storage-level full disk copy but get error",
err.Error(),
)
return
}
tflog.Debug(ctx, "----> Copy VM from a template")
vmRef, err = xenapi.VM.Copy(r.session, templateRef, plan.NameLabel.ValueString(), srRef)
if err != nil {
resp.Diagnostics.AddError(
"Unable to copy VM from template",
err.Error(),
)
return
}
} else {
tflog.Debug(ctx, "----> Clone VM from a template")
vmRef, err = xenapi.VM.Clone(r.session, templateRef, plan.NameLabel.ValueString())
if err != nil {
resp.Diagnostics.AddError(
"Unable to clone VM from template",
err.Error(),
)
return
}
}

err = setVMResourceModel(ctx, r.session, vmRef, plan)
Expand Down
93 changes: 74 additions & 19 deletions xenserver/vm_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,25 +131,26 @@ type vmRecordData struct {

// vmResourceModel describes the resource data model.
type vmResourceModel struct {
NameLabel types.String `tfsdk:"name_label"`
NameDescription types.String `tfsdk:"name_description"`
TemplateName types.String `tfsdk:"template_name"`
StaticMemMin types.Int64 `tfsdk:"static_mem_min"`
StaticMemMax types.Int64 `tfsdk:"static_mem_max"`
DynamicMemMin types.Int64 `tfsdk:"dynamic_mem_min"`
DynamicMemMax types.Int64 `tfsdk:"dynamic_mem_max"`
VCPUs types.Int32 `tfsdk:"vcpus"`
BootMode types.String `tfsdk:"boot_mode"`
BootOrder types.String `tfsdk:"boot_order"`
CorePerSocket types.Int32 `tfsdk:"cores_per_socket"`
OtherConfig types.Map `tfsdk:"other_config"`
HardDrive types.Set `tfsdk:"hard_drive"`
NetworkInterface types.Set `tfsdk:"network_interface"`
CDROM types.String `tfsdk:"cdrom"`
UUID types.String `tfsdk:"uuid"`
ID types.String `tfsdk:"id"`
DefaultIP types.String `tfsdk:"default_ip"`
CheckIPTimeout types.Int64 `tfsdk:"check_ip_timeout"`
NameLabel types.String `tfsdk:"name_label"`
NameDescription types.String `tfsdk:"name_description"`
TemplateName types.String `tfsdk:"template_name"`
StaticMemMin types.Int64 `tfsdk:"static_mem_min"`
StaticMemMax types.Int64 `tfsdk:"static_mem_max"`
DynamicMemMin types.Int64 `tfsdk:"dynamic_mem_min"`
DynamicMemMax types.Int64 `tfsdk:"dynamic_mem_max"`
VCPUs types.Int32 `tfsdk:"vcpus"`
BootMode types.String `tfsdk:"boot_mode"`
BootOrder types.String `tfsdk:"boot_order"`
CorePerSocket types.Int32 `tfsdk:"cores_per_socket"`
OtherConfig types.Map `tfsdk:"other_config"`
HardDrive types.Set `tfsdk:"hard_drive"`
SRForFullDiskCopy types.String `tfsdk:"sr_for_full_disk_copy"`
NetworkInterface types.Set `tfsdk:"network_interface"`
CDROM types.String `tfsdk:"cdrom"`
UUID types.String `tfsdk:"uuid"`
ID types.String `tfsdk:"id"`
DefaultIP types.String `tfsdk:"default_ip"`
CheckIPTimeout types.Int64 `tfsdk:"check_ip_timeout"`
}

func vmSchema() map[string]schema.Attribute {
Expand Down Expand Up @@ -229,6 +230,13 @@ func vmSchema() map[string]schema.Attribute {
Optional: true,
Computed: true,
},
"sr_for_full_disk_copy": schema.StringAttribute{
MarkdownDescription: "Use storage-level full disk copy. Give a SR uuid or set as `\"origin\"` to keep use the origin SR of template disks. Only support custom template." +
"\n\n-> **Note:** `sr_for_full_disk_copy` is not allowed to be updated.",
Optional: true,
Computed: true,
Default: stringdefault.StaticString(""),
},
"network_interface": schema.SetNestedAttribute{
MarkdownDescription: "A set of network interface attributes to attach to the virtual machine." + "<br />" +
"Set at least one item in this attribute when use it.",
Expand Down Expand Up @@ -473,6 +481,45 @@ func getFirstTemplate(session *xenapi.Session, templateName string) (xenapi.VMRe
return vmRef, errors.New("unable to find the VM template with the name: " + templateName)
}

func checkIfSupportFullCopy(session *xenapi.Session, templateRef xenapi.VMRef, srUUID string) (xenapi.SRRef, error) {
var srRef xenapi.SRRef
// show error if choose the XS default template
isDefaultTemplate, err := xenapi.VM.GetIsDefaultTemplate(session, templateRef)
if err != nil {
return srRef, errors.New("can't get is_default_template. " + err.Error())
}
if isDefaultTemplate {
return srRef, errors.New("don't support default template")
}

// check if VM template disk allow copy
templateHardDrives, err := getAllDiskTypeVBDs(session, templateRef)
if err != nil {
return srRef, err
}
for _, vbdRefStr := range templateHardDrives {
vdiRef, err := xenapi.VBD.GetVDI(session, xenapi.VBDRef(vbdRefStr))
if err != nil {
return srRef, errors.New("can't get VDI ref. " + err.Error())
}
allowedOps, err := xenapi.VDI.GetAllowedOperations(session, vdiRef)
if err != nil {
return srRef, errors.New("can't get VDI allowed_operations. " + err.Error())
}
if !slices.Contains(allowedOps, xenapi.VdiOperationsCopy) {
return srRef, errors.New("template disk doesn't allow copy")
}
}

if srUUID != "origin" {
srRef, err = xenapi.SR.GetByUUID(session, srUUID)
if err != nil {
return srRef, errors.New("can't get SR ref. " + err.Error())
}
}
return srRef, nil
}

func setOtherConfigWhenCreate(session *xenapi.Session, vmRef xenapi.VMRef) error {
vmOtherConfig, err := xenapi.VM.GetOtherConfig(session, vmRef)
if err != nil {
Expand Down Expand Up @@ -535,6 +582,7 @@ func updateOtherConfigFromPlan(ctx context.Context, session *xenapi.Session, vmR
vmOtherConfig["tf_other_config_keys"] = strings.Join(tfOtherConfigKeys, ",")
vmOtherConfig["tf_check_ip_timeout"] = plan.CheckIPTimeout.String()
vmOtherConfig["tf_template_name"] = plan.TemplateName.ValueString()
vmOtherConfig["tf_sr_for_full_disk_copy"] = plan.SRForFullDiskCopy.ValueString()

err = xenapi.VM.SetOtherConfig(session, vmRef, vmOtherConfig)
if err != nil {
Expand Down Expand Up @@ -638,6 +686,10 @@ func updateVMResourceModelComputed(ctx context.Context, session *xenapi.Session,
data.DefaultIP = types.StringValue(ip)
}

if _, ok := vmRecord.OtherConfig["tf_sr_for_full_disk_copy"]; ok {
data.SRForFullDiskCopy = types.StringValue(vmRecord.OtherConfig["tf_sr_for_full_disk_copy"])
}

return nil
}

Expand Down Expand Up @@ -1220,5 +1272,8 @@ func vmResourceModelUpdateCheck(plan vmResourceModel, state vmResourceModel) err
if !plan.BootMode.IsUnknown() && plan.BootMode != state.BootMode {
return errors.New(`"boot_mode" doesn't expected to be updated`)
}
if !plan.SRForFullDiskCopy.IsUnknown() && plan.SRForFullDiskCopy != state.SRForFullDiskCopy {
return errors.New(`"sr_for_full_disk_copy" doesn't expected to be updated`)
}
return nil
}

0 comments on commit d92a91d

Please sign in to comment.