Skip to content

Commit

Permalink
feat(vm): add support for watchdog (#1556)
Browse files Browse the repository at this point in the history
Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
  • Loading branch information
bpg authored Sep 30, 2024
1 parent 471c5f2 commit d226b59
Show file tree
Hide file tree
Showing 4 changed files with 247 additions and 1 deletion.
12 changes: 12 additions & 0 deletions docs/resources/virtual_environment_vm.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
- `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).
- `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

Expand Down
56 changes: 56 additions & 0 deletions fwprovider/test/resource_vm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion proxmox/nodes/vms/custom_watchdog_device.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type CustomWatchdogDevice struct {
// EncodeValues converts a CustomWatchdogDevice struct to a URL value.
func (r *CustomWatchdogDevice) EncodeValues(key string, v *url.Values) error {
values := []string{
fmt.Sprintf("model=%+v", r.Model),
fmt.Sprintf("model=%s", *r.Model),
}

if r.Action != nil {
Expand Down
178 changes: 178 additions & 0 deletions proxmoxtf/resource/vm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ const (
dvSCSIHardware = "virtio-scsi-pci"
dvStopOnDestroy = false
dvHookScript = ""
dvWatchdogModel = "i6300esb"
dvWatchdogAction = "none"

maxResourceVirtualEnvironmentVMAudioDevices = 1
maxResourceVirtualEnvironmentVMSerialDevices = 4
Expand Down Expand Up @@ -278,6 +280,11 @@ const (
mkSCSIHardware = "scsi_hardware"
mkHookScriptFileID = "hook_script_file_id"
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.
Expand Down Expand Up @@ -1457,6 +1464,56 @@ func VM() *schema.Resource {
Optional: true,
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())
Expand Down Expand Up @@ -1801,6 +1858,7 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d
protection := types.CustomBool(d.Get(mkProtection).(bool))
template := types.CustomBool(d.Get(mkTemplate).(bool))
vga := d.Get(mkVGA).([]interface{})
watchdog := d.Get(mkWatchdog).([]interface{})

updateBody := &vms.UpdateRequestBody{
AudioDevices: audioDevices,
Expand Down Expand Up @@ -2075,6 +2133,23 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d
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

e = vmAPI.UpdateVM(ctx, updateBody)
Expand Down Expand Up @@ -2521,6 +2596,32 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{})

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{
ACPI: &acpi,
Agent: &vms.CustomAgent{
Expand Down Expand Up @@ -2563,6 +2664,7 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{})
USBDevices: usbDeviceObjects,
VGADevice: vgaDevice,
VMID: vmID,
WatchdogDevice: watchdogObject,
CustomStorageDevices: diskDeviceObjects,
}

Expand Down Expand Up @@ -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)
started := d.Get(mkStarted).(bool)

Expand Down Expand Up @@ -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.
updateBody.Delete = del

Expand Down

0 comments on commit d226b59

Please sign in to comment.