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 

19import os 

20import re 

21import sys 

22import subprocess 

23import shutil 

24import tempfile 

25import signal 

26import time 

27import datetime 

28import errno 

29import functools 

30import socket 

31import threading 

32import xml.dom.minidom 

33import scsiutil 

34import stat 

35import xs_errors 

36import XenAPI # pylint: disable=import-error 

37import xmlrpc.client 

38import base64 

39import syslog 

40import resource 

41import traceback 

42import glob 

43import copy 

44import tempfile 

45 

46from functools import reduce 

47 

48NO_LOGGING_STAMPFILE = '/etc/xensource/no_sm_log' 

49 

50IORETRY_MAX = 20 # retries 

51IORETRY_PERIOD = 1.0 # seconds 

52 

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

54_SM_SYSLOG_FACILITY = syslog.LOG_LOCAL2 

55LOG_EMERG = syslog.LOG_EMERG 

56LOG_ALERT = syslog.LOG_ALERT 

57LOG_CRIT = syslog.LOG_CRIT 

58LOG_ERR = syslog.LOG_ERR 

59LOG_WARNING = syslog.LOG_WARNING 

60LOG_NOTICE = syslog.LOG_NOTICE 

61LOG_INFO = syslog.LOG_INFO 

62LOG_DEBUG = syslog.LOG_DEBUG 

63 

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

65 

66CMD_DD = "/bin/dd" 

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

68 

69FIST_PAUSE_PERIOD = 30 # seconds 

70 

71 

72class SMException(Exception): 

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

74 XenError""" 

75 

76 

77class CommandException(SMException): 

78 def error_message(self, code): 

79 if code > 0: 

80 return os.strerror(code) 

81 elif code < 0: 

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

83 return "Success" 

84 

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

86 self.code = code 

87 self.cmd = cmd 

88 self.reason = reason 

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

90 

91 

92class SRBusyException(SMException): 

93 """The SR could not be locked""" 

94 pass 

95 

96 

97def logException(tag): 

98 info = sys.exc_info() 

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

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

101 sys.exit(0) 

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

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

104 SMlog(str) 

105 

106 

107def roundup(divisor, value): 

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

109 

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

111 value = 1 

112 if value % divisor != 0: 

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

114 return value 

115 

116 

117def to_plain_string(obj): 

118 if obj is None: 

119 return None 

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

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

122 return "" 

123 return str(obj) 

124 

125 

126def shellquote(arg): 

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

128 

129 

130def make_WWN(name): 

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

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

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

134 # inject dashes for each nibble 

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

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

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

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

139 return name 

140 

141 

142def synchronized(func): 

143 lock = threading.RLock() 

144 

145 @functools.wraps(func) 

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

147 with lock: 

148 return func(*args, **kwargs) 

149 

150 return wrapper 

151 

152 

153@synchronized 

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

155 syslog.openlog(ident, 0, facility) 

156 syslog.syslog(priority, message) 

157 syslog.closelog() 

158 

159 

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

161 pid = os.getpid() 

162 thread_name = threading.current_thread().name 

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

164 

165 

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

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

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

169 _logToSyslog(ident, _SM_SYSLOG_FACILITY, priority, message_line) 

170 

171 

172def _getDateString(): 

173 d = datetime.datetime.now() 

174 t = d.timetuple() 

175 return "%s-%s-%s:%s:%s:%s" % \ 

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

177 

178 

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

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

181 env = None 

182 if new_env: 

183 env = dict(os.environ) 

184 env.update(new_env) 

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

186 stdout=subprocess.PIPE, 

187 stderr=subprocess.PIPE, 

188 close_fds=True, env=env, 

189 universal_newlines=text) 

190 

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

192 inputtext = inputtext.encode() 

193 

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

195 

196 rc = proc.returncode 

197 return rc, stdout, stderr 

198 

199 

200def is_string(value): 

201 return isinstance(value, str) 

202 

203 

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

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

206# replace the original ones at some later date. 

207# 

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

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

210# written to the logs. 

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

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

213 cmdlist_for_exec = [] 

214 cmdlist_for_log = [] 

215 for item in cmdlist: 

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

217 cmdlist_for_exec.append(item) 

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

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

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

221 else: 

222 cmdlist_for_log.append(item) 

223 else: 

224 cmdlist_for_log.append(item) 

225 else: 

226 cmdlist_for_exec.append(item[0]) 

227 cmdlist_for_log.append(item[1]) 

228 

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

230 SMlog(cmdlist_for_log) 

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

232 if rc != expect_rc: 

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

234 (rc, stdout, stderr)) 

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

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

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

238 stderr = stdout 

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

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

241 SMlog(" pread SUCCESS") 

242 return stdout 

243 

244 

245# POSIX guaranteed atomic within the same file system. 

246# Supply directory to ensure tempfile is created 

247# in the same directory. 

248def atomicFileWrite(targetFile, directory, text): 

249 

250 file = None 

251 try: 

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

253 # our responsibility to clean it up. 

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

255 file = open(tempPath, 'w') 

256 file.write(text) 

257 

258 # Ensure flushed to disk. 

259 file.flush() 

260 os.fsync(file.fileno()) 

261 file.close() 

262 

263 os.rename(tempPath, targetFile) 

264 except OSError: 

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

266 

267 finally: 

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

269 file.close() 

270 

271 if os.path.isfile(tempPath): 

272 os.remove(tempPath) 

273 

274 

275#Read STDOUT from cmdlist and discard STDERR output 

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

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

278 

279 

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

281def pread3(cmdlist, text): 

282 SMlog(cmdlist) 

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

284 if rc: 

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

286 (rc, stdout, stderr)) 

287 if '' == stderr: 

288 stderr = stdout 

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

290 SMlog(" pread3 SUCCESS") 

291 return stdout 

292 

293 

294def listdir(path, quiet=False): 

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

296 try: 

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

298 if len(text) == 0: 

299 return [] 

300 return text.split('\n') 

301 except CommandException as inst: 

302 if inst.code == errno.ENOENT: 

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

304 else: 

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

306 

307 

308def gen_uuid(): 

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

310 return pread(cmd)[:-1] 

311 

312 

313def match_uuid(s): 

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

315 return regex.search(s, 0) 

316 

317 

318def findall_uuid(s): 

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

320 return regex.findall(s, 0) 

321 

322 

323def exactmatch_uuid(s): 

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

325 return regex.search(s, 0) 

326 

327 

328def start_log_entry(srpath, path, args): 

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

330 logstring += " log: " 

331 logstring += srpath 

332 logstring += " " + path 

333 for element in args: 

334 logstring += " " + element 

335 try: 

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

337 file.write(logstring) 

338 file.write("\n") 

339 file.close() 

340 except: 

341 pass 

342 

343 # failed to write log ... 

344 

345def end_log_entry(srpath, path, args): 

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

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

348 logstring += " end: " 

349 logstring += srpath 

350 logstring += " " + path 

351 for element in args: 

352 logstring += " " + element 

353 try: 

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

355 file.write(logstring) 

356 file.write("\n") 

357 file.close() 

358 except: 

359 pass 

360 

361 # failed to write log ... 

362 # for now print 

363 # print "%s" % logstring 

364 

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

366 retries = 0 

367 while True: 

368 try: 

369 return f() 

370 except OSError as ose: 

371 err = int(ose.errno) 

372 if not err in errlist: 

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

374 except CommandException as ce: 

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

376 raise 

377 

378 retries += 1 

379 if retries >= maxretry: 

380 break 

381 

382 time.sleep(period) 

383 

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

385 

386 

387def ioretry_stat(path, maxretry=IORETRY_MAX): 

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

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

390 retries = 0 

391 while retries < maxretry: 

392 stat = os.statvfs(path) 

393 if stat.f_blocks != -1: 

394 return stat 

395 time.sleep(1) 

396 retries += 1 

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

398 

399 

400def sr_get_capability(sr_uuid, session=None): 

401 result = [] 

402 local_session = None 

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

404 local_session = get_localAPI_session() 

405 session = local_session 

406 

407 try: 

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

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

410 sm_rec = session.xenapi.SM.get_all_records_where( 

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

412 

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

414 if len(sm_rec) > 0: 

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

416 

417 return result 

418 finally: 

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

420 local_session.xenapi.session.logout() 

421 

422def sr_get_driver_info(driver_info): 

423 results = {} 

424 # first add in the vanilla stuff 

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

426 'driver_version', 'required_api_version']: 

427 results[key] = driver_info[key] 

428 # add the capabilities (xmlrpc array) 

429 # enforcing activate/deactivate for blktap2 

430 caps = driver_info['capabilities'] 

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

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

433 if not cap in caps: 

434 caps.append(cap) 

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

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

437 

438 results['capabilities'] = caps 

439 # add in the configuration options 

440 options = [] 

441 for option in driver_info['configuration']: 

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

443 results['configuration'] = options 

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

445 

446 

447def return_nil(): 

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

449 

450 

451def SRtoXML(SRlist): 

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

453 driver = dom.createElement("SRlist") 

454 dom.appendChild(driver) 

455 

456 for key in SRlist.keys(): 

457 dict = SRlist[key] 

458 entry = dom.createElement("SR") 

459 driver.appendChild(entry) 

460 

461 e = dom.createElement("UUID") 

462 entry.appendChild(e) 

463 textnode = dom.createTextNode(key) 

464 e.appendChild(textnode) 

465 

466 if 'size' in dict: 

467 e = dom.createElement("Size") 

468 entry.appendChild(e) 

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

470 e.appendChild(textnode) 

471 

472 if 'storagepool' in dict: 

473 e = dom.createElement("StoragePool") 

474 entry.appendChild(e) 

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

476 e.appendChild(textnode) 

477 

478 if 'aggregate' in dict: 

479 e = dom.createElement("Aggregate") 

480 entry.appendChild(e) 

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

482 e.appendChild(textnode) 

483 

484 return dom.toprettyxml() 

485 

486 

487def pathexists(path): 

488 try: 

489 os.lstat(path) 

490 return True 

491 except OSError as inst: 

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

493 time.sleep(1) 

494 try: 

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

496 os.lstat(path) 

497 return True 

498 except: 

499 pass 

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

501 return False 

502 

503 

504def force_unlink(path): 

505 try: 

506 os.unlink(path) 

507 except OSError as e: 

508 if e.errno != errno.ENOENT: 508 ↛ 509line 508 didn't jump to line 509, because the condition on line 508 was never true

509 raise 

510 

511 

512def create_secret(session, secret): 

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

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

515 

516 

517def get_secret(session, uuid): 

518 try: 

519 ref = session.xenapi.secret.get_by_uuid(uuid) 

520 return session.xenapi.secret.get_value(ref) 

521 except: 

522 raise xs_errors.XenError('InvalidSecret', opterr='Unable to look up secret [%s]' % uuid) 

523 

524 

525def get_real_path(path): 

526 "Follow symlinks to the actual file" 

527 absPath = path 

528 directory = '' 

529 while os.path.islink(absPath): 

530 directory = os.path.dirname(absPath) 

531 absPath = os.readlink(absPath) 

532 absPath = os.path.join(directory, absPath) 

533 return absPath 

534 

535 

536def wait_for_path(path, timeout): 

537 for i in range(0, timeout): 537 ↛ 541line 537 didn't jump to line 541, because the loop on line 537 didn't complete

538 if len(glob.glob(path)): 538 ↛ 540line 538 didn't jump to line 540, because the condition on line 538 was never false

539 return True 

540 time.sleep(1) 

541 return False 

542 

543 

544def wait_for_nopath(path, timeout): 

545 for i in range(0, timeout): 

546 if not os.path.exists(path): 

547 return True 

548 time.sleep(1) 

549 return False 

550 

551 

552def wait_for_path_multi(path, timeout): 

553 for i in range(0, timeout): 

554 paths = glob.glob(path) 

555 SMlog("_wait_for_paths_multi: paths = %s" % paths) 

556 if len(paths): 

557 SMlog("_wait_for_paths_multi: return first path: %s" % paths[0]) 

558 return paths[0] 

559 time.sleep(1) 

560 return "" 

561 

562 

563def isdir(path): 

564 try: 

565 st = os.stat(path) 

566 return stat.S_ISDIR(st.st_mode) 

567 except OSError as inst: 

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

569 raise CommandException(errno.EIO, "os.stat(%s)" % path, "failed") 

570 return False 

571 

572 

573def get_single_entry(path): 

574 f = open(path, 'r') 

575 line = f.readline() 

576 f.close() 

577 return line.rstrip() 

578 

579 

580def get_fs_size(path): 

581 st = ioretry_stat(path) 

582 return st.f_blocks * st.f_frsize 

583 

584 

585def get_fs_utilisation(path): 

586 st = ioretry_stat(path) 

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

588 st.f_frsize 

589 

590 

591def ismount(path): 

592 """Test whether a path is a mount point""" 

593 try: 

594 s1 = os.stat(path) 

595 s2 = os.stat(os.path.join(path, '..')) 

596 except OSError as inst: 

597 raise CommandException(inst.errno, "os.stat") 

598 dev1 = s1.st_dev 

599 dev2 = s2.st_dev 

600 if dev1 != dev2: 

601 return True # path/.. on a different device as path 

602 ino1 = s1.st_ino 

603 ino2 = s2.st_ino 

604 if ino1 == ino2: 

605 return True # path/.. is the same i-node as path 

606 return False 

607 

608 

609def makedirs(name, mode=0o777): 

610 head, tail = os.path.split(name) 

611 if not tail: 611 ↛ 612line 611 didn't jump to line 612, because the condition on line 611 was never true

612 head, tail = os.path.split(head) 

613 if head and tail and not pathexists(head): 

614 makedirs(head, mode) 

615 if tail == os.curdir: 615 ↛ 616line 615 didn't jump to line 616, because the condition on line 615 was never true

616 return 

617 try: 

618 os.mkdir(name, mode) 

619 except OSError as exc: 

620 if exc.errno == errno.EEXIST and os.path.isdir(name): 620 ↛ 621line 620 didn't jump to line 621, because the condition on line 620 was never true

621 if mode: 

622 os.chmod(name, mode) 

623 pass 

624 else: 

625 raise 

626 

627 

628def zeroOut(path, fromByte, bytes): 

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

630 blockSize = 4096 

631 

632 fromBlock = fromByte // blockSize 

633 if fromByte % blockSize: 

634 fromBlock += 1 

635 bytesBefore = fromBlock * blockSize - fromByte 

636 if bytesBefore > bytes: 

637 bytesBefore = bytes 

638 bytes -= bytesBefore 

639 cmd = [CMD_DD, "if=/dev/zero", "of=%s" % path, "bs=1", 

640 "seek=%s" % fromByte, "count=%s" % bytesBefore] 

641 try: 

642 pread2(cmd) 

643 except CommandException: 

644 return False 

645 

646 blocks = bytes // blockSize 

647 bytes -= blocks * blockSize 

648 fromByte = (fromBlock + blocks) * blockSize 

649 if blocks: 

650 cmd = [CMD_DD, "if=/dev/zero", "of=%s" % path, "bs=%s" % blockSize, 

651 "seek=%s" % fromBlock, "count=%s" % blocks] 

652 try: 

653 pread2(cmd) 

654 except CommandException: 

655 return False 

656 

657 if bytes: 

658 cmd = [CMD_DD, "if=/dev/zero", "of=%s" % path, "bs=1", 

659 "seek=%s" % fromByte, "count=%s" % bytes] 

660 try: 

661 pread2(cmd) 

662 except CommandException: 

663 return False 

664 

665 return True 

666 

667 

668def wipefs(blockdev): 

669 "Wipe filesystem signatures from `blockdev`" 

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

671 

672 

673def match_rootdev(s): 

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

675 return regex.search(s, 0) 

676 

677 

678def getrootdev(): 

679 filename = '/etc/xensource-inventory' 

680 try: 

681 f = open(filename, 'r') 

682 except: 

683 raise xs_errors.XenError('EIO', \ 

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

685 rootdev = '' 

686 for line in filter(match_rootdev, f.readlines()): 

687 rootdev = line.split("'")[1] 

688 if not rootdev: 688 ↛ 689line 688 didn't jump to line 689, because the condition on line 688 was never true

689 raise xs_errors.XenError('NoRootDev') 

690 return rootdev 

691 

692 

693def getrootdevID(): 

694 rootdev = getrootdev() 

695 try: 

696 rootdevID = scsiutil.getSCSIid(rootdev) 

697 except: 

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

699 % rootdev) 

700 return '' 

701 

702 if not len(rootdevID): 

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

704 % rootdev) 

705 

706 return rootdevID 

707 

708 

709def get_localAPI_session(): 

710 # First acquire a valid session 

711 session = XenAPI.xapi_local() 

712 try: 

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

714 except: 

715 raise xs_errors.XenError('APISession') 

716 return session 

717 

718 

719def get_this_host(): 

720 uuid = None 

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

722 for line in f.readlines(): 

723 if line.startswith("INSTALLATION_UUID"): 

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

725 f.close() 

726 return uuid 

727 

728 

729def get_master_ref(session): 

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

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

732 

733 

734def is_master(session): 

735 return get_this_host_ref(session) == get_master_ref(session) 

736 

737 

738def get_localhost_ref(session): 

739 filename = '/etc/xensource-inventory' 

740 try: 

741 f = open(filename, 'r') 

742 except: 

743 raise xs_errors.XenError('EIO', \ 

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

745 domid = '' 

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

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

748 if not domid: 

749 raise xs_errors.XenError('APILocalhost') 

750 

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

752 for vm in vms: 

753 record = vms[vm] 

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

755 hostid = record["resident_on"] 

756 return hostid 

757 raise xs_errors.XenError('APILocalhost') 

758 

759 

760def match_domain_id(s): 

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

762 return regex.search(s, 0) 

763 

764 

765def get_hosts_attached_on(session, vdi_uuids): 

766 host_refs = {} 

767 for vdi_uuid in vdi_uuids: 

768 try: 

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

770 except XenAPI.Failure: 

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

772 continue 

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

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

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

776 return host_refs.keys() 

777 

778def get_this_host_address(session): 

779 host_uuid = get_this_host() 

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

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

782 

783def get_host_addresses(session): 

784 addresses = [] 

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

786 for record in hosts.values(): 

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

788 return addresses 

789 

790def get_this_host_ref(session): 

791 host_uuid = get_this_host() 

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

793 return host_ref 

794 

795 

796def get_slaves_attached_on(session, vdi_uuids): 

797 "assume this host is the SR master" 

798 host_refs = get_hosts_attached_on(session, vdi_uuids) 

799 master_ref = get_this_host_ref(session) 

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

801 

802def get_enabled_hosts(session): 

803 """ 

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

805 """ 

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

807 

808def get_online_hosts(session): 

809 online_hosts = [] 

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

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

812 metricsRef = host_rec["metrics"] 

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

814 if metrics["live"]: 

815 online_hosts.append(host_ref) 

816 return online_hosts 

817 

818 

819def get_all_slaves(session): 

820 "assume this host is the SR master" 

821 host_refs = get_online_hosts(session) 

822 master_ref = get_this_host_ref(session) 

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

824 

825 

826def is_attached_rw(sm_config): 

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

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

829 return True 

830 return False 

831 

832 

833def attached_as(sm_config): 

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

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

836 return val 

837 

838 

839def find_my_pbd_record(session, host_ref, sr_ref): 

840 try: 

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

842 for pbd_ref in pbds.keys(): 

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

844 return [pbd_ref, pbds[pbd_ref]] 

845 return None 

846 except Exception as e: 

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

848 return None 

849 

850 

851def find_my_pbd(session, host_ref, sr_ref): 

852 ret = find_my_pbd_record(session, host_ref, sr_ref) 

853 if ret is not None: 

854 return ret[0] 

855 else: 

856 return None 

857 

858 

859def test_hostPBD_devs(session, sr_uuid, devs): 

860 host = get_localhost_ref(session) 

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

862 try: 

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

864 except: 

865 raise xs_errors.XenError('APIPBDQuery') 

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

867 for pbd in pbds: 

868 record = pbds[pbd] 

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

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

871 break 

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

873 devconfig = record["device_config"] 

874 if 'device' in devconfig: 

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

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

877 return True 

878 return False 

879 

880 

881def test_hostPBD_lun(session, targetIQN, LUNid): 

882 host = get_localhost_ref(session) 

883 try: 

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

885 except: 

886 raise xs_errors.XenError('APIPBDQuery') 

887 for pbd in pbds: 

888 record = pbds[pbd] 

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

890 devconfig = record["device_config"] 

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

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

893 devconfig['LUNid'] == LUNid: 

894 return True 

895 return False 

896 

897 

898def test_SCSIid(session, sr_uuid, SCSIid): 

899 if sr_uuid is not None: 

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

901 try: 

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

903 except: 

904 raise xs_errors.XenError('APIPBDQuery') 

905 for pbd in pbds: 

906 record = pbds[pbd] 

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

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

909 if sr_uuid is not None: 

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

911 break 

912 devconfig = record["device_config"] 

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

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

915 return True 

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

917 return True 

918 elif 'scsi-' + SCSIid in sm_config: 

919 return True 

920 return False 

921 

922 

923class TimeoutException(SMException): 

924 pass 

925 

926 

927def timeout_call(timeoutseconds, function, *arguments): 

928 def handler(signum, frame): 

929 raise TimeoutException() 

930 signal.signal(signal.SIGALRM, handler) 

931 signal.alarm(timeoutseconds) 

932 try: 

933 return function(*arguments) 

934 finally: 

935 signal.alarm(0) 

936 

937 

938def _incr_iscsiSR_refcount(targetIQN, uuid): 

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

940 os.mkdir(ISCSI_REFDIR) 

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

942 try: 

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

944 except: 

945 raise xs_errors.XenError('LVMRefCount', \ 

946 opterr='file %s' % filename) 

947 

948 f.seek(0) 

949 found = False 

950 refcount = 0 

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

952 refcount += 1 

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

954 found = True 

955 if not found: 

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

957 refcount += 1 

958 f.close() 

959 return refcount 

960 

961 

962def _decr_iscsiSR_refcount(targetIQN, uuid): 

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

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

965 return 0 

966 try: 

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

968 except: 

969 raise xs_errors.XenError('LVMRefCount', \ 

970 opterr='file %s' % filename) 

971 

972 f.seek(0) 

973 output = [] 

974 refcount = 0 

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

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

977 output.append(line.rstrip()) 

978 refcount += 1 

979 if not refcount: 

980 os.unlink(filename) 

981 return refcount 

982 

983 # Re-open file and truncate 

984 f.close() 

985 f = open(filename, 'w') 

986 for i in range(0, refcount): 

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

988 f.close() 

989 return refcount 

990 

991 

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

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

994def test_activePoolPBDs(session, host, uuid): 

995 try: 

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

997 except: 

998 raise xs_errors.XenError('APIPBDQuery') 

999 for pbd in pbds: 

1000 record = pbds[pbd] 

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

1002 and record["currently_attached"]: 

1003 return True 

1004 return False 

1005 

1006 

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

1008 try: 

1009 pbdref = find_my_pbd(session, host_ref, sr_ref) 

1010 if pbdref is not None: 

1011 key = "mpath-" + SCSIid 

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

1013 except: 

1014 pass 

1015 

1016 

1017def kickpipe_mpathcount(): 

1018 """ 

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

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

1021 by a UDEV event. 

1022 """ 

1023 cmd = [CMD_KICKPIPE, "mpathcount"] 

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

1025 return (rc == 0) 

1026 

1027 

1028def _testHost(hostname, port, errstring): 

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

1030 try: 

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

1032 except: 

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

1034 raise xs_errors.XenError('DNSError') 

1035 

1036 timeout = 5 

1037 

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

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

1040 sock.settimeout(timeout) 

1041 try: 

1042 sock.connect(sockinfo[4]) 

1043 # Fix for MS storage server bug 

1044 sock.send(b'\n') 

1045 sock.close() 

1046 except socket.error as reason: 

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

1048 % (timeout, hostname, reason)) 

1049 raise xs_errors.XenError(errstring) 

1050 

1051 

1052def match_scsiID(s, id): 

1053 regex = re.compile(id) 

1054 return regex.search(s, 0) 

1055 

1056 

1057def _isSCSIid(s): 

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

1059 return regex.search(s, 0) 

1060 

1061 

1062def is_usb_device(device): 

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

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

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

1066 

1067 

1068def test_scsiserial(session, device): 

1069 device = os.path.realpath(device) 

1070 if not scsiutil._isSCSIdev(device): 

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

1072 return False 

1073 serial = "" 

1074 try: 

1075 serial += scsiutil.getserial(device) 

1076 except: 

1077 # Error allowed, SCSIid is the important one 

1078 pass 

1079 

1080 try: 

1081 scsiID = scsiutil.getSCSIid(device) 

1082 except: 

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

1084 % device) 

1085 return False 

1086 if not len(scsiID): 

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

1088 % device) 

1089 return False 

1090 

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

1092 try: 

1093 usb_device_with_serial = serial and is_usb_device(device) 

1094 except: 

1095 usb_device_with_serial = False 

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

1097 SMlog(traceback.format_exc()) 

1098 

1099 try: 

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

1101 except: 

1102 raise xs_errors.XenError('APIFailure') 

1103 for SR in SRs: 

1104 record = SRs[SR] 

1105 conf = record["sm_config"] 

1106 if 'devserial' in conf: 

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

1108 if not usb_device_with_serial and _isSCSIid(dev): 

1109 if match_scsiID(dev, scsiID): 

1110 return True 

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

1112 return True 

1113 return False 

1114 

1115 

1116def default(self, field, thunk): 

1117 try: 

1118 return getattr(self, field) 

1119 except: 

1120 return thunk() 

1121 

1122 

1123def list_VDI_records_in_sr(sr): 

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

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

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

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

1128 return vdis 

1129 

1130 

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

1132def diskFromPartition(partition): 

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

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

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

1136 return m.group(2) 

1137 

1138 numlen = 0 # number of digit characters 

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

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

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

1142 

1143 # is it a cciss? 

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

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

1146 

1147 # is it a mapper path? 

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

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

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

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

1152 else: 

1153 numlen = 0 

1154 

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

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

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

1158 

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

1160 

1161 

1162def dom0_disks(): 

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

1164 disks = [] 

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

1166 for line in f: 

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

1168 if mountpoint == '/': 

1169 disk = diskFromPartition(dev) 

1170 if not (disk in disks): 

1171 disks.append(disk) 

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

1173 return disks 

1174 

1175 

1176def set_scheduler_sysfs_node(node, scheds): 

1177 """ 

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

1179 according to prioritized list schedulers 

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

1181 """ 

1182 

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

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

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

1186 return 

1187 

1188 stored_error = None 

1189 for sched in scheds: 

1190 try: 

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

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

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

1194 return 

1195 except (OSError, IOError) as err: 

1196 stored_error = err 

1197 

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

1199 

1200 

1201def set_scheduler(dev, schedulers=None): 

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

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

1204 

1205 devices = [] 

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

1207 # Remove partition numbers 

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

1209 else: 

1210 rawdev = diskFromPartition(dev) 

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

1212 

1213 for d in devices: 

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

1215 

1216 

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

1218def _getVDIs(srobj): 

1219 VDIs = [] 

1220 try: 

1221 sr_ref = getattr(srobj, 'sr_ref') 

1222 except AttributeError: 

1223 return VDIs 

1224 

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

1226 for vdi in refs: 

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

1228 ref['vdi_ref'] = vdi 

1229 VDIs.append(ref) 

1230 return VDIs 

1231 

1232 

1233def _getVDI(srobj, vdi_uuid): 

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

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

1236 ref['vdi_ref'] = vdi 

1237 return ref 

1238 

1239 

1240def _convertDNS(name): 

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

1242 return addr 

1243 

1244 

1245def _containsVDIinuse(srobj): 

1246 VDIs = _getVDIs(srobj) 

1247 for vdi in VDIs: 

1248 if not vdi['managed']: 

1249 continue 

1250 sm_config = vdi['sm_config'] 

1251 if 'SRRef' in sm_config: 

1252 try: 

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

1254 for pbd in PBDs: 

1255 record = PBDs[pbd] 

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

1257 record["currently_attached"]: 

1258 return True 

1259 except: 

1260 pass 

1261 return False 

1262 

1263 

1264def isVDICommand(cmd): 

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

1266 "vdi_activate", "vdi_deactivate", 

1267 "vdi_epoch_begin", "vdi_epoch_end"]: 

1268 return True 

1269 else: 

1270 return False 

1271 

1272 

1273######################### 

1274# Daemon helper functions 

1275def p_id_fork(): 

1276 try: 

1277 p_id = os.fork() 

1278 except OSError as e: 

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

1280 sys.exit(-1) 

1281 

1282 if (p_id == 0): 

1283 os.setsid() 

1284 try: 

1285 p_id = os.fork() 

1286 except OSError as e: 

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

1288 sys.exit(-1) 

1289 if (p_id == 0): 

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

1291 os.umask(0) 

1292 else: 

1293 os._exit(0) 

1294 else: 

1295 os._exit(0) 

1296 

1297 

1298def daemon(): 

1299 p_id_fork() 

1300 # Query the max file descriptor parameter for this process 

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

1302 

1303 # Close any fds that are open 

1304 for fd in range(0, maxfd): 

1305 try: 

1306 os.close(fd) 

1307 except: 

1308 pass 

1309 

1310 # Redirect STDIN to STDOUT and STDERR 

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

1312 os.dup2(0, 1) 

1313 os.dup2(0, 2) 

1314 

1315################################################################################ 

1316# 

1317# Fist points 

1318# 

1319 

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

1321# 

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

1323# on the SR master; 

1324# 

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

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

1327# - otherwise, the function called is _pause. 

1328 

1329def _pause(secs, name): 

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

1331 time.sleep(secs) 

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

1333 

1334 

1335def _exit(name): 

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

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

1338 

1339 

1340class FistPoint: 

1341 def __init__(self, points): 

1342 #SMlog("Fist points loaded") 

1343 self.points = points 

1344 

1345 def is_legal(self, name): 

1346 return (name in self.points) 

1347 

1348 def is_active(self, name): 

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

1350 

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

1352 session = get_localAPI_session() 

1353 try: 

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

1355 

1356 if started: 

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

1358 else: 

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

1360 finally: 

1361 session.xenapi.session.logout() 

1362 

1363 def activate(self, name, sruuid): 

1364 if name in self.points: 

1365 if self.is_active(name): 

1366 self.mark_sr(name, sruuid, True) 

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

1368 self.mark_sr(name, sruuid, False) 

1369 _exit(name) 

1370 else: 

1371 _pause(FIST_PAUSE_PERIOD, name) 

1372 self.mark_sr(name, sruuid, False) 

1373 else: 

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

1375 

1376 def activate_custom_fn(self, name, fn): 

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

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

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

1380 fn() 

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

1382 else: 

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

1384 

1385 

1386def list_find(f, seq): 

1387 for item in seq: 

1388 if f(item): 

1389 return item 

1390 

1391GCPAUSE_FISTPOINT = "GCLoop_no_pause" 

1392 

1393fistpoint = FistPoint(["LVHDRT_finding_a_suitable_pair", 

1394 "LVHDRT_inflating_the_parent", 

1395 "LVHDRT_resizing_while_vdis_are_paused", 

1396 "LVHDRT_coalescing_VHD_data", 

1397 "LVHDRT_coalescing_before_inflate_grandparent", 

1398 "LVHDRT_relinking_grandchildren", 

1399 "LVHDRT_before_create_relink_journal", 

1400 "LVHDRT_xapiSM_serialization_tests", 

1401 "LVHDRT_clone_vdi_after_create_journal", 

1402 "LVHDRT_clone_vdi_after_shrink_parent", 

1403 "LVHDRT_clone_vdi_after_first_snap", 

1404 "LVHDRT_clone_vdi_after_second_snap", 

1405 "LVHDRT_clone_vdi_after_parent_hidden", 

1406 "LVHDRT_clone_vdi_after_parent_ro", 

1407 "LVHDRT_clone_vdi_before_remove_journal", 

1408 "LVHDRT_clone_vdi_after_lvcreate", 

1409 "LVHDRT_clone_vdi_before_undo_clone", 

1410 "LVHDRT_clone_vdi_after_undo_clone", 

1411 "LVHDRT_inflate_after_create_journal", 

1412 "LVHDRT_inflate_after_setSize", 

1413 "LVHDRT_inflate_after_zeroOut", 

1414 "LVHDRT_inflate_after_setSizePhys", 

1415 "LVHDRT_inflate_after_setSizePhys", 

1416 "LVHDRT_coaleaf_before_coalesce", 

1417 "LVHDRT_coaleaf_after_coalesce", 

1418 "LVHDRT_coaleaf_one_renamed", 

1419 "LVHDRT_coaleaf_both_renamed", 

1420 "LVHDRT_coaleaf_after_vdirec", 

1421 "LVHDRT_coaleaf_before_delete", 

1422 "LVHDRT_coaleaf_after_delete", 

1423 "LVHDRT_coaleaf_before_remove_j", 

1424 "LVHDRT_coaleaf_undo_after_rename", 

1425 "LVHDRT_coaleaf_undo_after_rename2", 

1426 "LVHDRT_coaleaf_undo_after_refcount", 

1427 "LVHDRT_coaleaf_undo_after_deflate", 

1428 "LVHDRT_coaleaf_undo_end", 

1429 "LVHDRT_coaleaf_stop_after_recovery", 

1430 "LVHDRT_coaleaf_finish_after_inflate", 

1431 "LVHDRT_coaleaf_finish_end", 

1432 "LVHDRT_coaleaf_delay_1", 

1433 "LVHDRT_coaleaf_delay_2", 

1434 "LVHDRT_coaleaf_delay_3", 

1435 "testsm_clone_allow_raw", 

1436 "xenrt_default_vdi_type_legacy", 

1437 "blktap_activate_inject_failure", 

1438 "blktap_activate_error_handling", 

1439 GCPAUSE_FISTPOINT, 

1440 "cleanup_coalesceVHD_inject_failure", 

1441 "cleanup_tracker_no_progress", 

1442 "FileSR_fail_hardlink", 

1443 "FileSR_fail_snap1", 

1444 "FileSR_fail_snap2", 

1445 "LVM_journaler_exists", 

1446 "LVM_journaler_none", 

1447 "LVM_journaler_badname", 

1448 "LVM_journaler_readfail", 

1449 "LVM_journaler_writefail"]) 

1450 

1451 

1452def set_dirty(session, sr): 

1453 try: 

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

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

1456 except: 

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

1458 

1459 

1460def doesFileHaveOpenHandles(fileName): 

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

1462 (retVal, processAndPidTuples) = \ 

1463 findRunningProcessOrOpenFile(fileName, False) 

1464 

1465 if not retVal: 

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

1467 fileName) 

1468 # err on the side of caution 

1469 return True 

1470 else: 

1471 if len(processAndPidTuples) > 0: 

1472 return True 

1473 else: 

1474 return False 

1475 

1476 

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

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

1479def extractSRFromDevMapper(path): 

1480 try: 

1481 path = os.path.basename(path) 

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

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

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

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

1486 except: 

1487 return '' 

1488 

1489 

1490def pid_is_alive(pid): 

1491 """ 

1492 Try to kill PID with signal 0. 

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

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

1495 signal it. Still return true. 

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

1497 """ 

1498 try: 

1499 os.kill(pid, 0) 

1500 return True 

1501 except OSError as e: 

1502 if e.errno == errno.EPERM: 

1503 return True 

1504 return False 

1505 

1506 

1507# Looks at /proc and figures either 

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

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

1510# returns process names and pids 

1511def findRunningProcessOrOpenFile(name, process=True): 

1512 retVal = True 

1513 links = [] 

1514 processandpids = [] 

1515 sockets = set() 

1516 try: 

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

1518 [name, process]) 

1519 

1520 # Look at all pids 

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

1522 for pid in sorted(pids): 

1523 try: 

1524 try: 

1525 f = None 

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

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

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

1529 # Just want the process name 

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

1531 prog = argv[0] 

1532 except IOError as e: 

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

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

1535 continue 

1536 finally: 

1537 if f is not None: 1537 ↛ 1522,   1537 ↛ 15402 missed branches: 1) line 1537 didn't jump to line 1522, because the continue on line 1535 wasn't executed, 2) line 1537 didn't jump to line 1540, because the condition on line 1537 was never false

1538 f.close() 1538 ↛ 1522line 1538 didn't jump to line 1522, because the continue on line 1535 wasn't executed

1539 

1540 try: 

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

1542 files = os.listdir(fd_dir) 

1543 except OSError as e: 

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

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

1546 # Ignore pid that are no longer valid 

1547 continue 

1548 else: 

1549 raise 

1550 

1551 for file in files: 

1552 try: 

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

1554 except OSError: 

1555 continue 

1556 

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

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

1559 links.append(link) 

1560 else: 

1561 # need to return process name and pid tuples 

1562 if link == name: 

1563 processandpids.append((prog, pid)) 

1564 

1565 # Get the connected sockets 

1566 if name == prog: 

1567 sockets.update(get_connected_sockets(pid)) 

1568 

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

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

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

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

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

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

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

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

1577 

1578 except Exception as e: 

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

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

1581 retVal = False 

1582 

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

1584 return retVal, links, sockets 

1585 else: 

1586 return retVal, processandpids 

1587 

1588 

1589def get_connected_sockets(pid): 

1590 sockets = set() 

1591 try: 

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

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

1594 # - Pointer address to socket (hex) 

1595 # - Refcount (HEX) 

1596 # - 0 

1597 # - State (HEX, 0 or __SO_ACCEPTCON) 

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

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

1600 # - Inode number 

1601 # - Path (optional) 

1602 open_sock_matcher = re.compile( 

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

1604 with open( 

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

1606 lines = f.readlines() 

1607 for line in lines: 

1608 match = open_sock_matcher.match(line) 

1609 if match: 

1610 sockets.add(match[1]) 

1611 except OSError as e: 

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

1613 # Ignore pid that are no longer valid 

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

1615 (e.errno, pid)) 

1616 else: 

1617 raise 

1618 return sockets 

1619 

1620 

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

1622 retries = 0 

1623 while True: 

1624 try: 

1625 return f() 

1626 except Exception as e: 

1627 for exception in exceptions: 

1628 if isinstance(e, exception): 

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

1630 str(e), retries 

1631 )) 

1632 break 

1633 else: 

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

1635 raise e 

1636 

1637 retries += 1 

1638 if retries >= maxretry: 

1639 break 

1640 

1641 time.sleep(period) 

1642 

1643 return f() 

1644 

1645 

1646def getCslDevPath(svid): 

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

1648 if svid.startswith("NETAPP_"): 

1649 # special attention for NETAPP SVIDs 

1650 svid_parts = svid.split("__") 

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

1652 else: 

1653 globstr = basepath + svid + "*" 

1654 

1655 return globstr 

1656 

1657 

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

1659def get_scsiid_from_svid(md_svid): 

1660 cslg_path = getCslDevPath(md_svid) 

1661 abs_path = glob.glob(cslg_path) 

1662 if abs_path: 

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

1664 return scsiutil.getSCSIid(real_path) 

1665 else: 

1666 return None 

1667 

1668 

1669def get_isl_scsiids(session): 

1670 # Get cslg type SRs 

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

1672 

1673 # Iterate through the SR to get the scsi ids 

1674 scsi_id_ret = [] 

1675 for SR in SRs: 

1676 sr_rec = SRs[SR] 

1677 # Use the md_svid to get the scsi id 

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

1679 if scsi_id: 

1680 scsi_id_ret.append(scsi_id) 

1681 

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

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

1684 for vdi_rec in vdi_recs: 

1685 vdi_rec = vdi_recs[vdi_rec] 

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

1687 if scsi_id: 

1688 scsi_id_ret.append(scsi_id) 

1689 

1690 return scsi_id_ret 

1691 

1692 

1693class extractXVA: 

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

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

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

1697 HDR_SIZE = 512 

1698 BLOCK_SIZE = 512 

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

1700 SIZE_OFFSET = 124 

1701 ZERO_FILLED_REC = 2 

1702 NULL_IDEN = '\x00' 

1703 DIR_IDEN = '/' 

1704 CHECKSUM_IDEN = '.checksum' 

1705 OVA_FILE = 'ova.xml' 

1706 

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

1708 # as and when needed 

1709 def __init__(self, filename): 

1710 self.__extract_path = '' 

1711 self.__filename = filename 

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

1713 try: 

1714 self.spawn_p = subprocess.Popen( 

1715 cmd, shell=True, \ 

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

1717 stderr=subprocess.PIPE, close_fds=True) 

1718 except Exception as e: 

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

1720 raise Exception(str(e)) 

1721 

1722 # Create dir to extract the files 

1723 self.__extract_path = tempfile.mkdtemp() 

1724 

1725 def __del__(self): 

1726 shutil.rmtree(self.__extract_path) 

1727 

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

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

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

1731 def getTuple(self): 

1732 zerod_record = 0 

1733 ret_f_name = '' 

1734 ret_base_f_name = '' 

1735 

1736 try: 

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

1738 while True: 

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

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

1741 

1742 # Break out in case of end of file 

1743 if f_hdr == '': 

1744 if zerod_record == extractXVA.ZERO_FILLED_REC: 

1745 break 

1746 else: 

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

1748 extractXVA.ZERO_FILLED_REC) 

1749 raise Exception('Unrecognized end of file') 

1750 

1751 # Watch out for zero records, two zero records 

1752 # denote end of file. 

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

1754 zerod_record += 1 

1755 continue 

1756 

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

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

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

1760 f_size_octal = f_hdr[extractXVA.SIZE_OFFSET: \ 

1761 extractXVA.SIZE_OFFSET + extractXVA.SIZE_LEN] 

1762 f_size = int(f_size_octal, 8) 

1763 if f_name.endswith(extractXVA.CHECKSUM_IDEN): 

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

1765 ret_base_f_name: 

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

1767 yield(ret_f_name, checksum) 

1768 else: 

1769 # Expects file followed by its checksum 

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

1771 ret_f_name) 

1772 raise Exception( \ 

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

1774 ret_f_name) 

1775 else: 

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

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

1778 # read the contents into a file, it will 

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

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

1781 ret_base_f_name = f_name 

1782 

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

1784 # else create it. 

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

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

1787 os.mkdir(folder_path) 

1788 

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

1790 f = open(ret_f_name, 'w') 

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

1792 f.close() 

1793 if f_name == extractXVA.OVA_FILE: 

1794 yield(ret_f_name, '') 

1795 

1796 # Skip zero'd portion of data block 

1797 round_off = f_size % extractXVA.BLOCK_SIZE 

1798 if round_off != 0: 

1799 zeros = self.spawn_p.stdout.read( 

1800 extractXVA.BLOCK_SIZE - round_off) 

1801 except Exception as e: 

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

1803 self.__filename)) 

1804 

1805 # Kill and Drain stdout of the gunzip process, 

1806 # else gunzip might block on stdout 

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

1808 self.spawn_p.communicate() 

1809 raise Exception(str(e)) 

1810 

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

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

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

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

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

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

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

1818 (0x10FFFE, 0x10FFFF)] 

1819 

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

1821 for (low, high) in illegal_xml_chars 

1822 if low < sys.maxunicode] 

1823 

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

1825 

1826 

1827def isLegalXMLString(s): 

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

1829 illegal XML characters specified in 

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

1831 """ 

1832 

1833 if len(s) > 0: 

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

1835 else: 

1836 return True 

1837 

1838 

1839def unictrunc(string, max_bytes): 

1840 """ 

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

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

1843 more than the given number of bytes. 

1844 

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

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

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

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

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

1850 

1851 string: the string to truncate 

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

1853 """ 

1854 if isinstance(string, str): 

1855 return_chars = True 

1856 else: 

1857 return_chars = False 

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

1859 

1860 cur_chars = 0 

1861 cur_bytes = 0 

1862 for char in string: 

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

1864 if cur_bytes + charsize > max_bytes: 

1865 break 

1866 else: 

1867 cur_chars += 1 

1868 cur_bytes += charsize 

1869 return cur_chars if return_chars else cur_bytes 

1870 

1871 

1872def hideValuesInPropMap(propmap, propnames): 

1873 """ 

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

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

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

1877 value and return the altered map. 

1878 If none found, return the original map 

1879 """ 

1880 matches = [] 

1881 for propname in propnames: 

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

1883 matches.append(propname) 

1884 

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

1886 deepCopyRec = copy.deepcopy(propmap) 

1887 for match in matches: 

1888 deepCopyRec[match] = '******' 

1889 return deepCopyRec 

1890 

1891 return propmap 

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

1893 

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

1895DEFAULT_SEGMENT_LEN = 950 

1896 

1897 

1898def hidePasswdInConfig(config): 

1899 """ 

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

1901 for example "device_config" 

1902 """ 

1903 return hideValuesInPropMap(config, PASSWD_PROP_KEYS) 

1904 

1905 

1906def hidePasswdInParams(params, configProp): 

1907 """ 

1908 Function to hide password values in a specified property which 

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

1910 in a larger property map. 

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

1912 "sm_config", etc 

1913 """ 

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

1915 return params 

1916 

1917 

1918def hideMemberValuesInXmlParams(xmlParams, propnames=PASSWD_PROP_KEYS): 

1919 """ 

1920 Function to hide password values in XML params, specifically 

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

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

1923 whose values we want to hide, and: 

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

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

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

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

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

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

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

1931 """ 

1932 findStrPrefixHead = "<member><name>" 

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

1934 findStrSuffix = "</value>" 

1935 strlen = len(xmlParams) 

1936 

1937 for propname in propnames: 

1938 findStrPrefix = findStrPrefixHead + propname + findStrPrefixTail 

1939 idx = xmlParams.find(findStrPrefix) 

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

1941 idx += len(findStrPrefix) 

1942 idx2 = xmlParams.find(findStrSuffix, idx) 

1943 if idx2 != -1: 

1944 retStr = xmlParams[0:idx] 

1945 retStr += "******" 

1946 retStr += xmlParams[idx2:strlen] 

1947 return retStr 

1948 else: 

1949 return xmlParams 

1950 return xmlParams 

1951 

1952 

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

1954 """ 

1955 Split xml string data into substrings small enough for the 

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

1957 Usage: 

1958 strList = [] 

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

1960 """ 

1961 remainingData = str(xmlData) 

1962 

1963 # "Un-pretty-print" 

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

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

1966 

1967 remainingChars = len(remainingData) 

1968 returnData = '' 

1969 

1970 thisLineNum = 0 

1971 while remainingChars > segmentLen: 

1972 thisLineNum = thisLineNum + 1 

1973 index = segmentLen 

1974 tmpStr = remainingData[:segmentLen] 

1975 tmpIndex = tmpStr.rfind('>') 

1976 if tmpIndex != -1: 

1977 index = tmpIndex + 1 

1978 

1979 tmpStr = tmpStr[:index] 

1980 remainingData = remainingData[index:] 

1981 remainingChars = len(remainingData) 

1982 

1983 if showContd: 

1984 if thisLineNum != 1: 

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

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

1987 

1988 returnData += tmpStr + '\n' 

1989 

1990 if showContd and thisLineNum > 0: 

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

1992 returnData += remainingData 

1993 

1994 return returnData 

1995 

1996 

1997def inject_failure(): 

1998 raise Exception('injected failure') 

1999 

2000 

2001def open_atomic(path, mode=None): 

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

2003 Leaves the file open and returns the file object. 

2004 

2005 path: the path to atomically open 

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

2007 returns: an open file object""" 

2008 

2009 assert path 

2010 

2011 flags = os.O_CREAT | os.O_EXCL 

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

2013 if mode: 

2014 if mode not in modes: 

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

2016 flags |= modes[mode] 

2017 fd = os.open(path, flags) 

2018 try: 

2019 if mode: 

2020 return os.fdopen(fd, mode) 

2021 else: 

2022 return os.fdopen(fd) 

2023 except: 

2024 os.close(fd) 

2025 raise 

2026 

2027 

2028def isInvalidVDI(exception): 

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

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

2031 

2032 

2033def get_pool_restrictions(session): 

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

2035 established.""" 

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

2037 

2038 

2039def read_caching_is_restricted(session): 

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

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

2042 return True 

2043 restrictions = get_pool_restrictions(session) 

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

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

2046 return True 

2047 return False 

2048 

2049 

2050def sessions_less_than_targets(other_config, device_config): 

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

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

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

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

2055 return (sessions < targets) 

2056 else: 

2057 return False 

2058 

2059 

2060def enable_and_start_service(name, start): 

2061 attempt = 0 

2062 while True: 

2063 attempt += 1 

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

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

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

2067 if ret == 0: 

2068 return 

2069 elif attempt >= 3: 

2070 raise Exception( 

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

2072 ) 

2073 time.sleep(1) 

2074 

2075 

2076def stop_service(name): 

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

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

2079 if ret == 0: 

2080 return 

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

2082 

2083 

2084def restart_service(name): 

2085 attempt = 0 

2086 while True: 

2087 attempt += 1 

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

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

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

2091 if ret == 0: 

2092 return 

2093 elif attempt >= 3: 

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

2095 raise Exception( 

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

2097 ) 

2098 time.sleep(1) 

2099 

2100 

2101def check_pid_exists(pid): 

2102 try: 

2103 os.kill(pid, 0) 

2104 except OSError: 

2105 return False 

2106 else: 

2107 return True 

2108 

2109 

2110def make_profile(name, function): 

2111 """ 

2112 Helper to execute cProfile using unique log file. 

2113 """ 

2114 

2115 import cProfile 

2116 import itertools 

2117 import os.path 

2118 import time 

2119 

2120 assert name 

2121 assert function 

2122 

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

2124 makedirs(FOLDER) 

2125 

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

2127 

2128 def gen_path(path): 

2129 yield path 

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

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

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

2133 

2134 for profile_path in gen_path(FOLDER + filename): 

2135 try: 

2136 file = open_atomic(profile_path, 'w') 

2137 file.close() 

2138 break 

2139 except OSError as e: 

2140 if e.errno == errno.EEXIST: 

2141 pass 

2142 else: 

2143 raise 

2144 

2145 try: 

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

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

2148 finally: 

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

2150 

2151 

2152def strtobool(str): 

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

2154 # There is not alternative for strtobool. 

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

2156 # So this is a custom implementation with differences: 

2157 # - A boolean is returned instead of integer 

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

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

2160 return False 

2161 str = str.lower() 

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

2163 return True 

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

2165 return False 

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

2167 

2168 

2169def find_executable(name): 

2170 return shutil.which(name) 

2171 

2172 

2173def conditional_decorator(decorator, condition): 

2174 def wrapper(func): 

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

2176 return func 

2177 return decorator(func) 

2178 return wrapper