Skip to content

Commit 774c3be

Browse files
adds experimental cluster support
1 parent 48b1ffd commit 774c3be

File tree

2 files changed

+49
-31
lines changed

2 files changed

+49
-31
lines changed

netbox-event-driven-automation-flask-app/app.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import os
12
import logging
23
import json
34
import yaml
@@ -128,15 +129,30 @@ def post(self):
128129
results = tc.proxmox_delete_vm(webhook_json_data)
129130
elif webhook_json_data['event'] == 'updated':
130131
if webhook_json_data['data']['status']['value'] == 'offline':
132+
if (webhook_json_data['data']['status']['value'] != webhook_json_data['snapshots']['prechange']['status']) and (webhook_json_data['data']['custom_fields']['proxmox_node'] == webhook_json_data['snapshots']['prechange']['custom_fields']['proxmox_node']):
133+
results = tc.proxmox_stop_vm(webhook_json_data)
131134

132-
# if source node != target node -> migrate
135+
if webhook_json_data['data']['custom_fields']['proxmox_node'] != webhook_json_data['snapshots']['prechange']['custom_fields']['proxmox_node']:
136+
proxmox_vmid = int(webhook_json_data['data']['custom_fields']['proxmox_vmid'])
137+
source_node = webhook_json_data['snapshots']['prechange']['custom_fields']['proxmox_node']
138+
target_node = webhook_json_data['data']['custom_fields']['proxmox_node']
139+
140+
pxmx_migrate = NetBoxProxmoxHelperMigrate(app_config, None, DEBUG)
141+
142+
results = pxmx_migrate.migrate_vm(proxmox_vmid, source_node, target_node)
133143

134-
results = tc.proxmox_stop_vm(webhook_json_data)
135144
elif webhook_json_data['data']['status']['value'] == 'active':
145+
if (webhook_json_data['data']['status']['value'] != webhook_json_data['snapshots']['prechange']['status']) and (webhook_json_data['data']['custom_fields']['proxmox_node'] == webhook_json_data['snapshots']['prechange']['custom_fields']['proxmox_node']):
146+
results = tc.proxmox_start_vm(webhook_json_data)
147+
148+
if webhook_json_data['data']['custom_fields']['proxmox_node'] != webhook_json_data['snapshots']['prechange']['custom_fields']['proxmox_node']:
149+
proxmox_vmid = int(webhook_json_data['data']['custom_fields']['proxmox_vmid'])
150+
source_node = webhook_json_data['snapshots']['prechange']['custom_fields']['proxmox_node']
151+
target_node = webhook_json_data['data']['custom_fields']['proxmox_node']
136152

137-
# if source node != target node -> migrate
153+
pxmx_migrate = NetBoxProxmoxHelperMigrate(app_config, None, DEBUG)
138154

139-
results = tc.proxmox_start_vm(webhook_json_data)
155+
results = pxmx_migrate.migrate_vm(proxmox_vmid, source_node, target_node)
140156
else:
141157
results = (500, {'result': f"Unknown value {webhook_json_data['data']['status']['value']}"})
142158
elif webhook_json_data['event'] == 'deleted':

netbox-event-driven-automation-flask-app/helpers/netbox_proxmox.py

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import os
12
import pynetbox
23
import re
34
import requests
@@ -173,7 +174,7 @@ def proxmox_clone_vm(self, json_in):
173174
try:
174175
for required_netbox_object in ['proxmox_vm_templates', 'proxmox_vm_storage']:
175176
if not required_netbox_object in json_in['data']['custom_fields']:
176-
raise ValueError(f"Missing {required_netbox_object} in VM configuration")
177+
return 500, {'result': f"Missing {required_netbox_object} in VM configuration"}
177178

178179
netbox_collected_vms = {}
179180

@@ -213,7 +214,8 @@ def proxmox_clone_vm(self, json_in):
213214
newid=new_vm_id,
214215
full=1,
215216
name=json_in['data']['name'],
216-
storage=json_in['data']['custom_fields']['proxmox_vm_storage']
217+
storage=json_in['data']['custom_fields']['proxmox_vm_storage'],
218+
target=json_in['data']['custom_fields']['proxmox_node']
217219
)
218220

219221
self.proxmox_job_get_status(clone_data)
@@ -271,7 +273,7 @@ def proxmox_start_vm(self, json_in):
271273
try:
272274
self.json_data_check_proxmox_vmid_exists(json_in)
273275

274-
start_data = self.proxmox_api.nodes(self.proxmox_api_config['node']).qemu(json_in['data']['custom_fields']['proxmox_vmid']).status.start.post()
276+
start_data = self.proxmox_api.nodes(json_in['data']['custom_fields']['proxmox_node']).qemu(json_in['data']['custom_fields']['proxmox_vmid']).status.start.post()
275277

276278
self.proxmox_job_get_status(start_data)
277279

@@ -284,7 +286,7 @@ def proxmox_stop_vm(self, json_in):
284286
try:
285287
self.json_data_check_proxmox_vmid_exists(json_in)
286288

287-
stop_data = self.proxmox_api.nodes(self.proxmox_api_config['node']).qemu(json_in['data']['custom_fields']['proxmox_vmid']).status.stop.post()
289+
stop_data = self.proxmox_api.nodes(json_in['data']['custom_fields']['proxmox_node']).qemu(json_in['data']['custom_fields']['proxmox_vmid']).status.stop.post()
288290

289291
self.proxmox_job_get_status(stop_data)
290292

@@ -299,7 +301,7 @@ def proxmox_delete_vm(self, json_in):
299301

300302
self.proxmox_stop_vm(json_in)
301303

302-
delete_data = self.proxmox_api.nodes(self.proxmox_api_config['node']).qemu(json_in['data']['custom_fields']['proxmox_vmid']).delete()
304+
delete_data = self.proxmox_api.nodes(json_in['data']['custom_fields']['proxmox_node']).qemu(json_in['data']['custom_fields']['proxmox_vmid']).delete()
303305

304306
self.proxmox_job_get_status(delete_data)
305307

@@ -315,7 +317,7 @@ def proxmox_set_ipconfig0(self, json_in):
315317
primary_ip = json_in['data']['primary_ip']['address']
316318
gateway = self.generate_gateway_from_ip_address(primary_ip)
317319

318-
create_ipconfig0 = self.proxmox_api.nodes(self.proxmox_api_config['node']).qemu(json_in['data']['custom_fields']['proxmox_vmid']).config.post(
320+
create_ipconfig0 = self.proxmox_api.nodes(json_in['data']['custom_fields']['proxmox_node']).qemu(json_in['data']['custom_fields']['proxmox_vmid']).config.post(
319321
ipconfig0=f"ip={primary_ip},gw={gateway}"
320322
)
321323

@@ -332,7 +334,7 @@ def proxmox_set_ssh_public_key(self, json_in):
332334

333335
proxmox_public_ssh_key = urllib.parse.quote(json_in['data']['custom_fields']['proxmox_public_ssh_key'].rstrip(), safe='')
334336

335-
create_ssh_public_key = self.proxmox_api.nodes(self.proxmox_api_config['node']).qemu(json_in['data']['custom_fields']['proxmox_vmid']).config.post(
337+
create_ssh_public_key = self.proxmox_api.nodes(json_in['data']['custom_fields']['proxmox_node']).qemu(json_in['data']['custom_fields']['proxmox_vmid']).config.post(
336338
sshkeys=f"{proxmox_public_ssh_key}"
337339
)
338340

@@ -357,7 +359,7 @@ def proxmox_add_disk(self, json_in):
357359
f"{json_in['data']['name']}": f"{json_in['data']['custom_fields']['proxmox_disk_storage_volume']}:{int(json_in['data']['size'])/1000},backup=0,ssd=0"
358360
}
359361

360-
add_disk_data = self.proxmox_api.nodes(self.proxmox_api_config['node']).qemu(proxmox_vmid).config.post(
362+
add_disk_data = self.proxmox_api.nodes(json_in['data']['custom_fields']['proxmox_node']).qemu(proxmox_vmid).config.post(
361363
**config_data
362364
)
363365

@@ -374,7 +376,7 @@ def proxmox_resize_disk(self, json_in):
374376
try:
375377
proxmox_vmid = self.netbox_get_proxmox_vmid(json_in['data']['virtual_machine']['id'])
376378

377-
disk_resize_info = self.proxmox_api.nodes(self.proxmox_api_config['node']).qemu(proxmox_vmid).resize.put(
379+
disk_resize_info = self.proxmox_api.nodes(json_in['data']['custom_fields']['proxmox_node']).qemu(proxmox_vmid).resize.put(
378380
disk=json_in['data']['name'],
379381
size=f"{int(json_in['data']['size'])/1000}G"
380382
)
@@ -393,17 +395,17 @@ def proxmox_delete_disk(self, json_in):
393395

394396
proxmox_vmid = self.netbox_get_proxmox_vmid(json_in['data']['virtual_machine']['id'])
395397

396-
self.proxmox_api.nodes(self.proxmox_api_config['node']).qemu(proxmox_vmid).unlink.put(idlist=json_in['data']['name'], force=1)
398+
self.proxmox_api.nodes(json_in['data']['custom_fields']['proxmox_node']).qemu(proxmox_vmid).unlink.put(idlist=json_in['data']['name'], force=1)
397399

398400
return 200, {'result': f"Disk {json_in['data']['name']} for VM {json_in['data']['virtual_machine']['id']} deleted successfully"}
399401
except ResourceException as e:
400402
return 500, {'result': e.content}
401403

402404

403405
class NetBoxProxmoxHelperLXC(NetBoxProxmoxHelper):
404-
def __proxmox_update_lxc_vcpus_and_memory(self, vmid=1, vcpus=1, memory=512):
406+
def __proxmox_update_lxc_vcpus_and_memory(self, proxmox_node=None, vmid=1, vcpus=1, memory=512):
405407
try:
406-
update_lxc_vcpus = self.proxmox_api.nodes(self.proxmox_api_config['node']).lxc(vmid).config.put(
408+
update_lxc_vcpus = self.proxmox_api.nodes(proxmox_node).lxc(vmid).config.put(
407409
cores=int(float(vcpus)),
408410
memory=int(memory)
409411
)
@@ -424,7 +426,7 @@ def proxmox_create_lxc(self, json_in):
424426
print("JSON IN", json_in['data'])
425427

426428
if 'data' in json_in and 'custom_fields' in json_in['data'] and 'proxmox_vmid' in json_in['data']['custom_fields'] and json_in['data']['custom_fields']['proxmox_vmid']:
427-
self.proxmox_api.nodes(self.proxmox_api_config['node']).qemu(json_in['data']['custom_fields']['proxmox_vmid']).config.get()
429+
self.proxmox_api.nodes(json_in['data']['custom_fields']['proxmox_node']).qemu(json_in['data']['custom_fields']['proxmox_vmid']).config.get()
428430
else:
429431
new_vm_id = self.proxmox_api.cluster.get('nextid')
430432
except ResourceException as e:
@@ -460,7 +462,7 @@ def proxmox_create_lxc(self, json_in):
460462
if self.debug:
461463
print("LXC CREATE DATA", lxc_create_data, new_vm_id)
462464

463-
create_lxc_data = self.proxmox_api.nodes(self.proxmox_api_config['node']).lxc.create(**lxc_create_data)
465+
create_lxc_data = self.proxmox_api.nodes(json_in['data']['custom_fields']['proxmox_node']).lxc.create(**lxc_create_data)
464466

465467
self.proxmox_job_get_status(create_lxc_data)
466468

@@ -482,7 +484,7 @@ def proxmox_create_lxc(self, json_in):
482484
'rootfs': 'local-lvm:vm-104-disk-0,size=4G'}
483485
"""
484486

485-
lxc_config_info = self.proxmox_api.nodes(self.proxmox_api_config['node']).lxc(new_vm_id).config.get()
487+
lxc_config_info = self.proxmox_api.nodes(json_in['data']['custom_fields']['proxmox_node']).lxc(new_vm_id).config.get()
486488

487489
if self.debug:
488490
print("LXC CONFIG INFO", lxc_config_info)
@@ -506,7 +508,7 @@ def proxmox_update_lxc_vpus_and_memory(self, json_in):
506508

507509
# update VM vcpus and/or memory if defined
508510
if json_in['data']['custom_fields']['proxmox_vmid'] and json_in['snapshots']['postchange']['vcpus'] and json_in['snapshots']['postchange']['memory']:
509-
return self.__proxmox_update_lxc_vcpus_and_memory(json_in['data']['custom_fields']['proxmox_vmid'], json_in['snapshots']['postchange']['vcpus'], json_in['snapshots']['postchange']['memory'])
511+
return self.__proxmox_update_lxc_vcpus_and_memory(json_in['data']['custom_fields']['proxmox_node'], json_in['data']['custom_fields']['proxmox_vmid'], json_in['snapshots']['postchange']['vcpus'], json_in['snapshots']['postchange']['memory'])
510512

511513
return 500, {'result': f"Unable to set vcpus ({json_in['data']['vcpus']}) and/or memory ({json_in['data']['memory']}) for LXC (vmid: {json_in['data']['custom_fields']['proxmox_vmid']})"}
512514

@@ -518,7 +520,7 @@ def proxmox_lxc_set_net0(self, json_in):
518520
primary_ip = json_in['data']['primary_ip']['address']
519521
gateway = self.generate_gateway_from_ip_address(primary_ip)
520522

521-
self.proxmox_api.nodes(self.proxmox_api_config['node']).lxc(json_in['data']['custom_fields']['proxmox_vmid']).config.put(
523+
self.proxmox_api.nodes(json_in['data']['custom_fields']['proxmox_node']).lxc(json_in['data']['custom_fields']['proxmox_vmid']).config.put(
522524
net0=f"name=net0,bridge=vmbr0,ip={primary_ip},gw={gateway},firewall=1"
523525
)
524526

@@ -542,7 +544,7 @@ def proxmox_lxc_resize_disk(self, json_in):
542544
'size': disk_size
543545
}
544546

545-
disk_resize_info = self.proxmox_api.nodes(self.proxmox_api_config['node']).lxc(proxmox_vmid).resize.put(**lxc_disk_size_info)
547+
disk_resize_info = self.proxmox_api.nodes(json_in['data']['custom_fields']['proxmox_node']).lxc(proxmox_vmid).resize.put(**lxc_disk_size_info)
546548

547549
self.proxmox_job_get_status(disk_resize_info)
548550

@@ -555,7 +557,7 @@ def proxmox_start_lxc(self, json_in):
555557
try:
556558
self.json_data_check_proxmox_vmid_exists(json_in)
557559

558-
start_data = self.proxmox_api.nodes(self.proxmox_api_config['node']).lxc(json_in['data']['custom_fields']['proxmox_vmid']).status.start.post()
560+
start_data = self.proxmox_api.nodes(json_in['data']['custom_fields']['proxmox_node']).lxc(json_in['data']['custom_fields']['proxmox_vmid']).status.start.post()
559561

560562
self.proxmox_job_get_status(start_data)
561563

@@ -568,7 +570,7 @@ def proxmox_stop_lxc(self, json_in):
568570
try:
569571
self.json_data_check_proxmox_vmid_exists(json_in)
570572

571-
stop_data = self.proxmox_api.nodes(self.proxmox_api_config['node']).lxc(json_in['data']['custom_fields']['proxmox_vmid']).status.stop.post()
573+
stop_data = self.proxmox_api.nodes(json_in['data']['custom_fields']['proxmox_node']).lxc(json_in['data']['custom_fields']['proxmox_vmid']).status.stop.post()
572574

573575
self.proxmox_job_get_status(stop_data)
574576

@@ -583,7 +585,7 @@ def proxmox_delete_lxc(self, json_in):
583585

584586
self.proxmox_stop_lxc(json_in)
585587

586-
delete_data = self.proxmox_api.nodes(self.proxmox_api_config['node']).lxc.delete(json_in['data']['custom_fields']['proxmox_vmid'])
588+
delete_data = self.proxmox_api.nodes(json_in['data']['custom_fields']['proxmox_node']).lxc.delete(json_in['data']['custom_fields']['proxmox_vmid'])
587589

588590
self.proxmox_job_get_status(delete_data)
589591

@@ -593,7 +595,9 @@ def proxmox_delete_lxc(self, json_in):
593595

594596

595597
class NetBoxProxmoxHelperMigrate(NetBoxProxmoxHelper):
596-
def __init__(self):
598+
def __init__(self, cfg_data, proxmox_node, debug=False):
599+
super().__init__(cfg_data, proxmox_node, debug)
600+
597601
self.proxmox_cluster_name = 'default-proxmox-cluster-name'
598602
self.proxmox_nodes = {}
599603
self.proxmox_vms = {}
@@ -682,13 +686,13 @@ def __wait_for_migration_task(self, proxmox_node: str, proxmox_task_id: int):
682686
elapsed_seconds = current_time - start_time
683687

684688
if elapsed_seconds >= 600: # 10 minutes
685-
raise ValueError(f"Unable to complete task {proxmox_task_id} in defined time")
689+
return 500, {'content': f"Unable to complete task {proxmox_task_id} in defined time"}
686690

687691
task_status = self.proxmox_api.nodes(proxmox_node).tasks(proxmox_task_id).status.get()
688692

689693
if task_status['status'] == 'stopped':
690694
if 'exitstatus' in task_status and task_status['exitstatus'] == 'OK':
691-
break
695+
return 200, {'result': "Proxmox node migration successful"}
692696
else:
693697
return 500, {'result': f"Task {proxmox_task_id} is stopped but exit status does not appear to be successful: {task_status['exit_status']}"}
694698
except ResourceException as e:
@@ -708,9 +712,7 @@ def migrate_vm(self, proxmox_vmid: int, proxmox_node: str, proxmox_target_node:
708712

709713
try:
710714
migrate_vm_task_id = self.proxmox_api.nodes(proxmox_node).qemu(proxmox_vmid).migrate.post(**migrate_vm_data)
711-
self.__wait_for_migration_task(proxmox_node, migrate_vm_task_id)
712-
713-
return 200, {'result': f"VM (vmid: {proxmox_vmid}) has been migrated to node {proxmox_target_node}"}
715+
return self.__wait_for_migration_task(proxmox_node, migrate_vm_task_id)
714716
except ResourceException as e:
715717
return 500, {'result': f"Proxmox API error: {e}"}
716718
except requests.exceptions.ConnectionError:

0 commit comments

Comments
 (0)