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 socket 

30import xml.dom.minidom 

31import scsiutil 

32import stat 

33import xs_errors 

34import XenAPI # pylint: disable=import-error 

35import xmlrpc.client 

36import base64 

37import syslog 

38import resource 

39import traceback 

40import glob 

41import copy 

42import tempfile 

43 

44from functools import reduce 

45from sm_typing import List, Optional 

46 

47NO_LOGGING_STAMPFILE = '/etc/xensource/no_sm_log' 

48 

49IORETRY_MAX = 20 # retries 

50IORETRY_PERIOD = 1.0 # seconds 

51 

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

53_SM_SYSLOG_FACILITY = syslog.LOG_LOCAL2 

54LOG_EMERG = syslog.LOG_EMERG 

55LOG_ALERT = syslog.LOG_ALERT 

56LOG_CRIT = syslog.LOG_CRIT 

57LOG_ERR = syslog.LOG_ERR 

58LOG_WARNING = syslog.LOG_WARNING 

59LOG_NOTICE = syslog.LOG_NOTICE 

60LOG_INFO = syslog.LOG_INFO 

61LOG_DEBUG = syslog.LOG_DEBUG 

62 

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

64 

65CMD_DD = "/bin/dd" 

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

67 

68FIST_PAUSE_PERIOD = 30 # seconds 

69 

70 

71class SMException(Exception): 

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

73 XenError""" 

74 

75 

76class CommandException(SMException): 

77 def error_message(self, code): 

78 if code > 0: 

79 return os.strerror(code) 

80 elif code < 0: 

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

82 return "Success" 

83 

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

85 self.code = code 

86 self.cmd = cmd 

87 self.reason = reason 

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

89 

90 

91class SRBusyException(SMException): 

92 """The SR could not be locked""" 

93 pass 

94 

95 

96def logException(tag): 

97 info = sys.exc_info() 

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

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

100 sys.exit(0) 

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

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

103 SMlog(str) 

104 

105 

106def roundup(divisor, value): 

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

108 

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

110 value = 1 

111 if value % divisor != 0: 

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

113 return value 

114 

115 

116def to_plain_string(obj): 

117 if obj is None: 

118 return None 

119 if type(obj) == str: 

120 return obj 

121 return str(obj) 

122 

123 

124def shellquote(arg): 

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

126 

127 

128def make_WWN(name): 

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

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

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

132 # inject dashes for each nibble 

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

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

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

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

137 return name 

138 

139 

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

141 syslog.openlog(ident, 0, facility) 

142 syslog.syslog(priority, "[%d] %s" % (os.getpid(), message)) 

143 syslog.closelog() 

144 

145 

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

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

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

149 _logToSyslog(ident, _SM_SYSLOG_FACILITY, priority, message_line) 

150 

151 

152class LoggerCounter: 

153 def __init__(self, max_repeats): 

154 self.previous_message = None 

155 self.max_repeats = max_repeats 

156 self.repeat_counter = 0 

157 

158 def log(self, message): 

159 self.repeat_counter += 1 

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

161 SMlog(message) 

162 self.previous_message = message 

163 self.repeat_counter = 0 

164 

165def _getDateString(): 

166 d = datetime.datetime.now() 

167 t = d.timetuple() 

168 return "%s-%s-%s:%s:%s:%s" % \ 

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

170 

171 

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

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

174 env = None 

175 if new_env: 

176 env = dict(os.environ) 

177 env.update(new_env) 

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

179 stdout=subprocess.PIPE, 

180 stderr=subprocess.PIPE, 

181 close_fds=True, env=env, 

182 universal_newlines=text) 

183 

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

185 inputtext = inputtext.encode() 

186 

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

188 

189 rc = proc.returncode 

190 return rc, stdout, stderr 

191 

192 

193def is_string(value): 

194 return isinstance(value, str) 

195 

196 

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

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

199# replace the original ones at some later date. 

200# 

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

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

203# written to the logs. 

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

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

206 cmdlist_for_exec = [] 

207 cmdlist_for_log = [] 

208 for item in cmdlist: 

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

210 cmdlist_for_exec.append(item) 

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

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

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

214 else: 

215 cmdlist_for_log.append(item) 

216 else: 

217 cmdlist_for_log.append(item) 

218 else: 

219 cmdlist_for_exec.append(item[0]) 

220 cmdlist_for_log.append(item[1]) 

221 

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

223 SMlog(cmdlist_for_log) 

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

225 if rc != expect_rc: 

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

227 (rc, stdout, stderr)) 

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

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

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

231 stderr = stdout 

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

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

234 SMlog(" pread SUCCESS") 

235 return stdout 

236 

237 

238# POSIX guaranteed atomic within the same file system. 

239# Supply directory to ensure tempfile is created 

240# in the same directory. 

241def atomicFileWrite(targetFile, directory, text): 

242 

243 file = None 

244 try: 

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

246 # our responsibility to clean it up. 

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

248 file = open(tempPath, 'w') 

249 file.write(text) 

250 

251 # Ensure flushed to disk. 

252 file.flush() 

253 os.fsync(file.fileno()) 

254 file.close() 

255 

256 os.rename(tempPath, targetFile) 

257 except OSError: 

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

259 

260 finally: 

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

262 file.close() 

263 

264 if os.path.isfile(tempPath): 

265 os.remove(tempPath) 

266 

267 

268#Read STDOUT from cmdlist and discard STDERR output 

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

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

271 

272 

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

274def pread3(cmdlist, text): 

275 SMlog(cmdlist) 

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

277 if rc: 

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

279 (rc, stdout, stderr)) 

280 if '' == stderr: 

281 stderr = stdout 

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

283 SMlog(" pread3 SUCCESS") 

284 return stdout 

285 

286 

287def listdir(path, quiet=False): 

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

289 try: 

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

291 if len(text) == 0: 

292 return [] 

293 return text.split('\n') 

294 except CommandException as inst: 

295 if inst.code == errno.ENOENT: 

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

297 else: 

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

299 

300 

301def gen_uuid(): 

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

303 return pread(cmd)[:-1] 

304 

305 

306def match_uuid(s): 

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

308 return regex.search(s, 0) 

309 

310 

311def findall_uuid(s): 

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

313 return regex.findall(s, 0) 

314 

315 

316def exactmatch_uuid(s): 

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

318 return regex.search(s, 0) 

319 

320 

321def start_log_entry(srpath, path, args): 

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

323 logstring += " log: " 

324 logstring += srpath 

325 logstring += " " + path 

326 for element in args: 

327 logstring += " " + element 

328 try: 

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

330 file.write(logstring) 

331 file.write("\n") 

332 file.close() 

333 except: 

334 pass 

335 

336 # failed to write log ... 

337 

338def end_log_entry(srpath, path, args): 

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

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

341 logstring += " end: " 

342 logstring += srpath 

343 logstring += " " + path 

344 for element in args: 

345 logstring += " " + element 

346 try: 

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

348 file.write(logstring) 

349 file.write("\n") 

350 file.close() 

351 except: 

352 pass 

353 

354 # failed to write log ... 

355 # for now print 

356 # print "%s" % logstring 

357 

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

359 retries = 0 

360 while True: 

361 try: 

362 return f() 

363 except OSError as ose: 

364 err = int(ose.errno) 

365 if not err in errlist: 

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

367 except CommandException as ce: 

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

369 raise 

370 

371 retries += 1 

372 if retries >= maxretry: 

373 break 

374 

375 time.sleep(period) 

376 

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

378 

379 

380def ioretry_stat(path, maxretry=IORETRY_MAX): 

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

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

383 retries = 0 

384 while retries < maxretry: 

385 stat = os.statvfs(path) 

386 if stat.f_blocks != -1: 

387 return stat 

388 time.sleep(1) 

389 retries += 1 

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

391 

392 

393def sr_get_capability(sr_uuid, session=None): 

394 result = [] 

395 local_session = None 

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

397 local_session = get_localAPI_session() 

398 session = local_session 

399 

400 try: 

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

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

403 sm_rec = session.xenapi.SM.get_all_records_where( 

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

405 

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

407 if len(sm_rec) > 0: 

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

409 

410 return result 

411 finally: 

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

413 local_session.xenapi.session.logout() 

414 

415def sr_get_driver_info(driver_info): 

416 results = {} 

417 # first add in the vanilla stuff 

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

419 'driver_version', 'required_api_version']: 

420 results[key] = driver_info[key] 

421 # add the capabilities (xmlrpc array) 

422 # enforcing activate/deactivate for blktap2 

423 caps = driver_info['capabilities'] 

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

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

426 if not cap in caps: 

427 caps.append(cap) 

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

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

430 

431 results['capabilities'] = caps 

432 # add in the configuration options 

433 options = [] 

434 for option in driver_info['configuration']: 

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

436 results['configuration'] = options 

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

438 

439 

440def return_nil(): 

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

442 

443 

444def SRtoXML(SRlist): 

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

446 driver = dom.createElement("SRlist") 

447 dom.appendChild(driver) 

448 

449 for key in SRlist.keys(): 

450 dict = SRlist[key] 

451 entry = dom.createElement("SR") 

452 driver.appendChild(entry) 

453 

454 e = dom.createElement("UUID") 

455 entry.appendChild(e) 

456 textnode = dom.createTextNode(key) 

457 e.appendChild(textnode) 

458 

459 if 'size' in dict: 

460 e = dom.createElement("Size") 

461 entry.appendChild(e) 

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

463 e.appendChild(textnode) 

464 

465 if 'storagepool' in dict: 

466 e = dom.createElement("StoragePool") 

467 entry.appendChild(e) 

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

469 e.appendChild(textnode) 

470 

471 if 'aggregate' in dict: 

472 e = dom.createElement("Aggregate") 

473 entry.appendChild(e) 

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

475 e.appendChild(textnode) 

476 

477 return dom.toprettyxml() 

478 

479 

480def pathexists(path): 

481 try: 

482 os.lstat(path) 

483 return True 

484 except OSError as inst: 

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

486 time.sleep(1) 

487 try: 

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

489 os.lstat(path) 

490 return True 

491 except: 

492 pass 

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

494 return False 

495 

496 

497def force_unlink(path): 

498 try: 

499 os.unlink(path) 

500 except OSError as e: 

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

502 raise 

503 

504 

505def create_secret(session, secret): 

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

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

508 

509 

510def get_secret(session, uuid): 

511 try: 

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

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

514 except: 

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

516 

517 

518def get_real_path(path): 

519 "Follow symlinks to the actual file" 

520 absPath = path 

521 directory = '' 

522 while os.path.islink(absPath): 

523 directory = os.path.dirname(absPath) 

524 absPath = os.readlink(absPath) 

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

526 return absPath 

527 

528 

529def wait_for_path(path, timeout): 

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

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

532 return True 

533 time.sleep(1) 

534 return False 

535 

536 

537def wait_for_nopath(path, timeout): 

538 for i in range(0, timeout): 

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

540 return True 

541 time.sleep(1) 

542 return False 

543 

544 

545def wait_for_path_multi(path, timeout): 

546 for i in range(0, timeout): 

547 paths = glob.glob(path) 

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

549 if len(paths): 

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

551 return paths[0] 

552 time.sleep(1) 

553 return "" 

554 

555 

556def isdir(path): 

557 try: 

558 st = os.stat(path) 

559 return stat.S_ISDIR(st.st_mode) 

560 except OSError as inst: 

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

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

563 return False 

564 

565 

566def get_single_entry(path): 

567 f = open(path, 'r') 

568 line = f.readline() 

569 f.close() 

570 return line.rstrip() 

571 

572 

573def get_fs_size(path): 

574 st = ioretry_stat(path) 

575 return st.f_blocks * st.f_frsize 

576 

577 

578def get_fs_utilisation(path): 

579 st = ioretry_stat(path) 

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

581 st.f_frsize 

582 

583 

584def ismount(path): 

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

586 try: 

587 s1 = os.stat(path) 

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

589 except OSError as inst: 

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

591 dev1 = s1.st_dev 

592 dev2 = s2.st_dev 

593 if dev1 != dev2: 

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

595 ino1 = s1.st_ino 

596 ino2 = s2.st_ino 

597 if ino1 == ino2: 

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

599 return False 

600 

601 

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

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

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

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

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

607 makedirs(head, mode) 

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

609 return 

610 try: 

611 os.mkdir(name, mode) 

612 except OSError as exc: 

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

614 if mode: 

615 os.chmod(name, mode) 

616 pass 

617 else: 

618 raise 

619 

620 

621def zeroOut(path, fromByte, bytes): 

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

623 blockSize = 4096 

624 

625 fromBlock = fromByte // blockSize 

626 if fromByte % blockSize: 

627 fromBlock += 1 

628 bytesBefore = fromBlock * blockSize - fromByte 

629 if bytesBefore > bytes: 

630 bytesBefore = bytes 

631 bytes -= bytesBefore 

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

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

634 try: 

635 pread2(cmd) 

636 except CommandException: 

637 return False 

638 

639 blocks = bytes // blockSize 

640 bytes -= blocks * blockSize 

641 fromByte = (fromBlock + blocks) * blockSize 

642 if blocks: 

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

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

645 try: 

646 pread2(cmd) 

647 except CommandException: 

648 return False 

649 

650 if bytes: 

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

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

653 try: 

654 pread2(cmd) 

655 except CommandException: 

656 return False 

657 

658 return True 

659 

660 

661def wipefs(blockdev): 

662 "Wipe filesystem signatures from `blockdev`" 

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

664 

665 

666def match_rootdev(s): 

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

668 return regex.search(s, 0) 

669 

670 

671def getrootdev(): 

672 filename = '/etc/xensource-inventory' 

673 try: 

674 f = open(filename, 'r') 

675 except: 

676 raise xs_errors.XenError('EIO', \ 

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

678 rootdev = '' 

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

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

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

682 raise xs_errors.XenError('NoRootDev') 

683 return rootdev 

684 

685 

686def getrootdevID(): 

687 rootdev = getrootdev() 

688 try: 

689 rootdevID = scsiutil.getSCSIid(rootdev) 

690 except: 

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

692 % rootdev) 

693 return '' 

694 

695 if not len(rootdevID): 

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

697 % rootdev) 

698 

699 return rootdevID 

700 

701 

702def get_localAPI_session(): 

703 # First acquire a valid session 

704 session = XenAPI.xapi_local() 

705 try: 

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

707 except: 

708 raise xs_errors.XenError('APISession') 

709 return session 

710 

711 

712def get_this_host(): 

713 uuid = None 

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

715 for line in f.readlines(): 

716 if line.startswith("INSTALLATION_UUID"): 

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

718 f.close() 

719 return uuid 

720 

721 

722def get_master_ref(session): 

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

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

725 

726 

727def is_master(session): 

728 return get_this_host_ref(session) == get_master_ref(session) 

729 

730 

731def get_localhost_ref(session): 

732 filename = '/etc/xensource-inventory' 

733 try: 

734 f = open(filename, 'r') 

735 except: 

736 raise xs_errors.XenError('EIO', \ 

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

738 domid = '' 

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

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

741 if not domid: 

742 raise xs_errors.XenError('APILocalhost') 

743 

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

745 for vm in vms: 

746 record = vms[vm] 

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

748 hostid = record["resident_on"] 

749 return hostid 

750 raise xs_errors.XenError('APILocalhost') 

751 

752 

753def match_domain_id(s): 

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

755 return regex.search(s, 0) 

756 

757 

758def get_hosts_attached_on(session, vdi_uuids): 

759 host_refs = {} 

760 for vdi_uuid in vdi_uuids: 

761 try: 

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

763 except XenAPI.Failure: 

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

765 continue 

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

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

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

769 return host_refs.keys() 

770 

771def get_this_host_address(session): 

772 host_uuid = get_this_host() 

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

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

775 

776def get_host_addresses(session): 

777 addresses = [] 

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

779 for record in hosts.values(): 

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

781 return addresses 

782 

783def get_this_host_ref(session): 

784 host_uuid = get_this_host() 

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

786 return host_ref 

787 

788 

789def get_slaves_attached_on(session, vdi_uuids): 

790 "assume this host is the SR master" 

791 host_refs = get_hosts_attached_on(session, vdi_uuids) 

792 master_ref = get_this_host_ref(session) 

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

794 

795def get_enabled_hosts(session): 

796 """ 

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

798 """ 

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

800 

801def get_online_hosts(session): 

802 online_hosts = [] 

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

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

805 metricsRef = host_rec["metrics"] 

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

807 if metrics["live"]: 

808 online_hosts.append(host_ref) 

809 return online_hosts 

810 

811 

812def get_all_slaves(session): 

813 "assume this host is the SR master" 

814 host_refs = get_online_hosts(session) 

815 master_ref = get_this_host_ref(session) 

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

817 

818 

819def is_attached_rw(sm_config): 

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

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

822 return True 

823 return False 

824 

825 

826def attached_as(sm_config): 

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

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

829 return val 

830 

831 

832def find_my_pbd_record(session, host_ref, sr_ref): 

833 try: 

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

835 for pbd_ref in pbds.keys(): 

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

837 return [pbd_ref, pbds[pbd_ref]] 

838 return None 

839 except Exception as e: 

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

841 return None 

842 

843 

844def find_my_pbd(session, host_ref, sr_ref): 

845 ret = find_my_pbd_record(session, host_ref, sr_ref) 

846 if ret is not None: 

847 return ret[0] 

848 else: 

849 return None 

850 

851 

852def test_hostPBD_devs(session, sr_uuid, devs): 

853 host = get_localhost_ref(session) 

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

855 try: 

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

857 except: 

858 raise xs_errors.XenError('APIPBDQuery') 

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

860 for pbd in pbds: 

861 record = pbds[pbd] 

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

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

864 break 

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

866 devconfig = record["device_config"] 

867 if 'device' in devconfig: 

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

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

870 return True 

871 return False 

872 

873 

874def test_hostPBD_lun(session, targetIQN, LUNid): 

875 host = get_localhost_ref(session) 

876 try: 

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

878 except: 

879 raise xs_errors.XenError('APIPBDQuery') 

880 for pbd in pbds: 

881 record = pbds[pbd] 

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

883 devconfig = record["device_config"] 

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

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

886 devconfig['LUNid'] == LUNid: 

887 return True 

888 return False 

889 

890 

891def test_SCSIid(session, sr_uuid, SCSIid): 

892 if sr_uuid is not None: 

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

894 try: 

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

896 except: 

897 raise xs_errors.XenError('APIPBDQuery') 

898 for pbd in pbds: 

899 record = pbds[pbd] 

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

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

902 if sr_uuid is not None: 

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

904 break 

905 devconfig = record["device_config"] 

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

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

908 return True 

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

910 return True 

911 elif 'scsi-' + SCSIid in sm_config: 

912 return True 

913 return False 

914 

915 

916class TimeoutException(SMException): 

917 pass 

918 

919 

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

921 def handler(signum, frame): 

922 raise TimeoutException() 

923 signal.signal(signal.SIGALRM, handler) 

924 signal.alarm(timeoutseconds) 

925 try: 

926 return function(*arguments) 

927 finally: 

928 signal.alarm(0) 

929 

930 

931def _incr_iscsiSR_refcount(targetIQN, uuid): 

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

933 os.mkdir(ISCSI_REFDIR) 

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

935 try: 

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

937 except: 

938 raise xs_errors.XenError('LVMRefCount', \ 

939 opterr='file %s' % filename) 

940 

941 f.seek(0) 

942 found = False 

943 refcount = 0 

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

945 refcount += 1 

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

947 found = True 

948 if not found: 

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

950 refcount += 1 

951 f.close() 

952 return refcount 

953 

954 

955def _decr_iscsiSR_refcount(targetIQN, uuid): 

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

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

958 return 0 

959 try: 

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

961 except: 

962 raise xs_errors.XenError('LVMRefCount', \ 

963 opterr='file %s' % filename) 

964 

965 f.seek(0) 

966 output = [] 

967 refcount = 0 

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

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

970 output.append(line.rstrip()) 

971 refcount += 1 

972 if not refcount: 

973 os.unlink(filename) 

974 return refcount 

975 

976 # Re-open file and truncate 

977 f.close() 

978 f = open(filename, 'w') 

979 for i in range(0, refcount): 

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

981 f.close() 

982 return refcount 

983 

984 

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

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

987def test_activePoolPBDs(session, host, uuid): 

988 try: 

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

990 except: 

991 raise xs_errors.XenError('APIPBDQuery') 

992 for pbd in pbds: 

993 record = pbds[pbd] 

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

995 and record["currently_attached"]: 

996 return True 

997 return False 

998 

999 

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

1001 try: 

1002 pbdref = find_my_pbd(session, host_ref, sr_ref) 

1003 if pbdref is not None: 

1004 key = "mpath-" + SCSIid 

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

1006 except: 

1007 pass 

1008 

1009 

1010def kickpipe_mpathcount(): 

1011 """ 

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

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

1014 by a UDEV event. 

1015 """ 

1016 cmd = [CMD_KICKPIPE, "mpathcount"] 

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

1018 return (rc == 0) 

1019 

1020 

1021def _testHost(hostname, port, errstring): 

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

1023 try: 

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

1025 except: 

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

1027 raise xs_errors.XenError('DNSError') 

1028 

1029 timeout = 5 

1030 

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

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

1033 sock.settimeout(timeout) 

1034 try: 

1035 sock.connect(sockinfo[4]) 

1036 # Fix for MS storage server bug 

1037 sock.send(b'\n') 

1038 sock.close() 

1039 except socket.error as reason: 

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

1041 % (timeout, hostname, reason)) 

1042 raise xs_errors.XenError(errstring) 

1043 

1044 

1045def match_scsiID(s, id): 

1046 regex = re.compile(id) 

1047 return regex.search(s, 0) 

1048 

1049 

1050def _isSCSIid(s): 

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

1052 return regex.search(s, 0) 

1053 

1054 

1055def test_scsiserial(session, device): 

1056 device = os.path.realpath(device) 

1057 if not scsiutil._isSCSIdev(device): 

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

1059 return False 

1060 serial = "" 

1061 try: 

1062 serial += scsiutil.getserial(device) 

1063 except: 

1064 # Error allowed, SCSIid is the important one 

1065 pass 

1066 

1067 try: 

1068 scsiID = scsiutil.getSCSIid(device) 

1069 except: 

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

1071 % device) 

1072 return False 

1073 if not len(scsiID): 

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

1075 % device) 

1076 return False 

1077 

1078 try: 

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

1080 except: 

1081 raise xs_errors.XenError('APIFailure') 

1082 for SR in SRs: 

1083 record = SRs[SR] 

1084 conf = record["sm_config"] 

1085 if 'devserial' in conf: 

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

1087 if _isSCSIid(dev): 

1088 if match_scsiID(dev, scsiID): 

1089 return True 

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

1091 return True 

1092 return False 

1093 

1094 

1095def default(self, field, thunk): 

1096 try: 

1097 return getattr(self, field) 

1098 except: 

1099 return thunk() 

1100 

1101 

1102def list_VDI_records_in_sr(sr): 

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

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

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

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

1107 return vdis 

1108 

1109 

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

1111def diskFromPartition(partition): 

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

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

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

1115 return m.group(2) 

1116 

1117 numlen = 0 # number of digit characters 

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

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

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

1121 

1122 # is it a cciss? 

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

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

1125 

1126 # is it a mapper path? 

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

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

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

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

1131 else: 

1132 numlen = 0 

1133 

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

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

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

1137 

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

1139 

1140 

1141def dom0_disks(): 

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

1143 disks = [] 

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

1145 for line in f: 

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

1147 if mountpoint == '/': 

1148 disk = diskFromPartition(dev) 

1149 if not (disk in disks): 

1150 disks.append(disk) 

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

1152 return disks 

1153 

1154 

1155def set_scheduler_sysfs_node(node, scheds): 

1156 """ 

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

1158 according to prioritized list schedulers 

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

1160 """ 

1161 

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

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

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

1165 return 

1166 

1167 stored_error = None 

1168 for sched in scheds: 

1169 try: 

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

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

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

1173 return 

1174 except (OSError, IOError) as err: 

1175 stored_error = err 

1176 

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

1178 

1179 

1180def set_scheduler(dev, schedulers=None): 

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

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

1183 

1184 devices = [] 

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

1186 # Remove partition numbers 

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

1188 else: 

1189 rawdev = diskFromPartition(dev) 

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

1191 

1192 for d in devices: 

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

1194 

1195 

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

1197def _getVDIs(srobj): 

1198 VDIs = [] 

1199 try: 

1200 sr_ref = getattr(srobj, 'sr_ref') 

1201 except AttributeError: 

1202 return VDIs 

1203 

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

1205 for vdi in refs: 

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

1207 ref['vdi_ref'] = vdi 

1208 VDIs.append(ref) 

1209 return VDIs 

1210 

1211 

1212def _getVDI(srobj, vdi_uuid): 

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

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

1215 ref['vdi_ref'] = vdi 

1216 return ref 

1217 

1218 

1219def _convertDNS(name): 

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

1221 return addr 

1222 

1223 

1224def _containsVDIinuse(srobj): 

1225 VDIs = _getVDIs(srobj) 

1226 for vdi in VDIs: 

1227 if not vdi['managed']: 

1228 continue 

1229 sm_config = vdi['sm_config'] 

1230 if 'SRRef' in sm_config: 

1231 try: 

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

1233 for pbd in PBDs: 

1234 record = PBDs[pbd] 

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

1236 record["currently_attached"]: 

1237 return True 

1238 except: 

1239 pass 

1240 return False 

1241 

1242 

1243def isVDICommand(cmd): 

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

1245 "vdi_activate", "vdi_deactivate", 

1246 "vdi_epoch_begin", "vdi_epoch_end"]: 

1247 return True 

1248 else: 

1249 return False 

1250 

1251 

1252######################### 

1253# Daemon helper functions 

1254def p_id_fork(): 

1255 try: 

1256 p_id = os.fork() 

1257 except OSError as e: 

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

1259 sys.exit(-1) 

1260 

1261 if (p_id == 0): 

1262 os.setsid() 

1263 try: 

1264 p_id = os.fork() 

1265 except OSError as e: 

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

1267 sys.exit(-1) 

1268 if (p_id == 0): 

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

1270 os.umask(0) 

1271 else: 

1272 os._exit(0) 

1273 else: 

1274 os._exit(0) 

1275 

1276 

1277def daemon(): 

1278 p_id_fork() 

1279 # Query the max file descriptor parameter for this process 

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

1281 

1282 # Close any fds that are open 

1283 for fd in range(0, maxfd): 

1284 try: 

1285 os.close(fd) 

1286 except: 

1287 pass 

1288 

1289 # Redirect STDIN to STDOUT and STDERR 

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

1291 os.dup2(0, 1) 

1292 os.dup2(0, 2) 

1293 

1294################################################################################ 

1295# 

1296# Fist points 

1297# 

1298 

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

1300# 

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

1302# on the SR master; 

1303# 

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

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

1306# - otherwise, the function called is _pause. 

1307 

1308def _pause(secs, name): 

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

1310 time.sleep(secs) 

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

1312 

1313 

1314def _exit(name): 

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

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

1317 

1318 

1319class FistPoint: 

1320 def __init__(self, points): 

1321 #SMlog("Fist points loaded") 

1322 self.points = points 

1323 

1324 def is_legal(self, name): 

1325 return (name in self.points) 

1326 

1327 def is_active(self, name): 

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

1329 

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

1331 session = get_localAPI_session() 

1332 try: 

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

1334 

1335 if started: 

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

1337 else: 

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

1339 finally: 

1340 session.xenapi.session.logout() 

1341 

1342 def activate(self, name, sruuid): 

1343 if name in self.points: 

1344 if self.is_active(name): 

1345 self.mark_sr(name, sruuid, True) 

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

1347 self.mark_sr(name, sruuid, False) 

1348 _exit(name) 

1349 else: 

1350 _pause(FIST_PAUSE_PERIOD, name) 

1351 self.mark_sr(name, sruuid, False) 

1352 else: 

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

1354 

1355 def activate_custom_fn(self, name, fn): 

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

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

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

1359 fn() 

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

1361 else: 

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

1363 

1364 

1365def list_find(f, seq): 

1366 for item in seq: 

1367 if f(item): 

1368 return item 

1369 

1370GCPAUSE_FISTPOINT = "GCLoop_no_pause" 

1371 

1372fistpoint = FistPoint(["LVHDRT_finding_a_suitable_pair", 

1373 "LVHDRT_inflating_the_parent", 

1374 "LVHDRT_resizing_while_vdis_are_paused", 

1375 "LVHDRT_coalescing_VHD_data", 

1376 "LVHDRT_coalescing_before_inflate_grandparent", 

1377 "LVHDRT_relinking_grandchildren", 

1378 "LVHDRT_before_create_relink_journal", 

1379 "LVHDRT_xapiSM_serialization_tests", 

1380 "LVHDRT_clone_vdi_after_create_journal", 

1381 "LVHDRT_clone_vdi_after_shrink_parent", 

1382 "LVHDRT_clone_vdi_after_first_snap", 

1383 "LVHDRT_clone_vdi_after_second_snap", 

1384 "LVHDRT_clone_vdi_after_parent_hidden", 

1385 "LVHDRT_clone_vdi_after_parent_ro", 

1386 "LVHDRT_clone_vdi_before_remove_journal", 

1387 "LVHDRT_clone_vdi_after_lvcreate", 

1388 "LVHDRT_clone_vdi_before_undo_clone", 

1389 "LVHDRT_clone_vdi_after_undo_clone", 

1390 "LVHDRT_inflate_after_create_journal", 

1391 "LVHDRT_inflate_after_setSize", 

1392 "LVHDRT_inflate_after_zeroOut", 

1393 "LVHDRT_inflate_after_setSizePhys", 

1394 "LVHDRT_inflate_after_setSizePhys", 

1395 "LVHDRT_coaleaf_before_coalesce", 

1396 "LVHDRT_coaleaf_after_coalesce", 

1397 "LVHDRT_coaleaf_one_renamed", 

1398 "LVHDRT_coaleaf_both_renamed", 

1399 "LVHDRT_coaleaf_after_vdirec", 

1400 "LVHDRT_coaleaf_before_delete", 

1401 "LVHDRT_coaleaf_after_delete", 

1402 "LVHDRT_coaleaf_before_remove_j", 

1403 "LVHDRT_coaleaf_undo_after_rename", 

1404 "LVHDRT_coaleaf_undo_after_rename2", 

1405 "LVHDRT_coaleaf_undo_after_refcount", 

1406 "LVHDRT_coaleaf_undo_after_deflate", 

1407 "LVHDRT_coaleaf_undo_end", 

1408 "LVHDRT_coaleaf_stop_after_recovery", 

1409 "LVHDRT_coaleaf_finish_after_inflate", 

1410 "LVHDRT_coaleaf_finish_end", 

1411 "LVHDRT_coaleaf_delay_1", 

1412 "LVHDRT_coaleaf_delay_2", 

1413 "LVHDRT_coaleaf_delay_3", 

1414 "testsm_clone_allow_raw", 

1415 "xenrt_default_vdi_type_legacy", 

1416 "blktap_activate_inject_failure", 

1417 "blktap_activate_error_handling", 

1418 GCPAUSE_FISTPOINT, 

1419 "cleanup_coalesceVHD_inject_failure", 

1420 "cleanup_tracker_no_progress", 

1421 "FileSR_fail_hardlink", 

1422 "FileSR_fail_snap1", 

1423 "FileSR_fail_snap2", 

1424 "LVM_journaler_exists", 

1425 "LVM_journaler_none", 

1426 "LVM_journaler_badname", 

1427 "LVM_journaler_readfail", 

1428 "LVM_journaler_writefail"]) 

1429 

1430 

1431def set_dirty(session, sr): 

1432 try: 

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

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

1435 except: 

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

1437 

1438 

1439def doesFileHaveOpenHandles(fileName): 

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

1441 (retVal, processAndPidTuples) = \ 

1442 findRunningProcessOrOpenFile(fileName, False) 

1443 

1444 if not retVal: 

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

1446 fileName) 

1447 # err on the side of caution 

1448 return True 

1449 else: 

1450 if len(processAndPidTuples) > 0: 

1451 return True 

1452 else: 

1453 return False 

1454 

1455 

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

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

1458def extractSRFromDevMapper(path): 

1459 try: 

1460 path = os.path.basename(path) 

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

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

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

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

1465 except: 

1466 return '' 

1467 

1468 

1469def pid_is_alive(pid): 

1470 """ 

1471 Try to kill PID with signal 0. 

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

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

1474 signal it. Still return true. 

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

1476 """ 

1477 try: 

1478 os.kill(pid, 0) 

1479 return True 

1480 except OSError as e: 

1481 if e.errno == errno.EPERM: 

1482 return True 

1483 return False 

1484 

1485 

1486# Looks at /proc and figures either 

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

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

1489# returns process names and pids 

1490def findRunningProcessOrOpenFile(name, process=True): 

1491 retVal = True 

1492 links = [] 

1493 processandpids = [] 

1494 sockets = set() 

1495 try: 

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

1497 [name, process]) 

1498 

1499 # Look at all pids 

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

1501 for pid in sorted(pids): 

1502 try: 

1503 try: 

1504 f = None 

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

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

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

1508 # Just want the process name 

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

1510 prog = argv[0] 

1511 except IOError as e: 

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

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

1514 continue 

1515 finally: 

1516 if f is not None: 1516 ↛ 1501,   1516 ↛ 15192 missed branches: 1) line 1516 didn't jump to line 1501, because the continue on line 1514 wasn't executed, 2) line 1516 didn't jump to line 1519, because the condition on line 1516 was never false

1517 f.close() 1517 ↛ 1501line 1517 didn't jump to line 1501, because the continue on line 1514 wasn't executed

1518 

1519 try: 

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

1521 files = os.listdir(fd_dir) 

1522 except OSError as e: 

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

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

1525 # Ignore pid that are no longer valid 

1526 continue 

1527 else: 

1528 raise 

1529 

1530 for file in files: 

1531 try: 

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

1533 except OSError: 

1534 continue 

1535 

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

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

1538 links.append(link) 

1539 else: 

1540 # need to return process name and pid tuples 

1541 if link == name: 

1542 processandpids.append((prog, pid)) 

1543 

1544 # Get the connected sockets 

1545 if name == prog: 

1546 sockets.update(get_connected_sockets(pid)) 

1547 

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

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

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

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

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

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

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

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

1556 

1557 except Exception as e: 

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

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

1560 retVal = False 

1561 

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

1563 return retVal, links, sockets 

1564 else: 

1565 return retVal, processandpids 

1566 

1567 

1568def get_connected_sockets(pid): 

1569 sockets = set() 

1570 try: 

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

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

1573 # - Pointer address to socket (hex) 

1574 # - Refcount (HEX) 

1575 # - 0 

1576 # - State (HEX, 0 or __SO_ACCEPTCON) 

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

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

1579 # - Inode number 

1580 # - Path (optional) 

1581 open_sock_matcher = re.compile( 

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

1583 with open( 

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

1585 lines = f.readlines() 

1586 for line in lines: 

1587 match = open_sock_matcher.match(line) 

1588 if match: 

1589 sockets.add(match[1]) 

1590 except OSError as e: 

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

1592 # Ignore pid that are no longer valid 

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

1594 (e.errno, pid)) 

1595 else: 

1596 raise 

1597 return sockets 

1598 

1599 

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

1601 retries = 0 

1602 while True: 

1603 try: 

1604 return f() 

1605 except Exception as e: 

1606 for exception in exceptions: 

1607 if isinstance(e, exception): 

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

1609 str(e), retries 

1610 )) 

1611 break 

1612 else: 

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

1614 raise e 

1615 

1616 retries += 1 

1617 if retries >= maxretry: 

1618 break 

1619 

1620 time.sleep(period) 

1621 

1622 return f() 

1623 

1624 

1625def getCslDevPath(svid): 

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

1627 if svid.startswith("NETAPP_"): 

1628 # special attention for NETAPP SVIDs 

1629 svid_parts = svid.split("__") 

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

1631 else: 

1632 globstr = basepath + svid + "*" 

1633 

1634 return globstr 

1635 

1636 

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

1638def get_scsiid_from_svid(md_svid): 

1639 cslg_path = getCslDevPath(md_svid) 

1640 abs_path = glob.glob(cslg_path) 

1641 if abs_path: 

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

1643 return scsiutil.getSCSIid(real_path) 

1644 else: 

1645 return None 

1646 

1647 

1648def get_isl_scsiids(session): 

1649 # Get cslg type SRs 

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

1651 

1652 # Iterate through the SR to get the scsi ids 

1653 scsi_id_ret = [] 

1654 for SR in SRs: 

1655 sr_rec = SRs[SR] 

1656 # Use the md_svid to get the scsi id 

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

1658 if scsi_id: 

1659 scsi_id_ret.append(scsi_id) 

1660 

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

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

1663 for vdi_rec in vdi_recs: 

1664 vdi_rec = vdi_recs[vdi_rec] 

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

1666 if scsi_id: 

1667 scsi_id_ret.append(scsi_id) 

1668 

1669 return scsi_id_ret 

1670 

1671 

1672class extractXVA: 

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

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

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

1676 HDR_SIZE = 512 

1677 BLOCK_SIZE = 512 

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

1679 SIZE_OFFSET = 124 

1680 ZERO_FILLED_REC = 2 

1681 NULL_IDEN = '\x00' 

1682 DIR_IDEN = '/' 

1683 CHECKSUM_IDEN = '.checksum' 

1684 OVA_FILE = 'ova.xml' 

1685 

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

1687 # as and when needed 

1688 def __init__(self, filename): 

1689 self.__extract_path = '' 

1690 self.__filename = filename 

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

1692 try: 

1693 self.spawn_p = subprocess.Popen( 

1694 cmd, shell=True, \ 

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

1696 stderr=subprocess.PIPE, close_fds=True) 

1697 except Exception as e: 

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

1699 raise Exception(str(e)) 

1700 

1701 # Create dir to extract the files 

1702 self.__extract_path = tempfile.mkdtemp() 

1703 

1704 def __del__(self): 

1705 shutil.rmtree(self.__extract_path) 

1706 

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

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

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

1710 def getTuple(self): 

1711 zerod_record = 0 

1712 ret_f_name = '' 

1713 ret_base_f_name = '' 

1714 

1715 try: 

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

1717 while True: 

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

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

1720 

1721 # Break out in case of end of file 

1722 if f_hdr == '': 

1723 if zerod_record == extractXVA.ZERO_FILLED_REC: 

1724 break 

1725 else: 

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

1727 extractXVA.ZERO_FILLED_REC) 

1728 raise Exception('Unrecognized end of file') 

1729 

1730 # Watch out for zero records, two zero records 

1731 # denote end of file. 

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

1733 zerod_record += 1 

1734 continue 

1735 

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

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

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

1739 f_size_octal = f_hdr[extractXVA.SIZE_OFFSET: \ 

1740 extractXVA.SIZE_OFFSET + extractXVA.SIZE_LEN] 

1741 f_size = int(f_size_octal, 8) 

1742 if f_name.endswith(extractXVA.CHECKSUM_IDEN): 

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

1744 ret_base_f_name: 

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

1746 yield(ret_f_name, checksum) 

1747 else: 

1748 # Expects file followed by its checksum 

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

1750 ret_f_name) 

1751 raise Exception( \ 

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

1753 ret_f_name) 

1754 else: 

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

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

1757 # read the contents into a file, it will 

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

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

1760 ret_base_f_name = f_name 

1761 

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

1763 # else create it. 

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

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

1766 os.mkdir(folder_path) 

1767 

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

1769 f = open(ret_f_name, 'w') 

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

1771 f.close() 

1772 if f_name == extractXVA.OVA_FILE: 

1773 yield(ret_f_name, '') 

1774 

1775 # Skip zero'd portion of data block 

1776 round_off = f_size % extractXVA.BLOCK_SIZE 

1777 if round_off != 0: 

1778 zeros = self.spawn_p.stdout.read( 

1779 extractXVA.BLOCK_SIZE - round_off) 

1780 except Exception as e: 

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

1782 self.__filename)) 

1783 

1784 # Kill and Drain stdout of the gunzip process, 

1785 # else gunzip might block on stdout 

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

1787 self.spawn_p.communicate() 

1788 raise Exception(str(e)) 

1789 

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

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

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

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

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

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

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

1797 (0x10FFFE, 0x10FFFF)] 

1798 

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

1800 for (low, high) in illegal_xml_chars 

1801 if low < sys.maxunicode] 

1802 

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

1804 

1805 

1806def isLegalXMLString(s): 

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

1808 illegal XML characters specified in 

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

1810 """ 

1811 

1812 if len(s) > 0: 

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

1814 else: 

1815 return True 

1816 

1817 

1818def unictrunc(string, max_bytes): 

1819 """ 

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

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

1822 more than the given number of bytes. 

1823 

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

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

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

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

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

1829 

1830 string: the string to truncate 

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

1832 """ 

1833 if isinstance(string, str): 

1834 return_chars = True 

1835 else: 

1836 return_chars = False 

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

1838 

1839 cur_chars = 0 

1840 cur_bytes = 0 

1841 for char in string: 

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

1843 if cur_bytes + charsize > max_bytes: 

1844 break 

1845 else: 

1846 cur_chars += 1 

1847 cur_bytes += charsize 

1848 return cur_chars if return_chars else cur_bytes 

1849 

1850 

1851def hideValuesInPropMap(propmap, propnames): 

1852 """ 

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

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

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

1856 value and return the altered map. 

1857 If none found, return the original map 

1858 """ 

1859 matches = [] 

1860 for propname in propnames: 

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

1862 matches.append(propname) 

1863 

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

1865 deepCopyRec = copy.deepcopy(propmap) 

1866 for match in matches: 

1867 deepCopyRec[match] = '******' 

1868 return deepCopyRec 

1869 

1870 return propmap 

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

1872 

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

1874DEFAULT_SEGMENT_LEN = 950 

1875 

1876 

1877def hidePasswdInConfig(config): 

1878 """ 

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

1880 for example "device_config" 

1881 """ 

1882 return hideValuesInPropMap(config, PASSWD_PROP_KEYS) 

1883 

1884 

1885def hidePasswdInParams(params, configProp): 

1886 """ 

1887 Function to hide password values in a specified property which 

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

1889 in a larger property map. 

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

1891 "sm_config", etc 

1892 """ 

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

1894 return params 

1895 

1896 

1897def hideMemberValuesInXmlParams(xmlParams, propnames=PASSWD_PROP_KEYS): 

1898 """ 

1899 Function to hide password values in XML params, specifically 

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

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

1902 whose values we want to hide, and: 

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

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

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

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

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

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

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

1910 """ 

1911 findStrPrefixHead = "<member><name>" 

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

1913 findStrSuffix = "</value>" 

1914 strlen = len(xmlParams) 

1915 

1916 for propname in propnames: 

1917 findStrPrefix = findStrPrefixHead + propname + findStrPrefixTail 

1918 idx = xmlParams.find(findStrPrefix) 

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

1920 idx += len(findStrPrefix) 

1921 idx2 = xmlParams.find(findStrSuffix, idx) 

1922 if idx2 != -1: 

1923 retStr = xmlParams[0:idx] 

1924 retStr += "******" 

1925 retStr += xmlParams[idx2:strlen] 

1926 return retStr 

1927 else: 

1928 return xmlParams 

1929 return xmlParams 

1930 

1931 

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

1933 """ 

1934 Split xml string data into substrings small enough for the 

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

1936 Usage: 

1937 strList = [] 

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

1939 """ 

1940 remainingData = str(xmlData) 

1941 

1942 # "Un-pretty-print" 

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

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

1945 

1946 remainingChars = len(remainingData) 

1947 returnData = '' 

1948 

1949 thisLineNum = 0 

1950 while remainingChars > segmentLen: 

1951 thisLineNum = thisLineNum + 1 

1952 index = segmentLen 

1953 tmpStr = remainingData[:segmentLen] 

1954 tmpIndex = tmpStr.rfind('>') 

1955 if tmpIndex != -1: 

1956 index = tmpIndex + 1 

1957 

1958 tmpStr = tmpStr[:index] 

1959 remainingData = remainingData[index:] 

1960 remainingChars = len(remainingData) 

1961 

1962 if showContd: 

1963 if thisLineNum != 1: 

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

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

1966 

1967 returnData += tmpStr + '\n' 

1968 

1969 if showContd and thisLineNum > 0: 

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

1971 returnData += remainingData 

1972 

1973 return returnData 

1974 

1975 

1976def inject_failure(): 

1977 raise Exception('injected failure') 

1978 

1979 

1980def open_atomic(path, mode=None): 

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

1982 Leaves the file open and returns the file object. 

1983 

1984 path: the path to atomically open 

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

1986 returns: an open file object""" 

1987 

1988 assert path 

1989 

1990 flags = os.O_CREAT | os.O_EXCL 

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

1992 if mode: 

1993 if mode not in modes: 

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

1995 flags |= modes[mode] 

1996 fd = os.open(path, flags) 

1997 try: 

1998 if mode: 

1999 return os.fdopen(fd, mode) 

2000 else: 

2001 return os.fdopen(fd) 

2002 except: 

2003 os.close(fd) 

2004 raise 

2005 

2006 

2007def isInvalidVDI(exception): 

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

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

2010 

2011 

2012def get_pool_restrictions(session): 

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

2014 established.""" 

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

2016 

2017 

2018def read_caching_is_restricted(session): 

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

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

2021 return True 

2022 restrictions = get_pool_restrictions(session) 

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

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

2025 return True 

2026 return False 

2027 

2028 

2029def sessions_less_than_targets(other_config, device_config): 

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

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

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

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

2034 return (sessions < targets) 

2035 else: 

2036 return False 

2037 

2038 

2039def enable_and_start_service(name, start): 

2040 attempt = 0 

2041 while True: 

2042 attempt += 1 

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

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

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

2046 if ret == 0: 

2047 return 

2048 elif attempt >= 3: 

2049 raise Exception( 

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

2051 ) 

2052 time.sleep(1) 

2053 

2054 

2055def stop_service(name): 

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

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

2058 if ret == 0: 

2059 return 

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

2061 

2062 

2063def restart_service(name): 

2064 attempt = 0 

2065 while True: 

2066 attempt += 1 

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

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

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

2070 if ret == 0: 

2071 return 

2072 elif attempt >= 3: 

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

2074 raise Exception( 

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

2076 ) 

2077 time.sleep(1) 

2078 

2079 

2080def check_pid_exists(pid): 

2081 try: 

2082 os.kill(pid, 0) 

2083 except OSError: 

2084 return False 

2085 else: 

2086 return True 

2087 

2088 

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

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

2091 

2092 try: 

2093 list = [] 

2094 ret = pread2(cmd) 

2095 for line in ret.splitlines(): 

2096 list.append(int(line)) 

2097 return list 

2098 except CommandException as e: 

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

2100 return None 

2101 else: 

2102 raise e 

2103 

2104 

2105def make_profile(name, function): 

2106 """ 

2107 Helper to execute cProfile using unique log file. 

2108 """ 

2109 

2110 import cProfile 

2111 import itertools 

2112 import os.path 

2113 import time 

2114 

2115 assert name 

2116 assert function 

2117 

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

2119 makedirs(FOLDER) 

2120 

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

2122 

2123 def gen_path(path): 

2124 yield path 

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

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

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

2128 

2129 for profile_path in gen_path(FOLDER + filename): 

2130 try: 

2131 file = open_atomic(profile_path, 'w') 

2132 file.close() 

2133 break 

2134 except OSError as e: 

2135 if e.errno == errno.EEXIST: 

2136 pass 

2137 else: 

2138 raise 

2139 

2140 try: 

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

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

2143 finally: 

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

2145 

2146 

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

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

2149 # There is not alternative for strtobool. 

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

2151 # So this is a custom implementation with differences: 

2152 # - A boolean is returned instead of integer 

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

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

2155 return False 

2156 str = str.lower() 

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

2158 return True 

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

2160 return False 

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

2162 

2163 

2164def find_executable(name): 

2165 return shutil.which(name) 

2166 

2167 

2168def conditional_decorator(decorator, condition): 

2169 def wrapper(func): 

2170 if not condition: 

2171 return func 

2172 return decorator(func) 

2173 return wrapper