Hello,

I'm writing some code to make the following workflow:

Here is the code (I'm not a python expert)

#!/usr/libexec/platform-python
# 211217 NBT
# This is a vdsm hook that aims to auto delete oVirt dependencies when removing a VM directly from engine.
# It is triggered when filling the stop_reason field in oVirt with strict 'clean'.
# It initially concerns following actions:
# - Centreon subscription Removing
# - Backup erase from Sotora
# - DNS cleaning on Lilas
# - IPA deletion
# This set of actions can be extended into the concerned AWX job_template (180 or 160)
# When finished, vm can be manually removed from Engine.
# When string is 'force_delete' or 'force_remove', then in addition, the vm will be automatically erased at the same time.
import os
from vdsm.hook import hooking
from xml.dom import minidom
import requests
from xml.etree import ElementTree
import sys
import urllib3
import time
import traceback
import subprocess
from subprocess import PIPE, STDOUT
import logging
urllib3.disable_warnings()
logger = logging.getLogger("register_migration")
def exec_cmd(*args):
retcode, out, err = hooking.execCmd(args, sudo=True)
if retcode != 0:
raise RuntimeError("Failed to execute %s, due to: %s" %
(args, err))
return out
if __name__ == '__main__':
logging.basicConfig(filename="/var/log/vdsm/custom_hooks.log",
level=logging.INFO, format='%(asctime)s %(levelname)s %(name)s:%(message)s',
datefmt = '%Y-%m-%d %H:%M:%S')
if len(sys.argv) > 1:
vm_name= sys.argv[1]
else:
domxml = hooking.read_domxml()
vm_name = domxml.getElementsByTagName('name')[0].firstChild.nodeValue
print(vm_name)
# API oVirt: Initialize variables
user = 'admin@internal'
password = 'password'
url = "https://air-dev.v100.abes.fr/ovirt-engine/api/vms?search=name%3D" + vm_name
headers = {'Accept': 'application/xml'}
print('name: ' + vm_name)
# API oVirt: Test if VM stop_reason has been defined
while True:
# r = requests.get(url, headers=headers, auth=('admin@internal', 'password'), verify=False)
# tree = ElementTree.fromstring(r.content)
r = exec_cmd('curl', '--insecure', '--header', 'Accept: application/xml', '--user', 'admin@internal:password', 'https://air-dev.v100.abes.fr/ovirt-engine/api/vms?search=name%3D' + vm_name)
tree = ElementTree.fromstring(b''.join(r))
for vm in tree.findall('vm'):
status = vm.find('status')
stop_reason = vm.find('stop_reason')
print(status.text)
if stop_reason is not None:
print(status.text, stop_reason.text)
break
time.sleep(1)
for vm in tree.findall('vm'):
stop_reason = vm.find('stop_reason')
if stop_reason is None:
exit('stop_reason is not defined')
else:
# API AWX: Initialize variables
header1 = 'Content-Type: application/json'
header2 = 'Authorization: Bearer token'
curl_server = "nbt"
curl_extra_vars = "{\\\"comment\\\": \\\"Nbt\\\", \\\"survey_ovirt_password\\\": \\\"password\\\", \\\"force_erase\\\": \\\"yes\\\", \\\"survey_vms_list\\\": %s}" % (vm_name)
curl_config = '{"extra_vars": "%s"}' % (curl_extra_vars)
if stop_reason in ["clean"]:
curl_job_template = "180"
print('Cleaning' + vm_name + 'from oVirt on ancolie-' + curl_server + 'with workflow_job_template ' + curl_job_template)
curl_url = "http://ancolie-{}.v106.abes.fr/api/v2/workflow_job_templates/{}/launch/".format(curl_server,curl_job_template)
exec_cmd('curl', '-f', '-H', header1, '-H', header2, '-XPOST', '-d', curl_config, curl_url)
elif stop_reason in ["force_delete", "force_remove"]:
curl_job_template = "160"
print('Deleting and cleaning' + vm_name + 'from oVirt on ancolie-' + curl_server + 'with workflow_job_template ' + curl_job_template )
curl_url = "http://ancolie-{}.v106.abes.fr/api/v2/workflow_job_templates/{}/launch/".format(curl_server,curl_job_template)
exec_cmd('curl', '-f', '-H', header1, '-H', header2, '-XPOST', '-d', curl_config, curl_url)
else:
exit('Stop reason is ' + stop_reason + ' and there is no reason to do anything for ' + vm_name)

The idea is to use the stop_reason element into the vm xml definition. But after hours, I realized that this element is writed to the vm definition file only after the VM has been destroyed.

So if I test this value (if existing) when executing the hook, the text value doesn't still exist at early time

I added a 'while' loop to wait for the stop_reason element to be present, but vdsm hangs out because of an infinity loop:

2021-12-20 18:13:30,148+0100 INFO  (jsonrpc/7) [root] /usr/libexec/vdsm/hooks/after_vm_destroy/clean_vm_dependencies_2.py: rc=1 err=b'Traceback (most recent call last):\n  File "/usr/libexec/vdsm/hooks/after_vm_destroy/clean_vm_dependencies_2.py", line 84, in <module>\n    print(status.text, stop_reason.text)\nAttributeError: \'NoneType\' object has no attribute \'text\'\n' (hooks:122)

....

So I'm deducing I'm not able to accomplish my initial goal to use stop_reason as a trigger with after_vm_destroy event.

I searched an other way to do: I thought of replacing querying ovirt API with getting the value coming from the UI, but I can't find the suitable database query. Is there a way to do such a thing? Does engine hooks exist for stopped vm??

Thank you for your help.

PS: I'm already able to do this from ansible/AWX, but I have to do it from UI/vdsm for any reason.

-- 
Nathanaël Blanchet

Supervision réseau
SIRE
227 avenue Professeur-Jean-Louis-Viala
34193 MONTPELLIER CEDEX 5 	
Tél. 33 (0)4 67 54 84 55
Fax  33 (0)4 67 54 84 14
blanchet@abes.fr