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
1071def _incr_iscsiSR_refcount(targetIQN, uuid):
1072 if not os.path.exists(ISCSI_REFDIR):
1073 os.mkdir(ISCSI_REFDIR)
1074 filename = os.path.join(ISCSI_REFDIR, targetIQN)
1075 try:
1076 f = open(filename, 'a+')
1077 except:
1078 raise xs_errors.XenError('LVMRefCount', \
1079 opterr='file %s' % filename)
1081 f.seek(0)
1082 found = False
1083 refcount = 0
1084 for line in filter(match_uuid, f.readlines()):
1085 refcount += 1
1086 if line.find(uuid) != -1:
1087 found = True
1088 if not found:
1089 f.write("%s\n" % uuid)
1090 refcount += 1
1091 f.close()
1092 return refcount
1095def _decr_iscsiSR_refcount(targetIQN, uuid):
1096 filename = os.path.join(ISCSI_REFDIR, targetIQN)
1097 if not os.path.exists(filename):
1098 return 0
1099 try:
1100 f = open(filename, 'a+')
1101 except:
1102 raise xs_errors.XenError('LVMRefCount', \
1103 opterr='file %s' % filename)
1105 f.seek(0)
1106 output = []
1107 refcount = 0
1108 for line in filter(match_uuid, f.readlines()):
1109 if line.find(uuid) == -1:
1110 output.append(line.rstrip())
1111 refcount += 1
1112 if not refcount:
1113 os.unlink(filename)
1114 return refcount
1116 # Re-open file and truncate
1117 f.close()
1118 f = open(filename, 'w')
1119 for i in range(0, refcount):
1120 f.write("%s\n" % output[i])
1121 f.close()
1122 return refcount
1125# The agent enforces 1 PBD per SR per host, so we
1126# check for active SR entries not attached to this host
1127def test_activePoolPBDs(session, host, uuid):
1128 try:
1129 pbds = session.xenapi.PBD.get_all_records()
1130 except:
1131 raise xs_errors.XenError('APIPBDQuery')
1132 for pbd in pbds:
1133 record = pbds[pbd]
1134 if record["host"] != host and record["SR"] == uuid \
1135 and record["currently_attached"]:
1136 return True
1137 return False
1140def remove_mpathcount_field(session, host_ref, sr_ref, SCSIid):
1141 try:
1142 pbdref = find_my_pbd(session, host_ref, sr_ref)
1143 if pbdref is not None:
1144 key = "mpath-" + SCSIid
1145 session.xenapi.PBD.remove_from_other_config(pbdref, key)
1146 except:
1147 pass
1150def kickpipe_mpathcount():
1151 """
1152 Issue a kick to the mpathcount service. This will ensure that mpathcount runs
1153 shortly to update the multipath config records, if it was not already activated
1154 by a UDEV event.
1155 """
1156 cmd = [CMD_KICKPIPE, "mpathcount"]
1157 (rc, stdout, stderr) = doexec(cmd)
1158 return (rc == 0)
1161def _testHost(hostname, port, errstring):
1162 SMlog("_testHost: Testing host/port: %s,%d" % (hostname, port))
1163 try:
1164 sockinfo = socket.getaddrinfo(hostname, int(port))[0]
1165 except:
1166 logException('Exception occured getting IP for %s' % hostname)
1167 raise xs_errors.XenError('DNSError')
1169 timeout = 5
1171 sock = socket.socket(sockinfo[0], socket.SOCK_STREAM)
1172 # Only allow the connect to block for up to timeout seconds
1173 sock.settimeout(timeout)
1174 try:
1175 sock.connect(sockinfo[4])
1176 # Fix for MS storage server bug
1177 sock.send(b'\n')
1178 sock.close()
1179 except socket.error as reason:
1180 SMlog("_testHost: Connect failed after %d seconds (%s) - %s" \
1181 % (timeout, hostname, reason))
1182 raise xs_errors.XenError(errstring)
1185def match_scsiID(s, id):
1186 regex = re.compile(id)
1187 return regex.search(s, 0)
1190def _isSCSIid(s):
1191 regex = re.compile("^scsi-")
1192 return regex.search(s, 0)
1195def is_usb_device(device):
1196 cmd = ["udevadm", "info", "-q", "path", "-n", device]
1197 result = pread2(cmd).split('/')
1198 return len(result) >= 5 and result[4].startswith('usb')
1201def test_scsiserial(session, device):
1202 device = os.path.realpath(device)
1203 if not scsiutil._isSCSIdev(device):
1204 SMlog("util.test_scsiserial: Not a serial device: %s" % device)
1205 return False
1206 serial = ""
1207 try:
1208 serial += scsiutil.getserial(device)
1209 except:
1210 # Error allowed, SCSIid is the important one
1211 pass
1213 try:
1214 scsiID = scsiutil.getSCSIid(device)
1215 except:
1216 SMlog("util.test_scsiserial: Unable to verify serial or SCSIid of device: %s" \
1217 % device)
1218 return False
1219 if not len(scsiID):
1220 SMlog("util.test_scsiserial: Unable to identify scsi device [%s] via scsiID" \
1221 % device)
1222 return False
1224 # USB devices can have identical SCSI IDs - prefer matching with serial number
1225 try:
1226 usb_device_with_serial = serial and is_usb_device(device)
1227 except:
1228 usb_device_with_serial = False
1229 SMlog("Unable to check if device is USB:")
1230 SMlog(traceback.format_exc())
1232 try:
1233 SRs = session.xenapi.SR.get_all_records()
1234 except:
1235 raise xs_errors.XenError('APIFailure')
1236 for SR in SRs:
1237 record = SRs[SR]
1238 conf = record["sm_config"]
1239 if 'devserial' in conf:
1240 for dev in conf['devserial'].split(','):
1241 if not usb_device_with_serial and _isSCSIid(dev):
1242 if match_scsiID(dev, scsiID):
1243 return True
1244 elif len(serial) and dev == serial:
1245 return True
1246 return False
1249def default(self, field, thunk):
1250 try:
1251 return getattr(self, field)
1252 except:
1253 return thunk()
1256def list_VDI_records_in_sr(sr):
1257 """Helper function which returns a list of all VDI records for this SR
1258 stored in the XenAPI server, useful for implementing SR.scan"""
1259 sr_ref = sr.session.xenapi.SR.get_by_uuid(sr.uuid)
1260 vdis = sr.session.xenapi.VDI.get_all_records_where("field \"SR\" = \"%s\"" % sr_ref)
1261 return vdis
1264# Given a partition (e.g. sda1), get a disk name:
1265def diskFromPartition(partition):
1266 # check whether this is a device mapper device (e.g. /dev/dm-0)
1267 m = re.match('(/dev/)?(dm-[0-9]+)(p[0-9]+)?$', partition)
1268 if m is not None: 1268 ↛ 1269line 1268 didn't jump to line 1269, because the condition on line 1268 was never true
1269 return m.group(2)
1271 numlen = 0 # number of digit characters
1272 m = re.match(r"\D+(\d+)", partition)
1273 if m is not None: 1273 ↛ 1274line 1273 didn't jump to line 1274, because the condition on line 1273 was never true
1274 numlen = len(m.group(1))
1276 # is it a cciss?
1277 if True in [partition.startswith(x) for x in ['cciss', 'ida', 'rd']]: 1277 ↛ 1278line 1277 didn't jump to line 1278, because the condition on line 1277 was never true
1278 numlen += 1 # need to get rid of trailing 'p'
1280 # is it a mapper path?
1281 if partition.startswith("mapper"): 1281 ↛ 1282line 1281 didn't jump to line 1282, because the condition on line 1281 was never true
1282 if re.search("p[0-9]*$", partition):
1283 numlen = len(re.match(r"\d+", partition[::-1]).group(0)) + 1
1284 SMlog("Found mapper part, len %d" % numlen)
1285 else:
1286 numlen = 0
1288 # is it /dev/disk/by-id/XYZ-part<k>?
1289 if partition.startswith("disk/by-id"): 1289 ↛ 1290line 1289 didn't jump to line 1290, because the condition on line 1289 was never true
1290 return partition[:partition.rfind("-part")]
1292 return partition[:len(partition) - numlen]
1295def dom0_disks():
1296 """Disks carrying dom0, e.g. ['/dev/sda']"""
1297 disks = []
1298 with open("/etc/mtab", 'r') as f:
1299 for line in f:
1300 (dev, mountpoint, fstype, opts, freq, passno) = line.split(' ')
1301 if mountpoint == '/':
1302 disk = diskFromPartition(dev)
1303 if not (disk in disks):
1304 disks.append(disk)
1305 SMlog("Dom0 disks: %s" % disks)
1306 return disks
1309def set_scheduler_sysfs_node(node, scheds):
1310 """
1311 Set the scheduler for a sysfs node (e.g. '/sys/block/sda')
1312 according to prioritized list schedulers
1313 Try to set the first item, then fall back to the next on failure
1314 """
1316 path = os.path.join(node, "queue", "scheduler")
1317 if not os.path.exists(path): 1317 ↛ 1321line 1317 didn't jump to line 1321, because the condition on line 1317 was never false
1318 SMlog("no path %s" % path)
1319 return
1321 stored_error = None
1322 for sched in scheds:
1323 try:
1324 with open(path, 'w') as file:
1325 file.write("%s\n" % sched)
1326 SMlog("Set scheduler to [%s] on [%s]" % (sched, node))
1327 return
1328 except (OSError, IOError) as err:
1329 stored_error = err
1331 SMlog("Error setting schedulers to [%s] on [%s], %s" % (scheds, node, str(stored_error)))
1334def set_scheduler(dev, schedulers=None):
1335 if schedulers is None: 1335 ↛ 1338line 1335 didn't jump to line 1338, because the condition on line 1335 was never false
1336 schedulers = ["none", "noop"]
1338 devices = []
1339 if not scsiutil.match_dm(dev): 1339 ↛ 1343line 1339 didn't jump to line 1343, because the condition on line 1339 was never false
1340 # Remove partition numbers
1341 devices.append(diskFromPartition(dev).replace('/', '!'))
1342 else:
1343 rawdev = diskFromPartition(dev)
1344 devices = [os.path.realpath(x)[5:] for x in scsiutil._genReverseSCSIidmap(rawdev.split('/')[-1])]
1346 for d in devices:
1347 set_scheduler_sysfs_node("/sys/block/%s" % d, schedulers)
1350# This function queries XAPI for the existing VDI records for this SR
1351def _getVDIs(srobj):
1352 VDIs = []
1353 try:
1354 sr_ref = getattr(srobj, 'sr_ref')
1355 except AttributeError:
1356 return VDIs
1358 refs = srobj.session.xenapi.SR.get_VDIs(sr_ref)
1359 for vdi in refs:
1360 ref = srobj.session.xenapi.VDI.get_record(vdi)
1361 ref['vdi_ref'] = vdi
1362 VDIs.append(ref)
1363 return VDIs
1366def get_sr_uuid_from_vdi_ref(session, vdi_ref: str) -> str:
1367 sr_ref = session.xenapi.VDI.get_SR(vdi_ref)
1368 return session.xenapi.SR.get_uuid(sr_ref)
1371def get_sr_uuid_from_vdi_uuid(session, vdi_uuid: str) -> str:
1372 return get_sr_uuid_from_vdi_ref(session, session.xenapi.VDI.get_by_uuid(vdi_uuid))
1375def _getVDI(srobj, vdi_uuid):
1376 vdi = srobj.session.xenapi.VDI.get_by_uuid(vdi_uuid)
1377 ref = srobj.session.xenapi.VDI.get_record(vdi)
1378 ref['vdi_ref'] = vdi
1379 return ref
1382def _convertDNS(name):
1383 addr = socket.getaddrinfo(name, None)[0][4][0]
1384 return addr
1387def _containsVDIinuse(srobj):
1388 VDIs = _getVDIs(srobj)
1389 for vdi in VDIs:
1390 if not vdi['managed']:
1391 continue
1392 sm_config = vdi['sm_config']
1393 if 'SRRef' in sm_config:
1394 try:
1395 PBDs = srobj.session.xenapi.SR.get_PBDs(sm_config['SRRef'])
1396 for pbd in PBDs:
1397 record = PBDs[pbd]
1398 if record["host"] == srobj.host_ref and \
1399 record["currently_attached"]:
1400 return True
1401 except:
1402 pass
1403 return False
1406def isVDICommand(cmd):
1407 if cmd is None or cmd in ["vdi_attach", "vdi_detach",
1408 "vdi_activate", "vdi_deactivate",
1409 "vdi_epoch_begin", "vdi_epoch_end"]:
1410 return True
1411 else:
1412 return False
1415#########################
1416# Daemon helper functions
1417def p_id_fork():
1418 try:
1419 p_id = os.fork()
1420 except OSError as e:
1421 print("Fork failed: %s (%d)" % (e.strerror, e.errno))
1422 sys.exit(-1)
1424 if (p_id == 0):
1425 os.setsid()
1426 try:
1427 p_id = os.fork()
1428 except OSError as e:
1429 print("Fork failed: %s (%d)" % (e.strerror, e.errno))
1430 sys.exit(-1)
1431 if (p_id == 0):
1432 os.chdir('/opt/xensource/sm')
1433 os.umask(0)
1434 else:
1435 os._exit(0)
1436 else:
1437 os._exit(0)
1440def daemon():
1441 p_id_fork()
1442 # Query the max file descriptor parameter for this process
1443 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1445 # Close any fds that are open
1446 for fd in range(0, maxfd):
1447 try:
1448 os.close(fd)
1449 except:
1450 pass
1452 # Redirect STDIN to STDOUT and STDERR
1453 os.open('/dev/null', os.O_RDWR)
1454 os.dup2(0, 1)
1455 os.dup2(0, 2)
1457################################################################################
1458#
1459# Fist points
1460#
1462# * The global variable 'fistpoint' define the list of all possible fistpoints;
1463#
1464# * To activate a fistpoint called 'name', you need to create the file '/tmp/fist_name'
1465# on the SR master;
1466#
1467# * At the moment, activating a fist point can lead to two possible behaviors:
1468# - if '/tmp/fist_LVHDRT_exit' exists, then the function called during the fistpoint is _exit;
1469# - otherwise, the function called is _pause.
1471def _pause(secs, name):
1472 SMlog("Executing fist point %s: sleeping %d seconds ..." % (name, secs))
1473 time.sleep(secs)
1474 SMlog("Executing fist point %s: done" % name)
1477def _exit(name):
1478 SMlog("Executing fist point %s: exiting the current process ..." % name)
1479 raise xs_errors.XenError('FistPoint', opterr='%s' % name)
1482class FistPoint:
1483 def __init__(self, points):
1484 #SMlog("Fist points loaded")
1485 self.points = points
1487 def is_legal(self, name):
1488 return (name in self.points)
1490 def is_active(self, name):
1491 return os.path.exists("/tmp/fist_%s" % name)
1493 def mark_sr(self, name, sruuid, started):
1494 with ApiSession("SM-sr-fist-point") as session:
1495 sr = session.xenapi.SR.get_by_uuid(sruuid)
1496 if started:
1497 session.xenapi.SR.add_to_other_config(sr, name, "active")
1498 else:
1499 session.xenapi.SR.remove_from_other_config(sr, name)
1501 def activate(self, name, sruuid):
1502 if name in self.points:
1503 if self.is_active(name):
1504 self.mark_sr(name, sruuid, True)
1505 if self.is_active("LVHDRT_exit"): 1505 ↛ 1506line 1505 didn't jump to line 1506, because the condition on line 1505 was never true
1506 self.mark_sr(name, sruuid, False)
1507 _exit(name)
1508 else:
1509 _pause(FIST_PAUSE_PERIOD, name)
1510 self.mark_sr(name, sruuid, False)
1511 else:
1512 SMlog("Unknown fist point: %s" % name)
1514 def activate_custom_fn(self, name, fn):
1515 if name in self.points: 1515 ↛ 1521line 1515 didn't jump to line 1521, because the condition on line 1515 was never false
1516 if self.is_active(name): 1516 ↛ 1517line 1516 didn't jump to line 1517, because the condition on line 1516 was never true
1517 SMlog("Executing fist point %s: starting ..." % name)
1518 fn()
1519 SMlog("Executing fist point %s: done" % name)
1520 else:
1521 SMlog("Unknown fist point: %s" % name)
1524def list_find(f, seq):
1525 for item in seq:
1526 if f(item):
1527 return item
1529GCPAUSE_FISTPOINT = "GCLoop_no_pause"
1531fistpoint = FistPoint(["LVHDRT_finding_a_suitable_pair",
1532 "LVHDRT_inflating_the_parent",
1533 "LVHDRT_resizing_while_vdis_are_paused",
1534 "LVHDRT_coalescing_VHD_data",
1535 "LVHDRT_coalescing_before_inflate_grandparent",
1536 "LVHDRT_relinking_grandchildren",
1537 "LVHDRT_before_create_relink_journal",
1538 "LVHDRT_xapiSM_serialization_tests",
1539 "LVHDRT_clone_vdi_after_create_journal",
1540 "LVHDRT_clone_vdi_after_shrink_parent",
1541 "LVHDRT_clone_vdi_after_first_snap",
1542 "LVHDRT_clone_vdi_after_second_snap",
1543 "LVHDRT_clone_vdi_after_parent_hidden",
1544 "LVHDRT_clone_vdi_after_parent_ro",
1545 "LVHDRT_clone_vdi_before_remove_journal",
1546 "LVHDRT_clone_vdi_after_lvcreate",
1547 "LVHDRT_clone_vdi_before_undo_clone",
1548 "LVHDRT_clone_vdi_after_undo_clone",
1549 "LVHDRT_inflate_after_create_journal",
1550 "LVHDRT_inflate_after_setSize",
1551 "LVHDRT_inflate_after_zeroOut",
1552 "LVHDRT_inflate_after_setSizePhys",
1553 "LVHDRT_inflate_after_setSizePhys",
1554 "LVHDRT_coaleaf_before_coalesce",
1555 "LVHDRT_coaleaf_after_coalesce",
1556 "LVHDRT_coaleaf_one_renamed",
1557 "LVHDRT_coaleaf_both_renamed",
1558 "LVHDRT_coaleaf_after_vdirec",
1559 "LVHDRT_coaleaf_before_delete",
1560 "LVHDRT_coaleaf_after_delete",
1561 "LVHDRT_coaleaf_before_remove_j",
1562 "LVHDRT_coaleaf_undo_after_rename",
1563 "LVHDRT_coaleaf_undo_after_rename2",
1564 "LVHDRT_coaleaf_undo_after_refcount",
1565 "LVHDRT_coaleaf_undo_after_deflate",
1566 "LVHDRT_coaleaf_undo_end",
1567 "LVHDRT_coaleaf_stop_after_recovery",
1568 "LVHDRT_coaleaf_finish_after_inflate",
1569 "LVHDRT_coaleaf_finish_end",
1570 "LVHDRT_coaleaf_delay_1",
1571 "LVHDRT_coaleaf_delay_2",
1572 "LVHDRT_coaleaf_delay_3",
1573 "testsm_clone_allow_raw",
1574 "xenrt_default_vdi_type_legacy",
1575 "blktap_activate_inject_failure",
1576 "blktap_activate_error_handling",
1577 GCPAUSE_FISTPOINT,
1578 "cleanup_coalesceVHD_inject_failure",
1579 "cleanup_tracker_no_progress",
1580 "FileSR_fail_hardlink",
1581 "FileSR_fail_snap1",
1582 "FileSR_fail_snap2",
1583 "LVM_journaler_exists",
1584 "LVM_journaler_none",
1585 "LVM_journaler_badname",
1586 "LVM_journaler_readfail",
1587 "LVM_journaler_writefail"])
1590def set_dirty(session, sr):
1591 try:
1592 session.xenapi.SR.add_to_other_config(sr, "dirty", "")
1593 SMlog("set_dirty %s succeeded" % (repr(sr)))
1594 except:
1595 SMlog("set_dirty %s failed (flag already set?)" % (repr(sr)))
1598def doesFileHaveOpenHandles(fileName):
1599 SMlog("Entering doesFileHaveOpenHandles with file: %s" % fileName)
1600 (retVal, processAndPidTuples) = \
1601 findRunningProcessOrOpenFile(fileName, False)
1603 if not retVal:
1604 SMlog("Failed to determine if file %s has open handles." % \
1605 fileName)
1606 # err on the side of caution
1607 return True
1608 else:
1609 if len(processAndPidTuples) > 0:
1610 return True
1611 else:
1612 return False
1615# extract SR uuid from the passed in devmapper entry and return
1616# /dev/mapper/VG_XenStorage--c3d82e92--cb25--c99b--b83a--482eebab4a93-MGT
1617def extractSRFromDevMapper(path):
1618 try:
1619 path = os.path.basename(path)
1620 path = path[len('VG_XenStorage-') + 1:]
1621 path = path.replace('--', '/')
1622 path = path[0:path.rfind('-')]
1623 return path.replace('/', '-')
1624 except:
1625 return ''
1628def pid_is_alive(pid):
1629 """
1630 Try to kill PID with signal 0.
1631 If we succeed, the PID is alive, so return True.
1632 If we get an EPERM error, the PID is alive but we are not allowed to
1633 signal it. Still return true.
1634 Any other error (e.g. ESRCH), return False
1635 """
1636 try:
1637 os.kill(pid, 0)
1638 return True
1639 except OSError as e:
1640 if e.errno == errno.EPERM:
1641 return True
1642 return False
1645def get_process_cmdline(pid: int) -> List[str]:
1646 try:
1647 with open(os.path.join('/proc', str(pid), 'cmdline'), 'rb') as f:
1648 line = f.read().split(b'\0')
1649 return [arg.decode() for arg in line]
1650 except OSError as e:
1651 if e.errno != errno.ENOENT:
1652 raise
1653 return []
1656# Looks at /proc and figures either
1657# If a process is still running (default), returns open file names
1658# If any running process has open handles to the given file (process = False)
1659# returns process names and pids
1660def findRunningProcessOrOpenFile(name, process=True):
1661 retVal = True
1662 links = []
1663 processandpids = []
1664 sockets = set()
1665 try:
1666 SMlog("Entering findRunningProcessOrOpenFile with params: %s" % \
1667 [name, process])
1669 # Look at all pids
1670 pids = [pid for pid in os.listdir('/proc') if pid.isdigit()]
1671 for pid in sorted(pids):
1672 try:
1673 try:
1674 f = None
1675 f = open(os.path.join('/proc', pid, 'cmdline'), 'r')
1676 prog = f.read()[:-1]
1677 if prog: 1677 ↛ 1686line 1677 didn't jump to line 1686, because the condition on line 1677 was never false
1678 # Just want the process name
1679 argv = prog.split('\x00')
1680 prog = argv[0]
1681 except IOError as e:
1682 if e.errno in (errno.ENOENT, errno.ESRCH):
1683 SMlog("ERROR %s reading %s, ignore" % (e.errno, pid))
1684 continue
1685 finally:
1686 if f is not None: 1686 ↛ 1671, 1686 ↛ 16892 missed branches: 1) line 1686 didn't jump to line 1671, because the continue on line 1684 wasn't executed, 2) line 1686 didn't jump to line 1689, because the condition on line 1686 was never false
1687 f.close() 1687 ↛ 1671line 1687 didn't jump to line 1671, because the continue on line 1684 wasn't executed
1689 try:
1690 fd_dir = os.path.join('/proc', pid, 'fd')
1691 files = os.listdir(fd_dir)
1692 except OSError as e:
1693 if e.errno in (errno.ENOENT, errno.ESRCH):
1694 SMlog("ERROR %s reading fds for %s, ignore" % (e.errno, pid))
1695 # Ignore pid that are no longer valid
1696 continue
1697 else:
1698 raise
1700 for file in files:
1701 try:
1702 link = os.readlink(os.path.join(fd_dir, file))
1703 except OSError:
1704 continue
1706 if process: 1706 ↛ 1711line 1706 didn't jump to line 1711, because the condition on line 1706 was never false
1707 if name == prog: 1707 ↛ 1700line 1707 didn't jump to line 1700, because the condition on line 1707 was never false
1708 links.append(link)
1709 else:
1710 # need to return process name and pid tuples
1711 if link == name:
1712 processandpids.append((prog, pid))
1714 # Get the connected sockets
1715 if name == prog:
1716 sockets.update(get_connected_sockets(pid))
1718 # We will only have a non-empty processandpids if some fd entries were found.
1719 # Before returning them, verify that all the PIDs in question are properly alive.
1720 # There is no specific guarantee of when a PID's /proc directory will disappear
1721 # when it exits, particularly relative to filedescriptor cleanup, so we want to
1722 # make sure we're not reporting a false positive.
1723 processandpids = [x for x in processandpids if pid_is_alive(int(x[1]))]
1724 for pp in processandpids: 1724 ↛ 1725line 1724 didn't jump to line 1725, because the loop on line 1724 never started
1725 SMlog(f"File {name} has an open handle with process {pp[0]} with pid {pp[1]}")
1727 except Exception as e:
1728 SMlog("Exception checking running process or open file handles. " \
1729 "Error: %s" % str(e))
1730 retVal = False
1732 if process: 1732 ↛ 1735line 1732 didn't jump to line 1735, because the condition on line 1732 was never false
1733 return retVal, links, sockets
1734 else:
1735 return retVal, processandpids
1738def get_connected_sockets(pid):
1739 sockets = set()
1740 try:
1741 # Lines in /proc/<pid>/net/unix are formatted as follows
1742 # (see Linux source net/unix/af_unix.c, unix_seq_show() )
1743 # - Pointer address to socket (hex)
1744 # - Refcount (HEX)
1745 # - 0
1746 # - State (HEX, 0 or __SO_ACCEPTCON)
1747 # - Type (HEX - but only 0001 of interest)
1748 # - Connection state (HEX - but only 03, SS_CONNECTED of interest)
1749 # - Inode number
1750 # - Path (optional)
1751 open_sock_matcher = re.compile(
1752 r'^[0-9a-f]+: [0-9A-Fa-f]+ [0-9A-Fa-f]+ [0-9A-Fa-f]+ 0001 03 \d+ (.*)$')
1753 with open(
1754 os.path.join('/proc', str(pid), 'net', 'unix'), 'r') as f:
1755 lines = f.readlines()
1756 for line in lines:
1757 match = open_sock_matcher.match(line)
1758 if match:
1759 sockets.add(match[1])
1760 except OSError as e:
1761 if e.errno in (errno.ENOENT, errno.ESRCH):
1762 # Ignore pid that are no longer valid
1763 SMlog("ERROR %s reading sockets for %s, ignore" %
1764 (e.errno, pid))
1765 else:
1766 raise
1767 return sockets
1770def retry(f, maxretry=20, period=3, exceptions=[Exception]):
1771 retries = 0
1772 while True:
1773 try:
1774 return f()
1775 except Exception as e:
1776 for exception in exceptions:
1777 if isinstance(e, exception):
1778 SMlog('Got exception: {}. Retry number: {}'.format(
1779 str(e), retries
1780 ))
1781 break
1782 else:
1783 SMlog('Got bad exception: {}. Raising...'.format(e))
1784 raise e
1786 retries += 1
1787 if retries >= maxretry:
1788 break
1790 time.sleep(period)
1792 return f()
1795def getCslDevPath(svid):
1796 basepath = "/dev/disk/by-csldev/"
1797 if svid.startswith("NETAPP_"):
1798 # special attention for NETAPP SVIDs
1799 svid_parts = svid.split("__")
1800 globstr = basepath + "NETAPP__LUN__" + "*" + svid_parts[2] + "*" + svid_parts[-1] + "*"
1801 else:
1802 globstr = basepath + svid + "*"
1804 return globstr
1807# Use device in /dev pointed to by cslg path which consists of svid
1808def get_scsiid_from_svid(md_svid):
1809 cslg_path = getCslDevPath(md_svid)
1810 abs_path = glob.glob(cslg_path)
1811 if abs_path:
1812 real_path = os.path.realpath(abs_path[0])
1813 return scsiutil.getSCSIid(real_path)
1814 else:
1815 return None
1818def get_isl_scsiids(session):
1819 # Get cslg type SRs
1820 SRs = session.xenapi.SR.get_all_records_where('field "type" = "cslg"')
1822 # Iterate through the SR to get the scsi ids
1823 scsi_id_ret = []
1824 for SR in SRs:
1825 sr_rec = SRs[SR]
1826 # Use the md_svid to get the scsi id
1827 scsi_id = get_scsiid_from_svid(sr_rec['sm_config']['md_svid'])
1828 if scsi_id:
1829 scsi_id_ret.append(scsi_id)
1831 # Get the vdis in the SR and do the same procedure
1832 vdi_recs = session.xenapi.VDI.get_all_records_where('field "SR" = "%s"' % SR)
1833 for vdi_rec in vdi_recs:
1834 vdi_rec = vdi_recs[vdi_rec]
1835 scsi_id = get_scsiid_from_svid(vdi_rec['sm_config']['SVID'])
1836 if scsi_id:
1837 scsi_id_ret.append(scsi_id)
1839 return scsi_id_ret
1842class extractXVA:
1843 # streams files as a set of file and checksum, caller should remove
1844 # the files, if not needed. The entire directory (Where the files
1845 # and checksum) will only be deleted as part of class cleanup.
1846 HDR_SIZE = 512
1847 BLOCK_SIZE = 512
1848 SIZE_LEN = 12 - 1 # To remove \0 from tail
1849 SIZE_OFFSET = 124
1850 ZERO_FILLED_REC = 2
1851 NULL_IDEN = '\x00'
1852 DIR_IDEN = '/'
1853 CHECKSUM_IDEN = '.checksum'
1854 OVA_FILE = 'ova.xml'
1856 # Init gunzips the file using a subprocess, and reads stdout later
1857 # as and when needed
1858 def __init__(self, filename):
1859 self.__extract_path = ''
1860 self.__filename = filename
1861 cmd = 'gunzip -cd %s' % filename
1862 try:
1863 self.spawn_p = subprocess.Popen(
1864 cmd, shell=True, \
1865 stdin=subprocess.PIPE, stdout=subprocess.PIPE, \
1866 stderr=subprocess.PIPE, close_fds=True)
1867 except Exception as e:
1868 SMlog("Error: %s. Uncompress failed for %s" % (str(e), filename))
1869 raise Exception(str(e))
1871 # Create dir to extract the files
1872 self.__extract_path = tempfile.mkdtemp()
1874 def __del__(self):
1875 shutil.rmtree(self.__extract_path)
1877 # Class supports Generator expression. 'for f_name, checksum in getTuple()'
1878 # returns filename, checksum content. Returns filename, '' in case
1879 # of checksum file missing. e.g. ova.xml
1880 def getTuple(self):
1881 zerod_record = 0
1882 ret_f_name = ''
1883 ret_base_f_name = ''
1885 try:
1886 # Read tar file as sets of file and checksum.
1887 while True:
1888 # Read the output of spawned process, or output of gunzip
1889 f_hdr = self.spawn_p.stdout.read(self.HDR_SIZE)
1891 # Break out in case of end of file
1892 if f_hdr == '':
1893 if zerod_record == extractXVA.ZERO_FILLED_REC:
1894 break
1895 else:
1896 SMlog('Error. Expects %d zero records', \
1897 extractXVA.ZERO_FILLED_REC)
1898 raise Exception('Unrecognized end of file')
1900 # Watch out for zero records, two zero records
1901 # denote end of file.
1902 if f_hdr == extractXVA.NULL_IDEN * extractXVA.HDR_SIZE:
1903 zerod_record += 1
1904 continue
1906 f_name = f_hdr[:f_hdr.index(extractXVA.NULL_IDEN)]
1907 # File header may be for a folder, if so ignore the header
1908 if not f_name.endswith(extractXVA.DIR_IDEN):
1909 f_size_octal = f_hdr[extractXVA.SIZE_OFFSET: \
1910 extractXVA.SIZE_OFFSET + extractXVA.SIZE_LEN]
1911 f_size = int(f_size_octal, 8)
1912 if f_name.endswith(extractXVA.CHECKSUM_IDEN):
1913 if f_name.rstrip(extractXVA.CHECKSUM_IDEN) == \
1914 ret_base_f_name:
1915 checksum = self.spawn_p.stdout.read(f_size)
1916 yield(ret_f_name, checksum)
1917 else:
1918 # Expects file followed by its checksum
1919 SMlog('Error. Sequence mismatch starting with %s', \
1920 ret_f_name)
1921 raise Exception( \
1922 'Files out of sequence starting with %s', \
1923 ret_f_name)
1924 else:
1925 # In case of ova.xml, read the contents into a file and
1926 # return the file name to the caller. For other files,
1927 # read the contents into a file, it will
1928 # be used when a .checksum file is encountered.
1929 ret_f_name = '%s/%s' % (self.__extract_path, f_name)
1930 ret_base_f_name = f_name
1932 # Check if the folder exists on the target location,
1933 # else create it.
1934 folder_path = ret_f_name[:ret_f_name.rfind('/')]
1935 if not os.path.exists(folder_path):
1936 os.mkdir(folder_path)
1938 # Store the file to the tmp folder, strip the tail \0
1939 f = open(ret_f_name, 'w')
1940 f.write(self.spawn_p.stdout.read(f_size))
1941 f.close()
1942 if f_name == extractXVA.OVA_FILE:
1943 yield(ret_f_name, '')
1945 # Skip zero'd portion of data block
1946 round_off = f_size % extractXVA.BLOCK_SIZE
1947 if round_off != 0:
1948 zeros = self.spawn_p.stdout.read(
1949 extractXVA.BLOCK_SIZE - round_off)
1950 except Exception as e:
1951 SMlog("Error: %s. File set extraction failed %s" % (str(e), \
1952 self.__filename))
1954 # Kill and Drain stdout of the gunzip process,
1955 # else gunzip might block on stdout
1956 os.kill(self.spawn_p.pid, signal.SIGTERM)
1957 self.spawn_p.communicate()
1958 raise Exception(str(e))
1960illegal_xml_chars = [(0x00, 0x08), (0x0B, 0x1F), (0x7F, 0x84), (0x86, 0x9F),
1961 (0xD800, 0xDFFF), (0xFDD0, 0xFDDF), (0xFFFE, 0xFFFF),
1962 (0x1FFFE, 0x1FFFF), (0x2FFFE, 0x2FFFF), (0x3FFFE, 0x3FFFF),
1963 (0x4FFFE, 0x4FFFF), (0x5FFFE, 0x5FFFF), (0x6FFFE, 0x6FFFF),
1964 (0x7FFFE, 0x7FFFF), (0x8FFFE, 0x8FFFF), (0x9FFFE, 0x9FFFF),
1965 (0xAFFFE, 0xAFFFF), (0xBFFFE, 0xBFFFF), (0xCFFFE, 0xCFFFF),
1966 (0xDFFFE, 0xDFFFF), (0xEFFFE, 0xEFFFF), (0xFFFFE, 0xFFFFF),
1967 (0x10FFFE, 0x10FFFF)]
1969illegal_ranges = ["%s-%s" % (chr(low), chr(high))
1970 for (low, high) in illegal_xml_chars
1971 if low < sys.maxunicode]
1973illegal_xml_re = re.compile(u'[%s]' % u''.join(illegal_ranges))
1976def isLegalXMLString(s):
1977 """Tells whether this is a valid XML string (i.e. it does not contain
1978 illegal XML characters specified in
1979 http://www.w3.org/TR/2004/REC-xml-20040204/#charsets).
1980 """
1982 if len(s) > 0:
1983 return re.search(illegal_xml_re, s) is None
1984 else:
1985 return True
1988def unictrunc(string, max_bytes):
1989 """
1990 Given a string, returns the largest number of elements for a prefix
1991 substring of it, such that the UTF-8 encoding of this substring takes no
1992 more than the given number of bytes.
1994 The string may be given as a unicode string or a UTF-8 encoded byte
1995 string, and the number returned will be in characters or bytes
1996 accordingly. Note that in the latter case, the substring will still be a
1997 valid UTF-8 encoded string (which is to say, it won't have been truncated
1998 part way through a multibyte sequence for a unicode character).
2000 string: the string to truncate
2001 max_bytes: the maximum number of bytes the truncated string can be
2002 """
2003 if isinstance(string, str):
2004 return_chars = True
2005 else:
2006 return_chars = False
2007 string = string.decode('UTF-8')
2009 cur_chars = 0
2010 cur_bytes = 0
2011 for char in string:
2012 charsize = len(char.encode('UTF-8'))
2013 if cur_bytes + charsize > max_bytes:
2014 break
2015 else:
2016 cur_chars += 1
2017 cur_bytes += charsize
2018 return cur_chars if return_chars else cur_bytes
2021def hideValuesInPropMap(propmap, propnames):
2022 """
2023 Worker function: input simple map of prop name/value pairs, and
2024 a list of specific propnames whose values we want to hide.
2025 Loop through the "hide" list, and if any are found, hide the
2026 value and return the altered map.
2027 If none found, return the original map
2028 """
2029 matches = []
2030 for propname in propnames:
2031 if propname in propmap: 2031 ↛ 2032line 2031 didn't jump to line 2032, because the condition on line 2031 was never true
2032 matches.append(propname)
2034 if matches: 2034 ↛ 2035line 2034 didn't jump to line 2035, because the condition on line 2034 was never true
2035 deepCopyRec = copy.deepcopy(propmap)
2036 for match in matches:
2037 deepCopyRec[match] = '******'
2038 return deepCopyRec
2040 return propmap
2041# define the list of propnames whose value we want to hide
2043PASSWD_PROP_KEYS = ['password', 'cifspassword', 'chappassword', 'incoming_chappassword']
2044DEFAULT_SEGMENT_LEN = 950
2047def hidePasswdInConfig(config):
2048 """
2049 Function to hide passwd values in a simple prop map,
2050 for example "device_config"
2051 """
2052 return hideValuesInPropMap(config, PASSWD_PROP_KEYS)
2055def hidePasswdInParams(params, configProp):
2056 """
2057 Function to hide password values in a specified property which
2058 is a simple map of prop name/values, and is itself an prop entry
2059 in a larger property map.
2060 For example, param maps containing "device_config", or
2061 "sm_config", etc
2062 """
2063 params[configProp] = hideValuesInPropMap(params[configProp], PASSWD_PROP_KEYS)
2064 return params
2067def hideMemberValuesInXmlParams(xmlParams, propnames=PASSWD_PROP_KEYS):
2068 """
2069 Function to hide password values in XML params, specifically
2070 for the XML format of incoming params to SR modules.
2071 Uses text parsing: loop through the list of specific propnames
2072 whose values we want to hide, and:
2073 - Assemble a full "prefix" containing each property name, e.g.,
2074 "<member><name>password</name><value>"
2075 - Test the XML if it contains that string, save the index.
2076 - If found, get the index of the ending tag
2077 - Truncate the return string starting with the password value.
2078 - Append the substitute "*******" value string.
2079 - Restore the rest of the original string starting with the end tag.
2080 """
2081 findStrPrefixHead = "<member><name>"
2082 findStrPrefixTail = "</name><value>"
2083 findStrSuffix = "</value>"
2084 strlen = len(xmlParams)
2086 for propname in propnames:
2087 findStrPrefix = findStrPrefixHead + propname + findStrPrefixTail
2088 idx = xmlParams.find(findStrPrefix)
2089 if idx != -1: # if found any of them
2090 idx += len(findStrPrefix)
2091 idx2 = xmlParams.find(findStrSuffix, idx)
2092 if idx2 != -1:
2093 retStr = xmlParams[0:idx]
2094 retStr += "******"
2095 retStr += xmlParams[idx2:strlen]
2096 return retStr
2097 else:
2098 return xmlParams
2099 return xmlParams
2102def splitXmlText(xmlData, segmentLen=DEFAULT_SEGMENT_LEN, showContd=False):
2103 """
2104 Split xml string data into substrings small enough for the
2105 syslog line length limit. Split at tag end markers ( ">" ).
2106 Usage:
2107 strList = []
2108 strList = splitXmlText( longXmlText, maxLineLen ) # maxLineLen is optional
2109 """
2110 remainingData = str(xmlData)
2112 # "Un-pretty-print"
2113 remainingData = remainingData.replace('\n', '')
2114 remainingData = remainingData.replace('\t', '')
2116 remainingChars = len(remainingData)
2117 returnData = ''
2119 thisLineNum = 0
2120 while remainingChars > segmentLen:
2121 thisLineNum = thisLineNum + 1
2122 index = segmentLen
2123 tmpStr = remainingData[:segmentLen]
2124 tmpIndex = tmpStr.rfind('>')
2125 if tmpIndex != -1:
2126 index = tmpIndex + 1
2128 tmpStr = tmpStr[:index]
2129 remainingData = remainingData[index:]
2130 remainingChars = len(remainingData)
2132 if showContd:
2133 if thisLineNum != 1:
2134 tmpStr = '(Cont\'d): ' + tmpStr
2135 tmpStr = tmpStr + ' (Cont\'d):'
2137 returnData += tmpStr + '\n'
2139 if showContd and thisLineNum > 0:
2140 remainingData = '(Cont\'d): ' + remainingData
2141 returnData += remainingData
2143 return returnData
2146def inject_failure():
2147 raise Exception('injected failure')
2150def open_atomic(path, mode=None):
2151 """Atomically creates a file if, and only if it does not already exist.
2152 Leaves the file open and returns the file object.
2154 path: the path to atomically open
2155 mode: "r" (read), "w" (write), or "rw" (read/write)
2156 returns: an open file object"""
2158 assert path
2160 flags = os.O_CREAT | os.O_EXCL
2161 modes = {'r': os.O_RDONLY, 'w': os.O_WRONLY, 'rw': os.O_RDWR}
2162 if mode:
2163 if mode not in modes:
2164 raise Exception('invalid access mode ' + mode)
2165 flags |= modes[mode]
2166 fd = os.open(path, flags)
2167 try:
2168 if mode:
2169 return os.fdopen(fd, mode)
2170 else:
2171 return os.fdopen(fd)
2172 except:
2173 os.close(fd)
2174 raise
2177def isInvalidVDI(exception):
2178 return exception.details[0] == "HANDLE_INVALID" or \
2179 exception.details[0] == "UUID_INVALID"
2182def get_pool_restrictions(session):
2183 """Returns pool restrictions as a map, @session must be already
2184 established."""
2185 return list(session.xenapi.pool.get_all_records().values())[0]['restrictions']
2188def read_caching_is_restricted(session):
2189 """Tells whether read caching is restricted."""
2190 if session is None: 2190 ↛ 2191line 2190 didn't jump to line 2191, because the condition on line 2190 was never true
2191 return True
2192 restrictions = get_pool_restrictions(session)
2193 if 'restrict_read_caching' in restrictions and \ 2193 ↛ 2195line 2193 didn't jump to line 2195, because the condition on line 2193 was never true
2194 restrictions['restrict_read_caching'] == "true":
2195 return True
2196 return False
2199def sessions_less_than_targets(other_config, device_config):
2200 if 'multihomelist' in device_config and 'iscsi_sessions' in other_config:
2201 sessions = int(other_config['iscsi_sessions'])
2202 targets = len(device_config['multihomelist'].split(','))
2203 SMlog("Targets %d and iscsi_sessions %d" % (targets, sessions))
2204 return (sessions < targets)
2205 else:
2206 return False
2209def enable_and_start_service(name, start):
2210 attempt = 0
2211 while True:
2212 attempt += 1
2213 fn = 'enable' if start else 'disable'
2214 args = ('systemctl', fn, '--now', name)
2215 (ret, out, err) = doexec(args)
2216 if ret == 0:
2217 return
2218 elif attempt >= 3:
2219 raise Exception(
2220 'Failed to {} {}: {} {}'.format(fn, name, out, err)
2221 )
2222 time.sleep(1)
2225def stop_service(name):
2226 args = ('systemctl', 'stop', name)
2227 (ret, out, err) = doexec(args)
2228 if ret == 0:
2229 return
2230 raise Exception('Failed to stop {}: {} {}'.format(name, out, err))
2233def restart_service(name):
2234 attempt = 0
2235 while True:
2236 attempt += 1
2237 SMlog('Restarting service {} {}...'.format(name, attempt))
2238 args = ('systemctl', 'restart', name)
2239 (ret, out, err) = doexec(args)
2240 if ret == 0:
2241 return
2242 elif attempt >= 3:
2243 SMlog('Restart service FAILED {} {}'.format(name, attempt))
2244 raise Exception(
2245 'Failed to restart {}: {} {}'.format(name, out, err)
2246 )
2247 time.sleep(1)
2250def check_pid_exists(pid):
2251 try:
2252 os.kill(pid, 0)
2253 except OSError:
2254 return False
2255 else:
2256 return True
2259def get_openers_pid(path: str) -> Optional[List[int]]:
2260 cmd = ["lsof", "-t", path]
2262 try:
2263 list = []
2264 ret = pread2(cmd)
2265 for line in ret.splitlines():
2266 list.append(int(line))
2267 return list
2268 except CommandException as e:
2269 if e.code == 1: # `lsof` return 1 if there is no openers
2270 return None
2271 else:
2272 raise e
2275def make_profile(name, function):
2276 """
2277 Helper to execute cProfile using unique log file.
2278 """
2280 import cProfile
2281 import itertools
2282 import os.path
2283 import time
2285 assert name
2286 assert function
2288 FOLDER = '/tmp/sm-perfs/'
2289 makedirs(FOLDER)
2291 filename = time.strftime('{}_%Y%m%d_%H%M%S.prof'.format(name))
2293 def gen_path(path):
2294 yield path
2295 root, ext = os.path.splitext(path)
2296 for i in itertools.count(start=1, step=1):
2297 yield root + '.{}.'.format(i) + ext
2299 for profile_path in gen_path(FOLDER + filename):
2300 try:
2301 file = open_atomic(profile_path, 'w')
2302 file.close()
2303 break
2304 except OSError as e:
2305 if e.errno == errno.EEXIST:
2306 pass
2307 else:
2308 raise
2310 try:
2311 SMlog('* Start profiling of {} ({}) *'.format(name, filename))
2312 cProfile.runctx('function()', None, locals(), profile_path)
2313 finally:
2314 SMlog('* End profiling of {} ({}) *'.format(name, filename))
2317def strtobool(str: str) -> bool:
2318 # Note: `distutils` package is deprecated and slated for removal in Python 3.12.
2319 # There is not alternative for strtobool.
2320 # See: https://peps.python.org/pep-0632/#migration-advice
2321 # So this is a custom implementation with differences:
2322 # - A boolean is returned instead of integer
2323 # - Empty string and None are supported (False is returned in this case)
2324 if not str: 2324 ↛ 2326line 2324 didn't jump to line 2326, because the condition on line 2324 was never false
2325 return False
2326 str = str.lower()
2327 if str in ('y', 'yes', 't', 'true', 'on', '1'):
2328 return True
2329 if str in ('n', 'no', 'f', 'false', 'off', '0'):
2330 return False
2331 raise ValueError("invalid truth value '{}'".format(str))
2334def find_executable(name):
2335 return shutil.which(name)
2338def conditional_decorator(decorator, condition):
2339 def wrapper(func):
2340 if not condition: 2340 ↛ 2342line 2340 didn't jump to line 2342, because the condition on line 2340 was never false
2341 return func
2342 return decorator(func)
2343 return wrapper
2346def get_srs_uuid_from_type(session, type):
2347 srs = session.xenapi.SR.get_all_records_where(f"field \"type\" = \"{type}\"")
2348 return {sr["uuid"]: sr for sr in srs.values()}
2351def get_linstor_srs_uuid(session):
2352 import LinstorSR # pylint: disable=C0415
2353 return get_srs_uuid_from_type(session, LinstorSR.LinstorSR.DRIVER_TYPE)
2356def find_pbd_ref_from_dconf_value(session, srs, key, value, value_modifier = None):
2357 for sr in srs.values():
2358 for pbd_ref in sr["PBDs"]:
2359 device_config = session.xenapi.PBD.get_device_config(pbd_ref)
2360 cur_value = device_config.get(key)
2361 if value_modifier:
2362 cur_value = value_modifier(cur_value)
2363 if cur_value and cur_value == value:
2364 return pbd_ref
2365 return None