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