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 atexit
23import os
24import re
25import sys
26import subprocess
27import shutil
28import tempfile
29import signal
30import time
31import types
32import datetime
33import errno
34import functools
35import socket
36import threading
37import xml.dom.minidom
38import scsiutil
39import stat
40import xs_errors
41import XenAPI # pylint: disable=import-error
42import xmlrpc.client
43import http.client
44import base64
45import syslog
46import resource
47import traceback
48import glob
49import copy
50import contextlib
51from sm_typing import override
53from functools import reduce
54from sm_typing import List, Optional
56NO_LOGGING_STAMPFILE = '/etc/xensource/no_sm_log'
58IORETRY_MAX = 20 # retries
59IORETRY_PERIOD = 1.0 # seconds
61LOGGING = not (os.path.exists(NO_LOGGING_STAMPFILE))
62_SM_SYSLOG_FACILITY = syslog.LOG_LOCAL2
63LOG_EMERG = syslog.LOG_EMERG
64LOG_ALERT = syslog.LOG_ALERT
65LOG_CRIT = syslog.LOG_CRIT
66LOG_ERR = syslog.LOG_ERR
67LOG_WARNING = syslog.LOG_WARNING
68LOG_NOTICE = syslog.LOG_NOTICE
69LOG_INFO = syslog.LOG_INFO
70LOG_DEBUG = syslog.LOG_DEBUG
72ISCSI_REFDIR = '/var/run/sr-ref'
74CMD_DD = "/bin/dd"
75CMD_KICKPIPE = '/opt/xensource/libexec/kickpipe'
77FIST_PAUSE_PERIOD = 30 # seconds
79class _TimeoutContextManager(contextlib.AbstractContextManager):
80 def __init__(self, delay: int) -> None:
81 self.delay = delay
82 self.old_handler: Any = None
84 @override
85 def __enter__(self) -> "_TimeoutContextManager":
86 def handle_timeout(_signum: int, _frame: Optional[types.FrameType]) -> None:
87 raise TimeoutError(f"Timed out after {self.delay} seconds")
88 self.old_handler = signal.signal(signal.SIGALRM, handle_timeout)
89 signal.alarm(self.delay)
90 return self
92 @override
93 def __exit__(
94 self,
95 exc_type: Optional[Any],
96 exc_val: Optional[BaseException],
97 exc_tb: Optional[types.TracebackType]
98 ):
99 signal.alarm(0)
100 if self.old_handler is not None:
101 signal.signal(signal.SIGALRM, self.old_handler)
102 self.old_handler = None
103 return False
105 def __call__(self, func):
106 def wrapper(*args, **kwargs):
107 with self:
108 return func(*args, **kwargs)
109 return wrapper
111def timeout(*args, **kwargs):
112 if len(args) >= 2 and callable(args[1]):
113 delay, func = args[0], args[1]
114 remaining_args = args[2:]
115 with _TimeoutContextManager(delay):
116 return func(*remaining_args, **kwargs)
118 if len(args) == 1 and callable(args[0]):
119 raise TypeError("timeout() missing 1 required argument: 'delay'")
121 if args:
122 delay = args[0]
123 elif "delay" in kwargs:
124 delay = kwargs["delay"]
125 else:
126 raise TypeError("timeout() missing 1 required argument: 'delay'")
127 return _TimeoutContextManager(delay)
129class SMException(Exception):
130 """Base class for all SM exceptions for easier catching & wrapping in
131 XenError"""
134class CommandException(SMException):
135 def error_message(self, code):
136 if code > 0:
137 return os.strerror(code)
138 elif code < 0:
139 return "Signalled %s" % (abs(code))
140 return "Success"
142 def __init__(self, code, cmd="", reason='exec failed'):
143 self.code = code
144 self.cmd = cmd
145 self.reason = reason
146 Exception.__init__(self, self.error_message(code))
149class SRBusyException(SMException):
150 """The SR could not be locked"""
151 pass
154def logException(tag):
155 info = sys.exc_info()
156 if info[0] == SystemExit: 156 ↛ 158line 156 didn't jump to line 158, because the condition on line 156 was never true
157 # this should not be happening when catching "Exception", but it is
158 sys.exit(0)
159 tb = reduce(lambda a, b: "%s%s" % (a, b), traceback.format_tb(info[2]))
160 str = "***** %s: EXCEPTION %s, %s\n%s" % (tag, info[0], info[1], tb)
161 SMlog(str)
164def roundup(divisor, value):
165 """Retruns the rounded up value so it is divisible by divisor."""
167 if value == 0: 167 ↛ 168line 167 didn't jump to line 168, because the condition on line 167 was never true
168 value = 1
169 if value % divisor != 0:
170 return ((int(value) // divisor) + 1) * divisor
171 return value
174def to_plain_string(obj):
175 if obj is None:
176 return None
177 if isinstance(obj, dict) and len(obj) == 0:
178 SMlog(f"util.to_plain_string() corrected empty dict to empty str")
179 return ""
180 return str(obj)
183def shellquote(arg):
184 return '"%s"' % arg.replace('"', '\\"')
187def make_WWN(name):
188 hex_prefix = name.find("0x")
189 if (hex_prefix >= 0): 189 ↛ 192line 189 didn't jump to line 192, because the condition on line 189 was never false
190 name = name[name.find("0x") + 2:len(name)]
191 # inject dashes for each nibble
192 if (len(name) == 16): # sanity check 192 ↛ 196line 192 didn't jump to line 196, because the condition on line 192 was never false
193 name = name[0:2] + "-" + name[2:4] + "-" + name[4:6] + "-" + \
194 name[6:8] + "-" + name[8:10] + "-" + name[10:12] + "-" + \
195 name[12:14] + "-" + name[14:16]
196 return name
199def synchronized(func):
200 lock = threading.RLock()
202 @functools.wraps(func)
203 def wrapper(*args, **kwargs):
204 with lock:
205 return func(*args, **kwargs)
207 return wrapper
210@synchronized
211def _writeToSyslog(ident, facility, priority, message):
212 syslog.openlog(ident, 0, facility)
213 syslog.syslog(priority, message)
214 syslog.closelog()
217def _logToSyslog(ident, facility, priority, message):
218 pid = os.getpid()
219 thread_name = threading.current_thread().name
220 _writeToSyslog(ident, facility, priority, f"[{pid}][{thread_name}] {message}")
223def SMlog(message, ident="SM", priority=LOG_INFO):
224 if LOGGING: 224 ↛ exitline 224 didn't return from function 'SMlog', because the condition on line 224 was never false
225 for message_line in str(message).split('\n'):
226 _logToSyslog(ident, _SM_SYSLOG_FACILITY, priority, message_line)
229class LoggerCounter:
230 def __init__(self, max_repeats):
231 self.previous_message = None
232 self.max_repeats = max_repeats
233 self.repeat_counter = 0
235 def log(self, message):
236 self.repeat_counter += 1
237 if self.previous_message != message or self.repeat_counter == self.max_repeats:
238 SMlog(message)
239 self.previous_message = message
240 self.repeat_counter = 0
242def _getDateString():
243 d = datetime.datetime.now()
244 t = d.timetuple()
245 return "%s-%s-%s:%s:%s:%s" % \
246 (t[0], t[1], t[2], t[3], t[4], t[5])
249def doexec(args, inputtext=None, new_env=None, text=True):
250 """Execute a subprocess, then return its return code, stdout and stderr"""
251 env = None
252 if new_env:
253 env = dict(os.environ)
254 env.update(new_env)
255 proc = subprocess.Popen(args, stdin=subprocess.PIPE,
256 stdout=subprocess.PIPE,
257 stderr=subprocess.PIPE,
258 close_fds=True, env=env,
259 universal_newlines=text)
261 if not text and inputtext is not None: 261 ↛ 262line 261 didn't jump to line 262, because the condition on line 261 was never true
262 inputtext = inputtext.encode()
264 (stdout, stderr) = proc.communicate(inputtext)
266 rc = proc.returncode
267 return rc, stdout, stderr
270def is_string(value):
271 return isinstance(value, str)
274# These are partially tested functions that replicate the behaviour of
275# the original pread,pread2 and pread3 functions. Potentially these can
276# replace the original ones at some later date.
277#
278# cmdlist is a list of either single strings or pairs of strings. For
279# each pair, the first component is passed to exec while the second is
280# written to the logs.
281def pread(cmdlist, close_stdin=False, scramble=None, expect_rc=0,
282 quiet=False, new_env=None, text=True):
283 cmdlist_for_exec = []
284 cmdlist_for_log = []
285 for item in cmdlist:
286 if is_string(item): 286 ↛ 296line 286 didn't jump to line 296, because the condition on line 286 was never false
287 cmdlist_for_exec.append(item)
288 if scramble: 288 ↛ 289line 288 didn't jump to line 289, because the condition on line 288 was never true
289 if item.find(scramble) != -1:
290 cmdlist_for_log.append("<filtered out>")
291 else:
292 cmdlist_for_log.append(item)
293 else:
294 cmdlist_for_log.append(item)
295 else:
296 cmdlist_for_exec.append(item[0])
297 cmdlist_for_log.append(item[1])
299 if not quiet: 299 ↛ 301line 299 didn't jump to line 301, because the condition on line 299 was never false
300 SMlog(cmdlist_for_log)
301 (rc, stdout, stderr) = doexec(cmdlist_for_exec, new_env=new_env, text=text)
302 if rc != expect_rc:
303 SMlog("FAILED in util.pread: (rc %d) stdout: '%s', stderr: '%s'" % \
304 (rc, stdout, stderr))
305 if quiet: 305 ↛ 306line 305 didn't jump to line 306, because the condition on line 305 was never true
306 SMlog("Command was: %s" % cmdlist_for_log)
307 if '' == stderr: 307 ↛ 308line 307 didn't jump to line 308, because the condition on line 307 was never true
308 stderr = stdout
309 raise CommandException(rc, str(cmdlist), stderr.strip())
310 if not quiet: 310 ↛ 312line 310 didn't jump to line 312, because the condition on line 310 was never false
311 SMlog(" pread SUCCESS")
312 return stdout
315# POSIX guaranteed atomic within the same file system.
316# Supply directory to ensure tempfile is created
317# in the same directory.
318def atomicFileWrite(targetFile, directory, text):
320 file = None
321 try:
322 # Create file only current pid can write/read to
323 # our responsibility to clean it up.
324 _, tempPath = tempfile.mkstemp(dir=directory)
325 file = open(tempPath, 'w')
326 file.write(text)
328 # Ensure flushed to disk.
329 file.flush()
330 os.fsync(file.fileno())
331 file.close()
333 os.rename(tempPath, targetFile)
334 except OSError:
335 SMlog("FAILED to atomic write to %s" % (targetFile))
337 finally:
338 if (file is not None) and (not file.closed):
339 file.close()
341 if os.path.isfile(tempPath):
342 os.remove(tempPath)
345#Read STDOUT from cmdlist and discard STDERR output
346def pread2(cmdlist, quiet=False, text=True):
347 return pread(cmdlist, quiet=quiet, text=text)
350#Read STDOUT from cmdlist, feeding 'text' to STDIN
351def pread3(cmdlist, text):
352 SMlog(cmdlist)
353 (rc, stdout, stderr) = doexec(cmdlist, text)
354 if rc:
355 SMlog("FAILED in util.pread3: (errno %d) stdout: '%s', stderr: '%s'" % \
356 (rc, stdout, stderr))
357 if '' == stderr:
358 stderr = stdout
359 raise CommandException(rc, str(cmdlist), stderr.strip())
360 SMlog(" pread3 SUCCESS")
361 return stdout
364def listdir(path, quiet=False):
365 cmd = ["ls", path, "-1", "--color=never"]
366 try:
367 text = pread2(cmd, quiet=quiet)[:-1]
368 if len(text) == 0:
369 return []
370 return text.split('\n')
371 except CommandException as inst:
372 if inst.code == errno.ENOENT:
373 raise CommandException(errno.EIO, inst.cmd, inst.reason)
374 else:
375 raise CommandException(inst.code, inst.cmd, inst.reason)
378def gen_uuid():
379 cmd = ["uuidgen", "-r"]
380 return pread(cmd)[:-1]
383def match_uuid(s):
384 regex = re.compile("^[0-9a-f]{8}-(([0-9a-f]{4})-){3}[0-9a-f]{12}")
385 return regex.search(s, 0)
388def findall_uuid(s):
389 regex = re.compile("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}")
390 return regex.findall(s, 0)
393def exactmatch_uuid(s):
394 regex = re.compile("^[0-9a-f]{8}-(([0-9a-f]{4})-){3}[0-9a-f]{12}$")
395 return regex.search(s, 0)
398def start_log_entry(srpath, path, args):
399 logstring = str(datetime.datetime.now())
400 logstring += " log: "
401 logstring += srpath
402 logstring += " " + path
403 for element in args:
404 logstring += " " + element
405 try:
406 file = open(srpath + "/filelog.txt", "a")
407 file.write(logstring)
408 file.write("\n")
409 file.close()
410 except:
411 pass
413 # failed to write log ...
415def end_log_entry(srpath, path, args):
416 # for teminating, use "error" or "done"
417 logstring = str(datetime.datetime.now())
418 logstring += " end: "
419 logstring += srpath
420 logstring += " " + path
421 for element in args:
422 logstring += " " + element
423 try:
424 file = open(srpath + "/filelog.txt", "a")
425 file.write(logstring)
426 file.write("\n")
427 file.close()
428 except:
429 pass
431 # failed to write log ...
432 # for now print
433 # print "%s" % logstring
435def ioretry(f, errlist=[errno.EIO], maxretry=IORETRY_MAX, period=IORETRY_PERIOD, **ignored):
436 retries = 0
437 while True:
438 try:
439 return f()
440 except OSError as ose:
441 err = int(ose.errno)
442 if not err in errlist:
443 raise CommandException(err, str(f), "OSError")
444 except CommandException as ce:
445 if not int(ce.code) in errlist:
446 raise
448 retries += 1
449 if retries >= maxretry:
450 break
452 time.sleep(period)
454 raise CommandException(errno.ETIMEDOUT, str(f), "Timeout")
457def ioretry_stat(path, maxretry=IORETRY_MAX):
458 # this ioretry is similar to the previous method, but
459 # stat does not raise an error -- so check its return
460 retries = 0
461 while retries < maxretry:
462 stat = os.statvfs(path)
463 if stat.f_blocks != -1:
464 return stat
465 time.sleep(1)
466 retries += 1
467 raise CommandException(errno.EIO, "os.statvfs")
470def sr_get_capability(sr_uuid, session=None):
471 result = []
472 api_session = None
473 try:
474 if session is None:
475 api_session = ApiSession("SM-sr-get-capability")
476 session = api_session.session
477 sr_ref = session.xenapi.SR.get_by_uuid(sr_uuid)
478 sm_type = session.xenapi.SR.get_record(sr_ref)['type']
479 sm_rec = session.xenapi.SM.get_all_records_where(
480 "field \"type\" = \"%s\"" % sm_type)
482 # SM expects at least one entry of any SR type
483 if len(sm_rec) > 0:
484 result = list(sm_rec.values())[0]['capabilities']
485 finally:
486 if api_session:
487 session.logout()
488 return result
490def sr_get_driver_info(driver_info):
491 results = {}
492 # first add in the vanilla stuff
493 for key in ['name', 'description', 'vendor', 'copyright', \
494 'driver_version', 'required_api_version']:
495 results[key] = driver_info[key]
496 # add the capabilities (xmlrpc array)
497 # enforcing activate/deactivate for blktap2
498 caps = driver_info['capabilities']
499 if "ATOMIC_PAUSE" in caps: 499 ↛ 500line 499 didn't jump to line 500, because the condition on line 499 was never true
500 for cap in ("VDI_ACTIVATE", "VDI_DEACTIVATE"):
501 if not cap in caps:
502 caps.append(cap)
503 elif "VDI_ACTIVATE" in caps or "VDI_DEACTIVATE" in caps: 503 ↛ 504line 503 didn't jump to line 504, because the condition on line 503 was never true
504 SMlog("Warning: vdi_[de]activate present for %s" % driver_info["name"])
506 results['capabilities'] = caps
507 # add in the configuration options
508 options = []
509 for option in driver_info['configuration']:
510 options.append({'key': option[0], 'description': option[1]})
511 results['configuration'] = options
512 return xmlrpc.client.dumps((results, ), "", True)
515def return_nil():
516 return xmlrpc.client.dumps((None, ), "", True, allow_none=True)
519def SRtoXML(SRlist):
520 dom = xml.dom.minidom.Document()
521 driver = dom.createElement("SRlist")
522 dom.appendChild(driver)
524 for key in SRlist.keys():
525 dict = SRlist[key]
526 entry = dom.createElement("SR")
527 driver.appendChild(entry)
529 e = dom.createElement("UUID")
530 entry.appendChild(e)
531 textnode = dom.createTextNode(key)
532 e.appendChild(textnode)
534 if 'size' in dict:
535 e = dom.createElement("Size")
536 entry.appendChild(e)
537 textnode = dom.createTextNode(str(dict['size']))
538 e.appendChild(textnode)
540 if 'storagepool' in dict:
541 e = dom.createElement("StoragePool")
542 entry.appendChild(e)
543 textnode = dom.createTextNode(str(dict['storagepool']))
544 e.appendChild(textnode)
546 if 'aggregate' in dict:
547 e = dom.createElement("Aggregate")
548 entry.appendChild(e)
549 textnode = dom.createTextNode(str(dict['aggregate']))
550 e.appendChild(textnode)
552 return dom.toprettyxml()
555def pathexists(path):
556 try:
557 os.lstat(path)
558 return True
559 except OSError as inst:
560 if inst.errno == errno.EIO: 560 ↛ 561line 560 didn't jump to line 561, because the condition on line 560 was never true
561 time.sleep(1)
562 try:
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
776class ApiSession(contextlib.AbstractContextManager):
777 session = None
779 def __init__(self, originator="SM"):
780 self.originator = originator
781 # First acquire a valid session
782 self.session = self._login()
783 atexit.register(self._atexit)
785 def _login(self):
786 if self.session:
787 return self.session
788 session = XenAPI.xapi_local()
789 try:
790 session.xenapi.login_with_password('root', '', '', self.originator)
791 except Exception as exc:
792 raise xs_errors.XenError(
793 "APISession",
794 opterr=f"[{self.originator}] Unable to open local XAPI session"
795 ) from exc
796 SMlog("ApiSession [{}] login".format(self.originator), priority=LOG_DEBUG)
797 return session
799 def _logout(self, log):
800 """Closes an API session"""
801 if self.session is None: 801 ↛ 802line 801 didn't jump to line 802, because the condition on line 801 was never true
802 SMlog("ApiSession [{}] session is None {}".format(self.originator, log), priority=LOG_DEBUG)
803 return
804 self.session.xenapi.session.logout()
805 SMlog("ApiSession [{}] {}".format(self.originator, log), priority=LOG_DEBUG)
806 self.session = None
808 def logout(self, log="logout"):
809 atexit.unregister(self._atexit)
810 self._logout(log=log)
812 def __del__(self):
813 if self.session: 813 ↛ 814line 813 didn't jump to line 814, because the condition on line 813 was never true
814 self.logout(log="logout del")
816 def _atexit(self):
817 self._logout(log="logout atexit")
819 @override
820 def __enter__(self):
821 self.session = self._login()
822 return self.session
824 @override
825 def __exit__(self, _type, _value, _traceback):
826 self.logout(log=f"logout exception[{_type}] {_value}" if _type else "logout context")
829def xapi_safe_call(function):
830 """
831 Decorator to catch classic XAPI connection problems and retry forever.
832 The method should be only called for XAPI calls.
833 eg: blktap2.py#VDI:_remove_tag()
834 """
835 def wrapper(*args, **kwargs):
836 call_str = f"{function.__name__}(args={args}, kwargs={kwargs})"
837 while True:
838 try:
839 return function(*args, **kwargs)
840 except xmlrpc.client.ProtocolError as e:
841 # If there's a connection error, keep trying forever.
842 if e.errcode == http.HTTPStatus.INTERNAL_SERVER_ERROR.value:
843 continue
844 raise
845 except Exception as e:
846 SMlog(f"Failed XAPI call `{call_str}`: `{e}`")
847 raise
848 return wrapper
851def get_this_host():
852 uuid = None
853 f = open("/etc/xensource-inventory", 'r')
854 for line in f.readlines():
855 if line.startswith("INSTALLATION_UUID"):
856 uuid = line.split("'")[1]
857 f.close()
858 return uuid
861def get_master_ref(session):
862 pools = session.xenapi.pool.get_all()
863 return session.xenapi.pool.get_master(pools[0])
866def is_master(session):
867 return get_this_host_ref(session) == get_master_ref(session)
870def get_localhost_ref(session):
871 filename = '/etc/xensource-inventory'
872 try:
873 f = open(filename, 'r')
874 except:
875 raise xs_errors.XenError('EIO', \
876 opterr="Unable to open inventory file [%s]" % filename)
877 domid = ''
878 for line in filter(match_domain_id, f.readlines()):
879 domid = line.split("'")[1]
880 if not domid:
881 raise xs_errors.XenError('APILocalhost')
883 vms = session.xenapi.VM.get_all_records_where('field "uuid" = "%s"' % domid)
884 for vm in vms:
885 record = vms[vm]
886 if record["uuid"] == domid:
887 hostid = record["resident_on"]
888 return hostid
889 raise xs_errors.XenError('APILocalhost')
892def match_domain_id(s):
893 regex = re.compile("^CONTROL_DOMAIN_UUID")
894 return regex.search(s, 0)
897def get_hosts_attached_on(session, vdi_uuids):
898 host_refs = {}
899 for vdi_uuid in vdi_uuids:
900 try:
901 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
902 except XenAPI.Failure:
903 SMlog("VDI %s not in db, ignoring" % vdi_uuid)
904 continue
905 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
906 for key in [x for x in sm_config.keys() if x.startswith('host_')]:
907 host_refs[key[len('host_'):]] = True
908 return host_refs.keys()
910def get_hosts_attached_on_with_vdi_uuid(session, vdi_uuids):
911 """
912 Return a dict of {vdi_uuid: host OpaqueRef}
913 """
914 host_refs = {}
915 for vdi_uuid in vdi_uuids:
916 try:
917 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
918 except XenAPI.Failure:
919 SMlog("VDI %s not in db, ignoring" % vdi_uuid)
920 continue
921 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
922 for key in [x for x in sm_config.keys() if x.startswith('host_')]:
923 host_refs[vdi_uuid] = key[len('host_'):]
924 return host_refs
926def get_this_host_address(session):
927 host_uuid = get_this_host()
928 host_ref = session.xenapi.host.get_by_uuid(host_uuid)
929 return session.xenapi.host.get_record(host_ref)['address']
931def get_host_addresses(session):
932 addresses = []
933 hosts = session.xenapi.host.get_all_records()
934 for record in hosts.values():
935 addresses.append(record['address'])
936 return addresses
938def get_this_host_ref(session):
939 host_uuid = get_this_host()
940 host_ref = session.xenapi.host.get_by_uuid(host_uuid)
941 return host_ref
944def get_slaves_attached_on(session, vdi_uuids):
945 "assume this host is the SR master"
946 host_refs = get_hosts_attached_on(session, vdi_uuids)
947 master_ref = get_this_host_ref(session)
948 return [x for x in host_refs if x != master_ref]
950def get_enabled_hosts(session):
951 """
952 Returns a list of host refs that are enabled in the pool.
953 """
954 return list(session.xenapi.host.get_all_records_where('field "enabled" = "true"').keys())
956def get_online_hosts(session):
957 online_hosts = []
958 hosts = session.xenapi.host.get_all_records()
959 for host_ref, host_rec in hosts.items():
960 metricsRef = host_rec["metrics"]
961 metrics = session.xenapi.host_metrics.get_record(metricsRef)
962 if metrics["live"]:
963 online_hosts.append(host_ref)
964 return online_hosts
967def get_all_slaves(session):
968 "assume this host is the SR master"
969 host_refs = get_online_hosts(session)
970 master_ref = get_this_host_ref(session)
971 return [x for x in host_refs if x != master_ref]
974def is_attached_rw(sm_config):
975 for key, val in sm_config.items():
976 if key.startswith("host_") and val == "RW":
977 return True
978 return False
981def attached_as(sm_config):
982 for key, val in sm_config.items():
983 if key.startswith("host_") and (val == "RW" or val == "RO"): 983 ↛ 984line 983 didn't jump to line 984, because the condition on line 983 was never true
984 return val
987def find_my_pbd_record(session, host_ref, sr_ref):
988 try:
989 pbds = session.xenapi.PBD.get_all_records()
990 for pbd_ref in pbds.keys():
991 if pbds[pbd_ref]['host'] == host_ref and pbds[pbd_ref]['SR'] == sr_ref:
992 return [pbd_ref, pbds[pbd_ref]]
993 return None
994 except Exception as e:
995 SMlog("Caught exception while looking up PBD for host %s SR %s: %s" % (str(host_ref), str(sr_ref), str(e)))
996 return None
999def find_my_pbd(session, host_ref, sr_ref):
1000 ret = find_my_pbd_record(session, host_ref, sr_ref)
1001 if ret is not None:
1002 return ret[0]
1003 else:
1004 return None
1007def test_hostPBD_devs(session, sr_uuid, devs):
1008 host = get_localhost_ref(session)
1009 sr = session.xenapi.SR.get_by_uuid(sr_uuid)
1010 try:
1011 pbds = session.xenapi.PBD.get_all_records()
1012 except:
1013 raise xs_errors.XenError('APIPBDQuery')
1014 for dev in devs.split(','):
1015 for pbd in pbds:
1016 record = pbds[pbd]
1017 # it's ok if it's *our* PBD
1018 if record["SR"] == sr:
1019 break
1020 if record["host"] == host:
1021 devconfig = record["device_config"]
1022 if 'device' in devconfig:
1023 for device in devconfig['device'].split(','):
1024 if os.path.realpath(device) == os.path.realpath(dev):
1025 return True
1026 return False
1029def test_hostPBD_lun(session, targetIQN, LUNid):
1030 host = get_localhost_ref(session)
1031 try:
1032 pbds = session.xenapi.PBD.get_all_records()
1033 except:
1034 raise xs_errors.XenError('APIPBDQuery')
1035 for pbd in pbds:
1036 record = pbds[pbd]
1037 if record["host"] == host:
1038 devconfig = record["device_config"]
1039 if 'targetIQN' in devconfig and 'LUNid' in devconfig:
1040 if devconfig['targetIQN'] == targetIQN and \
1041 devconfig['LUNid'] == LUNid:
1042 return True
1043 return False
1046def test_SCSIid(session, sr_uuid, SCSIid):
1047 if sr_uuid is not None:
1048 sr = session.xenapi.SR.get_by_uuid(sr_uuid)
1049 try:
1050 pbds = session.xenapi.PBD.get_all_records()
1051 except:
1052 raise xs_errors.XenError('APIPBDQuery')
1053 for pbd in pbds:
1054 record = pbds[pbd]
1055 # it's ok if it's *our* PBD
1056 # During FC SR creation, devscan.py passes sr_uuid as None
1057 if sr_uuid is not None:
1058 if record["SR"] == sr:
1059 break
1060 devconfig = record["device_config"]
1061 sm_config = session.xenapi.SR.get_sm_config(record["SR"])
1062 if 'SCSIid' in devconfig and devconfig['SCSIid'] == SCSIid:
1063 return True
1064 elif 'SCSIid' in sm_config and sm_config['SCSIid'] == SCSIid:
1065 return True
1066 elif 'scsi-' + SCSIid in sm_config:
1067 return True
1068 return False
1071class TimeoutException(SMException):
1072 pass
1075def _incr_iscsiSR_refcount(targetIQN, uuid):
1076 if not os.path.exists(ISCSI_REFDIR):
1077 os.mkdir(ISCSI_REFDIR)
1078 filename = os.path.join(ISCSI_REFDIR, targetIQN)
1079 try:
1080 f = open(filename, 'a+')
1081 except:
1082 raise xs_errors.XenError('LVMRefCount', \
1083 opterr='file %s' % filename)
1085 f.seek(0)
1086 found = False
1087 refcount = 0
1088 for line in filter(match_uuid, f.readlines()):
1089 refcount += 1
1090 if line.find(uuid) != -1:
1091 found = True
1092 if not found:
1093 f.write("%s\n" % uuid)
1094 refcount += 1
1095 f.close()
1096 return refcount
1099def _decr_iscsiSR_refcount(targetIQN, uuid):
1100 filename = os.path.join(ISCSI_REFDIR, targetIQN)
1101 if not os.path.exists(filename):
1102 return 0
1103 try:
1104 f = open(filename, 'a+')
1105 except:
1106 raise xs_errors.XenError('LVMRefCount', \
1107 opterr='file %s' % filename)
1109 f.seek(0)
1110 output = []
1111 refcount = 0
1112 for line in filter(match_uuid, f.readlines()):
1113 if line.find(uuid) == -1:
1114 output.append(line.rstrip())
1115 refcount += 1
1116 if not refcount:
1117 os.unlink(filename)
1118 return refcount
1120 # Re-open file and truncate
1121 f.close()
1122 f = open(filename, 'w')
1123 for i in range(0, refcount):
1124 f.write("%s\n" % output[i])
1125 f.close()
1126 return refcount
1129# The agent enforces 1 PBD per SR per host, so we
1130# check for active SR entries not attached to this host
1131def test_activePoolPBDs(session, host, uuid):
1132 try:
1133 pbds = session.xenapi.PBD.get_all_records()
1134 except:
1135 raise xs_errors.XenError('APIPBDQuery')
1136 for pbd in pbds:
1137 record = pbds[pbd]
1138 if record["host"] != host and record["SR"] == uuid \
1139 and record["currently_attached"]:
1140 return True
1141 return False
1144def remove_mpathcount_field(session, host_ref, sr_ref, SCSIid):
1145 try:
1146 pbdref = find_my_pbd(session, host_ref, sr_ref)
1147 if pbdref is not None:
1148 key = "mpath-" + SCSIid
1149 session.xenapi.PBD.remove_from_other_config(pbdref, key)
1150 except:
1151 pass
1154def kickpipe_mpathcount():
1155 """
1156 Issue a kick to the mpathcount service. This will ensure that mpathcount runs
1157 shortly to update the multipath config records, if it was not already activated
1158 by a UDEV event.
1159 """
1160 cmd = [CMD_KICKPIPE, "mpathcount"]
1161 (rc, stdout, stderr) = doexec(cmd)
1162 return (rc == 0)
1165def _testHost(hostname, port, errstring):
1166 SMlog("_testHost: Testing host/port: %s,%d" % (hostname, port))
1167 try:
1168 sockinfo = socket.getaddrinfo(hostname, int(port))[0]
1169 except:
1170 logException('Exception occured getting IP for %s' % hostname)
1171 raise xs_errors.XenError('DNSError')
1173 timeout = 5
1175 sock = socket.socket(sockinfo[0], socket.SOCK_STREAM)
1176 # Only allow the connect to block for up to timeout seconds
1177 sock.settimeout(timeout)
1178 try:
1179 sock.connect(sockinfo[4])
1180 # Fix for MS storage server bug
1181 sock.send(b'\n')
1182 sock.close()
1183 except socket.error as reason:
1184 SMlog("_testHost: Connect failed after %d seconds (%s) - %s" \
1185 % (timeout, hostname, reason))
1186 raise xs_errors.XenError(errstring)
1189def match_scsiID(s, id):
1190 regex = re.compile(id)
1191 return regex.search(s, 0)
1194def _isSCSIid(s):
1195 regex = re.compile("^scsi-")
1196 return regex.search(s, 0)
1199def is_usb_device(device):
1200 cmd = ["udevadm", "info", "-q", "path", "-n", device]
1201 result = pread2(cmd).split('/')
1202 return len(result) >= 5 and result[4].startswith('usb')
1205def test_scsiserial(session, device):
1206 device = os.path.realpath(device)
1207 if not scsiutil._isSCSIdev(device):
1208 SMlog("util.test_scsiserial: Not a serial device: %s" % device)
1209 return False
1210 serial = ""
1211 try:
1212 serial += scsiutil.getserial(device)
1213 except:
1214 # Error allowed, SCSIid is the important one
1215 pass
1217 try:
1218 scsiID = scsiutil.getSCSIid(device)
1219 except:
1220 SMlog("util.test_scsiserial: Unable to verify serial or SCSIid of device: %s" \
1221 % device)
1222 return False
1223 if not len(scsiID):
1224 SMlog("util.test_scsiserial: Unable to identify scsi device [%s] via scsiID" \
1225 % device)
1226 return False
1228 # USB devices can have identical SCSI IDs - prefer matching with serial number
1229 try:
1230 usb_device_with_serial = serial and is_usb_device(device)
1231 except:
1232 usb_device_with_serial = False
1233 SMlog("Unable to check if device is USB:")
1234 SMlog(traceback.format_exc())
1236 try:
1237 SRs = session.xenapi.SR.get_all_records()
1238 except:
1239 raise xs_errors.XenError('APIFailure')
1240 for SR in SRs:
1241 record = SRs[SR]
1242 conf = record["sm_config"]
1243 if 'devserial' in conf:
1244 for dev in conf['devserial'].split(','):
1245 if not usb_device_with_serial and _isSCSIid(dev):
1246 if match_scsiID(dev, scsiID):
1247 return True
1248 elif len(serial) and dev == serial:
1249 return True
1250 return False
1253def default(self, field, thunk):
1254 try:
1255 return getattr(self, field)
1256 except:
1257 return thunk()
1260def list_VDI_records_in_sr(sr):
1261 """Helper function which returns a list of all VDI records for this SR
1262 stored in the XenAPI server, useful for implementing SR.scan"""
1263 sr_ref = sr.session.xenapi.SR.get_by_uuid(sr.uuid)
1264 vdis = sr.session.xenapi.VDI.get_all_records_where("field \"SR\" = \"%s\"" % sr_ref)
1265 return vdis
1268# Given a partition (e.g. sda1), get a disk name:
1269def diskFromPartition(partition):
1270 # check whether this is a device mapper device (e.g. /dev/dm-0)
1271 m = re.match('(/dev/)?(dm-[0-9]+)(p[0-9]+)?$', partition)
1272 if m is not None: 1272 ↛ 1273line 1272 didn't jump to line 1273, because the condition on line 1272 was never true
1273 return m.group(2)
1275 numlen = 0 # number of digit characters
1276 m = re.match(r"\D+(\d+)", partition)
1277 if m is not None: 1277 ↛ 1278line 1277 didn't jump to line 1278, because the condition on line 1277 was never true
1278 numlen = len(m.group(1))
1280 # is it a cciss?
1281 if True in [partition.startswith(x) for x in ['cciss', 'ida', 'rd']]: 1281 ↛ 1282line 1281 didn't jump to line 1282, because the condition on line 1281 was never true
1282 numlen += 1 # need to get rid of trailing 'p'
1284 # is it a mapper path?
1285 if partition.startswith("mapper"): 1285 ↛ 1286line 1285 didn't jump to line 1286, because the condition on line 1285 was never true
1286 if re.search("p[0-9]*$", partition):
1287 numlen = len(re.match(r"\d+", partition[::-1]).group(0)) + 1
1288 SMlog("Found mapper part, len %d" % numlen)
1289 else:
1290 numlen = 0
1292 # is it /dev/disk/by-id/XYZ-part<k>?
1293 if partition.startswith("disk/by-id"): 1293 ↛ 1294line 1293 didn't jump to line 1294, because the condition on line 1293 was never true
1294 return partition[:partition.rfind("-part")]
1296 return partition[:len(partition) - numlen]
1299def dom0_disks():
1300 """Disks carrying dom0, e.g. ['/dev/sda']"""
1301 disks = []
1302 with open("/etc/mtab", 'r') as f:
1303 for line in f:
1304 (dev, mountpoint, fstype, opts, freq, passno) = line.split(' ')
1305 if mountpoint == '/':
1306 disk = diskFromPartition(dev)
1307 if not (disk in disks):
1308 disks.append(disk)
1309 SMlog("Dom0 disks: %s" % disks)
1310 return disks
1313def set_scheduler_sysfs_node(node, scheds):
1314 """
1315 Set the scheduler for a sysfs node (e.g. '/sys/block/sda')
1316 according to prioritized list schedulers
1317 Try to set the first item, then fall back to the next on failure
1318 """
1320 path = os.path.join(node, "queue", "scheduler")
1321 if not os.path.exists(path): 1321 ↛ 1325line 1321 didn't jump to line 1325, because the condition on line 1321 was never false
1322 SMlog("no path %s" % path)
1323 return
1325 stored_error = None
1326 for sched in scheds:
1327 try:
1328 with open(path, 'w') as file:
1329 file.write("%s\n" % sched)
1330 SMlog("Set scheduler to [%s] on [%s]" % (sched, node))
1331 return
1332 except (OSError, IOError) as err:
1333 stored_error = err
1335 SMlog("Error setting schedulers to [%s] on [%s], %s" % (scheds, node, str(stored_error)))
1338def set_scheduler(dev, schedulers=None):
1339 if schedulers is None: 1339 ↛ 1342line 1339 didn't jump to line 1342, because the condition on line 1339 was never false
1340 schedulers = ["none", "noop"]
1342 devices = []
1343 if not scsiutil.match_dm(dev): 1343 ↛ 1347line 1343 didn't jump to line 1347, because the condition on line 1343 was never false
1344 # Remove partition numbers
1345 devices.append(diskFromPartition(dev).replace('/', '!'))
1346 else:
1347 rawdev = diskFromPartition(dev)
1348 devices = [os.path.realpath(x)[5:] for x in scsiutil._genReverseSCSIidmap(rawdev.split('/')[-1])]
1350 for d in devices:
1351 set_scheduler_sysfs_node("/sys/block/%s" % d, schedulers)
1354# This function queries XAPI for the existing VDI records for this SR
1355def _getVDIs(srobj):
1356 VDIs = []
1357 try:
1358 sr_ref = getattr(srobj, 'sr_ref')
1359 except AttributeError:
1360 return VDIs
1362 refs = srobj.session.xenapi.SR.get_VDIs(sr_ref)
1363 for vdi in refs:
1364 ref = srobj.session.xenapi.VDI.get_record(vdi)
1365 ref['vdi_ref'] = vdi
1366 VDIs.append(ref)
1367 return VDIs
1370def get_sr_uuid_from_vdi_ref(session, vdi_ref: str) -> str:
1371 sr_ref = session.xenapi.VDI.get_SR(vdi_ref)
1372 return session.xenapi.SR.get_uuid(sr_ref)
1375def get_sr_uuid_from_vdi_uuid(session, vdi_uuid: str) -> str:
1376 return get_sr_uuid_from_vdi_ref(session, session.xenapi.VDI.get_by_uuid(vdi_uuid))
1379def _getVDI(srobj, vdi_uuid):
1380 vdi = srobj.session.xenapi.VDI.get_by_uuid(vdi_uuid)
1381 ref = srobj.session.xenapi.VDI.get_record(vdi)
1382 ref['vdi_ref'] = vdi
1383 return ref
1386def _convertDNS(name):
1387 addr = socket.getaddrinfo(name, None)[0][4][0]
1388 return addr
1391def _containsVDIinuse(srobj):
1392 VDIs = _getVDIs(srobj)
1393 for vdi in VDIs:
1394 if not vdi['managed']:
1395 continue
1396 sm_config = vdi['sm_config']
1397 if 'SRRef' in sm_config:
1398 try:
1399 PBDs = srobj.session.xenapi.SR.get_PBDs(sm_config['SRRef'])
1400 for pbd in PBDs:
1401 record = PBDs[pbd]
1402 if record["host"] == srobj.host_ref and \
1403 record["currently_attached"]:
1404 return True
1405 except:
1406 pass
1407 return False
1410def isVDICommand(cmd):
1411 if cmd is None or cmd in ["vdi_attach", "vdi_detach",
1412 "vdi_activate", "vdi_deactivate",
1413 "vdi_epoch_begin", "vdi_epoch_end"]:
1414 return True
1415 else:
1416 return False
1419#########################
1420# Daemon helper functions
1421def p_id_fork():
1422 try:
1423 p_id = os.fork()
1424 except OSError as e:
1425 print("Fork failed: %s (%d)" % (e.strerror, e.errno))
1426 sys.exit(-1)
1428 if (p_id == 0):
1429 os.setsid()
1430 try:
1431 p_id = os.fork()
1432 except OSError as e:
1433 print("Fork failed: %s (%d)" % (e.strerror, e.errno))
1434 sys.exit(-1)
1435 if (p_id == 0):
1436 os.chdir('/opt/xensource/sm')
1437 os.umask(0)
1438 else:
1439 os._exit(0)
1440 else:
1441 os._exit(0)
1444def daemon():
1445 p_id_fork()
1446 # Query the max file descriptor parameter for this process
1447 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1449 # Close any fds that are open
1450 for fd in range(0, maxfd):
1451 try:
1452 os.close(fd)
1453 except:
1454 pass
1456 # Redirect STDIN to STDOUT and STDERR
1457 os.open('/dev/null', os.O_RDWR)
1458 os.dup2(0, 1)
1459 os.dup2(0, 2)
1461################################################################################
1462#
1463# Fist points
1464#
1466# * The global variable 'fistpoint' define the list of all possible fistpoints;
1467#
1468# * To activate a fistpoint called 'name', you need to create the file '/tmp/fist_name'
1469# on the SR master;
1470#
1471# * At the moment, activating a fist point can lead to two possible behaviors:
1472# - if '/tmp/fist_LVHDRT_exit' exists, then the function called during the fistpoint is _exit;
1473# - otherwise, the function called is _pause.
1475def _pause(secs, name):
1476 SMlog("Executing fist point %s: sleeping %d seconds ..." % (name, secs))
1477 time.sleep(secs)
1478 SMlog("Executing fist point %s: done" % name)
1481def _exit(name):
1482 SMlog("Executing fist point %s: exiting the current process ..." % name)
1483 raise xs_errors.XenError('FistPoint', opterr='%s' % name)
1486class FistPoint:
1487 def __init__(self, points):
1488 #SMlog("Fist points loaded")
1489 self.points = points
1491 def is_legal(self, name):
1492 return (name in self.points)
1494 def is_active(self, name):
1495 return os.path.exists("/tmp/fist_%s" % name)
1497 def mark_sr(self, name, sruuid, started):
1498 with ApiSession("SM-sr-fist-point") as session:
1499 sr = session.xenapi.SR.get_by_uuid(sruuid)
1500 if started:
1501 session.xenapi.SR.add_to_other_config(sr, name, "active")
1502 else:
1503 session.xenapi.SR.remove_from_other_config(sr, name)
1505 def activate(self, name, sruuid):
1506 if name in self.points:
1507 if self.is_active(name):
1508 self.mark_sr(name, sruuid, True)
1509 if self.is_active("LVHDRT_exit"): 1509 ↛ 1510line 1509 didn't jump to line 1510, because the condition on line 1509 was never true
1510 self.mark_sr(name, sruuid, False)
1511 _exit(name)
1512 else:
1513 _pause(FIST_PAUSE_PERIOD, name)
1514 self.mark_sr(name, sruuid, False)
1515 else:
1516 SMlog("Unknown fist point: %s" % name)
1518 def activate_custom_fn(self, name, fn):
1519 if name in self.points: 1519 ↛ 1525line 1519 didn't jump to line 1525, because the condition on line 1519 was never false
1520 if self.is_active(name): 1520 ↛ 1521line 1520 didn't jump to line 1521, because the condition on line 1520 was never true
1521 SMlog("Executing fist point %s: starting ..." % name)
1522 fn()
1523 SMlog("Executing fist point %s: done" % name)
1524 else:
1525 SMlog("Unknown fist point: %s" % name)
1528def list_find(f, seq):
1529 for item in seq:
1530 if f(item):
1531 return item
1533GCPAUSE_FISTPOINT = "GCLoop_no_pause"
1535fistpoint = FistPoint(["LVHDRT_finding_a_suitable_pair",
1536 "LVHDRT_inflating_the_parent",
1537 "LVHDRT_resizing_while_vdis_are_paused",
1538 "LVHDRT_coalescing_VHD_data",
1539 "LVHDRT_coalescing_before_inflate_grandparent",
1540 "LVHDRT_relinking_grandchildren",
1541 "LVHDRT_before_create_relink_journal",
1542 "LVHDRT_xapiSM_serialization_tests",
1543 "LVHDRT_clone_vdi_after_create_journal",
1544 "LVHDRT_clone_vdi_after_shrink_parent",
1545 "LVHDRT_clone_vdi_after_first_snap",
1546 "LVHDRT_clone_vdi_after_second_snap",
1547 "LVHDRT_clone_vdi_after_parent_hidden",
1548 "LVHDRT_clone_vdi_after_parent_ro",
1549 "LVHDRT_clone_vdi_before_remove_journal",
1550 "LVHDRT_clone_vdi_after_lvcreate",
1551 "LVHDRT_clone_vdi_before_undo_clone",
1552 "LVHDRT_clone_vdi_after_undo_clone",
1553 "LVHDRT_inflate_after_create_journal",
1554 "LVHDRT_inflate_after_setSize",
1555 "LVHDRT_inflate_after_zeroOut",
1556 "LVHDRT_inflate_after_setSizePhys",
1557 "LVHDRT_inflate_after_setSizePhys",
1558 "LVHDRT_coaleaf_before_coalesce",
1559 "LVHDRT_coaleaf_after_coalesce",
1560 "LVHDRT_coaleaf_one_renamed",
1561 "LVHDRT_coaleaf_both_renamed",
1562 "LVHDRT_coaleaf_after_vdirec",
1563 "LVHDRT_coaleaf_before_delete",
1564 "LVHDRT_coaleaf_after_delete",
1565 "LVHDRT_coaleaf_before_remove_j",
1566 "LVHDRT_coaleaf_undo_after_rename",
1567 "LVHDRT_coaleaf_undo_after_rename2",
1568 "LVHDRT_coaleaf_undo_after_refcount",
1569 "LVHDRT_coaleaf_undo_after_deflate",
1570 "LVHDRT_coaleaf_undo_end",
1571 "LVHDRT_coaleaf_stop_after_recovery",
1572 "LVHDRT_coaleaf_finish_after_inflate",
1573 "LVHDRT_coaleaf_finish_end",
1574 "LVHDRT_coaleaf_delay_1",
1575 "LVHDRT_coaleaf_delay_2",
1576 "LVHDRT_coaleaf_delay_3",
1577 "testsm_clone_allow_raw",
1578 "xenrt_default_vdi_type_legacy",
1579 "blktap_activate_inject_failure",
1580 "blktap_activate_error_handling",
1581 GCPAUSE_FISTPOINT,
1582 "cleanup_coalesceVHD_inject_failure",
1583 "cleanup_tracker_no_progress",
1584 "FileSR_fail_hardlink",
1585 "FileSR_fail_snap1",
1586 "FileSR_fail_snap2",
1587 "LVM_journaler_exists",
1588 "LVM_journaler_none",
1589 "LVM_journaler_badname",
1590 "LVM_journaler_readfail",
1591 "LVM_journaler_writefail"])
1594def set_dirty(session, sr):
1595 try:
1596 session.xenapi.SR.add_to_other_config(sr, "dirty", "")
1597 SMlog("set_dirty %s succeeded" % (repr(sr)))
1598 except:
1599 SMlog("set_dirty %s failed (flag already set?)" % (repr(sr)))
1602def doesFileHaveOpenHandles(fileName):
1603 SMlog("Entering doesFileHaveOpenHandles with file: %s" % fileName)
1604 (retVal, processAndPidTuples) = \
1605 findRunningProcessOrOpenFile(fileName, False)
1607 if not retVal:
1608 SMlog("Failed to determine if file %s has open handles." % \
1609 fileName)
1610 # err on the side of caution
1611 return True
1612 else:
1613 if len(processAndPidTuples) > 0:
1614 return True
1615 else:
1616 return False
1619# extract SR uuid from the passed in devmapper entry and return
1620# /dev/mapper/VG_XenStorage--c3d82e92--cb25--c99b--b83a--482eebab4a93-MGT
1621def extractSRFromDevMapper(path):
1622 try:
1623 path = os.path.basename(path)
1624 path = path[len('VG_XenStorage-') + 1:]
1625 path = path.replace('--', '/')
1626 path = path[0:path.rfind('-')]
1627 return path.replace('/', '-')
1628 except:
1629 return ''
1632def pid_is_alive(pid):
1633 """
1634 Try to kill PID with signal 0.
1635 If we succeed, the PID is alive, so return True.
1636 If we get an EPERM error, the PID is alive but we are not allowed to
1637 signal it. Still return true.
1638 Any other error (e.g. ESRCH), return False
1639 """
1640 try:
1641 os.kill(pid, 0)
1642 return True
1643 except OSError as e:
1644 if e.errno == errno.EPERM:
1645 return True
1646 return False
1649def get_process_cmdline(pid: int) -> List[str]:
1650 try:
1651 with open(os.path.join('/proc', str(pid), 'cmdline'), 'rb') as f:
1652 line = f.read().split(b'\0')
1653 return [arg.decode() for arg in line]
1654 except OSError as e:
1655 if e.errno != errno.ENOENT:
1656 raise
1657 return []
1660# Looks at /proc and figures either
1661# If a process is still running (default), returns open file names
1662# If any running process has open handles to the given file (process = False)
1663# returns process names and pids
1664def findRunningProcessOrOpenFile(name, process=True):
1665 retVal = True
1666 links = []
1667 processandpids = []
1668 sockets = set()
1669 try:
1670 SMlog("Entering findRunningProcessOrOpenFile with params: %s" % \
1671 [name, process])
1673 # Look at all pids
1674 pids = [pid for pid in os.listdir('/proc') if pid.isdigit()]
1675 for pid in sorted(pids):
1676 try:
1677 try:
1678 f = None
1679 f = open(os.path.join('/proc', pid, 'cmdline'), 'r')
1680 prog = f.read()[:-1]
1681 if prog: 1681 ↛ 1690line 1681 didn't jump to line 1690, because the condition on line 1681 was never false
1682 # Just want the process name
1683 argv = prog.split('\x00')
1684 prog = argv[0]
1685 except IOError as e:
1686 if e.errno in (errno.ENOENT, errno.ESRCH):
1687 SMlog("ERROR %s reading %s, ignore" % (e.errno, pid))
1688 continue
1689 finally:
1690 if f is not None: 1690 ↛ 1675, 1690 ↛ 16932 missed branches: 1) line 1690 didn't jump to line 1675, because the continue on line 1688 wasn't executed, 2) line 1690 didn't jump to line 1693, because the condition on line 1690 was never false
1691 f.close() 1691 ↛ 1675line 1691 didn't jump to line 1675, because the continue on line 1688 wasn't executed
1693 try:
1694 fd_dir = os.path.join('/proc', pid, 'fd')
1695 files = os.listdir(fd_dir)
1696 except OSError as e:
1697 if e.errno in (errno.ENOENT, errno.ESRCH):
1698 SMlog("ERROR %s reading fds for %s, ignore" % (e.errno, pid))
1699 # Ignore pid that are no longer valid
1700 continue
1701 else:
1702 raise
1704 for file in files:
1705 try:
1706 link = os.readlink(os.path.join(fd_dir, file))
1707 except OSError:
1708 continue
1710 if process: 1710 ↛ 1715line 1710 didn't jump to line 1715, because the condition on line 1710 was never false
1711 if name == prog: 1711 ↛ 1704line 1711 didn't jump to line 1704, because the condition on line 1711 was never false
1712 links.append(link)
1713 else:
1714 # need to return process name and pid tuples
1715 if link == name:
1716 processandpids.append((prog, pid))
1718 # Get the connected sockets
1719 if name == prog:
1720 sockets.update(get_connected_sockets(pid))
1722 # We will only have a non-empty processandpids if some fd entries were found.
1723 # Before returning them, verify that all the PIDs in question are properly alive.
1724 # There is no specific guarantee of when a PID's /proc directory will disappear
1725 # when it exits, particularly relative to filedescriptor cleanup, so we want to
1726 # make sure we're not reporting a false positive.
1727 processandpids = [x for x in processandpids if pid_is_alive(int(x[1]))]
1728 for pp in processandpids: 1728 ↛ 1729line 1728 didn't jump to line 1729, because the loop on line 1728 never started
1729 SMlog(f"File {name} has an open handle with process {pp[0]} with pid {pp[1]}")
1731 except Exception as e:
1732 SMlog("Exception checking running process or open file handles. " \
1733 "Error: %s" % str(e))
1734 retVal = False
1736 if process: 1736 ↛ 1739line 1736 didn't jump to line 1739, because the condition on line 1736 was never false
1737 return retVal, links, sockets
1738 else:
1739 return retVal, processandpids
1742def get_connected_sockets(pid):
1743 sockets = set()
1744 try:
1745 # Lines in /proc/<pid>/net/unix are formatted as follows
1746 # (see Linux source net/unix/af_unix.c, unix_seq_show() )
1747 # - Pointer address to socket (hex)
1748 # - Refcount (HEX)
1749 # - 0
1750 # - State (HEX, 0 or __SO_ACCEPTCON)
1751 # - Type (HEX - but only 0001 of interest)
1752 # - Connection state (HEX - but only 03, SS_CONNECTED of interest)
1753 # - Inode number
1754 # - Path (optional)
1755 open_sock_matcher = re.compile(
1756 r'^[0-9a-f]+: [0-9A-Fa-f]+ [0-9A-Fa-f]+ [0-9A-Fa-f]+ 0001 03 \d+ (.*)$')
1757 with open(
1758 os.path.join('/proc', str(pid), 'net', 'unix'), 'r') as f:
1759 lines = f.readlines()
1760 for line in lines:
1761 match = open_sock_matcher.match(line)
1762 if match:
1763 sockets.add(match[1])
1764 except OSError as e:
1765 if e.errno in (errno.ENOENT, errno.ESRCH):
1766 # Ignore pid that are no longer valid
1767 SMlog("ERROR %s reading sockets for %s, ignore" %
1768 (e.errno, pid))
1769 else:
1770 raise
1771 return sockets
1774def retry(f, maxretry=20, period=3, exceptions=[Exception]):
1775 retries = 0
1776 while True:
1777 try:
1778 return f()
1779 except Exception as e:
1780 for exception in exceptions:
1781 if isinstance(e, exception):
1782 SMlog('Got exception: {}. Retry number: {}'.format(
1783 str(e), retries
1784 ))
1785 break
1786 else:
1787 SMlog('Got bad exception: {}. Raising...'.format(e))
1788 raise e
1790 retries += 1
1791 if retries >= maxretry:
1792 break
1794 time.sleep(period)
1796 return f()
1799def getCslDevPath(svid):
1800 basepath = "/dev/disk/by-csldev/"
1801 if svid.startswith("NETAPP_"):
1802 # special attention for NETAPP SVIDs
1803 svid_parts = svid.split("__")
1804 globstr = basepath + "NETAPP__LUN__" + "*" + svid_parts[2] + "*" + svid_parts[-1] + "*"
1805 else:
1806 globstr = basepath + svid + "*"
1808 return globstr
1811# Use device in /dev pointed to by cslg path which consists of svid
1812def get_scsiid_from_svid(md_svid):
1813 cslg_path = getCslDevPath(md_svid)
1814 abs_path = glob.glob(cslg_path)
1815 if abs_path:
1816 real_path = os.path.realpath(abs_path[0])
1817 return scsiutil.getSCSIid(real_path)
1818 else:
1819 return None
1822def get_isl_scsiids(session):
1823 # Get cslg type SRs
1824 SRs = session.xenapi.SR.get_all_records_where('field "type" = "cslg"')
1826 # Iterate through the SR to get the scsi ids
1827 scsi_id_ret = []
1828 for SR in SRs:
1829 sr_rec = SRs[SR]
1830 # Use the md_svid to get the scsi id
1831 scsi_id = get_scsiid_from_svid(sr_rec['sm_config']['md_svid'])
1832 if scsi_id:
1833 scsi_id_ret.append(scsi_id)
1835 # Get the vdis in the SR and do the same procedure
1836 vdi_recs = session.xenapi.VDI.get_all_records_where('field "SR" = "%s"' % SR)
1837 for vdi_rec in vdi_recs:
1838 vdi_rec = vdi_recs[vdi_rec]
1839 scsi_id = get_scsiid_from_svid(vdi_rec['sm_config']['SVID'])
1840 if scsi_id:
1841 scsi_id_ret.append(scsi_id)
1843 return scsi_id_ret
1846class extractXVA:
1847 # streams files as a set of file and checksum, caller should remove
1848 # the files, if not needed. The entire directory (Where the files
1849 # and checksum) will only be deleted as part of class cleanup.
1850 HDR_SIZE = 512
1851 BLOCK_SIZE = 512
1852 SIZE_LEN = 12 - 1 # To remove \0 from tail
1853 SIZE_OFFSET = 124
1854 ZERO_FILLED_REC = 2
1855 NULL_IDEN = '\x00'
1856 DIR_IDEN = '/'
1857 CHECKSUM_IDEN = '.checksum'
1858 OVA_FILE = 'ova.xml'
1860 # Init gunzips the file using a subprocess, and reads stdout later
1861 # as and when needed
1862 def __init__(self, filename):
1863 self.__extract_path = ''
1864 self.__filename = filename
1865 cmd = 'gunzip -cd %s' % filename
1866 try:
1867 self.spawn_p = subprocess.Popen(
1868 cmd, shell=True, \
1869 stdin=subprocess.PIPE, stdout=subprocess.PIPE, \
1870 stderr=subprocess.PIPE, close_fds=True)
1871 except Exception as e:
1872 SMlog("Error: %s. Uncompress failed for %s" % (str(e), filename))
1873 raise Exception(str(e))
1875 # Create dir to extract the files
1876 self.__extract_path = tempfile.mkdtemp()
1878 def __del__(self):
1879 shutil.rmtree(self.__extract_path)
1881 # Class supports Generator expression. 'for f_name, checksum in getTuple()'
1882 # returns filename, checksum content. Returns filename, '' in case
1883 # of checksum file missing. e.g. ova.xml
1884 def getTuple(self):
1885 zerod_record = 0
1886 ret_f_name = ''
1887 ret_base_f_name = ''
1889 try:
1890 # Read tar file as sets of file and checksum.
1891 while True:
1892 # Read the output of spawned process, or output of gunzip
1893 f_hdr = self.spawn_p.stdout.read(self.HDR_SIZE)
1895 # Break out in case of end of file
1896 if f_hdr == '':
1897 if zerod_record == extractXVA.ZERO_FILLED_REC:
1898 break
1899 else:
1900 SMlog('Error. Expects %d zero records', \
1901 extractXVA.ZERO_FILLED_REC)
1902 raise Exception('Unrecognized end of file')
1904 # Watch out for zero records, two zero records
1905 # denote end of file.
1906 if f_hdr == extractXVA.NULL_IDEN * extractXVA.HDR_SIZE:
1907 zerod_record += 1
1908 continue
1910 f_name = f_hdr[:f_hdr.index(extractXVA.NULL_IDEN)]
1911 # File header may be for a folder, if so ignore the header
1912 if not f_name.endswith(extractXVA.DIR_IDEN):
1913 f_size_octal = f_hdr[extractXVA.SIZE_OFFSET: \
1914 extractXVA.SIZE_OFFSET + extractXVA.SIZE_LEN]
1915 f_size = int(f_size_octal, 8)
1916 if f_name.endswith(extractXVA.CHECKSUM_IDEN):
1917 if f_name.rstrip(extractXVA.CHECKSUM_IDEN) == \
1918 ret_base_f_name:
1919 checksum = self.spawn_p.stdout.read(f_size)
1920 yield(ret_f_name, checksum)
1921 else:
1922 # Expects file followed by its checksum
1923 SMlog('Error. Sequence mismatch starting with %s', \
1924 ret_f_name)
1925 raise Exception( \
1926 'Files out of sequence starting with %s', \
1927 ret_f_name)
1928 else:
1929 # In case of ova.xml, read the contents into a file and
1930 # return the file name to the caller. For other files,
1931 # read the contents into a file, it will
1932 # be used when a .checksum file is encountered.
1933 ret_f_name = '%s/%s' % (self.__extract_path, f_name)
1934 ret_base_f_name = f_name
1936 # Check if the folder exists on the target location,
1937 # else create it.
1938 folder_path = ret_f_name[:ret_f_name.rfind('/')]
1939 if not os.path.exists(folder_path):
1940 os.mkdir(folder_path)
1942 # Store the file to the tmp folder, strip the tail \0
1943 f = open(ret_f_name, 'w')
1944 f.write(self.spawn_p.stdout.read(f_size))
1945 f.close()
1946 if f_name == extractXVA.OVA_FILE:
1947 yield(ret_f_name, '')
1949 # Skip zero'd portion of data block
1950 round_off = f_size % extractXVA.BLOCK_SIZE
1951 if round_off != 0:
1952 zeros = self.spawn_p.stdout.read(
1953 extractXVA.BLOCK_SIZE - round_off)
1954 except Exception as e:
1955 SMlog("Error: %s. File set extraction failed %s" % (str(e), \
1956 self.__filename))
1958 # Kill and Drain stdout of the gunzip process,
1959 # else gunzip might block on stdout
1960 os.kill(self.spawn_p.pid, signal.SIGTERM)
1961 self.spawn_p.communicate()
1962 raise Exception(str(e))
1964illegal_xml_chars = [(0x00, 0x08), (0x0B, 0x1F), (0x7F, 0x84), (0x86, 0x9F),
1965 (0xD800, 0xDFFF), (0xFDD0, 0xFDDF), (0xFFFE, 0xFFFF),
1966 (0x1FFFE, 0x1FFFF), (0x2FFFE, 0x2FFFF), (0x3FFFE, 0x3FFFF),
1967 (0x4FFFE, 0x4FFFF), (0x5FFFE, 0x5FFFF), (0x6FFFE, 0x6FFFF),
1968 (0x7FFFE, 0x7FFFF), (0x8FFFE, 0x8FFFF), (0x9FFFE, 0x9FFFF),
1969 (0xAFFFE, 0xAFFFF), (0xBFFFE, 0xBFFFF), (0xCFFFE, 0xCFFFF),
1970 (0xDFFFE, 0xDFFFF), (0xEFFFE, 0xEFFFF), (0xFFFFE, 0xFFFFF),
1971 (0x10FFFE, 0x10FFFF)]
1973illegal_ranges = ["%s-%s" % (chr(low), chr(high))
1974 for (low, high) in illegal_xml_chars
1975 if low < sys.maxunicode]
1977illegal_xml_re = re.compile(u'[%s]' % u''.join(illegal_ranges))
1980def isLegalXMLString(s):
1981 """Tells whether this is a valid XML string (i.e. it does not contain
1982 illegal XML characters specified in
1983 http://www.w3.org/TR/2004/REC-xml-20040204/#charsets).
1984 """
1986 if len(s) > 0:
1987 return re.search(illegal_xml_re, s) is None
1988 else:
1989 return True
1992def unictrunc(string, max_bytes):
1993 """
1994 Given a string, returns the largest number of elements for a prefix
1995 substring of it, such that the UTF-8 encoding of this substring takes no
1996 more than the given number of bytes.
1998 The string may be given as a unicode string or a UTF-8 encoded byte
1999 string, and the number returned will be in characters or bytes
2000 accordingly. Note that in the latter case, the substring will still be a
2001 valid UTF-8 encoded string (which is to say, it won't have been truncated
2002 part way through a multibyte sequence for a unicode character).
2004 string: the string to truncate
2005 max_bytes: the maximum number of bytes the truncated string can be
2006 """
2007 if isinstance(string, str):
2008 return_chars = True
2009 else:
2010 return_chars = False
2011 string = string.decode('UTF-8')
2013 cur_chars = 0
2014 cur_bytes = 0
2015 for char in string:
2016 charsize = len(char.encode('UTF-8'))
2017 if cur_bytes + charsize > max_bytes:
2018 break
2019 else:
2020 cur_chars += 1
2021 cur_bytes += charsize
2022 return cur_chars if return_chars else cur_bytes
2025def hideValuesInPropMap(propmap, propnames):
2026 """
2027 Worker function: input simple map of prop name/value pairs, and
2028 a list of specific propnames whose values we want to hide.
2029 Loop through the "hide" list, and if any are found, hide the
2030 value and return the altered map.
2031 If none found, return the original map
2032 """
2033 matches = []
2034 for propname in propnames:
2035 if propname in propmap: 2035 ↛ 2036line 2035 didn't jump to line 2036, because the condition on line 2035 was never true
2036 matches.append(propname)
2038 if matches: 2038 ↛ 2039line 2038 didn't jump to line 2039, because the condition on line 2038 was never true
2039 deepCopyRec = copy.deepcopy(propmap)
2040 for match in matches:
2041 deepCopyRec[match] = '******'
2042 return deepCopyRec
2044 return propmap
2045# define the list of propnames whose value we want to hide
2047PASSWD_PROP_KEYS = ['password', 'cifspassword', 'chappassword', 'incoming_chappassword']
2048DEFAULT_SEGMENT_LEN = 950
2051def hidePasswdInConfig(config):
2052 """
2053 Function to hide passwd values in a simple prop map,
2054 for example "device_config"
2055 """
2056 return hideValuesInPropMap(config, PASSWD_PROP_KEYS)
2059def hidePasswdInParams(params, configProp):
2060 """
2061 Function to hide password values in a specified property which
2062 is a simple map of prop name/values, and is itself an prop entry
2063 in a larger property map.
2064 For example, param maps containing "device_config", or
2065 "sm_config", etc
2066 """
2067 params[configProp] = hideValuesInPropMap(params[configProp], PASSWD_PROP_KEYS)
2068 return params
2071def hideMemberValuesInXmlParams(xmlParams, propnames=PASSWD_PROP_KEYS):
2072 """
2073 Function to hide password values in XML params, specifically
2074 for the XML format of incoming params to SR modules.
2075 Uses text parsing: loop through the list of specific propnames
2076 whose values we want to hide, and:
2077 - Assemble a full "prefix" containing each property name, e.g.,
2078 "<member><name>password</name><value>"
2079 - Test the XML if it contains that string, save the index.
2080 - If found, get the index of the ending tag
2081 - Truncate the return string starting with the password value.
2082 - Append the substitute "*******" value string.
2083 - Restore the rest of the original string starting with the end tag.
2084 """
2085 findStrPrefixHead = "<member><name>"
2086 findStrPrefixTail = "</name><value>"
2087 findStrSuffix = "</value>"
2088 strlen = len(xmlParams)
2090 for propname in propnames:
2091 findStrPrefix = findStrPrefixHead + propname + findStrPrefixTail
2092 idx = xmlParams.find(findStrPrefix)
2093 if idx != -1: # if found any of them
2094 idx += len(findStrPrefix)
2095 idx2 = xmlParams.find(findStrSuffix, idx)
2096 if idx2 != -1:
2097 retStr = xmlParams[0:idx]
2098 retStr += "******"
2099 retStr += xmlParams[idx2:strlen]
2100 return retStr
2101 else:
2102 return xmlParams
2103 return xmlParams
2106def splitXmlText(xmlData, segmentLen=DEFAULT_SEGMENT_LEN, showContd=False):
2107 """
2108 Split xml string data into substrings small enough for the
2109 syslog line length limit. Split at tag end markers ( ">" ).
2110 Usage:
2111 strList = []
2112 strList = splitXmlText( longXmlText, maxLineLen ) # maxLineLen is optional
2113 """
2114 remainingData = str(xmlData)
2116 # "Un-pretty-print"
2117 remainingData = remainingData.replace('\n', '')
2118 remainingData = remainingData.replace('\t', '')
2120 remainingChars = len(remainingData)
2121 returnData = ''
2123 thisLineNum = 0
2124 while remainingChars > segmentLen:
2125 thisLineNum = thisLineNum + 1
2126 index = segmentLen
2127 tmpStr = remainingData[:segmentLen]
2128 tmpIndex = tmpStr.rfind('>')
2129 if tmpIndex != -1:
2130 index = tmpIndex + 1
2132 tmpStr = tmpStr[:index]
2133 remainingData = remainingData[index:]
2134 remainingChars = len(remainingData)
2136 if showContd:
2137 if thisLineNum != 1:
2138 tmpStr = '(Cont\'d): ' + tmpStr
2139 tmpStr = tmpStr + ' (Cont\'d):'
2141 returnData += tmpStr + '\n'
2143 if showContd and thisLineNum > 0:
2144 remainingData = '(Cont\'d): ' + remainingData
2145 returnData += remainingData
2147 return returnData
2150def inject_failure():
2151 raise Exception('injected failure')
2154def open_atomic(path, mode=None):
2155 """Atomically creates a file if, and only if it does not already exist.
2156 Leaves the file open and returns the file object.
2158 path: the path to atomically open
2159 mode: "r" (read), "w" (write), or "rw" (read/write)
2160 returns: an open file object"""
2162 assert path
2164 flags = os.O_CREAT | os.O_EXCL
2165 modes = {'r': os.O_RDONLY, 'w': os.O_WRONLY, 'rw': os.O_RDWR}
2166 if mode:
2167 if mode not in modes:
2168 raise Exception('invalid access mode ' + mode)
2169 flags |= modes[mode]
2170 fd = os.open(path, flags)
2171 try:
2172 if mode:
2173 return os.fdopen(fd, mode)
2174 else:
2175 return os.fdopen(fd)
2176 except:
2177 os.close(fd)
2178 raise
2181def isInvalidVDI(exception):
2182 return exception.details[0] == "HANDLE_INVALID" or \
2183 exception.details[0] == "UUID_INVALID"
2186def get_pool_restrictions(session):
2187 """Returns pool restrictions as a map, @session must be already
2188 established."""
2189 return list(session.xenapi.pool.get_all_records().values())[0]['restrictions']
2192def read_caching_is_restricted(session):
2193 """Tells whether read caching is restricted."""
2194 if session is None: 2194 ↛ 2195line 2194 didn't jump to line 2195, because the condition on line 2194 was never true
2195 return True
2196 restrictions = get_pool_restrictions(session)
2197 if 'restrict_read_caching' in restrictions and \ 2197 ↛ 2199line 2197 didn't jump to line 2199, because the condition on line 2197 was never true
2198 restrictions['restrict_read_caching'] == "true":
2199 return True
2200 return False
2203def sessions_less_than_targets(other_config, device_config):
2204 if 'multihomelist' in device_config and 'iscsi_sessions' in other_config:
2205 sessions = int(other_config['iscsi_sessions'])
2206 targets = len(device_config['multihomelist'].split(','))
2207 SMlog("Targets %d and iscsi_sessions %d" % (targets, sessions))
2208 return (sessions < targets)
2209 else:
2210 return False
2213def enable_and_start_service(name, start):
2214 attempt = 0
2215 while True:
2216 attempt += 1
2217 fn = 'enable' if start else 'disable'
2218 args = ('systemctl', fn, '--now', name)
2219 (ret, out, err) = doexec(args)
2220 if ret == 0:
2221 return
2222 elif attempt >= 3:
2223 raise Exception(
2224 'Failed to {} {}: {} {}'.format(fn, name, out, err)
2225 )
2226 time.sleep(1)
2229def stop_service(name):
2230 args = ('systemctl', 'stop', name)
2231 (ret, out, err) = doexec(args)
2232 if ret == 0:
2233 return
2234 raise Exception('Failed to stop {}: {} {}'.format(name, out, err))
2237def restart_service(name):
2238 attempt = 0
2239 while True:
2240 attempt += 1
2241 SMlog('Restarting service {} {}...'.format(name, attempt))
2242 args = ('systemctl', 'restart', name)
2243 (ret, out, err) = doexec(args)
2244 if ret == 0:
2245 return
2246 elif attempt >= 3:
2247 SMlog('Restart service FAILED {} {}'.format(name, attempt))
2248 raise Exception(
2249 'Failed to restart {}: {} {}'.format(name, out, err)
2250 )
2251 time.sleep(1)
2254def check_pid_exists(pid):
2255 try:
2256 os.kill(pid, 0)
2257 except OSError:
2258 return False
2259 else:
2260 return True
2263def get_openers_pid(path: str) -> Optional[List[int]]:
2264 cmd = ["lsof", "-t", path]
2266 try:
2267 list = []
2268 ret = pread2(cmd)
2269 for line in ret.splitlines():
2270 list.append(int(line))
2271 return list
2272 except CommandException as e:
2273 if e.code == 1: # `lsof` return 1 if there is no openers
2274 return None
2275 else:
2276 raise e
2279def make_profile(name, function):
2280 """
2281 Helper to execute cProfile using unique log file.
2282 """
2284 import cProfile
2285 import itertools
2286 import os.path
2287 import time
2289 assert name
2290 assert function
2292 FOLDER = '/tmp/sm-perfs/'
2293 makedirs(FOLDER)
2295 filename = time.strftime('{}_%Y%m%d_%H%M%S.prof'.format(name))
2297 def gen_path(path):
2298 yield path
2299 root, ext = os.path.splitext(path)
2300 for i in itertools.count(start=1, step=1):
2301 yield root + '.{}.'.format(i) + ext
2303 for profile_path in gen_path(FOLDER + filename):
2304 try:
2305 file = open_atomic(profile_path, 'w')
2306 file.close()
2307 break
2308 except OSError as e:
2309 if e.errno == errno.EEXIST:
2310 pass
2311 else:
2312 raise
2314 try:
2315 SMlog('* Start profiling of {} ({}) *'.format(name, filename))
2316 cProfile.runctx('function()', None, locals(), profile_path)
2317 finally:
2318 SMlog('* End profiling of {} ({}) *'.format(name, filename))
2321def strtobool(str: str) -> bool:
2322 # Note: `distutils` package is deprecated and slated for removal in Python 3.12.
2323 # There is not alternative for strtobool.
2324 # See: https://peps.python.org/pep-0632/#migration-advice
2325 # So this is a custom implementation with differences:
2326 # - A boolean is returned instead of integer
2327 # - Empty string and None are supported (False is returned in this case)
2328 if not str: 2328 ↛ 2330line 2328 didn't jump to line 2330, because the condition on line 2328 was never false
2329 return False
2330 str = str.lower()
2331 if str in ('y', 'yes', 't', 'true', 'on', '1'):
2332 return True
2333 if str in ('n', 'no', 'f', 'false', 'off', '0'):
2334 return False
2335 raise ValueError("invalid truth value '{}'".format(str))
2338def find_executable(name):
2339 return shutil.which(name)
2342def conditional_decorator(decorator, condition):
2343 def wrapper(func):
2344 if not condition: 2344 ↛ 2346line 2344 didn't jump to line 2346, because the condition on line 2344 was never false
2345 return func
2346 return decorator(func)
2347 return wrapper
2350def get_srs_uuid_from_type(session, type):
2351 srs = session.xenapi.SR.get_all_records_where(f"field \"type\" = \"{type}\"")
2352 return {sr["uuid"]: sr for sr in srs.values()}
2355def get_linstor_srs_uuid(session):
2356 import LinstorSR # pylint: disable=C0415
2357 return get_srs_uuid_from_type(session, LinstorSR.LinstorSR.DRIVER_TYPE)
2360def find_pbd_ref_from_dconf_value(session, srs, key, value, value_modifier = None):
2361 for sr in srs.values():
2362 for pbd_ref in sr["PBDs"]:
2363 device_config = session.xenapi.PBD.get_device_config(pbd_ref)
2364 cur_value = device_config.get(key)
2365 if value_modifier:
2366 cur_value = value_modifier(cur_value)
2367 if cur_value and cur_value == value:
2368 return pbd_ref
2369 return None