Skip to content

Commit 13779dd

Browse files
ernjvryadvr
authored andcommitted
kvm: add support for custom KVM hook scripts (#2819)
KVM hook script include - logic to execute custom scripts & logging requirements KVM hook script include - add logic to create custom directory if not exists & extra logging
1 parent e9003fa commit 13779dd

1 file changed

Lines changed: 78 additions & 6 deletions

File tree

agent/bindir/libvirtqemuhook.in

Lines changed: 78 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@
66
# to you under the Apache License, Version 2.0 (the
77
# "License"); you may not use this file except in compliance
88
# with the License. You may obtain a copy of the License at
9-
#
109
# http://www.apache.org/licenses/LICENSE-2.0
11-
#
1210
# Unless required by applicable law or agreed to in writing,
1311
# software distributed under the License is distributed on an
1412
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -19,6 +17,9 @@
1917
import logging
2018
import re
2119
import sys
20+
import os
21+
import subprocess
22+
from threading import Timer
2223
from xml.dom.minidom import parse
2324
from cloudutils.configFileOps import configFileOps
2425
from cloudutils.networkConfig import networkConfig
@@ -30,19 +31,24 @@ logging.basicConfig(filename='/var/log/libvirt/qemu-hook.log',
3031
level=logging.INFO)
3132
logger = logging.getLogger('qemu-hook')
3233

34+
customDir = "/etc/libvirt/hooks/custom"
35+
customDirPermissions = 0744
36+
timeoutSeconds = 10 * 60
37+
validQemuActions = ['prepare', 'start', 'started', 'stopped', 'release', 'migrate', 'restore', 'reconnect', 'attach']
38+
3339
def isOldStyleBridge(brName):
3440
if brName.find("cloudVirBr") == 0:
35-
return True
41+
return True
3642
else:
37-
return False
43+
return False
3844

3945
def isNewStyleBridge(brName):
4046
if brName.startswith('brvx-'):
4147
return False
4248
if re.match(r"br(\w+)-(\d+)", brName) == None:
43-
return False
49+
return False
4450
else:
45-
return True
51+
return True
4652

4753
def getGuestNetworkDevice():
4854
netlib = networkConfig()
@@ -71,6 +77,66 @@ def handleMigrateBegin():
7177
pass
7278

7379

80+
def executeCustomScripts(sysArgs):
81+
createDirectoryIfNotExists(customDir, customDirPermissions)
82+
scripts = getCustomScriptsFromDirectory()
83+
84+
for scriptName in scripts:
85+
executeScript(scriptName, sysArgs)
86+
87+
88+
def executeScript(scriptName, sysArgs):
89+
logger.info('Executing custom script: %s, parameters: %s' % (scriptName, ' '.join(map(str, sysArgs))))
90+
path = customDir + os.path.sep + scriptName
91+
92+
if not os.access(path, os.X_OK):
93+
logger.warning('Custom script: %s is not executable; skipping execution.' % scriptName)
94+
return
95+
96+
try:
97+
process = subprocess.Popen([path] + sysArgs, stdout=subprocess.PIPE,
98+
stderr=subprocess.PIPE, shell=False)
99+
try:
100+
timer = Timer(timeoutSeconds, terminateProcess, [process, scriptName])
101+
timer.start()
102+
output, error = process.communicate()
103+
104+
if process.returncode == -15:
105+
logger.error('Custom script: %s terminated after timeout of %s second[s].'
106+
% (scriptName, timeoutSeconds))
107+
return
108+
if process.returncode != 0:
109+
logger.info('return code: %s' % str(process.returncode))
110+
raise Exception(error)
111+
logger.info('Custom script: %s finished successfully; output: \n%s' %
112+
(scriptName, str(output)))
113+
finally:
114+
timer.cancel()
115+
except (OSError, Exception) as e:
116+
logger.exception("Custom script: %s finished with error: \n%s" % (scriptName, e))
117+
118+
119+
def terminateProcess(process, scriptName):
120+
logger.warning('Custom script: %s taking longer than %s second[s]; terminating..' % (scriptName, str(timeoutSeconds)))
121+
process.terminate()
122+
123+
124+
def getCustomScriptsFromDirectory():
125+
return sorted(filter(lambda fileName: (fileName is not None) & (fileName != "") & ('_' in fileName) &
126+
(fileName.startswith((action + '_')) | fileName.startswith(('all' + '_'))),
127+
os.listdir(customDir)), key=lambda fileName: substringAfter(fileName, '_'))
128+
129+
130+
def createDirectoryIfNotExists(dir, permissions):
131+
if not os.path.exists(dir):
132+
logger.info('Directory %s does not exist; creating it.' % dir)
133+
os.makedirs(dir, permissions)
134+
135+
136+
def substringAfter(s, delimiter):
137+
return s.partition(delimiter)[2]
138+
139+
74140
if __name__ == '__main__':
75141
if len(sys.argv) != 5:
76142
sys.exit(0)
@@ -79,5 +145,11 @@ if __name__ == '__main__':
79145
logger.debug("Executing qemu hook with args: %s" % sys.argv)
80146
action, status = sys.argv[2:4]
81147

148+
if action not in validQemuActions:
149+
logger.error('The given action: %s, is not a valid libvirt qemu operation.' % action)
150+
sys.exit(0)
151+
82152
if action == "migrate" and status == "begin":
83153
handleMigrateBegin()
154+
155+
executeCustomScripts(sys.argv[1:])

0 commit comments

Comments
 (0)