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