Skip to content

Commit b011402

Browse files
ernjvrrohityadavcloud
authored andcommitted
FR201: KVM hook script include (#5)
* 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 * add timeout functionality, action validation, pass global parameters from main * add slash seperator, filter out empty and unconventional script names, remove global variables parameters, remove zero length check and related log
1 parent d003125 commit b011402

File tree

1 file changed

+106
-13
lines changed

1 file changed

+106
-13
lines changed

agent/bindir/libvirtqemuhook.in

Lines changed: 106 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,59 +6,152 @@
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-
#
9+
#
1010
# http://www.apache.org/licenses/LICENSE-2.0
11-
#
11+
#
1212
# Unless required by applicable law or agreed to in writing,
1313
# software distributed under the License is distributed on an
1414
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
1515
# KIND, either express or implied. See the License for the
1616
# specific language governing permissions and limitations
1717
# under the License.
18-
import sys
18+
19+
import logging
1920
import re
21+
import sys
22+
import os
23+
import subprocess
24+
from threading import Timer
2025
from xml.dom.minidom import parse
2126
from cloudutils.configFileOps import configFileOps
2227
from cloudutils.networkConfig import networkConfig
28+
29+
logging.basicConfig(filename='/var/log/libvirt/qemu-hook.log',
30+
filemode='a',
31+
format='%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s',
32+
datefmt='%H:%M:%S',
33+
level=logging.INFO)
34+
logger = logging.getLogger('qemu-hook')
35+
36+
customDir = "/etc/libvirt/hooks/custom"
37+
customDirPermissions = 0744
38+
timeoutSeconds = 10 * 60
39+
validQemuActions = ['prepare', 'start', 'started', 'stopped', 'release', 'migrate', 'restore', 'reconnect', 'attach']
40+
2341
def isOldStyleBridge(brName):
2442
if brName.find("cloudVirBr") == 0:
25-
return True
43+
return True
2644
else:
27-
return False
45+
return False
46+
2847
def isNewStyleBridge(brName):
2948
if brName.startswith('brvx-'):
3049
return False
3150
if re.match(r"br(\w+)-(\d+)", brName) == None:
32-
return False
51+
return False
3352
else:
34-
return True
53+
return True
54+
3555
def getGuestNetworkDevice():
36-
netlib = networkConfig()
56+
netlib = networkConfig()
3757
cfo = configFileOps("/etc/cloudstack/agent/agent.properties")
3858
guestDev = cfo.getEntry("guest.network.device")
3959
enslavedDev = netlib.getEnslavedDev(guestDev, 1)
4060
return enslavedDev.split(".")[0]
61+
4162
def handleMigrateBegin():
4263
try:
4364
domain = parse(sys.stdin)
4465
for interface in domain.getElementsByTagName("interface"):
4566
source = interface.getElementsByTagName("source")[0]
4667
bridge = source.getAttribute("bridge")
4768
if isOldStyleBridge(bridge):
48-
vlanId = bridge.replace("cloudVirBr","")
69+
vlanId = bridge.replace("cloudVirBr", "")
4970
elif isNewStyleBridge(bridge):
50-
vlanId = re.sub(r"br(\w+)-","",bridge)
71+
vlanId = re.sub(r"br(\w+)-", "", bridge)
5172
else:
5273
continue
5374
phyDev = getGuestNetworkDevice()
54-
newBrName="br" + phyDev + "-" + vlanId
75+
newBrName = "br" + phyDev + "-" + vlanId
5576
source.setAttribute("bridge", newBrName)
5677
print(domain.toxml())
5778
except:
5879
pass
80+
81+
82+
def executeCustomScripts(sysArgs):
83+
createDirectoryIfNotExists(customDir, customDirPermissions)
84+
scripts = getCustomScriptsFromDirectory()
85+
86+
for scriptName in scripts:
87+
executeScript(scriptName, sysArgs)
88+
89+
90+
def executeScript(scriptName, sysArgs):
91+
logger.info('Executing custom script: %s, parameters: %s' % (scriptName, ' '.join(map(str, sysArgs))))
92+
path = customDir + os.path.sep + scriptName
93+
94+
if not os.access(path, os.X_OK):
95+
logger.warning('Custom script: %s is not executable; skipping execution.' % scriptName)
96+
return
97+
98+
try:
99+
process = subprocess.Popen([path] + sysArgs, stdout=subprocess.PIPE,
100+
stderr=subprocess.PIPE, shell=False)
101+
try:
102+
timer = Timer(timeoutSeconds, terminateProcess, [process, scriptName])
103+
timer.start()
104+
output, error = process.communicate()
105+
106+
if process.returncode == -15:
107+
logger.error('Custom script: %s terminated after timeout of %s second[s].'
108+
% (scriptName, timeoutSeconds))
109+
return
110+
if process.returncode != 0:
111+
logger.info('return code: %s' % str(process.returncode))
112+
raise Exception(error)
113+
logger.info('Custom script: %s finished successfully; output: \n%s' %
114+
(scriptName, str(output)))
115+
finally:
116+
timer.cancel()
117+
except (OSError, Exception) as e:
118+
logger.exception("Custom script: %s finished with error: \n%s" % (scriptName, e))
119+
120+
121+
def terminateProcess(process, scriptName):
122+
logger.warning('Custom script: %s taking longer than %s second[s]; terminating..' % (scriptName, str(timeoutSeconds)))
123+
process.terminate()
124+
125+
126+
def getCustomScriptsFromDirectory():
127+
return sorted(filter(lambda fileName: (fileName is not None) & (fileName != "") & ('_' in fileName) &
128+
(fileName.startswith((action + '_')) | fileName.startswith(('all' + '_'))),
129+
os.listdir(customDir)), key=lambda fileName: substringAfter(fileName, '_'))
130+
131+
132+
def createDirectoryIfNotExists(dir, permissions):
133+
if not os.path.exists(dir):
134+
logger.info('Directory %s does not exist; creating it.' % dir)
135+
os.makedirs(dir, permissions)
136+
137+
138+
def substringAfter(s, delimiter):
139+
return s.partition(delimiter)[2]
140+
141+
59142
if __name__ == '__main__':
60143
if len(sys.argv) != 5:
61144
sys.exit(0)
62145

63-
if sys.argv[2] == "migrate" and sys.argv[3] == "begin":
64-
handleMigrateBegin()
146+
# For docs refer https://libvirt.org/hooks.html#qemu
147+
logger.debug("Executing qemu hook with args: %s" % sys.argv)
148+
action, status = sys.argv[2:4]
149+
150+
if action not in validQemuActions:
151+
logger.error('The given action: %s, is not a valid libvirt qemu operation.' % action)
152+
sys.exit(0)
153+
154+
if action == "migrate" and status == "begin":
155+
handleMigrateBegin()
156+
157+
executeCustomScripts(sys.argv[1:])

0 commit comments

Comments
 (0)