Skip to content

Commit a11cdfb

Browse files
committed
Add Proxmox custom actions
1 parent 5836a2d commit a11cdfb

File tree

2 files changed

+192
-20
lines changed

2 files changed

+192
-20
lines changed

engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql

Lines changed: 126 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ CREATE TABLE IF NOT EXISTS `cloud`.`extension_custom_action_details` (
289289

290290
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vm_template', 'extension_id', 'bigint unsigned DEFAULT NULL COMMENT "id of the extension"');
291291

292-
-- Add built-in extensions
292+
-- Add built-in Extensions and Custom Actions
293293

294294
DROP PROCEDURE IF EXISTS `cloud`.`INSERT_EXTENSION_IF_NOT_EXISTS`;
295295
CREATE PROCEDURE `cloud`.`INSERT_EXTENSION_IF_NOT_EXISTS`(
@@ -299,18 +299,18 @@ CREATE PROCEDURE `cloud`.`INSERT_EXTENSION_IF_NOT_EXISTS`(
299299
)
300300
BEGIN
301301
IF NOT EXISTS (
302-
SELECT 1 FROM extension WHERE name = ext_name
302+
SELECT 1 FROM `cloud`.`extension` WHERE `name` = ext_name
303303
) THEN
304-
INSERT INTO extension (
305-
uuid, name, description, type,
306-
relative_entry_point, entry_point_ready,
307-
is_user_defined, state, created, removed
304+
INSERT INTO `cloud`.`extension` (
305+
`uuid`, `name`, `description`, `type`,
306+
`relative_entry_point`, `entry_point_ready`,
307+
`is_user_defined`, `state`, `created`, `removed`
308308
)
309309
VALUES (
310310
UUID(), ext_name, ext_desc, 'Orchestrator',
311311
entry_point, 1, 0, 'Enabled', NOW(), NULL
312312
)
313-
;END IF
313+
; END IF
314314
;END;
315315

316316
DROP PROCEDURE IF EXISTS `cloud`.`INSERT_EXTENSION_DETAIL_IF_NOT_EXISTS`;
@@ -321,18 +321,18 @@ CREATE PROCEDURE `cloud`.`INSERT_EXTENSION_DETAIL_IF_NOT_EXISTS`(
321321
)
322322
BEGIN
323323
DECLARE ext_id BIGINT
324-
; SELECT id INTO ext_id FROM extension WHERE name = ext_name LIMIT 1
324+
; SELECT `id` INTO ext_id FROM `cloud`.`extension` WHERE `name` = ext_name LIMIT 1
325325
; IF NOT EXISTS (
326-
SELECT 1 FROM extension_details
327-
WHERE extension_id = ext_id AND name = detail_key
326+
SELECT 1 FROM `cloud`.`extension_details`
327+
WHERE `extension_id` = ext_id AND `name` = detail_key
328328
) THEN
329-
INSERT INTO extension_details (
330-
extension_id, name, value, display
329+
INSERT INTO `cloud`.`extension_details` (
330+
`extension_id`, `name`, `value`, `display`
331331
)
332332
VALUES (
333333
ext_id, detail_key, detail_value, 1
334334
)
335-
;END IF
335+
; END IF
336336
;END;
337337

338338
CALL `cloud`.`INSERT_EXTENSION_IF_NOT_EXISTS`('Proxmox', 'Sample extension for Proxmox written in bash', 'Proxmox/proxmox.sh');
@@ -345,3 +345,116 @@ CALL `cloud`.`INSERT_EXTENSION_IF_NOT_EXISTS`('HyperV', 'Sample extension for Hy
345345
CALL `cloud`.`INSERT_EXTENSION_DETAIL_IF_NOT_EXISTS`('HyperV', 'url', '');
346346
CALL `cloud`.`INSERT_EXTENSION_DETAIL_IF_NOT_EXISTS`('HyperV', 'username', '');
347347
CALL `cloud`.`INSERT_EXTENSION_DETAIL_IF_NOT_EXISTS`('HyperV', 'password', '');
348+
349+
DROP PROCEDURE IF EXISTS `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_IF_NOT_EXISTS`;
350+
CREATE PROCEDURE `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_IF_NOT_EXISTS`(
351+
IN ext_name VARCHAR(255),
352+
IN action_name VARCHAR(255),
353+
IN action_desc VARCHAR(4096),
354+
IN resource_type VARCHAR(255),
355+
IN allowed_roles INT UNSIGNED,
356+
IN success_msg VARCHAR(4096),
357+
IN error_msg VARCHAR(4096),
358+
IN timeout_seconds INT UNSIGNED
359+
)
360+
BEGIN
361+
DECLARE ext_id BIGINT
362+
; SELECT `id` INTO ext_id FROM `cloud`.`extension` WHERE `name` = ext_name LIMIT 1
363+
; IF NOT EXISTS (
364+
SELECT 1 FROM `cloud`.`extension_custom_action` WHERE `name` = action_name AND `extension_id` = ext_id
365+
) THEN
366+
INSERT INTO `cloud`.`extension_custom_action` (
367+
`uuid`, `name`, `description`, `extension_id`, `resource_type`,
368+
`allowed_role_types`, `success_message`, `error_message`,
369+
`enabled`, `timeout`, `created`, `removed`
370+
)
371+
VALUES (
372+
UUID(), action_name, action_desc, ext_id, resource_type,
373+
allowed_roles, success_msg, error_msg,
374+
1, timeout_seconds, NOW(), NULL
375+
)
376+
; END IF
377+
;END;
378+
379+
DROP PROCEDURE IF EXISTS `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_DETAILS_IF_NOT_EXISTS`;
380+
CREATE PROCEDURE `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_DETAILS_IF_NOT_EXISTS` (
381+
IN ext_name VARCHAR(255),
382+
IN action_name VARCHAR(255),
383+
IN param_json TEXT
384+
)
385+
BEGIN
386+
DECLARE action_id BIGINT UNSIGNED
387+
; SELECT `eca`.`id` INTO action_id FROM `cloud`.`extension_custom_action` `eca`
388+
JOIN `cloud`.`extension` `e` ON `e`.`id` = `eca`.`extension_id`
389+
WHERE `eca`.`name` = action_name AND `e`.`name` = ext_name LIMIT 1
390+
; IF NOT EXISTS (
391+
SELECT 1 FROM `cloud`.`extension_custom_action_details`
392+
WHERE `extension_custom_action_id` = action_id
393+
AND `name` = 'parameters'
394+
) THEN
395+
INSERT INTO `cloud`.`extension_custom_action_details` (
396+
`extension_custom_action_id`,
397+
`name`,
398+
`value`,
399+
`display`
400+
) VALUES (
401+
action_id,
402+
'parameters',
403+
param_json,
404+
0
405+
)
406+
; END IF
407+
;END;
408+
409+
CALL `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_IF_NOT_EXISTS`('Proxmox', 'CreateSnapshot', 'Create Instance Snapshot', 'VirtualMachine', 15, 'Snapshot created for {{resourceName}} in {{extensionName}}', 'Snapshot creation failed for {{resourceName}}', 60);
410+
CALL `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_IF_NOT_EXISTS`('Proxmox', 'RestoreSnapshot', 'Restore Instance Snapshot', 'VirtualMachine', 15, 'Successfully restored snapshot for {{resourceName}} in {{extensionName}}', 'Restore snapshot failed for {{resourceName}}', 60);
411+
CALL `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_IF_NOT_EXISTS`('Proxmox', 'DeleteSnapshot', 'Delete Instance Snapshot', 'VirtualMachine', 15, 'Successfully deleted snapshot for {{resourceName}} in {{extensionName}}', 'Delete snapshot failed for {{resourceName}}', 60);
412+
413+
CALL cloud.INSERT_EXTENSION_CUSTOM_ACTION_DETAILS_IF_NOT_EXISTS(
414+
'Proxmox',
415+
'CreateSnapshot',
416+
'[
417+
{
418+
"name": "snap_name",
419+
"type": "STRING",
420+
"validationformat": "NONE",
421+
"required": true
422+
},
423+
{
424+
"name": "snap_description",
425+
"type": "STRING",
426+
"validationformat": "NONE",
427+
"required": false
428+
},
429+
{
430+
"name": "snap_save_memory",
431+
"type": "BOOLEAN",
432+
"validationformat": "NONE",
433+
"required": false
434+
}
435+
]'
436+
);
437+
CALL cloud.INSERT_EXTENSION_CUSTOM_ACTION_DETAILS_IF_NOT_EXISTS(
438+
'Proxmox',
439+
'RestoreSnapshot',
440+
'[
441+
{
442+
"name": "snap_name",
443+
"type": "STRING",
444+
"validationformat": "NONE",
445+
"required": true
446+
}
447+
]'
448+
);
449+
CALL cloud.INSERT_EXTENSION_CUSTOM_ACTION_DETAILS_IF_NOT_EXISTS(
450+
'Proxmox',
451+
'DeleteSnapshot',
452+
'[
453+
{
454+
"name": "snap_name",
455+
"type": "STRING",
456+
"validationformat": "NONE",
457+
"required": true
458+
}
459+
]'
460+
);

extensions/Proxmox/proxmox.sh

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,11 @@ cloudstack_vm_internal_name_to_proxmox_vmid() {
5656
echo "$vmid"
5757
}
5858

59-
validate_vm_name() {
60-
local name="$1"
61-
59+
validate_name() {
60+
local entity="$1"
61+
local name="$2"
6262
if [[ ! "$name" =~ ^[a-zA-Z0-9-]+$ ]]; then
63-
echo "{\"error\":\"Invalid VM name '$name'. Only alphanumeric characters and dashes (-) are allowed.\"}"
63+
echo "{\"error\":\"Invalid $entity name '$name'. Only alphanumeric characters and dashes (-) are allowed.\"}"
6464
exit 1
6565
fi
6666
}
@@ -86,7 +86,10 @@ parse_json() {
8686
"vmmemory": (."cloudstack.vm.details".minRam // ""),
8787
"vmcpus": (."cloudstack.vm.details".cpus // ""),
8888
"vlan": (."cloudstack.vm.details".nics[0].broadcastUri // "" | sub("vlan://"; "")),
89-
"mac_address": (."cloudstack.vm.details".nics[0].mac // "")
89+
"mac_address": (."cloudstack.vm.details".nics[0].mac // ""),
90+
"snap_name": (.parameters.snap_name // ""),
91+
"snap_description": (.parameters.snap_description // ""),
92+
"snap_save_memory": (.parameters.snap_save_memory // "")
9093
} | to_entries | .[] | "\(.key)=\(.value)"')
9194

9295
for key in "${!details[@]}"; do
@@ -98,7 +101,7 @@ parse_json() {
98101
if [[ -z "$vm_name" ]]; then
99102
vm_name="$vm_internal_name"
100103
fi
101-
validate_vm_name "$vm_name"
104+
validate_name "VM" "$vm_name"
102105
vmid=$(cloudstack_vm_internal_name_to_proxmox_vmid "$vm_internal_name")
103106
}
104107

@@ -193,6 +196,8 @@ create() {
193196
parse_json "$1" || exit 1
194197

195198
check_required_fields vm_name vlan mac_address
199+
validate_name "VM" "$vm_name"
200+
196201
if [[ "${template_type^^}" == "ISO" ]]; then
197202
check_required_fields iso_path vmcpus vmmemory
198203
local data="vmid=$vmid"
@@ -210,7 +215,7 @@ create() {
210215
else
211216
check_required_fields template_id
212217
local data="newid=$vmid"
213-
data+="&name=\"$vm_name\""
218+
data+="&name=$vm_name"
214219
execute_and_wait POST "/nodes/${node}/qemu/${template_id}/clone" "$data"
215220
fi
216221

@@ -261,6 +266,51 @@ status() {
261266
echo "{\"status\": \"success\", \"power_state\": \"$powerstate\"}"
262267
}
263268

269+
create_snapshot() {
270+
parse_json "$1" || exit 1
271+
272+
check_required_fields snap_name
273+
validate_name "Snapshot" "$snap_name"
274+
275+
local data, vmstate
276+
data="snapname=$snap_name"
277+
if [[ -n "$snap_description" ]]; then
278+
data+="&description=$snap_description"
279+
fi
280+
if [[ -n "$snap_save_memory" && "$snap_save_memory" == "true" ]]; then
281+
vmstate="1"
282+
else
283+
vmstate="0"
284+
fi
285+
data+="&vmstate=$vmstate"
286+
287+
execute_and_wait POST "/nodes/${node}/qemu/${vmid}/snapshot" "$data"
288+
echo '{"status": "success", "message": "Instance Snapshot created"}'
289+
}
290+
291+
restore_snapshot() {
292+
parse_json "$1" || exit 1
293+
294+
check_required_fields snap_name
295+
validate_name "Snapshot" "$snap_name"
296+
297+
execute_and_wait POST "/nodes/${node}/qemu/${vmid}/snapshot/${snap_name}/rollback"
298+
299+
execute_and_wait POST "/nodes/${node}/qemu/${vmid}/status/start"
300+
301+
echo '{"status": "success", "message": "Instance Snapshot restored"}'
302+
}
303+
304+
delete_snapshot() {
305+
parse_json "$1" || exit 1
306+
307+
check_required_fields snap_name
308+
validate_name "Snapshot" "$snap_name"
309+
310+
execute_and_wait DELETE "/nodes/${node}/qemu/${vmid}/snapshot/${snap_name}"
311+
echo '{"status": "success", "message": "Instance Snapshot deleted"}'
312+
}
313+
264314
action=$1
265315
parameters_file="$2"
266316
wait_time=$3
@@ -295,6 +345,15 @@ case $action in
295345
status)
296346
status "$parameters"
297347
;;
348+
CreateSnapshot)
349+
create_snapshot "$parameters"
350+
;;
351+
RestoreSnapshot)
352+
restore_snapshot "$parameters"
353+
;;
354+
DeleteSnapshot)
355+
delete_snapshot "$parameters"
356+
;;
298357
*)
299358
echo '{"error":"Invalid action"}'
300359
exit 1

0 commit comments

Comments
 (0)