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 

47from sm_typing import List, Optional 

48 

49NO_LOGGING_STAMPFILE = '/etc/xensource/no_sm_log' 

50 

51IORETRY_MAX = 20 # retries 

52IORETRY_PERIOD = 1.0 # seconds 

53 

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

55_SM_SYSLOG_FACILITY = syslog.LOG_LOCAL2 

56LOG_EMERG = syslog.LOG_EMERG 

57LOG_ALERT = syslog.LOG_ALERT 

58LOG_CRIT = syslog.LOG_CRIT 

59LOG_ERR = syslog.LOG_ERR 

60LOG_WARNING = syslog.LOG_WARNING 

61LOG_NOTICE = syslog.LOG_NOTICE 

62LOG_INFO = syslog.LOG_INFO 

63LOG_DEBUG = syslog.LOG_DEBUG 

64 

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

66 

67CMD_DD = "/bin/dd" 

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

69 

70FIST_PAUSE_PERIOD = 30 # seconds 

71 

72 

73class SMException(Exception): 

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

75 XenError""" 

76 

77 

78class CommandException(SMException): 

79 def error_message(self, code): 

80 if code > 0: 

81 return os.strerror(code) 

82 elif code < 0: 

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

84 return "Success" 

85 

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

87 self.code = code 

88 self.cmd = cmd 

89 self.reason = reason 

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

91 

92 

93class SRBusyException(SMException): 

94 """The SR could not be locked""" 

95 pass 

96 

97 

98def logException(tag): 

99 info = sys.exc_info() 

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

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

102 sys.exit(0) 

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

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

105 SMlog(str) 

106 

107 

108def roundup(divisor, value): 

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

110 

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

112 value = 1 

113 if value % divisor != 0: 

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

115 return value 

116 

117 

118def to_plain_string(obj): 

119 if obj is None: 

120 return None 

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

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

123 return "" 

124 return str(obj) 

125 

126 

127def shellquote(arg): 

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

129 

130 

131def make_WWN(name): 

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

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

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

135 # inject dashes for each nibble 

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

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

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

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

140 return name 

141 

142 

143def synchronized(func): 

144 lock = threading.RLock() 

145 

146 @functools.wraps(func) 

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

148 with lock: 

149 return func(*args, **kwargs) 

150 

151 return wrapper 

152 

153 

154@synchronized 

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

156 syslog.openlog(ident, 0, facility) 

157 syslog.syslog(priority, message) 

158 syslog.closelog() 

159 

160 

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

162 pid = os.getpid() 

163 thread_name = threading.current_thread().name 

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

165 

166 

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

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

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

170 _logToSyslog(ident, _SM_SYSLOG_FACILITY, priority, message_line) 

171 

172 

173class LoggerCounter: 

174 def __init__(self, max_repeats): 

175 self.previous_message = None 

176 self.max_repeats = max_repeats 

177 self.repeat_counter = 0 

178 

179 def log(self, message): 

180 self.repeat_counter += 1 

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

182 SMlog(message) 

183 self.previous_message = message 

184 self.repeat_counter = 0 

185 

186def _getDateString(): 

187 d = datetime.datetime.now() 

188 t = d.timetuple() 

189 return "%s-%s-%s:%s:%s:%s" % \ 

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

191 

192 

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

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

195 env = None 

196 if new_env: 

197 env = dict(os.environ) 

198 env.update(new_env) 

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

200 stdout=subprocess.PIPE, 

201 stderr=subprocess.PIPE, 

202 close_fds=True, env=env, 

203 universal_newlines=text) 

204 

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

206 inputtext = inputtext.encode() 

207 

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

209 

210 rc = proc.returncode 

211 return rc, stdout, stderr 

212 

213 

214def is_string(value): 

215 return isinstance(value, str) 

216 

217 

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

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

220# replace the original ones at some later date. 

221# 

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

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

224# written to the logs. 

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

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

227 cmdlist_for_exec = [] 

228 cmdlist_for_log = [] 

229 for item in cmdlist: 

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

231 cmdlist_for_exec.append(item) 

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

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

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

235 else: 

236 cmdlist_for_log.append(item) 

237 else: 

238 cmdlist_for_log.append(item) 

239 else: 

240 cmdlist_for_exec.append(item[0]) 

241 cmdlist_for_log.append(item[1]) 

242 

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

244 SMlog(cmdlist_for_log) 

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

246 if rc != expect_rc: 

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

248 (rc, stdout, stderr)) 

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

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

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

252 stderr = stdout 

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

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

255 SMlog(" pread SUCCESS") 

256 return stdout 

257 

258 

259# POSIX guaranteed atomic within the same file system. 

260# Supply directory to ensure tempfile is created 

261# in the same directory. 

262def atomicFileWrite(targetFile, directory, text): 

263 

264 file = None 

265 try: 

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

267 # our responsibility to clean it up. 

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

269 file = open(tempPath, 'w') 

270 file.write(text) 

271 

272 # Ensure flushed to disk. 

273 file.flush() 

274 os.fsync(file.fileno()) 

275 file.close() 

276 

277 os.rename(tempPath, targetFile) 

278 except OSError: 

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

280 

281 finally: 

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

283 file.close() 

284 

285 if os.path.isfile(tempPath): 

286 os.remove(tempPath) 

287 

288 

289#Read STDOUT from cmdlist and discard STDERR output 

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

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

292 

293 

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

295def pread3(cmdlist, text): 

296 SMlog(cmdlist) 

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

298 if rc: 

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

300 (rc, stdout, stderr)) 

301 if '' == stderr: 

302 stderr = stdout 

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

304 SMlog(" pread3 SUCCESS") 

305 return stdout 

306 

307 

308def listdir(path, quiet=False): 

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

310 try: 

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

312 if len(text) == 0: 

313 return [] 

314 return text.split('\n') 

315 except CommandException as inst: 

316 if inst.code == errno.ENOENT: 

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

318 else: 

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

320 

321 

322def gen_uuid(): 

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

324 return pread(cmd)[:-1] 

325 

326 

327def match_uuid(s): 

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

329 return regex.search(s, 0) 

330 

331 

332def findall_uuid(s): 

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

334 return regex.findall(s, 0) 

335 

336 

337def exactmatch_uuid(s): 

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

339 return regex.search(s, 0) 

340 

341 

342def start_log_entry(srpath, path, args): 

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

344 logstring += " log: " 

345 logstring += srpath 

346 logstring += " " + path 

347 for element in args: 

348 logstring += " " + element 

349 try: 

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

351 file.write(logstring) 

352 file.write("\n") 

353 file.close() 

354 except: 

355 pass 

356 

357 # failed to write log ... 

358 

359def end_log_entry(srpath, path, args): 

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

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

362 logstring += " end: " 

363 logstring += srpath 

364 logstring += " " + path 

365 for element in args: 

366 logstring += " " + element 

367 try: 

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

369 file.write(logstring) 

370 file.write("\n") 

371 file.close() 

372 except: 

373 pass 

374 

375 # failed to write log ... 

376 # for now print 

377 # print "%s" % logstring 

378 

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

380 retries = 0 

381 while True: 

382 try: 

383 return f() 

384 except OSError as ose: 

385 err = int(ose.errno) 

386 if not err in errlist: 

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

388 except CommandException as ce: 

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

390 raise 

391 

392 retries += 1 

393 if retries >= maxretry: 

394 break 

395 

396 time.sleep(period) 

397 

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

399 

400 

401def ioretry_stat(path, maxretry=IORETRY_MAX): 

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

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

404 retries = 0 

405 while retries < maxretry: 

406 stat = os.statvfs(path) 

407 if stat.f_blocks != -1: 

408 return stat 

409 time.sleep(1) 

410 retries += 1 

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

412 

413 

414def sr_get_capability(sr_uuid, session=None): 

415 result = [] 

416 local_session = None 

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

418 local_session = get_localAPI_session() 

419 session = local_session 

420 

421 try: 

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

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

424 sm_rec = session.xenapi.SM.get_all_records_where( 

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

426 

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

428 if len(sm_rec) > 0: 

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

430 

431 return result 

432 finally: 

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

434 local_session.xenapi.session.logout() 

435 

436def sr_get_driver_info(driver_info): 

437 results = {} 

438 # first add in the vanilla stuff 

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

440 'driver_version', 'required_api_version']: 

441 results[key] = driver_info[key] 

442 # add the capabilities (xmlrpc array) 

443 # enforcing activate/deactivate for blktap2 

444 caps = driver_info['capabilities'] 

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

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

447 if not cap in caps: 

448 caps.append(cap) 

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

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

451 

452 results['capabilities'] = caps 

453 # add in the configuration options 

454 options = [] 

455 for option in driver_info['configuration']: 

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

457 results['configuration'] = options 

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

459 

460 

461def return_nil(): 

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

463 

464 

465def SRtoXML(SRlist): 

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

467 driver = dom.createElement("SRlist") 

468 dom.appendChild(driver) 

469 

470 for key in SRlist.keys(): 

471 dict = SRlist[key] 

472 entry = dom.createElement("SR") 

473 driver.appendChild(entry) 

474 

475 e = dom.createElement("UUID") 

476 entry.appendChild(e) 

477 textnode = dom.createTextNode(key) 

478 e.appendChild(textnode) 

479 

480 if 'size' in dict: 

481 e = dom.createElement("Size") 

482 entry.appendChild(e) 

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

484 e.appendChild(textnode) 

485 

486 if 'storagepool' in dict: 

487 e = dom.createElement("StoragePool") 

488 entry.appendChild(e) 

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

490 e.appendChild(textnode) 

491 

492 if 'aggregate' in dict: 

493 e = dom.createElement("Aggregate") 

494 entry.appendChild(e) 

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

496 e.appendChild(textnode) 

497 

498 return dom.toprettyxml() 

499 

500 

501def pathexists(path): 

502 try: 

503 os.lstat(path) 

504 return True 

505 except OSError as inst: 

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

507 time.sleep(1) 

508 try: 

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

510 os.lstat(path) 

511 return True 

512 except: 

513 pass 

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

515 return False 

516 

517 

518def force_unlink(path): 

519 try: 

520 os.unlink(path) 

521 except OSError as e: 

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

523 raise 

524 

525 

526def create_secret(session, secret): 

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

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

529 

530 

531def get_secret(session, uuid): 

532 try: 

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

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

535 except: 

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

537 

538 

539def get_real_path(path): 

540 "Follow symlinks to the actual file" 

541 absPath = path 

542 directory = '' 

543 while os.path.islink(absPath): 

544 directory = os.path.dirname(absPath) 

545 absPath = os.readlink(absPath) 

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

547 return absPath 

548 

549 

550def wait_for_path(path, timeout): 

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

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

553 return True 

554 time.sleep(1) 

555 return False 

556 

557 

558def wait_for_nopath(path, timeout): 

559 for i in range(0, timeout): 

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

561 return True 

562 time.sleep(1) 

563 return False 

564 

565 

566def wait_for_path_multi(path, timeout): 

567 for i in range(0, timeout): 

568 paths = glob.glob(path) 

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

570 if len(paths): 

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

572 return paths[0] 

573 time.sleep(1) 

574 return "" 

575 

576 

577def isdir(path): 

578 try: 

579 st = os.stat(path) 

580 return stat.S_ISDIR(st.st_mode) 

581 except OSError as inst: 

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

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

584 return False 

585 

586 

587def get_single_entry(path): 

588 f = open(path, 'r') 

589 line = f.readline() 

590 f.close() 

591 return line.rstrip() 

592 

593 

594def get_fs_size(path): 

595 st = ioretry_stat(path) 

596 return st.f_blocks * st.f_frsize 

597 

598 

599def get_fs_utilisation(path): 

600 st = ioretry_stat(path) 

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

602 st.f_frsize 

603 

604 

605def ismount(path): 

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

607 try: 

608 s1 = os.stat(path) 

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

610 except OSError as inst: 

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

612 dev1 = s1.st_dev 

613 dev2 = s2.st_dev 

614 if dev1 != dev2: 

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

616 ino1 = s1.st_ino 

617 ino2 = s2.st_ino 

618 if ino1 == ino2: 

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

620 return False 

621 

622 

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

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

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

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

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

628 makedirs(head, mode) 

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

630 return 

631 try: 

632 os.mkdir(name, mode) 

633 except OSError as exc: 

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

635 if mode: 

636 os.chmod(name, mode) 

637 pass 

638 else: 

639 raise 

640 

641 

642def zeroOut(path, fromByte, bytes): 

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

644 blockSize = 4096 

645 

646 fromBlock = fromByte // blockSize 

647 if fromByte % blockSize: 

648 fromBlock += 1 

649 bytesBefore = fromBlock * blockSize - fromByte 

650 if bytesBefore > bytes: 

651 bytesBefore = bytes 

652 bytes -= bytesBefore 

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

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

655 try: 

656 pread2(cmd) 

657 except CommandException: 

658 return False 

659 

660 blocks = bytes // blockSize 

661 bytes -= blocks * blockSize 

662 fromByte = (fromBlock + blocks) * blockSize 

663 if blocks: 

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

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

666 try: 

667 pread2(cmd) 

668 except CommandException: 

669 return False 

670 

671 if bytes: 

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

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

674 try: 

675 pread2(cmd) 

676 except CommandException: 

677 return False 

678 

679 return True 

680 

681 

682def wipefs(blockdev): 

683 "Wipe filesystem signatures from `blockdev`" 

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

685 

686 

687def match_rootdev(s): 

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

689 return regex.search(s, 0) 

690 

691 

692def getrootdev(): 

693 filename = '/etc/xensource-inventory' 

694 try: 

695 f = open(filename, 'r') 

696 except: 

697 raise xs_errors.XenError('EIO', \ 

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

699 rootdev = '' 

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

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

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

703 raise xs_errors.XenError('NoRootDev') 

704 return rootdev 

705 

706 

707def getrootdevID(): 

708 rootdev = getrootdev() 

709 try: 

710 rootdevID = scsiutil.getSCSIid(rootdev) 

711 except: 

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

713 % rootdev) 

714 return '' 

715 

716 if not len(rootdevID): 

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

718 % rootdev) 

719 

720 return rootdevID 

721 

722 

723def get_localAPI_session(): 

724 # First acquire a valid session 

725 session = XenAPI.xapi_local() 

726 try: 

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

728 except: 

729 raise xs_errors.XenError('APISession') 

730 return session 

731 

732 

733def get_this_host(): 

734 uuid = None 

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

736 for line in f.readlines(): 

737 if line.startswith("INSTALLATION_UUID"): 

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

739 f.close() 

740 return uuid 

741 

742 

743def get_master_ref(session): 

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

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

746 

747 

748def is_master(session): 

749 return get_this_host_ref(session) == get_master_ref(session) 

750 

751 

752def get_localhost_ref(session): 

753 filename = '/etc/xensource-inventory' 

754 try: 

755 f = open(filename, 'r') 

756 except: 

757 raise xs_errors.XenError('EIO', \ 

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

759 domid = '' 

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

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

762 if not domid: 

763 raise xs_errors.XenError('APILocalhost') 

764 

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

766 for vm in vms: 

767 record = vms[vm] 

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

769 hostid = record["resident_on"] 

770 return hostid 

771 raise xs_errors.XenError('APILocalhost') 

772 

773 

774def match_domain_id(s): 

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

776 return regex.search(s, 0) 

777 

778 

779def get_hosts_attached_on(session, vdi_uuids): 

780 host_refs = {} 

781 for vdi_uuid in vdi_uuids: 

782 try: 

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

784 except XenAPI.Failure: 

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

786 continue 

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

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

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

790 return host_refs.keys() 

791 

792def get_hosts_attached_on_with_vdi_uuid(session, vdi_uuids): 

793 """ 

794 Return a dict of {vdi_uuid: host OpaqueRef} 

795 """ 

796 host_refs = {} 

797 for vdi_uuid in vdi_uuids: 

798 try: 

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

800 except XenAPI.Failure: 

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

802 continue 

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

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

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

806 return host_refs 

807 

808def get_this_host_address(session): 

809 host_uuid = get_this_host() 

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

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

812 

813def get_host_addresses(session): 

814 addresses = [] 

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

816 for record in hosts.values(): 

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

818 return addresses 

819 

820def get_this_host_ref(session): 

821 host_uuid = get_this_host() 

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

823 return host_ref 

824 

825 

826def get_slaves_attached_on(session, vdi_uuids): 

827 "assume this host is the SR master" 

828 host_refs = get_hosts_attached_on(session, vdi_uuids) 

829 master_ref = get_this_host_ref(session) 

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

831 

832def get_enabled_hosts(session): 

833 """ 

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

835 """ 

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

837 

838def get_online_hosts(session): 

839 online_hosts = [] 

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

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

842 metricsRef = host_rec["metrics"] 

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

844 if metrics["live"]: 

845 online_hosts.append(host_ref) 

846 return online_hosts 

847 

848 

849def get_all_slaves(session): 

850 "assume this host is the SR master" 

851 host_refs = get_online_hosts(session) 

852 master_ref = get_this_host_ref(session) 

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

854 

855 

856def is_attached_rw(sm_config): 

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

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

859 return True 

860 return False 

861 

862 

863def attached_as(sm_config): 

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

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

866 return val 

867 

868 

869def find_my_pbd_record(session, host_ref, sr_ref): 

870 try: 

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

872 for pbd_ref in pbds.keys(): 

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

874 return [pbd_ref, pbds[pbd_ref]] 

875 return None 

876 except Exception as e: 

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

878 return None 

879 

880 

881def find_my_pbd(session, host_ref, sr_ref): 

882 ret = find_my_pbd_record(session, host_ref, sr_ref) 

883 if ret is not None: 

884 return ret[0] 

885 else: 

886 return None 

887 

888 

889def test_hostPBD_devs(session, sr_uuid, devs): 

890 host = get_localhost_ref(session) 

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

892 try: 

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

894 except: 

895 raise xs_errors.XenError('APIPBDQuery') 

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

897 for pbd in pbds: 

898 record = pbds[pbd] 

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

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

901 break 

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

903 devconfig = record["device_config"] 

904 if 'device' in devconfig: 

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

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

907 return True 

908 return False 

909 

910 

911def test_hostPBD_lun(session, targetIQN, LUNid): 

912 host = get_localhost_ref(session) 

913 try: 

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

915 except: 

916 raise xs_errors.XenError('APIPBDQuery') 

917 for pbd in pbds: 

918 record = pbds[pbd] 

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

920 devconfig = record["device_config"] 

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

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

923 devconfig['LUNid'] == LUNid: 

924 return True 

925 return False 

926 

927 

928def test_SCSIid(session, sr_uuid, SCSIid): 

929 if sr_uuid is not None: 

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

931 try: 

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

933 except: 

934 raise xs_errors.XenError('APIPBDQuery') 

935 for pbd in pbds: 

936 record = pbds[pbd] 

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

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

939 if sr_uuid is not None: 

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

941 break 

942 devconfig = record["device_config"] 

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

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

945 return True 

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

947 return True 

948 elif 'scsi-' + SCSIid in sm_config: 

949 return True 

950 return False 

951 

952 

953class TimeoutException(SMException): 

954 pass 

955 

956 

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

958 def handler(signum, frame): 

959 raise TimeoutException() 

960 signal.signal(signal.SIGALRM, handler) 

961 signal.alarm(timeoutseconds) 

962 try: 

963 return function(*arguments) 

964 finally: 

965 signal.alarm(0) 

966 

967 

968def _incr_iscsiSR_refcount(targetIQN, uuid): 

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

970 os.mkdir(ISCSI_REFDIR) 

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

972 try: 

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

974 except: 

975 raise xs_errors.XenError('LVMRefCount', \ 

976 opterr='file %s' % filename) 

977 

978 f.seek(0) 

979 found = False 

980 refcount = 0 

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

982 refcount += 1 

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

984 found = True 

985 if not found: 

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

987 refcount += 1 

988 f.close() 

989 return refcount 

990 

991 

992def _decr_iscsiSR_refcount(targetIQN, uuid): 

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

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

995 return 0 

996 try: 

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

998 except: 

999 raise xs_errors.XenError('LVMRefCount', \ 

1000 opterr='file %s' % filename) 

1001 

1002 f.seek(0) 

1003 output = [] 

1004 refcount = 0 

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

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

1007 output.append(line.rstrip()) 

1008 refcount += 1 

1009 if not refcount: 

1010 os.unlink(filename) 

1011 return refcount 

1012 

1013 # Re-open file and truncate 

1014 f.close() 

1015 f = open(filename, 'w') 

1016 for i in range(0, refcount): 

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

1018 f.close() 

1019 return refcount 

1020 

1021 

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

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

1024def test_activePoolPBDs(session, host, uuid): 

1025 try: 

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

1027 except: 

1028 raise xs_errors.XenError('APIPBDQuery') 

1029 for pbd in pbds: 

1030 record = pbds[pbd] 

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

1032 and record["currently_attached"]: 

1033 return True 

1034 return False 

1035 

1036 

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

1038 try: 

1039 pbdref = find_my_pbd(session, host_ref, sr_ref) 

1040 if pbdref is not None: 

1041 key = "mpath-" + SCSIid 

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

1043 except: 

1044 pass 

1045 

1046 

1047def kickpipe_mpathcount(): 

1048 """ 

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

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

1051 by a UDEV event. 

1052 """ 

1053 cmd = [CMD_KICKPIPE, "mpathcount"] 

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

1055 return (rc == 0) 

1056 

1057 

1058def _testHost(hostname, port, errstring): 

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

1060 try: 

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

1062 except: 

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

1064 raise xs_errors.XenError('DNSError') 

1065 

1066 timeout = 5 

1067 

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

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

1070 sock.settimeout(timeout) 

1071 try: 

1072 sock.connect(sockinfo[4]) 

1073 # Fix for MS storage server bug 

1074 sock.send(b'\n') 

1075 sock.close() 

1076 except socket.error as reason: 

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

1078 % (timeout, hostname, reason)) 

1079 raise xs_errors.XenError(errstring) 

1080 

1081 

1082def match_scsiID(s, id): 

1083 regex = re.compile(id) 

1084 return regex.search(s, 0) 

1085 

1086 

1087def _isSCSIid(s): 

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

1089 return regex.search(s, 0) 

1090 

1091 

1092def is_usb_device(device): 

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

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

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

1096 

1097 

1098def test_scsiserial(session, device): 

1099 device = os.path.realpath(device) 

1100 if not scsiutil._isSCSIdev(device): 

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

1102 return False 

1103 serial = "" 

1104 try: 

1105 serial += scsiutil.getserial(device) 

1106 except: 

1107 # Error allowed, SCSIid is the important one 

1108 pass 

1109 

1110 try: 

1111 scsiID = scsiutil.getSCSIid(device) 

1112 except: 

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

1114 % device) 

1115 return False 

1116 if not len(scsiID): 

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

1118 % device) 

1119 return False 

1120 

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

1122 try: 

1123 usb_device_with_serial = serial and is_usb_device(device) 

1124 except: 

1125 usb_device_with_serial = False 

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

1127 SMlog(traceback.format_exc()) 

1128 

1129 try: 

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

1131 except: 

1132 raise xs_errors.XenError('APIFailure') 

1133 for SR in SRs: 

1134 record = SRs[SR] 

1135 conf = record["sm_config"] 

1136 if 'devserial' in conf: 

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

1138 if not usb_device_with_serial and _isSCSIid(dev): 

1139 if match_scsiID(dev, scsiID): 

1140 return True 

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

1142 return True 

1143 return False 

1144 

1145 

1146def default(self, field, thunk): 

1147 try: 

1148 return getattr(self, field) 

1149 except: 

1150 return thunk() 

1151 

1152 

1153def list_VDI_records_in_sr(sr): 

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

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

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

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

1158 return vdis 

1159 

1160 

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

1162def diskFromPartition(partition): 

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

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

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

1166 return m.group(2) 

1167 

1168 numlen = 0 # number of digit characters 

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

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

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

1172 

1173 # is it a cciss? 

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

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

1176 

1177 # is it a mapper path? 

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

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

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

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

1182 else: 

1183 numlen = 0 

1184 

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

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

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

1188 

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

1190 

1191 

1192def dom0_disks(): 

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

1194 disks = [] 

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

1196 for line in f: 

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

1198 if mountpoint == '/': 

1199 disk = diskFromPartition(dev) 

1200 if not (disk in disks): 

1201 disks.append(disk) 

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

1203 return disks 

1204 

1205 

1206def set_scheduler_sysfs_node(node, scheds): 

1207 """ 

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

1209 according to prioritized list schedulers 

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

1211 """ 

1212 

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

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

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

1216 return 

1217 

1218 stored_error = None 

1219 for sched in scheds: 

1220 try: 

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

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

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

1224 return 

1225 except (OSError, IOError) as err: 

1226 stored_error = err 

1227 

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

1229 

1230 

1231def set_scheduler(dev, schedulers=None): 

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

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

1234 

1235 devices = [] 

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

1237 # Remove partition numbers 

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

1239 else: 

1240 rawdev = diskFromPartition(dev) 

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

1242 

1243 for d in devices: 

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

1245 

1246 

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

1248def _getVDIs(srobj): 

1249 VDIs = [] 

1250 try: 

1251 sr_ref = getattr(srobj, 'sr_ref') 

1252 except AttributeError: 

1253 return VDIs 

1254 

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

1256 for vdi in refs: 

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

1258 ref['vdi_ref'] = vdi 

1259 VDIs.append(ref) 

1260 return VDIs 

1261 

1262 

1263def _getVDI(srobj, vdi_uuid): 

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

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

1266 ref['vdi_ref'] = vdi 

1267 return ref 

1268 

1269 

1270def _convertDNS(name): 

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

1272 return addr 

1273 

1274 

1275def _containsVDIinuse(srobj): 

1276 VDIs = _getVDIs(srobj) 

1277 for vdi in VDIs: 

1278 if not vdi['managed']: 

1279 continue 

1280 sm_config = vdi['sm_config'] 

1281 if 'SRRef' in sm_config: 

1282 try: 

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

1284 for pbd in PBDs: 

1285 record = PBDs[pbd] 

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

1287 record["currently_attached"]: 

1288 return True 

1289 except: 

1290 pass 

1291 return False 

1292 

1293 

1294def isVDICommand(cmd): 

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

1296 "vdi_activate", "vdi_deactivate", 

1297 "vdi_epoch_begin", "vdi_epoch_end"]: 

1298 return True 

1299 else: 

1300 return False 

1301 

1302 

1303######################### 

1304# Daemon helper functions 

1305def p_id_fork(): 

1306 try: 

1307 p_id = os.fork() 

1308 except OSError as e: 

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

1310 sys.exit(-1) 

1311 

1312 if (p_id == 0): 

1313 os.setsid() 

1314 try: 

1315 p_id = os.fork() 

1316 except OSError as e: 

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

1318 sys.exit(-1) 

1319 if (p_id == 0): 

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

1321 os.umask(0) 

1322 else: 

1323 os._exit(0) 

1324 else: 

1325 os._exit(0) 

1326 

1327 

1328def daemon(): 

1329 p_id_fork() 

1330 # Query the max file descriptor parameter for this process 

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

1332 

1333 # Close any fds that are open 

1334 for fd in range(0, maxfd): 

1335 try: 

1336 os.close(fd) 

1337 except: 

1338 pass 

1339 

1340 # Redirect STDIN to STDOUT and STDERR 

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

1342 os.dup2(0, 1) 

1343 os.dup2(0, 2) 

1344 

1345################################################################################ 

1346# 

1347# Fist points 

1348# 

1349 

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

1351# 

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

1353# on the SR master; 

1354# 

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

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

1357# - otherwise, the function called is _pause. 

1358 

1359def _pause(secs, name): 

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

1361 time.sleep(secs) 

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

1363 

1364 

1365def _exit(name): 

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

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

1368 

1369 

1370class FistPoint: 

1371 def __init__(self, points): 

1372 #SMlog("Fist points loaded") 

1373 self.points = points 

1374 

1375 def is_legal(self, name): 

1376 return (name in self.points) 

1377 

1378 def is_active(self, name): 

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

1380 

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

1382 session = get_localAPI_session() 

1383 try: 

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

1385 

1386 if started: 

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

1388 else: 

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

1390 finally: 

1391 session.xenapi.session.logout() 

1392 

1393 def activate(self, name, sruuid): 

1394 if name in self.points: 

1395 if self.is_active(name): 

1396 self.mark_sr(name, sruuid, True) 

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

1398 self.mark_sr(name, sruuid, False) 

1399 _exit(name) 

1400 else: 

1401 _pause(FIST_PAUSE_PERIOD, name) 

1402 self.mark_sr(name, sruuid, False) 

1403 else: 

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

1405 

1406 def activate_custom_fn(self, name, fn): 

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

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

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

1410 fn() 

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

1412 else: 

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

1414 

1415 

1416def list_find(f, seq): 

1417 for item in seq: 

1418 if f(item): 

1419 return item 

1420 

1421GCPAUSE_FISTPOINT = "GCLoop_no_pause" 

1422 

1423fistpoint = FistPoint(["LVHDRT_finding_a_suitable_pair", 

1424 "LVHDRT_inflating_the_parent", 

1425 "LVHDRT_resizing_while_vdis_are_paused", 

1426 "LVHDRT_coalescing_VHD_data", 

1427 "LVHDRT_coalescing_before_inflate_grandparent", 

1428 "LVHDRT_relinking_grandchildren", 

1429 "LVHDRT_before_create_relink_journal", 

1430 "LVHDRT_xapiSM_serialization_tests", 

1431 "LVHDRT_clone_vdi_after_create_journal", 

1432 "LVHDRT_clone_vdi_after_shrink_parent", 

1433 "LVHDRT_clone_vdi_after_first_snap", 

1434 "LVHDRT_clone_vdi_after_second_snap", 

1435 "LVHDRT_clone_vdi_after_parent_hidden", 

1436 "LVHDRT_clone_vdi_after_parent_ro", 

1437 "LVHDRT_clone_vdi_before_remove_journal", 

1438 "LVHDRT_clone_vdi_after_lvcreate", 

1439 "LVHDRT_clone_vdi_before_undo_clone", 

1440 "LVHDRT_clone_vdi_after_undo_clone", 

1441 "LVHDRT_inflate_after_create_journal", 

1442 "LVHDRT_inflate_after_setSize", 

1443 "LVHDRT_inflate_after_zeroOut", 

1444 "LVHDRT_inflate_after_setSizePhys", 

1445 "LVHDRT_inflate_after_setSizePhys", 

1446 "LVHDRT_coaleaf_before_coalesce", 

1447 "LVHDRT_coaleaf_after_coalesce", 

1448 "LVHDRT_coaleaf_one_renamed", 

1449 "LVHDRT_coaleaf_both_renamed", 

1450 "LVHDRT_coaleaf_after_vdirec", 

1451 "LVHDRT_coaleaf_before_delete", 

1452 "LVHDRT_coaleaf_after_delete", 

1453 "LVHDRT_coaleaf_before_remove_j", 

1454 "LVHDRT_coaleaf_undo_after_rename", 

1455 "LVHDRT_coaleaf_undo_after_rename2", 

1456 "LVHDRT_coaleaf_undo_after_refcount", 

1457 "LVHDRT_coaleaf_undo_after_deflate", 

1458 "LVHDRT_coaleaf_undo_end", 

1459 "LVHDRT_coaleaf_stop_after_recovery", 

1460 "LVHDRT_coaleaf_finish_after_inflate", 

1461 "LVHDRT_coaleaf_finish_end", 

1462 "LVHDRT_coaleaf_delay_1", 

1463 "LVHDRT_coaleaf_delay_2", 

1464 "LVHDRT_coaleaf_delay_3", 

1465 "testsm_clone_allow_raw", 

1466 "xenrt_default_vdi_type_legacy", 

1467 "blktap_activate_inject_failure", 

1468 "blktap_activate_error_handling", 

1469 GCPAUSE_FISTPOINT, 

1470 "cleanup_coalesceVHD_inject_failure", 

1471 "cleanup_tracker_no_progress", 

1472 "FileSR_fail_hardlink", 

1473 "FileSR_fail_snap1", 

1474 "FileSR_fail_snap2", 

1475 "LVM_journaler_exists", 

1476 "LVM_journaler_none", 

1477 "LVM_journaler_badname", 

1478 "LVM_journaler_readfail", 

1479 "LVM_journaler_writefail"]) 

1480 

1481 

1482def set_dirty(session, sr): 

1483 try: 

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

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

1486 except: 

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

1488 

1489 

1490def doesFileHaveOpenHandles(fileName): 

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

1492 (retVal, processAndPidTuples) = \ 

1493 findRunningProcessOrOpenFile(fileName, False) 

1494 

1495 if not retVal: 

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

1497 fileName) 

1498 # err on the side of caution 

1499 return True 

1500 else: 

1501 if len(processAndPidTuples) > 0: 

1502 return True 

1503 else: 

1504 return False 

1505 

1506 

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

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

1509def extractSRFromDevMapper(path): 

1510 try: 

1511 path = os.path.basename(path) 

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

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

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

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

1516 except: 

1517 return '' 

1518 

1519 

1520def pid_is_alive(pid): 

1521 """ 

1522 Try to kill PID with signal 0. 

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

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

1525 signal it. Still return true. 

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

1527 """ 

1528 try: 

1529 os.kill(pid, 0) 

1530 return True 

1531 except OSError as e: 

1532 if e.errno == errno.EPERM: 

1533 return True 

1534 return False 

1535 

1536 

1537# Looks at /proc and figures either 

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

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

1540# returns process names and pids 

1541def findRunningProcessOrOpenFile(name, process=True): 

1542 retVal = True 

1543 links = [] 

1544 processandpids = [] 

1545 sockets = set() 

1546 try: 

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

1548 [name, process]) 

1549 

1550 # Look at all pids 

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

1552 for pid in sorted(pids): 

1553 try: 

1554 try: 

1555 f = None 

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

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

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

1559 # Just want the process name 

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

1561 prog = argv[0] 

1562 except IOError as e: 

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

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

1565 continue 

1566 finally: 

1567 if f is not None: 1567 ↛ 1552,   1567 ↛ 15702 missed branches: 1) line 1567 didn't jump to line 1552, because the continue on line 1565 wasn't executed, 2) line 1567 didn't jump to line 1570, because the condition on line 1567 was never false

1568 f.close() 1568 ↛ 1552line 1568 didn't jump to line 1552, because the continue on line 1565 wasn't executed

1569 

1570 try: 

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

1572 files = os.listdir(fd_dir) 

1573 except OSError as e: 

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

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

1576 # Ignore pid that are no longer valid 

1577 continue 

1578 else: 

1579 raise 

1580 

1581 for file in files: 

1582 try: 

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

1584 except OSError: 

1585 continue 

1586 

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

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

1589 links.append(link) 

1590 else: 

1591 # need to return process name and pid tuples 

1592 if link == name: 

1593 processandpids.append((prog, pid)) 

1594 

1595 # Get the connected sockets 

1596 if name == prog: 

1597 sockets.update(get_connected_sockets(pid)) 

1598 

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

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

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

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

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

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

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

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

1607 

1608 except Exception as e: 

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

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

1611 retVal = False 

1612 

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

1614 return retVal, links, sockets 

1615 else: 

1616 return retVal, processandpids 

1617 

1618 

1619def get_connected_sockets(pid): 

1620 sockets = set() 

1621 try: 

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

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

1624 # - Pointer address to socket (hex) 

1625 # - Refcount (HEX) 

1626 # - 0 

1627 # - State (HEX, 0 or __SO_ACCEPTCON) 

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

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

1630 # - Inode number 

1631 # - Path (optional) 

1632 open_sock_matcher = re.compile( 

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

1634 with open( 

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

1636 lines = f.readlines() 

1637 for line in lines: 

1638 match = open_sock_matcher.match(line) 

1639 if match: 

1640 sockets.add(match[1]) 

1641 except OSError as e: 

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

1643 # Ignore pid that are no longer valid 

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

1645 (e.errno, pid)) 

1646 else: 

1647 raise 

1648 return sockets 

1649 

1650 

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

1652 retries = 0 

1653 while True: 

1654 try: 

1655 return f() 

1656 except Exception as e: 

1657 for exception in exceptions: 

1658 if isinstance(e, exception): 

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

1660 str(e), retries 

1661 )) 

1662 break 

1663 else: 

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

1665 raise e 

1666 

1667 retries += 1 

1668 if retries >= maxretry: 

1669 break 

1670 

1671 time.sleep(period) 

1672 

1673 return f() 

1674 

1675 

1676def getCslDevPath(svid): 

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

1678 if svid.startswith("NETAPP_"): 

1679 # special attention for NETAPP SVIDs 

1680 svid_parts = svid.split("__") 

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

1682 else: 

1683 globstr = basepath + svid + "*" 

1684 

1685 return globstr 

1686 

1687 

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

1689def get_scsiid_from_svid(md_svid): 

1690 cslg_path = getCslDevPath(md_svid) 

1691 abs_path = glob.glob(cslg_path) 

1692 if abs_path: 

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

1694 return scsiutil.getSCSIid(real_path) 

1695 else: 

1696 return None 

1697 

1698 

1699def get_isl_scsiids(session): 

1700 # Get cslg type SRs 

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

1702 

1703 # Iterate through the SR to get the scsi ids 

1704 scsi_id_ret = [] 

1705 for SR in SRs: 

1706 sr_rec = SRs[SR] 

1707 # Use the md_svid to get the scsi id 

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

1709 if scsi_id: 

1710 scsi_id_ret.append(scsi_id) 

1711 

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

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

1714 for vdi_rec in vdi_recs: 

1715 vdi_rec = vdi_recs[vdi_rec] 

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

1717 if scsi_id: 

1718 scsi_id_ret.append(scsi_id) 

1719 

1720 return scsi_id_ret 

1721 

1722 

1723class extractXVA: 

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

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

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

1727 HDR_SIZE = 512 

1728 BLOCK_SIZE = 512 

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

1730 SIZE_OFFSET = 124 

1731 ZERO_FILLED_REC = 2 

1732 NULL_IDEN = '\x00' 

1733 DIR_IDEN = '/' 

1734 CHECKSUM_IDEN = '.checksum' 

1735 OVA_FILE = 'ova.xml' 

1736 

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

1738 # as and when needed 

1739 def __init__(self, filename): 

1740 self.__extract_path = '' 

1741 self.__filename = filename 

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

1743 try: 

1744 self.spawn_p = subprocess.Popen( 

1745 cmd, shell=True, \ 

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

1747 stderr=subprocess.PIPE, close_fds=True) 

1748 except Exception as e: 

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

1750 raise Exception(str(e)) 

1751 

1752 # Create dir to extract the files 

1753 self.__extract_path = tempfile.mkdtemp() 

1754 

1755 def __del__(self): 

1756 shutil.rmtree(self.__extract_path) 

1757 

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

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

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

1761 def getTuple(self): 

1762 zerod_record = 0 

1763 ret_f_name = '' 

1764 ret_base_f_name = '' 

1765 

1766 try: 

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

1768 while True: 

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

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

1771 

1772 # Break out in case of end of file 

1773 if f_hdr == '': 

1774 if zerod_record == extractXVA.ZERO_FILLED_REC: 

1775 break 

1776 else: 

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

1778 extractXVA.ZERO_FILLED_REC) 

1779 raise Exception('Unrecognized end of file') 

1780 

1781 # Watch out for zero records, two zero records 

1782 # denote end of file. 

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

1784 zerod_record += 1 

1785 continue 

1786 

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

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

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

1790 f_size_octal = f_hdr[extractXVA.SIZE_OFFSET: \ 

1791 extractXVA.SIZE_OFFSET + extractXVA.SIZE_LEN] 

1792 f_size = int(f_size_octal, 8) 

1793 if f_name.endswith(extractXVA.CHECKSUM_IDEN): 

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

1795 ret_base_f_name: 

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

1797 yield(ret_f_name, checksum) 

1798 else: 

1799 # Expects file followed by its checksum 

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

1801 ret_f_name) 

1802 raise Exception( \ 

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

1804 ret_f_name) 

1805 else: 

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

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

1808 # read the contents into a file, it will 

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

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

1811 ret_base_f_name = f_name 

1812 

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

1814 # else create it. 

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

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

1817 os.mkdir(folder_path) 

1818 

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

1820 f = open(ret_f_name, 'w') 

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

1822 f.close() 

1823 if f_name == extractXVA.OVA_FILE: 

1824 yield(ret_f_name, '') 

1825 

1826 # Skip zero'd portion of data block 

1827 round_off = f_size % extractXVA.BLOCK_SIZE 

1828 if round_off != 0: 

1829 zeros = self.spawn_p.stdout.read( 

1830 extractXVA.BLOCK_SIZE - round_off) 

1831 except Exception as e: 

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

1833 self.__filename)) 

1834 

1835 # Kill and Drain stdout of the gunzip process, 

1836 # else gunzip might block on stdout 

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

1838 self.spawn_p.communicate() 

1839 raise Exception(str(e)) 

1840 

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

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

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

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

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

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

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

1848 (0x10FFFE, 0x10FFFF)] 

1849 

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

1851 for (low, high) in illegal_xml_chars 

1852 if low < sys.maxunicode] 

1853 

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

1855 

1856 

1857def isLegalXMLString(s): 

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

1859 illegal XML characters specified in 

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

1861 """ 

1862 

1863 if len(s) > 0: 

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

1865 else: 

1866 return True 

1867 

1868 

1869def unictrunc(string, max_bytes): 

1870 """ 

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

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

1873 more than the given number of bytes. 

1874 

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

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

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

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

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

1880 

1881 string: the string to truncate 

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

1883 """ 

1884 if isinstance(string, str): 

1885 return_chars = True 

1886 else: 

1887 return_chars = False 

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

1889 

1890 cur_chars = 0 

1891 cur_bytes = 0 

1892 for char in string: 

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

1894 if cur_bytes + charsize > max_bytes: 

1895 break 

1896 else: 

1897 cur_chars += 1 

1898 cur_bytes += charsize 

1899 return cur_chars if return_chars else cur_bytes 

1900 

1901 

1902def hideValuesInPropMap(propmap, propnames): 

1903 """ 

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

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

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

1907 value and return the altered map. 

1908 If none found, return the original map 

1909 """ 

1910 matches = [] 

1911 for propname in propnames: 

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

1913 matches.append(propname) 

1914 

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

1916 deepCopyRec = copy.deepcopy(propmap) 

1917 for match in matches: 

1918 deepCopyRec[match] = '******' 

1919 return deepCopyRec 

1920 

1921 return propmap 

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

1923 

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

1925DEFAULT_SEGMENT_LEN = 950 

1926 

1927 

1928def hidePasswdInConfig(config): 

1929 """ 

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

1931 for example "device_config" 

1932 """ 

1933 return hideValuesInPropMap(config, PASSWD_PROP_KEYS) 

1934 

1935 

1936def hidePasswdInParams(params, configProp): 

1937 """ 

1938 Function to hide password values in a specified property which 

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

1940 in a larger property map. 

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

1942 "sm_config", etc 

1943 """ 

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

1945 return params 

1946 

1947 

1948def hideMemberValuesInXmlParams(xmlParams, propnames=PASSWD_PROP_KEYS): 

1949 """ 

1950 Function to hide password values in XML params, specifically 

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

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

1953 whose values we want to hide, and: 

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

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

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

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

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

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

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

1961 """ 

1962 findStrPrefixHead = "<member><name>" 

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

1964 findStrSuffix = "</value>" 

1965 strlen = len(xmlParams) 

1966 

1967 for propname in propnames: 

1968 findStrPrefix = findStrPrefixHead + propname + findStrPrefixTail 

1969 idx = xmlParams.find(findStrPrefix) 

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

1971 idx += len(findStrPrefix) 

1972 idx2 = xmlParams.find(findStrSuffix, idx) 

1973 if idx2 != -1: 

1974 retStr = xmlParams[0:idx] 

1975 retStr += "******" 

1976 retStr += xmlParams[idx2:strlen] 

1977 return retStr 

1978 else: 

1979 return xmlParams 

1980 return xmlParams 

1981 

1982 

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

1984 """ 

1985 Split xml string data into substrings small enough for the 

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

1987 Usage: 

1988 strList = [] 

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

1990 """ 

1991 remainingData = str(xmlData) 

1992 

1993 # "Un-pretty-print" 

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

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

1996 

1997 remainingChars = len(remainingData) 

1998 returnData = '' 

1999 

2000 thisLineNum = 0 

2001 while remainingChars > segmentLen: 

2002 thisLineNum = thisLineNum + 1 

2003 index = segmentLen 

2004 tmpStr = remainingData[:segmentLen] 

2005 tmpIndex = tmpStr.rfind('>') 

2006 if tmpIndex != -1: 

2007 index = tmpIndex + 1 

2008 

2009 tmpStr = tmpStr[:index] 

2010 remainingData = remainingData[index:] 

2011 remainingChars = len(remainingData) 

2012 

2013 if showContd: 

2014 if thisLineNum != 1: 

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

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

2017 

2018 returnData += tmpStr + '\n' 

2019 

2020 if showContd and thisLineNum > 0: 

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

2022 returnData += remainingData 

2023 

2024 return returnData 

2025 

2026 

2027def inject_failure(): 

2028 raise Exception('injected failure') 

2029 

2030 

2031def open_atomic(path, mode=None): 

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

2033 Leaves the file open and returns the file object. 

2034 

2035 path: the path to atomically open 

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

2037 returns: an open file object""" 

2038 

2039 assert path 

2040 

2041 flags = os.O_CREAT | os.O_EXCL 

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

2043 if mode: 

2044 if mode not in modes: 

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

2046 flags |= modes[mode] 

2047 fd = os.open(path, flags) 

2048 try: 

2049 if mode: 

2050 return os.fdopen(fd, mode) 

2051 else: 

2052 return os.fdopen(fd) 

2053 except: 

2054 os.close(fd) 

2055 raise 

2056 

2057 

2058def isInvalidVDI(exception): 

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

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

2061 

2062 

2063def get_pool_restrictions(session): 

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

2065 established.""" 

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

2067 

2068 

2069def read_caching_is_restricted(session): 

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

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

2072 return True 

2073 restrictions = get_pool_restrictions(session) 

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

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

2076 return True 

2077 return False 

2078 

2079 

2080def sessions_less_than_targets(other_config, device_config): 

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

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

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

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

2085 return (sessions < targets) 

2086 else: 

2087 return False 

2088 

2089 

2090def enable_and_start_service(name, start): 

2091 attempt = 0 

2092 while True: 

2093 attempt += 1 

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

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

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

2097 if ret == 0: 

2098 return 

2099 elif attempt >= 3: 

2100 raise Exception( 

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

2102 ) 

2103 time.sleep(1) 

2104 

2105 

2106def stop_service(name): 

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

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

2109 if ret == 0: 

2110 return 

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

2112 

2113 

2114def restart_service(name): 

2115 attempt = 0 

2116 while True: 

2117 attempt += 1 

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

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

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

2121 if ret == 0: 

2122 return 

2123 elif attempt >= 3: 

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

2125 raise Exception( 

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

2127 ) 

2128 time.sleep(1) 

2129 

2130 

2131def check_pid_exists(pid): 

2132 try: 

2133 os.kill(pid, 0) 

2134 except OSError: 

2135 return False 

2136 else: 

2137 return True 

2138 

2139 

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

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

2142 

2143 try: 

2144 list = [] 

2145 ret = pread2(cmd) 

2146 for line in ret.splitlines(): 

2147 list.append(int(line)) 

2148 return list 

2149 except CommandException as e: 

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

2151 return None 

2152 else: 

2153 raise e 

2154 

2155 

2156def make_profile(name, function): 

2157 """ 

2158 Helper to execute cProfile using unique log file. 

2159 """ 

2160 

2161 import cProfile 

2162 import itertools 

2163 import os.path 

2164 import time 

2165 

2166 assert name 

2167 assert function 

2168 

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

2170 makedirs(FOLDER) 

2171 

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

2173 

2174 def gen_path(path): 

2175 yield path 

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

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

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

2179 

2180 for profile_path in gen_path(FOLDER + filename): 

2181 try: 

2182 file = open_atomic(profile_path, 'w') 

2183 file.close() 

2184 break 

2185 except OSError as e: 

2186 if e.errno == errno.EEXIST: 

2187 pass 

2188 else: 

2189 raise 

2190 

2191 try: 

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

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

2194 finally: 

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

2196 

2197 

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

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

2200 # There is not alternative for strtobool. 

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

2202 # So this is a custom implementation with differences: 

2203 # - A boolean is returned instead of integer 

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

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

2206 return False 

2207 str = str.lower() 

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

2209 return True 

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

2211 return False 

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

2213 

2214 

2215def find_executable(name): 

2216 return shutil.which(name) 

2217 

2218 

2219def conditional_decorator(decorator, condition): 

2220 def wrapper(func): 

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

2222 return func 

2223 return decorator(func) 

2224 return wrapper