Hide keyboard shortcuts

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# 

18 

19from sm_typing import Any, List, Optional, override 

20 

21import contextlib 

22import os 

23import re 

24import sys 

25import subprocess 

26import shutil 

27import tempfile 

28import signal 

29import time 

30import types 

31import datetime 

32import errno 

33import functools 

34import socket 

35import threading 

36import xml.dom.minidom 

37import scsiutil 

38import stat 

39import xs_errors 

40import XenAPI # pylint: disable=import-error 

41import xmlrpc.client 

42import base64 

43import syslog 

44import resource 

45import traceback 

46import glob 

47import copy 

48import tempfile 

49 

50from functools import reduce 

51from sm_typing import List, Optional 

52 

53NO_LOGGING_STAMPFILE = '/etc/xensource/no_sm_log' 

54 

55IORETRY_MAX = 20 # retries 

56IORETRY_PERIOD = 1.0 # seconds 

57 

58LOGGING = not (os.path.exists(NO_LOGGING_STAMPFILE)) 

59_SM_SYSLOG_FACILITY = syslog.LOG_LOCAL2 

60LOG_EMERG = syslog.LOG_EMERG 

61LOG_ALERT = syslog.LOG_ALERT 

62LOG_CRIT = syslog.LOG_CRIT 

63LOG_ERR = syslog.LOG_ERR 

64LOG_WARNING = syslog.LOG_WARNING 

65LOG_NOTICE = syslog.LOG_NOTICE 

66LOG_INFO = syslog.LOG_INFO 

67LOG_DEBUG = syslog.LOG_DEBUG 

68 

69ISCSI_REFDIR = '/var/run/sr-ref' 

70 

71CMD_DD = "/bin/dd" 

72CMD_KICKPIPE = '/opt/xensource/libexec/kickpipe' 

73 

74FIST_PAUSE_PERIOD = 30 # seconds 

75 

76class _TimeoutContextManager(contextlib.AbstractContextManager): 

77 def __init__(self, delay: int) -> None: 

78 self.delay = delay 

79 self.old_handler: Any = None 

80 

81 @override 

82 def __enter__(self) -> "_TimeoutContextManager": 

83 def handle_timeout(_signum: int, _frame: Optional[types.FrameType]) -> None: 

84 raise TimeoutError(f"Timed out after {self.delay} seconds") 

85 self.old_handler = signal.signal(signal.SIGALRM, handle_timeout) 

86 signal.alarm(self.delay) 

87 return self 

88 

89 @override 

90 def __exit__( 

91 self, 

92 exc_type: Optional[Any], 

93 exc_val: Optional[BaseException], 

94 exc_tb: Optional[types.TracebackType] 

95 ): 

96 signal.alarm(0) 

97 if self.old_handler is not None: 

98 signal.signal(signal.SIGALRM, self.old_handler) 

99 self.old_handler = None 

100 return False 

101 

102 def __call__(self, func): 

103 def wrapper(*args, **kwargs): 

104 with self: 

105 return func(*args, **kwargs) 

106 return wrapper 

107 

108def timeout(*args, **kwargs): 

109 if len(args) >= 2 and callable(args[1]): 

110 delay, func = args[0], args[1] 

111 remaining_args = args[2:] 

112 with _TimeoutContextManager(delay): 

113 return func(*remaining_args, **kwargs) 

114 

115 if len(args) == 1 and callable(args[0]): 

116 raise TypeError("timeout() missing 1 required argument: 'delay'") 

117 

118 if args: 

119 delay = args[0] 

120 elif "delay" in kwargs: 

121 delay = kwargs["delay"] 

122 else: 

123 raise TypeError("timeout() missing 1 required argument: 'delay'") 

124 return _TimeoutContextManager(delay) 

125 

126class SMException(Exception): 

127 """Base class for all SM exceptions for easier catching & wrapping in 

128 XenError""" 

129 

130 

131class CommandException(SMException): 

132 def error_message(self, code): 

133 if code > 0: 

134 return os.strerror(code) 

135 elif code < 0: 

136 return "Signalled %s" % (abs(code)) 

137 return "Success" 

138 

139 def __init__(self, code, cmd="", reason='exec failed'): 

140 self.code = code 

141 self.cmd = cmd 

142 self.reason = reason 

143 Exception.__init__(self, self.error_message(code)) 

144 

145 

146class SRBusyException(SMException): 

147 """The SR could not be locked""" 

148 pass 

149 

150 

151def logException(tag): 

152 info = sys.exc_info() 

153 if info[0] == SystemExit: 153 ↛ 155line 153 didn't jump to line 155, because the condition on line 153 was never true

154 # this should not be happening when catching "Exception", but it is 

155 sys.exit(0) 

156 tb = reduce(lambda a, b: "%s%s" % (a, b), traceback.format_tb(info[2])) 

157 str = "***** %s: EXCEPTION %s, %s\n%s" % (tag, info[0], info[1], tb) 

158 SMlog(str) 

159 

160 

161def roundup(divisor, value): 

162 """Retruns the rounded up value so it is divisible by divisor.""" 

163 

164 if value == 0: 164 ↛ 165line 164 didn't jump to line 165, because the condition on line 164 was never true

165 value = 1 

166 if value % divisor != 0: 

167 return ((int(value) // divisor) + 1) * divisor 

168 return value 

169 

170 

171def to_plain_string(obj): 

172 if obj is None: 

173 return None 

174 if isinstance(obj, dict) and len(obj) == 0: 

175 SMlog(f"util.to_plain_string() corrected empty dict to empty str") 

176 return "" 

177 return str(obj) 

178 

179 

180def shellquote(arg): 

181 return '"%s"' % arg.replace('"', '\\"') 

182 

183 

184def make_WWN(name): 

185 hex_prefix = name.find("0x") 

186 if (hex_prefix >= 0): 186 ↛ 189line 186 didn't jump to line 189, because the condition on line 186 was never false

187 name = name[name.find("0x") + 2:len(name)] 

188 # inject dashes for each nibble 

189 if (len(name) == 16): # sanity check 189 ↛ 193line 189 didn't jump to line 193, because the condition on line 189 was never false

190 name = name[0:2] + "-" + name[2:4] + "-" + name[4:6] + "-" + \ 

191 name[6:8] + "-" + name[8:10] + "-" + name[10:12] + "-" + \ 

192 name[12:14] + "-" + name[14:16] 

193 return name 

194 

195 

196def synchronized(func): 

197 lock = threading.RLock() 

198 

199 @functools.wraps(func) 

200 def wrapper(*args, **kwargs): 

201 with lock: 

202 return func(*args, **kwargs) 

203 

204 return wrapper 

205 

206 

207@synchronized 

208def _writeToSyslog(ident, facility, priority, message): 

209 syslog.openlog(ident, 0, facility) 

210 syslog.syslog(priority, message) 

211 syslog.closelog() 

212 

213 

214def _logToSyslog(ident, facility, priority, message): 

215 pid = os.getpid() 

216 thread_name = threading.current_thread().name 

217 _writeToSyslog(ident, facility, priority, f"[{pid}][{thread_name}] {message}") 

218 

219 

220def SMlog(message, ident="SM", priority=LOG_INFO): 

221 if LOGGING: 221 ↛ exitline 221 didn't return from function 'SMlog', because the condition on line 221 was never false

222 for message_line in str(message).split('\n'): 

223 _logToSyslog(ident, _SM_SYSLOG_FACILITY, priority, message_line) 

224 

225 

226class LoggerCounter: 

227 def __init__(self, max_repeats): 

228 self.previous_message = None 

229 self.max_repeats = max_repeats 

230 self.repeat_counter = 0 

231 

232 def log(self, message): 

233 self.repeat_counter += 1 

234 if self.previous_message != message or self.repeat_counter == self.max_repeats: 

235 SMlog(message) 

236 self.previous_message = message 

237 self.repeat_counter = 0 

238 

239def _getDateString(): 

240 d = datetime.datetime.now() 

241 t = d.timetuple() 

242 return "%s-%s-%s:%s:%s:%s" % \ 

243 (t[0], t[1], t[2], t[3], t[4], t[5]) 

244 

245 

246def doexec(args, inputtext=None, new_env=None, text=True): 

247 """Execute a subprocess, then return its return code, stdout and stderr""" 

248 env = None 

249 if new_env: 

250 env = dict(os.environ) 

251 env.update(new_env) 

252 proc = subprocess.Popen(args, stdin=subprocess.PIPE, 

253 stdout=subprocess.PIPE, 

254 stderr=subprocess.PIPE, 

255 close_fds=True, env=env, 

256 universal_newlines=text) 

257 

258 if not text and inputtext is not None: 258 ↛ 259line 258 didn't jump to line 259, because the condition on line 258 was never true

259 inputtext = inputtext.encode() 

260 

261 (stdout, stderr) = proc.communicate(inputtext) 

262 

263 rc = proc.returncode 

264 return rc, stdout, stderr 

265 

266 

267def is_string(value): 

268 return isinstance(value, str) 

269 

270 

271# These are partially tested functions that replicate the behaviour of 

272# the original pread,pread2 and pread3 functions. Potentially these can 

273# replace the original ones at some later date. 

274# 

275# cmdlist is a list of either single strings or pairs of strings. For 

276# each pair, the first component is passed to exec while the second is 

277# written to the logs. 

278def pread(cmdlist, close_stdin=False, scramble=None, expect_rc=0, 

279 quiet=False, new_env=None, text=True): 

280 cmdlist_for_exec = [] 

281 cmdlist_for_log = [] 

282 for item in cmdlist: 

283 if is_string(item): 283 ↛ 293line 283 didn't jump to line 293, because the condition on line 283 was never false

284 cmdlist_for_exec.append(item) 

285 if scramble: 285 ↛ 286line 285 didn't jump to line 286, because the condition on line 285 was never true

286 if item.find(scramble) != -1: 

287 cmdlist_for_log.append("<filtered out>") 

288 else: 

289 cmdlist_for_log.append(item) 

290 else: 

291 cmdlist_for_log.append(item) 

292 else: 

293 cmdlist_for_exec.append(item[0]) 

294 cmdlist_for_log.append(item[1]) 

295 

296 if not quiet: 296 ↛ 298line 296 didn't jump to line 298, because the condition on line 296 was never false

297 SMlog(cmdlist_for_log) 

298 (rc, stdout, stderr) = doexec(cmdlist_for_exec, new_env=new_env, text=text) 

299 if rc != expect_rc: 

300 SMlog("FAILED in util.pread: (rc %d) stdout: '%s', stderr: '%s'" % \ 

301 (rc, stdout, stderr)) 

302 if quiet: 302 ↛ 303line 302 didn't jump to line 303, because the condition on line 302 was never true

303 SMlog("Command was: %s" % cmdlist_for_log) 

304 if '' == stderr: 304 ↛ 305line 304 didn't jump to line 305, because the condition on line 304 was never true

305 stderr = stdout 

306 raise CommandException(rc, str(cmdlist), stderr.strip()) 

307 if not quiet: 307 ↛ 309line 307 didn't jump to line 309, because the condition on line 307 was never false

308 SMlog(" pread SUCCESS") 

309 return stdout 

310 

311 

312# POSIX guaranteed atomic within the same file system. 

313# Supply directory to ensure tempfile is created 

314# in the same directory. 

315def atomicFileWrite(targetFile, directory, text): 

316 

317 file = None 

318 try: 

319 # Create file only current pid can write/read to 

320 # our responsibility to clean it up. 

321 _, tempPath = tempfile.mkstemp(dir=directory) 

322 file = open(tempPath, 'w') 

323 file.write(text) 

324 

325 # Ensure flushed to disk. 

326 file.flush() 

327 os.fsync(file.fileno()) 

328 file.close() 

329 

330 os.rename(tempPath, targetFile) 

331 except OSError: 

332 SMlog("FAILED to atomic write to %s" % (targetFile)) 

333 

334 finally: 

335 if (file is not None) and (not file.closed): 

336 file.close() 

337 

338 if os.path.isfile(tempPath): 

339 os.remove(tempPath) 

340 

341 

342#Read STDOUT from cmdlist and discard STDERR output 

343def pread2(cmdlist, quiet=False, text=True): 

344 return pread(cmdlist, quiet=quiet, text=text) 

345 

346 

347#Read STDOUT from cmdlist, feeding 'text' to STDIN 

348def pread3(cmdlist, text): 

349 SMlog(cmdlist) 

350 (rc, stdout, stderr) = doexec(cmdlist, text) 

351 if rc: 

352 SMlog("FAILED in util.pread3: (errno %d) stdout: '%s', stderr: '%s'" % \ 

353 (rc, stdout, stderr)) 

354 if '' == stderr: 

355 stderr = stdout 

356 raise CommandException(rc, str(cmdlist), stderr.strip()) 

357 SMlog(" pread3 SUCCESS") 

358 return stdout 

359 

360 

361def listdir(path, quiet=False): 

362 cmd = ["ls", path, "-1", "--color=never"] 

363 try: 

364 text = pread2(cmd, quiet=quiet)[:-1] 

365 if len(text) == 0: 

366 return [] 

367 return text.split('\n') 

368 except CommandException as inst: 

369 if inst.code == errno.ENOENT: 

370 raise CommandException(errno.EIO, inst.cmd, inst.reason) 

371 else: 

372 raise CommandException(inst.code, inst.cmd, inst.reason) 

373 

374 

375def gen_uuid(): 

376 cmd = ["uuidgen", "-r"] 

377 return pread(cmd)[:-1] 

378 

379 

380def match_uuid(s): 

381 regex = re.compile("^[0-9a-f]{8}-(([0-9a-f]{4})-){3}[0-9a-f]{12}") 

382 return regex.search(s, 0) 

383 

384 

385def findall_uuid(s): 

386 regex = re.compile("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}") 

387 return regex.findall(s, 0) 

388 

389 

390def exactmatch_uuid(s): 

391 regex = re.compile("^[0-9a-f]{8}-(([0-9a-f]{4})-){3}[0-9a-f]{12}$") 

392 return regex.search(s, 0) 

393 

394 

395def start_log_entry(srpath, path, args): 

396 logstring = str(datetime.datetime.now()) 

397 logstring += " log: " 

398 logstring += srpath 

399 logstring += " " + path 

400 for element in args: 

401 logstring += " " + element 

402 try: 

403 file = open(srpath + "/filelog.txt", "a") 

404 file.write(logstring) 

405 file.write("\n") 

406 file.close() 

407 except: 

408 pass 

409 

410 # failed to write log ... 

411 

412def end_log_entry(srpath, path, args): 

413 # for teminating, use "error" or "done" 

414 logstring = str(datetime.datetime.now()) 

415 logstring += " end: " 

416 logstring += srpath 

417 logstring += " " + path 

418 for element in args: 

419 logstring += " " + element 

420 try: 

421 file = open(srpath + "/filelog.txt", "a") 

422 file.write(logstring) 

423 file.write("\n") 

424 file.close() 

425 except: 

426 pass 

427 

428 # failed to write log ... 

429 # for now print 

430 # print "%s" % logstring 

431 

432def ioretry(f, errlist=[errno.EIO], maxretry=IORETRY_MAX, period=IORETRY_PERIOD, **ignored): 

433 retries = 0 

434 while True: 

435 try: 

436 return f() 

437 except OSError as ose: 

438 err = int(ose.errno) 

439 if not err in errlist: 

440 raise CommandException(err, str(f), "OSError") 

441 except CommandException as ce: 

442 if not int(ce.code) in errlist: 

443 raise 

444 

445 retries += 1 

446 if retries >= maxretry: 

447 break 

448 

449 time.sleep(period) 

450 

451 raise CommandException(errno.ETIMEDOUT, str(f), "Timeout") 

452 

453 

454def ioretry_stat(path, maxretry=IORETRY_MAX): 

455 # this ioretry is similar to the previous method, but 

456 # stat does not raise an error -- so check its return 

457 retries = 0 

458 while retries < maxretry: 

459 stat = os.statvfs(path) 

460 if stat.f_blocks != -1: 

461 return stat 

462 time.sleep(1) 

463 retries += 1 

464 raise CommandException(errno.EIO, "os.statvfs") 

465 

466 

467def sr_get_capability(sr_uuid, session=None): 

468 result = [] 

469 local_session = None 

470 if session is None: 470 ↛ 474line 470 didn't jump to line 474, because the condition on line 470 was never false

471 local_session = get_localAPI_session() 

472 session = local_session 

473 

474 try: 

475 sr_ref = session.xenapi.SR.get_by_uuid(sr_uuid) 

476 sm_type = session.xenapi.SR.get_record(sr_ref)['type'] 

477 sm_rec = session.xenapi.SM.get_all_records_where( 

478 "field \"type\" = \"%s\"" % sm_type) 

479 

480 # SM expects at least one entry of any SR type 

481 if len(sm_rec) > 0: 

482 result = list(sm_rec.values())[0]['capabilities'] 

483 

484 return result 

485 finally: 

486 if local_session: 486 ↛ exitline 486 didn't return from function 'sr_get_capability', because the return on line 484 wasn't executed

487 local_session.xenapi.session.logout() 

488 

489def sr_get_driver_info(driver_info): 

490 results = {} 

491 # first add in the vanilla stuff 

492 for key in ['name', 'description', 'vendor', 'copyright', \ 

493 'driver_version', 'required_api_version']: 

494 results[key] = driver_info[key] 

495 # add the capabilities (xmlrpc array) 

496 # enforcing activate/deactivate for blktap2 

497 caps = driver_info['capabilities'] 

498 if "ATOMIC_PAUSE" in caps: 498 ↛ 499line 498 didn't jump to line 499, because the condition on line 498 was never true

499 for cap in ("VDI_ACTIVATE", "VDI_DEACTIVATE"): 

500 if not cap in caps: 

501 caps.append(cap) 

502 elif "VDI_ACTIVATE" in caps or "VDI_DEACTIVATE" in caps: 502 ↛ 503line 502 didn't jump to line 503, because the condition on line 502 was never true

503 SMlog("Warning: vdi_[de]activate present for %s" % driver_info["name"]) 

504 

505 results['capabilities'] = caps 

506 # add in the configuration options 

507 options = [] 

508 for option in driver_info['configuration']: 

509 options.append({'key': option[0], 'description': option[1]}) 

510 results['configuration'] = options 

511 return xmlrpc.client.dumps((results, ), "", True) 

512 

513 

514def return_nil(): 

515 return xmlrpc.client.dumps((None, ), "", True, allow_none=True) 

516 

517 

518def SRtoXML(SRlist): 

519 dom = xml.dom.minidom.Document() 

520 driver = dom.createElement("SRlist") 

521 dom.appendChild(driver) 

522 

523 for key in SRlist.keys(): 

524 dict = SRlist[key] 

525 entry = dom.createElement("SR") 

526 driver.appendChild(entry) 

527 

528 e = dom.createElement("UUID") 

529 entry.appendChild(e) 

530 textnode = dom.createTextNode(key) 

531 e.appendChild(textnode) 

532 

533 if 'size' in dict: 

534 e = dom.createElement("Size") 

535 entry.appendChild(e) 

536 textnode = dom.createTextNode(str(dict['size'])) 

537 e.appendChild(textnode) 

538 

539 if 'storagepool' in dict: 

540 e = dom.createElement("StoragePool") 

541 entry.appendChild(e) 

542 textnode = dom.createTextNode(str(dict['storagepool'])) 

543 e.appendChild(textnode) 

544 

545 if 'aggregate' in dict: 

546 e = dom.createElement("Aggregate") 

547 entry.appendChild(e) 

548 textnode = dom.createTextNode(str(dict['aggregate'])) 

549 e.appendChild(textnode) 

550 

551 return dom.toprettyxml() 

552 

553 

554def pathexists(path): 

555 try: 

556 os.lstat(path) 

557 return True 

558 except OSError as inst: 

559 if inst.errno == errno.EIO: 559 ↛ 560line 559 didn't jump to line 560, because the condition on line 559 was never true

560 time.sleep(1) 

561 try: 

562 listdir(os.path.realpath(os.path.dirname(path))) 

563 os.lstat(path) 

564 return True 

565 except: 

566 pass 

567 raise CommandException(errno.EIO, "os.lstat(%s)" % path, "failed") 

568 return False 

569 

570 

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 

577 

578 

579def create_secret(session, secret): 

580 ref = session.xenapi.secret.create({'value': secret}) 

581 return session.xenapi.secret.get_uuid(ref) 

582 

583 

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) 

590 

591 

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 

601 

602 

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 

609 

610 

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 

617 

618 

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 "" 

628 

629 

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 

638 

639 

640def get_single_entry(path): 

641 f = open(path, 'r') 

642 line = f.readline() 

643 f.close() 

644 return line.rstrip() 

645 

646 

647def get_fs_size(path): 

648 st = ioretry_stat(path) 

649 return st.f_blocks * st.f_frsize 

650 

651 

652def get_fs_utilisation(path): 

653 st = ioretry_stat(path) 

654 return (st.f_blocks - st.f_bfree) * \ 

655 st.f_frsize 

656 

657 

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 

674 

675 

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 

693 

694 

695def zeroOut(path, fromByte, bytes): 

696 """write 'bytes' zeros to 'path' starting from fromByte (inclusive)""" 

697 blockSize = 4096 

698 

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 

712 

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 

723 

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 

731 

732 return True 

733 

734 

735def wipefs(blockdev): 

736 "Wipe filesystem signatures from `blockdev`" 

737 pread2(["/usr/sbin/wipefs", "-a", blockdev]) 

738 

739 

740def match_rootdev(s): 

741 regex = re.compile("^PRIMARY_DISK") 

742 return regex.search(s, 0) 

743 

744 

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 

758 

759 

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 '' 

768 

769 if not len(rootdevID): 

770 SMlog("util.getrootdevID: Unable to identify scsi device [%s] via scsiID" \ 

771 % rootdev) 

772 

773 return rootdevID 

774 

775 

776def get_localAPI_session(): 

777 # First acquire a valid session 

778 session = XenAPI.xapi_local() 

779 try: 

780 session.xenapi.login_with_password('root', '', '', 'SM') 

781 except: 

782 raise xs_errors.XenError('APISession') 

783 return session 

784 

785 

786def get_this_host(): 

787 uuid = None 

788 f = open("/etc/xensource-inventory", 'r') 

789 for line in f.readlines(): 

790 if line.startswith("INSTALLATION_UUID"): 

791 uuid = line.split("'")[1] 

792 f.close() 

793 return uuid 

794 

795 

796def get_master_ref(session): 

797 pools = session.xenapi.pool.get_all() 

798 return session.xenapi.pool.get_master(pools[0]) 

799 

800 

801def is_master(session): 

802 return get_this_host_ref(session) == get_master_ref(session) 

803 

804 

805def get_localhost_ref(session): 

806 filename = '/etc/xensource-inventory' 

807 try: 

808 f = open(filename, 'r') 

809 except: 

810 raise xs_errors.XenError('EIO', \ 

811 opterr="Unable to open inventory file [%s]" % filename) 

812 domid = '' 

813 for line in filter(match_domain_id, f.readlines()): 

814 domid = line.split("'")[1] 

815 if not domid: 

816 raise xs_errors.XenError('APILocalhost') 

817 

818 vms = session.xenapi.VM.get_all_records_where('field "uuid" = "%s"' % domid) 

819 for vm in vms: 

820 record = vms[vm] 

821 if record["uuid"] == domid: 

822 hostid = record["resident_on"] 

823 return hostid 

824 raise xs_errors.XenError('APILocalhost') 

825 

826 

827def match_domain_id(s): 

828 regex = re.compile("^CONTROL_DOMAIN_UUID") 

829 return regex.search(s, 0) 

830 

831 

832def get_hosts_attached_on(session, vdi_uuids): 

833 host_refs = {} 

834 for vdi_uuid in vdi_uuids: 

835 try: 

836 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid) 

837 except XenAPI.Failure: 

838 SMlog("VDI %s not in db, ignoring" % vdi_uuid) 

839 continue 

840 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref) 

841 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 

842 host_refs[key[len('host_'):]] = True 

843 return host_refs.keys() 

844 

845def get_hosts_attached_on_with_vdi_uuid(session, vdi_uuids): 

846 """ 

847 Return a dict of {vdi_uuid: host OpaqueRef} 

848 """ 

849 host_refs = {} 

850 for vdi_uuid in vdi_uuids: 

851 try: 

852 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid) 

853 except XenAPI.Failure: 

854 SMlog("VDI %s not in db, ignoring" % vdi_uuid) 

855 continue 

856 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref) 

857 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 

858 host_refs[vdi_uuid] = key[len('host_'):] 

859 return host_refs 

860 

861def get_this_host_address(session): 

862 host_uuid = get_this_host() 

863 host_ref = session.xenapi.host.get_by_uuid(host_uuid) 

864 return session.xenapi.host.get_record(host_ref)['address'] 

865 

866def get_host_addresses(session): 

867 addresses = [] 

868 hosts = session.xenapi.host.get_all_records() 

869 for record in hosts.values(): 

870 addresses.append(record['address']) 

871 return addresses 

872 

873def get_this_host_ref(session): 

874 host_uuid = get_this_host() 

875 host_ref = session.xenapi.host.get_by_uuid(host_uuid) 

876 return host_ref 

877 

878 

879def get_slaves_attached_on(session, vdi_uuids): 

880 "assume this host is the SR master" 

881 host_refs = get_hosts_attached_on(session, vdi_uuids) 

882 master_ref = get_this_host_ref(session) 

883 return [x for x in host_refs if x != master_ref] 

884 

885def get_enabled_hosts(session): 

886 """ 

887 Returns a list of host refs that are enabled in the pool. 

888 """ 

889 return list(session.xenapi.host.get_all_records_where('field "enabled" = "true"').keys()) 

890 

891def get_online_hosts(session): 

892 online_hosts = [] 

893 hosts = session.xenapi.host.get_all_records() 

894 for host_ref, host_rec in hosts.items(): 

895 metricsRef = host_rec["metrics"] 

896 metrics = session.xenapi.host_metrics.get_record(metricsRef) 

897 if metrics["live"]: 

898 online_hosts.append(host_ref) 

899 return online_hosts 

900 

901 

902def get_all_slaves(session): 

903 "assume this host is the SR master" 

904 host_refs = get_online_hosts(session) 

905 master_ref = get_this_host_ref(session) 

906 return [x for x in host_refs if x != master_ref] 

907 

908 

909def is_attached_rw(sm_config): 

910 for key, val in sm_config.items(): 

911 if key.startswith("host_") and val == "RW": 

912 return True 

913 return False 

914 

915 

916def attached_as(sm_config): 

917 for key, val in sm_config.items(): 

918 if key.startswith("host_") and (val == "RW" or val == "RO"): 918 ↛ 919line 918 didn't jump to line 919, because the condition on line 918 was never true

919 return val 

920 

921 

922def find_my_pbd_record(session, host_ref, sr_ref): 

923 try: 

924 pbds = session.xenapi.PBD.get_all_records() 

925 for pbd_ref in pbds.keys(): 

926 if pbds[pbd_ref]['host'] == host_ref and pbds[pbd_ref]['SR'] == sr_ref: 

927 return [pbd_ref, pbds[pbd_ref]] 

928 return None 

929 except Exception as e: 

930 SMlog("Caught exception while looking up PBD for host %s SR %s: %s" % (str(host_ref), str(sr_ref), str(e))) 

931 return None 

932 

933 

934def find_my_pbd(session, host_ref, sr_ref): 

935 ret = find_my_pbd_record(session, host_ref, sr_ref) 

936 if ret is not None: 

937 return ret[0] 

938 else: 

939 return None 

940 

941 

942def test_hostPBD_devs(session, sr_uuid, devs): 

943 host = get_localhost_ref(session) 

944 sr = session.xenapi.SR.get_by_uuid(sr_uuid) 

945 try: 

946 pbds = session.xenapi.PBD.get_all_records() 

947 except: 

948 raise xs_errors.XenError('APIPBDQuery') 

949 for dev in devs.split(','): 

950 for pbd in pbds: 

951 record = pbds[pbd] 

952 # it's ok if it's *our* PBD 

953 if record["SR"] == sr: 

954 break 

955 if record["host"] == host: 

956 devconfig = record["device_config"] 

957 if 'device' in devconfig: 

958 for device in devconfig['device'].split(','): 

959 if os.path.realpath(device) == os.path.realpath(dev): 

960 return True 

961 return False 

962 

963 

964def test_hostPBD_lun(session, targetIQN, LUNid): 

965 host = get_localhost_ref(session) 

966 try: 

967 pbds = session.xenapi.PBD.get_all_records() 

968 except: 

969 raise xs_errors.XenError('APIPBDQuery') 

970 for pbd in pbds: 

971 record = pbds[pbd] 

972 if record["host"] == host: 

973 devconfig = record["device_config"] 

974 if 'targetIQN' in devconfig and 'LUNid' in devconfig: 

975 if devconfig['targetIQN'] == targetIQN and \ 

976 devconfig['LUNid'] == LUNid: 

977 return True 

978 return False 

979 

980 

981def test_SCSIid(session, sr_uuid, SCSIid): 

982 if sr_uuid is not None: 

983 sr = session.xenapi.SR.get_by_uuid(sr_uuid) 

984 try: 

985 pbds = session.xenapi.PBD.get_all_records() 

986 except: 

987 raise xs_errors.XenError('APIPBDQuery') 

988 for pbd in pbds: 

989 record = pbds[pbd] 

990 # it's ok if it's *our* PBD 

991 # During FC SR creation, devscan.py passes sr_uuid as None 

992 if sr_uuid is not None: 

993 if record["SR"] == sr: 

994 break 

995 devconfig = record["device_config"] 

996 sm_config = session.xenapi.SR.get_sm_config(record["SR"]) 

997 if 'SCSIid' in devconfig and devconfig['SCSIid'] == SCSIid: 

998 return True 

999 elif 'SCSIid' in sm_config and sm_config['SCSIid'] == SCSIid: 

1000 return True 

1001 elif 'scsi-' + SCSIid in sm_config: 

1002 return True 

1003 return False 

1004 

1005 

1006class TimeoutException(SMException): 

1007 pass 

1008 

1009 

1010def _incr_iscsiSR_refcount(targetIQN, uuid): 

1011 if not os.path.exists(ISCSI_REFDIR): 

1012 os.mkdir(ISCSI_REFDIR) 

1013 filename = os.path.join(ISCSI_REFDIR, targetIQN) 

1014 try: 

1015 f = open(filename, 'a+') 

1016 except: 

1017 raise xs_errors.XenError('LVMRefCount', \ 

1018 opterr='file %s' % filename) 

1019 

1020 f.seek(0) 

1021 found = False 

1022 refcount = 0 

1023 for line in filter(match_uuid, f.readlines()): 

1024 refcount += 1 

1025 if line.find(uuid) != -1: 

1026 found = True 

1027 if not found: 

1028 f.write("%s\n" % uuid) 

1029 refcount += 1 

1030 f.close() 

1031 return refcount 

1032 

1033 

1034def _decr_iscsiSR_refcount(targetIQN, uuid): 

1035 filename = os.path.join(ISCSI_REFDIR, targetIQN) 

1036 if not os.path.exists(filename): 

1037 return 0 

1038 try: 

1039 f = open(filename, 'a+') 

1040 except: 

1041 raise xs_errors.XenError('LVMRefCount', \ 

1042 opterr='file %s' % filename) 

1043 

1044 f.seek(0) 

1045 output = [] 

1046 refcount = 0 

1047 for line in filter(match_uuid, f.readlines()): 

1048 if line.find(uuid) == -1: 

1049 output.append(line.rstrip()) 

1050 refcount += 1 

1051 if not refcount: 

1052 os.unlink(filename) 

1053 return refcount 

1054 

1055 # Re-open file and truncate 

1056 f.close() 

1057 f = open(filename, 'w') 

1058 for i in range(0, refcount): 

1059 f.write("%s\n" % output[i]) 

1060 f.close() 

1061 return refcount 

1062 

1063 

1064# The agent enforces 1 PBD per SR per host, so we 

1065# check for active SR entries not attached to this host 

1066def test_activePoolPBDs(session, host, uuid): 

1067 try: 

1068 pbds = session.xenapi.PBD.get_all_records() 

1069 except: 

1070 raise xs_errors.XenError('APIPBDQuery') 

1071 for pbd in pbds: 

1072 record = pbds[pbd] 

1073 if record["host"] != host and record["SR"] == uuid \ 

1074 and record["currently_attached"]: 

1075 return True 

1076 return False 

1077 

1078 

1079def remove_mpathcount_field(session, host_ref, sr_ref, SCSIid): 

1080 try: 

1081 pbdref = find_my_pbd(session, host_ref, sr_ref) 

1082 if pbdref is not None: 

1083 key = "mpath-" + SCSIid 

1084 session.xenapi.PBD.remove_from_other_config(pbdref, key) 

1085 except: 

1086 pass 

1087 

1088 

1089def kickpipe_mpathcount(): 

1090 """ 

1091 Issue a kick to the mpathcount service. This will ensure that mpathcount runs 

1092 shortly to update the multipath config records, if it was not already activated 

1093 by a UDEV event. 

1094 """ 

1095 cmd = [CMD_KICKPIPE, "mpathcount"] 

1096 (rc, stdout, stderr) = doexec(cmd) 

1097 return (rc == 0) 

1098 

1099 

1100def _testHost(hostname, port, errstring): 

1101 SMlog("_testHost: Testing host/port: %s,%d" % (hostname, port)) 

1102 try: 

1103 sockinfo = socket.getaddrinfo(hostname, int(port))[0] 

1104 except: 

1105 logException('Exception occured getting IP for %s' % hostname) 

1106 raise xs_errors.XenError('DNSError') 

1107 

1108 timeout = 5 

1109 

1110 sock = socket.socket(sockinfo[0], socket.SOCK_STREAM) 

1111 # Only allow the connect to block for up to timeout seconds 

1112 sock.settimeout(timeout) 

1113 try: 

1114 sock.connect(sockinfo[4]) 

1115 # Fix for MS storage server bug 

1116 sock.send(b'\n') 

1117 sock.close() 

1118 except socket.error as reason: 

1119 SMlog("_testHost: Connect failed after %d seconds (%s) - %s" \ 

1120 % (timeout, hostname, reason)) 

1121 raise xs_errors.XenError(errstring) 

1122 

1123 

1124def match_scsiID(s, id): 

1125 regex = re.compile(id) 

1126 return regex.search(s, 0) 

1127 

1128 

1129def _isSCSIid(s): 

1130 regex = re.compile("^scsi-") 

1131 return regex.search(s, 0) 

1132 

1133 

1134def is_usb_device(device): 

1135 cmd = ["udevadm", "info", "-q", "path", "-n", device] 

1136 result = pread2(cmd).split('/') 

1137 return len(result) >= 5 and result[4].startswith('usb') 

1138 

1139 

1140def test_scsiserial(session, device): 

1141 device = os.path.realpath(device) 

1142 if not scsiutil._isSCSIdev(device): 

1143 SMlog("util.test_scsiserial: Not a serial device: %s" % device) 

1144 return False 

1145 serial = "" 

1146 try: 

1147 serial += scsiutil.getserial(device) 

1148 except: 

1149 # Error allowed, SCSIid is the important one 

1150 pass 

1151 

1152 try: 

1153 scsiID = scsiutil.getSCSIid(device) 

1154 except: 

1155 SMlog("util.test_scsiserial: Unable to verify serial or SCSIid of device: %s" \ 

1156 % device) 

1157 return False 

1158 if not len(scsiID): 

1159 SMlog("util.test_scsiserial: Unable to identify scsi device [%s] via scsiID" \ 

1160 % device) 

1161 return False 

1162 

1163 # USB devices can have identical SCSI IDs - prefer matching with serial number 

1164 try: 

1165 usb_device_with_serial = serial and is_usb_device(device) 

1166 except: 

1167 usb_device_with_serial = False 

1168 SMlog("Unable to check if device is USB:") 

1169 SMlog(traceback.format_exc()) 

1170 

1171 try: 

1172 SRs = session.xenapi.SR.get_all_records() 

1173 except: 

1174 raise xs_errors.XenError('APIFailure') 

1175 for SR in SRs: 

1176 record = SRs[SR] 

1177 conf = record["sm_config"] 

1178 if 'devserial' in conf: 

1179 for dev in conf['devserial'].split(','): 

1180 if not usb_device_with_serial and _isSCSIid(dev): 

1181 if match_scsiID(dev, scsiID): 

1182 return True 

1183 elif len(serial) and dev == serial: 

1184 return True 

1185 return False 

1186 

1187 

1188def default(self, field, thunk): 

1189 try: 

1190 return getattr(self, field) 

1191 except: 

1192 return thunk() 

1193 

1194 

1195def list_VDI_records_in_sr(sr): 

1196 """Helper function which returns a list of all VDI records for this SR 

1197 stored in the XenAPI server, useful for implementing SR.scan""" 

1198 sr_ref = sr.session.xenapi.SR.get_by_uuid(sr.uuid) 

1199 vdis = sr.session.xenapi.VDI.get_all_records_where("field \"SR\" = \"%s\"" % sr_ref) 

1200 return vdis 

1201 

1202 

1203# Given a partition (e.g. sda1), get a disk name: 

1204def diskFromPartition(partition): 

1205 # check whether this is a device mapper device (e.g. /dev/dm-0) 

1206 m = re.match('(/dev/)?(dm-[0-9]+)(p[0-9]+)?$', partition) 

1207 if m is not None: 1207 ↛ 1208line 1207 didn't jump to line 1208, because the condition on line 1207 was never true

1208 return m.group(2) 

1209 

1210 numlen = 0 # number of digit characters 

1211 m = re.match(r"\D+(\d+)", partition) 

1212 if m is not None: 1212 ↛ 1213line 1212 didn't jump to line 1213, because the condition on line 1212 was never true

1213 numlen = len(m.group(1)) 

1214 

1215 # is it a cciss? 

1216 if True in [partition.startswith(x) for x in ['cciss', 'ida', 'rd']]: 1216 ↛ 1217line 1216 didn't jump to line 1217, because the condition on line 1216 was never true

1217 numlen += 1 # need to get rid of trailing 'p' 

1218 

1219 # is it a mapper path? 

1220 if partition.startswith("mapper"): 1220 ↛ 1221line 1220 didn't jump to line 1221, because the condition on line 1220 was never true

1221 if re.search("p[0-9]*$", partition): 

1222 numlen = len(re.match(r"\d+", partition[::-1]).group(0)) + 1 

1223 SMlog("Found mapper part, len %d" % numlen) 

1224 else: 

1225 numlen = 0 

1226 

1227 # is it /dev/disk/by-id/XYZ-part<k>? 

1228 if partition.startswith("disk/by-id"): 1228 ↛ 1229line 1228 didn't jump to line 1229, because the condition on line 1228 was never true

1229 return partition[:partition.rfind("-part")] 

1230 

1231 return partition[:len(partition) - numlen] 

1232 

1233 

1234def dom0_disks(): 

1235 """Disks carrying dom0, e.g. ['/dev/sda']""" 

1236 disks = [] 

1237 with open("/etc/mtab", 'r') as f: 

1238 for line in f: 

1239 (dev, mountpoint, fstype, opts, freq, passno) = line.split(' ') 

1240 if mountpoint == '/': 

1241 disk = diskFromPartition(dev) 

1242 if not (disk in disks): 

1243 disks.append(disk) 

1244 SMlog("Dom0 disks: %s" % disks) 

1245 return disks 

1246 

1247 

1248def set_scheduler_sysfs_node(node, scheds): 

1249 """ 

1250 Set the scheduler for a sysfs node (e.g. '/sys/block/sda') 

1251 according to prioritized list schedulers 

1252 Try to set the first item, then fall back to the next on failure 

1253 """ 

1254 

1255 path = os.path.join(node, "queue", "scheduler") 

1256 if not os.path.exists(path): 1256 ↛ 1260line 1256 didn't jump to line 1260, because the condition on line 1256 was never false

1257 SMlog("no path %s" % path) 

1258 return 

1259 

1260 stored_error = None 

1261 for sched in scheds: 

1262 try: 

1263 with open(path, 'w') as file: 

1264 file.write("%s\n" % sched) 

1265 SMlog("Set scheduler to [%s] on [%s]" % (sched, node)) 

1266 return 

1267 except (OSError, IOError) as err: 

1268 stored_error = err 

1269 

1270 SMlog("Error setting schedulers to [%s] on [%s], %s" % (scheds, node, str(stored_error))) 

1271 

1272 

1273def set_scheduler(dev, schedulers=None): 

1274 if schedulers is None: 1274 ↛ 1277line 1274 didn't jump to line 1277, because the condition on line 1274 was never false

1275 schedulers = ["none", "noop"] 

1276 

1277 devices = [] 

1278 if not scsiutil.match_dm(dev): 1278 ↛ 1282line 1278 didn't jump to line 1282, because the condition on line 1278 was never false

1279 # Remove partition numbers 

1280 devices.append(diskFromPartition(dev).replace('/', '!')) 

1281 else: 

1282 rawdev = diskFromPartition(dev) 

1283 devices = [os.path.realpath(x)[5:] for x in scsiutil._genReverseSCSIidmap(rawdev.split('/')[-1])] 

1284 

1285 for d in devices: 

1286 set_scheduler_sysfs_node("/sys/block/%s" % d, schedulers) 

1287 

1288 

1289# This function queries XAPI for the existing VDI records for this SR 

1290def _getVDIs(srobj): 

1291 VDIs = [] 

1292 try: 

1293 sr_ref = getattr(srobj, 'sr_ref') 

1294 except AttributeError: 

1295 return VDIs 

1296 

1297 refs = srobj.session.xenapi.SR.get_VDIs(sr_ref) 

1298 for vdi in refs: 

1299 ref = srobj.session.xenapi.VDI.get_record(vdi) 

1300 ref['vdi_ref'] = vdi 

1301 VDIs.append(ref) 

1302 return VDIs 

1303 

1304 

1305def get_sr_uuid_from_vdi_ref(session, vdi_ref: str) -> str: 

1306 sr_ref = session.xenapi.VDI.get_SR(vdi_ref) 

1307 return session.xenapi.SR.get_uuid(sr_ref) 

1308 

1309 

1310def get_sr_uuid_from_vdi_uuid(session, vdi_uuid: str) -> str: 

1311 return get_sr_uuid_from_vdi_ref(session, session.xenapi.VDI.get_by_uuid(vdi_uuid)) 

1312 

1313 

1314def _getVDI(srobj, vdi_uuid): 

1315 vdi = srobj.session.xenapi.VDI.get_by_uuid(vdi_uuid) 

1316 ref = srobj.session.xenapi.VDI.get_record(vdi) 

1317 ref['vdi_ref'] = vdi 

1318 return ref 

1319 

1320 

1321def _convertDNS(name): 

1322 addr = socket.getaddrinfo(name, None)[0][4][0] 

1323 return addr 

1324 

1325 

1326def _containsVDIinuse(srobj): 

1327 VDIs = _getVDIs(srobj) 

1328 for vdi in VDIs: 

1329 if not vdi['managed']: 

1330 continue 

1331 sm_config = vdi['sm_config'] 

1332 if 'SRRef' in sm_config: 

1333 try: 

1334 PBDs = srobj.session.xenapi.SR.get_PBDs(sm_config['SRRef']) 

1335 for pbd in PBDs: 

1336 record = PBDs[pbd] 

1337 if record["host"] == srobj.host_ref and \ 

1338 record["currently_attached"]: 

1339 return True 

1340 except: 

1341 pass 

1342 return False 

1343 

1344 

1345def isVDICommand(cmd): 

1346 if cmd is None or cmd in ["vdi_attach", "vdi_detach", 

1347 "vdi_activate", "vdi_deactivate", 

1348 "vdi_epoch_begin", "vdi_epoch_end"]: 

1349 return True 

1350 else: 

1351 return False 

1352 

1353 

1354######################### 

1355# Daemon helper functions 

1356def p_id_fork(): 

1357 try: 

1358 p_id = os.fork() 

1359 except OSError as e: 

1360 print("Fork failed: %s (%d)" % (e.strerror, e.errno)) 

1361 sys.exit(-1) 

1362 

1363 if (p_id == 0): 

1364 os.setsid() 

1365 try: 

1366 p_id = os.fork() 

1367 except OSError as e: 

1368 print("Fork failed: %s (%d)" % (e.strerror, e.errno)) 

1369 sys.exit(-1) 

1370 if (p_id == 0): 

1371 os.chdir('/opt/xensource/sm') 

1372 os.umask(0) 

1373 else: 

1374 os._exit(0) 

1375 else: 

1376 os._exit(0) 

1377 

1378 

1379def daemon(): 

1380 p_id_fork() 

1381 # Query the max file descriptor parameter for this process 

1382 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1] 

1383 

1384 # Close any fds that are open 

1385 for fd in range(0, maxfd): 

1386 try: 

1387 os.close(fd) 

1388 except: 

1389 pass 

1390 

1391 # Redirect STDIN to STDOUT and STDERR 

1392 os.open('/dev/null', os.O_RDWR) 

1393 os.dup2(0, 1) 

1394 os.dup2(0, 2) 

1395 

1396################################################################################ 

1397# 

1398# Fist points 

1399# 

1400 

1401# * The global variable 'fistpoint' define the list of all possible fistpoints; 

1402# 

1403# * To activate a fistpoint called 'name', you need to create the file '/tmp/fist_name' 

1404# on the SR master; 

1405# 

1406# * At the moment, activating a fist point can lead to two possible behaviors: 

1407# - if '/tmp/fist_LVHDRT_exit' exists, then the function called during the fistpoint is _exit; 

1408# - otherwise, the function called is _pause. 

1409 

1410def _pause(secs, name): 

1411 SMlog("Executing fist point %s: sleeping %d seconds ..." % (name, secs)) 

1412 time.sleep(secs) 

1413 SMlog("Executing fist point %s: done" % name) 

1414 

1415 

1416def _exit(name): 

1417 SMlog("Executing fist point %s: exiting the current process ..." % name) 

1418 raise xs_errors.XenError('FistPoint', opterr='%s' % name) 

1419 

1420 

1421class FistPoint: 

1422 def __init__(self, points): 

1423 #SMlog("Fist points loaded") 

1424 self.points = points 

1425 

1426 def is_legal(self, name): 

1427 return (name in self.points) 

1428 

1429 def is_active(self, name): 

1430 return os.path.exists("/tmp/fist_%s" % name) 

1431 

1432 def mark_sr(self, name, sruuid, started): 

1433 session = get_localAPI_session() 

1434 try: 

1435 sr = session.xenapi.SR.get_by_uuid(sruuid) 

1436 

1437 if started: 

1438 session.xenapi.SR.add_to_other_config(sr, name, "active") 

1439 else: 

1440 session.xenapi.SR.remove_from_other_config(sr, name) 

1441 finally: 

1442 session.xenapi.session.logout() 

1443 

1444 def activate(self, name, sruuid): 

1445 if name in self.points: 

1446 if self.is_active(name): 

1447 self.mark_sr(name, sruuid, True) 

1448 if self.is_active("LVHDRT_exit"): 1448 ↛ 1449line 1448 didn't jump to line 1449, because the condition on line 1448 was never true

1449 self.mark_sr(name, sruuid, False) 

1450 _exit(name) 

1451 else: 

1452 _pause(FIST_PAUSE_PERIOD, name) 

1453 self.mark_sr(name, sruuid, False) 

1454 else: 

1455 SMlog("Unknown fist point: %s" % name) 

1456 

1457 def activate_custom_fn(self, name, fn): 

1458 if name in self.points: 1458 ↛ 1464line 1458 didn't jump to line 1464, because the condition on line 1458 was never false

1459 if self.is_active(name): 1459 ↛ 1460line 1459 didn't jump to line 1460, because the condition on line 1459 was never true

1460 SMlog("Executing fist point %s: starting ..." % name) 

1461 fn() 

1462 SMlog("Executing fist point %s: done" % name) 

1463 else: 

1464 SMlog("Unknown fist point: %s" % name) 

1465 

1466 

1467def list_find(f, seq): 

1468 for item in seq: 

1469 if f(item): 

1470 return item 

1471 

1472GCPAUSE_FISTPOINT = "GCLoop_no_pause" 

1473 

1474fistpoint = FistPoint(["LVHDRT_finding_a_suitable_pair", 

1475 "LVHDRT_inflating_the_parent", 

1476 "LVHDRT_resizing_while_vdis_are_paused", 

1477 "LVHDRT_coalescing_VHD_data", 

1478 "LVHDRT_coalescing_before_inflate_grandparent", 

1479 "LVHDRT_relinking_grandchildren", 

1480 "LVHDRT_before_create_relink_journal", 

1481 "LVHDRT_xapiSM_serialization_tests", 

1482 "LVHDRT_clone_vdi_after_create_journal", 

1483 "LVHDRT_clone_vdi_after_shrink_parent", 

1484 "LVHDRT_clone_vdi_after_first_snap", 

1485 "LVHDRT_clone_vdi_after_second_snap", 

1486 "LVHDRT_clone_vdi_after_parent_hidden", 

1487 "LVHDRT_clone_vdi_after_parent_ro", 

1488 "LVHDRT_clone_vdi_before_remove_journal", 

1489 "LVHDRT_clone_vdi_after_lvcreate", 

1490 "LVHDRT_clone_vdi_before_undo_clone", 

1491 "LVHDRT_clone_vdi_after_undo_clone", 

1492 "LVHDRT_inflate_after_create_journal", 

1493 "LVHDRT_inflate_after_setSize", 

1494 "LVHDRT_inflate_after_zeroOut", 

1495 "LVHDRT_inflate_after_setSizePhys", 

1496 "LVHDRT_inflate_after_setSizePhys", 

1497 "LVHDRT_coaleaf_before_coalesce", 

1498 "LVHDRT_coaleaf_after_coalesce", 

1499 "LVHDRT_coaleaf_one_renamed", 

1500 "LVHDRT_coaleaf_both_renamed", 

1501 "LVHDRT_coaleaf_after_vdirec", 

1502 "LVHDRT_coaleaf_before_delete", 

1503 "LVHDRT_coaleaf_after_delete", 

1504 "LVHDRT_coaleaf_before_remove_j", 

1505 "LVHDRT_coaleaf_undo_after_rename", 

1506 "LVHDRT_coaleaf_undo_after_rename2", 

1507 "LVHDRT_coaleaf_undo_after_refcount", 

1508 "LVHDRT_coaleaf_undo_after_deflate", 

1509 "LVHDRT_coaleaf_undo_end", 

1510 "LVHDRT_coaleaf_stop_after_recovery", 

1511 "LVHDRT_coaleaf_finish_after_inflate", 

1512 "LVHDRT_coaleaf_finish_end", 

1513 "LVHDRT_coaleaf_delay_1", 

1514 "LVHDRT_coaleaf_delay_2", 

1515 "LVHDRT_coaleaf_delay_3", 

1516 "testsm_clone_allow_raw", 

1517 "xenrt_default_vdi_type_legacy", 

1518 "blktap_activate_inject_failure", 

1519 "blktap_activate_error_handling", 

1520 GCPAUSE_FISTPOINT, 

1521 "cleanup_coalesceVHD_inject_failure", 

1522 "cleanup_tracker_no_progress", 

1523 "FileSR_fail_hardlink", 

1524 "FileSR_fail_snap1", 

1525 "FileSR_fail_snap2", 

1526 "LVM_journaler_exists", 

1527 "LVM_journaler_none", 

1528 "LVM_journaler_badname", 

1529 "LVM_journaler_readfail", 

1530 "LVM_journaler_writefail"]) 

1531 

1532 

1533def set_dirty(session, sr): 

1534 try: 

1535 session.xenapi.SR.add_to_other_config(sr, "dirty", "") 

1536 SMlog("set_dirty %s succeeded" % (repr(sr))) 

1537 except: 

1538 SMlog("set_dirty %s failed (flag already set?)" % (repr(sr))) 

1539 

1540 

1541def doesFileHaveOpenHandles(fileName): 

1542 SMlog("Entering doesFileHaveOpenHandles with file: %s" % fileName) 

1543 (retVal, processAndPidTuples) = \ 

1544 findRunningProcessOrOpenFile(fileName, False) 

1545 

1546 if not retVal: 

1547 SMlog("Failed to determine if file %s has open handles." % \ 

1548 fileName) 

1549 # err on the side of caution 

1550 return True 

1551 else: 

1552 if len(processAndPidTuples) > 0: 

1553 return True 

1554 else: 

1555 return False 

1556 

1557 

1558# extract SR uuid from the passed in devmapper entry and return 

1559# /dev/mapper/VG_XenStorage--c3d82e92--cb25--c99b--b83a--482eebab4a93-MGT 

1560def extractSRFromDevMapper(path): 

1561 try: 

1562 path = os.path.basename(path) 

1563 path = path[len('VG_XenStorage-') + 1:] 

1564 path = path.replace('--', '/') 

1565 path = path[0:path.rfind('-')] 

1566 return path.replace('/', '-') 

1567 except: 

1568 return '' 

1569 

1570 

1571def pid_is_alive(pid): 

1572 """ 

1573 Try to kill PID with signal 0. 

1574 If we succeed, the PID is alive, so return True. 

1575 If we get an EPERM error, the PID is alive but we are not allowed to 

1576 signal it. Still return true. 

1577 Any other error (e.g. ESRCH), return False 

1578 """ 

1579 try: 

1580 os.kill(pid, 0) 

1581 return True 

1582 except OSError as e: 

1583 if e.errno == errno.EPERM: 

1584 return True 

1585 return False 

1586 

1587 

1588def get_process_cmdline(pid: int) -> List[str]: 

1589 try: 

1590 with open(os.path.join('/proc', str(pid), 'cmdline'), 'rb') as f: 

1591 line = f.read().split(b'\0') 

1592 return [arg.decode() for arg in line] 

1593 except OSError as e: 

1594 if e.errno != errno.ENOENT: 

1595 raise 

1596 return [] 

1597 

1598 

1599# Looks at /proc and figures either 

1600# If a process is still running (default), returns open file names 

1601# If any running process has open handles to the given file (process = False) 

1602# returns process names and pids 

1603def findRunningProcessOrOpenFile(name, process=True): 

1604 retVal = True 

1605 links = [] 

1606 processandpids = [] 

1607 sockets = set() 

1608 try: 

1609 SMlog("Entering findRunningProcessOrOpenFile with params: %s" % \ 

1610 [name, process]) 

1611 

1612 # Look at all pids 

1613 pids = [pid for pid in os.listdir('/proc') if pid.isdigit()] 

1614 for pid in sorted(pids): 

1615 try: 

1616 try: 

1617 f = None 

1618 f = open(os.path.join('/proc', pid, 'cmdline'), 'r') 

1619 prog = f.read()[:-1] 

1620 if prog: 1620 ↛ 1629line 1620 didn't jump to line 1629, because the condition on line 1620 was never false

1621 # Just want the process name 

1622 argv = prog.split('\x00') 

1623 prog = argv[0] 

1624 except IOError as e: 

1625 if e.errno in (errno.ENOENT, errno.ESRCH): 

1626 SMlog("ERROR %s reading %s, ignore" % (e.errno, pid)) 

1627 continue 

1628 finally: 

1629 if f is not None: 1629 ↛ 1614,   1629 ↛ 16322 missed branches: 1) line 1629 didn't jump to line 1614, because the continue on line 1627 wasn't executed, 2) line 1629 didn't jump to line 1632, because the condition on line 1629 was never false

1630 f.close() 1630 ↛ 1614line 1630 didn't jump to line 1614, because the continue on line 1627 wasn't executed

1631 

1632 try: 

1633 fd_dir = os.path.join('/proc', pid, 'fd') 

1634 files = os.listdir(fd_dir) 

1635 except OSError as e: 

1636 if e.errno in (errno.ENOENT, errno.ESRCH): 

1637 SMlog("ERROR %s reading fds for %s, ignore" % (e.errno, pid)) 

1638 # Ignore pid that are no longer valid 

1639 continue 

1640 else: 

1641 raise 

1642 

1643 for file in files: 

1644 try: 

1645 link = os.readlink(os.path.join(fd_dir, file)) 

1646 except OSError: 

1647 continue 

1648 

1649 if process: 1649 ↛ 1654line 1649 didn't jump to line 1654, because the condition on line 1649 was never false

1650 if name == prog: 1650 ↛ 1643line 1650 didn't jump to line 1643, because the condition on line 1650 was never false

1651 links.append(link) 

1652 else: 

1653 # need to return process name and pid tuples 

1654 if link == name: 

1655 processandpids.append((prog, pid)) 

1656 

1657 # Get the connected sockets 

1658 if name == prog: 

1659 sockets.update(get_connected_sockets(pid)) 

1660 

1661 # We will only have a non-empty processandpids if some fd entries were found. 

1662 # Before returning them, verify that all the PIDs in question are properly alive. 

1663 # There is no specific guarantee of when a PID's /proc directory will disappear 

1664 # when it exits, particularly relative to filedescriptor cleanup, so we want to 

1665 # make sure we're not reporting a false positive. 

1666 processandpids = [x for x in processandpids if pid_is_alive(int(x[1]))] 

1667 for pp in processandpids: 1667 ↛ 1668line 1667 didn't jump to line 1668, because the loop on line 1667 never started

1668 SMlog(f"File {name} has an open handle with process {pp[0]} with pid {pp[1]}") 

1669 

1670 except Exception as e: 

1671 SMlog("Exception checking running process or open file handles. " \ 

1672 "Error: %s" % str(e)) 

1673 retVal = False 

1674 

1675 if process: 1675 ↛ 1678line 1675 didn't jump to line 1678, because the condition on line 1675 was never false

1676 return retVal, links, sockets 

1677 else: 

1678 return retVal, processandpids 

1679 

1680 

1681def get_connected_sockets(pid): 

1682 sockets = set() 

1683 try: 

1684 # Lines in /proc/<pid>/net/unix are formatted as follows 

1685 # (see Linux source net/unix/af_unix.c, unix_seq_show() ) 

1686 # - Pointer address to socket (hex) 

1687 # - Refcount (HEX) 

1688 # - 0 

1689 # - State (HEX, 0 or __SO_ACCEPTCON) 

1690 # - Type (HEX - but only 0001 of interest) 

1691 # - Connection state (HEX - but only 03, SS_CONNECTED of interest) 

1692 # - Inode number 

1693 # - Path (optional) 

1694 open_sock_matcher = re.compile( 

1695 r'^[0-9a-f]+: [0-9A-Fa-f]+ [0-9A-Fa-f]+ [0-9A-Fa-f]+ 0001 03 \d+ (.*)$') 

1696 with open( 

1697 os.path.join('/proc', str(pid), 'net', 'unix'), 'r') as f: 

1698 lines = f.readlines() 

1699 for line in lines: 

1700 match = open_sock_matcher.match(line) 

1701 if match: 

1702 sockets.add(match[1]) 

1703 except OSError as e: 

1704 if e.errno in (errno.ENOENT, errno.ESRCH): 

1705 # Ignore pid that are no longer valid 

1706 SMlog("ERROR %s reading sockets for %s, ignore" % 

1707 (e.errno, pid)) 

1708 else: 

1709 raise 

1710 return sockets 

1711 

1712 

1713def retry(f, maxretry=20, period=3, exceptions=[Exception]): 

1714 retries = 0 

1715 while True: 

1716 try: 

1717 return f() 

1718 except Exception as e: 

1719 for exception in exceptions: 

1720 if isinstance(e, exception): 

1721 SMlog('Got exception: {}. Retry number: {}'.format( 

1722 str(e), retries 

1723 )) 

1724 break 

1725 else: 

1726 SMlog('Got bad exception: {}. Raising...'.format(e)) 

1727 raise e 

1728 

1729 retries += 1 

1730 if retries >= maxretry: 

1731 break 

1732 

1733 time.sleep(period) 

1734 

1735 return f() 

1736 

1737 

1738def getCslDevPath(svid): 

1739 basepath = "/dev/disk/by-csldev/" 

1740 if svid.startswith("NETAPP_"): 

1741 # special attention for NETAPP SVIDs 

1742 svid_parts = svid.split("__") 

1743 globstr = basepath + "NETAPP__LUN__" + "*" + svid_parts[2] + "*" + svid_parts[-1] + "*" 

1744 else: 

1745 globstr = basepath + svid + "*" 

1746 

1747 return globstr 

1748 

1749 

1750# Use device in /dev pointed to by cslg path which consists of svid 

1751def get_scsiid_from_svid(md_svid): 

1752 cslg_path = getCslDevPath(md_svid) 

1753 abs_path = glob.glob(cslg_path) 

1754 if abs_path: 

1755 real_path = os.path.realpath(abs_path[0]) 

1756 return scsiutil.getSCSIid(real_path) 

1757 else: 

1758 return None 

1759 

1760 

1761def get_isl_scsiids(session): 

1762 # Get cslg type SRs 

1763 SRs = session.xenapi.SR.get_all_records_where('field "type" = "cslg"') 

1764 

1765 # Iterate through the SR to get the scsi ids 

1766 scsi_id_ret = [] 

1767 for SR in SRs: 

1768 sr_rec = SRs[SR] 

1769 # Use the md_svid to get the scsi id 

1770 scsi_id = get_scsiid_from_svid(sr_rec['sm_config']['md_svid']) 

1771 if scsi_id: 

1772 scsi_id_ret.append(scsi_id) 

1773 

1774 # Get the vdis in the SR and do the same procedure 

1775 vdi_recs = session.xenapi.VDI.get_all_records_where('field "SR" = "%s"' % SR) 

1776 for vdi_rec in vdi_recs: 

1777 vdi_rec = vdi_recs[vdi_rec] 

1778 scsi_id = get_scsiid_from_svid(vdi_rec['sm_config']['SVID']) 

1779 if scsi_id: 

1780 scsi_id_ret.append(scsi_id) 

1781 

1782 return scsi_id_ret 

1783 

1784 

1785class extractXVA: 

1786 # streams files as a set of file and checksum, caller should remove 

1787 # the files, if not needed. The entire directory (Where the files 

1788 # and checksum) will only be deleted as part of class cleanup. 

1789 HDR_SIZE = 512 

1790 BLOCK_SIZE = 512 

1791 SIZE_LEN = 12 - 1 # To remove \0 from tail 

1792 SIZE_OFFSET = 124 

1793 ZERO_FILLED_REC = 2 

1794 NULL_IDEN = '\x00' 

1795 DIR_IDEN = '/' 

1796 CHECKSUM_IDEN = '.checksum' 

1797 OVA_FILE = 'ova.xml' 

1798 

1799 # Init gunzips the file using a subprocess, and reads stdout later 

1800 # as and when needed 

1801 def __init__(self, filename): 

1802 self.__extract_path = '' 

1803 self.__filename = filename 

1804 cmd = 'gunzip -cd %s' % filename 

1805 try: 

1806 self.spawn_p = subprocess.Popen( 

1807 cmd, shell=True, \ 

1808 stdin=subprocess.PIPE, stdout=subprocess.PIPE, \ 

1809 stderr=subprocess.PIPE, close_fds=True) 

1810 except Exception as e: 

1811 SMlog("Error: %s. Uncompress failed for %s" % (str(e), filename)) 

1812 raise Exception(str(e)) 

1813 

1814 # Create dir to extract the files 

1815 self.__extract_path = tempfile.mkdtemp() 

1816 

1817 def __del__(self): 

1818 shutil.rmtree(self.__extract_path) 

1819 

1820 # Class supports Generator expression. 'for f_name, checksum in getTuple()' 

1821 # returns filename, checksum content. Returns filename, '' in case 

1822 # of checksum file missing. e.g. ova.xml 

1823 def getTuple(self): 

1824 zerod_record = 0 

1825 ret_f_name = '' 

1826 ret_base_f_name = '' 

1827 

1828 try: 

1829 # Read tar file as sets of file and checksum. 

1830 while True: 

1831 # Read the output of spawned process, or output of gunzip 

1832 f_hdr = self.spawn_p.stdout.read(self.HDR_SIZE) 

1833 

1834 # Break out in case of end of file 

1835 if f_hdr == '': 

1836 if zerod_record == extractXVA.ZERO_FILLED_REC: 

1837 break 

1838 else: 

1839 SMlog('Error. Expects %d zero records', \ 

1840 extractXVA.ZERO_FILLED_REC) 

1841 raise Exception('Unrecognized end of file') 

1842 

1843 # Watch out for zero records, two zero records 

1844 # denote end of file. 

1845 if f_hdr == extractXVA.NULL_IDEN * extractXVA.HDR_SIZE: 

1846 zerod_record += 1 

1847 continue 

1848 

1849 f_name = f_hdr[:f_hdr.index(extractXVA.NULL_IDEN)] 

1850 # File header may be for a folder, if so ignore the header 

1851 if not f_name.endswith(extractXVA.DIR_IDEN): 

1852 f_size_octal = f_hdr[extractXVA.SIZE_OFFSET: \ 

1853 extractXVA.SIZE_OFFSET + extractXVA.SIZE_LEN] 

1854 f_size = int(f_size_octal, 8) 

1855 if f_name.endswith(extractXVA.CHECKSUM_IDEN): 

1856 if f_name.rstrip(extractXVA.CHECKSUM_IDEN) == \ 

1857 ret_base_f_name: 

1858 checksum = self.spawn_p.stdout.read(f_size) 

1859 yield(ret_f_name, checksum) 

1860 else: 

1861 # Expects file followed by its checksum 

1862 SMlog('Error. Sequence mismatch starting with %s', \ 

1863 ret_f_name) 

1864 raise Exception( \ 

1865 'Files out of sequence starting with %s', \ 

1866 ret_f_name) 

1867 else: 

1868 # In case of ova.xml, read the contents into a file and 

1869 # return the file name to the caller. For other files, 

1870 # read the contents into a file, it will 

1871 # be used when a .checksum file is encountered. 

1872 ret_f_name = '%s/%s' % (self.__extract_path, f_name) 

1873 ret_base_f_name = f_name 

1874 

1875 # Check if the folder exists on the target location, 

1876 # else create it. 

1877 folder_path = ret_f_name[:ret_f_name.rfind('/')] 

1878 if not os.path.exists(folder_path): 

1879 os.mkdir(folder_path) 

1880 

1881 # Store the file to the tmp folder, strip the tail \0 

1882 f = open(ret_f_name, 'w') 

1883 f.write(self.spawn_p.stdout.read(f_size)) 

1884 f.close() 

1885 if f_name == extractXVA.OVA_FILE: 

1886 yield(ret_f_name, '') 

1887 

1888 # Skip zero'd portion of data block 

1889 round_off = f_size % extractXVA.BLOCK_SIZE 

1890 if round_off != 0: 

1891 zeros = self.spawn_p.stdout.read( 

1892 extractXVA.BLOCK_SIZE - round_off) 

1893 except Exception as e: 

1894 SMlog("Error: %s. File set extraction failed %s" % (str(e), \ 

1895 self.__filename)) 

1896 

1897 # Kill and Drain stdout of the gunzip process, 

1898 # else gunzip might block on stdout 

1899 os.kill(self.spawn_p.pid, signal.SIGTERM) 

1900 self.spawn_p.communicate() 

1901 raise Exception(str(e)) 

1902 

1903illegal_xml_chars = [(0x00, 0x08), (0x0B, 0x1F), (0x7F, 0x84), (0x86, 0x9F), 

1904 (0xD800, 0xDFFF), (0xFDD0, 0xFDDF), (0xFFFE, 0xFFFF), 

1905 (0x1FFFE, 0x1FFFF), (0x2FFFE, 0x2FFFF), (0x3FFFE, 0x3FFFF), 

1906 (0x4FFFE, 0x4FFFF), (0x5FFFE, 0x5FFFF), (0x6FFFE, 0x6FFFF), 

1907 (0x7FFFE, 0x7FFFF), (0x8FFFE, 0x8FFFF), (0x9FFFE, 0x9FFFF), 

1908 (0xAFFFE, 0xAFFFF), (0xBFFFE, 0xBFFFF), (0xCFFFE, 0xCFFFF), 

1909 (0xDFFFE, 0xDFFFF), (0xEFFFE, 0xEFFFF), (0xFFFFE, 0xFFFFF), 

1910 (0x10FFFE, 0x10FFFF)] 

1911 

1912illegal_ranges = ["%s-%s" % (chr(low), chr(high)) 

1913 for (low, high) in illegal_xml_chars 

1914 if low < sys.maxunicode] 

1915 

1916illegal_xml_re = re.compile(u'[%s]' % u''.join(illegal_ranges)) 

1917 

1918 

1919def isLegalXMLString(s): 

1920 """Tells whether this is a valid XML string (i.e. it does not contain 

1921 illegal XML characters specified in 

1922 http://www.w3.org/TR/2004/REC-xml-20040204/#charsets). 

1923 """ 

1924 

1925 if len(s) > 0: 

1926 return re.search(illegal_xml_re, s) is None 

1927 else: 

1928 return True 

1929 

1930 

1931def unictrunc(string, max_bytes): 

1932 """ 

1933 Given a string, returns the largest number of elements for a prefix 

1934 substring of it, such that the UTF-8 encoding of this substring takes no 

1935 more than the given number of bytes. 

1936 

1937 The string may be given as a unicode string or a UTF-8 encoded byte 

1938 string, and the number returned will be in characters or bytes 

1939 accordingly. Note that in the latter case, the substring will still be a 

1940 valid UTF-8 encoded string (which is to say, it won't have been truncated 

1941 part way through a multibyte sequence for a unicode character). 

1942 

1943 string: the string to truncate 

1944 max_bytes: the maximum number of bytes the truncated string can be 

1945 """ 

1946 if isinstance(string, str): 

1947 return_chars = True 

1948 else: 

1949 return_chars = False 

1950 string = string.decode('UTF-8') 

1951 

1952 cur_chars = 0 

1953 cur_bytes = 0 

1954 for char in string: 

1955 charsize = len(char.encode('UTF-8')) 

1956 if cur_bytes + charsize > max_bytes: 

1957 break 

1958 else: 

1959 cur_chars += 1 

1960 cur_bytes += charsize 

1961 return cur_chars if return_chars else cur_bytes 

1962 

1963 

1964def hideValuesInPropMap(propmap, propnames): 

1965 """ 

1966 Worker function: input simple map of prop name/value pairs, and 

1967 a list of specific propnames whose values we want to hide. 

1968 Loop through the "hide" list, and if any are found, hide the 

1969 value and return the altered map. 

1970 If none found, return the original map 

1971 """ 

1972 matches = [] 

1973 for propname in propnames: 

1974 if propname in propmap: 1974 ↛ 1975line 1974 didn't jump to line 1975, because the condition on line 1974 was never true

1975 matches.append(propname) 

1976 

1977 if matches: 1977 ↛ 1978line 1977 didn't jump to line 1978, because the condition on line 1977 was never true

1978 deepCopyRec = copy.deepcopy(propmap) 

1979 for match in matches: 

1980 deepCopyRec[match] = '******' 

1981 return deepCopyRec 

1982 

1983 return propmap 

1984# define the list of propnames whose value we want to hide 

1985 

1986PASSWD_PROP_KEYS = ['password', 'cifspassword', 'chappassword', 'incoming_chappassword'] 

1987DEFAULT_SEGMENT_LEN = 950 

1988 

1989 

1990def hidePasswdInConfig(config): 

1991 """ 

1992 Function to hide passwd values in a simple prop map, 

1993 for example "device_config" 

1994 """ 

1995 return hideValuesInPropMap(config, PASSWD_PROP_KEYS) 

1996 

1997 

1998def hidePasswdInParams(params, configProp): 

1999 """ 

2000 Function to hide password values in a specified property which 

2001 is a simple map of prop name/values, and is itself an prop entry 

2002 in a larger property map. 

2003 For example, param maps containing "device_config", or 

2004 "sm_config", etc 

2005 """ 

2006 params[configProp] = hideValuesInPropMap(params[configProp], PASSWD_PROP_KEYS) 

2007 return params 

2008 

2009 

2010def hideMemberValuesInXmlParams(xmlParams, propnames=PASSWD_PROP_KEYS): 

2011 """ 

2012 Function to hide password values in XML params, specifically 

2013 for the XML format of incoming params to SR modules. 

2014 Uses text parsing: loop through the list of specific propnames 

2015 whose values we want to hide, and: 

2016 - Assemble a full "prefix" containing each property name, e.g., 

2017 "<member><name>password</name><value>" 

2018 - Test the XML if it contains that string, save the index. 

2019 - If found, get the index of the ending tag 

2020 - Truncate the return string starting with the password value. 

2021 - Append the substitute "*******" value string. 

2022 - Restore the rest of the original string starting with the end tag. 

2023 """ 

2024 findStrPrefixHead = "<member><name>" 

2025 findStrPrefixTail = "</name><value>" 

2026 findStrSuffix = "</value>" 

2027 strlen = len(xmlParams) 

2028 

2029 for propname in propnames: 

2030 findStrPrefix = findStrPrefixHead + propname + findStrPrefixTail 

2031 idx = xmlParams.find(findStrPrefix) 

2032 if idx != -1: # if found any of them 

2033 idx += len(findStrPrefix) 

2034 idx2 = xmlParams.find(findStrSuffix, idx) 

2035 if idx2 != -1: 

2036 retStr = xmlParams[0:idx] 

2037 retStr += "******" 

2038 retStr += xmlParams[idx2:strlen] 

2039 return retStr 

2040 else: 

2041 return xmlParams 

2042 return xmlParams 

2043 

2044 

2045def splitXmlText(xmlData, segmentLen=DEFAULT_SEGMENT_LEN, showContd=False): 

2046 """ 

2047 Split xml string data into substrings small enough for the 

2048 syslog line length limit. Split at tag end markers ( ">" ). 

2049 Usage: 

2050 strList = [] 

2051 strList = splitXmlText( longXmlText, maxLineLen ) # maxLineLen is optional 

2052 """ 

2053 remainingData = str(xmlData) 

2054 

2055 # "Un-pretty-print" 

2056 remainingData = remainingData.replace('\n', '') 

2057 remainingData = remainingData.replace('\t', '') 

2058 

2059 remainingChars = len(remainingData) 

2060 returnData = '' 

2061 

2062 thisLineNum = 0 

2063 while remainingChars > segmentLen: 

2064 thisLineNum = thisLineNum + 1 

2065 index = segmentLen 

2066 tmpStr = remainingData[:segmentLen] 

2067 tmpIndex = tmpStr.rfind('>') 

2068 if tmpIndex != -1: 

2069 index = tmpIndex + 1 

2070 

2071 tmpStr = tmpStr[:index] 

2072 remainingData = remainingData[index:] 

2073 remainingChars = len(remainingData) 

2074 

2075 if showContd: 

2076 if thisLineNum != 1: 

2077 tmpStr = '(Cont\'d): ' + tmpStr 

2078 tmpStr = tmpStr + ' (Cont\'d):' 

2079 

2080 returnData += tmpStr + '\n' 

2081 

2082 if showContd and thisLineNum > 0: 

2083 remainingData = '(Cont\'d): ' + remainingData 

2084 returnData += remainingData 

2085 

2086 return returnData 

2087 

2088 

2089def inject_failure(): 

2090 raise Exception('injected failure') 

2091 

2092 

2093def open_atomic(path, mode=None): 

2094 """Atomically creates a file if, and only if it does not already exist. 

2095 Leaves the file open and returns the file object. 

2096 

2097 path: the path to atomically open 

2098 mode: "r" (read), "w" (write), or "rw" (read/write) 

2099 returns: an open file object""" 

2100 

2101 assert path 

2102 

2103 flags = os.O_CREAT | os.O_EXCL 

2104 modes = {'r': os.O_RDONLY, 'w': os.O_WRONLY, 'rw': os.O_RDWR} 

2105 if mode: 

2106 if mode not in modes: 

2107 raise Exception('invalid access mode ' + mode) 

2108 flags |= modes[mode] 

2109 fd = os.open(path, flags) 

2110 try: 

2111 if mode: 

2112 return os.fdopen(fd, mode) 

2113 else: 

2114 return os.fdopen(fd) 

2115 except: 

2116 os.close(fd) 

2117 raise 

2118 

2119 

2120def isInvalidVDI(exception): 

2121 return exception.details[0] == "HANDLE_INVALID" or \ 

2122 exception.details[0] == "UUID_INVALID" 

2123 

2124 

2125def get_pool_restrictions(session): 

2126 """Returns pool restrictions as a map, @session must be already 

2127 established.""" 

2128 return list(session.xenapi.pool.get_all_records().values())[0]['restrictions'] 

2129 

2130 

2131def read_caching_is_restricted(session): 

2132 """Tells whether read caching is restricted.""" 

2133 if session is None: 2133 ↛ 2134line 2133 didn't jump to line 2134, because the condition on line 2133 was never true

2134 return True 

2135 restrictions = get_pool_restrictions(session) 

2136 if 'restrict_read_caching' in restrictions and \ 2136 ↛ 2138line 2136 didn't jump to line 2138, because the condition on line 2136 was never true

2137 restrictions['restrict_read_caching'] == "true": 

2138 return True 

2139 return False 

2140 

2141 

2142def sessions_less_than_targets(other_config, device_config): 

2143 if 'multihomelist' in device_config and 'iscsi_sessions' in other_config: 

2144 sessions = int(other_config['iscsi_sessions']) 

2145 targets = len(device_config['multihomelist'].split(',')) 

2146 SMlog("Targets %d and iscsi_sessions %d" % (targets, sessions)) 

2147 return (sessions < targets) 

2148 else: 

2149 return False 

2150 

2151 

2152def enable_and_start_service(name, start): 

2153 attempt = 0 

2154 while True: 

2155 attempt += 1 

2156 fn = 'enable' if start else 'disable' 

2157 args = ('systemctl', fn, '--now', name) 

2158 (ret, out, err) = doexec(args) 

2159 if ret == 0: 

2160 return 

2161 elif attempt >= 3: 

2162 raise Exception( 

2163 'Failed to {} {}: {} {}'.format(fn, name, out, err) 

2164 ) 

2165 time.sleep(1) 

2166 

2167 

2168def stop_service(name): 

2169 args = ('systemctl', 'stop', name) 

2170 (ret, out, err) = doexec(args) 

2171 if ret == 0: 

2172 return 

2173 raise Exception('Failed to stop {}: {} {}'.format(name, out, err)) 

2174 

2175 

2176def restart_service(name): 

2177 attempt = 0 

2178 while True: 

2179 attempt += 1 

2180 SMlog('Restarting service {} {}...'.format(name, attempt)) 

2181 args = ('systemctl', 'restart', name) 

2182 (ret, out, err) = doexec(args) 

2183 if ret == 0: 

2184 return 

2185 elif attempt >= 3: 

2186 SMlog('Restart service FAILED {} {}'.format(name, attempt)) 

2187 raise Exception( 

2188 'Failed to restart {}: {} {}'.format(name, out, err) 

2189 ) 

2190 time.sleep(1) 

2191 

2192 

2193def check_pid_exists(pid): 

2194 try: 

2195 os.kill(pid, 0) 

2196 except OSError: 

2197 return False 

2198 else: 

2199 return True 

2200 

2201 

2202def get_openers_pid(path: str) -> Optional[List[int]]: 

2203 cmd = ["lsof", "-t", path] 

2204 

2205 try: 

2206 list = [] 

2207 ret = pread2(cmd) 

2208 for line in ret.splitlines(): 

2209 list.append(int(line)) 

2210 return list 

2211 except CommandException as e: 

2212 if e.code == 1: # `lsof` return 1 if there is no openers 

2213 return None 

2214 else: 

2215 raise e 

2216 

2217 

2218def make_profile(name, function): 

2219 """ 

2220 Helper to execute cProfile using unique log file. 

2221 """ 

2222 

2223 import cProfile 

2224 import itertools 

2225 import os.path 

2226 import time 

2227 

2228 assert name 

2229 assert function 

2230 

2231 FOLDER = '/tmp/sm-perfs/' 

2232 makedirs(FOLDER) 

2233 

2234 filename = time.strftime('{}_%Y%m%d_%H%M%S.prof'.format(name)) 

2235 

2236 def gen_path(path): 

2237 yield path 

2238 root, ext = os.path.splitext(path) 

2239 for i in itertools.count(start=1, step=1): 

2240 yield root + '.{}.'.format(i) + ext 

2241 

2242 for profile_path in gen_path(FOLDER + filename): 

2243 try: 

2244 file = open_atomic(profile_path, 'w') 

2245 file.close() 

2246 break 

2247 except OSError as e: 

2248 if e.errno == errno.EEXIST: 

2249 pass 

2250 else: 

2251 raise 

2252 

2253 try: 

2254 SMlog('* Start profiling of {} ({}) *'.format(name, filename)) 

2255 cProfile.runctx('function()', None, locals(), profile_path) 

2256 finally: 

2257 SMlog('* End profiling of {} ({}) *'.format(name, filename)) 

2258 

2259 

2260def strtobool(str: str) -> bool: 

2261 # Note: `distutils` package is deprecated and slated for removal in Python 3.12. 

2262 # There is not alternative for strtobool. 

2263 # See: https://peps.python.org/pep-0632/#migration-advice 

2264 # So this is a custom implementation with differences: 

2265 # - A boolean is returned instead of integer 

2266 # - Empty string and None are supported (False is returned in this case) 

2267 if not str: 2267 ↛ 2269line 2267 didn't jump to line 2269, because the condition on line 2267 was never false

2268 return False 

2269 str = str.lower() 

2270 if str in ('y', 'yes', 't', 'true', 'on', '1'): 

2271 return True 

2272 if str in ('n', 'no', 'f', 'false', 'off', '0'): 

2273 return False 

2274 raise ValueError("invalid truth value '{}'".format(str)) 

2275 

2276 

2277def find_executable(name): 

2278 return shutil.which(name) 

2279 

2280 

2281def conditional_decorator(decorator, condition): 

2282 def wrapper(func): 

2283 if not condition: 2283 ↛ 2285line 2283 didn't jump to line 2285, because the condition on line 2283 was never false

2284 return func 

2285 return decorator(func) 

2286 return wrapper 

2287 

2288 

2289def get_srs_uuid_from_type(session, type): 

2290 srs = session.xenapi.SR.get_all_records_where(f"field \"type\" = \"{type}\"") 

2291 return {sr["uuid"]: sr for sr in srs.values()} 

2292 

2293 

2294def get_linstor_srs_uuid(session): 

2295 import LinstorSR # pylint: disable=C0415 

2296 return get_srs_uuid_from_type(session, LinstorSR.LinstorSR.DRIVER_TYPE) 

2297 

2298 

2299def find_pbd_ref_from_dconf_value(session, srs, key, value, value_modifier = None): 

2300 for sr in srs.values(): 

2301 for pbd_ref in sr["PBDs"]: 

2302 device_config = session.xenapi.PBD.get_device_config(pbd_ref) 

2303 cur_value = device_config.get(key) 

2304 if value_modifier: 

2305 cur_value = value_modifier(cur_value) 

2306 if cur_value and cur_value == value: 

2307 return pbd_ref 

2308 return None