Coverage for drivers/util.py : 43%
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#
19from sm_typing import Any, List, Optional, override
21import contextlib
22import os
23import re
24import sys
25import subprocess
26import shutil
27import tempfile
28import signal
29import time
30import types
31import datetime
32import errno
33import functools
34import socket
35import threading
36import xml.dom.minidom
37import scsiutil
38import stat
39import xs_errors
40import XenAPI # pylint: disable=import-error
41import xmlrpc.client
42import base64
43import syslog
44import resource
45import traceback
46import glob
47import copy
48import tempfile
50from functools import reduce
51from sm_typing import List, Optional
53NO_LOGGING_STAMPFILE = '/etc/xensource/no_sm_log'
55IORETRY_MAX = 20 # retries
56IORETRY_PERIOD = 1.0 # seconds
58LOGGING = not (os.path.exists(NO_LOGGING_STAMPFILE))
59_SM_SYSLOG_FACILITY = syslog.LOG_LOCAL2
60LOG_EMERG = syslog.LOG_EMERG
61LOG_ALERT = syslog.LOG_ALERT
62LOG_CRIT = syslog.LOG_CRIT
63LOG_ERR = syslog.LOG_ERR
64LOG_WARNING = syslog.LOG_WARNING
65LOG_NOTICE = syslog.LOG_NOTICE
66LOG_INFO = syslog.LOG_INFO
67LOG_DEBUG = syslog.LOG_DEBUG
69ISCSI_REFDIR = '/var/run/sr-ref'
71CMD_DD = "/bin/dd"
72CMD_KICKPIPE = '/opt/xensource/libexec/kickpipe'
74FIST_PAUSE_PERIOD = 30 # seconds
76class _TimeoutContextManager(contextlib.AbstractContextManager):
77 def __init__(self, delay: int) -> None:
78 self.delay = delay
79 self.old_handler: Any = None
81 @override
82 def __enter__(self) -> "_TimeoutContextManager":
83 def handle_timeout(_signum: int, _frame: Optional[types.FrameType]) -> None:
84 raise TimeoutError(f"Timed out after {self.delay} seconds")
85 self.old_handler = signal.signal(signal.SIGALRM, handle_timeout)
86 signal.alarm(self.delay)
87 return self
89 @override
90 def __exit__(
91 self,
92 exc_type: Optional[Any],
93 exc_val: Optional[BaseException],
94 exc_tb: Optional[types.TracebackType]
95 ):
96 signal.alarm(0)
97 if self.old_handler is not None:
98 signal.signal(signal.SIGALRM, self.old_handler)
99 self.old_handler = None
100 return False
102 def __call__(self, func):
103 def wrapper(*args, **kwargs):
104 with self:
105 return func(*args, **kwargs)
106 return wrapper
108def timeout(*args, **kwargs):
109 if len(args) >= 2 and callable(args[1]):
110 delay, func = args[0], args[1]
111 remaining_args = args[2:]
112 with _TimeoutContextManager(delay):
113 return func(*remaining_args, **kwargs)
115 if len(args) == 1 and callable(args[0]):
116 raise TypeError("timeout() missing 1 required argument: 'delay'")
118 if args:
119 delay = args[0]
120 elif "delay" in kwargs:
121 delay = kwargs["delay"]
122 else:
123 raise TypeError("timeout() missing 1 required argument: 'delay'")
124 return _TimeoutContextManager(delay)
126class SMException(Exception):
127 """Base class for all SM exceptions for easier catching & wrapping in
128 XenError"""
131class CommandException(SMException):
132 def error_message(self, code):
133 if code > 0:
134 return os.strerror(code)
135 elif code < 0:
136 return "Signalled %s" % (abs(code))
137 return "Success"
139 def __init__(self, code, cmd="", reason='exec failed'):
140 self.code = code
141 self.cmd = cmd
142 self.reason = reason
143 Exception.__init__(self, self.error_message(code))
146class SRBusyException(SMException):
147 """The SR could not be locked"""
148 pass
151def logException(tag):
152 info = sys.exc_info()
153 if info[0] == SystemExit: 153 ↛ 155line 153 didn't jump to line 155, because the condition on line 153 was never true
154 # this should not be happening when catching "Exception", but it is
155 sys.exit(0)
156 tb = reduce(lambda a, b: "%s%s" % (a, b), traceback.format_tb(info[2]))
157 str = "***** %s: EXCEPTION %s, %s\n%s" % (tag, info[0], info[1], tb)
158 SMlog(str)
161def roundup(divisor, value):
162 """Retruns the rounded up value so it is divisible by divisor."""
164 if value == 0: 164 ↛ 165line 164 didn't jump to line 165, because the condition on line 164 was never true
165 value = 1
166 if value % divisor != 0:
167 return ((int(value) // divisor) + 1) * divisor
168 return value
171def to_plain_string(obj):
172 if obj is None:
173 return None
174 if isinstance(obj, dict) and len(obj) == 0:
175 SMlog(f"util.to_plain_string() corrected empty dict to empty str")
176 return ""
177 return str(obj)
180def shellquote(arg):
181 return '"%s"' % arg.replace('"', '\\"')
184def make_WWN(name):
185 hex_prefix = name.find("0x")
186 if (hex_prefix >= 0): 186 ↛ 189line 186 didn't jump to line 189, because the condition on line 186 was never false
187 name = name[name.find("0x") + 2:len(name)]
188 # inject dashes for each nibble
189 if (len(name) == 16): # sanity check 189 ↛ 193line 189 didn't jump to line 193, because the condition on line 189 was never false
190 name = name[0:2] + "-" + name[2:4] + "-" + name[4:6] + "-" + \
191 name[6:8] + "-" + name[8:10] + "-" + name[10:12] + "-" + \
192 name[12:14] + "-" + name[14:16]
193 return name
196def synchronized(func):
197 lock = threading.RLock()
199 @functools.wraps(func)
200 def wrapper(*args, **kwargs):
201 with lock:
202 return func(*args, **kwargs)
204 return wrapper
207@synchronized
208def _writeToSyslog(ident, facility, priority, message):
209 syslog.openlog(ident, 0, facility)
210 syslog.syslog(priority, message)
211 syslog.closelog()
214def _logToSyslog(ident, facility, priority, message):
215 pid = os.getpid()
216 thread_name = threading.current_thread().name
217 _writeToSyslog(ident, facility, priority, f"[{pid}][{thread_name}] {message}")
220def SMlog(message, ident="SM", priority=LOG_INFO):
221 if LOGGING: 221 ↛ exitline 221 didn't return from function 'SMlog', because the condition on line 221 was never false
222 for message_line in str(message).split('\n'):
223 _logToSyslog(ident, _SM_SYSLOG_FACILITY, priority, message_line)
226class LoggerCounter:
227 def __init__(self, max_repeats):
228 self.previous_message = None
229 self.max_repeats = max_repeats
230 self.repeat_counter = 0
232 def log(self, message):
233 self.repeat_counter += 1
234 if self.previous_message != message or self.repeat_counter == self.max_repeats:
235 SMlog(message)
236 self.previous_message = message
237 self.repeat_counter = 0
239def _getDateString():
240 d = datetime.datetime.now()
241 t = d.timetuple()
242 return "%s-%s-%s:%s:%s:%s" % \
243 (t[0], t[1], t[2], t[3], t[4], t[5])
246def doexec(args, inputtext=None, new_env=None, text=True):
247 """Execute a subprocess, then return its return code, stdout and stderr"""
248 env = None
249 if new_env:
250 env = dict(os.environ)
251 env.update(new_env)
252 proc = subprocess.Popen(args, stdin=subprocess.PIPE,
253 stdout=subprocess.PIPE,
254 stderr=subprocess.PIPE,
255 close_fds=True, env=env,
256 universal_newlines=text)
258 if not text and inputtext is not None: 258 ↛ 259line 258 didn't jump to line 259, because the condition on line 258 was never true
259 inputtext = inputtext.encode()
261 (stdout, stderr) = proc.communicate(inputtext)
263 rc = proc.returncode
264 return rc, stdout, stderr
267def is_string(value):
268 return isinstance(value, str)
271# These are partially tested functions that replicate the behaviour of
272# the original pread,pread2 and pread3 functions. Potentially these can
273# replace the original ones at some later date.
274#
275# cmdlist is a list of either single strings or pairs of strings. For
276# each pair, the first component is passed to exec while the second is
277# written to the logs.
278def pread(cmdlist, close_stdin=False, scramble=None, expect_rc=0,
279 quiet=False, new_env=None, text=True):
280 cmdlist_for_exec = []
281 cmdlist_for_log = []
282 for item in cmdlist:
283 if is_string(item): 283 ↛ 293line 283 didn't jump to line 293, because the condition on line 283 was never false
284 cmdlist_for_exec.append(item)
285 if scramble: 285 ↛ 286line 285 didn't jump to line 286, because the condition on line 285 was never true
286 if item.find(scramble) != -1:
287 cmdlist_for_log.append("<filtered out>")
288 else:
289 cmdlist_for_log.append(item)
290 else:
291 cmdlist_for_log.append(item)
292 else:
293 cmdlist_for_exec.append(item[0])
294 cmdlist_for_log.append(item[1])
296 if not quiet: 296 ↛ 298line 296 didn't jump to line 298, because the condition on line 296 was never false
297 SMlog(cmdlist_for_log)
298 (rc, stdout, stderr) = doexec(cmdlist_for_exec, new_env=new_env, text=text)
299 if rc != expect_rc:
300 SMlog("FAILED in util.pread: (rc %d) stdout: '%s', stderr: '%s'" % \
301 (rc, stdout, stderr))
302 if quiet: 302 ↛ 303line 302 didn't jump to line 303, because the condition on line 302 was never true
303 SMlog("Command was: %s" % cmdlist_for_log)
304 if '' == stderr: 304 ↛ 305line 304 didn't jump to line 305, because the condition on line 304 was never true
305 stderr = stdout
306 raise CommandException(rc, str(cmdlist), stderr.strip())
307 if not quiet: 307 ↛ 309line 307 didn't jump to line 309, because the condition on line 307 was never false
308 SMlog(" pread SUCCESS")
309 return stdout
312# POSIX guaranteed atomic within the same file system.
313# Supply directory to ensure tempfile is created
314# in the same directory.
315def atomicFileWrite(targetFile, directory, text):
317 file = None
318 try:
319 # Create file only current pid can write/read to
320 # our responsibility to clean it up.
321 _, tempPath = tempfile.mkstemp(dir=directory)
322 file = open(tempPath, 'w')
323 file.write(text)
325 # Ensure flushed to disk.
326 file.flush()
327 os.fsync(file.fileno())
328 file.close()
330 os.rename(tempPath, targetFile)
331 except OSError:
332 SMlog("FAILED to atomic write to %s" % (targetFile))
334 finally:
335 if (file is not None) and (not file.closed):
336 file.close()
338 if os.path.isfile(tempPath):
339 os.remove(tempPath)
342#Read STDOUT from cmdlist and discard STDERR output
343def pread2(cmdlist, quiet=False, text=True):
344 return pread(cmdlist, quiet=quiet, text=text)
347#Read STDOUT from cmdlist, feeding 'text' to STDIN
348def pread3(cmdlist, text):
349 SMlog(cmdlist)
350 (rc, stdout, stderr) = doexec(cmdlist, text)
351 if rc:
352 SMlog("FAILED in util.pread3: (errno %d) stdout: '%s', stderr: '%s'" % \
353 (rc, stdout, stderr))
354 if '' == stderr:
355 stderr = stdout
356 raise CommandException(rc, str(cmdlist), stderr.strip())
357 SMlog(" pread3 SUCCESS")
358 return stdout
361def listdir(path, quiet=False):
362 cmd = ["ls", path, "-1", "--color=never"]
363 try:
364 text = pread2(cmd, quiet=quiet)[:-1]
365 if len(text) == 0:
366 return []
367 return text.split('\n')
368 except CommandException as inst:
369 if inst.code == errno.ENOENT:
370 raise CommandException(errno.EIO, inst.cmd, inst.reason)
371 else:
372 raise CommandException(inst.code, inst.cmd, inst.reason)
375def gen_uuid():
376 cmd = ["uuidgen", "-r"]
377 return pread(cmd)[:-1]
380def match_uuid(s):
381 regex = re.compile("^[0-9a-f]{8}-(([0-9a-f]{4})-){3}[0-9a-f]{12}")
382 return regex.search(s, 0)
385def findall_uuid(s):
386 regex = re.compile("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}")
387 return regex.findall(s, 0)
390def exactmatch_uuid(s):
391 regex = re.compile("^[0-9a-f]{8}-(([0-9a-f]{4})-){3}[0-9a-f]{12}$")
392 return regex.search(s, 0)
395def start_log_entry(srpath, path, args):
396 logstring = str(datetime.datetime.now())
397 logstring += " log: "
398 logstring += srpath
399 logstring += " " + path
400 for element in args:
401 logstring += " " + element
402 try:
403 file = open(srpath + "/filelog.txt", "a")
404 file.write(logstring)
405 file.write("\n")
406 file.close()
407 except:
408 pass
410 # failed to write log ...
412def end_log_entry(srpath, path, args):
413 # for teminating, use "error" or "done"
414 logstring = str(datetime.datetime.now())
415 logstring += " end: "
416 logstring += srpath
417 logstring += " " + path
418 for element in args:
419 logstring += " " + element
420 try:
421 file = open(srpath + "/filelog.txt", "a")
422 file.write(logstring)
423 file.write("\n")
424 file.close()
425 except:
426 pass
428 # failed to write log ...
429 # for now print
430 # print "%s" % logstring
432def ioretry(f, errlist=[errno.EIO], maxretry=IORETRY_MAX, period=IORETRY_PERIOD, **ignored):
433 retries = 0
434 while True:
435 try:
436 return f()
437 except OSError as ose:
438 err = int(ose.errno)
439 if not err in errlist:
440 raise CommandException(err, str(f), "OSError")
441 except CommandException as ce:
442 if not int(ce.code) in errlist:
443 raise
445 retries += 1
446 if retries >= maxretry:
447 break
449 time.sleep(period)
451 raise CommandException(errno.ETIMEDOUT, str(f), "Timeout")
454def ioretry_stat(path, maxretry=IORETRY_MAX):
455 # this ioretry is similar to the previous method, but
456 # stat does not raise an error -- so check its return
457 retries = 0
458 while retries < maxretry:
459 stat = os.statvfs(path)
460 if stat.f_blocks != -1:
461 return stat
462 time.sleep(1)
463 retries += 1
464 raise CommandException(errno.EIO, "os.statvfs")
467def sr_get_capability(sr_uuid, session=None):
468 result = []
469 local_session = None
470 if session is None: 470 ↛ 474line 470 didn't jump to line 474, because the condition on line 470 was never false
471 local_session = get_localAPI_session()
472 session = local_session
474 try:
475 sr_ref = session.xenapi.SR.get_by_uuid(sr_uuid)
476 sm_type = session.xenapi.SR.get_record(sr_ref)['type']
477 sm_rec = session.xenapi.SM.get_all_records_where(
478 "field \"type\" = \"%s\"" % sm_type)
480 # SM expects at least one entry of any SR type
481 if len(sm_rec) > 0:
482 result = list(sm_rec.values())[0]['capabilities']
484 return result
485 finally:
486 if local_session: 486 ↛ exitline 486 didn't return from function 'sr_get_capability', because the return on line 484 wasn't executed
487 local_session.xenapi.session.logout()
489def sr_get_driver_info(driver_info):
490 results = {}
491 # first add in the vanilla stuff
492 for key in ['name', 'description', 'vendor', 'copyright', \
493 'driver_version', 'required_api_version']:
494 results[key] = driver_info[key]
495 # add the capabilities (xmlrpc array)
496 # enforcing activate/deactivate for blktap2
497 caps = driver_info['capabilities']
498 if "ATOMIC_PAUSE" in caps: 498 ↛ 499line 498 didn't jump to line 499, because the condition on line 498 was never true
499 for cap in ("VDI_ACTIVATE", "VDI_DEACTIVATE"):
500 if not cap in caps:
501 caps.append(cap)
502 elif "VDI_ACTIVATE" in caps or "VDI_DEACTIVATE" in caps: 502 ↛ 503line 502 didn't jump to line 503, because the condition on line 502 was never true
503 SMlog("Warning: vdi_[de]activate present for %s" % driver_info["name"])
505 results['capabilities'] = caps
506 # add in the configuration options
507 options = []
508 for option in driver_info['configuration']:
509 options.append({'key': option[0], 'description': option[1]})
510 results['configuration'] = options
511 return xmlrpc.client.dumps((results, ), "", True)
514def return_nil():
515 return xmlrpc.client.dumps((None, ), "", True, allow_none=True)
518def SRtoXML(SRlist):
519 dom = xml.dom.minidom.Document()
520 driver = dom.createElement("SRlist")
521 dom.appendChild(driver)
523 for key in SRlist.keys():
524 dict = SRlist[key]
525 entry = dom.createElement("SR")
526 driver.appendChild(entry)
528 e = dom.createElement("UUID")
529 entry.appendChild(e)
530 textnode = dom.createTextNode(key)
531 e.appendChild(textnode)
533 if 'size' in dict:
534 e = dom.createElement("Size")
535 entry.appendChild(e)
536 textnode = dom.createTextNode(str(dict['size']))
537 e.appendChild(textnode)
539 if 'storagepool' in dict:
540 e = dom.createElement("StoragePool")
541 entry.appendChild(e)
542 textnode = dom.createTextNode(str(dict['storagepool']))
543 e.appendChild(textnode)
545 if 'aggregate' in dict:
546 e = dom.createElement("Aggregate")
547 entry.appendChild(e)
548 textnode = dom.createTextNode(str(dict['aggregate']))
549 e.appendChild(textnode)
551 return dom.toprettyxml()
554def pathexists(path):
555 try:
556 os.lstat(path)
557 return True
558 except OSError as inst:
559 if inst.errno == errno.EIO: 559 ↛ 560line 559 didn't jump to line 560, because the condition on line 559 was never true
560 time.sleep(1)
561 try:
562 listdir(os.path.realpath(os.path.dirname(path)))
563 os.lstat(path)
564 return True
565 except:
566 pass
567 raise CommandException(errno.EIO, "os.lstat(%s)" % path, "failed")
568 return False
571def force_unlink(path):
572 try:
573 os.unlink(path)
574 except OSError as e:
575 if e.errno != errno.ENOENT: 575 ↛ 576line 575 didn't jump to line 576, because the condition on line 575 was never true
576 raise
579def create_secret(session, secret):
580 ref = session.xenapi.secret.create({'value': secret})
581 return session.xenapi.secret.get_uuid(ref)
584def get_secret(session, uuid):
585 try:
586 ref = session.xenapi.secret.get_by_uuid(uuid)
587 return session.xenapi.secret.get_value(ref)
588 except:
589 raise xs_errors.XenError('InvalidSecret', opterr='Unable to look up secret [%s]' % uuid)
592def get_real_path(path):
593 "Follow symlinks to the actual file"
594 absPath = path
595 directory = ''
596 while os.path.islink(absPath):
597 directory = os.path.dirname(absPath)
598 absPath = os.readlink(absPath)
599 absPath = os.path.join(directory, absPath)
600 return absPath
603def wait_for_path(path, timeout):
604 for i in range(0, timeout): 604 ↛ 608line 604 didn't jump to line 608, because the loop on line 604 didn't complete
605 if len(glob.glob(path)): 605 ↛ 607line 605 didn't jump to line 607, because the condition on line 605 was never false
606 return True
607 time.sleep(1)
608 return False
611def wait_for_nopath(path, timeout):
612 for i in range(0, timeout):
613 if not os.path.exists(path):
614 return True
615 time.sleep(1)
616 return False
619def wait_for_path_multi(path, timeout):
620 for i in range(0, timeout):
621 paths = glob.glob(path)
622 SMlog("_wait_for_paths_multi: paths = %s" % paths)
623 if len(paths):
624 SMlog("_wait_for_paths_multi: return first path: %s" % paths[0])
625 return paths[0]
626 time.sleep(1)
627 return ""
630def isdir(path):
631 try:
632 st = os.stat(path)
633 return stat.S_ISDIR(st.st_mode)
634 except OSError as inst:
635 if inst.errno == errno.EIO: 635 ↛ 636line 635 didn't jump to line 636, because the condition on line 635 was never true
636 raise CommandException(errno.EIO, "os.stat(%s)" % path, "failed")
637 return False
640def get_single_entry(path):
641 f = open(path, 'r')
642 line = f.readline()
643 f.close()
644 return line.rstrip()
647def get_fs_size(path):
648 st = ioretry_stat(path)
649 return st.f_blocks * st.f_frsize
652def get_fs_utilisation(path):
653 st = ioretry_stat(path)
654 return (st.f_blocks - st.f_bfree) * \
655 st.f_frsize
658def ismount(path):
659 """Test whether a path is a mount point"""
660 try:
661 s1 = os.stat(path)
662 s2 = os.stat(os.path.join(path, '..'))
663 except OSError as inst:
664 raise CommandException(inst.errno, "os.stat")
665 dev1 = s1.st_dev
666 dev2 = s2.st_dev
667 if dev1 != dev2:
668 return True # path/.. on a different device as path
669 ino1 = s1.st_ino
670 ino2 = s2.st_ino
671 if ino1 == ino2:
672 return True # path/.. is the same i-node as path
673 return False
676def makedirs(name, mode=0o777):
677 head, tail = os.path.split(name)
678 if not tail: 678 ↛ 679line 678 didn't jump to line 679, because the condition on line 678 was never true
679 head, tail = os.path.split(head)
680 if head and tail and not pathexists(head):
681 makedirs(head, mode)
682 if tail == os.curdir: 682 ↛ 683line 682 didn't jump to line 683, because the condition on line 682 was never true
683 return
684 try:
685 os.mkdir(name, mode)
686 except OSError as exc:
687 if exc.errno == errno.EEXIST and os.path.isdir(name): 687 ↛ 688line 687 didn't jump to line 688, because the condition on line 687 was never true
688 if mode:
689 os.chmod(name, mode)
690 pass
691 else:
692 raise
695def zeroOut(path, fromByte, bytes):
696 """write 'bytes' zeros to 'path' starting from fromByte (inclusive)"""
697 blockSize = 4096
699 fromBlock = fromByte // blockSize
700 if fromByte % blockSize:
701 fromBlock += 1
702 bytesBefore = fromBlock * blockSize - fromByte
703 if bytesBefore > bytes:
704 bytesBefore = bytes
705 bytes -= bytesBefore
706 cmd = [CMD_DD, "if=/dev/zero", "of=%s" % path, "bs=1",
707 "seek=%s" % fromByte, "count=%s" % bytesBefore]
708 try:
709 pread2(cmd)
710 except CommandException:
711 return False
713 blocks = bytes // blockSize
714 bytes -= blocks * blockSize
715 fromByte = (fromBlock + blocks) * blockSize
716 if blocks:
717 cmd = [CMD_DD, "if=/dev/zero", "of=%s" % path, "bs=%s" % blockSize,
718 "seek=%s" % fromBlock, "count=%s" % blocks]
719 try:
720 pread2(cmd)
721 except CommandException:
722 return False
724 if bytes:
725 cmd = [CMD_DD, "if=/dev/zero", "of=%s" % path, "bs=1",
726 "seek=%s" % fromByte, "count=%s" % bytes]
727 try:
728 pread2(cmd)
729 except CommandException:
730 return False
732 return True
735def wipefs(blockdev):
736 "Wipe filesystem signatures from `blockdev`"
737 pread2(["/usr/sbin/wipefs", "-a", blockdev])
740def match_rootdev(s):
741 regex = re.compile("^PRIMARY_DISK")
742 return regex.search(s, 0)
745def getrootdev():
746 filename = '/etc/xensource-inventory'
747 try:
748 f = open(filename, 'r')
749 except:
750 raise xs_errors.XenError('EIO', \
751 opterr="Unable to open inventory file [%s]" % filename)
752 rootdev = ''
753 for line in filter(match_rootdev, f.readlines()):
754 rootdev = line.split("'")[1]
755 if not rootdev: 755 ↛ 756line 755 didn't jump to line 756, because the condition on line 755 was never true
756 raise xs_errors.XenError('NoRootDev')
757 return rootdev
760def getrootdevID():
761 rootdev = getrootdev()
762 try:
763 rootdevID = scsiutil.getSCSIid(rootdev)
764 except:
765 SMlog("util.getrootdevID: Unable to verify serial or SCSIid of device: %s" \
766 % rootdev)
767 return ''
769 if not len(rootdevID):
770 SMlog("util.getrootdevID: Unable to identify scsi device [%s] via scsiID" \
771 % rootdev)
773 return rootdevID
776def get_localAPI_session():
777 # First acquire a valid session
778 session = XenAPI.xapi_local()
779 try:
780 session.xenapi.login_with_password('root', '', '', 'SM')
781 except:
782 raise xs_errors.XenError('APISession')
783 return session
786def get_this_host():
787 uuid = None
788 f = open("/etc/xensource-inventory", 'r')
789 for line in f.readlines():
790 if line.startswith("INSTALLATION_UUID"):
791 uuid = line.split("'")[1]
792 f.close()
793 return uuid
796def get_master_ref(session):
797 pools = session.xenapi.pool.get_all()
798 return session.xenapi.pool.get_master(pools[0])
801def is_master(session):
802 return get_this_host_ref(session) == get_master_ref(session)
805def get_localhost_ref(session):
806 filename = '/etc/xensource-inventory'
807 try:
808 f = open(filename, 'r')
809 except:
810 raise xs_errors.XenError('EIO', \
811 opterr="Unable to open inventory file [%s]" % filename)
812 domid = ''
813 for line in filter(match_domain_id, f.readlines()):
814 domid = line.split("'")[1]
815 if not domid:
816 raise xs_errors.XenError('APILocalhost')
818 vms = session.xenapi.VM.get_all_records_where('field "uuid" = "%s"' % domid)
819 for vm in vms:
820 record = vms[vm]
821 if record["uuid"] == domid:
822 hostid = record["resident_on"]
823 return hostid
824 raise xs_errors.XenError('APILocalhost')
827def match_domain_id(s):
828 regex = re.compile("^CONTROL_DOMAIN_UUID")
829 return regex.search(s, 0)
832def get_hosts_attached_on(session, vdi_uuids):
833 host_refs = {}
834 for vdi_uuid in vdi_uuids:
835 try:
836 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
837 except XenAPI.Failure:
838 SMlog("VDI %s not in db, ignoring" % vdi_uuid)
839 continue
840 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
841 for key in [x for x in sm_config.keys() if x.startswith('host_')]:
842 host_refs[key[len('host_'):]] = True
843 return host_refs.keys()
845def get_hosts_attached_on_with_vdi_uuid(session, vdi_uuids):
846 """
847 Return a dict of {vdi_uuid: host OpaqueRef}
848 """
849 host_refs = {}
850 for vdi_uuid in vdi_uuids:
851 try:
852 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
853 except XenAPI.Failure:
854 SMlog("VDI %s not in db, ignoring" % vdi_uuid)
855 continue
856 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
857 for key in [x for x in sm_config.keys() if x.startswith('host_')]:
858 host_refs[vdi_uuid] = key[len('host_'):]
859 return host_refs
861def get_this_host_address(session):
862 host_uuid = get_this_host()
863 host_ref = session.xenapi.host.get_by_uuid(host_uuid)
864 return session.xenapi.host.get_record(host_ref)['address']
866def get_host_addresses(session):
867 addresses = []
868 hosts = session.xenapi.host.get_all_records()
869 for record in hosts.values():
870 addresses.append(record['address'])
871 return addresses
873def get_this_host_ref(session):
874 host_uuid = get_this_host()
875 host_ref = session.xenapi.host.get_by_uuid(host_uuid)
876 return host_ref
879def get_slaves_attached_on(session, vdi_uuids):
880 "assume this host is the SR master"
881 host_refs = get_hosts_attached_on(session, vdi_uuids)
882 master_ref = get_this_host_ref(session)
883 return [x for x in host_refs if x != master_ref]
885def get_enabled_hosts(session):
886 """
887 Returns a list of host refs that are enabled in the pool.
888 """
889 return list(session.xenapi.host.get_all_records_where('field "enabled" = "true"').keys())
891def get_online_hosts(session):
892 online_hosts = []
893 hosts = session.xenapi.host.get_all_records()
894 for host_ref, host_rec in hosts.items():
895 metricsRef = host_rec["metrics"]
896 metrics = session.xenapi.host_metrics.get_record(metricsRef)
897 if metrics["live"]:
898 online_hosts.append(host_ref)
899 return online_hosts
902def get_all_slaves(session):
903 "assume this host is the SR master"
904 host_refs = get_online_hosts(session)
905 master_ref = get_this_host_ref(session)
906 return [x for x in host_refs if x != master_ref]
909def is_attached_rw(sm_config):
910 for key, val in sm_config.items():
911 if key.startswith("host_") and val == "RW":
912 return True
913 return False
916def attached_as(sm_config):
917 for key, val in sm_config.items():
918 if key.startswith("host_") and (val == "RW" or val == "RO"): 918 ↛ 919line 918 didn't jump to line 919, because the condition on line 918 was never true
919 return val
922def find_my_pbd_record(session, host_ref, sr_ref):
923 try:
924 pbds = session.xenapi.PBD.get_all_records()
925 for pbd_ref in pbds.keys():
926 if pbds[pbd_ref]['host'] == host_ref and pbds[pbd_ref]['SR'] == sr_ref:
927 return [pbd_ref, pbds[pbd_ref]]
928 return None
929 except Exception as e:
930 SMlog("Caught exception while looking up PBD for host %s SR %s: %s" % (str(host_ref), str(sr_ref), str(e)))
931 return None
934def find_my_pbd(session, host_ref, sr_ref):
935 ret = find_my_pbd_record(session, host_ref, sr_ref)
936 if ret is not None:
937 return ret[0]
938 else:
939 return None
942def test_hostPBD_devs(session, sr_uuid, devs):
943 host = get_localhost_ref(session)
944 sr = session.xenapi.SR.get_by_uuid(sr_uuid)
945 try:
946 pbds = session.xenapi.PBD.get_all_records()
947 except:
948 raise xs_errors.XenError('APIPBDQuery')
949 for dev in devs.split(','):
950 for pbd in pbds:
951 record = pbds[pbd]
952 # it's ok if it's *our* PBD
953 if record["SR"] == sr:
954 break
955 if record["host"] == host:
956 devconfig = record["device_config"]
957 if 'device' in devconfig:
958 for device in devconfig['device'].split(','):
959 if os.path.realpath(device) == os.path.realpath(dev):
960 return True
961 return False
964def test_hostPBD_lun(session, targetIQN, LUNid):
965 host = get_localhost_ref(session)
966 try:
967 pbds = session.xenapi.PBD.get_all_records()
968 except:
969 raise xs_errors.XenError('APIPBDQuery')
970 for pbd in pbds:
971 record = pbds[pbd]
972 if record["host"] == host:
973 devconfig = record["device_config"]
974 if 'targetIQN' in devconfig and 'LUNid' in devconfig:
975 if devconfig['targetIQN'] == targetIQN and \
976 devconfig['LUNid'] == LUNid:
977 return True
978 return False
981def test_SCSIid(session, sr_uuid, SCSIid):
982 if sr_uuid is not None:
983 sr = session.xenapi.SR.get_by_uuid(sr_uuid)
984 try:
985 pbds = session.xenapi.PBD.get_all_records()
986 except:
987 raise xs_errors.XenError('APIPBDQuery')
988 for pbd in pbds:
989 record = pbds[pbd]
990 # it's ok if it's *our* PBD
991 # During FC SR creation, devscan.py passes sr_uuid as None
992 if sr_uuid is not None:
993 if record["SR"] == sr:
994 break
995 devconfig = record["device_config"]
996 sm_config = session.xenapi.SR.get_sm_config(record["SR"])
997 if 'SCSIid' in devconfig and devconfig['SCSIid'] == SCSIid:
998 return True
999 elif 'SCSIid' in sm_config and sm_config['SCSIid'] == SCSIid:
1000 return True
1001 elif 'scsi-' + SCSIid in sm_config:
1002 return True
1003 return False
1006class TimeoutException(SMException):
1007 pass
1010def _incr_iscsiSR_refcount(targetIQN, uuid):
1011 if not os.path.exists(ISCSI_REFDIR):
1012 os.mkdir(ISCSI_REFDIR)
1013 filename = os.path.join(ISCSI_REFDIR, targetIQN)
1014 try:
1015 f = open(filename, 'a+')
1016 except:
1017 raise xs_errors.XenError('LVMRefCount', \
1018 opterr='file %s' % filename)
1020 f.seek(0)
1021 found = False
1022 refcount = 0
1023 for line in filter(match_uuid, f.readlines()):
1024 refcount += 1
1025 if line.find(uuid) != -1:
1026 found = True
1027 if not found:
1028 f.write("%s\n" % uuid)
1029 refcount += 1
1030 f.close()
1031 return refcount
1034def _decr_iscsiSR_refcount(targetIQN, uuid):
1035 filename = os.path.join(ISCSI_REFDIR, targetIQN)
1036 if not os.path.exists(filename):
1037 return 0
1038 try:
1039 f = open(filename, 'a+')
1040 except:
1041 raise xs_errors.XenError('LVMRefCount', \
1042 opterr='file %s' % filename)
1044 f.seek(0)
1045 output = []
1046 refcount = 0
1047 for line in filter(match_uuid, f.readlines()):
1048 if line.find(uuid) == -1:
1049 output.append(line.rstrip())
1050 refcount += 1
1051 if not refcount:
1052 os.unlink(filename)
1053 return refcount
1055 # Re-open file and truncate
1056 f.close()
1057 f = open(filename, 'w')
1058 for i in range(0, refcount):
1059 f.write("%s\n" % output[i])
1060 f.close()
1061 return refcount
1064# The agent enforces 1 PBD per SR per host, so we
1065# check for active SR entries not attached to this host
1066def test_activePoolPBDs(session, host, uuid):
1067 try:
1068 pbds = session.xenapi.PBD.get_all_records()
1069 except:
1070 raise xs_errors.XenError('APIPBDQuery')
1071 for pbd in pbds:
1072 record = pbds[pbd]
1073 if record["host"] != host and record["SR"] == uuid \
1074 and record["currently_attached"]:
1075 return True
1076 return False
1079def remove_mpathcount_field(session, host_ref, sr_ref, SCSIid):
1080 try:
1081 pbdref = find_my_pbd(session, host_ref, sr_ref)
1082 if pbdref is not None:
1083 key = "mpath-" + SCSIid
1084 session.xenapi.PBD.remove_from_other_config(pbdref, key)
1085 except:
1086 pass
1089def kickpipe_mpathcount():
1090 """
1091 Issue a kick to the mpathcount service. This will ensure that mpathcount runs
1092 shortly to update the multipath config records, if it was not already activated
1093 by a UDEV event.
1094 """
1095 cmd = [CMD_KICKPIPE, "mpathcount"]
1096 (rc, stdout, stderr) = doexec(cmd)
1097 return (rc == 0)
1100def _testHost(hostname, port, errstring):
1101 SMlog("_testHost: Testing host/port: %s,%d" % (hostname, port))
1102 try:
1103 sockinfo = socket.getaddrinfo(hostname, int(port))[0]
1104 except:
1105 logException('Exception occured getting IP for %s' % hostname)
1106 raise xs_errors.XenError('DNSError')
1108 timeout = 5
1110 sock = socket.socket(sockinfo[0], socket.SOCK_STREAM)
1111 # Only allow the connect to block for up to timeout seconds
1112 sock.settimeout(timeout)
1113 try:
1114 sock.connect(sockinfo[4])
1115 # Fix for MS storage server bug
1116 sock.send(b'\n')
1117 sock.close()
1118 except socket.error as reason:
1119 SMlog("_testHost: Connect failed after %d seconds (%s) - %s" \
1120 % (timeout, hostname, reason))
1121 raise xs_errors.XenError(errstring)
1124def match_scsiID(s, id):
1125 regex = re.compile(id)
1126 return regex.search(s, 0)
1129def _isSCSIid(s):
1130 regex = re.compile("^scsi-")
1131 return regex.search(s, 0)
1134def is_usb_device(device):
1135 cmd = ["udevadm", "info", "-q", "path", "-n", device]
1136 result = pread2(cmd).split('/')
1137 return len(result) >= 5 and result[4].startswith('usb')
1140def test_scsiserial(session, device):
1141 device = os.path.realpath(device)
1142 if not scsiutil._isSCSIdev(device):
1143 SMlog("util.test_scsiserial: Not a serial device: %s" % device)
1144 return False
1145 serial = ""
1146 try:
1147 serial += scsiutil.getserial(device)
1148 except:
1149 # Error allowed, SCSIid is the important one
1150 pass
1152 try:
1153 scsiID = scsiutil.getSCSIid(device)
1154 except:
1155 SMlog("util.test_scsiserial: Unable to verify serial or SCSIid of device: %s" \
1156 % device)
1157 return False
1158 if not len(scsiID):
1159 SMlog("util.test_scsiserial: Unable to identify scsi device [%s] via scsiID" \
1160 % device)
1161 return False
1163 # USB devices can have identical SCSI IDs - prefer matching with serial number
1164 try:
1165 usb_device_with_serial = serial and is_usb_device(device)
1166 except:
1167 usb_device_with_serial = False
1168 SMlog("Unable to check if device is USB:")
1169 SMlog(traceback.format_exc())
1171 try:
1172 SRs = session.xenapi.SR.get_all_records()
1173 except:
1174 raise xs_errors.XenError('APIFailure')
1175 for SR in SRs:
1176 record = SRs[SR]
1177 conf = record["sm_config"]
1178 if 'devserial' in conf:
1179 for dev in conf['devserial'].split(','):
1180 if not usb_device_with_serial and _isSCSIid(dev):
1181 if match_scsiID(dev, scsiID):
1182 return True
1183 elif len(serial) and dev == serial:
1184 return True
1185 return False
1188def default(self, field, thunk):
1189 try:
1190 return getattr(self, field)
1191 except:
1192 return thunk()
1195def list_VDI_records_in_sr(sr):
1196 """Helper function which returns a list of all VDI records for this SR
1197 stored in the XenAPI server, useful for implementing SR.scan"""
1198 sr_ref = sr.session.xenapi.SR.get_by_uuid(sr.uuid)
1199 vdis = sr.session.xenapi.VDI.get_all_records_where("field \"SR\" = \"%s\"" % sr_ref)
1200 return vdis
1203# Given a partition (e.g. sda1), get a disk name:
1204def diskFromPartition(partition):
1205 # check whether this is a device mapper device (e.g. /dev/dm-0)
1206 m = re.match('(/dev/)?(dm-[0-9]+)(p[0-9]+)?$', partition)
1207 if m is not None: 1207 ↛ 1208line 1207 didn't jump to line 1208, because the condition on line 1207 was never true
1208 return m.group(2)
1210 numlen = 0 # number of digit characters
1211 m = re.match(r"\D+(\d+)", partition)
1212 if m is not None: 1212 ↛ 1213line 1212 didn't jump to line 1213, because the condition on line 1212 was never true
1213 numlen = len(m.group(1))
1215 # is it a cciss?
1216 if True in [partition.startswith(x) for x in ['cciss', 'ida', 'rd']]: 1216 ↛ 1217line 1216 didn't jump to line 1217, because the condition on line 1216 was never true
1217 numlen += 1 # need to get rid of trailing 'p'
1219 # is it a mapper path?
1220 if partition.startswith("mapper"): 1220 ↛ 1221line 1220 didn't jump to line 1221, because the condition on line 1220 was never true
1221 if re.search("p[0-9]*$", partition):
1222 numlen = len(re.match(r"\d+", partition[::-1]).group(0)) + 1
1223 SMlog("Found mapper part, len %d" % numlen)
1224 else:
1225 numlen = 0
1227 # is it /dev/disk/by-id/XYZ-part<k>?
1228 if partition.startswith("disk/by-id"): 1228 ↛ 1229line 1228 didn't jump to line 1229, because the condition on line 1228 was never true
1229 return partition[:partition.rfind("-part")]
1231 return partition[:len(partition) - numlen]
1234def dom0_disks():
1235 """Disks carrying dom0, e.g. ['/dev/sda']"""
1236 disks = []
1237 with open("/etc/mtab", 'r') as f:
1238 for line in f:
1239 (dev, mountpoint, fstype, opts, freq, passno) = line.split(' ')
1240 if mountpoint == '/':
1241 disk = diskFromPartition(dev)
1242 if not (disk in disks):
1243 disks.append(disk)
1244 SMlog("Dom0 disks: %s" % disks)
1245 return disks
1248def set_scheduler_sysfs_node(node, scheds):
1249 """
1250 Set the scheduler for a sysfs node (e.g. '/sys/block/sda')
1251 according to prioritized list schedulers
1252 Try to set the first item, then fall back to the next on failure
1253 """
1255 path = os.path.join(node, "queue", "scheduler")
1256 if not os.path.exists(path): 1256 ↛ 1260line 1256 didn't jump to line 1260, because the condition on line 1256 was never false
1257 SMlog("no path %s" % path)
1258 return
1260 stored_error = None
1261 for sched in scheds:
1262 try:
1263 with open(path, 'w') as file:
1264 file.write("%s\n" % sched)
1265 SMlog("Set scheduler to [%s] on [%s]" % (sched, node))
1266 return
1267 except (OSError, IOError) as err:
1268 stored_error = err
1270 SMlog("Error setting schedulers to [%s] on [%s], %s" % (scheds, node, str(stored_error)))
1273def set_scheduler(dev, schedulers=None):
1274 if schedulers is None: 1274 ↛ 1277line 1274 didn't jump to line 1277, because the condition on line 1274 was never false
1275 schedulers = ["none", "noop"]
1277 devices = []
1278 if not scsiutil.match_dm(dev): 1278 ↛ 1282line 1278 didn't jump to line 1282, because the condition on line 1278 was never false
1279 # Remove partition numbers
1280 devices.append(diskFromPartition(dev).replace('/', '!'))
1281 else:
1282 rawdev = diskFromPartition(dev)
1283 devices = [os.path.realpath(x)[5:] for x in scsiutil._genReverseSCSIidmap(rawdev.split('/')[-1])]
1285 for d in devices:
1286 set_scheduler_sysfs_node("/sys/block/%s" % d, schedulers)
1289# This function queries XAPI for the existing VDI records for this SR
1290def _getVDIs(srobj):
1291 VDIs = []
1292 try:
1293 sr_ref = getattr(srobj, 'sr_ref')
1294 except AttributeError:
1295 return VDIs
1297 refs = srobj.session.xenapi.SR.get_VDIs(sr_ref)
1298 for vdi in refs:
1299 ref = srobj.session.xenapi.VDI.get_record(vdi)
1300 ref['vdi_ref'] = vdi
1301 VDIs.append(ref)
1302 return VDIs
1305def get_sr_uuid_from_vdi_ref(session, vdi_ref: str) -> str:
1306 sr_ref = session.xenapi.VDI.get_SR(vdi_ref)
1307 return session.xenapi.SR.get_uuid(sr_ref)
1310def get_sr_uuid_from_vdi_uuid(session, vdi_uuid: str) -> str:
1311 return get_sr_uuid_from_vdi_ref(session, session.xenapi.VDI.get_by_uuid(vdi_uuid))
1314def _getVDI(srobj, vdi_uuid):
1315 vdi = srobj.session.xenapi.VDI.get_by_uuid(vdi_uuid)
1316 ref = srobj.session.xenapi.VDI.get_record(vdi)
1317 ref['vdi_ref'] = vdi
1318 return ref
1321def _convertDNS(name):
1322 addr = socket.getaddrinfo(name, None)[0][4][0]
1323 return addr
1326def _containsVDIinuse(srobj):
1327 VDIs = _getVDIs(srobj)
1328 for vdi in VDIs:
1329 if not vdi['managed']:
1330 continue
1331 sm_config = vdi['sm_config']
1332 if 'SRRef' in sm_config:
1333 try:
1334 PBDs = srobj.session.xenapi.SR.get_PBDs(sm_config['SRRef'])
1335 for pbd in PBDs:
1336 record = PBDs[pbd]
1337 if record["host"] == srobj.host_ref and \
1338 record["currently_attached"]:
1339 return True
1340 except:
1341 pass
1342 return False
1345def isVDICommand(cmd):
1346 if cmd is None or cmd in ["vdi_attach", "vdi_detach",
1347 "vdi_activate", "vdi_deactivate",
1348 "vdi_epoch_begin", "vdi_epoch_end"]:
1349 return True
1350 else:
1351 return False
1354#########################
1355# Daemon helper functions
1356def p_id_fork():
1357 try:
1358 p_id = os.fork()
1359 except OSError as e:
1360 print("Fork failed: %s (%d)" % (e.strerror, e.errno))
1361 sys.exit(-1)
1363 if (p_id == 0):
1364 os.setsid()
1365 try:
1366 p_id = os.fork()
1367 except OSError as e:
1368 print("Fork failed: %s (%d)" % (e.strerror, e.errno))
1369 sys.exit(-1)
1370 if (p_id == 0):
1371 os.chdir('/opt/xensource/sm')
1372 os.umask(0)
1373 else:
1374 os._exit(0)
1375 else:
1376 os._exit(0)
1379def daemon():
1380 p_id_fork()
1381 # Query the max file descriptor parameter for this process
1382 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1384 # Close any fds that are open
1385 for fd in range(0, maxfd):
1386 try:
1387 os.close(fd)
1388 except:
1389 pass
1391 # Redirect STDIN to STDOUT and STDERR
1392 os.open('/dev/null', os.O_RDWR)
1393 os.dup2(0, 1)
1394 os.dup2(0, 2)
1396################################################################################
1397#
1398# Fist points
1399#
1401# * The global variable 'fistpoint' define the list of all possible fistpoints;
1402#
1403# * To activate a fistpoint called 'name', you need to create the file '/tmp/fist_name'
1404# on the SR master;
1405#
1406# * At the moment, activating a fist point can lead to two possible behaviors:
1407# - if '/tmp/fist_LVHDRT_exit' exists, then the function called during the fistpoint is _exit;
1408# - otherwise, the function called is _pause.
1410def _pause(secs, name):
1411 SMlog("Executing fist point %s: sleeping %d seconds ..." % (name, secs))
1412 time.sleep(secs)
1413 SMlog("Executing fist point %s: done" % name)
1416def _exit(name):
1417 SMlog("Executing fist point %s: exiting the current process ..." % name)
1418 raise xs_errors.XenError('FistPoint', opterr='%s' % name)
1421class FistPoint:
1422 def __init__(self, points):
1423 #SMlog("Fist points loaded")
1424 self.points = points
1426 def is_legal(self, name):
1427 return (name in self.points)
1429 def is_active(self, name):
1430 return os.path.exists("/tmp/fist_%s" % name)
1432 def mark_sr(self, name, sruuid, started):
1433 session = get_localAPI_session()
1434 try:
1435 sr = session.xenapi.SR.get_by_uuid(sruuid)
1437 if started:
1438 session.xenapi.SR.add_to_other_config(sr, name, "active")
1439 else:
1440 session.xenapi.SR.remove_from_other_config(sr, name)
1441 finally:
1442 session.xenapi.session.logout()
1444 def activate(self, name, sruuid):
1445 if name in self.points:
1446 if self.is_active(name):
1447 self.mark_sr(name, sruuid, True)
1448 if self.is_active("LVHDRT_exit"): 1448 ↛ 1449line 1448 didn't jump to line 1449, because the condition on line 1448 was never true
1449 self.mark_sr(name, sruuid, False)
1450 _exit(name)
1451 else:
1452 _pause(FIST_PAUSE_PERIOD, name)
1453 self.mark_sr(name, sruuid, False)
1454 else:
1455 SMlog("Unknown fist point: %s" % name)
1457 def activate_custom_fn(self, name, fn):
1458 if name in self.points: 1458 ↛ 1464line 1458 didn't jump to line 1464, because the condition on line 1458 was never false
1459 if self.is_active(name): 1459 ↛ 1460line 1459 didn't jump to line 1460, because the condition on line 1459 was never true
1460 SMlog("Executing fist point %s: starting ..." % name)
1461 fn()
1462 SMlog("Executing fist point %s: done" % name)
1463 else:
1464 SMlog("Unknown fist point: %s" % name)
1467def list_find(f, seq):
1468 for item in seq:
1469 if f(item):
1470 return item
1472GCPAUSE_FISTPOINT = "GCLoop_no_pause"
1474fistpoint = FistPoint(["LVHDRT_finding_a_suitable_pair",
1475 "LVHDRT_inflating_the_parent",
1476 "LVHDRT_resizing_while_vdis_are_paused",
1477 "LVHDRT_coalescing_VHD_data",
1478 "LVHDRT_coalescing_before_inflate_grandparent",
1479 "LVHDRT_relinking_grandchildren",
1480 "LVHDRT_before_create_relink_journal",
1481 "LVHDRT_xapiSM_serialization_tests",
1482 "LVHDRT_clone_vdi_after_create_journal",
1483 "LVHDRT_clone_vdi_after_shrink_parent",
1484 "LVHDRT_clone_vdi_after_first_snap",
1485 "LVHDRT_clone_vdi_after_second_snap",
1486 "LVHDRT_clone_vdi_after_parent_hidden",
1487 "LVHDRT_clone_vdi_after_parent_ro",
1488 "LVHDRT_clone_vdi_before_remove_journal",
1489 "LVHDRT_clone_vdi_after_lvcreate",
1490 "LVHDRT_clone_vdi_before_undo_clone",
1491 "LVHDRT_clone_vdi_after_undo_clone",
1492 "LVHDRT_inflate_after_create_journal",
1493 "LVHDRT_inflate_after_setSize",
1494 "LVHDRT_inflate_after_zeroOut",
1495 "LVHDRT_inflate_after_setSizePhys",
1496 "LVHDRT_inflate_after_setSizePhys",
1497 "LVHDRT_coaleaf_before_coalesce",
1498 "LVHDRT_coaleaf_after_coalesce",
1499 "LVHDRT_coaleaf_one_renamed",
1500 "LVHDRT_coaleaf_both_renamed",
1501 "LVHDRT_coaleaf_after_vdirec",
1502 "LVHDRT_coaleaf_before_delete",
1503 "LVHDRT_coaleaf_after_delete",
1504 "LVHDRT_coaleaf_before_remove_j",
1505 "LVHDRT_coaleaf_undo_after_rename",
1506 "LVHDRT_coaleaf_undo_after_rename2",
1507 "LVHDRT_coaleaf_undo_after_refcount",
1508 "LVHDRT_coaleaf_undo_after_deflate",
1509 "LVHDRT_coaleaf_undo_end",
1510 "LVHDRT_coaleaf_stop_after_recovery",
1511 "LVHDRT_coaleaf_finish_after_inflate",
1512 "LVHDRT_coaleaf_finish_end",
1513 "LVHDRT_coaleaf_delay_1",
1514 "LVHDRT_coaleaf_delay_2",
1515 "LVHDRT_coaleaf_delay_3",
1516 "testsm_clone_allow_raw",
1517 "xenrt_default_vdi_type_legacy",
1518 "blktap_activate_inject_failure",
1519 "blktap_activate_error_handling",
1520 GCPAUSE_FISTPOINT,
1521 "cleanup_coalesceVHD_inject_failure",
1522 "cleanup_tracker_no_progress",
1523 "FileSR_fail_hardlink",
1524 "FileSR_fail_snap1",
1525 "FileSR_fail_snap2",
1526 "LVM_journaler_exists",
1527 "LVM_journaler_none",
1528 "LVM_journaler_badname",
1529 "LVM_journaler_readfail",
1530 "LVM_journaler_writefail"])
1533def set_dirty(session, sr):
1534 try:
1535 session.xenapi.SR.add_to_other_config(sr, "dirty", "")
1536 SMlog("set_dirty %s succeeded" % (repr(sr)))
1537 except:
1538 SMlog("set_dirty %s failed (flag already set?)" % (repr(sr)))
1541def doesFileHaveOpenHandles(fileName):
1542 SMlog("Entering doesFileHaveOpenHandles with file: %s" % fileName)
1543 (retVal, processAndPidTuples) = \
1544 findRunningProcessOrOpenFile(fileName, False)
1546 if not retVal:
1547 SMlog("Failed to determine if file %s has open handles." % \
1548 fileName)
1549 # err on the side of caution
1550 return True
1551 else:
1552 if len(processAndPidTuples) > 0:
1553 return True
1554 else:
1555 return False
1558# extract SR uuid from the passed in devmapper entry and return
1559# /dev/mapper/VG_XenStorage--c3d82e92--cb25--c99b--b83a--482eebab4a93-MGT
1560def extractSRFromDevMapper(path):
1561 try:
1562 path = os.path.basename(path)
1563 path = path[len('VG_XenStorage-') + 1:]
1564 path = path.replace('--', '/')
1565 path = path[0:path.rfind('-')]
1566 return path.replace('/', '-')
1567 except:
1568 return ''
1571def pid_is_alive(pid):
1572 """
1573 Try to kill PID with signal 0.
1574 If we succeed, the PID is alive, so return True.
1575 If we get an EPERM error, the PID is alive but we are not allowed to
1576 signal it. Still return true.
1577 Any other error (e.g. ESRCH), return False
1578 """
1579 try:
1580 os.kill(pid, 0)
1581 return True
1582 except OSError as e:
1583 if e.errno == errno.EPERM:
1584 return True
1585 return False
1588def get_process_cmdline(pid: int) -> List[str]:
1589 try:
1590 with open(os.path.join('/proc', str(pid), 'cmdline'), 'rb') as f:
1591 line = f.read().split(b'\0')
1592 return [arg.decode() for arg in line]
1593 except OSError as e:
1594 if e.errno != errno.ENOENT:
1595 raise
1596 return []
1599# Looks at /proc and figures either
1600# If a process is still running (default), returns open file names
1601# If any running process has open handles to the given file (process = False)
1602# returns process names and pids
1603def findRunningProcessOrOpenFile(name, process=True):
1604 retVal = True
1605 links = []
1606 processandpids = []
1607 sockets = set()
1608 try:
1609 SMlog("Entering findRunningProcessOrOpenFile with params: %s" % \
1610 [name, process])
1612 # Look at all pids
1613 pids = [pid for pid in os.listdir('/proc') if pid.isdigit()]
1614 for pid in sorted(pids):
1615 try:
1616 try:
1617 f = None
1618 f = open(os.path.join('/proc', pid, 'cmdline'), 'r')
1619 prog = f.read()[:-1]
1620 if prog: 1620 ↛ 1629line 1620 didn't jump to line 1629, because the condition on line 1620 was never false
1621 # Just want the process name
1622 argv = prog.split('\x00')
1623 prog = argv[0]
1624 except IOError as e:
1625 if e.errno in (errno.ENOENT, errno.ESRCH):
1626 SMlog("ERROR %s reading %s, ignore" % (e.errno, pid))
1627 continue
1628 finally:
1629 if f is not None: 1629 ↛ 1614, 1629 ↛ 16322 missed branches: 1) line 1629 didn't jump to line 1614, because the continue on line 1627 wasn't executed, 2) line 1629 didn't jump to line 1632, because the condition on line 1629 was never false
1630 f.close() 1630 ↛ 1614line 1630 didn't jump to line 1614, because the continue on line 1627 wasn't executed
1632 try:
1633 fd_dir = os.path.join('/proc', pid, 'fd')
1634 files = os.listdir(fd_dir)
1635 except OSError as e:
1636 if e.errno in (errno.ENOENT, errno.ESRCH):
1637 SMlog("ERROR %s reading fds for %s, ignore" % (e.errno, pid))
1638 # Ignore pid that are no longer valid
1639 continue
1640 else:
1641 raise
1643 for file in files:
1644 try:
1645 link = os.readlink(os.path.join(fd_dir, file))
1646 except OSError:
1647 continue
1649 if process: 1649 ↛ 1654line 1649 didn't jump to line 1654, because the condition on line 1649 was never false
1650 if name == prog: 1650 ↛ 1643line 1650 didn't jump to line 1643, because the condition on line 1650 was never false
1651 links.append(link)
1652 else:
1653 # need to return process name and pid tuples
1654 if link == name:
1655 processandpids.append((prog, pid))
1657 # Get the connected sockets
1658 if name == prog:
1659 sockets.update(get_connected_sockets(pid))
1661 # We will only have a non-empty processandpids if some fd entries were found.
1662 # Before returning them, verify that all the PIDs in question are properly alive.
1663 # There is no specific guarantee of when a PID's /proc directory will disappear
1664 # when it exits, particularly relative to filedescriptor cleanup, so we want to
1665 # make sure we're not reporting a false positive.
1666 processandpids = [x for x in processandpids if pid_is_alive(int(x[1]))]
1667 for pp in processandpids: 1667 ↛ 1668line 1667 didn't jump to line 1668, because the loop on line 1667 never started
1668 SMlog(f"File {name} has an open handle with process {pp[0]} with pid {pp[1]}")
1670 except Exception as e:
1671 SMlog("Exception checking running process or open file handles. " \
1672 "Error: %s" % str(e))
1673 retVal = False
1675 if process: 1675 ↛ 1678line 1675 didn't jump to line 1678, because the condition on line 1675 was never false
1676 return retVal, links, sockets
1677 else:
1678 return retVal, processandpids
1681def get_connected_sockets(pid):
1682 sockets = set()
1683 try:
1684 # Lines in /proc/<pid>/net/unix are formatted as follows
1685 # (see Linux source net/unix/af_unix.c, unix_seq_show() )
1686 # - Pointer address to socket (hex)
1687 # - Refcount (HEX)
1688 # - 0
1689 # - State (HEX, 0 or __SO_ACCEPTCON)
1690 # - Type (HEX - but only 0001 of interest)
1691 # - Connection state (HEX - but only 03, SS_CONNECTED of interest)
1692 # - Inode number
1693 # - Path (optional)
1694 open_sock_matcher = re.compile(
1695 r'^[0-9a-f]+: [0-9A-Fa-f]+ [0-9A-Fa-f]+ [0-9A-Fa-f]+ 0001 03 \d+ (.*)$')
1696 with open(
1697 os.path.join('/proc', str(pid), 'net', 'unix'), 'r') as f:
1698 lines = f.readlines()
1699 for line in lines:
1700 match = open_sock_matcher.match(line)
1701 if match:
1702 sockets.add(match[1])
1703 except OSError as e:
1704 if e.errno in (errno.ENOENT, errno.ESRCH):
1705 # Ignore pid that are no longer valid
1706 SMlog("ERROR %s reading sockets for %s, ignore" %
1707 (e.errno, pid))
1708 else:
1709 raise
1710 return sockets
1713def retry(f, maxretry=20, period=3, exceptions=[Exception]):
1714 retries = 0
1715 while True:
1716 try:
1717 return f()
1718 except Exception as e:
1719 for exception in exceptions:
1720 if isinstance(e, exception):
1721 SMlog('Got exception: {}. Retry number: {}'.format(
1722 str(e), retries
1723 ))
1724 break
1725 else:
1726 SMlog('Got bad exception: {}. Raising...'.format(e))
1727 raise e
1729 retries += 1
1730 if retries >= maxretry:
1731 break
1733 time.sleep(period)
1735 return f()
1738def getCslDevPath(svid):
1739 basepath = "/dev/disk/by-csldev/"
1740 if svid.startswith("NETAPP_"):
1741 # special attention for NETAPP SVIDs
1742 svid_parts = svid.split("__")
1743 globstr = basepath + "NETAPP__LUN__" + "*" + svid_parts[2] + "*" + svid_parts[-1] + "*"
1744 else:
1745 globstr = basepath + svid + "*"
1747 return globstr
1750# Use device in /dev pointed to by cslg path which consists of svid
1751def get_scsiid_from_svid(md_svid):
1752 cslg_path = getCslDevPath(md_svid)
1753 abs_path = glob.glob(cslg_path)
1754 if abs_path:
1755 real_path = os.path.realpath(abs_path[0])
1756 return scsiutil.getSCSIid(real_path)
1757 else:
1758 return None
1761def get_isl_scsiids(session):
1762 # Get cslg type SRs
1763 SRs = session.xenapi.SR.get_all_records_where('field "type" = "cslg"')
1765 # Iterate through the SR to get the scsi ids
1766 scsi_id_ret = []
1767 for SR in SRs:
1768 sr_rec = SRs[SR]
1769 # Use the md_svid to get the scsi id
1770 scsi_id = get_scsiid_from_svid(sr_rec['sm_config']['md_svid'])
1771 if scsi_id:
1772 scsi_id_ret.append(scsi_id)
1774 # Get the vdis in the SR and do the same procedure
1775 vdi_recs = session.xenapi.VDI.get_all_records_where('field "SR" = "%s"' % SR)
1776 for vdi_rec in vdi_recs:
1777 vdi_rec = vdi_recs[vdi_rec]
1778 scsi_id = get_scsiid_from_svid(vdi_rec['sm_config']['SVID'])
1779 if scsi_id:
1780 scsi_id_ret.append(scsi_id)
1782 return scsi_id_ret
1785class extractXVA:
1786 # streams files as a set of file and checksum, caller should remove
1787 # the files, if not needed. The entire directory (Where the files
1788 # and checksum) will only be deleted as part of class cleanup.
1789 HDR_SIZE = 512
1790 BLOCK_SIZE = 512
1791 SIZE_LEN = 12 - 1 # To remove \0 from tail
1792 SIZE_OFFSET = 124
1793 ZERO_FILLED_REC = 2
1794 NULL_IDEN = '\x00'
1795 DIR_IDEN = '/'
1796 CHECKSUM_IDEN = '.checksum'
1797 OVA_FILE = 'ova.xml'
1799 # Init gunzips the file using a subprocess, and reads stdout later
1800 # as and when needed
1801 def __init__(self, filename):
1802 self.__extract_path = ''
1803 self.__filename = filename
1804 cmd = 'gunzip -cd %s' % filename
1805 try:
1806 self.spawn_p = subprocess.Popen(
1807 cmd, shell=True, \
1808 stdin=subprocess.PIPE, stdout=subprocess.PIPE, \
1809 stderr=subprocess.PIPE, close_fds=True)
1810 except Exception as e:
1811 SMlog("Error: %s. Uncompress failed for %s" % (str(e), filename))
1812 raise Exception(str(e))
1814 # Create dir to extract the files
1815 self.__extract_path = tempfile.mkdtemp()
1817 def __del__(self):
1818 shutil.rmtree(self.__extract_path)
1820 # Class supports Generator expression. 'for f_name, checksum in getTuple()'
1821 # returns filename, checksum content. Returns filename, '' in case
1822 # of checksum file missing. e.g. ova.xml
1823 def getTuple(self):
1824 zerod_record = 0
1825 ret_f_name = ''
1826 ret_base_f_name = ''
1828 try:
1829 # Read tar file as sets of file and checksum.
1830 while True:
1831 # Read the output of spawned process, or output of gunzip
1832 f_hdr = self.spawn_p.stdout.read(self.HDR_SIZE)
1834 # Break out in case of end of file
1835 if f_hdr == '':
1836 if zerod_record == extractXVA.ZERO_FILLED_REC:
1837 break
1838 else:
1839 SMlog('Error. Expects %d zero records', \
1840 extractXVA.ZERO_FILLED_REC)
1841 raise Exception('Unrecognized end of file')
1843 # Watch out for zero records, two zero records
1844 # denote end of file.
1845 if f_hdr == extractXVA.NULL_IDEN * extractXVA.HDR_SIZE:
1846 zerod_record += 1
1847 continue
1849 f_name = f_hdr[:f_hdr.index(extractXVA.NULL_IDEN)]
1850 # File header may be for a folder, if so ignore the header
1851 if not f_name.endswith(extractXVA.DIR_IDEN):
1852 f_size_octal = f_hdr[extractXVA.SIZE_OFFSET: \
1853 extractXVA.SIZE_OFFSET + extractXVA.SIZE_LEN]
1854 f_size = int(f_size_octal, 8)
1855 if f_name.endswith(extractXVA.CHECKSUM_IDEN):
1856 if f_name.rstrip(extractXVA.CHECKSUM_IDEN) == \
1857 ret_base_f_name:
1858 checksum = self.spawn_p.stdout.read(f_size)
1859 yield(ret_f_name, checksum)
1860 else:
1861 # Expects file followed by its checksum
1862 SMlog('Error. Sequence mismatch starting with %s', \
1863 ret_f_name)
1864 raise Exception( \
1865 'Files out of sequence starting with %s', \
1866 ret_f_name)
1867 else:
1868 # In case of ova.xml, read the contents into a file and
1869 # return the file name to the caller. For other files,
1870 # read the contents into a file, it will
1871 # be used when a .checksum file is encountered.
1872 ret_f_name = '%s/%s' % (self.__extract_path, f_name)
1873 ret_base_f_name = f_name
1875 # Check if the folder exists on the target location,
1876 # else create it.
1877 folder_path = ret_f_name[:ret_f_name.rfind('/')]
1878 if not os.path.exists(folder_path):
1879 os.mkdir(folder_path)
1881 # Store the file to the tmp folder, strip the tail \0
1882 f = open(ret_f_name, 'w')
1883 f.write(self.spawn_p.stdout.read(f_size))
1884 f.close()
1885 if f_name == extractXVA.OVA_FILE:
1886 yield(ret_f_name, '')
1888 # Skip zero'd portion of data block
1889 round_off = f_size % extractXVA.BLOCK_SIZE
1890 if round_off != 0:
1891 zeros = self.spawn_p.stdout.read(
1892 extractXVA.BLOCK_SIZE - round_off)
1893 except Exception as e:
1894 SMlog("Error: %s. File set extraction failed %s" % (str(e), \
1895 self.__filename))
1897 # Kill and Drain stdout of the gunzip process,
1898 # else gunzip might block on stdout
1899 os.kill(self.spawn_p.pid, signal.SIGTERM)
1900 self.spawn_p.communicate()
1901 raise Exception(str(e))
1903illegal_xml_chars = [(0x00, 0x08), (0x0B, 0x1F), (0x7F, 0x84), (0x86, 0x9F),
1904 (0xD800, 0xDFFF), (0xFDD0, 0xFDDF), (0xFFFE, 0xFFFF),
1905 (0x1FFFE, 0x1FFFF), (0x2FFFE, 0x2FFFF), (0x3FFFE, 0x3FFFF),
1906 (0x4FFFE, 0x4FFFF), (0x5FFFE, 0x5FFFF), (0x6FFFE, 0x6FFFF),
1907 (0x7FFFE, 0x7FFFF), (0x8FFFE, 0x8FFFF), (0x9FFFE, 0x9FFFF),
1908 (0xAFFFE, 0xAFFFF), (0xBFFFE, 0xBFFFF), (0xCFFFE, 0xCFFFF),
1909 (0xDFFFE, 0xDFFFF), (0xEFFFE, 0xEFFFF), (0xFFFFE, 0xFFFFF),
1910 (0x10FFFE, 0x10FFFF)]
1912illegal_ranges = ["%s-%s" % (chr(low), chr(high))
1913 for (low, high) in illegal_xml_chars
1914 if low < sys.maxunicode]
1916illegal_xml_re = re.compile(u'[%s]' % u''.join(illegal_ranges))
1919def isLegalXMLString(s):
1920 """Tells whether this is a valid XML string (i.e. it does not contain
1921 illegal XML characters specified in
1922 http://www.w3.org/TR/2004/REC-xml-20040204/#charsets).
1923 """
1925 if len(s) > 0:
1926 return re.search(illegal_xml_re, s) is None
1927 else:
1928 return True
1931def unictrunc(string, max_bytes):
1932 """
1933 Given a string, returns the largest number of elements for a prefix
1934 substring of it, such that the UTF-8 encoding of this substring takes no
1935 more than the given number of bytes.
1937 The string may be given as a unicode string or a UTF-8 encoded byte
1938 string, and the number returned will be in characters or bytes
1939 accordingly. Note that in the latter case, the substring will still be a
1940 valid UTF-8 encoded string (which is to say, it won't have been truncated
1941 part way through a multibyte sequence for a unicode character).
1943 string: the string to truncate
1944 max_bytes: the maximum number of bytes the truncated string can be
1945 """
1946 if isinstance(string, str):
1947 return_chars = True
1948 else:
1949 return_chars = False
1950 string = string.decode('UTF-8')
1952 cur_chars = 0
1953 cur_bytes = 0
1954 for char in string:
1955 charsize = len(char.encode('UTF-8'))
1956 if cur_bytes + charsize > max_bytes:
1957 break
1958 else:
1959 cur_chars += 1
1960 cur_bytes += charsize
1961 return cur_chars if return_chars else cur_bytes
1964def hideValuesInPropMap(propmap, propnames):
1965 """
1966 Worker function: input simple map of prop name/value pairs, and
1967 a list of specific propnames whose values we want to hide.
1968 Loop through the "hide" list, and if any are found, hide the
1969 value and return the altered map.
1970 If none found, return the original map
1971 """
1972 matches = []
1973 for propname in propnames:
1974 if propname in propmap: 1974 ↛ 1975line 1974 didn't jump to line 1975, because the condition on line 1974 was never true
1975 matches.append(propname)
1977 if matches: 1977 ↛ 1978line 1977 didn't jump to line 1978, because the condition on line 1977 was never true
1978 deepCopyRec = copy.deepcopy(propmap)
1979 for match in matches:
1980 deepCopyRec[match] = '******'
1981 return deepCopyRec
1983 return propmap
1984# define the list of propnames whose value we want to hide
1986PASSWD_PROP_KEYS = ['password', 'cifspassword', 'chappassword', 'incoming_chappassword']
1987DEFAULT_SEGMENT_LEN = 950
1990def hidePasswdInConfig(config):
1991 """
1992 Function to hide passwd values in a simple prop map,
1993 for example "device_config"
1994 """
1995 return hideValuesInPropMap(config, PASSWD_PROP_KEYS)
1998def hidePasswdInParams(params, configProp):
1999 """
2000 Function to hide password values in a specified property which
2001 is a simple map of prop name/values, and is itself an prop entry
2002 in a larger property map.
2003 For example, param maps containing "device_config", or
2004 "sm_config", etc
2005 """
2006 params[configProp] = hideValuesInPropMap(params[configProp], PASSWD_PROP_KEYS)
2007 return params
2010def hideMemberValuesInXmlParams(xmlParams, propnames=PASSWD_PROP_KEYS):
2011 """
2012 Function to hide password values in XML params, specifically
2013 for the XML format of incoming params to SR modules.
2014 Uses text parsing: loop through the list of specific propnames
2015 whose values we want to hide, and:
2016 - Assemble a full "prefix" containing each property name, e.g.,
2017 "<member><name>password</name><value>"
2018 - Test the XML if it contains that string, save the index.
2019 - If found, get the index of the ending tag
2020 - Truncate the return string starting with the password value.
2021 - Append the substitute "*******" value string.
2022 - Restore the rest of the original string starting with the end tag.
2023 """
2024 findStrPrefixHead = "<member><name>"
2025 findStrPrefixTail = "</name><value>"
2026 findStrSuffix = "</value>"
2027 strlen = len(xmlParams)
2029 for propname in propnames:
2030 findStrPrefix = findStrPrefixHead + propname + findStrPrefixTail
2031 idx = xmlParams.find(findStrPrefix)
2032 if idx != -1: # if found any of them
2033 idx += len(findStrPrefix)
2034 idx2 = xmlParams.find(findStrSuffix, idx)
2035 if idx2 != -1:
2036 retStr = xmlParams[0:idx]
2037 retStr += "******"
2038 retStr += xmlParams[idx2:strlen]
2039 return retStr
2040 else:
2041 return xmlParams
2042 return xmlParams
2045def splitXmlText(xmlData, segmentLen=DEFAULT_SEGMENT_LEN, showContd=False):
2046 """
2047 Split xml string data into substrings small enough for the
2048 syslog line length limit. Split at tag end markers ( ">" ).
2049 Usage:
2050 strList = []
2051 strList = splitXmlText( longXmlText, maxLineLen ) # maxLineLen is optional
2052 """
2053 remainingData = str(xmlData)
2055 # "Un-pretty-print"
2056 remainingData = remainingData.replace('\n', '')
2057 remainingData = remainingData.replace('\t', '')
2059 remainingChars = len(remainingData)
2060 returnData = ''
2062 thisLineNum = 0
2063 while remainingChars > segmentLen:
2064 thisLineNum = thisLineNum + 1
2065 index = segmentLen
2066 tmpStr = remainingData[:segmentLen]
2067 tmpIndex = tmpStr.rfind('>')
2068 if tmpIndex != -1:
2069 index = tmpIndex + 1
2071 tmpStr = tmpStr[:index]
2072 remainingData = remainingData[index:]
2073 remainingChars = len(remainingData)
2075 if showContd:
2076 if thisLineNum != 1:
2077 tmpStr = '(Cont\'d): ' + tmpStr
2078 tmpStr = tmpStr + ' (Cont\'d):'
2080 returnData += tmpStr + '\n'
2082 if showContd and thisLineNum > 0:
2083 remainingData = '(Cont\'d): ' + remainingData
2084 returnData += remainingData
2086 return returnData
2089def inject_failure():
2090 raise Exception('injected failure')
2093def open_atomic(path, mode=None):
2094 """Atomically creates a file if, and only if it does not already exist.
2095 Leaves the file open and returns the file object.
2097 path: the path to atomically open
2098 mode: "r" (read), "w" (write), or "rw" (read/write)
2099 returns: an open file object"""
2101 assert path
2103 flags = os.O_CREAT | os.O_EXCL
2104 modes = {'r': os.O_RDONLY, 'w': os.O_WRONLY, 'rw': os.O_RDWR}
2105 if mode:
2106 if mode not in modes:
2107 raise Exception('invalid access mode ' + mode)
2108 flags |= modes[mode]
2109 fd = os.open(path, flags)
2110 try:
2111 if mode:
2112 return os.fdopen(fd, mode)
2113 else:
2114 return os.fdopen(fd)
2115 except:
2116 os.close(fd)
2117 raise
2120def isInvalidVDI(exception):
2121 return exception.details[0] == "HANDLE_INVALID" or \
2122 exception.details[0] == "UUID_INVALID"
2125def get_pool_restrictions(session):
2126 """Returns pool restrictions as a map, @session must be already
2127 established."""
2128 return list(session.xenapi.pool.get_all_records().values())[0]['restrictions']
2131def read_caching_is_restricted(session):
2132 """Tells whether read caching is restricted."""
2133 if session is None: 2133 ↛ 2134line 2133 didn't jump to line 2134, because the condition on line 2133 was never true
2134 return True
2135 restrictions = get_pool_restrictions(session)
2136 if 'restrict_read_caching' in restrictions and \ 2136 ↛ 2138line 2136 didn't jump to line 2138, because the condition on line 2136 was never true
2137 restrictions['restrict_read_caching'] == "true":
2138 return True
2139 return False
2142def sessions_less_than_targets(other_config, device_config):
2143 if 'multihomelist' in device_config and 'iscsi_sessions' in other_config:
2144 sessions = int(other_config['iscsi_sessions'])
2145 targets = len(device_config['multihomelist'].split(','))
2146 SMlog("Targets %d and iscsi_sessions %d" % (targets, sessions))
2147 return (sessions < targets)
2148 else:
2149 return False
2152def enable_and_start_service(name, start):
2153 attempt = 0
2154 while True:
2155 attempt += 1
2156 fn = 'enable' if start else 'disable'
2157 args = ('systemctl', fn, '--now', name)
2158 (ret, out, err) = doexec(args)
2159 if ret == 0:
2160 return
2161 elif attempt >= 3:
2162 raise Exception(
2163 'Failed to {} {}: {} {}'.format(fn, name, out, err)
2164 )
2165 time.sleep(1)
2168def stop_service(name):
2169 args = ('systemctl', 'stop', name)
2170 (ret, out, err) = doexec(args)
2171 if ret == 0:
2172 return
2173 raise Exception('Failed to stop {}: {} {}'.format(name, out, err))
2176def restart_service(name):
2177 attempt = 0
2178 while True:
2179 attempt += 1
2180 SMlog('Restarting service {} {}...'.format(name, attempt))
2181 args = ('systemctl', 'restart', name)
2182 (ret, out, err) = doexec(args)
2183 if ret == 0:
2184 return
2185 elif attempt >= 3:
2186 SMlog('Restart service FAILED {} {}'.format(name, attempt))
2187 raise Exception(
2188 'Failed to restart {}: {} {}'.format(name, out, err)
2189 )
2190 time.sleep(1)
2193def check_pid_exists(pid):
2194 try:
2195 os.kill(pid, 0)
2196 except OSError:
2197 return False
2198 else:
2199 return True
2202def get_openers_pid(path: str) -> Optional[List[int]]:
2203 cmd = ["lsof", "-t", path]
2205 try:
2206 list = []
2207 ret = pread2(cmd)
2208 for line in ret.splitlines():
2209 list.append(int(line))
2210 return list
2211 except CommandException as e:
2212 if e.code == 1: # `lsof` return 1 if there is no openers
2213 return None
2214 else:
2215 raise e
2218def make_profile(name, function):
2219 """
2220 Helper to execute cProfile using unique log file.
2221 """
2223 import cProfile
2224 import itertools
2225 import os.path
2226 import time
2228 assert name
2229 assert function
2231 FOLDER = '/tmp/sm-perfs/'
2232 makedirs(FOLDER)
2234 filename = time.strftime('{}_%Y%m%d_%H%M%S.prof'.format(name))
2236 def gen_path(path):
2237 yield path
2238 root, ext = os.path.splitext(path)
2239 for i in itertools.count(start=1, step=1):
2240 yield root + '.{}.'.format(i) + ext
2242 for profile_path in gen_path(FOLDER + filename):
2243 try:
2244 file = open_atomic(profile_path, 'w')
2245 file.close()
2246 break
2247 except OSError as e:
2248 if e.errno == errno.EEXIST:
2249 pass
2250 else:
2251 raise
2253 try:
2254 SMlog('* Start profiling of {} ({}) *'.format(name, filename))
2255 cProfile.runctx('function()', None, locals(), profile_path)
2256 finally:
2257 SMlog('* End profiling of {} ({}) *'.format(name, filename))
2260def strtobool(str: str) -> bool:
2261 # Note: `distutils` package is deprecated and slated for removal in Python 3.12.
2262 # There is not alternative for strtobool.
2263 # See: https://peps.python.org/pep-0632/#migration-advice
2264 # So this is a custom implementation with differences:
2265 # - A boolean is returned instead of integer
2266 # - Empty string and None are supported (False is returned in this case)
2267 if not str: 2267 ↛ 2269line 2267 didn't jump to line 2269, because the condition on line 2267 was never false
2268 return False
2269 str = str.lower()
2270 if str in ('y', 'yes', 't', 'true', 'on', '1'):
2271 return True
2272 if str in ('n', 'no', 'f', 'false', 'off', '0'):
2273 return False
2274 raise ValueError("invalid truth value '{}'".format(str))
2277def find_executable(name):
2278 return shutil.which(name)
2281def conditional_decorator(decorator, condition):
2282 def wrapper(func):
2283 if not condition: 2283 ↛ 2285line 2283 didn't jump to line 2285, because the condition on line 2283 was never false
2284 return func
2285 return decorator(func)
2286 return wrapper
2289def get_srs_uuid_from_type(session, type):
2290 srs = session.xenapi.SR.get_all_records_where(f"field \"type\" = \"{type}\"")
2291 return {sr["uuid"]: sr for sr in srs.values()}
2294def get_linstor_srs_uuid(session):
2295 import LinstorSR # pylint: disable=C0415
2296 return get_srs_uuid_from_type(session, LinstorSR.LinstorSR.DRIVER_TYPE)
2299def find_pbd_ref_from_dconf_value(session, srs, key, value, value_modifier = None):
2300 for sr in srs.values():
2301 for pbd_ref in sr["PBDs"]:
2302 device_config = session.xenapi.PBD.get_device_config(pbd_ref)
2303 cur_value = device_config.get(key)
2304 if value_modifier:
2305 cur_value = value_modifier(cur_value)
2306 if cur_value and cur_value == value:
2307 return pbd_ref
2308 return None