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