Coverage for drivers/util.py : 44%
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 type(obj) == str:
120 return obj
121 return str(obj)
124def shellquote(arg):
125 return '"%s"' % arg.replace('"', '\\"')
128def make_WWN(name):
129 hex_prefix = name.find("0x")
130 if (hex_prefix >= 0): 130 ↛ 133line 130 didn't jump to line 133, because the condition on line 130 was never false
131 name = name[name.find("0x") + 2:len(name)]
132 # inject dashes for each nibble
133 if (len(name) == 16): # sanity check 133 ↛ 137line 133 didn't jump to line 137, because the condition on line 133 was never false
134 name = name[0:2] + "-" + name[2:4] + "-" + name[4:6] + "-" + \
135 name[6:8] + "-" + name[8:10] + "-" + name[10:12] + "-" + \
136 name[12:14] + "-" + name[14:16]
137 return name
140def _logToSyslog(ident, facility, priority, message):
141 syslog.openlog(ident, 0, facility)
142 syslog.syslog(priority, "[%d] %s" % (os.getpid(), message))
143 syslog.closelog()
146def SMlog(message, ident="SM", priority=LOG_INFO):
147 if LOGGING: 147 ↛ exitline 147 didn't return from function 'SMlog', because the condition on line 147 was never false
148 for message_line in str(message).split('\n'):
149 _logToSyslog(ident, _SM_SYSLOG_FACILITY, priority, message_line)
152class LoggerCounter:
153 def __init__(self, max_repeats):
154 self.previous_message = None
155 self.max_repeats = max_repeats
156 self.repeat_counter = 0
158 def log(self, message):
159 self.repeat_counter += 1
160 if self.previous_message != message or self.repeat_counter == self.max_repeats:
161 SMlog(message)
162 self.previous_message = message
163 self.repeat_counter = 0
165def _getDateString():
166 d = datetime.datetime.now()
167 t = d.timetuple()
168 return "%s-%s-%s:%s:%s:%s" % \
169 (t[0], t[1], t[2], t[3], t[4], t[5])
172def doexec(args, inputtext=None, new_env=None, text=True):
173 """Execute a subprocess, then return its return code, stdout and stderr"""
174 env = None
175 if new_env:
176 env = dict(os.environ)
177 env.update(new_env)
178 proc = subprocess.Popen(args, stdin=subprocess.PIPE,
179 stdout=subprocess.PIPE,
180 stderr=subprocess.PIPE,
181 close_fds=True, env=env,
182 universal_newlines=text)
184 if not text and inputtext is not None: 184 ↛ 185line 184 didn't jump to line 185, because the condition on line 184 was never true
185 inputtext = inputtext.encode()
187 (stdout, stderr) = proc.communicate(inputtext)
189 rc = proc.returncode
190 return rc, stdout, stderr
193def is_string(value):
194 return isinstance(value, str)
197# These are partially tested functions that replicate the behaviour of
198# the original pread,pread2 and pread3 functions. Potentially these can
199# replace the original ones at some later date.
200#
201# cmdlist is a list of either single strings or pairs of strings. For
202# each pair, the first component is passed to exec while the second is
203# written to the logs.
204def pread(cmdlist, close_stdin=False, scramble=None, expect_rc=0,
205 quiet=False, new_env=None, text=True):
206 cmdlist_for_exec = []
207 cmdlist_for_log = []
208 for item in cmdlist:
209 if is_string(item): 209 ↛ 219line 209 didn't jump to line 219, because the condition on line 209 was never false
210 cmdlist_for_exec.append(item)
211 if scramble: 211 ↛ 212line 211 didn't jump to line 212, because the condition on line 211 was never true
212 if item.find(scramble) != -1:
213 cmdlist_for_log.append("<filtered out>")
214 else:
215 cmdlist_for_log.append(item)
216 else:
217 cmdlist_for_log.append(item)
218 else:
219 cmdlist_for_exec.append(item[0])
220 cmdlist_for_log.append(item[1])
222 if not quiet: 222 ↛ 224line 222 didn't jump to line 224, because the condition on line 222 was never false
223 SMlog(cmdlist_for_log)
224 (rc, stdout, stderr) = doexec(cmdlist_for_exec, new_env=new_env, text=text)
225 if rc != expect_rc:
226 SMlog("FAILED in util.pread: (rc %d) stdout: '%s', stderr: '%s'" % \
227 (rc, stdout, stderr))
228 if quiet: 228 ↛ 229line 228 didn't jump to line 229, because the condition on line 228 was never true
229 SMlog("Command was: %s" % cmdlist_for_log)
230 if '' == stderr: 230 ↛ 231line 230 didn't jump to line 231, because the condition on line 230 was never true
231 stderr = stdout
232 raise CommandException(rc, str(cmdlist), stderr.strip())
233 if not quiet: 233 ↛ 235line 233 didn't jump to line 235, because the condition on line 233 was never false
234 SMlog(" pread SUCCESS")
235 return stdout
238# POSIX guaranteed atomic within the same file system.
239# Supply directory to ensure tempfile is created
240# in the same directory.
241def atomicFileWrite(targetFile, directory, text):
243 file = None
244 try:
245 # Create file only current pid can write/read to
246 # our responsibility to clean it up.
247 _, tempPath = tempfile.mkstemp(dir=directory)
248 file = open(tempPath, 'w')
249 file.write(text)
251 # Ensure flushed to disk.
252 file.flush()
253 os.fsync(file.fileno())
254 file.close()
256 os.rename(tempPath, targetFile)
257 except OSError:
258 SMlog("FAILED to atomic write to %s" % (targetFile))
260 finally:
261 if (file is not None) and (not file.closed):
262 file.close()
264 if os.path.isfile(tempPath):
265 os.remove(tempPath)
268#Read STDOUT from cmdlist and discard STDERR output
269def pread2(cmdlist, quiet=False, text=True):
270 return pread(cmdlist, quiet=quiet, text=text)
273#Read STDOUT from cmdlist, feeding 'text' to STDIN
274def pread3(cmdlist, text):
275 SMlog(cmdlist)
276 (rc, stdout, stderr) = doexec(cmdlist, text)
277 if rc:
278 SMlog("FAILED in util.pread3: (errno %d) stdout: '%s', stderr: '%s'" % \
279 (rc, stdout, stderr))
280 if '' == stderr:
281 stderr = stdout
282 raise CommandException(rc, str(cmdlist), stderr.strip())
283 SMlog(" pread3 SUCCESS")
284 return stdout
287def listdir(path, quiet=False):
288 cmd = ["ls", path, "-1", "--color=never"]
289 try:
290 text = pread2(cmd, quiet=quiet)[:-1]
291 if len(text) == 0:
292 return []
293 return text.split('\n')
294 except CommandException as inst:
295 if inst.code == errno.ENOENT:
296 raise CommandException(errno.EIO, inst.cmd, inst.reason)
297 else:
298 raise CommandException(inst.code, inst.cmd, inst.reason)
301def gen_uuid():
302 cmd = ["uuidgen", "-r"]
303 return pread(cmd)[:-1]
306def match_uuid(s):
307 regex = re.compile("^[0-9a-f]{8}-(([0-9a-f]{4})-){3}[0-9a-f]{12}")
308 return regex.search(s, 0)
311def findall_uuid(s):
312 regex = re.compile("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}")
313 return regex.findall(s, 0)
316def exactmatch_uuid(s):
317 regex = re.compile("^[0-9a-f]{8}-(([0-9a-f]{4})-){3}[0-9a-f]{12}$")
318 return regex.search(s, 0)
321def start_log_entry(srpath, path, args):
322 logstring = str(datetime.datetime.now())
323 logstring += " log: "
324 logstring += srpath
325 logstring += " " + path
326 for element in args:
327 logstring += " " + element
328 try:
329 file = open(srpath + "/filelog.txt", "a")
330 file.write(logstring)
331 file.write("\n")
332 file.close()
333 except:
334 pass
336 # failed to write log ...
338def end_log_entry(srpath, path, args):
339 # for teminating, use "error" or "done"
340 logstring = str(datetime.datetime.now())
341 logstring += " end: "
342 logstring += srpath
343 logstring += " " + path
344 for element in args:
345 logstring += " " + element
346 try:
347 file = open(srpath + "/filelog.txt", "a")
348 file.write(logstring)
349 file.write("\n")
350 file.close()
351 except:
352 pass
354 # failed to write log ...
355 # for now print
356 # print "%s" % logstring
358def ioretry(f, errlist=[errno.EIO], maxretry=IORETRY_MAX, period=IORETRY_PERIOD, **ignored):
359 retries = 0
360 while True:
361 try:
362 return f()
363 except OSError as ose:
364 err = int(ose.errno)
365 if not err in errlist:
366 raise CommandException(err, str(f), "OSError")
367 except CommandException as ce:
368 if not int(ce.code) in errlist:
369 raise
371 retries += 1
372 if retries >= maxretry:
373 break
375 time.sleep(period)
377 raise CommandException(errno.ETIMEDOUT, str(f), "Timeout")
380def ioretry_stat(path, maxretry=IORETRY_MAX):
381 # this ioretry is similar to the previous method, but
382 # stat does not raise an error -- so check its return
383 retries = 0
384 while retries < maxretry:
385 stat = os.statvfs(path)
386 if stat.f_blocks != -1:
387 return stat
388 time.sleep(1)
389 retries += 1
390 raise CommandException(errno.EIO, "os.statvfs")
393def sr_get_capability(sr_uuid, session=None):
394 result = []
395 local_session = None
396 if session is None: 396 ↛ 400line 396 didn't jump to line 400, because the condition on line 396 was never false
397 local_session = get_localAPI_session()
398 session = local_session
400 try:
401 sr_ref = session.xenapi.SR.get_by_uuid(sr_uuid)
402 sm_type = session.xenapi.SR.get_record(sr_ref)['type']
403 sm_rec = session.xenapi.SM.get_all_records_where(
404 "field \"type\" = \"%s\"" % sm_type)
406 # SM expects at least one entry of any SR type
407 if len(sm_rec) > 0:
408 result = list(sm_rec.values())[0]['capabilities']
410 return result
411 finally:
412 if local_session: 412 ↛ exitline 412 didn't return from function 'sr_get_capability', because the return on line 410 wasn't executed
413 local_session.xenapi.session.logout()
415def sr_get_driver_info(driver_info):
416 results = {}
417 # first add in the vanilla stuff
418 for key in ['name', 'description', 'vendor', 'copyright', \
419 'driver_version', 'required_api_version']:
420 results[key] = driver_info[key]
421 # add the capabilities (xmlrpc array)
422 # enforcing activate/deactivate for blktap2
423 caps = driver_info['capabilities']
424 if "ATOMIC_PAUSE" in caps: 424 ↛ 425line 424 didn't jump to line 425, because the condition on line 424 was never true
425 for cap in ("VDI_ACTIVATE", "VDI_DEACTIVATE"):
426 if not cap in caps:
427 caps.append(cap)
428 elif "VDI_ACTIVATE" in caps or "VDI_DEACTIVATE" in caps: 428 ↛ 429line 428 didn't jump to line 429, because the condition on line 428 was never true
429 SMlog("Warning: vdi_[de]activate present for %s" % driver_info["name"])
431 results['capabilities'] = caps
432 # add in the configuration options
433 options = []
434 for option in driver_info['configuration']:
435 options.append({'key': option[0], 'description': option[1]})
436 results['configuration'] = options
437 return xmlrpc.client.dumps((results, ), "", True)
440def return_nil():
441 return xmlrpc.client.dumps((None, ), "", True, allow_none=True)
444def SRtoXML(SRlist):
445 dom = xml.dom.minidom.Document()
446 driver = dom.createElement("SRlist")
447 dom.appendChild(driver)
449 for key in SRlist.keys():
450 dict = SRlist[key]
451 entry = dom.createElement("SR")
452 driver.appendChild(entry)
454 e = dom.createElement("UUID")
455 entry.appendChild(e)
456 textnode = dom.createTextNode(key)
457 e.appendChild(textnode)
459 if 'size' in dict:
460 e = dom.createElement("Size")
461 entry.appendChild(e)
462 textnode = dom.createTextNode(str(dict['size']))
463 e.appendChild(textnode)
465 if 'storagepool' in dict:
466 e = dom.createElement("StoragePool")
467 entry.appendChild(e)
468 textnode = dom.createTextNode(str(dict['storagepool']))
469 e.appendChild(textnode)
471 if 'aggregate' in dict:
472 e = dom.createElement("Aggregate")
473 entry.appendChild(e)
474 textnode = dom.createTextNode(str(dict['aggregate']))
475 e.appendChild(textnode)
477 return dom.toprettyxml()
480def pathexists(path):
481 try:
482 os.lstat(path)
483 return True
484 except OSError as inst:
485 if inst.errno == errno.EIO: 485 ↛ 486line 485 didn't jump to line 486, because the condition on line 485 was never true
486 time.sleep(1)
487 try:
488 listdir(os.path.realpath(os.path.dirname(path)))
489 os.lstat(path)
490 return True
491 except:
492 pass
493 raise CommandException(errno.EIO, "os.lstat(%s)" % path, "failed")
494 return False
497def force_unlink(path):
498 try:
499 os.unlink(path)
500 except OSError as e:
501 if e.errno != errno.ENOENT: 501 ↛ 502line 501 didn't jump to line 502, because the condition on line 501 was never true
502 raise
505def create_secret(session, secret):
506 ref = session.xenapi.secret.create({'value': secret})
507 return session.xenapi.secret.get_uuid(ref)
510def get_secret(session, uuid):
511 try:
512 ref = session.xenapi.secret.get_by_uuid(uuid)
513 return session.xenapi.secret.get_value(ref)
514 except:
515 raise xs_errors.XenError('InvalidSecret', opterr='Unable to look up secret [%s]' % uuid)
518def get_real_path(path):
519 "Follow symlinks to the actual file"
520 absPath = path
521 directory = ''
522 while os.path.islink(absPath):
523 directory = os.path.dirname(absPath)
524 absPath = os.readlink(absPath)
525 absPath = os.path.join(directory, absPath)
526 return absPath
529def wait_for_path(path, timeout):
530 for i in range(0, timeout): 530 ↛ 534line 530 didn't jump to line 534, because the loop on line 530 didn't complete
531 if len(glob.glob(path)): 531 ↛ 533line 531 didn't jump to line 533, because the condition on line 531 was never false
532 return True
533 time.sleep(1)
534 return False
537def wait_for_nopath(path, timeout):
538 for i in range(0, timeout):
539 if not os.path.exists(path):
540 return True
541 time.sleep(1)
542 return False
545def wait_for_path_multi(path, timeout):
546 for i in range(0, timeout):
547 paths = glob.glob(path)
548 SMlog("_wait_for_paths_multi: paths = %s" % paths)
549 if len(paths):
550 SMlog("_wait_for_paths_multi: return first path: %s" % paths[0])
551 return paths[0]
552 time.sleep(1)
553 return ""
556def isdir(path):
557 try:
558 st = os.stat(path)
559 return stat.S_ISDIR(st.st_mode)
560 except OSError as inst:
561 if inst.errno == errno.EIO: 561 ↛ 562line 561 didn't jump to line 562, because the condition on line 561 was never true
562 raise CommandException(errno.EIO, "os.stat(%s)" % path, "failed")
563 return False
566def get_single_entry(path):
567 f = open(path, 'r')
568 line = f.readline()
569 f.close()
570 return line.rstrip()
573def get_fs_size(path):
574 st = ioretry_stat(path)
575 return st.f_blocks * st.f_frsize
578def get_fs_utilisation(path):
579 st = ioretry_stat(path)
580 return (st.f_blocks - st.f_bfree) * \
581 st.f_frsize
584def ismount(path):
585 """Test whether a path is a mount point"""
586 try:
587 s1 = os.stat(path)
588 s2 = os.stat(os.path.join(path, '..'))
589 except OSError as inst:
590 raise CommandException(inst.errno, "os.stat")
591 dev1 = s1.st_dev
592 dev2 = s2.st_dev
593 if dev1 != dev2:
594 return True # path/.. on a different device as path
595 ino1 = s1.st_ino
596 ino2 = s2.st_ino
597 if ino1 == ino2:
598 return True # path/.. is the same i-node as path
599 return False
602def makedirs(name, mode=0o777):
603 head, tail = os.path.split(name)
604 if not tail: 604 ↛ 605line 604 didn't jump to line 605, because the condition on line 604 was never true
605 head, tail = os.path.split(head)
606 if head and tail and not pathexists(head):
607 makedirs(head, mode)
608 if tail == os.curdir: 608 ↛ 609line 608 didn't jump to line 609, because the condition on line 608 was never true
609 return
610 try:
611 os.mkdir(name, mode)
612 except OSError as exc:
613 if exc.errno == errno.EEXIST and os.path.isdir(name): 613 ↛ 614line 613 didn't jump to line 614, because the condition on line 613 was never true
614 if mode:
615 os.chmod(name, mode)
616 pass
617 else:
618 raise
621def zeroOut(path, fromByte, bytes):
622 """write 'bytes' zeros to 'path' starting from fromByte (inclusive)"""
623 blockSize = 4096
625 fromBlock = fromByte // blockSize
626 if fromByte % blockSize:
627 fromBlock += 1
628 bytesBefore = fromBlock * blockSize - fromByte
629 if bytesBefore > bytes:
630 bytesBefore = bytes
631 bytes -= bytesBefore
632 cmd = [CMD_DD, "if=/dev/zero", "of=%s" % path, "bs=1",
633 "seek=%s" % fromByte, "count=%s" % bytesBefore]
634 try:
635 pread2(cmd)
636 except CommandException:
637 return False
639 blocks = bytes // blockSize
640 bytes -= blocks * blockSize
641 fromByte = (fromBlock + blocks) * blockSize
642 if blocks:
643 cmd = [CMD_DD, "if=/dev/zero", "of=%s" % path, "bs=%s" % blockSize,
644 "seek=%s" % fromBlock, "count=%s" % blocks]
645 try:
646 pread2(cmd)
647 except CommandException:
648 return False
650 if bytes:
651 cmd = [CMD_DD, "if=/dev/zero", "of=%s" % path, "bs=1",
652 "seek=%s" % fromByte, "count=%s" % bytes]
653 try:
654 pread2(cmd)
655 except CommandException:
656 return False
658 return True
661def wipefs(blockdev):
662 "Wipe filesystem signatures from `blockdev`"
663 pread2(["/usr/sbin/wipefs", "-a", blockdev])
666def match_rootdev(s):
667 regex = re.compile("^PRIMARY_DISK")
668 return regex.search(s, 0)
671def getrootdev():
672 filename = '/etc/xensource-inventory'
673 try:
674 f = open(filename, 'r')
675 except:
676 raise xs_errors.XenError('EIO', \
677 opterr="Unable to open inventory file [%s]" % filename)
678 rootdev = ''
679 for line in filter(match_rootdev, f.readlines()):
680 rootdev = line.split("'")[1]
681 if not rootdev: 681 ↛ 682line 681 didn't jump to line 682, because the condition on line 681 was never true
682 raise xs_errors.XenError('NoRootDev')
683 return rootdev
686def getrootdevID():
687 rootdev = getrootdev()
688 try:
689 rootdevID = scsiutil.getSCSIid(rootdev)
690 except:
691 SMlog("util.getrootdevID: Unable to verify serial or SCSIid of device: %s" \
692 % rootdev)
693 return ''
695 if not len(rootdevID):
696 SMlog("util.getrootdevID: Unable to identify scsi device [%s] via scsiID" \
697 % rootdev)
699 return rootdevID
702def get_localAPI_session():
703 # First acquire a valid session
704 session = XenAPI.xapi_local()
705 try:
706 session.xenapi.login_with_password('root', '', '', 'SM')
707 except:
708 raise xs_errors.XenError('APISession')
709 return session
712def get_this_host():
713 uuid = None
714 f = open("/etc/xensource-inventory", 'r')
715 for line in f.readlines():
716 if line.startswith("INSTALLATION_UUID"):
717 uuid = line.split("'")[1]
718 f.close()
719 return uuid
722def get_master_ref(session):
723 pools = session.xenapi.pool.get_all()
724 return session.xenapi.pool.get_master(pools[0])
727def is_master(session):
728 return get_this_host_ref(session) == get_master_ref(session)
731def get_localhost_ref(session):
732 filename = '/etc/xensource-inventory'
733 try:
734 f = open(filename, 'r')
735 except:
736 raise xs_errors.XenError('EIO', \
737 opterr="Unable to open inventory file [%s]" % filename)
738 domid = ''
739 for line in filter(match_domain_id, f.readlines()):
740 domid = line.split("'")[1]
741 if not domid:
742 raise xs_errors.XenError('APILocalhost')
744 vms = session.xenapi.VM.get_all_records_where('field "uuid" = "%s"' % domid)
745 for vm in vms:
746 record = vms[vm]
747 if record["uuid"] == domid:
748 hostid = record["resident_on"]
749 return hostid
750 raise xs_errors.XenError('APILocalhost')
753def match_domain_id(s):
754 regex = re.compile("^CONTROL_DOMAIN_UUID")
755 return regex.search(s, 0)
758def get_hosts_attached_on(session, vdi_uuids):
759 host_refs = {}
760 for vdi_uuid in vdi_uuids:
761 try:
762 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
763 except XenAPI.Failure:
764 SMlog("VDI %s not in db, ignoring" % vdi_uuid)
765 continue
766 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
767 for key in [x for x in sm_config.keys() if x.startswith('host_')]:
768 host_refs[key[len('host_'):]] = True
769 return host_refs.keys()
771def get_this_host_address(session):
772 host_uuid = get_this_host()
773 host_ref = session.xenapi.host.get_by_uuid(host_uuid)
774 return session.xenapi.host.get_record(host_ref)['address']
776def get_host_addresses(session):
777 addresses = []
778 hosts = session.xenapi.host.get_all_records()
779 for record in hosts.values():
780 addresses.append(record['address'])
781 return addresses
783def get_this_host_ref(session):
784 host_uuid = get_this_host()
785 host_ref = session.xenapi.host.get_by_uuid(host_uuid)
786 return host_ref
789def get_slaves_attached_on(session, vdi_uuids):
790 "assume this host is the SR master"
791 host_refs = get_hosts_attached_on(session, vdi_uuids)
792 master_ref = get_this_host_ref(session)
793 return [x for x in host_refs if x != master_ref]
795def get_enabled_hosts(session):
796 """
797 Returns a list of host refs that are enabled in the pool.
798 """
799 return list(session.xenapi.host.get_all_records_where('field "enabled" = "true"').keys())
801def get_online_hosts(session):
802 online_hosts = []
803 hosts = session.xenapi.host.get_all_records()
804 for host_ref, host_rec in hosts.items():
805 metricsRef = host_rec["metrics"]
806 metrics = session.xenapi.host_metrics.get_record(metricsRef)
807 if metrics["live"]:
808 online_hosts.append(host_ref)
809 return online_hosts
812def get_all_slaves(session):
813 "assume this host is the SR master"
814 host_refs = get_online_hosts(session)
815 master_ref = get_this_host_ref(session)
816 return [x for x in host_refs if x != master_ref]
819def is_attached_rw(sm_config):
820 for key, val in sm_config.items():
821 if key.startswith("host_") and val == "RW":
822 return True
823 return False
826def attached_as(sm_config):
827 for key, val in sm_config.items():
828 if key.startswith("host_") and (val == "RW" or val == "RO"): 828 ↛ 829line 828 didn't jump to line 829, because the condition on line 828 was never true
829 return val
832def find_my_pbd_record(session, host_ref, sr_ref):
833 try:
834 pbds = session.xenapi.PBD.get_all_records()
835 for pbd_ref in pbds.keys():
836 if pbds[pbd_ref]['host'] == host_ref and pbds[pbd_ref]['SR'] == sr_ref:
837 return [pbd_ref, pbds[pbd_ref]]
838 return None
839 except Exception as e:
840 SMlog("Caught exception while looking up PBD for host %s SR %s: %s" % (str(host_ref), str(sr_ref), str(e)))
841 return None
844def find_my_pbd(session, host_ref, sr_ref):
845 ret = find_my_pbd_record(session, host_ref, sr_ref)
846 if ret is not None:
847 return ret[0]
848 else:
849 return None
852def test_hostPBD_devs(session, sr_uuid, devs):
853 host = get_localhost_ref(session)
854 sr = session.xenapi.SR.get_by_uuid(sr_uuid)
855 try:
856 pbds = session.xenapi.PBD.get_all_records()
857 except:
858 raise xs_errors.XenError('APIPBDQuery')
859 for dev in devs.split(','):
860 for pbd in pbds:
861 record = pbds[pbd]
862 # it's ok if it's *our* PBD
863 if record["SR"] == sr:
864 break
865 if record["host"] == host:
866 devconfig = record["device_config"]
867 if 'device' in devconfig:
868 for device in devconfig['device'].split(','):
869 if os.path.realpath(device) == os.path.realpath(dev):
870 return True
871 return False
874def test_hostPBD_lun(session, targetIQN, LUNid):
875 host = get_localhost_ref(session)
876 try:
877 pbds = session.xenapi.PBD.get_all_records()
878 except:
879 raise xs_errors.XenError('APIPBDQuery')
880 for pbd in pbds:
881 record = pbds[pbd]
882 if record["host"] == host:
883 devconfig = record["device_config"]
884 if 'targetIQN' in devconfig and 'LUNid' in devconfig:
885 if devconfig['targetIQN'] == targetIQN and \
886 devconfig['LUNid'] == LUNid:
887 return True
888 return False
891def test_SCSIid(session, sr_uuid, SCSIid):
892 if sr_uuid is not None:
893 sr = session.xenapi.SR.get_by_uuid(sr_uuid)
894 try:
895 pbds = session.xenapi.PBD.get_all_records()
896 except:
897 raise xs_errors.XenError('APIPBDQuery')
898 for pbd in pbds:
899 record = pbds[pbd]
900 # it's ok if it's *our* PBD
901 # During FC SR creation, devscan.py passes sr_uuid as None
902 if sr_uuid is not None:
903 if record["SR"] == sr:
904 break
905 devconfig = record["device_config"]
906 sm_config = session.xenapi.SR.get_sm_config(record["SR"])
907 if 'SCSIid' in devconfig and devconfig['SCSIid'] == SCSIid:
908 return True
909 elif 'SCSIid' in sm_config and sm_config['SCSIid'] == SCSIid:
910 return True
911 elif 'scsi-' + SCSIid in sm_config:
912 return True
913 return False
916class TimeoutException(SMException):
917 pass
920def timeout_call(timeoutseconds, function, *arguments):
921 def handler(signum, frame):
922 raise TimeoutException()
923 signal.signal(signal.SIGALRM, handler)
924 signal.alarm(timeoutseconds)
925 try:
926 return function(*arguments)
927 finally:
928 signal.alarm(0)
931def _incr_iscsiSR_refcount(targetIQN, uuid):
932 if not os.path.exists(ISCSI_REFDIR):
933 os.mkdir(ISCSI_REFDIR)
934 filename = os.path.join(ISCSI_REFDIR, targetIQN)
935 try:
936 f = open(filename, 'a+')
937 except:
938 raise xs_errors.XenError('LVMRefCount', \
939 opterr='file %s' % filename)
941 f.seek(0)
942 found = False
943 refcount = 0
944 for line in filter(match_uuid, f.readlines()):
945 refcount += 1
946 if line.find(uuid) != -1:
947 found = True
948 if not found:
949 f.write("%s\n" % uuid)
950 refcount += 1
951 f.close()
952 return refcount
955def _decr_iscsiSR_refcount(targetIQN, uuid):
956 filename = os.path.join(ISCSI_REFDIR, targetIQN)
957 if not os.path.exists(filename):
958 return 0
959 try:
960 f = open(filename, 'a+')
961 except:
962 raise xs_errors.XenError('LVMRefCount', \
963 opterr='file %s' % filename)
965 f.seek(0)
966 output = []
967 refcount = 0
968 for line in filter(match_uuid, f.readlines()):
969 if line.find(uuid) == -1:
970 output.append(line.rstrip())
971 refcount += 1
972 if not refcount:
973 os.unlink(filename)
974 return refcount
976 # Re-open file and truncate
977 f.close()
978 f = open(filename, 'w')
979 for i in range(0, refcount):
980 f.write("%s\n" % output[i])
981 f.close()
982 return refcount
985# The agent enforces 1 PBD per SR per host, so we
986# check for active SR entries not attached to this host
987def test_activePoolPBDs(session, host, uuid):
988 try:
989 pbds = session.xenapi.PBD.get_all_records()
990 except:
991 raise xs_errors.XenError('APIPBDQuery')
992 for pbd in pbds:
993 record = pbds[pbd]
994 if record["host"] != host and record["SR"] == uuid \
995 and record["currently_attached"]:
996 return True
997 return False
1000def remove_mpathcount_field(session, host_ref, sr_ref, SCSIid):
1001 try:
1002 pbdref = find_my_pbd(session, host_ref, sr_ref)
1003 if pbdref is not None:
1004 key = "mpath-" + SCSIid
1005 session.xenapi.PBD.remove_from_other_config(pbdref, key)
1006 except:
1007 pass
1010def kickpipe_mpathcount():
1011 """
1012 Issue a kick to the mpathcount service. This will ensure that mpathcount runs
1013 shortly to update the multipath config records, if it was not already activated
1014 by a UDEV event.
1015 """
1016 cmd = [CMD_KICKPIPE, "mpathcount"]
1017 (rc, stdout, stderr) = doexec(cmd)
1018 return (rc == 0)
1021def _testHost(hostname, port, errstring):
1022 SMlog("_testHost: Testing host/port: %s,%d" % (hostname, port))
1023 try:
1024 sockinfo = socket.getaddrinfo(hostname, int(port))[0]
1025 except:
1026 logException('Exception occured getting IP for %s' % hostname)
1027 raise xs_errors.XenError('DNSError')
1029 timeout = 5
1031 sock = socket.socket(sockinfo[0], socket.SOCK_STREAM)
1032 # Only allow the connect to block for up to timeout seconds
1033 sock.settimeout(timeout)
1034 try:
1035 sock.connect(sockinfo[4])
1036 # Fix for MS storage server bug
1037 sock.send(b'\n')
1038 sock.close()
1039 except socket.error as reason:
1040 SMlog("_testHost: Connect failed after %d seconds (%s) - %s" \
1041 % (timeout, hostname, reason))
1042 raise xs_errors.XenError(errstring)
1045def match_scsiID(s, id):
1046 regex = re.compile(id)
1047 return regex.search(s, 0)
1050def _isSCSIid(s):
1051 regex = re.compile("^scsi-")
1052 return regex.search(s, 0)
1055def test_scsiserial(session, device):
1056 device = os.path.realpath(device)
1057 if not scsiutil._isSCSIdev(device):
1058 SMlog("util.test_scsiserial: Not a serial device: %s" % device)
1059 return False
1060 serial = ""
1061 try:
1062 serial += scsiutil.getserial(device)
1063 except:
1064 # Error allowed, SCSIid is the important one
1065 pass
1067 try:
1068 scsiID = scsiutil.getSCSIid(device)
1069 except:
1070 SMlog("util.test_scsiserial: Unable to verify serial or SCSIid of device: %s" \
1071 % device)
1072 return False
1073 if not len(scsiID):
1074 SMlog("util.test_scsiserial: Unable to identify scsi device [%s] via scsiID" \
1075 % device)
1076 return False
1078 try:
1079 SRs = session.xenapi.SR.get_all_records()
1080 except:
1081 raise xs_errors.XenError('APIFailure')
1082 for SR in SRs:
1083 record = SRs[SR]
1084 conf = record["sm_config"]
1085 if 'devserial' in conf:
1086 for dev in conf['devserial'].split(','):
1087 if _isSCSIid(dev):
1088 if match_scsiID(dev, scsiID):
1089 return True
1090 elif len(serial) and dev == serial:
1091 return True
1092 return False
1095def default(self, field, thunk):
1096 try:
1097 return getattr(self, field)
1098 except:
1099 return thunk()
1102def list_VDI_records_in_sr(sr):
1103 """Helper function which returns a list of all VDI records for this SR
1104 stored in the XenAPI server, useful for implementing SR.scan"""
1105 sr_ref = sr.session.xenapi.SR.get_by_uuid(sr.uuid)
1106 vdis = sr.session.xenapi.VDI.get_all_records_where("field \"SR\" = \"%s\"" % sr_ref)
1107 return vdis
1110# Given a partition (e.g. sda1), get a disk name:
1111def diskFromPartition(partition):
1112 # check whether this is a device mapper device (e.g. /dev/dm-0)
1113 m = re.match('(/dev/)?(dm-[0-9]+)(p[0-9]+)?$', partition)
1114 if m is not None: 1114 ↛ 1115line 1114 didn't jump to line 1115, because the condition on line 1114 was never true
1115 return m.group(2)
1117 numlen = 0 # number of digit characters
1118 m = re.match(r"\D+(\d+)", partition)
1119 if m is not None: 1119 ↛ 1120line 1119 didn't jump to line 1120, because the condition on line 1119 was never true
1120 numlen = len(m.group(1))
1122 # is it a cciss?
1123 if True in [partition.startswith(x) for x in ['cciss', 'ida', 'rd']]: 1123 ↛ 1124line 1123 didn't jump to line 1124, because the condition on line 1123 was never true
1124 numlen += 1 # need to get rid of trailing 'p'
1126 # is it a mapper path?
1127 if partition.startswith("mapper"): 1127 ↛ 1128line 1127 didn't jump to line 1128, because the condition on line 1127 was never true
1128 if re.search("p[0-9]*$", partition):
1129 numlen = len(re.match(r"\d+", partition[::-1]).group(0)) + 1
1130 SMlog("Found mapper part, len %d" % numlen)
1131 else:
1132 numlen = 0
1134 # is it /dev/disk/by-id/XYZ-part<k>?
1135 if partition.startswith("disk/by-id"): 1135 ↛ 1136line 1135 didn't jump to line 1136, because the condition on line 1135 was never true
1136 return partition[:partition.rfind("-part")]
1138 return partition[:len(partition) - numlen]
1141def dom0_disks():
1142 """Disks carrying dom0, e.g. ['/dev/sda']"""
1143 disks = []
1144 with open("/etc/mtab", 'r') as f:
1145 for line in f:
1146 (dev, mountpoint, fstype, opts, freq, passno) = line.split(' ')
1147 if mountpoint == '/':
1148 disk = diskFromPartition(dev)
1149 if not (disk in disks):
1150 disks.append(disk)
1151 SMlog("Dom0 disks: %s" % disks)
1152 return disks
1155def set_scheduler_sysfs_node(node, scheds):
1156 """
1157 Set the scheduler for a sysfs node (e.g. '/sys/block/sda')
1158 according to prioritized list schedulers
1159 Try to set the first item, then fall back to the next on failure
1160 """
1162 path = os.path.join(node, "queue", "scheduler")
1163 if not os.path.exists(path): 1163 ↛ 1167line 1163 didn't jump to line 1167, because the condition on line 1163 was never false
1164 SMlog("no path %s" % path)
1165 return
1167 stored_error = None
1168 for sched in scheds:
1169 try:
1170 with open(path, 'w') as file:
1171 file.write("%s\n" % sched)
1172 SMlog("Set scheduler to [%s] on [%s]" % (sched, node))
1173 return
1174 except (OSError, IOError) as err:
1175 stored_error = err
1177 SMlog("Error setting schedulers to [%s] on [%s], %s" % (scheds, node, str(stored_error)))
1180def set_scheduler(dev, schedulers=None):
1181 if schedulers is None: 1181 ↛ 1184line 1181 didn't jump to line 1184, because the condition on line 1181 was never false
1182 schedulers = ["none", "noop"]
1184 devices = []
1185 if not scsiutil.match_dm(dev): 1185 ↛ 1189line 1185 didn't jump to line 1189, because the condition on line 1185 was never false
1186 # Remove partition numbers
1187 devices.append(diskFromPartition(dev).replace('/', '!'))
1188 else:
1189 rawdev = diskFromPartition(dev)
1190 devices = [os.path.realpath(x)[5:] for x in scsiutil._genReverseSCSIidmap(rawdev.split('/')[-1])]
1192 for d in devices:
1193 set_scheduler_sysfs_node("/sys/block/%s" % d, schedulers)
1196# This function queries XAPI for the existing VDI records for this SR
1197def _getVDIs(srobj):
1198 VDIs = []
1199 try:
1200 sr_ref = getattr(srobj, 'sr_ref')
1201 except AttributeError:
1202 return VDIs
1204 refs = srobj.session.xenapi.SR.get_VDIs(sr_ref)
1205 for vdi in refs:
1206 ref = srobj.session.xenapi.VDI.get_record(vdi)
1207 ref['vdi_ref'] = vdi
1208 VDIs.append(ref)
1209 return VDIs
1212def _getVDI(srobj, vdi_uuid):
1213 vdi = srobj.session.xenapi.VDI.get_by_uuid(vdi_uuid)
1214 ref = srobj.session.xenapi.VDI.get_record(vdi)
1215 ref['vdi_ref'] = vdi
1216 return ref
1219def _convertDNS(name):
1220 addr = socket.getaddrinfo(name, None)[0][4][0]
1221 return addr
1224def _containsVDIinuse(srobj):
1225 VDIs = _getVDIs(srobj)
1226 for vdi in VDIs:
1227 if not vdi['managed']:
1228 continue
1229 sm_config = vdi['sm_config']
1230 if 'SRRef' in sm_config:
1231 try:
1232 PBDs = srobj.session.xenapi.SR.get_PBDs(sm_config['SRRef'])
1233 for pbd in PBDs:
1234 record = PBDs[pbd]
1235 if record["host"] == srobj.host_ref and \
1236 record["currently_attached"]:
1237 return True
1238 except:
1239 pass
1240 return False
1243def isVDICommand(cmd):
1244 if cmd is None or cmd in ["vdi_attach", "vdi_detach",
1245 "vdi_activate", "vdi_deactivate",
1246 "vdi_epoch_begin", "vdi_epoch_end"]:
1247 return True
1248 else:
1249 return False
1252#########################
1253# Daemon helper functions
1254def p_id_fork():
1255 try:
1256 p_id = os.fork()
1257 except OSError as e:
1258 print("Fork failed: %s (%d)" % (e.strerror, e.errno))
1259 sys.exit(-1)
1261 if (p_id == 0):
1262 os.setsid()
1263 try:
1264 p_id = os.fork()
1265 except OSError as e:
1266 print("Fork failed: %s (%d)" % (e.strerror, e.errno))
1267 sys.exit(-1)
1268 if (p_id == 0):
1269 os.chdir('/opt/xensource/sm')
1270 os.umask(0)
1271 else:
1272 os._exit(0)
1273 else:
1274 os._exit(0)
1277def daemon():
1278 p_id_fork()
1279 # Query the max file descriptor parameter for this process
1280 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1282 # Close any fds that are open
1283 for fd in range(0, maxfd):
1284 try:
1285 os.close(fd)
1286 except:
1287 pass
1289 # Redirect STDIN to STDOUT and STDERR
1290 os.open('/dev/null', os.O_RDWR)
1291 os.dup2(0, 1)
1292 os.dup2(0, 2)
1294################################################################################
1295#
1296# Fist points
1297#
1299# * The global variable 'fistpoint' define the list of all possible fistpoints;
1300#
1301# * To activate a fistpoint called 'name', you need to create the file '/tmp/fist_name'
1302# on the SR master;
1303#
1304# * At the moment, activating a fist point can lead to two possible behaviors:
1305# - if '/tmp/fist_LVHDRT_exit' exists, then the function called during the fistpoint is _exit;
1306# - otherwise, the function called is _pause.
1308def _pause(secs, name):
1309 SMlog("Executing fist point %s: sleeping %d seconds ..." % (name, secs))
1310 time.sleep(secs)
1311 SMlog("Executing fist point %s: done" % name)
1314def _exit(name):
1315 SMlog("Executing fist point %s: exiting the current process ..." % name)
1316 raise xs_errors.XenError('FistPoint', opterr='%s' % name)
1319class FistPoint:
1320 def __init__(self, points):
1321 #SMlog("Fist points loaded")
1322 self.points = points
1324 def is_legal(self, name):
1325 return (name in self.points)
1327 def is_active(self, name):
1328 return os.path.exists("/tmp/fist_%s" % name)
1330 def mark_sr(self, name, sruuid, started):
1331 session = get_localAPI_session()
1332 try:
1333 sr = session.xenapi.SR.get_by_uuid(sruuid)
1335 if started:
1336 session.xenapi.SR.add_to_other_config(sr, name, "active")
1337 else:
1338 session.xenapi.SR.remove_from_other_config(sr, name)
1339 finally:
1340 session.xenapi.session.logout()
1342 def activate(self, name, sruuid):
1343 if name in self.points:
1344 if self.is_active(name):
1345 self.mark_sr(name, sruuid, True)
1346 if self.is_active("LVHDRT_exit"): 1346 ↛ 1347line 1346 didn't jump to line 1347, because the condition on line 1346 was never true
1347 self.mark_sr(name, sruuid, False)
1348 _exit(name)
1349 else:
1350 _pause(FIST_PAUSE_PERIOD, name)
1351 self.mark_sr(name, sruuid, False)
1352 else:
1353 SMlog("Unknown fist point: %s" % name)
1355 def activate_custom_fn(self, name, fn):
1356 if name in self.points: 1356 ↛ 1362line 1356 didn't jump to line 1362, because the condition on line 1356 was never false
1357 if self.is_active(name): 1357 ↛ 1358line 1357 didn't jump to line 1358, because the condition on line 1357 was never true
1358 SMlog("Executing fist point %s: starting ..." % name)
1359 fn()
1360 SMlog("Executing fist point %s: done" % name)
1361 else:
1362 SMlog("Unknown fist point: %s" % name)
1365def list_find(f, seq):
1366 for item in seq:
1367 if f(item):
1368 return item
1370GCPAUSE_FISTPOINT = "GCLoop_no_pause"
1372fistpoint = FistPoint(["LVHDRT_finding_a_suitable_pair",
1373 "LVHDRT_inflating_the_parent",
1374 "LVHDRT_resizing_while_vdis_are_paused",
1375 "LVHDRT_coalescing_VHD_data",
1376 "LVHDRT_coalescing_before_inflate_grandparent",
1377 "LVHDRT_relinking_grandchildren",
1378 "LVHDRT_before_create_relink_journal",
1379 "LVHDRT_xapiSM_serialization_tests",
1380 "LVHDRT_clone_vdi_after_create_journal",
1381 "LVHDRT_clone_vdi_after_shrink_parent",
1382 "LVHDRT_clone_vdi_after_first_snap",
1383 "LVHDRT_clone_vdi_after_second_snap",
1384 "LVHDRT_clone_vdi_after_parent_hidden",
1385 "LVHDRT_clone_vdi_after_parent_ro",
1386 "LVHDRT_clone_vdi_before_remove_journal",
1387 "LVHDRT_clone_vdi_after_lvcreate",
1388 "LVHDRT_clone_vdi_before_undo_clone",
1389 "LVHDRT_clone_vdi_after_undo_clone",
1390 "LVHDRT_inflate_after_create_journal",
1391 "LVHDRT_inflate_after_setSize",
1392 "LVHDRT_inflate_after_zeroOut",
1393 "LVHDRT_inflate_after_setSizePhys",
1394 "LVHDRT_inflate_after_setSizePhys",
1395 "LVHDRT_coaleaf_before_coalesce",
1396 "LVHDRT_coaleaf_after_coalesce",
1397 "LVHDRT_coaleaf_one_renamed",
1398 "LVHDRT_coaleaf_both_renamed",
1399 "LVHDRT_coaleaf_after_vdirec",
1400 "LVHDRT_coaleaf_before_delete",
1401 "LVHDRT_coaleaf_after_delete",
1402 "LVHDRT_coaleaf_before_remove_j",
1403 "LVHDRT_coaleaf_undo_after_rename",
1404 "LVHDRT_coaleaf_undo_after_rename2",
1405 "LVHDRT_coaleaf_undo_after_refcount",
1406 "LVHDRT_coaleaf_undo_after_deflate",
1407 "LVHDRT_coaleaf_undo_end",
1408 "LVHDRT_coaleaf_stop_after_recovery",
1409 "LVHDRT_coaleaf_finish_after_inflate",
1410 "LVHDRT_coaleaf_finish_end",
1411 "LVHDRT_coaleaf_delay_1",
1412 "LVHDRT_coaleaf_delay_2",
1413 "LVHDRT_coaleaf_delay_3",
1414 "testsm_clone_allow_raw",
1415 "xenrt_default_vdi_type_legacy",
1416 "blktap_activate_inject_failure",
1417 "blktap_activate_error_handling",
1418 GCPAUSE_FISTPOINT,
1419 "cleanup_coalesceVHD_inject_failure",
1420 "cleanup_tracker_no_progress",
1421 "FileSR_fail_hardlink",
1422 "FileSR_fail_snap1",
1423 "FileSR_fail_snap2",
1424 "LVM_journaler_exists",
1425 "LVM_journaler_none",
1426 "LVM_journaler_badname",
1427 "LVM_journaler_readfail",
1428 "LVM_journaler_writefail"])
1431def set_dirty(session, sr):
1432 try:
1433 session.xenapi.SR.add_to_other_config(sr, "dirty", "")
1434 SMlog("set_dirty %s succeeded" % (repr(sr)))
1435 except:
1436 SMlog("set_dirty %s failed (flag already set?)" % (repr(sr)))
1439def doesFileHaveOpenHandles(fileName):
1440 SMlog("Entering doesFileHaveOpenHandles with file: %s" % fileName)
1441 (retVal, processAndPidTuples) = \
1442 findRunningProcessOrOpenFile(fileName, False)
1444 if not retVal:
1445 SMlog("Failed to determine if file %s has open handles." % \
1446 fileName)
1447 # err on the side of caution
1448 return True
1449 else:
1450 if len(processAndPidTuples) > 0:
1451 return True
1452 else:
1453 return False
1456# extract SR uuid from the passed in devmapper entry and return
1457# /dev/mapper/VG_XenStorage--c3d82e92--cb25--c99b--b83a--482eebab4a93-MGT
1458def extractSRFromDevMapper(path):
1459 try:
1460 path = os.path.basename(path)
1461 path = path[len('VG_XenStorage-') + 1:]
1462 path = path.replace('--', '/')
1463 path = path[0:path.rfind('-')]
1464 return path.replace('/', '-')
1465 except:
1466 return ''
1469def pid_is_alive(pid):
1470 """
1471 Try to kill PID with signal 0.
1472 If we succeed, the PID is alive, so return True.
1473 If we get an EPERM error, the PID is alive but we are not allowed to
1474 signal it. Still return true.
1475 Any other error (e.g. ESRCH), return False
1476 """
1477 try:
1478 os.kill(pid, 0)
1479 return True
1480 except OSError as e:
1481 if e.errno == errno.EPERM:
1482 return True
1483 return False
1486# Looks at /proc and figures either
1487# If a process is still running (default), returns open file names
1488# If any running process has open handles to the given file (process = False)
1489# returns process names and pids
1490def findRunningProcessOrOpenFile(name, process=True):
1491 retVal = True
1492 links = []
1493 processandpids = []
1494 sockets = set()
1495 try:
1496 SMlog("Entering findRunningProcessOrOpenFile with params: %s" % \
1497 [name, process])
1499 # Look at all pids
1500 pids = [pid for pid in os.listdir('/proc') if pid.isdigit()]
1501 for pid in sorted(pids):
1502 try:
1503 try:
1504 f = None
1505 f = open(os.path.join('/proc', pid, 'cmdline'), 'r')
1506 prog = f.read()[:-1]
1507 if prog: 1507 ↛ 1516line 1507 didn't jump to line 1516, because the condition on line 1507 was never false
1508 # Just want the process name
1509 argv = prog.split('\x00')
1510 prog = argv[0]
1511 except IOError as e:
1512 if e.errno in (errno.ENOENT, errno.ESRCH):
1513 SMlog("ERROR %s reading %s, ignore" % (e.errno, pid))
1514 continue
1515 finally:
1516 if f is not None: 1516 ↛ 1501, 1516 ↛ 15192 missed branches: 1) line 1516 didn't jump to line 1501, because the continue on line 1514 wasn't executed, 2) line 1516 didn't jump to line 1519, because the condition on line 1516 was never false
1517 f.close() 1517 ↛ 1501line 1517 didn't jump to line 1501, because the continue on line 1514 wasn't executed
1519 try:
1520 fd_dir = os.path.join('/proc', pid, 'fd')
1521 files = os.listdir(fd_dir)
1522 except OSError as e:
1523 if e.errno in (errno.ENOENT, errno.ESRCH):
1524 SMlog("ERROR %s reading fds for %s, ignore" % (e.errno, pid))
1525 # Ignore pid that are no longer valid
1526 continue
1527 else:
1528 raise
1530 for file in files:
1531 try:
1532 link = os.readlink(os.path.join(fd_dir, file))
1533 except OSError:
1534 continue
1536 if process: 1536 ↛ 1541line 1536 didn't jump to line 1541, because the condition on line 1536 was never false
1537 if name == prog: 1537 ↛ 1530line 1537 didn't jump to line 1530, because the condition on line 1537 was never false
1538 links.append(link)
1539 else:
1540 # need to return process name and pid tuples
1541 if link == name:
1542 processandpids.append((prog, pid))
1544 # Get the connected sockets
1545 if name == prog:
1546 sockets.update(get_connected_sockets(pid))
1548 # We will only have a non-empty processandpids if some fd entries were found.
1549 # Before returning them, verify that all the PIDs in question are properly alive.
1550 # There is no specific guarantee of when a PID's /proc directory will disappear
1551 # when it exits, particularly relative to filedescriptor cleanup, so we want to
1552 # make sure we're not reporting a false positive.
1553 processandpids = [x for x in processandpids if pid_is_alive(int(x[1]))]
1554 for pp in processandpids: 1554 ↛ 1555line 1554 didn't jump to line 1555, because the loop on line 1554 never started
1555 SMlog(f"File {name} has an open handle with process {pp[0]} with pid {pp[1]}")
1557 except Exception as e:
1558 SMlog("Exception checking running process or open file handles. " \
1559 "Error: %s" % str(e))
1560 retVal = False
1562 if process: 1562 ↛ 1565line 1562 didn't jump to line 1565, because the condition on line 1562 was never false
1563 return retVal, links, sockets
1564 else:
1565 return retVal, processandpids
1568def get_connected_sockets(pid):
1569 sockets = set()
1570 try:
1571 # Lines in /proc/<pid>/net/unix are formatted as follows
1572 # (see Linux source net/unix/af_unix.c, unix_seq_show() )
1573 # - Pointer address to socket (hex)
1574 # - Refcount (HEX)
1575 # - 0
1576 # - State (HEX, 0 or __SO_ACCEPTCON)
1577 # - Type (HEX - but only 0001 of interest)
1578 # - Connection state (HEX - but only 03, SS_CONNECTED of interest)
1579 # - Inode number
1580 # - Path (optional)
1581 open_sock_matcher = re.compile(
1582 r'^[0-9a-f]+: [0-9A-Fa-f]+ [0-9A-Fa-f]+ [0-9A-Fa-f]+ 0001 03 \d+ (.*)$')
1583 with open(
1584 os.path.join('/proc', str(pid), 'net', 'unix'), 'r') as f:
1585 lines = f.readlines()
1586 for line in lines:
1587 match = open_sock_matcher.match(line)
1588 if match:
1589 sockets.add(match[1])
1590 except OSError as e:
1591 if e.errno in (errno.ENOENT, errno.ESRCH):
1592 # Ignore pid that are no longer valid
1593 SMlog("ERROR %s reading sockets for %s, ignore" %
1594 (e.errno, pid))
1595 else:
1596 raise
1597 return sockets
1600def retry(f, maxretry=20, period=3, exceptions=[Exception]):
1601 retries = 0
1602 while True:
1603 try:
1604 return f()
1605 except Exception as e:
1606 for exception in exceptions:
1607 if isinstance(e, exception):
1608 SMlog('Got exception: {}. Retry number: {}'.format(
1609 str(e), retries
1610 ))
1611 break
1612 else:
1613 SMlog('Got bad exception: {}. Raising...'.format(e))
1614 raise e
1616 retries += 1
1617 if retries >= maxretry:
1618 break
1620 time.sleep(period)
1622 return f()
1625def getCslDevPath(svid):
1626 basepath = "/dev/disk/by-csldev/"
1627 if svid.startswith("NETAPP_"):
1628 # special attention for NETAPP SVIDs
1629 svid_parts = svid.split("__")
1630 globstr = basepath + "NETAPP__LUN__" + "*" + svid_parts[2] + "*" + svid_parts[-1] + "*"
1631 else:
1632 globstr = basepath + svid + "*"
1634 return globstr
1637# Use device in /dev pointed to by cslg path which consists of svid
1638def get_scsiid_from_svid(md_svid):
1639 cslg_path = getCslDevPath(md_svid)
1640 abs_path = glob.glob(cslg_path)
1641 if abs_path:
1642 real_path = os.path.realpath(abs_path[0])
1643 return scsiutil.getSCSIid(real_path)
1644 else:
1645 return None
1648def get_isl_scsiids(session):
1649 # Get cslg type SRs
1650 SRs = session.xenapi.SR.get_all_records_where('field "type" = "cslg"')
1652 # Iterate through the SR to get the scsi ids
1653 scsi_id_ret = []
1654 for SR in SRs:
1655 sr_rec = SRs[SR]
1656 # Use the md_svid to get the scsi id
1657 scsi_id = get_scsiid_from_svid(sr_rec['sm_config']['md_svid'])
1658 if scsi_id:
1659 scsi_id_ret.append(scsi_id)
1661 # Get the vdis in the SR and do the same procedure
1662 vdi_recs = session.xenapi.VDI.get_all_records_where('field "SR" = "%s"' % SR)
1663 for vdi_rec in vdi_recs:
1664 vdi_rec = vdi_recs[vdi_rec]
1665 scsi_id = get_scsiid_from_svid(vdi_rec['sm_config']['SVID'])
1666 if scsi_id:
1667 scsi_id_ret.append(scsi_id)
1669 return scsi_id_ret
1672class extractXVA:
1673 # streams files as a set of file and checksum, caller should remove
1674 # the files, if not needed. The entire directory (Where the files
1675 # and checksum) will only be deleted as part of class cleanup.
1676 HDR_SIZE = 512
1677 BLOCK_SIZE = 512
1678 SIZE_LEN = 12 - 1 # To remove \0 from tail
1679 SIZE_OFFSET = 124
1680 ZERO_FILLED_REC = 2
1681 NULL_IDEN = '\x00'
1682 DIR_IDEN = '/'
1683 CHECKSUM_IDEN = '.checksum'
1684 OVA_FILE = 'ova.xml'
1686 # Init gunzips the file using a subprocess, and reads stdout later
1687 # as and when needed
1688 def __init__(self, filename):
1689 self.__extract_path = ''
1690 self.__filename = filename
1691 cmd = 'gunzip -cd %s' % filename
1692 try:
1693 self.spawn_p = subprocess.Popen(
1694 cmd, shell=True, \
1695 stdin=subprocess.PIPE, stdout=subprocess.PIPE, \
1696 stderr=subprocess.PIPE, close_fds=True)
1697 except Exception as e:
1698 SMlog("Error: %s. Uncompress failed for %s" % (str(e), filename))
1699 raise Exception(str(e))
1701 # Create dir to extract the files
1702 self.__extract_path = tempfile.mkdtemp()
1704 def __del__(self):
1705 shutil.rmtree(self.__extract_path)
1707 # Class supports Generator expression. 'for f_name, checksum in getTuple()'
1708 # returns filename, checksum content. Returns filename, '' in case
1709 # of checksum file missing. e.g. ova.xml
1710 def getTuple(self):
1711 zerod_record = 0
1712 ret_f_name = ''
1713 ret_base_f_name = ''
1715 try:
1716 # Read tar file as sets of file and checksum.
1717 while True:
1718 # Read the output of spawned process, or output of gunzip
1719 f_hdr = self.spawn_p.stdout.read(self.HDR_SIZE)
1721 # Break out in case of end of file
1722 if f_hdr == '':
1723 if zerod_record == extractXVA.ZERO_FILLED_REC:
1724 break
1725 else:
1726 SMlog('Error. Expects %d zero records', \
1727 extractXVA.ZERO_FILLED_REC)
1728 raise Exception('Unrecognized end of file')
1730 # Watch out for zero records, two zero records
1731 # denote end of file.
1732 if f_hdr == extractXVA.NULL_IDEN * extractXVA.HDR_SIZE:
1733 zerod_record += 1
1734 continue
1736 f_name = f_hdr[:f_hdr.index(extractXVA.NULL_IDEN)]
1737 # File header may be for a folder, if so ignore the header
1738 if not f_name.endswith(extractXVA.DIR_IDEN):
1739 f_size_octal = f_hdr[extractXVA.SIZE_OFFSET: \
1740 extractXVA.SIZE_OFFSET + extractXVA.SIZE_LEN]
1741 f_size = int(f_size_octal, 8)
1742 if f_name.endswith(extractXVA.CHECKSUM_IDEN):
1743 if f_name.rstrip(extractXVA.CHECKSUM_IDEN) == \
1744 ret_base_f_name:
1745 checksum = self.spawn_p.stdout.read(f_size)
1746 yield(ret_f_name, checksum)
1747 else:
1748 # Expects file followed by its checksum
1749 SMlog('Error. Sequence mismatch starting with %s', \
1750 ret_f_name)
1751 raise Exception( \
1752 'Files out of sequence starting with %s', \
1753 ret_f_name)
1754 else:
1755 # In case of ova.xml, read the contents into a file and
1756 # return the file name to the caller. For other files,
1757 # read the contents into a file, it will
1758 # be used when a .checksum file is encountered.
1759 ret_f_name = '%s/%s' % (self.__extract_path, f_name)
1760 ret_base_f_name = f_name
1762 # Check if the folder exists on the target location,
1763 # else create it.
1764 folder_path = ret_f_name[:ret_f_name.rfind('/')]
1765 if not os.path.exists(folder_path):
1766 os.mkdir(folder_path)
1768 # Store the file to the tmp folder, strip the tail \0
1769 f = open(ret_f_name, 'w')
1770 f.write(self.spawn_p.stdout.read(f_size))
1771 f.close()
1772 if f_name == extractXVA.OVA_FILE:
1773 yield(ret_f_name, '')
1775 # Skip zero'd portion of data block
1776 round_off = f_size % extractXVA.BLOCK_SIZE
1777 if round_off != 0:
1778 zeros = self.spawn_p.stdout.read(
1779 extractXVA.BLOCK_SIZE - round_off)
1780 except Exception as e:
1781 SMlog("Error: %s. File set extraction failed %s" % (str(e), \
1782 self.__filename))
1784 # Kill and Drain stdout of the gunzip process,
1785 # else gunzip might block on stdout
1786 os.kill(self.spawn_p.pid, signal.SIGTERM)
1787 self.spawn_p.communicate()
1788 raise Exception(str(e))
1790illegal_xml_chars = [(0x00, 0x08), (0x0B, 0x1F), (0x7F, 0x84), (0x86, 0x9F),
1791 (0xD800, 0xDFFF), (0xFDD0, 0xFDDF), (0xFFFE, 0xFFFF),
1792 (0x1FFFE, 0x1FFFF), (0x2FFFE, 0x2FFFF), (0x3FFFE, 0x3FFFF),
1793 (0x4FFFE, 0x4FFFF), (0x5FFFE, 0x5FFFF), (0x6FFFE, 0x6FFFF),
1794 (0x7FFFE, 0x7FFFF), (0x8FFFE, 0x8FFFF), (0x9FFFE, 0x9FFFF),
1795 (0xAFFFE, 0xAFFFF), (0xBFFFE, 0xBFFFF), (0xCFFFE, 0xCFFFF),
1796 (0xDFFFE, 0xDFFFF), (0xEFFFE, 0xEFFFF), (0xFFFFE, 0xFFFFF),
1797 (0x10FFFE, 0x10FFFF)]
1799illegal_ranges = ["%s-%s" % (chr(low), chr(high))
1800 for (low, high) in illegal_xml_chars
1801 if low < sys.maxunicode]
1803illegal_xml_re = re.compile(u'[%s]' % u''.join(illegal_ranges))
1806def isLegalXMLString(s):
1807 """Tells whether this is a valid XML string (i.e. it does not contain
1808 illegal XML characters specified in
1809 http://www.w3.org/TR/2004/REC-xml-20040204/#charsets).
1810 """
1812 if len(s) > 0:
1813 return re.search(illegal_xml_re, s) is None
1814 else:
1815 return True
1818def unictrunc(string, max_bytes):
1819 """
1820 Given a string, returns the largest number of elements for a prefix
1821 substring of it, such that the UTF-8 encoding of this substring takes no
1822 more than the given number of bytes.
1824 The string may be given as a unicode string or a UTF-8 encoded byte
1825 string, and the number returned will be in characters or bytes
1826 accordingly. Note that in the latter case, the substring will still be a
1827 valid UTF-8 encoded string (which is to say, it won't have been truncated
1828 part way through a multibyte sequence for a unicode character).
1830 string: the string to truncate
1831 max_bytes: the maximum number of bytes the truncated string can be
1832 """
1833 if isinstance(string, str):
1834 return_chars = True
1835 else:
1836 return_chars = False
1837 string = string.decode('UTF-8')
1839 cur_chars = 0
1840 cur_bytes = 0
1841 for char in string:
1842 charsize = len(char.encode('UTF-8'))
1843 if cur_bytes + charsize > max_bytes:
1844 break
1845 else:
1846 cur_chars += 1
1847 cur_bytes += charsize
1848 return cur_chars if return_chars else cur_bytes
1851def hideValuesInPropMap(propmap, propnames):
1852 """
1853 Worker function: input simple map of prop name/value pairs, and
1854 a list of specific propnames whose values we want to hide.
1855 Loop through the "hide" list, and if any are found, hide the
1856 value and return the altered map.
1857 If none found, return the original map
1858 """
1859 matches = []
1860 for propname in propnames:
1861 if propname in propmap: 1861 ↛ 1862line 1861 didn't jump to line 1862, because the condition on line 1861 was never true
1862 matches.append(propname)
1864 if matches: 1864 ↛ 1865line 1864 didn't jump to line 1865, because the condition on line 1864 was never true
1865 deepCopyRec = copy.deepcopy(propmap)
1866 for match in matches:
1867 deepCopyRec[match] = '******'
1868 return deepCopyRec
1870 return propmap
1871# define the list of propnames whose value we want to hide
1873PASSWD_PROP_KEYS = ['password', 'cifspassword', 'chappassword', 'incoming_chappassword']
1874DEFAULT_SEGMENT_LEN = 950
1877def hidePasswdInConfig(config):
1878 """
1879 Function to hide passwd values in a simple prop map,
1880 for example "device_config"
1881 """
1882 return hideValuesInPropMap(config, PASSWD_PROP_KEYS)
1885def hidePasswdInParams(params, configProp):
1886 """
1887 Function to hide password values in a specified property which
1888 is a simple map of prop name/values, and is itself an prop entry
1889 in a larger property map.
1890 For example, param maps containing "device_config", or
1891 "sm_config", etc
1892 """
1893 params[configProp] = hideValuesInPropMap(params[configProp], PASSWD_PROP_KEYS)
1894 return params
1897def hideMemberValuesInXmlParams(xmlParams, propnames=PASSWD_PROP_KEYS):
1898 """
1899 Function to hide password values in XML params, specifically
1900 for the XML format of incoming params to SR modules.
1901 Uses text parsing: loop through the list of specific propnames
1902 whose values we want to hide, and:
1903 - Assemble a full "prefix" containing each property name, e.g.,
1904 "<member><name>password</name><value>"
1905 - Test the XML if it contains that string, save the index.
1906 - If found, get the index of the ending tag
1907 - Truncate the return string starting with the password value.
1908 - Append the substitute "*******" value string.
1909 - Restore the rest of the original string starting with the end tag.
1910 """
1911 findStrPrefixHead = "<member><name>"
1912 findStrPrefixTail = "</name><value>"
1913 findStrSuffix = "</value>"
1914 strlen = len(xmlParams)
1916 for propname in propnames:
1917 findStrPrefix = findStrPrefixHead + propname + findStrPrefixTail
1918 idx = xmlParams.find(findStrPrefix)
1919 if idx != -1: # if found any of them
1920 idx += len(findStrPrefix)
1921 idx2 = xmlParams.find(findStrSuffix, idx)
1922 if idx2 != -1:
1923 retStr = xmlParams[0:idx]
1924 retStr += "******"
1925 retStr += xmlParams[idx2:strlen]
1926 return retStr
1927 else:
1928 return xmlParams
1929 return xmlParams
1932def splitXmlText(xmlData, segmentLen=DEFAULT_SEGMENT_LEN, showContd=False):
1933 """
1934 Split xml string data into substrings small enough for the
1935 syslog line length limit. Split at tag end markers ( ">" ).
1936 Usage:
1937 strList = []
1938 strList = splitXmlText( longXmlText, maxLineLen ) # maxLineLen is optional
1939 """
1940 remainingData = str(xmlData)
1942 # "Un-pretty-print"
1943 remainingData = remainingData.replace('\n', '')
1944 remainingData = remainingData.replace('\t', '')
1946 remainingChars = len(remainingData)
1947 returnData = ''
1949 thisLineNum = 0
1950 while remainingChars > segmentLen:
1951 thisLineNum = thisLineNum + 1
1952 index = segmentLen
1953 tmpStr = remainingData[:segmentLen]
1954 tmpIndex = tmpStr.rfind('>')
1955 if tmpIndex != -1:
1956 index = tmpIndex + 1
1958 tmpStr = tmpStr[:index]
1959 remainingData = remainingData[index:]
1960 remainingChars = len(remainingData)
1962 if showContd:
1963 if thisLineNum != 1:
1964 tmpStr = '(Cont\'d): ' + tmpStr
1965 tmpStr = tmpStr + ' (Cont\'d):'
1967 returnData += tmpStr + '\n'
1969 if showContd and thisLineNum > 0:
1970 remainingData = '(Cont\'d): ' + remainingData
1971 returnData += remainingData
1973 return returnData
1976def inject_failure():
1977 raise Exception('injected failure')
1980def open_atomic(path, mode=None):
1981 """Atomically creates a file if, and only if it does not already exist.
1982 Leaves the file open and returns the file object.
1984 path: the path to atomically open
1985 mode: "r" (read), "w" (write), or "rw" (read/write)
1986 returns: an open file object"""
1988 assert path
1990 flags = os.O_CREAT | os.O_EXCL
1991 modes = {'r': os.O_RDONLY, 'w': os.O_WRONLY, 'rw': os.O_RDWR}
1992 if mode:
1993 if mode not in modes:
1994 raise Exception('invalid access mode ' + mode)
1995 flags |= modes[mode]
1996 fd = os.open(path, flags)
1997 try:
1998 if mode:
1999 return os.fdopen(fd, mode)
2000 else:
2001 return os.fdopen(fd)
2002 except:
2003 os.close(fd)
2004 raise
2007def isInvalidVDI(exception):
2008 return exception.details[0] == "HANDLE_INVALID" or \
2009 exception.details[0] == "UUID_INVALID"
2012def get_pool_restrictions(session):
2013 """Returns pool restrictions as a map, @session must be already
2014 established."""
2015 return list(session.xenapi.pool.get_all_records().values())[0]['restrictions']
2018def read_caching_is_restricted(session):
2019 """Tells whether read caching is restricted."""
2020 if session is None: 2020 ↛ 2021line 2020 didn't jump to line 2021, because the condition on line 2020 was never true
2021 return True
2022 restrictions = get_pool_restrictions(session)
2023 if 'restrict_read_caching' in restrictions and \ 2023 ↛ 2025line 2023 didn't jump to line 2025, because the condition on line 2023 was never true
2024 restrictions['restrict_read_caching'] == "true":
2025 return True
2026 return False
2029def sessions_less_than_targets(other_config, device_config):
2030 if 'multihomelist' in device_config and 'iscsi_sessions' in other_config:
2031 sessions = int(other_config['iscsi_sessions'])
2032 targets = len(device_config['multihomelist'].split(','))
2033 SMlog("Targets %d and iscsi_sessions %d" % (targets, sessions))
2034 return (sessions < targets)
2035 else:
2036 return False
2039def enable_and_start_service(name, start):
2040 attempt = 0
2041 while True:
2042 attempt += 1
2043 fn = 'enable' if start else 'disable'
2044 args = ('systemctl', fn, '--now', name)
2045 (ret, out, err) = doexec(args)
2046 if ret == 0:
2047 return
2048 elif attempt >= 3:
2049 raise Exception(
2050 'Failed to {} {}: {} {}'.format(fn, name, out, err)
2051 )
2052 time.sleep(1)
2055def stop_service(name):
2056 args = ('systemctl', 'stop', name)
2057 (ret, out, err) = doexec(args)
2058 if ret == 0:
2059 return
2060 raise Exception('Failed to stop {}: {} {}'.format(name, out, err))
2063def restart_service(name):
2064 attempt = 0
2065 while True:
2066 attempt += 1
2067 SMlog('Restarting service {} {}...'.format(name, attempt))
2068 args = ('systemctl', 'restart', name)
2069 (ret, out, err) = doexec(args)
2070 if ret == 0:
2071 return
2072 elif attempt >= 3:
2073 SMlog('Restart service FAILED {} {}'.format(name, attempt))
2074 raise Exception(
2075 'Failed to restart {}: {} {}'.format(name, out, err)
2076 )
2077 time.sleep(1)
2080def check_pid_exists(pid):
2081 try:
2082 os.kill(pid, 0)
2083 except OSError:
2084 return False
2085 else:
2086 return True
2089def get_openers_pid(path: str) -> Optional[List[int]]:
2090 cmd = ["lsof", "-t", path]
2092 try:
2093 list = []
2094 ret = pread2(cmd)
2095 for line in ret.splitlines():
2096 list.append(int(line))
2097 return list
2098 except CommandException as e:
2099 if e.code == 1: # `lsof` return 1 if there is no openers
2100 return None
2101 else:
2102 raise e
2105def make_profile(name, function):
2106 """
2107 Helper to execute cProfile using unique log file.
2108 """
2110 import cProfile
2111 import itertools
2112 import os.path
2113 import time
2115 assert name
2116 assert function
2118 FOLDER = '/tmp/sm-perfs/'
2119 makedirs(FOLDER)
2121 filename = time.strftime('{}_%Y%m%d_%H%M%S.prof'.format(name))
2123 def gen_path(path):
2124 yield path
2125 root, ext = os.path.splitext(path)
2126 for i in itertools.count(start=1, step=1):
2127 yield root + '.{}.'.format(i) + ext
2129 for profile_path in gen_path(FOLDER + filename):
2130 try:
2131 file = open_atomic(profile_path, 'w')
2132 file.close()
2133 break
2134 except OSError as e:
2135 if e.errno == errno.EEXIST:
2136 pass
2137 else:
2138 raise
2140 try:
2141 SMlog('* Start profiling of {} ({}) *'.format(name, filename))
2142 cProfile.runctx('function()', None, locals(), profile_path)
2143 finally:
2144 SMlog('* End profiling of {} ({}) *'.format(name, filename))
2147def strtobool(str: str) -> bool:
2148 # Note: `distutils` package is deprecated and slated for removal in Python 3.12.
2149 # There is not alternative for strtobool.
2150 # See: https://peps.python.org/pep-0632/#migration-advice
2151 # So this is a custom implementation with differences:
2152 # - A boolean is returned instead of integer
2153 # - Empty string and None are supported (False is returned in this case)
2154 if not str: 2154 ↛ 2156line 2154 didn't jump to line 2156, because the condition on line 2154 was never false
2155 return False
2156 str = str.lower()
2157 if str in ('y', 'yes', 't', 'true', 'on', '1'):
2158 return True
2159 if str in ('n', 'no', 'f', 'false', 'off', '0'):
2160 return False
2161 raise ValueError("invalid truth value '{}'".format(str))
2164def find_executable(name):
2165 return shutil.which(name)
2168def conditional_decorator(decorator, condition):
2169 def wrapper(func):
2170 if not condition:
2171 return func
2172 return decorator(func)
2173 return wrapper