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