Coverage for drivers/util.py : 43%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Copyright (C) Citrix Systems Inc.
2#
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU Lesser General Public License as published
5# by the Free Software Foundation; version 2.1 only.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU Lesser General Public License for more details.
11#
12# You should have received a copy of the GNU Lesser General Public License
13# along with this program; if not, write to the Free Software Foundation, Inc.,
14# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15#
16# Miscellaneous utility functions
17#
19import os
20import re
21import sys
22import subprocess
23import shutil
24import tempfile
25import signal
26import time
27import datetime
28import errno
29import socket
30import xml.dom.minidom
31import scsiutil
32import stat
33import xs_errors
34import XenAPI # pylint: disable=import-error
35import xmlrpc.client
36import base64
37import syslog
38import resource
39import traceback
40import glob
41import copy
42import tempfile
44from functools import reduce
45from sm_typing import List, Optional
47NO_LOGGING_STAMPFILE = '/etc/xensource/no_sm_log'
49IORETRY_MAX = 20 # retries
50IORETRY_PERIOD = 1.0 # seconds
52LOGGING = not (os.path.exists(NO_LOGGING_STAMPFILE))
53_SM_SYSLOG_FACILITY = syslog.LOG_LOCAL2
54LOG_EMERG = syslog.LOG_EMERG
55LOG_ALERT = syslog.LOG_ALERT
56LOG_CRIT = syslog.LOG_CRIT
57LOG_ERR = syslog.LOG_ERR
58LOG_WARNING = syslog.LOG_WARNING
59LOG_NOTICE = syslog.LOG_NOTICE
60LOG_INFO = syslog.LOG_INFO
61LOG_DEBUG = syslog.LOG_DEBUG
63ISCSI_REFDIR = '/var/run/sr-ref'
65CMD_DD = "/bin/dd"
66CMD_KICKPIPE = '/opt/xensource/libexec/kickpipe'
68FIST_PAUSE_PERIOD = 30 # seconds
71class SMException(Exception):
72 """Base class for all SM exceptions for easier catching & wrapping in
73 XenError"""
76class CommandException(SMException):
77 def error_message(self, code):
78 if code > 0:
79 return os.strerror(code)
80 elif code < 0:
81 return "Signalled %s" % (abs(code))
82 return "Success"
84 def __init__(self, code, cmd="", reason='exec failed'):
85 self.code = code
86 self.cmd = cmd
87 self.reason = reason
88 Exception.__init__(self, self.error_message(code))
91class SRBusyException(SMException):
92 """The SR could not be locked"""
93 pass
96def logException(tag):
97 info = sys.exc_info()
98 if info[0] == SystemExit: 98 ↛ 100line 98 didn't jump to line 100, because the condition on line 98 was never true
99 # this should not be happening when catching "Exception", but it is
100 sys.exit(0)
101 tb = reduce(lambda a, b: "%s%s" % (a, b), traceback.format_tb(info[2]))
102 str = "***** %s: EXCEPTION %s, %s\n%s" % (tag, info[0], info[1], tb)
103 SMlog(str)
106def roundup(divisor, value):
107 """Retruns the rounded up value so it is divisible by divisor."""
109 if value == 0: 109 ↛ 110line 109 didn't jump to line 110, because the condition on line 109 was never true
110 value = 1
111 if value % divisor != 0:
112 return ((int(value) // divisor) + 1) * divisor
113 return value
116def to_plain_string(obj):
117 if obj is None:
118 return None
119 if isinstance(obj, dict) and len(obj) == 0:
120 SMlog(f"util.to_plain_string() corrected empty dict to empty str")
121 return ""
122 return str(obj)
125def shellquote(arg):
126 return '"%s"' % arg.replace('"', '\\"')
129def make_WWN(name):
130 hex_prefix = name.find("0x")
131 if (hex_prefix >= 0): 131 ↛ 134line 131 didn't jump to line 134, because the condition on line 131 was never false
132 name = name[name.find("0x") + 2:len(name)]
133 # inject dashes for each nibble
134 if (len(name) == 16): # sanity check 134 ↛ 138line 134 didn't jump to line 138, because the condition on line 134 was never false
135 name = name[0:2] + "-" + name[2:4] + "-" + name[4:6] + "-" + \
136 name[6:8] + "-" + name[8:10] + "-" + name[10:12] + "-" + \
137 name[12:14] + "-" + name[14:16]
138 return name
141def _logToSyslog(ident, facility, priority, message):
142 syslog.openlog(ident, 0, facility)
143 syslog.syslog(priority, "[%d] %s" % (os.getpid(), message))
144 syslog.closelog()
147def SMlog(message, ident="SM", priority=LOG_INFO):
148 if LOGGING: 148 ↛ exitline 148 didn't return from function 'SMlog', because the condition on line 148 was never false
149 for message_line in str(message).split('\n'):
150 _logToSyslog(ident, _SM_SYSLOG_FACILITY, priority, message_line)
153class LoggerCounter:
154 def __init__(self, max_repeats):
155 self.previous_message = None
156 self.max_repeats = max_repeats
157 self.repeat_counter = 0
159 def log(self, message):
160 self.repeat_counter += 1
161 if self.previous_message != message or self.repeat_counter == self.max_repeats:
162 SMlog(message)
163 self.previous_message = message
164 self.repeat_counter = 0
166def _getDateString():
167 d = datetime.datetime.now()
168 t = d.timetuple()
169 return "%s-%s-%s:%s:%s:%s" % \
170 (t[0], t[1], t[2], t[3], t[4], t[5])
173def doexec(args, inputtext=None, new_env=None, text=True):
174 """Execute a subprocess, then return its return code, stdout and stderr"""
175 env = None
176 if new_env:
177 env = dict(os.environ)
178 env.update(new_env)
179 proc = subprocess.Popen(args, stdin=subprocess.PIPE,
180 stdout=subprocess.PIPE,
181 stderr=subprocess.PIPE,
182 close_fds=True, env=env,
183 universal_newlines=text)
185 if not text and inputtext is not None: 185 ↛ 186line 185 didn't jump to line 186, because the condition on line 185 was never true
186 inputtext = inputtext.encode()
188 (stdout, stderr) = proc.communicate(inputtext)
190 rc = proc.returncode
191 return rc, stdout, stderr
194def is_string(value):
195 return isinstance(value, str)
198# These are partially tested functions that replicate the behaviour of
199# the original pread,pread2 and pread3 functions. Potentially these can
200# replace the original ones at some later date.
201#
202# cmdlist is a list of either single strings or pairs of strings. For
203# each pair, the first component is passed to exec while the second is
204# written to the logs.
205def pread(cmdlist, close_stdin=False, scramble=None, expect_rc=0,
206 quiet=False, new_env=None, text=True):
207 cmdlist_for_exec = []
208 cmdlist_for_log = []
209 for item in cmdlist:
210 if is_string(item): 210 ↛ 220line 210 didn't jump to line 220, because the condition on line 210 was never false
211 cmdlist_for_exec.append(item)
212 if scramble: 212 ↛ 213line 212 didn't jump to line 213, because the condition on line 212 was never true
213 if item.find(scramble) != -1:
214 cmdlist_for_log.append("<filtered out>")
215 else:
216 cmdlist_for_log.append(item)
217 else:
218 cmdlist_for_log.append(item)
219 else:
220 cmdlist_for_exec.append(item[0])
221 cmdlist_for_log.append(item[1])
223 if not quiet: 223 ↛ 225line 223 didn't jump to line 225, because the condition on line 223 was never false
224 SMlog(cmdlist_for_log)
225 (rc, stdout, stderr) = doexec(cmdlist_for_exec, new_env=new_env, text=text)
226 if rc != expect_rc:
227 SMlog("FAILED in util.pread: (rc %d) stdout: '%s', stderr: '%s'" % \
228 (rc, stdout, stderr))
229 if quiet: 229 ↛ 230line 229 didn't jump to line 230, because the condition on line 229 was never true
230 SMlog("Command was: %s" % cmdlist_for_log)
231 if '' == stderr: 231 ↛ 232line 231 didn't jump to line 232, because the condition on line 231 was never true
232 stderr = stdout
233 raise CommandException(rc, str(cmdlist), stderr.strip())
234 if not quiet: 234 ↛ 236line 234 didn't jump to line 236, because the condition on line 234 was never false
235 SMlog(" pread SUCCESS")
236 return stdout
239# POSIX guaranteed atomic within the same file system.
240# Supply directory to ensure tempfile is created
241# in the same directory.
242def atomicFileWrite(targetFile, directory, text):
244 file = None
245 try:
246 # Create file only current pid can write/read to
247 # our responsibility to clean it up.
248 _, tempPath = tempfile.mkstemp(dir=directory)
249 file = open(tempPath, 'w')
250 file.write(text)
252 # Ensure flushed to disk.
253 file.flush()
254 os.fsync(file.fileno())
255 file.close()
257 os.rename(tempPath, targetFile)
258 except OSError:
259 SMlog("FAILED to atomic write to %s" % (targetFile))
261 finally:
262 if (file is not None) and (not file.closed):
263 file.close()
265 if os.path.isfile(tempPath):
266 os.remove(tempPath)
269#Read STDOUT from cmdlist and discard STDERR output
270def pread2(cmdlist, quiet=False, text=True):
271 return pread(cmdlist, quiet=quiet, text=text)
274#Read STDOUT from cmdlist, feeding 'text' to STDIN
275def pread3(cmdlist, text):
276 SMlog(cmdlist)
277 (rc, stdout, stderr) = doexec(cmdlist, text)
278 if rc:
279 SMlog("FAILED in util.pread3: (errno %d) stdout: '%s', stderr: '%s'" % \
280 (rc, stdout, stderr))
281 if '' == stderr:
282 stderr = stdout
283 raise CommandException(rc, str(cmdlist), stderr.strip())
284 SMlog(" pread3 SUCCESS")
285 return stdout
288def listdir(path, quiet=False):
289 cmd = ["ls", path, "-1", "--color=never"]
290 try:
291 text = pread2(cmd, quiet=quiet)[:-1]
292 if len(text) == 0:
293 return []
294 return text.split('\n')
295 except CommandException as inst:
296 if inst.code == errno.ENOENT:
297 raise CommandException(errno.EIO, inst.cmd, inst.reason)
298 else:
299 raise CommandException(inst.code, inst.cmd, inst.reason)
302def gen_uuid():
303 cmd = ["uuidgen", "-r"]
304 return pread(cmd)[:-1]
307def match_uuid(s):
308 regex = re.compile("^[0-9a-f]{8}-(([0-9a-f]{4})-){3}[0-9a-f]{12}")
309 return regex.search(s, 0)
312def findall_uuid(s):
313 regex = re.compile("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}")
314 return regex.findall(s, 0)
317def exactmatch_uuid(s):
318 regex = re.compile("^[0-9a-f]{8}-(([0-9a-f]{4})-){3}[0-9a-f]{12}$")
319 return regex.search(s, 0)
322def start_log_entry(srpath, path, args):
323 logstring = str(datetime.datetime.now())
324 logstring += " log: "
325 logstring += srpath
326 logstring += " " + path
327 for element in args:
328 logstring += " " + element
329 try:
330 file = open(srpath + "/filelog.txt", "a")
331 file.write(logstring)
332 file.write("\n")
333 file.close()
334 except:
335 pass
337 # failed to write log ...
339def end_log_entry(srpath, path, args):
340 # for teminating, use "error" or "done"
341 logstring = str(datetime.datetime.now())
342 logstring += " end: "
343 logstring += srpath
344 logstring += " " + path
345 for element in args:
346 logstring += " " + element
347 try:
348 file = open(srpath + "/filelog.txt", "a")
349 file.write(logstring)
350 file.write("\n")
351 file.close()
352 except:
353 pass
355 # failed to write log ...
356 # for now print
357 # print "%s" % logstring
359def ioretry(f, errlist=[errno.EIO], maxretry=IORETRY_MAX, period=IORETRY_PERIOD, **ignored):
360 retries = 0
361 while True:
362 try:
363 return f()
364 except OSError as ose:
365 err = int(ose.errno)
366 if not err in errlist:
367 raise CommandException(err, str(f), "OSError")
368 except CommandException as ce:
369 if not int(ce.code) in errlist:
370 raise
372 retries += 1
373 if retries >= maxretry:
374 break
376 time.sleep(period)
378 raise CommandException(errno.ETIMEDOUT, str(f), "Timeout")
381def ioretry_stat(path, maxretry=IORETRY_MAX):
382 # this ioretry is similar to the previous method, but
383 # stat does not raise an error -- so check its return
384 retries = 0
385 while retries < maxretry:
386 stat = os.statvfs(path)
387 if stat.f_blocks != -1:
388 return stat
389 time.sleep(1)
390 retries += 1
391 raise CommandException(errno.EIO, "os.statvfs")
394def sr_get_capability(sr_uuid, session=None):
395 result = []
396 local_session = None
397 if session is None: 397 ↛ 401line 397 didn't jump to line 401, because the condition on line 397 was never false
398 local_session = get_localAPI_session()
399 session = local_session
401 try:
402 sr_ref = session.xenapi.SR.get_by_uuid(sr_uuid)
403 sm_type = session.xenapi.SR.get_record(sr_ref)['type']
404 sm_rec = session.xenapi.SM.get_all_records_where(
405 "field \"type\" = \"%s\"" % sm_type)
407 # SM expects at least one entry of any SR type
408 if len(sm_rec) > 0:
409 result = list(sm_rec.values())[0]['capabilities']
411 return result
412 finally:
413 if local_session: 413 ↛ exitline 413 didn't return from function 'sr_get_capability', because the return on line 411 wasn't executed
414 local_session.xenapi.session.logout()
416def sr_get_driver_info(driver_info):
417 results = {}
418 # first add in the vanilla stuff
419 for key in ['name', 'description', 'vendor', 'copyright', \
420 'driver_version', 'required_api_version']:
421 results[key] = driver_info[key]
422 # add the capabilities (xmlrpc array)
423 # enforcing activate/deactivate for blktap2
424 caps = driver_info['capabilities']
425 if "ATOMIC_PAUSE" in caps: 425 ↛ 426line 425 didn't jump to line 426, because the condition on line 425 was never true
426 for cap in ("VDI_ACTIVATE", "VDI_DEACTIVATE"):
427 if not cap in caps:
428 caps.append(cap)
429 elif "VDI_ACTIVATE" in caps or "VDI_DEACTIVATE" in caps: 429 ↛ 430line 429 didn't jump to line 430, because the condition on line 429 was never true
430 SMlog("Warning: vdi_[de]activate present for %s" % driver_info["name"])
432 results['capabilities'] = caps
433 # add in the configuration options
434 options = []
435 for option in driver_info['configuration']:
436 options.append({'key': option[0], 'description': option[1]})
437 results['configuration'] = options
438 return xmlrpc.client.dumps((results, ), "", True)
441def return_nil():
442 return xmlrpc.client.dumps((None, ), "", True, allow_none=True)
445def SRtoXML(SRlist):
446 dom = xml.dom.minidom.Document()
447 driver = dom.createElement("SRlist")
448 dom.appendChild(driver)
450 for key in SRlist.keys():
451 dict = SRlist[key]
452 entry = dom.createElement("SR")
453 driver.appendChild(entry)
455 e = dom.createElement("UUID")
456 entry.appendChild(e)
457 textnode = dom.createTextNode(key)
458 e.appendChild(textnode)
460 if 'size' in dict:
461 e = dom.createElement("Size")
462 entry.appendChild(e)
463 textnode = dom.createTextNode(str(dict['size']))
464 e.appendChild(textnode)
466 if 'storagepool' in dict:
467 e = dom.createElement("StoragePool")
468 entry.appendChild(e)
469 textnode = dom.createTextNode(str(dict['storagepool']))
470 e.appendChild(textnode)
472 if 'aggregate' in dict:
473 e = dom.createElement("Aggregate")
474 entry.appendChild(e)
475 textnode = dom.createTextNode(str(dict['aggregate']))
476 e.appendChild(textnode)
478 return dom.toprettyxml()
481def pathexists(path):
482 try:
483 os.lstat(path)
484 return True
485 except OSError as inst:
486 if inst.errno == errno.EIO: 486 ↛ 487line 486 didn't jump to line 487, because the condition on line 486 was never true
487 time.sleep(1)
488 try:
489 listdir(os.path.realpath(os.path.dirname(path)))
490 os.lstat(path)
491 return True
492 except:
493 pass
494 raise CommandException(errno.EIO, "os.lstat(%s)" % path, "failed")
495 return False
498def force_unlink(path):
499 try:
500 os.unlink(path)
501 except OSError as e:
502 if e.errno != errno.ENOENT: 502 ↛ 503line 502 didn't jump to line 503, because the condition on line 502 was never true
503 raise
506def create_secret(session, secret):
507 ref = session.xenapi.secret.create({'value': secret})
508 return session.xenapi.secret.get_uuid(ref)
511def get_secret(session, uuid):
512 try:
513 ref = session.xenapi.secret.get_by_uuid(uuid)
514 return session.xenapi.secret.get_value(ref)
515 except:
516 raise xs_errors.XenError('InvalidSecret', opterr='Unable to look up secret [%s]' % uuid)
519def get_real_path(path):
520 "Follow symlinks to the actual file"
521 absPath = path
522 directory = ''
523 while os.path.islink(absPath):
524 directory = os.path.dirname(absPath)
525 absPath = os.readlink(absPath)
526 absPath = os.path.join(directory, absPath)
527 return absPath
530def wait_for_path(path, timeout):
531 for i in range(0, timeout): 531 ↛ 535line 531 didn't jump to line 535, because the loop on line 531 didn't complete
532 if len(glob.glob(path)): 532 ↛ 534line 532 didn't jump to line 534, because the condition on line 532 was never false
533 return True
534 time.sleep(1)
535 return False
538def wait_for_nopath(path, timeout):
539 for i in range(0, timeout):
540 if not os.path.exists(path):
541 return True
542 time.sleep(1)
543 return False
546def wait_for_path_multi(path, timeout):
547 for i in range(0, timeout):
548 paths = glob.glob(path)
549 SMlog("_wait_for_paths_multi: paths = %s" % paths)
550 if len(paths):
551 SMlog("_wait_for_paths_multi: return first path: %s" % paths[0])
552 return paths[0]
553 time.sleep(1)
554 return ""
557def isdir(path):
558 try:
559 st = os.stat(path)
560 return stat.S_ISDIR(st.st_mode)
561 except OSError as inst:
562 if inst.errno == errno.EIO: 562 ↛ 563line 562 didn't jump to line 563, because the condition on line 562 was never true
563 raise CommandException(errno.EIO, "os.stat(%s)" % path, "failed")
564 return False
567def get_single_entry(path):
568 f = open(path, 'r')
569 line = f.readline()
570 f.close()
571 return line.rstrip()
574def get_fs_size(path):
575 st = ioretry_stat(path)
576 return st.f_blocks * st.f_frsize
579def get_fs_utilisation(path):
580 st = ioretry_stat(path)
581 return (st.f_blocks - st.f_bfree) * \
582 st.f_frsize
585def ismount(path):
586 """Test whether a path is a mount point"""
587 try:
588 s1 = os.stat(path)
589 s2 = os.stat(os.path.join(path, '..'))
590 except OSError as inst:
591 raise CommandException(inst.errno, "os.stat")
592 dev1 = s1.st_dev
593 dev2 = s2.st_dev
594 if dev1 != dev2:
595 return True # path/.. on a different device as path
596 ino1 = s1.st_ino
597 ino2 = s2.st_ino
598 if ino1 == ino2:
599 return True # path/.. is the same i-node as path
600 return False
603def makedirs(name, mode=0o777):
604 head, tail = os.path.split(name)
605 if not tail: 605 ↛ 606line 605 didn't jump to line 606, because the condition on line 605 was never true
606 head, tail = os.path.split(head)
607 if head and tail and not pathexists(head):
608 makedirs(head, mode)
609 if tail == os.curdir: 609 ↛ 610line 609 didn't jump to line 610, because the condition on line 609 was never true
610 return
611 try:
612 os.mkdir(name, mode)
613 except OSError as exc:
614 if exc.errno == errno.EEXIST and os.path.isdir(name): 614 ↛ 615line 614 didn't jump to line 615, because the condition on line 614 was never true
615 if mode:
616 os.chmod(name, mode)
617 pass
618 else:
619 raise
622def zeroOut(path, fromByte, bytes):
623 """write 'bytes' zeros to 'path' starting from fromByte (inclusive)"""
624 blockSize = 4096
626 fromBlock = fromByte // blockSize
627 if fromByte % blockSize:
628 fromBlock += 1
629 bytesBefore = fromBlock * blockSize - fromByte
630 if bytesBefore > bytes:
631 bytesBefore = bytes
632 bytes -= bytesBefore
633 cmd = [CMD_DD, "if=/dev/zero", "of=%s" % path, "bs=1",
634 "seek=%s" % fromByte, "count=%s" % bytesBefore]
635 try:
636 pread2(cmd)
637 except CommandException:
638 return False
640 blocks = bytes // blockSize
641 bytes -= blocks * blockSize
642 fromByte = (fromBlock + blocks) * blockSize
643 if blocks:
644 cmd = [CMD_DD, "if=/dev/zero", "of=%s" % path, "bs=%s" % blockSize,
645 "seek=%s" % fromBlock, "count=%s" % blocks]
646 try:
647 pread2(cmd)
648 except CommandException:
649 return False
651 if bytes:
652 cmd = [CMD_DD, "if=/dev/zero", "of=%s" % path, "bs=1",
653 "seek=%s" % fromByte, "count=%s" % bytes]
654 try:
655 pread2(cmd)
656 except CommandException:
657 return False
659 return True
662def wipefs(blockdev):
663 "Wipe filesystem signatures from `blockdev`"
664 pread2(["/usr/sbin/wipefs", "-a", blockdev])
667def match_rootdev(s):
668 regex = re.compile("^PRIMARY_DISK")
669 return regex.search(s, 0)
672def getrootdev():
673 filename = '/etc/xensource-inventory'
674 try:
675 f = open(filename, 'r')
676 except:
677 raise xs_errors.XenError('EIO', \
678 opterr="Unable to open inventory file [%s]" % filename)
679 rootdev = ''
680 for line in filter(match_rootdev, f.readlines()):
681 rootdev = line.split("'")[1]
682 if not rootdev: 682 ↛ 683line 682 didn't jump to line 683, because the condition on line 682 was never true
683 raise xs_errors.XenError('NoRootDev')
684 return rootdev
687def getrootdevID():
688 rootdev = getrootdev()
689 try:
690 rootdevID = scsiutil.getSCSIid(rootdev)
691 except:
692 SMlog("util.getrootdevID: Unable to verify serial or SCSIid of device: %s" \
693 % rootdev)
694 return ''
696 if not len(rootdevID):
697 SMlog("util.getrootdevID: Unable to identify scsi device [%s] via scsiID" \
698 % rootdev)
700 return rootdevID
703def get_localAPI_session():
704 # First acquire a valid session
705 session = XenAPI.xapi_local()
706 try:
707 session.xenapi.login_with_password('root', '', '', 'SM')
708 except:
709 raise xs_errors.XenError('APISession')
710 return session
713def get_this_host():
714 uuid = None
715 f = open("/etc/xensource-inventory", 'r')
716 for line in f.readlines():
717 if line.startswith("INSTALLATION_UUID"):
718 uuid = line.split("'")[1]
719 f.close()
720 return uuid
723def get_master_ref(session):
724 pools = session.xenapi.pool.get_all()
725 return session.xenapi.pool.get_master(pools[0])
728def is_master(session):
729 return get_this_host_ref(session) == get_master_ref(session)
732def get_localhost_ref(session):
733 filename = '/etc/xensource-inventory'
734 try:
735 f = open(filename, 'r')
736 except:
737 raise xs_errors.XenError('EIO', \
738 opterr="Unable to open inventory file [%s]" % filename)
739 domid = ''
740 for line in filter(match_domain_id, f.readlines()):
741 domid = line.split("'")[1]
742 if not domid:
743 raise xs_errors.XenError('APILocalhost')
745 vms = session.xenapi.VM.get_all_records_where('field "uuid" = "%s"' % domid)
746 for vm in vms:
747 record = vms[vm]
748 if record["uuid"] == domid:
749 hostid = record["resident_on"]
750 return hostid
751 raise xs_errors.XenError('APILocalhost')
754def match_domain_id(s):
755 regex = re.compile("^CONTROL_DOMAIN_UUID")
756 return regex.search(s, 0)
759def get_hosts_attached_on(session, vdi_uuids):
760 host_refs = {}
761 for vdi_uuid in vdi_uuids:
762 try:
763 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
764 except XenAPI.Failure:
765 SMlog("VDI %s not in db, ignoring" % vdi_uuid)
766 continue
767 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
768 for key in [x for x in sm_config.keys() if x.startswith('host_')]:
769 host_refs[key[len('host_'):]] = True
770 return host_refs.keys()
772def get_hosts_attached_on_with_vdi_uuid(session, vdi_uuids):
773 """
774 Return a dict of {vdi_uuid: host OpaqueRef}
775 """
776 host_refs = {}
777 for vdi_uuid in vdi_uuids:
778 try:
779 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
780 except XenAPI.Failure:
781 SMlog("VDI %s not in db, ignoring" % vdi_uuid)
782 continue
783 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
784 for key in [x for x in sm_config.keys() if x.startswith('host_')]:
785 host_refs[vdi_uuid] = key[len('host_'):]
786 return host_refs
788def get_this_host_address(session):
789 host_uuid = get_this_host()
790 host_ref = session.xenapi.host.get_by_uuid(host_uuid)
791 return session.xenapi.host.get_record(host_ref)['address']
793def get_host_addresses(session):
794 addresses = []
795 hosts = session.xenapi.host.get_all_records()
796 for record in hosts.values():
797 addresses.append(record['address'])
798 return addresses
800def get_this_host_ref(session):
801 host_uuid = get_this_host()
802 host_ref = session.xenapi.host.get_by_uuid(host_uuid)
803 return host_ref
806def get_slaves_attached_on(session, vdi_uuids):
807 "assume this host is the SR master"
808 host_refs = get_hosts_attached_on(session, vdi_uuids)
809 master_ref = get_this_host_ref(session)
810 return [x for x in host_refs if x != master_ref]
812def get_enabled_hosts(session):
813 """
814 Returns a list of host refs that are enabled in the pool.
815 """
816 return list(session.xenapi.host.get_all_records_where('field "enabled" = "true"').keys())
818def get_online_hosts(session):
819 online_hosts = []
820 hosts = session.xenapi.host.get_all_records()
821 for host_ref, host_rec in hosts.items():
822 metricsRef = host_rec["metrics"]
823 metrics = session.xenapi.host_metrics.get_record(metricsRef)
824 if metrics["live"]:
825 online_hosts.append(host_ref)
826 return online_hosts
829def get_all_slaves(session):
830 "assume this host is the SR master"
831 host_refs = get_online_hosts(session)
832 master_ref = get_this_host_ref(session)
833 return [x for x in host_refs if x != master_ref]
836def is_attached_rw(sm_config):
837 for key, val in sm_config.items():
838 if key.startswith("host_") and val == "RW":
839 return True
840 return False
843def attached_as(sm_config):
844 for key, val in sm_config.items():
845 if key.startswith("host_") and (val == "RW" or val == "RO"): 845 ↛ 846line 845 didn't jump to line 846, because the condition on line 845 was never true
846 return val
849def find_my_pbd_record(session, host_ref, sr_ref):
850 try:
851 pbds = session.xenapi.PBD.get_all_records()
852 for pbd_ref in pbds.keys():
853 if pbds[pbd_ref]['host'] == host_ref and pbds[pbd_ref]['SR'] == sr_ref:
854 return [pbd_ref, pbds[pbd_ref]]
855 return None
856 except Exception as e:
857 SMlog("Caught exception while looking up PBD for host %s SR %s: %s" % (str(host_ref), str(sr_ref), str(e)))
858 return None
861def find_my_pbd(session, host_ref, sr_ref):
862 ret = find_my_pbd_record(session, host_ref, sr_ref)
863 if ret is not None:
864 return ret[0]
865 else:
866 return None
869def test_hostPBD_devs(session, sr_uuid, devs):
870 host = get_localhost_ref(session)
871 sr = session.xenapi.SR.get_by_uuid(sr_uuid)
872 try:
873 pbds = session.xenapi.PBD.get_all_records()
874 except:
875 raise xs_errors.XenError('APIPBDQuery')
876 for dev in devs.split(','):
877 for pbd in pbds:
878 record = pbds[pbd]
879 # it's ok if it's *our* PBD
880 if record["SR"] == sr:
881 break
882 if record["host"] == host:
883 devconfig = record["device_config"]
884 if 'device' in devconfig:
885 for device in devconfig['device'].split(','):
886 if os.path.realpath(device) == os.path.realpath(dev):
887 return True
888 return False
891def test_hostPBD_lun(session, targetIQN, LUNid):
892 host = get_localhost_ref(session)
893 try:
894 pbds = session.xenapi.PBD.get_all_records()
895 except:
896 raise xs_errors.XenError('APIPBDQuery')
897 for pbd in pbds:
898 record = pbds[pbd]
899 if record["host"] == host:
900 devconfig = record["device_config"]
901 if 'targetIQN' in devconfig and 'LUNid' in devconfig:
902 if devconfig['targetIQN'] == targetIQN and \
903 devconfig['LUNid'] == LUNid:
904 return True
905 return False
908def test_SCSIid(session, sr_uuid, SCSIid):
909 if sr_uuid is not None:
910 sr = session.xenapi.SR.get_by_uuid(sr_uuid)
911 try:
912 pbds = session.xenapi.PBD.get_all_records()
913 except:
914 raise xs_errors.XenError('APIPBDQuery')
915 for pbd in pbds:
916 record = pbds[pbd]
917 # it's ok if it's *our* PBD
918 # During FC SR creation, devscan.py passes sr_uuid as None
919 if sr_uuid is not None:
920 if record["SR"] == sr:
921 break
922 devconfig = record["device_config"]
923 sm_config = session.xenapi.SR.get_sm_config(record["SR"])
924 if 'SCSIid' in devconfig and devconfig['SCSIid'] == SCSIid:
925 return True
926 elif 'SCSIid' in sm_config and sm_config['SCSIid'] == SCSIid:
927 return True
928 elif 'scsi-' + SCSIid in sm_config:
929 return True
930 return False
933class TimeoutException(SMException):
934 pass
937def timeout_call(timeoutseconds, function, *arguments):
938 def handler(signum, frame):
939 raise TimeoutException()
940 signal.signal(signal.SIGALRM, handler)
941 signal.alarm(timeoutseconds)
942 try:
943 return function(*arguments)
944 finally:
945 signal.alarm(0)
948def _incr_iscsiSR_refcount(targetIQN, uuid):
949 if not os.path.exists(ISCSI_REFDIR):
950 os.mkdir(ISCSI_REFDIR)
951 filename = os.path.join(ISCSI_REFDIR, targetIQN)
952 try:
953 f = open(filename, 'a+')
954 except:
955 raise xs_errors.XenError('LVMRefCount', \
956 opterr='file %s' % filename)
958 f.seek(0)
959 found = False
960 refcount = 0
961 for line in filter(match_uuid, f.readlines()):
962 refcount += 1
963 if line.find(uuid) != -1:
964 found = True
965 if not found:
966 f.write("%s\n" % uuid)
967 refcount += 1
968 f.close()
969 return refcount
972def _decr_iscsiSR_refcount(targetIQN, uuid):
973 filename = os.path.join(ISCSI_REFDIR, targetIQN)
974 if not os.path.exists(filename):
975 return 0
976 try:
977 f = open(filename, 'a+')
978 except:
979 raise xs_errors.XenError('LVMRefCount', \
980 opterr='file %s' % filename)
982 f.seek(0)
983 output = []
984 refcount = 0
985 for line in filter(match_uuid, f.readlines()):
986 if line.find(uuid) == -1:
987 output.append(line.rstrip())
988 refcount += 1
989 if not refcount:
990 os.unlink(filename)
991 return refcount
993 # Re-open file and truncate
994 f.close()
995 f = open(filename, 'w')
996 for i in range(0, refcount):
997 f.write("%s\n" % output[i])
998 f.close()
999 return refcount
1002# The agent enforces 1 PBD per SR per host, so we
1003# check for active SR entries not attached to this host
1004def test_activePoolPBDs(session, host, uuid):
1005 try:
1006 pbds = session.xenapi.PBD.get_all_records()
1007 except:
1008 raise xs_errors.XenError('APIPBDQuery')
1009 for pbd in pbds:
1010 record = pbds[pbd]
1011 if record["host"] != host and record["SR"] == uuid \
1012 and record["currently_attached"]:
1013 return True
1014 return False
1017def remove_mpathcount_field(session, host_ref, sr_ref, SCSIid):
1018 try:
1019 pbdref = find_my_pbd(session, host_ref, sr_ref)
1020 if pbdref is not None:
1021 key = "mpath-" + SCSIid
1022 session.xenapi.PBD.remove_from_other_config(pbdref, key)
1023 except:
1024 pass
1027def kickpipe_mpathcount():
1028 """
1029 Issue a kick to the mpathcount service. This will ensure that mpathcount runs
1030 shortly to update the multipath config records, if it was not already activated
1031 by a UDEV event.
1032 """
1033 cmd = [CMD_KICKPIPE, "mpathcount"]
1034 (rc, stdout, stderr) = doexec(cmd)
1035 return (rc == 0)
1038def _testHost(hostname, port, errstring):
1039 SMlog("_testHost: Testing host/port: %s,%d" % (hostname, port))
1040 try:
1041 sockinfo = socket.getaddrinfo(hostname, int(port))[0]
1042 except:
1043 logException('Exception occured getting IP for %s' % hostname)
1044 raise xs_errors.XenError('DNSError')
1046 timeout = 5
1048 sock = socket.socket(sockinfo[0], socket.SOCK_STREAM)
1049 # Only allow the connect to block for up to timeout seconds
1050 sock.settimeout(timeout)
1051 try:
1052 sock.connect(sockinfo[4])
1053 # Fix for MS storage server bug
1054 sock.send(b'\n')
1055 sock.close()
1056 except socket.error as reason:
1057 SMlog("_testHost: Connect failed after %d seconds (%s) - %s" \
1058 % (timeout, hostname, reason))
1059 raise xs_errors.XenError(errstring)
1062def match_scsiID(s, id):
1063 regex = re.compile(id)
1064 return regex.search(s, 0)
1067def _isSCSIid(s):
1068 regex = re.compile("^scsi-")
1069 return regex.search(s, 0)
1072def is_usb_device(device):
1073 cmd = ["udevadm", "info", "-q", "path", "-n", device]
1074 result = pread2(cmd).split('/')
1075 return len(result) >= 5 and result[4].startswith('usb')
1078def test_scsiserial(session, device):
1079 device = os.path.realpath(device)
1080 if not scsiutil._isSCSIdev(device):
1081 SMlog("util.test_scsiserial: Not a serial device: %s" % device)
1082 return False
1083 serial = ""
1084 try:
1085 serial += scsiutil.getserial(device)
1086 except:
1087 # Error allowed, SCSIid is the important one
1088 pass
1090 try:
1091 scsiID = scsiutil.getSCSIid(device)
1092 except:
1093 SMlog("util.test_scsiserial: Unable to verify serial or SCSIid of device: %s" \
1094 % device)
1095 return False
1096 if not len(scsiID):
1097 SMlog("util.test_scsiserial: Unable to identify scsi device [%s] via scsiID" \
1098 % device)
1099 return False
1101 # USB devices can have identical SCSI IDs - prefer matching with serial number
1102 try:
1103 usb_device_with_serial = serial and is_usb_device(device)
1104 except:
1105 usb_device_with_serial = False
1106 SMlog("Unable to check if device is USB:")
1107 SMlog(traceback.format_exc())
1109 try:
1110 SRs = session.xenapi.SR.get_all_records()
1111 except:
1112 raise xs_errors.XenError('APIFailure')
1113 for SR in SRs:
1114 record = SRs[SR]
1115 conf = record["sm_config"]
1116 if 'devserial' in conf:
1117 for dev in conf['devserial'].split(','):
1118 if not usb_device_with_serial and _isSCSIid(dev):
1119 if match_scsiID(dev, scsiID):
1120 return True
1121 elif len(serial) and dev == serial:
1122 return True
1123 return False
1126def default(self, field, thunk):
1127 try:
1128 return getattr(self, field)
1129 except:
1130 return thunk()
1133def list_VDI_records_in_sr(sr):
1134 """Helper function which returns a list of all VDI records for this SR
1135 stored in the XenAPI server, useful for implementing SR.scan"""
1136 sr_ref = sr.session.xenapi.SR.get_by_uuid(sr.uuid)
1137 vdis = sr.session.xenapi.VDI.get_all_records_where("field \"SR\" = \"%s\"" % sr_ref)
1138 return vdis
1141# Given a partition (e.g. sda1), get a disk name:
1142def diskFromPartition(partition):
1143 # check whether this is a device mapper device (e.g. /dev/dm-0)
1144 m = re.match('(/dev/)?(dm-[0-9]+)(p[0-9]+)?$', partition)
1145 if m is not None: 1145 ↛ 1146line 1145 didn't jump to line 1146, because the condition on line 1145 was never true
1146 return m.group(2)
1148 numlen = 0 # number of digit characters
1149 m = re.match(r"\D+(\d+)", partition)
1150 if m is not None: 1150 ↛ 1151line 1150 didn't jump to line 1151, because the condition on line 1150 was never true
1151 numlen = len(m.group(1))
1153 # is it a cciss?
1154 if True in [partition.startswith(x) for x in ['cciss', 'ida', 'rd']]: 1154 ↛ 1155line 1154 didn't jump to line 1155, because the condition on line 1154 was never true
1155 numlen += 1 # need to get rid of trailing 'p'
1157 # is it a mapper path?
1158 if partition.startswith("mapper"): 1158 ↛ 1159line 1158 didn't jump to line 1159, because the condition on line 1158 was never true
1159 if re.search("p[0-9]*$", partition):
1160 numlen = len(re.match(r"\d+", partition[::-1]).group(0)) + 1
1161 SMlog("Found mapper part, len %d" % numlen)
1162 else:
1163 numlen = 0
1165 # is it /dev/disk/by-id/XYZ-part<k>?
1166 if partition.startswith("disk/by-id"): 1166 ↛ 1167line 1166 didn't jump to line 1167, because the condition on line 1166 was never true
1167 return partition[:partition.rfind("-part")]
1169 return partition[:len(partition) - numlen]
1172def dom0_disks():
1173 """Disks carrying dom0, e.g. ['/dev/sda']"""
1174 disks = []
1175 with open("/etc/mtab", 'r') as f:
1176 for line in f:
1177 (dev, mountpoint, fstype, opts, freq, passno) = line.split(' ')
1178 if mountpoint == '/':
1179 disk = diskFromPartition(dev)
1180 if not (disk in disks):
1181 disks.append(disk)
1182 SMlog("Dom0 disks: %s" % disks)
1183 return disks
1186def set_scheduler_sysfs_node(node, scheds):
1187 """
1188 Set the scheduler for a sysfs node (e.g. '/sys/block/sda')
1189 according to prioritized list schedulers
1190 Try to set the first item, then fall back to the next on failure
1191 """
1193 path = os.path.join(node, "queue", "scheduler")
1194 if not os.path.exists(path): 1194 ↛ 1198line 1194 didn't jump to line 1198, because the condition on line 1194 was never false
1195 SMlog("no path %s" % path)
1196 return
1198 stored_error = None
1199 for sched in scheds:
1200 try:
1201 with open(path, 'w') as file:
1202 file.write("%s\n" % sched)
1203 SMlog("Set scheduler to [%s] on [%s]" % (sched, node))
1204 return
1205 except (OSError, IOError) as err:
1206 stored_error = err
1208 SMlog("Error setting schedulers to [%s] on [%s], %s" % (scheds, node, str(stored_error)))
1211def set_scheduler(dev, schedulers=None):
1212 if schedulers is None: 1212 ↛ 1215line 1212 didn't jump to line 1215, because the condition on line 1212 was never false
1213 schedulers = ["none", "noop"]
1215 devices = []
1216 if not scsiutil.match_dm(dev): 1216 ↛ 1220line 1216 didn't jump to line 1220, because the condition on line 1216 was never false
1217 # Remove partition numbers
1218 devices.append(diskFromPartition(dev).replace('/', '!'))
1219 else:
1220 rawdev = diskFromPartition(dev)
1221 devices = [os.path.realpath(x)[5:] for x in scsiutil._genReverseSCSIidmap(rawdev.split('/')[-1])]
1223 for d in devices:
1224 set_scheduler_sysfs_node("/sys/block/%s" % d, schedulers)
1227# This function queries XAPI for the existing VDI records for this SR
1228def _getVDIs(srobj):
1229 VDIs = []
1230 try:
1231 sr_ref = getattr(srobj, 'sr_ref')
1232 except AttributeError:
1233 return VDIs
1235 refs = srobj.session.xenapi.SR.get_VDIs(sr_ref)
1236 for vdi in refs:
1237 ref = srobj.session.xenapi.VDI.get_record(vdi)
1238 ref['vdi_ref'] = vdi
1239 VDIs.append(ref)
1240 return VDIs
1243def _getVDI(srobj, vdi_uuid):
1244 vdi = srobj.session.xenapi.VDI.get_by_uuid(vdi_uuid)
1245 ref = srobj.session.xenapi.VDI.get_record(vdi)
1246 ref['vdi_ref'] = vdi
1247 return ref
1250def _convertDNS(name):
1251 addr = socket.getaddrinfo(name, None)[0][4][0]
1252 return addr
1255def _containsVDIinuse(srobj):
1256 VDIs = _getVDIs(srobj)
1257 for vdi in VDIs:
1258 if not vdi['managed']:
1259 continue
1260 sm_config = vdi['sm_config']
1261 if 'SRRef' in sm_config:
1262 try:
1263 PBDs = srobj.session.xenapi.SR.get_PBDs(sm_config['SRRef'])
1264 for pbd in PBDs:
1265 record = PBDs[pbd]
1266 if record["host"] == srobj.host_ref and \
1267 record["currently_attached"]:
1268 return True
1269 except:
1270 pass
1271 return False
1274def isVDICommand(cmd):
1275 if cmd is None or cmd in ["vdi_attach", "vdi_detach",
1276 "vdi_activate", "vdi_deactivate",
1277 "vdi_epoch_begin", "vdi_epoch_end"]:
1278 return True
1279 else:
1280 return False
1283#########################
1284# Daemon helper functions
1285def p_id_fork():
1286 try:
1287 p_id = os.fork()
1288 except OSError as e:
1289 print("Fork failed: %s (%d)" % (e.strerror, e.errno))
1290 sys.exit(-1)
1292 if (p_id == 0):
1293 os.setsid()
1294 try:
1295 p_id = os.fork()
1296 except OSError as e:
1297 print("Fork failed: %s (%d)" % (e.strerror, e.errno))
1298 sys.exit(-1)
1299 if (p_id == 0):
1300 os.chdir('/opt/xensource/sm')
1301 os.umask(0)
1302 else:
1303 os._exit(0)
1304 else:
1305 os._exit(0)
1308def daemon():
1309 p_id_fork()
1310 # Query the max file descriptor parameter for this process
1311 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1313 # Close any fds that are open
1314 for fd in range(0, maxfd):
1315 try:
1316 os.close(fd)
1317 except:
1318 pass
1320 # Redirect STDIN to STDOUT and STDERR
1321 os.open('/dev/null', os.O_RDWR)
1322 os.dup2(0, 1)
1323 os.dup2(0, 2)
1325################################################################################
1326#
1327# Fist points
1328#
1330# * The global variable 'fistpoint' define the list of all possible fistpoints;
1331#
1332# * To activate a fistpoint called 'name', you need to create the file '/tmp/fist_name'
1333# on the SR master;
1334#
1335# * At the moment, activating a fist point can lead to two possible behaviors:
1336# - if '/tmp/fist_LVHDRT_exit' exists, then the function called during the fistpoint is _exit;
1337# - otherwise, the function called is _pause.
1339def _pause(secs, name):
1340 SMlog("Executing fist point %s: sleeping %d seconds ..." % (name, secs))
1341 time.sleep(secs)
1342 SMlog("Executing fist point %s: done" % name)
1345def _exit(name):
1346 SMlog("Executing fist point %s: exiting the current process ..." % name)
1347 raise xs_errors.XenError('FistPoint', opterr='%s' % name)
1350class FistPoint:
1351 def __init__(self, points):
1352 #SMlog("Fist points loaded")
1353 self.points = points
1355 def is_legal(self, name):
1356 return (name in self.points)
1358 def is_active(self, name):
1359 return os.path.exists("/tmp/fist_%s" % name)
1361 def mark_sr(self, name, sruuid, started):
1362 session = get_localAPI_session()
1363 try:
1364 sr = session.xenapi.SR.get_by_uuid(sruuid)
1366 if started:
1367 session.xenapi.SR.add_to_other_config(sr, name, "active")
1368 else:
1369 session.xenapi.SR.remove_from_other_config(sr, name)
1370 finally:
1371 session.xenapi.session.logout()
1373 def activate(self, name, sruuid):
1374 if name in self.points:
1375 if self.is_active(name):
1376 self.mark_sr(name, sruuid, True)
1377 if self.is_active("LVHDRT_exit"): 1377 ↛ 1378line 1377 didn't jump to line 1378, because the condition on line 1377 was never true
1378 self.mark_sr(name, sruuid, False)
1379 _exit(name)
1380 else:
1381 _pause(FIST_PAUSE_PERIOD, name)
1382 self.mark_sr(name, sruuid, False)
1383 else:
1384 SMlog("Unknown fist point: %s" % name)
1386 def activate_custom_fn(self, name, fn):
1387 if name in self.points: 1387 ↛ 1393line 1387 didn't jump to line 1393, because the condition on line 1387 was never false
1388 if self.is_active(name): 1388 ↛ 1389line 1388 didn't jump to line 1389, because the condition on line 1388 was never true
1389 SMlog("Executing fist point %s: starting ..." % name)
1390 fn()
1391 SMlog("Executing fist point %s: done" % name)
1392 else:
1393 SMlog("Unknown fist point: %s" % name)
1396def list_find(f, seq):
1397 for item in seq:
1398 if f(item):
1399 return item
1401GCPAUSE_FISTPOINT = "GCLoop_no_pause"
1403fistpoint = FistPoint(["LVHDRT_finding_a_suitable_pair",
1404 "LVHDRT_inflating_the_parent",
1405 "LVHDRT_resizing_while_vdis_are_paused",
1406 "LVHDRT_coalescing_VHD_data",
1407 "LVHDRT_coalescing_before_inflate_grandparent",
1408 "LVHDRT_relinking_grandchildren",
1409 "LVHDRT_before_create_relink_journal",
1410 "LVHDRT_xapiSM_serialization_tests",
1411 "LVHDRT_clone_vdi_after_create_journal",
1412 "LVHDRT_clone_vdi_after_shrink_parent",
1413 "LVHDRT_clone_vdi_after_first_snap",
1414 "LVHDRT_clone_vdi_after_second_snap",
1415 "LVHDRT_clone_vdi_after_parent_hidden",
1416 "LVHDRT_clone_vdi_after_parent_ro",
1417 "LVHDRT_clone_vdi_before_remove_journal",
1418 "LVHDRT_clone_vdi_after_lvcreate",
1419 "LVHDRT_clone_vdi_before_undo_clone",
1420 "LVHDRT_clone_vdi_after_undo_clone",
1421 "LVHDRT_inflate_after_create_journal",
1422 "LVHDRT_inflate_after_setSize",
1423 "LVHDRT_inflate_after_zeroOut",
1424 "LVHDRT_inflate_after_setSizePhys",
1425 "LVHDRT_inflate_after_setSizePhys",
1426 "LVHDRT_coaleaf_before_coalesce",
1427 "LVHDRT_coaleaf_after_coalesce",
1428 "LVHDRT_coaleaf_one_renamed",
1429 "LVHDRT_coaleaf_both_renamed",
1430 "LVHDRT_coaleaf_after_vdirec",
1431 "LVHDRT_coaleaf_before_delete",
1432 "LVHDRT_coaleaf_after_delete",
1433 "LVHDRT_coaleaf_before_remove_j",
1434 "LVHDRT_coaleaf_undo_after_rename",
1435 "LVHDRT_coaleaf_undo_after_rename2",
1436 "LVHDRT_coaleaf_undo_after_refcount",
1437 "LVHDRT_coaleaf_undo_after_deflate",
1438 "LVHDRT_coaleaf_undo_end",
1439 "LVHDRT_coaleaf_stop_after_recovery",
1440 "LVHDRT_coaleaf_finish_after_inflate",
1441 "LVHDRT_coaleaf_finish_end",
1442 "LVHDRT_coaleaf_delay_1",
1443 "LVHDRT_coaleaf_delay_2",
1444 "LVHDRT_coaleaf_delay_3",
1445 "testsm_clone_allow_raw",
1446 "xenrt_default_vdi_type_legacy",
1447 "blktap_activate_inject_failure",
1448 "blktap_activate_error_handling",
1449 GCPAUSE_FISTPOINT,
1450 "cleanup_coalesceVHD_inject_failure",
1451 "cleanup_tracker_no_progress",
1452 "FileSR_fail_hardlink",
1453 "FileSR_fail_snap1",
1454 "FileSR_fail_snap2",
1455 "LVM_journaler_exists",
1456 "LVM_journaler_none",
1457 "LVM_journaler_badname",
1458 "LVM_journaler_readfail",
1459 "LVM_journaler_writefail"])
1462def set_dirty(session, sr):
1463 try:
1464 session.xenapi.SR.add_to_other_config(sr, "dirty", "")
1465 SMlog("set_dirty %s succeeded" % (repr(sr)))
1466 except:
1467 SMlog("set_dirty %s failed (flag already set?)" % (repr(sr)))
1470def doesFileHaveOpenHandles(fileName):
1471 SMlog("Entering doesFileHaveOpenHandles with file: %s" % fileName)
1472 (retVal, processAndPidTuples) = \
1473 findRunningProcessOrOpenFile(fileName, False)
1475 if not retVal:
1476 SMlog("Failed to determine if file %s has open handles." % \
1477 fileName)
1478 # err on the side of caution
1479 return True
1480 else:
1481 if len(processAndPidTuples) > 0:
1482 return True
1483 else:
1484 return False
1487# extract SR uuid from the passed in devmapper entry and return
1488# /dev/mapper/VG_XenStorage--c3d82e92--cb25--c99b--b83a--482eebab4a93-MGT
1489def extractSRFromDevMapper(path):
1490 try:
1491 path = os.path.basename(path)
1492 path = path[len('VG_XenStorage-') + 1:]
1493 path = path.replace('--', '/')
1494 path = path[0:path.rfind('-')]
1495 return path.replace('/', '-')
1496 except:
1497 return ''
1500def pid_is_alive(pid):
1501 """
1502 Try to kill PID with signal 0.
1503 If we succeed, the PID is alive, so return True.
1504 If we get an EPERM error, the PID is alive but we are not allowed to
1505 signal it. Still return true.
1506 Any other error (e.g. ESRCH), return False
1507 """
1508 try:
1509 os.kill(pid, 0)
1510 return True
1511 except OSError as e:
1512 if e.errno == errno.EPERM:
1513 return True
1514 return False
1517# Looks at /proc and figures either
1518# If a process is still running (default), returns open file names
1519# If any running process has open handles to the given file (process = False)
1520# returns process names and pids
1521def findRunningProcessOrOpenFile(name, process=True):
1522 retVal = True
1523 links = []
1524 processandpids = []
1525 sockets = set()
1526 try:
1527 SMlog("Entering findRunningProcessOrOpenFile with params: %s" % \
1528 [name, process])
1530 # Look at all pids
1531 pids = [pid for pid in os.listdir('/proc') if pid.isdigit()]
1532 for pid in sorted(pids):
1533 try:
1534 try:
1535 f = None
1536 f = open(os.path.join('/proc', pid, 'cmdline'), 'r')
1537 prog = f.read()[:-1]
1538 if prog: 1538 ↛ 1547line 1538 didn't jump to line 1547, because the condition on line 1538 was never false
1539 # Just want the process name
1540 argv = prog.split('\x00')
1541 prog = argv[0]
1542 except IOError as e:
1543 if e.errno in (errno.ENOENT, errno.ESRCH):
1544 SMlog("ERROR %s reading %s, ignore" % (e.errno, pid))
1545 continue
1546 finally:
1547 if f is not None: 1547 ↛ 1532, 1547 ↛ 15502 missed branches: 1) line 1547 didn't jump to line 1532, because the continue on line 1545 wasn't executed, 2) line 1547 didn't jump to line 1550, because the condition on line 1547 was never false
1548 f.close() 1548 ↛ 1532line 1548 didn't jump to line 1532, because the continue on line 1545 wasn't executed
1550 try:
1551 fd_dir = os.path.join('/proc', pid, 'fd')
1552 files = os.listdir(fd_dir)
1553 except OSError as e:
1554 if e.errno in (errno.ENOENT, errno.ESRCH):
1555 SMlog("ERROR %s reading fds for %s, ignore" % (e.errno, pid))
1556 # Ignore pid that are no longer valid
1557 continue
1558 else:
1559 raise
1561 for file in files:
1562 try:
1563 link = os.readlink(os.path.join(fd_dir, file))
1564 except OSError:
1565 continue
1567 if process: 1567 ↛ 1572line 1567 didn't jump to line 1572, because the condition on line 1567 was never false
1568 if name == prog: 1568 ↛ 1561line 1568 didn't jump to line 1561, because the condition on line 1568 was never false
1569 links.append(link)
1570 else:
1571 # need to return process name and pid tuples
1572 if link == name:
1573 processandpids.append((prog, pid))
1575 # Get the connected sockets
1576 if name == prog:
1577 sockets.update(get_connected_sockets(pid))
1579 # We will only have a non-empty processandpids if some fd entries were found.
1580 # Before returning them, verify that all the PIDs in question are properly alive.
1581 # There is no specific guarantee of when a PID's /proc directory will disappear
1582 # when it exits, particularly relative to filedescriptor cleanup, so we want to
1583 # make sure we're not reporting a false positive.
1584 processandpids = [x for x in processandpids if pid_is_alive(int(x[1]))]
1585 for pp in processandpids: 1585 ↛ 1586line 1585 didn't jump to line 1586, because the loop on line 1585 never started
1586 SMlog(f"File {name} has an open handle with process {pp[0]} with pid {pp[1]}")
1588 except Exception as e:
1589 SMlog("Exception checking running process or open file handles. " \
1590 "Error: %s" % str(e))
1591 retVal = False
1593 if process: 1593 ↛ 1596line 1593 didn't jump to line 1596, because the condition on line 1593 was never false
1594 return retVal, links, sockets
1595 else:
1596 return retVal, processandpids
1599def get_connected_sockets(pid):
1600 sockets = set()
1601 try:
1602 # Lines in /proc/<pid>/net/unix are formatted as follows
1603 # (see Linux source net/unix/af_unix.c, unix_seq_show() )
1604 # - Pointer address to socket (hex)
1605 # - Refcount (HEX)
1606 # - 0
1607 # - State (HEX, 0 or __SO_ACCEPTCON)
1608 # - Type (HEX - but only 0001 of interest)
1609 # - Connection state (HEX - but only 03, SS_CONNECTED of interest)
1610 # - Inode number
1611 # - Path (optional)
1612 open_sock_matcher = re.compile(
1613 r'^[0-9a-f]+: [0-9A-Fa-f]+ [0-9A-Fa-f]+ [0-9A-Fa-f]+ 0001 03 \d+ (.*)$')
1614 with open(
1615 os.path.join('/proc', str(pid), 'net', 'unix'), 'r') as f:
1616 lines = f.readlines()
1617 for line in lines:
1618 match = open_sock_matcher.match(line)
1619 if match:
1620 sockets.add(match[1])
1621 except OSError as e:
1622 if e.errno in (errno.ENOENT, errno.ESRCH):
1623 # Ignore pid that are no longer valid
1624 SMlog("ERROR %s reading sockets for %s, ignore" %
1625 (e.errno, pid))
1626 else:
1627 raise
1628 return sockets
1631def retry(f, maxretry=20, period=3, exceptions=[Exception]):
1632 retries = 0
1633 while True:
1634 try:
1635 return f()
1636 except Exception as e:
1637 for exception in exceptions:
1638 if isinstance(e, exception):
1639 SMlog('Got exception: {}. Retry number: {}'.format(
1640 str(e), retries
1641 ))
1642 break
1643 else:
1644 SMlog('Got bad exception: {}. Raising...'.format(e))
1645 raise e
1647 retries += 1
1648 if retries >= maxretry:
1649 break
1651 time.sleep(period)
1653 return f()
1656def getCslDevPath(svid):
1657 basepath = "/dev/disk/by-csldev/"
1658 if svid.startswith("NETAPP_"):
1659 # special attention for NETAPP SVIDs
1660 svid_parts = svid.split("__")
1661 globstr = basepath + "NETAPP__LUN__" + "*" + svid_parts[2] + "*" + svid_parts[-1] + "*"
1662 else:
1663 globstr = basepath + svid + "*"
1665 return globstr
1668# Use device in /dev pointed to by cslg path which consists of svid
1669def get_scsiid_from_svid(md_svid):
1670 cslg_path = getCslDevPath(md_svid)
1671 abs_path = glob.glob(cslg_path)
1672 if abs_path:
1673 real_path = os.path.realpath(abs_path[0])
1674 return scsiutil.getSCSIid(real_path)
1675 else:
1676 return None
1679def get_isl_scsiids(session):
1680 # Get cslg type SRs
1681 SRs = session.xenapi.SR.get_all_records_where('field "type" = "cslg"')
1683 # Iterate through the SR to get the scsi ids
1684 scsi_id_ret = []
1685 for SR in SRs:
1686 sr_rec = SRs[SR]
1687 # Use the md_svid to get the scsi id
1688 scsi_id = get_scsiid_from_svid(sr_rec['sm_config']['md_svid'])
1689 if scsi_id:
1690 scsi_id_ret.append(scsi_id)
1692 # Get the vdis in the SR and do the same procedure
1693 vdi_recs = session.xenapi.VDI.get_all_records_where('field "SR" = "%s"' % SR)
1694 for vdi_rec in vdi_recs:
1695 vdi_rec = vdi_recs[vdi_rec]
1696 scsi_id = get_scsiid_from_svid(vdi_rec['sm_config']['SVID'])
1697 if scsi_id:
1698 scsi_id_ret.append(scsi_id)
1700 return scsi_id_ret
1703class extractXVA:
1704 # streams files as a set of file and checksum, caller should remove
1705 # the files, if not needed. The entire directory (Where the files
1706 # and checksum) will only be deleted as part of class cleanup.
1707 HDR_SIZE = 512
1708 BLOCK_SIZE = 512
1709 SIZE_LEN = 12 - 1 # To remove \0 from tail
1710 SIZE_OFFSET = 124
1711 ZERO_FILLED_REC = 2
1712 NULL_IDEN = '\x00'
1713 DIR_IDEN = '/'
1714 CHECKSUM_IDEN = '.checksum'
1715 OVA_FILE = 'ova.xml'
1717 # Init gunzips the file using a subprocess, and reads stdout later
1718 # as and when needed
1719 def __init__(self, filename):
1720 self.__extract_path = ''
1721 self.__filename = filename
1722 cmd = 'gunzip -cd %s' % filename
1723 try:
1724 self.spawn_p = subprocess.Popen(
1725 cmd, shell=True, \
1726 stdin=subprocess.PIPE, stdout=subprocess.PIPE, \
1727 stderr=subprocess.PIPE, close_fds=True)
1728 except Exception as e:
1729 SMlog("Error: %s. Uncompress failed for %s" % (str(e), filename))
1730 raise Exception(str(e))
1732 # Create dir to extract the files
1733 self.__extract_path = tempfile.mkdtemp()
1735 def __del__(self):
1736 shutil.rmtree(self.__extract_path)
1738 # Class supports Generator expression. 'for f_name, checksum in getTuple()'
1739 # returns filename, checksum content. Returns filename, '' in case
1740 # of checksum file missing. e.g. ova.xml
1741 def getTuple(self):
1742 zerod_record = 0
1743 ret_f_name = ''
1744 ret_base_f_name = ''
1746 try:
1747 # Read tar file as sets of file and checksum.
1748 while True:
1749 # Read the output of spawned process, or output of gunzip
1750 f_hdr = self.spawn_p.stdout.read(self.HDR_SIZE)
1752 # Break out in case of end of file
1753 if f_hdr == '':
1754 if zerod_record == extractXVA.ZERO_FILLED_REC:
1755 break
1756 else:
1757 SMlog('Error. Expects %d zero records', \
1758 extractXVA.ZERO_FILLED_REC)
1759 raise Exception('Unrecognized end of file')
1761 # Watch out for zero records, two zero records
1762 # denote end of file.
1763 if f_hdr == extractXVA.NULL_IDEN * extractXVA.HDR_SIZE:
1764 zerod_record += 1
1765 continue
1767 f_name = f_hdr[:f_hdr.index(extractXVA.NULL_IDEN)]
1768 # File header may be for a folder, if so ignore the header
1769 if not f_name.endswith(extractXVA.DIR_IDEN):
1770 f_size_octal = f_hdr[extractXVA.SIZE_OFFSET: \
1771 extractXVA.SIZE_OFFSET + extractXVA.SIZE_LEN]
1772 f_size = int(f_size_octal, 8)
1773 if f_name.endswith(extractXVA.CHECKSUM_IDEN):
1774 if f_name.rstrip(extractXVA.CHECKSUM_IDEN) == \
1775 ret_base_f_name:
1776 checksum = self.spawn_p.stdout.read(f_size)
1777 yield(ret_f_name, checksum)
1778 else:
1779 # Expects file followed by its checksum
1780 SMlog('Error. Sequence mismatch starting with %s', \
1781 ret_f_name)
1782 raise Exception( \
1783 'Files out of sequence starting with %s', \
1784 ret_f_name)
1785 else:
1786 # In case of ova.xml, read the contents into a file and
1787 # return the file name to the caller. For other files,
1788 # read the contents into a file, it will
1789 # be used when a .checksum file is encountered.
1790 ret_f_name = '%s/%s' % (self.__extract_path, f_name)
1791 ret_base_f_name = f_name
1793 # Check if the folder exists on the target location,
1794 # else create it.
1795 folder_path = ret_f_name[:ret_f_name.rfind('/')]
1796 if not os.path.exists(folder_path):
1797 os.mkdir(folder_path)
1799 # Store the file to the tmp folder, strip the tail \0
1800 f = open(ret_f_name, 'w')
1801 f.write(self.spawn_p.stdout.read(f_size))
1802 f.close()
1803 if f_name == extractXVA.OVA_FILE:
1804 yield(ret_f_name, '')
1806 # Skip zero'd portion of data block
1807 round_off = f_size % extractXVA.BLOCK_SIZE
1808 if round_off != 0:
1809 zeros = self.spawn_p.stdout.read(
1810 extractXVA.BLOCK_SIZE - round_off)
1811 except Exception as e:
1812 SMlog("Error: %s. File set extraction failed %s" % (str(e), \
1813 self.__filename))
1815 # Kill and Drain stdout of the gunzip process,
1816 # else gunzip might block on stdout
1817 os.kill(self.spawn_p.pid, signal.SIGTERM)
1818 self.spawn_p.communicate()
1819 raise Exception(str(e))
1821illegal_xml_chars = [(0x00, 0x08), (0x0B, 0x1F), (0x7F, 0x84), (0x86, 0x9F),
1822 (0xD800, 0xDFFF), (0xFDD0, 0xFDDF), (0xFFFE, 0xFFFF),
1823 (0x1FFFE, 0x1FFFF), (0x2FFFE, 0x2FFFF), (0x3FFFE, 0x3FFFF),
1824 (0x4FFFE, 0x4FFFF), (0x5FFFE, 0x5FFFF), (0x6FFFE, 0x6FFFF),
1825 (0x7FFFE, 0x7FFFF), (0x8FFFE, 0x8FFFF), (0x9FFFE, 0x9FFFF),
1826 (0xAFFFE, 0xAFFFF), (0xBFFFE, 0xBFFFF), (0xCFFFE, 0xCFFFF),
1827 (0xDFFFE, 0xDFFFF), (0xEFFFE, 0xEFFFF), (0xFFFFE, 0xFFFFF),
1828 (0x10FFFE, 0x10FFFF)]
1830illegal_ranges = ["%s-%s" % (chr(low), chr(high))
1831 for (low, high) in illegal_xml_chars
1832 if low < sys.maxunicode]
1834illegal_xml_re = re.compile(u'[%s]' % u''.join(illegal_ranges))
1837def isLegalXMLString(s):
1838 """Tells whether this is a valid XML string (i.e. it does not contain
1839 illegal XML characters specified in
1840 http://www.w3.org/TR/2004/REC-xml-20040204/#charsets).
1841 """
1843 if len(s) > 0:
1844 return re.search(illegal_xml_re, s) is None
1845 else:
1846 return True
1849def unictrunc(string, max_bytes):
1850 """
1851 Given a string, returns the largest number of elements for a prefix
1852 substring of it, such that the UTF-8 encoding of this substring takes no
1853 more than the given number of bytes.
1855 The string may be given as a unicode string or a UTF-8 encoded byte
1856 string, and the number returned will be in characters or bytes
1857 accordingly. Note that in the latter case, the substring will still be a
1858 valid UTF-8 encoded string (which is to say, it won't have been truncated
1859 part way through a multibyte sequence for a unicode character).
1861 string: the string to truncate
1862 max_bytes: the maximum number of bytes the truncated string can be
1863 """
1864 if isinstance(string, str):
1865 return_chars = True
1866 else:
1867 return_chars = False
1868 string = string.decode('UTF-8')
1870 cur_chars = 0
1871 cur_bytes = 0
1872 for char in string:
1873 charsize = len(char.encode('UTF-8'))
1874 if cur_bytes + charsize > max_bytes:
1875 break
1876 else:
1877 cur_chars += 1
1878 cur_bytes += charsize
1879 return cur_chars if return_chars else cur_bytes
1882def hideValuesInPropMap(propmap, propnames):
1883 """
1884 Worker function: input simple map of prop name/value pairs, and
1885 a list of specific propnames whose values we want to hide.
1886 Loop through the "hide" list, and if any are found, hide the
1887 value and return the altered map.
1888 If none found, return the original map
1889 """
1890 matches = []
1891 for propname in propnames:
1892 if propname in propmap: 1892 ↛ 1893line 1892 didn't jump to line 1893, because the condition on line 1892 was never true
1893 matches.append(propname)
1895 if matches: 1895 ↛ 1896line 1895 didn't jump to line 1896, because the condition on line 1895 was never true
1896 deepCopyRec = copy.deepcopy(propmap)
1897 for match in matches:
1898 deepCopyRec[match] = '******'
1899 return deepCopyRec
1901 return propmap
1902# define the list of propnames whose value we want to hide
1904PASSWD_PROP_KEYS = ['password', 'cifspassword', 'chappassword', 'incoming_chappassword']
1905DEFAULT_SEGMENT_LEN = 950
1908def hidePasswdInConfig(config):
1909 """
1910 Function to hide passwd values in a simple prop map,
1911 for example "device_config"
1912 """
1913 return hideValuesInPropMap(config, PASSWD_PROP_KEYS)
1916def hidePasswdInParams(params, configProp):
1917 """
1918 Function to hide password values in a specified property which
1919 is a simple map of prop name/values, and is itself an prop entry
1920 in a larger property map.
1921 For example, param maps containing "device_config", or
1922 "sm_config", etc
1923 """
1924 params[configProp] = hideValuesInPropMap(params[configProp], PASSWD_PROP_KEYS)
1925 return params
1928def hideMemberValuesInXmlParams(xmlParams, propnames=PASSWD_PROP_KEYS):
1929 """
1930 Function to hide password values in XML params, specifically
1931 for the XML format of incoming params to SR modules.
1932 Uses text parsing: loop through the list of specific propnames
1933 whose values we want to hide, and:
1934 - Assemble a full "prefix" containing each property name, e.g.,
1935 "<member><name>password</name><value>"
1936 - Test the XML if it contains that string, save the index.
1937 - If found, get the index of the ending tag
1938 - Truncate the return string starting with the password value.
1939 - Append the substitute "*******" value string.
1940 - Restore the rest of the original string starting with the end tag.
1941 """
1942 findStrPrefixHead = "<member><name>"
1943 findStrPrefixTail = "</name><value>"
1944 findStrSuffix = "</value>"
1945 strlen = len(xmlParams)
1947 for propname in propnames:
1948 findStrPrefix = findStrPrefixHead + propname + findStrPrefixTail
1949 idx = xmlParams.find(findStrPrefix)
1950 if idx != -1: # if found any of them
1951 idx += len(findStrPrefix)
1952 idx2 = xmlParams.find(findStrSuffix, idx)
1953 if idx2 != -1:
1954 retStr = xmlParams[0:idx]
1955 retStr += "******"
1956 retStr += xmlParams[idx2:strlen]
1957 return retStr
1958 else:
1959 return xmlParams
1960 return xmlParams
1963def splitXmlText(xmlData, segmentLen=DEFAULT_SEGMENT_LEN, showContd=False):
1964 """
1965 Split xml string data into substrings small enough for the
1966 syslog line length limit. Split at tag end markers ( ">" ).
1967 Usage:
1968 strList = []
1969 strList = splitXmlText( longXmlText, maxLineLen ) # maxLineLen is optional
1970 """
1971 remainingData = str(xmlData)
1973 # "Un-pretty-print"
1974 remainingData = remainingData.replace('\n', '')
1975 remainingData = remainingData.replace('\t', '')
1977 remainingChars = len(remainingData)
1978 returnData = ''
1980 thisLineNum = 0
1981 while remainingChars > segmentLen:
1982 thisLineNum = thisLineNum + 1
1983 index = segmentLen
1984 tmpStr = remainingData[:segmentLen]
1985 tmpIndex = tmpStr.rfind('>')
1986 if tmpIndex != -1:
1987 index = tmpIndex + 1
1989 tmpStr = tmpStr[:index]
1990 remainingData = remainingData[index:]
1991 remainingChars = len(remainingData)
1993 if showContd:
1994 if thisLineNum != 1:
1995 tmpStr = '(Cont\'d): ' + tmpStr
1996 tmpStr = tmpStr + ' (Cont\'d):'
1998 returnData += tmpStr + '\n'
2000 if showContd and thisLineNum > 0:
2001 remainingData = '(Cont\'d): ' + remainingData
2002 returnData += remainingData
2004 return returnData
2007def inject_failure():
2008 raise Exception('injected failure')
2011def open_atomic(path, mode=None):
2012 """Atomically creates a file if, and only if it does not already exist.
2013 Leaves the file open and returns the file object.
2015 path: the path to atomically open
2016 mode: "r" (read), "w" (write), or "rw" (read/write)
2017 returns: an open file object"""
2019 assert path
2021 flags = os.O_CREAT | os.O_EXCL
2022 modes = {'r': os.O_RDONLY, 'w': os.O_WRONLY, 'rw': os.O_RDWR}
2023 if mode:
2024 if mode not in modes:
2025 raise Exception('invalid access mode ' + mode)
2026 flags |= modes[mode]
2027 fd = os.open(path, flags)
2028 try:
2029 if mode:
2030 return os.fdopen(fd, mode)
2031 else:
2032 return os.fdopen(fd)
2033 except:
2034 os.close(fd)
2035 raise
2038def isInvalidVDI(exception):
2039 return exception.details[0] == "HANDLE_INVALID" or \
2040 exception.details[0] == "UUID_INVALID"
2043def get_pool_restrictions(session):
2044 """Returns pool restrictions as a map, @session must be already
2045 established."""
2046 return list(session.xenapi.pool.get_all_records().values())[0]['restrictions']
2049def read_caching_is_restricted(session):
2050 """Tells whether read caching is restricted."""
2051 if session is None: 2051 ↛ 2052line 2051 didn't jump to line 2052, because the condition on line 2051 was never true
2052 return True
2053 restrictions = get_pool_restrictions(session)
2054 if 'restrict_read_caching' in restrictions and \ 2054 ↛ 2056line 2054 didn't jump to line 2056, because the condition on line 2054 was never true
2055 restrictions['restrict_read_caching'] == "true":
2056 return True
2057 return False
2060def sessions_less_than_targets(other_config, device_config):
2061 if 'multihomelist' in device_config and 'iscsi_sessions' in other_config:
2062 sessions = int(other_config['iscsi_sessions'])
2063 targets = len(device_config['multihomelist'].split(','))
2064 SMlog("Targets %d and iscsi_sessions %d" % (targets, sessions))
2065 return (sessions < targets)
2066 else:
2067 return False
2070def enable_and_start_service(name, start):
2071 attempt = 0
2072 while True:
2073 attempt += 1
2074 fn = 'enable' if start else 'disable'
2075 args = ('systemctl', fn, '--now', name)
2076 (ret, out, err) = doexec(args)
2077 if ret == 0:
2078 return
2079 elif attempt >= 3:
2080 raise Exception(
2081 'Failed to {} {}: {} {}'.format(fn, name, out, err)
2082 )
2083 time.sleep(1)
2086def stop_service(name):
2087 args = ('systemctl', 'stop', name)
2088 (ret, out, err) = doexec(args)
2089 if ret == 0:
2090 return
2091 raise Exception('Failed to stop {}: {} {}'.format(name, out, err))
2094def restart_service(name):
2095 attempt = 0
2096 while True:
2097 attempt += 1
2098 SMlog('Restarting service {} {}...'.format(name, attempt))
2099 args = ('systemctl', 'restart', name)
2100 (ret, out, err) = doexec(args)
2101 if ret == 0:
2102 return
2103 elif attempt >= 3:
2104 SMlog('Restart service FAILED {} {}'.format(name, attempt))
2105 raise Exception(
2106 'Failed to restart {}: {} {}'.format(name, out, err)
2107 )
2108 time.sleep(1)
2111def check_pid_exists(pid):
2112 try:
2113 os.kill(pid, 0)
2114 except OSError:
2115 return False
2116 else:
2117 return True
2120def get_openers_pid(path: str) -> Optional[List[int]]:
2121 cmd = ["lsof", "-t", path]
2123 try:
2124 list = []
2125 ret = pread2(cmd)
2126 for line in ret.splitlines():
2127 list.append(int(line))
2128 return list
2129 except CommandException as e:
2130 if e.code == 1: # `lsof` return 1 if there is no openers
2131 return None
2132 else:
2133 raise e
2136def make_profile(name, function):
2137 """
2138 Helper to execute cProfile using unique log file.
2139 """
2141 import cProfile
2142 import itertools
2143 import os.path
2144 import time
2146 assert name
2147 assert function
2149 FOLDER = '/tmp/sm-perfs/'
2150 makedirs(FOLDER)
2152 filename = time.strftime('{}_%Y%m%d_%H%M%S.prof'.format(name))
2154 def gen_path(path):
2155 yield path
2156 root, ext = os.path.splitext(path)
2157 for i in itertools.count(start=1, step=1):
2158 yield root + '.{}.'.format(i) + ext
2160 for profile_path in gen_path(FOLDER + filename):
2161 try:
2162 file = open_atomic(profile_path, 'w')
2163 file.close()
2164 break
2165 except OSError as e:
2166 if e.errno == errno.EEXIST:
2167 pass
2168 else:
2169 raise
2171 try:
2172 SMlog('* Start profiling of {} ({}) *'.format(name, filename))
2173 cProfile.runctx('function()', None, locals(), profile_path)
2174 finally:
2175 SMlog('* End profiling of {} ({}) *'.format(name, filename))
2178def strtobool(str: str) -> bool:
2179 # Note: `distutils` package is deprecated and slated for removal in Python 3.12.
2180 # There is not alternative for strtobool.
2181 # See: https://peps.python.org/pep-0632/#migration-advice
2182 # So this is a custom implementation with differences:
2183 # - A boolean is returned instead of integer
2184 # - Empty string and None are supported (False is returned in this case)
2185 if not str: 2185 ↛ 2187line 2185 didn't jump to line 2187, because the condition on line 2185 was never false
2186 return False
2187 str = str.lower()
2188 if str in ('y', 'yes', 't', 'true', 'on', '1'):
2189 return True
2190 if str in ('n', 'no', 'f', 'false', 'off', '0'):
2191 return False
2192 raise ValueError("invalid truth value '{}'".format(str))
2195def find_executable(name):
2196 return shutil.which(name)
2199def conditional_decorator(decorator, condition):
2200 def wrapper(func):
2201 if not condition:
2202 return func
2203 return decorator(func)
2204 return wrapper