0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-06-30 02:31:10 +00:00

feat(vm): add support for watchdog (#1556)

Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
Pavel Boldyrev 2024-09-30 11:14:55 -04:00 committed by GitHub
parent 471c5f23bb
commit d226b59e2e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 247 additions and 1 deletions

View File

@ -555,6 +555,18 @@ output "ubuntu_vm_public_key" {
- `clipboard` - (Optional) Enable VNC clipboard by setting to `vnc`. See the [Proxmox documentation](https://pve.proxmox.com/pve-docs/pve-admin-guide.html#qm_virtual_machines_settings) section 10.2.8 for more information. - `clipboard` - (Optional) Enable VNC clipboard by setting to `vnc`. See the [Proxmox documentation](https://pve.proxmox.com/pve-docs/pve-admin-guide.html#qm_virtual_machines_settings) section 10.2.8 for more information.
- `vm_id` - (Optional) The VM identifier. - `vm_id` - (Optional) The VM identifier.
- `hook_script_file_id` - (Optional) The identifier for a file containing a hook script (needs to be executable, e.g. by using the `proxmox_virtual_environment_file.file_mode` attribute). - `hook_script_file_id` - (Optional) The identifier for a file containing a hook script (needs to be executable, e.g. by using the `proxmox_virtual_environment_file.file_mode` attribute).
- `watchdog` - (Optional) The watchdog configuration. Once enabled (by a guest action), the watchdog must be periodically polled by an agent inside the guest or else the watchdog will reset the guest (or execute the respective action specified).
- `enabled` - (Optional) Whether the watchdog is enabled (defaults to `false`).
- `model` - (Optional) The watchdog type to emulate (defaults to `i6300esb`).
- `i6300esb` - Intel 6300ESB.
- `ib700` - iBase IB700.
- `action` - (Optional) The action to perform if after activation the guest fails to poll the watchdog in time (defaults to `none`).
- `debug`
- `none`
- `pause`
- `poweroff`
- `reset`
- `shutdown`
## Attribute Reference ## Attribute Reference

View File

@ -217,6 +217,62 @@ func TestAccResourceVM(t *testing.T) {
), ),
}}, }},
}, },
{
"update watchdog block", []resource.TestStep{{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_vm" {
node_name = "{{.NodeName}}"
started = false
watchdog {
enabled = "true"
}
}`),
Check: resource.ComposeTestCheckFunc(
ResourceAttributes("proxmox_virtual_environment_vm.test_vm", map[string]string{
"watchdog.0.model": "i6300esb",
"watchdog.0.action": "none",
}),
),
}, {
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_vm" {
node_name = "{{.NodeName}}"
started = false
watchdog {
enabled = "true"
model = "ib700"
action = "reset"
}
}`),
Check: resource.ComposeTestCheckFunc(
ResourceAttributes("proxmox_virtual_environment_vm.test_vm", map[string]string{
"watchdog.0.model": "ib700",
"watchdog.0.action": "reset",
}),
),
}, {
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_vm" {
node_name = "{{.NodeName}}"
started = false
watchdog {
enabled = "false"
model = "ib700"
action = "reset"
}
}`),
Check: resource.ComposeTestCheckFunc(
ResourceAttributes("proxmox_virtual_environment_vm.test_vm", map[string]string{
"watchdog.0.enabled": "false",
"watchdog.0.model": "ib700",
"watchdog.0.action": "reset",
}),
),
}},
},
} }
for _, tt := range tests { for _, tt := range tests {

View File

@ -22,7 +22,7 @@ type CustomWatchdogDevice struct {
// EncodeValues converts a CustomWatchdogDevice struct to a URL value. // EncodeValues converts a CustomWatchdogDevice struct to a URL value.
func (r *CustomWatchdogDevice) EncodeValues(key string, v *url.Values) error { func (r *CustomWatchdogDevice) EncodeValues(key string, v *url.Values) error {
values := []string{ values := []string{
fmt.Sprintf("model=%+v", r.Model), fmt.Sprintf("model=%s", *r.Model),
} }
if r.Action != nil { if r.Action != nil {

View File

@ -129,6 +129,8 @@ const (
dvSCSIHardware = "virtio-scsi-pci" dvSCSIHardware = "virtio-scsi-pci"
dvStopOnDestroy = false dvStopOnDestroy = false
dvHookScript = "" dvHookScript = ""
dvWatchdogModel = "i6300esb"
dvWatchdogAction = "none"
maxResourceVirtualEnvironmentVMAudioDevices = 1 maxResourceVirtualEnvironmentVMAudioDevices = 1
maxResourceVirtualEnvironmentVMSerialDevices = 4 maxResourceVirtualEnvironmentVMSerialDevices = 4
@ -278,6 +280,11 @@ const (
mkSCSIHardware = "scsi_hardware" mkSCSIHardware = "scsi_hardware"
mkHookScriptFileID = "hook_script_file_id" mkHookScriptFileID = "hook_script_file_id"
mkStopOnDestroy = "stop_on_destroy" mkStopOnDestroy = "stop_on_destroy"
mkWatchdog = "watchdog"
// a workaround for the lack of proper support of default and undefined values in SDK.
mkWatchdogEnabled = "enabled"
mkWatchdogModel = "model"
mkWatchdogAction = "action"
) )
// VM returns a resource that manages VMs. // VM returns a resource that manages VMs.
@ -1457,6 +1464,56 @@ func VM() *schema.Resource {
Optional: true, Optional: true,
Default: dvStopOnDestroy, Default: dvStopOnDestroy,
}, },
mkWatchdog: {
Type: schema.TypeList,
Description: "The watchdog configuration",
Optional: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{
map[string]interface{}{
mkWatchdogAction: dvWatchdogAction,
mkWatchdogEnabled: false,
mkWatchdogModel: dvWatchdogModel,
},
}, nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkWatchdogAction: {
Type: schema.TypeString,
Description: "The watchdog action",
Optional: true,
Default: dvWatchdogAction,
ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{
"debug",
"none",
"pause",
"poweroff",
"reset",
"shutdown",
}, true)),
},
mkWatchdogEnabled: {
Type: schema.TypeBool,
Description: "Whether the watchdog is enabled",
Optional: true,
Default: false,
},
mkWatchdogModel: {
Type: schema.TypeString,
Description: "The watchdog model",
Optional: true,
Default: dvWatchdogModel,
ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{
"i6300esb",
"ib700",
}, true)),
},
},
},
MaxItems: 1,
MinItems: 0,
},
} }
structure.MergeSchema(s, disk.Schema()) structure.MergeSchema(s, disk.Schema())
@ -1801,6 +1858,7 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d
protection := types.CustomBool(d.Get(mkProtection).(bool)) protection := types.CustomBool(d.Get(mkProtection).(bool))
template := types.CustomBool(d.Get(mkTemplate).(bool)) template := types.CustomBool(d.Get(mkTemplate).(bool))
vga := d.Get(mkVGA).([]interface{}) vga := d.Get(mkVGA).([]interface{})
watchdog := d.Get(mkWatchdog).([]interface{})
updateBody := &vms.UpdateRequestBody{ updateBody := &vms.UpdateRequestBody{
AudioDevices: audioDevices, AudioDevices: audioDevices,
@ -2075,6 +2133,23 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d
del = append(del, "hookscript") del = append(del, "hookscript")
} }
if len(watchdog) > 0 && watchdog[0] != nil {
watchdogBlock := watchdog[0].(map[string]interface{})
watchdogEnabled := types.CustomBool(
watchdogBlock[mkWatchdogEnabled].(bool),
)
if watchdogEnabled {
watchdogAction := watchdogBlock[mkWatchdogAction].(string)
watchdogModel := watchdogBlock[mkWatchdogModel].(string)
updateBody.WatchdogDevice = &vms.CustomWatchdogDevice{
Action: &watchdogAction,
Model: &watchdogModel,
}
}
}
updateBody.Delete = del updateBody.Delete = del
e = vmAPI.UpdateVM(ctx, updateBody) e = vmAPI.UpdateVM(ctx, updateBody)
@ -2521,6 +2596,32 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{})
scsiHardware := d.Get(mkSCSIHardware).(string) scsiHardware := d.Get(mkSCSIHardware).(string)
var watchdogObject *vms.CustomWatchdogDevice
watchdogBlock, err := structure.GetSchemaBlock(
resource,
d,
[]string{mkWatchdog},
0,
true,
)
if err != nil {
return diag.FromErr(err)
}
watchdogEnabled := types.CustomBool(
watchdogBlock[mkWatchdogEnabled].(bool),
)
if watchdogEnabled {
watchdogAction := watchdogBlock[mkWatchdogAction].(string)
watchdogModel := watchdogBlock[mkWatchdogModel].(string)
watchdogObject = &vms.CustomWatchdogDevice{
Action: &watchdogAction,
Model: &watchdogModel,
}
}
createBody := &vms.CreateRequestBody{ createBody := &vms.CreateRequestBody{
ACPI: &acpi, ACPI: &acpi,
Agent: &vms.CustomAgent{ Agent: &vms.CustomAgent{
@ -2563,6 +2664,7 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{})
USBDevices: usbDeviceObjects, USBDevices: usbDeviceObjects,
VGADevice: vgaDevice, VGADevice: vgaDevice,
VMID: vmID, VMID: vmID,
WatchdogDevice: watchdogObject,
CustomStorageDevices: diskDeviceObjects, CustomStorageDevices: diskDeviceObjects,
} }
@ -4325,6 +4427,51 @@ func vmReadCustom(
} }
} }
watchdog := map[string]interface{}{}
if vmConfig.WatchdogDevice != nil {
watchdog[mkWatchdogEnabled] = true
if vmConfig.WatchdogDevice.Action != nil {
watchdog[mkWatchdogAction] = *vmConfig.WatchdogDevice.Action
} else {
watchdog[mkWatchdogAction] = dvWatchdogAction
}
if vmConfig.WatchdogDevice.Model != nil {
watchdog[mkWatchdogModel] = *vmConfig.WatchdogDevice.Model
} else {
watchdog[mkWatchdogModel] = dvWatchdogModel
}
} else {
watchdog[mkWatchdogEnabled] = false
watchdog[mkWatchdogAction] = dvWatchdogAction
watchdog[mkWatchdogModel] = dvWatchdogModel
}
currentWatchdog := d.Get(mkWatchdog).([]interface{})
currentWatchdogEnabled := len(currentWatchdog) > 0 &&
currentWatchdog[0] != nil && currentWatchdog[0].(map[string]interface{})[mkWatchdogEnabled].(bool)
currentWatchdogDisabled := len(currentWatchdog) > 0 &&
currentWatchdog[0] != nil && !currentWatchdog[0].(map[string]interface{})[mkWatchdogEnabled].(bool)
switch {
case len(clone) > 0 && len(currentWatchdog) > 0:
err := d.Set(mkWatchdog, []interface{}{watchdog})
diags = append(diags, diag.FromErr(err)...)
case currentWatchdogEnabled ||
watchdog[mkWatchdogEnabled] != false ||
watchdog[mkWatchdogAction] != dvWatchdogAction ||
watchdog[mkWatchdogModel] != dvWatchdogModel:
err := d.Set(mkWatchdog, []interface{}{watchdog})
diags = append(diags, diag.FromErr(err)...)
case currentWatchdogDisabled && vmConfig.WatchdogDevice == nil:
// do nothing
default:
err := d.Set(mkWatchdog, []interface{}{})
diags = append(diags, diag.FromErr(err)...)
}
vmAPI := client.Node(nodeName).VM(vmID) vmAPI := client.Node(nodeName).VM(vmID)
started := d.Get(mkStarted).(bool) started := d.Get(mkStarted).(bool)
@ -5160,6 +5307,37 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D
} }
} }
// Prepare the new watchdog configuration.
if d.HasChange(mkWatchdog) {
watchdogBlock, err := structure.GetSchemaBlock(
resource,
d,
[]string{mkWatchdog},
0,
true,
)
if err != nil {
return diag.FromErr(err)
}
watchdogEnabled := types.CustomBool(
watchdogBlock[mkWatchdogEnabled].(bool),
)
if watchdogEnabled {
watchdogAction := watchdogBlock[mkWatchdogAction].(string)
watchdogModel := watchdogBlock[mkWatchdogModel].(string)
updateBody.WatchdogDevice = &vms.CustomWatchdogDevice{
Action: &watchdogAction,
Model: &watchdogModel,
}
} else {
del = append(del, "watchdog")
}
rebootRequired = true
}
// Update the configuration now that everything has been prepared. // Update the configuration now that everything has been prepared.
updateBody.Delete = del updateBody.Delete = del