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#!/usr/bin/python3 

2# 

3# Copyright (C) Citrix Systems Inc. 

4# 

5# This program is free software; you can redistribute it and/or modify 

6# it under the terms of the GNU Lesser General Public License as published 

7# by the Free Software Foundation; version 2.1 only. 

8# 

9# This program is distributed in the hope that it will be useful, 

10# but WITHOUT ANY WARRANTY; without even the implied warranty of 

11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

12# GNU Lesser General Public License for more details. 

13# 

14# You should have received a copy of the GNU Lesser General Public License 

15# along with this program; if not, write to the Free Software Foundation, Inc., 

16# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 

17# 

18# blktap2: blktap/tapdisk management layer 

19# 

20 

21from sm_typing import Any, Callable, ClassVar, Dict, override, List, Union 

22 

23from abc import abstractmethod 

24 

25import grp 

26import os 

27import re 

28import stat 

29import time 

30import copy 

31from lock import Lock 

32import util 

33import xmlrpc.client 

34import http.client 

35import errno 

36import signal 

37import subprocess 

38import syslog as _syslog 

39import glob 

40import json 

41import xs_errors 

42import XenAPI # pylint: disable=import-error 

43import scsiutil 

44from constants import NS_PREFIX_LVM 

45from syslog import openlog, syslog 

46from stat import * # S_ISBLK(), ... 

47from vditype import VdiType 

48 

49import resetvdis 

50 

51import VDI as sm 

52 

53from cowutil import getCowUtil 

54 

55# For RRDD Plugin Registration 

56from xmlrpc.client import ServerProxy, Transport 

57from socket import socket, AF_UNIX, SOCK_STREAM 

58 

59 

60try: 

61 from linstorvolumemanager import log_drbd_openers 

62 LINSTOR_AVAILABLE = True 

63except ImportError: 

64 LINSTOR_AVAILABLE = False 

65 

66PLUGIN_TAP_PAUSE = "tapdisk-pause" 

67PLUGIN_ON_SLAVE = "on-slave" 

68 

69SOCKPATH = "/var/xapi/xcp-rrdd" 

70 

71NUM_PAGES_PER_RING = 32 * 11 

72MAX_FULL_RINGS = 8 

73POOL_NAME_KEY = "mem-pool" 

74POOL_SIZE_KEY = "mem-pool-size-rings" 

75 

76ENABLE_MULTIPLE_ATTACH = "/etc/xensource/allow_multiple_vdi_attach" 

77NO_MULTIPLE_ATTACH = not (os.path.exists(ENABLE_MULTIPLE_ATTACH)) 

78 

79 

80def locking(excType, override=True): 

81 def locking2(op): 

82 def wrapper(self, *args): 

83 self.lock.acquire() 

84 try: 

85 try: 

86 ret = op(self, * args) 

87 except (util.CommandException, util.SMException, XenAPI.Failure) as e: 87 ↛ 97line 87 didn't jump to line 97

88 util.logException("BLKTAP2:%s" % op) 

89 msg = str(e) 

90 if isinstance(e, util.CommandException): 90 ↛ 91line 90 didn't jump to line 91, because the condition on line 90 was never true

91 msg = "Command %s failed (%s): %s" % \ 

92 (e.cmd, e.code, e.reason) 

93 if override: 93 ↛ 96line 93 didn't jump to line 96, because the condition on line 93 was never false

94 raise xs_errors.XenError(excType, opterr=msg) 

95 else: 

96 raise 

97 except: 

98 util.logException("BLKTAP2:%s" % op) 

99 raise 

100 finally: 

101 self.lock.release() 

102 return ret 

103 return wrapper 

104 return locking2 

105 

106 

107class RetryLoop(object): 

108 

109 def __init__(self, backoff, limit): 

110 self.backoff = backoff 

111 self.limit = limit 

112 

113 def __call__(self, f): 

114 

115 def loop(*__t, **__d): 

116 attempt = 0 

117 

118 while True: 

119 attempt += 1 

120 

121 try: 

122 return f( * __t, ** __d) 

123 

124 except self.TransientFailure as e: 

125 e = e.exception 

126 

127 if attempt >= self.limit: 127 ↛ 128line 127 didn't jump to line 128, because the condition on line 127 was never true

128 raise e 

129 

130 time.sleep(self.backoff) 

131 

132 return loop 

133 

134 class TransientFailure(Exception): 

135 def __init__(self, exception): 

136 self.exception = exception 

137 

138 

139def retried(**args): 

140 return RetryLoop( ** args) 

141 

142 

143class TapCtl(object): 

144 """Tapdisk IPC utility calls.""" 

145 

146 PATH = "/usr/sbin/tap-ctl" 

147 

148 def __init__(self, cmd, p): 

149 self.cmd = cmd 

150 self._p = p 

151 self.stdout = p.stdout 

152 

153 class CommandFailure(Exception): 

154 """TapCtl cmd failure.""" 

155 

156 def __init__(self, cmd, **info): 

157 self.cmd = cmd 

158 self.info = info 

159 

160 @override 

161 def __str__(self) -> str: 

162 items = self.info.items() 

163 info = ", ".join("%s=%s" % item 

164 for item in items) 

165 return "%s failed: %s" % (self.cmd, info) 

166 

167 # Trying to get a non-existent attribute throws an AttributeError 

168 # exception 

169 def __getattr__(self, key): 

170 if key in self.info: 170 ↛ 172line 170 didn't jump to line 172, because the condition on line 170 was never false

171 return self.info[key] 

172 return object.__getattribute__(self, key) 

173 

174 @property 

175 def has_status(self): 

176 return 'status' in self.info 

177 

178 @property 

179 def has_signal(self): 

180 return 'signal' in self.info 

181 

182 # Retrieves the error code returned by the command. If the error code 

183 # was not supplied at object-construction time, zero is returned. 

184 def get_error_code(self): 

185 key = 'status' 

186 if key in self.info: 186 ↛ 189line 186 didn't jump to line 189, because the condition on line 186 was never false

187 return self.info[key] 

188 else: 

189 return 0 

190 

191 @classmethod 

192 def __mkcmd_real(cls, args): 

193 return [cls.PATH] + [str(x) for x in args] 

194 

195 __next_mkcmd = __mkcmd_real 

196 

197 @classmethod 

198 def _mkcmd(cls, args): 

199 

200 __next_mkcmd = cls.__next_mkcmd 

201 cls.__next_mkcmd = cls.__mkcmd_real 

202 

203 return __next_mkcmd(args) 

204 

205 @classmethod 

206 def _call(cls, args, quiet=False, input=None, text_mode=True): 

207 """ 

208 Spawn a tap-ctl process. Return a TapCtl invocation. 

209 Raises a TapCtl.CommandFailure if subprocess creation failed. 

210 """ 

211 cmd = cls._mkcmd(args) 

212 

213 if not quiet: 

214 util.SMlog(cmd) 

215 try: 

216 p = subprocess.Popen(cmd, 

217 stdin=subprocess.PIPE, 

218 stdout=subprocess.PIPE, 

219 stderr=subprocess.PIPE, 

220 close_fds=True, 

221 universal_newlines=text_mode) 

222 if input: 

223 p.stdin.write(input) 

224 except OSError as e: 

225 raise cls.CommandFailure(cmd, errno=e.errno) 

226 

227 return cls(cmd, p) 

228 

229 def _errmsg(self, stderr): 

230 output = map(str.rstrip, stderr) 

231 return "; ".join(output) 

232 

233 def _wait(self, quiet=False, text_mode=True): 

234 """ 

235 Reap the child tap-ctl process of this invocation. 

236 Raises a TapCtl.CommandFailure on non-zero exit status. 

237 """ 

238 stdout, stderr = self._p.communicate() 

239 status = self._p.returncode 

240 if not quiet: 

241 util.SMlog(" = %d" % status) 

242 

243 if status == 0: 

244 return stdout 

245 

246 info = {'errmsg': self._errmsg( 

247 stderr if text_mode else stderr.decode()), 

248 'pid': self._p.pid} 

249 

250 if status < 0: 

251 info['signal'] = -status 

252 else: 

253 info['status'] = status 

254 

255 raise self.CommandFailure(self.cmd, ** info) 

256 

257 @classmethod 

258 def _pread(cls, args, quiet=False, input=None, text_mode=True): 

259 """ 

260 Spawn a tap-ctl invocation and read a single line. 

261 """ 

262 tapctl = cls._call(args=args, quiet=quiet, input=input, 

263 text_mode=text_mode) 

264 

265 output = tapctl._wait(quiet=quiet, text_mode=text_mode) 

266 return output 

267 

268 @staticmethod 

269 def _maybe(opt, parm): 

270 if parm is not None: 

271 return [opt, parm] 

272 return [] 

273 

274 @classmethod 

275 def __list(cls, minor=None, pid=None, _type=None, path=None): 

276 args = ["list"] 

277 args += cls._maybe("-m", minor) 

278 args += cls._maybe("-p", pid) 

279 args += cls._maybe("-t", _type) 

280 args += cls._maybe("-f", path) 

281 

282 tapctl = cls._call(args, quiet=True) 

283 stdout = tapctl._wait(quiet=True) 

284 

285 for stdout_line in stdout.splitlines(): 

286 # FIXME: tap-ctl writes error messages to stdout and 

287 # confuses this parser 

288 if stdout_line == "blktap kernel module not installed\n": 288 ↛ 291line 288 didn't jump to line 291, because the condition on line 288 was never true

289 # This isn't pretty but (a) neither is confusing stdout/stderr 

290 # and at least causes the error to describe the fix 

291 raise Exception("blktap kernel module not installed: try 'modprobe blktap'") 

292 row = {} 

293 

294 for field in stdout_line.rstrip().split(' ', 3): 

295 bits = field.split('=') 

296 if len(bits) == 2: 296 ↛ 308line 296 didn't jump to line 308, because the condition on line 296 was never false

297 key, val = field.split('=') 

298 

299 if key in ('pid', 'minor'): 

300 row[key] = int(val, 10) 

301 

302 elif key in ('state'): 

303 row[key] = int(val, 0x10) 

304 

305 else: 

306 row[key] = val 

307 else: 

308 util.SMlog("Ignoring unexpected tap-ctl output: %s" % repr(field)) 

309 yield row 

310 

311 @classmethod 

312 @retried(backoff=.5, limit=10) 

313 def list(cls, **args): 

314 

315 # FIXME. We typically get an EPROTO when uevents interleave 

316 # with SM ops and a tapdisk shuts down under our feet. Should 

317 # be fixed in SM. 

318 

319 try: 

320 return list(cls.__list( ** args)) 

321 

322 except cls.CommandFailure as e: 

323 transient = [errno.EPROTO, errno.ENOENT] 

324 if e.has_status and e.status in transient: 

325 raise RetryLoop.TransientFailure(e) 

326 raise 

327 

328 @classmethod 

329 def allocate(cls, devpath=None): 

330 args = ["allocate"] 

331 args += cls._maybe("-d", devpath) 

332 return cls._pread(args) 

333 

334 @classmethod 

335 def free(cls, minor): 

336 args = ["free", "-m", minor] 

337 cls._pread(args) 

338 

339 @classmethod 

340 @retried(backoff=.5, limit=10) 

341 def spawn(cls): 

342 args = ["spawn"] 

343 try: 

344 pid = cls._pread(args) 

345 return int(pid) 

346 except cls.CommandFailure as ce: 

347 # intermittent failures to spawn. CA-292268 

348 if ce.status == 1: 

349 raise RetryLoop.TransientFailure(ce) 

350 raise 

351 

352 @classmethod 

353 def attach(cls, pid, minor): 

354 args = ["attach", "-p", pid, "-m", minor] 

355 cls._pread(args) 

356 

357 @classmethod 

358 def detach(cls, pid, minor): 

359 args = ["detach", "-p", pid, "-m", minor] 

360 cls._pread(args) 

361 

362 @classmethod 

363 def _load_key(cls, key_hash, vdi_uuid): 

364 import plugins 

365 

366 return plugins.load_key(key_hash, vdi_uuid) 

367 

368 @classmethod 

369 def open(cls, pid, minor, _type, _file, options): 

370 params = Tapdisk.Arg(_type, _file) 

371 args = ["open", "-p", pid, "-m", minor, '-a', str(params)] 

372 text_mode = True 

373 input = None 

374 if options.get("rdonly"): 

375 args.append('-R') 

376 if options.get("lcache"): 

377 args.append("-r") 

378 if options.get("existing_prt") is not None: 

379 args.append("-e") 

380 args.append(str(options["existing_prt"])) 

381 if options.get("secondary"): 

382 args.append("-2") 

383 args.append(options["secondary"]) 

384 if options.get("standby"): 

385 args.append("-s") 

386 if options.get("timeout"): 

387 args.append("-t") 

388 args.append(str(options["timeout"])) 

389 if not options.get("o_direct", True): 

390 args.append("-D") 

391 if options.get('cbtlog'): 

392 args.extend(['-C', options['cbtlog']]) 

393 if options.get('key_hash'): 

394 key_hash = options['key_hash'] 

395 vdi_uuid = options['vdi_uuid'] 

396 key = cls._load_key(key_hash, vdi_uuid) 

397 

398 if not key: 

399 raise util.SMException("No key found with key hash {}".format(key_hash)) 

400 input = key 

401 text_mode = False 

402 args.append('-E') 

403 

404 cls._pread(args=args, input=input, text_mode=text_mode) 

405 

406 @classmethod 

407 def close(cls, pid, minor, force=False): 

408 args = ["close", "-p", pid, "-m", minor, "-t", "120"] 

409 if force: 

410 args += ["-f"] 

411 cls._pread(args) 

412 

413 @classmethod 

414 def pause(cls, pid, minor): 

415 args = ["pause", "-p", pid, "-m", minor] 

416 cls._pread(args) 

417 

418 @classmethod 

419 def unpause(cls, pid, minor, _type=None, _file=None, mirror=None, 

420 cbtlog=None): 

421 args = ["unpause", "-p", pid, "-m", minor] 

422 if mirror: 

423 args.extend(["-2", mirror]) 

424 if _type and _file: 

425 params = Tapdisk.Arg(_type, _file) 

426 args += ["-a", str(params)] 

427 if cbtlog: 

428 args.extend(["-c", cbtlog]) 

429 cls._pread(args) 

430 

431 @classmethod 

432 def shutdown(cls, pid): 

433 # TODO: This should be a real tap-ctl command 

434 os.kill(pid, signal.SIGTERM) 

435 os.waitpid(pid, 0) 

436 

437 @classmethod 

438 def stats(cls, pid, minor): 

439 args = ["stats", "-p", pid, "-m", minor] 

440 return cls._pread(args, quiet=True) 

441 

442 @classmethod 

443 def major(cls): 

444 args = ["major"] 

445 major = cls._pread(args) 

446 return int(major) 

447 

448 @classmethod 

449 def commit(cls, pid, minor, vdi_type, path): 

450 args = ["commit", "-p", pid, "-m", minor, "-a", path] 

451 cls._pread(args) 

452 

453 @classmethod 

454 def query(cls, pid, minor, quiet=False): 

455 args = ["query", "-p", pid, "-m", minor] 

456 output = cls._pread(args, quiet=quiet) 

457 m = re.match(r"Commit status '(.+)' \((\d+)\/(\d+)\)", output) 

458 status = m.group(1) 

459 coalesced = int(m.group(2)) 

460 total_coalesce = int(m.group(3)) 

461 return (status, coalesced, total_coalesce) 

462 

463 @classmethod 

464 def cancel_commit(cls, pid, minor, wait=True): 

465 args = ["cancel", "-p", pid, "-m", minor] 

466 if wait: 

467 args.append("-w") 

468 cls._pread(args) 

469 

470class TapdiskExists(Exception): 

471 """Tapdisk already running.""" 

472 

473 def __init__(self, tapdisk): 

474 self.tapdisk = tapdisk 

475 

476 @override 

477 def __str__(self) -> str: 

478 return "%s already running" % self.tapdisk 

479 

480 

481class TapdiskNotRunning(Exception): 

482 """No such Tapdisk.""" 

483 

484 def __init__(self, **attrs): 

485 self.attrs = attrs 

486 

487 @override 

488 def __str__(self) -> str: 

489 items = iter(self.attrs.items()) 

490 attrs = ", ".join("%s=%s" % attr 

491 for attr in items) 

492 return "No such Tapdisk(%s)" % attrs 

493 

494 

495class TapdiskNotUnique(Exception): 

496 """More than one tapdisk on one path.""" 

497 

498 def __init__(self, tapdisks): 

499 self.tapdisks = tapdisks 

500 

501 @override 

502 def __str__(self) -> str: 

503 tapdisks = map(str, self.tapdisks) 

504 return "Found multiple tapdisks: %s" % tapdisks 

505 

506 

507class TapdiskFailed(Exception): 

508 """Tapdisk launch failure.""" 

509 

510 def __init__(self, arg, err): 

511 self.arg = arg 

512 self.err = err 

513 

514 @override 

515 def __str__(self) -> str: 

516 return "Tapdisk(%s): %s" % (self.arg, self.err) 

517 

518 def get_error(self): 

519 return self.err 

520 

521 

522class TapdiskInvalidState(Exception): 

523 """Tapdisk pause/unpause failure""" 

524 

525 def __init__(self, tapdisk): 

526 self.tapdisk = tapdisk 

527 

528 @override 

529 def __str__(self) -> str: 

530 return str(self.tapdisk) 

531 

532 

533def mkdirs(path, mode=0o777): 

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

535 parent, subdir = os.path.split(path) 

536 assert parent != path 

537 try: 

538 if parent: 

539 mkdirs(parent, mode) 

540 if subdir: 

541 os.mkdir(path, mode) 

542 except OSError as e: 

543 if e.errno != errno.EEXIST: 

544 raise 

545 

546 

547class KObject(object): 

548 

549 SYSFS_CLASSTYPE: ClassVar[str] = "" 

550 

551 @abstractmethod 

552 def sysfs_devname(self) -> str: 

553 pass 

554 

555 

556class Attribute(object): 

557 

558 SYSFS_NODENAME: ClassVar[str] = "" 

559 

560 def __init__(self, path): 

561 self.path = path 

562 

563 @classmethod 

564 def from_kobject(cls, kobj): 

565 path = "%s/%s" % (kobj.sysfs_path(), cls.SYSFS_NODENAME) 

566 return cls(path) 

567 

568 class NoSuchAttribute(Exception): 

569 def __init__(self, name): 

570 self.name = name 

571 

572 @override 

573 def __str__(self) -> str: 

574 return "No such attribute: %s" % self.name 

575 

576 def _open(self, mode='r'): 

577 try: 

578 return open(self.path, mode) 

579 except IOError as e: 

580 if e.errno == errno.ENOENT: 

581 raise self.NoSuchAttribute(self) 

582 raise 

583 

584 def readline(self): 

585 f = self._open('r') 

586 s = f.readline().rstrip() 

587 f.close() 

588 return s 

589 

590 def writeline(self, val): 

591 f = self._open('w') 

592 f.write(val) 

593 f.close() 

594 

595 

596class ClassDevice(KObject): 

597 

598 @classmethod 

599 def sysfs_class_path(cls): 

600 return "/sys/class/%s" % cls.SYSFS_CLASSTYPE 

601 

602 def sysfs_path(self): 

603 return "%s/%s" % (self.sysfs_class_path(), 

604 self.sysfs_devname()) 

605 

606 

607class Blktap(ClassDevice): 

608 

609 DEV_BASEDIR = '/dev/xen/blktap-2' 

610 

611 SYSFS_CLASSTYPE = "blktap2" 

612 

613 def __init__(self, minor): 

614 self.minor = minor 

615 self._pool = None 

616 self._task = None 

617 

618 @classmethod 

619 def allocate(cls): 

620 # FIXME. Should rather go into init. 

621 mkdirs(cls.DEV_BASEDIR) 

622 

623 devname = TapCtl.allocate() 

624 minor = Tapdisk._parse_minor(devname) 

625 return cls(minor) 

626 

627 def free(self): 

628 TapCtl.free(self.minor) 

629 

630 @override 

631 def __str__(self) -> str: 

632 return "%s(minor=%d)" % (self.__class__.__name__, self.minor) 

633 

634 @override 

635 def sysfs_devname(self) -> str: 

636 return "blktap!blktap%d" % self.minor 

637 

638 class Pool(Attribute): 

639 SYSFS_NODENAME = "pool" 

640 

641 def get_pool_attr(self): 

642 if not self._pool: 

643 self._pool = self.Pool.from_kobject(self) 

644 return self._pool 

645 

646 def get_pool_name(self): 

647 return self.get_pool_attr().readline() 

648 

649 def set_pool_name(self, name): 

650 self.get_pool_attr().writeline(name) 

651 

652 def set_pool_size(self, pages): 

653 self.get_pool().set_size(pages) 

654 

655 def get_pool(self): 

656 return BlktapControl.get_pool(self.get_pool_name()) 

657 

658 def set_pool(self, pool): 

659 self.set_pool_name(pool.name) 

660 

661 class Task(Attribute): 

662 SYSFS_NODENAME = "task" 

663 

664 def get_task_attr(self): 

665 if not self._task: 

666 self._task = self.Task.from_kobject(self) 

667 return self._task 

668 

669 def get_task_pid(self): 

670 pid = self.get_task_attr().readline() 

671 try: 

672 return int(pid) 

673 except ValueError: 

674 return None 

675 

676 def find_tapdisk(self): 

677 pid = self.get_task_pid() 

678 if pid is None: 

679 return None 

680 

681 return Tapdisk.find(pid=pid, minor=self.minor) 

682 

683 def get_tapdisk(self): 

684 tapdisk = self.find_tapdisk() 

685 if not tapdisk: 

686 raise TapdiskNotRunning(minor=self.minor) 

687 return tapdisk 

688 

689 

690class Tapdisk(object): 

691 

692 TYPES = ['aio', 'vhd', 'qcow2'] 

693 

694 def __init__(self, pid, minor, _type, path, state): 

695 self.pid = pid 

696 self.minor = minor 

697 self.type = _type 

698 self.path = path 

699 self.state = state 

700 self._dirty = False 

701 self._blktap = None 

702 

703 @override 

704 def __str__(self) -> str: 

705 state = self.pause_state() 

706 return "Tapdisk(%s, pid=%d, minor=%s, state=%s)" % \ 

707 (self.get_arg(), self.pid, self.minor, state) 

708 

709 @classmethod 

710 def list(cls, **args): 

711 

712 for row in TapCtl.list( ** args): 

713 

714 args = {'pid': None, 

715 'minor': None, 

716 'state': None, 

717 '_type': None, 

718 'path': None} 

719 

720 for key, val in row.items(): 

721 if key in args: 

722 args[key] = val 

723 

724 if 'args' in row: 724 ↛ 729line 724 didn't jump to line 729, because the condition on line 724 was never false

725 image = Tapdisk.Arg.parse(row['args']) 

726 args['_type'] = image.type 

727 args['path'] = image.path 

728 

729 if None in args.values(): 729 ↛ 730line 729 didn't jump to line 730, because the condition on line 729 was never true

730 continue 

731 

732 yield Tapdisk( ** args) 

733 

734 @classmethod 

735 def find(cls, **args): 

736 

737 found = list(cls.list( ** args)) 

738 

739 if len(found) > 1: 739 ↛ 740line 739 didn't jump to line 740, because the condition on line 739 was never true

740 raise TapdiskNotUnique(found) 

741 

742 if found: 

743 return found[0] 

744 

745 return None 

746 

747 @classmethod 

748 def find_by_path(cls, path): 

749 return cls.find(path=path) 

750 

751 @classmethod 

752 def find_by_minor(cls, minor): 

753 return cls.find(minor=minor) 

754 

755 @classmethod 

756 def get(cls, **attrs): 

757 

758 tapdisk = cls.find( ** attrs) 

759 

760 if not tapdisk: 760 ↛ 761line 760 didn't jump to line 761, because the condition on line 760 was never true

761 raise TapdiskNotRunning( ** attrs) 

762 

763 return tapdisk 

764 

765 @classmethod 

766 def from_path(cls, path): 

767 return cls.get(path=path) 

768 

769 @classmethod 

770 def get_pid_for_path(cls, path: str) -> str: 

771 return util.pread2(['/usr/sbin/lsof', '-t', path]).strip() 

772 

773 @classmethod 

774 def from_minor(cls, minor): 

775 pid = None 

776 dev_path = os.path.join(Blktap.DEV_BASEDIR, f"blktap{minor}") 

777 if os.path.exists(dev_path): 777 ↛ 780line 777 didn't jump to line 780, because the condition on line 777 was never false

778 pid = cls.get_pid_for_path(dev_path) 

779 

780 return cls.get(minor=minor, pid=pid) 

781 

782 @classmethod 

783 def __from_blktap(cls, blktap): 

784 tapdisk = cls.from_minor(minor=blktap.minor) 

785 tapdisk._blktap = blktap 

786 return tapdisk 

787 

788 def get_blktap(self): 

789 if not self._blktap: 

790 self._blktap = Blktap(self.minor) 

791 return self._blktap 

792 

793 class Arg: 

794 

795 def __init__(self, _type, path): 

796 self.type = _type 

797 self.path = path 

798 

799 @override 

800 def __str__(self) -> str: 

801 return "%s:%s" % (self.type, self.path) 

802 

803 @classmethod 

804 def parse(cls, arg): 

805 

806 try: 

807 _type, path = arg.split(":", 1) 

808 except ValueError: 

809 raise cls.InvalidArgument(arg) 

810 

811 if _type not in Tapdisk.TYPES: 811 ↛ 812line 811 didn't jump to line 812, because the condition on line 811 was never true

812 raise cls.InvalidType(_type) 

813 

814 return cls(_type, path) 

815 

816 class InvalidType(Exception): 

817 def __init__(self, _type): 

818 self.type = _type 

819 

820 @override 

821 def __str__(self) -> str: 

822 return "Not a Tapdisk type: %s" % self.type 

823 

824 class InvalidArgument(Exception): 

825 def __init__(self, arg): 

826 self.arg = arg 

827 

828 @override 

829 def __str__(self) -> str: 

830 return "Not a Tapdisk image: %s" % self.arg 

831 

832 def get_arg(self): 

833 return self.Arg(self.type, self.path) 

834 

835 def get_devpath(self): 

836 return "%s/tapdev%d" % (Blktap.DEV_BASEDIR, self.minor) 

837 

838 @classmethod 

839 def launch_from_arg(cls, arg): 

840 arg = cls.Arg.parse(arg) 

841 return cls.launch(arg.path, arg.type, False) 

842 

843 @staticmethod 

844 def cgclassify(pid): 

845 

846 # We dont provide any <controllers>:<path> 

847 # so cgclassify uses /etc/cgrules.conf which 

848 # we have configured in the spec file. 

849 cmd = ["cgclassify", str(pid)] 

850 try: 

851 util.pread2(cmd) 

852 except util.CommandException as e: 

853 util.logException(e) 

854 

855 @classmethod 

856 def launch_on_tap(cls, blktap, path, _type, options): 

857 

858 tapdisk = cls.find_by_path(path) 

859 if tapdisk: 859 ↛ 860line 859 didn't jump to line 860, because the condition on line 859 was never true

860 raise TapdiskExists(tapdisk) 

861 

862 minor = blktap.minor 

863 try: 

864 pid = TapCtl.spawn() 

865 cls.cgclassify(pid) 

866 try: 

867 TapCtl.attach(pid, minor) 

868 

869 try: 

870 retry_open = 0 

871 while True: 

872 try: 

873 TapCtl.open(pid, minor, _type, path, options) 

874 break 

875 except TapCtl.CommandFailure as e: 

876 err = ( 

877 'status' in e.info and e.info['status'] 

878 ) or None 

879 if err in (errno.EIO, errno.EROFS, errno.EAGAIN): 879 ↛ 880line 879 didn't jump to line 880, because the condition on line 879 was never true

880 if retry_open < 5: 

881 retry_open += 1 

882 time.sleep(1) 

883 continue 

884 if LINSTOR_AVAILABLE and err == errno.EROFS: 

885 log_drbd_openers(path) 

886 raise 

887 try: 

888 tapdisk = cls.__from_blktap(blktap) 

889 node = '/sys/dev/block/%d:%d' % (tapdisk.major(), tapdisk.minor) 

890 util.set_scheduler_sysfs_node(node, ['none', 'noop']) 

891 return tapdisk 

892 except: 

893 TapCtl.close(pid, minor) 

894 raise 

895 

896 except: 

897 TapCtl.detach(pid, minor) 

898 raise 

899 

900 except: 

901 try: 

902 TapCtl.shutdown(pid) 

903 except: 

904 # Best effort to shutdown 

905 pass 

906 raise 

907 

908 except TapCtl.CommandFailure as ctl: 

909 util.logException(ctl) 

910 if ((path.startswith('/dev/xapi/cd/') or path.startswith('/dev/sr')) and 910 ↛ 914line 910 didn't jump to line 914, because the condition on line 910 was never false

911 ctl.has_status and ctl.get_error_code() == 123): # ENOMEDIUM (No medium found) 

912 raise xs_errors.XenError('TapdiskDriveEmpty') 

913 else: 

914 raise TapdiskFailed(cls.Arg(_type, path), ctl) 

915 

916 @classmethod 

917 def launch(cls, path, _type, rdonly): 

918 blktap = Blktap.allocate() 

919 try: 

920 return cls.launch_on_tap(blktap, path, _type, {"rdonly": rdonly}) 

921 except: 

922 blktap.free() 

923 raise 

924 

925 def shutdown(self, force=False): 

926 

927 TapCtl.close(self.pid, self.minor, force) 

928 

929 TapCtl.detach(self.pid, self.minor) 

930 

931 self.get_blktap().free() 

932 

933 def pause(self): 

934 

935 if not self.is_running(): 

936 raise TapdiskInvalidState(self) 

937 

938 TapCtl.pause(self.pid, self.minor) 

939 

940 self._set_dirty() 

941 

942 def unpause(self, _type=None, path=None, mirror=None, cbtlog=None): 

943 

944 if not self.is_paused(): 

945 raise TapdiskInvalidState(self) 

946 

947 # FIXME: should the arguments be optional? 

948 if _type is None: 

949 _type = self.type 

950 if path is None: 

951 path = self.path 

952 

953 TapCtl.unpause(self.pid, self.minor, _type, path, mirror=mirror, 

954 cbtlog=cbtlog) 

955 

956 self._set_dirty() 

957 

958 def stats(self): 

959 return json.loads(TapCtl.stats(self.pid, self.minor)) 

960 # 

961 # NB. dirty/refresh: reload attributes on next access 

962 # 

963 

964 def _set_dirty(self): 

965 self._dirty = True 

966 

967 def _refresh(self, __get): 

968 t = self.from_minor(__get('minor')) 

969 self.__init__(t.pid, t.minor, t.type, t.path, t.state) 

970 

971 @override 

972 def __getattribute__(self, name) -> Any: 

973 def __get(name): 

974 # NB. avoid(rec(ursion) 

975 return object.__getattribute__(self, name) 

976 

977 if __get('_dirty') and \ 977 ↛ 979line 977 didn't jump to line 979, because the condition on line 977 was never true

978 name in ['minor', 'type', 'path', 'state']: 

979 self._refresh(__get) 

980 self._dirty = False 

981 

982 return __get(name) 

983 

984 class PauseState: 

985 RUNNING = 'R' 

986 PAUSING = 'r' 

987 PAUSED = 'P' 

988 

989 class Flags: 

990 DEAD = 0x0001 

991 CLOSED = 0x0002 

992 QUIESCE_REQUESTED = 0x0004 

993 QUIESCED = 0x0008 

994 PAUSE_REQUESTED = 0x0010 

995 PAUSED = 0x0020 

996 SHUTDOWN_REQUESTED = 0x0040 

997 LOCKING = 0x0080 

998 RETRY_NEEDED = 0x0100 

999 LOG_DROPPED = 0x0200 

1000 

1001 PAUSE_MASK = PAUSE_REQUESTED | PAUSED 

1002 

1003 def is_paused(self): 

1004 return not not (self.state & self.Flags.PAUSED) 

1005 

1006 def is_running(self): 

1007 return not (self.state & self.Flags.PAUSE_MASK) 

1008 

1009 def pause_state(self): 

1010 if self.state & self.Flags.PAUSED: 1010 ↛ 1011line 1010 didn't jump to line 1011, because the condition on line 1010 was never true

1011 return self.PauseState.PAUSED 

1012 

1013 if self.state & self.Flags.PAUSE_REQUESTED: 1013 ↛ 1014line 1013 didn't jump to line 1014, because the condition on line 1013 was never true

1014 return self.PauseState.PAUSING 

1015 

1016 return self.PauseState.RUNNING 

1017 

1018 @staticmethod 

1019 def _parse_minor(devpath): 

1020 regex = r'%s/(blktap|tapdev)(\d+)$' % Blktap.DEV_BASEDIR 

1021 pattern = re.compile(regex) 

1022 groups = pattern.search(devpath) 

1023 if not groups: 

1024 raise Exception("malformed tap device: '%s' (%s) " % (devpath, regex)) 

1025 

1026 minor = groups.group(2) 

1027 return int(minor) 

1028 

1029 _major = None 

1030 

1031 @classmethod 

1032 def major(cls): 

1033 if cls._major: 

1034 return cls._major 

1035 

1036 devices = open("/proc/devices") 

1037 for line in devices: 

1038 

1039 row = line.rstrip().split(' ') 

1040 if len(row) != 2: 

1041 continue 

1042 

1043 major, name = row 

1044 if name != 'tapdev': 

1045 continue 

1046 

1047 cls._major = int(major) 

1048 break 

1049 

1050 devices.close() 

1051 return cls._major 

1052 

1053 

1054class VDI(object): 

1055 """SR.vdi driver decorator for blktap2""" 

1056 

1057 CONF_KEY_ALLOW_CACHING = "vdi_allow_caching" 

1058 CONF_KEY_MODE_ON_BOOT = "vdi_on_boot" 

1059 CONF_KEY_CACHE_SR = "local_cache_sr" 

1060 CONF_KEY_O_DIRECT = "o_direct" 

1061 LOCK_CACHE_SETUP = "cachesetup" 

1062 

1063 ATTACH_DETACH_RETRY_SECS = 120 

1064 

1065 def __init__(self, uuid, target, driver_info): 

1066 self.target = self.TargetDriver(target, driver_info) 

1067 self._vdi_uuid = uuid 

1068 self._session = target.session 

1069 self.xenstore_data = scsiutil.update_XS_SCSIdata(uuid, scsiutil.gen_synthetic_page_data(uuid)) 

1070 self.__o_direct = None 

1071 self.__o_direct_reason = None 

1072 self.lock = Lock("vdi", uuid) 

1073 self.tap = None 

1074 

1075 def get_o_direct_capability(self, options): 

1076 """Returns True/False based on licensing and caching_params""" 

1077 if self.__o_direct is not None: 1077 ↛ 1078line 1077 didn't jump to line 1078, because the condition on line 1077 was never true

1078 return self.__o_direct, self.__o_direct_reason 

1079 

1080 if util.read_caching_is_restricted(self._session): 1080 ↛ 1081line 1080 didn't jump to line 1081, because the condition on line 1080 was never true

1081 self.__o_direct = True 

1082 self.__o_direct_reason = "LICENSE_RESTRICTION" 

1083 elif not ((self.target.vdi.sr.handles("nfs") or self.target.vdi.sr.handles("ext") or self.target.vdi.sr.handles("smb"))): 1083 ↛ 1086line 1083 didn't jump to line 1086, because the condition on line 1083 was never false

1084 self.__o_direct = True 

1085 self.__o_direct_reason = "SR_NOT_SUPPORTED" 

1086 elif options.get("rdonly") and not self.target.vdi.parent: 

1087 self.__o_direct = True 

1088 self.__o_direct_reason = "RO_WITH_NO_PARENT" 

1089 elif options.get(self.CONF_KEY_O_DIRECT): 

1090 self.__o_direct = True 

1091 self.__o_direct_reason = "SR_OVERRIDE" 

1092 

1093 if self.__o_direct is None: 1093 ↛ 1094line 1093 didn't jump to line 1094, because the condition on line 1093 was never true

1094 self.__o_direct = False 

1095 self.__o_direct_reason = "" 

1096 

1097 return self.__o_direct, self.__o_direct_reason 

1098 

1099 @classmethod 

1100 def from_cli(cls, uuid): 

1101 session = XenAPI.xapi_local() 

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

1103 

1104 target = sm.VDI.from_uuid(session, uuid) 

1105 driver_info = target.sr.srcmd.driver_info 

1106 

1107 session.xenapi.session.logout() 

1108 

1109 return cls(uuid, target, driver_info) 

1110 

1111 @staticmethod 

1112 def _tap_type(vdi_type): 

1113 """Map a VDI type (e.g. 'raw') to a tapdisk driver type (e.g. 'aio')""" 

1114 return { 

1115 'raw': 'aio', 

1116 'vhd': 'vhd', 

1117 'qcow2': 'qcow2', 

1118 'iso': 'aio', # for ISO SR 

1119 'aio': 'aio', # for LVHD 

1120 'file': 'aio', 

1121 'phy': 'aio' 

1122 }[vdi_type] 

1123 

1124 def get_tap_type(self): 

1125 vdi_type = self.target.get_vdi_type() 

1126 return VDI._tap_type(vdi_type) 

1127 

1128 def get_phy_path(self): 

1129 return self.target.get_vdi_path() 

1130 

1131 class UnexpectedVDIType(Exception): 

1132 

1133 def __init__(self, vdi_type, target): 

1134 self.vdi_type = vdi_type 

1135 self.target = target 

1136 

1137 @override 

1138 def __str__(self) -> str: 

1139 return \ 

1140 "Target %s has unexpected VDI type '%s'" % \ 

1141 (type(self.target), self.vdi_type) 

1142 

1143 VDI_PLUG_TYPE = {'phy': 'phy', # for NETAPP 

1144 'raw': 'phy', 

1145 'aio': 'tap', # for LVM raw nodes 

1146 'iso': 'tap', # for ISOSR 

1147 'file': 'tap', 

1148 'vhd': 'tap', 

1149 'qcow2': 'tap'} 

1150 

1151 def tap_wanted(self): 

1152 # 1. Let the target vdi_type decide 

1153 

1154 vdi_type = self.target.get_vdi_type() 

1155 

1156 try: 

1157 plug_type = self.VDI_PLUG_TYPE[vdi_type] 

1158 except KeyError: 

1159 raise self.UnexpectedVDIType(vdi_type, 

1160 self.target.vdi) 

1161 

1162 if plug_type == 'tap': 1162 ↛ 1164line 1162 didn't jump to line 1164, because the condition on line 1162 was never false

1163 return True 

1164 elif self.target.vdi.sr.handles('udev'): 

1165 return True 

1166 # 2. Otherwise, there may be more reasons 

1167 # 

1168 # .. TBD 

1169 

1170 return False 

1171 

1172 class TargetDriver: 

1173 """Safe target driver access.""" 

1174 # NB. *Must* test caps for optional calls. Some targets 

1175 # actually implement some slots, but do not enable them. Just 

1176 # try/except would risk breaking compatibility. 

1177 

1178 def __init__(self, vdi, driver_info): 

1179 self.vdi = vdi 

1180 self._caps = driver_info['capabilities'] 

1181 

1182 def has_cap(self, cap): 

1183 """Determine if target has given capability""" 

1184 return cap in self._caps 

1185 

1186 def attach(self, sr_uuid, vdi_uuid): 

1187 #assert self.has_cap("VDI_ATTACH") 

1188 return self.vdi.attach(sr_uuid, vdi_uuid) 

1189 

1190 def detach(self, sr_uuid, vdi_uuid): 

1191 #assert self.has_cap("VDI_DETACH") 

1192 self.vdi.detach(sr_uuid, vdi_uuid) 

1193 

1194 def activate(self, sr_uuid, vdi_uuid): 

1195 if self.has_cap("VDI_ACTIVATE"): 

1196 return self.vdi.activate(sr_uuid, vdi_uuid) 

1197 

1198 def deactivate(self, sr_uuid, vdi_uuid): 

1199 if self.has_cap("VDI_DEACTIVATE"): 

1200 self.vdi.deactivate(sr_uuid, vdi_uuid) 

1201 #def resize(self, sr_uuid, vdi_uuid, size): 

1202 # return self.vdi.resize(sr_uuid, vdi_uuid, size) 

1203 

1204 def get_vdi_type(self): 

1205 _type = self.vdi.vdi_type 

1206 if not _type: 

1207 raise VDI.UnexpectedVDIType(_type, self.vdi) 

1208 return _type 

1209 

1210 def get_vdi_path(self): 

1211 return self.vdi.path 

1212 

1213 class Link(object): 

1214 """Relink a node under a common name""" 

1215 # NB. We have to provide the device node path during 

1216 # VDI.attach, but currently do not allocate the tapdisk minor 

1217 # before VDI.activate. Therefore those link steps where we 

1218 # relink existing devices under deterministic path names. 

1219 

1220 BASEDIR: ClassVar[str] = "" 

1221 

1222 def _mklink(self, target) -> None: 

1223 pass 

1224 

1225 @abstractmethod 

1226 def _equals(self, target) -> bool: 

1227 pass 

1228 

1229 def __init__(self, path): 

1230 self._path = path 

1231 

1232 @classmethod 

1233 def from_name(cls, name): 

1234 path = "%s/%s" % (cls.BASEDIR, name) 

1235 return cls(path) 

1236 

1237 @classmethod 

1238 def from_uuid(cls, sr_uuid, vdi_uuid): 

1239 name = "%s/%s" % (sr_uuid, vdi_uuid) 

1240 return cls.from_name(name) 

1241 

1242 def path(self): 

1243 return self._path 

1244 

1245 def stat(self): 

1246 return os.stat(self.path()) 

1247 

1248 def mklink(self, target) -> None: 

1249 

1250 path = self.path() 

1251 util.SMlog("%s -> %s" % (self, target)) 

1252 

1253 mkdirs(os.path.dirname(path)) 

1254 try: 

1255 self._mklink(target) 

1256 except OSError as e: 

1257 # We do unlink during teardown, but have to stay 

1258 # idempotent. However, a *wrong* target should never 

1259 # be seen. 

1260 if e.errno != errno.EEXIST: 

1261 raise 

1262 assert self._equals(target), "'%s' not equal to '%s'" % (path, target) 

1263 

1264 def unlink(self): 

1265 try: 

1266 os.unlink(self.path()) 

1267 except OSError as e: 

1268 if e.errno != errno.ENOENT: 

1269 raise 

1270 

1271 @override 

1272 def __str__(self) -> str: 

1273 path = self.path() 

1274 return "%s(%s)" % (self.__class__.__name__, path) 

1275 

1276 class SymLink(Link): 

1277 """Symlink some file to a common name""" 

1278 

1279 def readlink(self): 

1280 return os.readlink(self.path()) 

1281 

1282 def symlink(self): 

1283 return self.path() 

1284 

1285 @override 

1286 def _mklink(self, target) -> None: 

1287 os.symlink(target, self.path()) 

1288 

1289 @override 

1290 def _equals(self, target) -> bool: 

1291 return self.readlink() == target 

1292 

1293 class DeviceNode(Link): 

1294 """Relink a block device node to a common name""" 

1295 

1296 @classmethod 

1297 def _real_stat(cls, target): 

1298 """stat() not on @target, but its realpath()""" 

1299 _target = os.path.realpath(target) 

1300 return os.stat(_target) 

1301 

1302 @classmethod 

1303 def is_block(cls, target): 

1304 """Whether @target refers to a block device.""" 

1305 return S_ISBLK(cls._real_stat(target).st_mode) 

1306 

1307 @override 

1308 def _mklink(self, target) -> None: 

1309 

1310 st = self._real_stat(target) 

1311 if not S_ISBLK(st.st_mode): 

1312 raise self.NotABlockDevice(target, st) 

1313 

1314 # set group read for disk group as well as root 

1315 os.mknod(self.path(), st.st_mode | stat.S_IRGRP, st.st_rdev) 

1316 os.chown(self.path(), st.st_uid, grp.getgrnam("disk").gr_gid) 

1317 

1318 @override 

1319 def _equals(self, target) -> bool: 

1320 target_rdev = self._real_stat(target).st_rdev 

1321 return self.stat().st_rdev == target_rdev 

1322 

1323 def rdev(self): 

1324 st = self.stat() 

1325 assert S_ISBLK(st.st_mode) 

1326 return os.major(st.st_rdev), os.minor(st.st_rdev) 

1327 

1328 class NotABlockDevice(Exception): 

1329 

1330 def __init__(self, path, st): 

1331 self.path = path 

1332 self.st = st 

1333 

1334 @override 

1335 def __str__(self) -> str: 

1336 return "%s is not a block device: %s" % (self.path, self.st) 

1337 

1338 class Hybrid(Link): 

1339 

1340 def __init__(self, path): 

1341 VDI.Link.__init__(self, path) 

1342 self._devnode = VDI.DeviceNode(path) 

1343 self._symlink = VDI.SymLink(path) 

1344 

1345 def rdev(self): 

1346 st = self.stat() 

1347 if S_ISBLK(st.st_mode): 

1348 return self._devnode.rdev() 

1349 raise self._devnode.NotABlockDevice(self.path(), st) 

1350 

1351 @override 

1352 def mklink(self, target) -> None: 

1353 if self._devnode.is_block(target): 

1354 self._obj = self._devnode 

1355 else: 

1356 self._obj = self._symlink 

1357 self._obj.mklink(target) 

1358 

1359 @override 

1360 def _equals(self, target) -> bool: 

1361 return self._obj._equals(target) 

1362 

1363 class PhyLink(SymLink): 

1364 BASEDIR = "/dev/sm/phy" 

1365 # NB. Cannot use DeviceNodes, e.g. FileVDIs aren't bdevs. 

1366 

1367 class NBDLink(SymLink): 

1368 

1369 BASEDIR = "/run/blktap-control/nbd" 

1370 

1371 class BackendLink(Hybrid): 

1372 BASEDIR = "/dev/sm/backend" 

1373 # NB. Could be SymLinks as well, but saving major,minor pairs in 

1374 # Links enables neat state capturing when managing Tapdisks. Note 

1375 # that we essentially have a tap-ctl list replacement here. For 

1376 # now make it a 'Hybrid'. Likely to collapse into a DeviceNode as 

1377 # soon as ISOs are tapdisks. 

1378 

1379 @staticmethod 

1380 def _tap_activate(phy_path, vdi_type, sr_uuid, options, pool_size=None): 

1381 

1382 tapdisk = Tapdisk.find_by_path(phy_path) 

1383 if not tapdisk: 1383 ↛ 1384line 1383 didn't jump to line 1384, because the condition on line 1383 was never true

1384 blktap = Blktap.allocate() 

1385 blktap.set_pool_name(sr_uuid) 

1386 if pool_size: 

1387 blktap.set_pool_size(pool_size) 

1388 

1389 try: 

1390 tapdisk = \ 

1391 Tapdisk.launch_on_tap(blktap, 

1392 phy_path, 

1393 VDI._tap_type(vdi_type), 

1394 options) 

1395 except: 

1396 blktap.free() 

1397 raise 

1398 util.SMlog("tap.activate: Launched %s" % tapdisk) 

1399 

1400 else: 

1401 util.SMlog("tap.activate: Found %s" % tapdisk) 

1402 

1403 return tapdisk.get_devpath(), tapdisk 

1404 

1405 @staticmethod 

1406 def _tap_deactivate(minor): 

1407 

1408 try: 

1409 tapdisk = Tapdisk.from_minor(minor) 

1410 except TapdiskNotRunning as e: 

1411 util.SMlog("tap.deactivate: Warning, %s" % e) 

1412 # NB. Should not be here unless the agent refcount 

1413 # broke. Also, a clean shutdown should not have leaked 

1414 # the recorded minor. 

1415 else: 

1416 tapdisk.shutdown() 

1417 util.SMlog("tap.deactivate: Shut down %s" % tapdisk) 

1418 

1419 @classmethod 

1420 def tap_pause(cls, session, sr_uuid, vdi_uuid, failfast=False): 

1421 """ 

1422 Pauses the tapdisk. 

1423 

1424 session: a XAPI session 

1425 sr_uuid: the UUID of the SR on which VDI lives 

1426 vdi_uuid: the UUID of the VDI to pause 

1427 failfast: controls whether the VDI lock should be acquired in a 

1428 non-blocking manner 

1429 """ 

1430 util.SMlog("Pause request for %s" % vdi_uuid) 

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

1432 session.xenapi.VDI.add_to_sm_config(vdi_ref, 'paused', 'true') 

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

1434 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 1434 ↛ 1435line 1434 didn't jump to line 1435, because the loop on line 1434 never started

1435 host_ref = key[len('host_'):] 

1436 util.SMlog("Calling tap-pause on host %s" % host_ref) 

1437 if not cls.call_pluginhandler(session, host_ref, 

1438 sr_uuid, vdi_uuid, "pause", failfast=failfast): 

1439 # Failed to pause node 

1440 session.xenapi.VDI.remove_from_sm_config(vdi_ref, 'paused') 

1441 return False 

1442 return True 

1443 

1444 @classmethod 

1445 def tap_unpause(cls, session, sr_uuid, vdi_uuid, secondary=None, 

1446 activate_parents=False): 

1447 util.SMlog("Unpause request for %s secondary=%s" % (vdi_uuid, secondary)) 

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

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

1450 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 1450 ↛ 1451line 1450 didn't jump to line 1451, because the loop on line 1450 never started

1451 host_ref = key[len('host_'):] 

1452 util.SMlog("Calling tap-unpause on host %s" % host_ref) 

1453 if not cls.call_pluginhandler(session, host_ref, 

1454 sr_uuid, vdi_uuid, "unpause", secondary, activate_parents): 

1455 # Failed to unpause node 

1456 return False 

1457 session.xenapi.VDI.remove_from_sm_config(vdi_ref, 'paused') 

1458 return True 

1459 

1460 @classmethod 

1461 def tap_refresh(cls, session, sr_uuid, vdi_uuid, activate_parents=False): 

1462 util.SMlog("Refresh request for %s" % vdi_uuid) 

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

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

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

1466 host_ref = key[len('host_'):] 

1467 util.SMlog("Calling tap-refresh on host %s" % host_ref) 

1468 if not cls.call_pluginhandler(session, host_ref, 

1469 sr_uuid, vdi_uuid, "refresh", None, 

1470 activate_parents=activate_parents): 

1471 # Failed to refresh node 

1472 return False 

1473 return True 

1474 

1475 @classmethod 

1476 def tap_status(cls, session, vdi_uuid): 

1477 """Return True if disk is attached, false if it isn't""" 

1478 util.SMlog("Disk status request for %s" % vdi_uuid) 

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

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

1481 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 1481 ↛ 1482line 1481 didn't jump to line 1482, because the loop on line 1481 never started

1482 return True 

1483 return False 

1484 

1485 @classmethod 

1486 def call_pluginhandler(cls, session, host_ref, sr_uuid, vdi_uuid, action, 

1487 secondary=None, activate_parents=False, failfast=False): 

1488 """Optionally, activate the parent LV before unpausing""" 

1489 try: 

1490 args = {"sr_uuid": sr_uuid, "vdi_uuid": vdi_uuid, 

1491 "failfast": str(failfast)} 

1492 if secondary: 

1493 args["secondary"] = secondary 

1494 if activate_parents: 

1495 args["activate_parents"] = "true" 

1496 ret = session.xenapi.host.call_plugin( 

1497 host_ref, PLUGIN_TAP_PAUSE, action, 

1498 args) 

1499 return ret == "True" 

1500 except Exception as e: 

1501 util.logException("BLKTAP2:call_pluginhandler %s" % e) 

1502 return False 

1503 

1504 def _add_tag(self, vdi_uuid, writable): 

1505 util.SMlog("Adding tag to: %s" % vdi_uuid) 

1506 attach_mode = "RO" 

1507 if writable: 

1508 attach_mode = "RW" 

1509 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid) 

1510 host_ref = self._session.xenapi.host.get_by_uuid(util.get_this_host()) 

1511 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref) 

1512 attached_as = util.attached_as(sm_config) 

1513 if NO_MULTIPLE_ATTACH and (attached_as == "RW" or \ 1513 ↛ 1515line 1513 didn't jump to line 1515, because the condition on line 1513 was never true

1514 (attached_as == "RO" and attach_mode == "RW")): 

1515 util.SMlog("need to reset VDI %s" % vdi_uuid) 

1516 if not resetvdis.reset_vdi(self._session, vdi_uuid, force=False, 

1517 term_output=False, writable=writable): 

1518 raise util.SMException("VDI %s not detached cleanly" % vdi_uuid) 

1519 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref) 

1520 if 'relinking' in sm_config: 

1521 util.SMlog("Relinking key found, back-off and retry" % sm_config) 

1522 return False 

1523 if 'paused' in sm_config: 

1524 util.SMlog("Paused or host_ref key found [%s]" % sm_config) 

1525 return False 

1526 try: 

1527 self._session.xenapi.VDI.add_to_sm_config( 

1528 vdi_ref, 'activating', 'True') 

1529 except XenAPI.Failure as e: 

1530 if e.details[0] == 'MAP_DUPLICATE_KEY' and not writable: 

1531 # Someone else is activating - a retry might succeed 

1532 return False 

1533 raise 

1534 host_key = "host_%s" % host_ref 

1535 assert host_key not in sm_config 

1536 self._session.xenapi.VDI.add_to_sm_config(vdi_ref, host_key, 

1537 attach_mode) 

1538 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref) 

1539 if 'paused' in sm_config or 'relinking' in sm_config: 

1540 util.SMlog("Found %s key, aborting" % ( 

1541 'paused' if 'paused' in sm_config else 'relinking')) 

1542 self._session.xenapi.VDI.remove_from_sm_config(vdi_ref, host_key) 

1543 self._session.xenapi.VDI.remove_from_sm_config( 

1544 vdi_ref, 'activating') 

1545 return False 

1546 util.SMlog("Activate lock succeeded") 

1547 return True 

1548 

1549 def _check_tag(self, vdi_uuid): 

1550 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid) 

1551 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref) 

1552 if 'paused' in sm_config: 

1553 util.SMlog("Paused key found [%s]" % sm_config) 

1554 return False 

1555 return True 

1556 

1557 def _remove_tag(self, vdi_uuid): 

1558 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid) 

1559 host_ref = self._session.xenapi.host.get_by_uuid(util.get_this_host()) 

1560 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref) 

1561 host_key = "host_%s" % host_ref 

1562 if host_key in sm_config: 

1563 self._session.xenapi.VDI.remove_from_sm_config(vdi_ref, host_key) 

1564 util.SMlog("Removed host key %s for %s" % (host_key, vdi_uuid)) 

1565 else: 

1566 util.SMlog("_remove_tag: host key %s not found, ignore" % host_key) 

1567 

1568 def _get_pool_config(self, pool_name): 

1569 pool_info = dict() 

1570 vdi_ref = self.target.vdi.sr.srcmd.params.get('vdi_ref') 

1571 if not vdi_ref: 1571 ↛ 1574line 1571 didn't jump to line 1574, because the condition on line 1571 was never true

1572 # attach_from_config context: HA disks don't need to be in any 

1573 # special pool 

1574 return pool_info 

1575 

1576 sr_ref = self.target.vdi.sr.srcmd.params.get('sr_ref') 

1577 sr_config = self._session.xenapi.SR.get_other_config(sr_ref) 

1578 vdi_config = self._session.xenapi.VDI.get_other_config(vdi_ref) 

1579 pool_size_str = sr_config.get(POOL_SIZE_KEY) 

1580 pool_name_override = vdi_config.get(POOL_NAME_KEY) 

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

1582 pool_name = pool_name_override 

1583 pool_size_override = vdi_config.get(POOL_SIZE_KEY) 

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

1585 pool_size_str = pool_size_override 

1586 pool_size = 0 

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

1588 try: 

1589 pool_size = int(pool_size_str) 

1590 if pool_size < 1 or pool_size > MAX_FULL_RINGS: 1590 ↛ 1591line 1590 didn't jump to line 1591, because the condition on line 1590 was never true

1591 raise ValueError("outside of range") 

1592 pool_size = NUM_PAGES_PER_RING * pool_size 

1593 except ValueError: 

1594 util.SMlog("Error: invalid mem-pool-size %s" % pool_size_str) 

1595 pool_size = 0 

1596 

1597 pool_info["mem-pool"] = pool_name 

1598 if pool_size: 1598 ↛ 1601line 1598 didn't jump to line 1601, because the condition on line 1598 was never false

1599 pool_info["mem-pool-size"] = str(pool_size) 

1600 

1601 return pool_info 

1602 

1603 def linkNBD(self, sr_uuid, vdi_uuid): 

1604 if self.tap: 

1605 nbd_path = '/run/blktap-control/nbd%d.%d' % (int(self.tap.pid), 

1606 int(self.tap.minor)) 

1607 VDI.NBDLink.from_uuid(sr_uuid, vdi_uuid).mklink(nbd_path) 

1608 

1609 def attach(self, sr_uuid, vdi_uuid, writable, activate=False, caching_params={}): 

1610 """Return/dev/sm/backend symlink path""" 

1611 self.xenstore_data.update(self._get_pool_config(sr_uuid)) 

1612 if not self.target.has_cap("ATOMIC_PAUSE") or activate: 

1613 util.SMlog("Attach & activate") 

1614 self._attach(sr_uuid, vdi_uuid) 

1615 dev_path = self._activate(sr_uuid, vdi_uuid, 

1616 {"rdonly": not writable}) 

1617 self.BackendLink.from_uuid(sr_uuid, vdi_uuid).mklink(dev_path) 

1618 self.linkNBD(sr_uuid, vdi_uuid) 

1619 

1620 # Return backend/ link 

1621 back_path = self.BackendLink.from_uuid(sr_uuid, vdi_uuid).path() 

1622 if self.tap_wanted(): 

1623 # Only have NBD if we also have a tap 

1624 nbd_path = "nbd:unix:{}:exportname={}".format( 

1625 VDI.NBDLink.from_uuid(sr_uuid, vdi_uuid).path(), 

1626 vdi_uuid) 

1627 else: 

1628 nbd_path = "" 

1629 

1630 options = {"rdonly": not writable} 

1631 options.update(caching_params) 

1632 o_direct, o_direct_reason = self.get_o_direct_capability(options) 

1633 struct = {'params': back_path, 

1634 'params_nbd': nbd_path, 

1635 'o_direct': o_direct, 

1636 'o_direct_reason': o_direct_reason, 

1637 'xenstore_data': self.xenstore_data} 

1638 util.SMlog('result: %s' % struct) 

1639 

1640 try: 

1641 f = open("%s.attach_info" % back_path, 'a') 

1642 f.write(xmlrpc.client.dumps((struct, ), "", True)) 

1643 f.close() 

1644 except: 

1645 pass 

1646 

1647 return xmlrpc.client.dumps((struct, ), "", True) 

1648 

1649 def activate(self, sr_uuid, vdi_uuid, writable, caching_params): 

1650 util.SMlog("blktap2.activate") 

1651 options = {"rdonly": not writable} 

1652 options.update(caching_params) 

1653 

1654 sr_ref = self.target.vdi.sr.srcmd.params.get('sr_ref') 

1655 sr_other_config = self._session.xenapi.SR.get_other_config(sr_ref) 

1656 for i in range(self.ATTACH_DETACH_RETRY_SECS): 1656 ↛ 1663line 1656 didn't jump to line 1663, because the loop on line 1656 didn't complete

1657 try: 

1658 if self._activate_locked(sr_uuid, vdi_uuid, options): 

1659 return 

1660 except util.SRBusyException: 

1661 util.SMlog("SR locked, retrying") 

1662 time.sleep(1) 

1663 raise util.SMException("VDI %s locked" % vdi_uuid) 

1664 

1665 def _get_sr_master_host_ref(self) -> str: 

1666 """ 

1667 Give the host ref of the one responsible for Garbage Collection for a SR. 

1668 Meaning this host for a local SR, the master for a shared SR. 

1669 """ 

1670 sr = self.target.vdi.sr 

1671 if sr.is_shared(): 

1672 host_ref = util.get_master_ref(self._session) 

1673 else: 

1674 host_ref = sr.host_ref 

1675 return host_ref 

1676 

1677 def _get_vdi_chain(self, cowutil, extractUuid) -> List[str]: 

1678 vdi_chain = [] 

1679 path = self.target.get_vdi_path() 

1680 

1681 #TODO: Need to add handling of error for getParentNoCheck, e.g. corrupted VDI where we can't read parent 

1682 vdi_chain.append(extractUuid(path)) 

1683 parent = cowutil.getParentNoCheck(path) 

1684 while parent: 

1685 vdi_chain.append(extractUuid(parent)) 

1686 parent = cowutil.getParentNoCheck(parent) 

1687 vdi_chain.reverse() 

1688 return vdi_chain 

1689 

1690 def _check_journal_coalesce_chain(self, sr_uuid: str, vdi_uuid: str) -> bool: 

1691 vdi_type = self.target.get_vdi_type() 

1692 cowutil = getCowUtil(vdi_type) 

1693 

1694 if not cowutil.isCoalesceableOnRemote(): #We only need to stop the coalesce in case of QCOW2 

1695 return True 

1696 

1697 path = self.target.get_vdi_path() 

1698 

1699 import fjournaler 

1700 import journaler 

1701 from lvmcowutil import LvmCowUtil 

1702 from FileSR import FileVDI 

1703 import lvmcache 

1704 

1705 journal: Union[journaler.Journaler, fjournaler.Journaler] 

1706 # Different extractUUID & journaler function for LVMSR and FileSR 

1707 if path.startswith("/dev/"): #TODO: How to identify SR type easily, we could ask XAPI since we have the sruuid (and even ref) 

1708 vgName = "VG_XenStorage-{}".format(sr_uuid) 

1709 lvmCache = lvmcache.LVMCache(vgName) 

1710 journal = journaler.Journaler(lvmCache) 

1711 

1712 extractUuid = LvmCowUtil.extractUuid 

1713 else: 

1714 journal = fjournaler.Journaler(os.getcwd()) 

1715 extractUuid = FileVDI.extractUuid 

1716 

1717 # Get the VDI chain 

1718 vdi_chain = self._get_vdi_chain(cowutil, extractUuid) 

1719 

1720 if len(vdi_chain) == 1: 

1721 # We only have a leaf, do nothing 

1722 util.SMlog("VDI {} is only a leaf, continuing...".format(vdi_uuid)) 

1723 return True 

1724 

1725 # Log the chain of active VDI 

1726 level = 0 

1727 util.SMlog("VDI chain:") 

1728 for vdi in vdi_chain: 

1729 prefix = " " * level 

1730 level += 1 

1731 util.SMlog("{}{}".format(prefix, vdi)) 

1732 

1733 vdi_to_cancel = [] 

1734 for entry in journal.getAll("coalesce").keys(): 

1735 if entry in vdi_chain: 

1736 vdi_to_cancel.append(entry) 

1737 util.SMlog("Coalescing VDI {} in chain".format(entry)) 

1738 

1739 # Get the host_ref from the host doing the GC work 

1740 host_ref = self._get_sr_master_host_ref() 

1741 for vdi in vdi_to_cancel: 

1742 args = {"sr_uuid": sr_uuid, "vdi_uuid": vdi} 

1743 util.SMlog("Calling cancel_coalesce_master with args: {}".format(args)) 

1744 self._session.xenapi.host.call_plugin(\ 

1745 host_ref, PLUGIN_ON_SLAVE, "cancel_coalesce_master", args) 

1746 

1747 return True 

1748 

1749 @locking("VDIUnavailable") 

1750 def _activate_locked(self, sr_uuid, vdi_uuid, options): 

1751 """Wraps target.activate and adds a tapdisk""" 

1752 

1753 #util.SMlog("VDI.activate %s" % vdi_uuid) 

1754 refresh = False 

1755 if self.tap_wanted(): 1755 ↛ 1760line 1755 didn't jump to line 1760, because the condition on line 1755 was never false

1756 if not self._add_tag(vdi_uuid, not options["rdonly"]): 

1757 return False 

1758 refresh = True 

1759 

1760 try: 

1761 if refresh: 1761 ↛ 1772line 1761 didn't jump to line 1772, because the condition on line 1761 was never false

1762 # it is possible that while the VDI was paused some of its 

1763 # attributes have changed (e.g. its size if it was inflated; or its 

1764 # path if it was leaf-coalesced onto a raw LV), so refresh the 

1765 # object completely 

1766 params = self.target.vdi.sr.srcmd.params 

1767 target = sm.VDI.from_uuid(self.target.vdi.session, vdi_uuid) 

1768 target.sr.srcmd.params = params 

1769 driver_info = target.sr.srcmd.driver_info 

1770 self.target = self.TargetDriver(target, driver_info) 

1771 

1772 util.fistpoint.activate_custom_fn( 1772 ↛ exitline 1772 didn't jump to the function exit

1773 "blktap_activate_inject_failure", 

1774 lambda: util.inject_failure()) 

1775 

1776 # Attach the physical node 

1777 if self.target.has_cap("ATOMIC_PAUSE"): 1777 ↛ 1778line 1777 didn't jump to line 1778, because the condition on line 1777 was never true

1778 self._attach(sr_uuid, vdi_uuid) 

1779 

1780 vdi_type = self.target.get_vdi_type() 

1781 

1782 if not self._check_journal_coalesce_chain(sr_uuid, vdi_uuid): 1782 ↛ 1783line 1782 didn't jump to line 1783, because the condition on line 1782 was never true

1783 return False 

1784 

1785 # Take lvchange-p Lock before running 

1786 # tap-ctl open 

1787 # Needed to avoid race with lvchange -p which is 

1788 # now taking the same lock 

1789 # This is a fix for CA-155766 

1790 if hasattr(self.target.vdi.sr, 'DRIVER_TYPE') and \ 1790 ↛ 1793line 1790 didn't jump to line 1793, because the condition on line 1790 was never true

1791 self.target.vdi.sr.DRIVER_TYPE == 'lvhd' and \ 

1792 VdiType.isCowImage(vdi_type): 

1793 lock = Lock("lvchange-p", NS_PREFIX_LVM + sr_uuid) 

1794 lock.acquire() 

1795 

1796 # When we attach a static VDI for HA, we cannot communicate with 

1797 # xapi, because has not started yet. These VDIs are raw. 

1798 if VdiType.isCowImage(vdi_type): 1798 ↛ 1799line 1798 didn't jump to line 1799, because the condition on line 1798 was never true

1799 session = self.target.vdi.session 

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

1801 # pylint: disable=used-before-assignment 

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

1803 if 'key_hash' in sm_config: 

1804 key_hash = sm_config['key_hash'] 

1805 options['key_hash'] = key_hash 

1806 options['vdi_uuid'] = vdi_uuid 

1807 util.SMlog('Using key with hash {} for VDI {}'.format(key_hash, vdi_uuid)) 

1808 # Activate the physical node 

1809 dev_path = self._activate(sr_uuid, vdi_uuid, options) 

1810 

1811 if hasattr(self.target.vdi.sr, 'DRIVER_TYPE') and \ 1811 ↛ 1814line 1811 didn't jump to line 1814, because the condition on line 1811 was never true

1812 self.target.vdi.sr.DRIVER_TYPE == 'lvhd' and \ 

1813 VdiType.isCowImage(self.target.get_vdi_type()): 

1814 lock.release() 

1815 except: 

1816 util.SMlog("Exception in activate/attach") 

1817 if self.tap_wanted(): 

1818 util.fistpoint.activate_custom_fn( 

1819 "blktap_activate_error_handling", 

1820 lambda: time.sleep(30)) 

1821 while True: 

1822 try: 

1823 self._remove_tag(vdi_uuid) 

1824 break 

1825 except xmlrpc.client.ProtocolError as e: 

1826 # If there's a connection error, keep trying forever. 

1827 if e.errcode == http.HTTPStatus.INTERNAL_SERVER_ERROR.value: 

1828 continue 

1829 else: 

1830 util.SMlog('failed to remove tag: %s' % e) 

1831 break 

1832 except Exception as e: 

1833 util.SMlog('failed to remove tag: %s' % e) 

1834 break 

1835 raise 

1836 finally: 

1837 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid) 

1838 self._session.xenapi.VDI.remove_from_sm_config( 

1839 vdi_ref, 'activating') 

1840 util.SMlog("Removed activating flag from %s" % vdi_uuid) 1840 ↛ exitline 1840 didn't except from function '_activate_locked', because the raise on line 1835 wasn't executed or line 1840 didn't return from function '_activate_locked', because the return on line 1783 wasn't executed

1841 

1842 # Link result to backend/ 

1843 self.BackendLink.from_uuid(sr_uuid, vdi_uuid).mklink(dev_path) 

1844 self.linkNBD(sr_uuid, vdi_uuid) 

1845 return True 

1846 

1847 def _activate(self, sr_uuid, vdi_uuid, options): 

1848 vdi_options = self.target.activate(sr_uuid, vdi_uuid) 

1849 

1850 dev_path = self.setup_cache(sr_uuid, vdi_uuid, options) 

1851 if not dev_path: 1851 ↛ 1865line 1851 didn't jump to line 1865, because the condition on line 1851 was never false

1852 phy_path = self.PhyLink.from_uuid(sr_uuid, vdi_uuid).readlink() 

1853 # Maybe launch a tapdisk on the physical link 

1854 if self.tap_wanted(): 1854 ↛ 1863line 1854 didn't jump to line 1863, because the condition on line 1854 was never false

1855 vdi_type = self.target.get_vdi_type() 

1856 options["o_direct"] = self.get_o_direct_capability(options)[0] 

1857 if vdi_options: 1857 ↛ 1859line 1857 didn't jump to line 1859, because the condition on line 1857 was never false

1858 options.update(vdi_options) 

1859 dev_path, self.tap = self._tap_activate(phy_path, vdi_type, 

1860 sr_uuid, options, 

1861 self._get_pool_config(sr_uuid).get("mem-pool-size")) 

1862 else: 

1863 dev_path = phy_path # Just reuse phy 

1864 

1865 return dev_path 

1866 

1867 def _attach(self, sr_uuid, vdi_uuid): 

1868 attach_info = xmlrpc.client.loads(self.target.attach(sr_uuid, vdi_uuid))[0][0] 

1869 params = attach_info['params'] 

1870 xenstore_data = attach_info['xenstore_data'] 

1871 phy_path = util.to_plain_string(params) 

1872 self.xenstore_data.update(xenstore_data) 

1873 # Save it to phy/ 

1874 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).mklink(phy_path) 

1875 

1876 def deactivate(self, sr_uuid, vdi_uuid, caching_params): 

1877 util.SMlog("blktap2.deactivate") 

1878 for i in range(self.ATTACH_DETACH_RETRY_SECS): 

1879 try: 

1880 if self._deactivate_locked(sr_uuid, vdi_uuid, caching_params): 

1881 return 

1882 except util.SRBusyException as e: 

1883 util.SMlog("SR locked, retrying") 

1884 time.sleep(1) 

1885 raise util.SMException("VDI %s locked" % vdi_uuid) 

1886 

1887 @locking("VDIUnavailable") 

1888 def _deactivate_locked(self, sr_uuid, vdi_uuid, caching_params): 

1889 """Wraps target.deactivate and removes a tapdisk""" 

1890 

1891 #util.SMlog("VDI.deactivate %s" % vdi_uuid) 

1892 if self.tap_wanted() and not self._check_tag(vdi_uuid): 

1893 return False 

1894 

1895 self._deactivate(sr_uuid, vdi_uuid, caching_params) 

1896 if self.target.has_cap("ATOMIC_PAUSE"): 

1897 self._detach(sr_uuid, vdi_uuid) 

1898 if self.tap_wanted(): 

1899 self._remove_tag(vdi_uuid) 

1900 

1901 return True 

1902 

1903 def _resetPhylink(self, sr_uuid, vdi_uuid, path): 

1904 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).mklink(path) 

1905 

1906 def detach(self, sr_uuid, vdi_uuid, deactivate=False, caching_params={}): 

1907 if not self.target.has_cap("ATOMIC_PAUSE") or deactivate: 

1908 util.SMlog("Deactivate & detach") 

1909 self._deactivate(sr_uuid, vdi_uuid, caching_params) 

1910 self._detach(sr_uuid, vdi_uuid) 

1911 else: 

1912 pass # nothing to do 

1913 

1914 def _deactivate(self, sr_uuid, vdi_uuid, caching_params): 

1915 # Shutdown tapdisk 

1916 back_link = self.BackendLink.from_uuid(sr_uuid, vdi_uuid) 

1917 

1918 if not util.pathexists(back_link.path()): 

1919 util.SMlog("Backend path %s does not exist" % back_link.path()) 

1920 return 

1921 

1922 try: 

1923 attach_info_path = "%s.attach_info" % (back_link.path()) 

1924 os.unlink(attach_info_path) 

1925 except: 

1926 util.SMlog("unlink of attach_info failed") 

1927 

1928 try: 

1929 major, minor = back_link.rdev() 

1930 except self.DeviceNode.NotABlockDevice: 

1931 pass 

1932 else: 

1933 if major == Tapdisk.major(): 

1934 self._tap_deactivate(minor) 

1935 self.remove_cache(caching_params) 

1936 

1937 # Remove the backend link 

1938 back_link.unlink() 

1939 VDI.NBDLink.from_uuid(sr_uuid, vdi_uuid).unlink() 

1940 

1941 # Deactivate & detach the physical node 

1942 if self.tap_wanted() and self.target.vdi.session is not None: 

1943 # it is possible that while the VDI was paused some of its 

1944 # attributes have changed (e.g. its size if it was inflated; or its 

1945 # path if it was leaf-coalesced onto a raw LV), so refresh the 

1946 # object completely 

1947 target = sm.VDI.from_uuid(self.target.vdi.session, vdi_uuid) 

1948 driver_info = target.sr.srcmd.driver_info 

1949 self.target = self.TargetDriver(target, driver_info) 

1950 

1951 self.target.deactivate(sr_uuid, vdi_uuid) 

1952 

1953 def _detach(self, sr_uuid, vdi_uuid): 

1954 self.target.detach(sr_uuid, vdi_uuid) 

1955 

1956 # Remove phy/ 

1957 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).unlink() 

1958 

1959 def _updateCacheRecord(self, session, vdi_uuid, on_boot, caching): 

1960 # Remove existing VDI.sm_config fields 

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

1962 for key in ["on_boot", "caching"]: 

1963 session.xenapi.VDI.remove_from_sm_config(vdi_ref, key) 

1964 if not on_boot is None: 1964 ↛ 1965line 1964 didn't jump to line 1965, because the condition on line 1964 was never true

1965 session.xenapi.VDI.add_to_sm_config(vdi_ref, 'on_boot', on_boot) 

1966 if not caching is None: 

1967 session.xenapi.VDI.add_to_sm_config(vdi_ref, 'caching', caching) 

1968 

1969 def setup_cache(self, sr_uuid, vdi_uuid, params): 

1970 if params.get(self.CONF_KEY_ALLOW_CACHING) != "true": 

1971 return 

1972 

1973 util.SMlog("Requested local caching") 

1974 if not self.target.has_cap("SR_CACHING"): 

1975 util.SMlog("Error: local caching not supported by this SR") 

1976 return 

1977 

1978 scratch_mode = False 

1979 if params.get(self.CONF_KEY_MODE_ON_BOOT) == "reset": 

1980 scratch_mode = True 

1981 util.SMlog("Requested scratch mode") 

1982 if not self.target.has_cap("VDI_RESET_ON_BOOT/2"): 1982 ↛ 1986line 1982 didn't jump to line 1986, because the condition on line 1982 was never false

1983 util.SMlog("Error: scratch mode not supported by this SR") 

1984 return 

1985 

1986 dev_path = None 

1987 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR) 

1988 if not local_sr_uuid: 

1989 util.SMlog("ERROR: Local cache SR not specified, not enabling") 

1990 return 

1991 dev_path = self._setup_cache(self._session, sr_uuid, vdi_uuid, 

1992 local_sr_uuid, scratch_mode, params) 

1993 

1994 if dev_path: 

1995 self._updateCacheRecord(self._session, self.target.vdi.uuid, 

1996 params.get(self.CONF_KEY_MODE_ON_BOOT), 

1997 params.get(self.CONF_KEY_ALLOW_CACHING)) 

1998 

1999 return dev_path 

2000 

2001 def alert_no_cache(self, session, vdi_uuid, cache_sr_uuid, err): 

2002 vm_uuid = None 

2003 vm_label = "" 

2004 try: 

2005 cache_sr_ref = session.xenapi.SR.get_by_uuid(cache_sr_uuid) 

2006 cache_sr_rec = session.xenapi.SR.get_record(cache_sr_ref) 

2007 cache_sr_label = cache_sr_rec.get("name_label") 

2008 

2009 host_ref = session.xenapi.host.get_by_uuid(util.get_this_host()) 

2010 host_rec = session.xenapi.host.get_record(host_ref) 

2011 host_label = host_rec.get("name_label") 

2012 

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

2014 vbds = session.xenapi.VBD.get_all_records_where( \ 

2015 "field \"VDI\" = \"%s\"" % vdi_ref) 

2016 for vbd_rec in vbds.values(): 

2017 vm_ref = vbd_rec.get("VM") 

2018 vm_rec = session.xenapi.VM.get_record(vm_ref) 

2019 vm_uuid = vm_rec.get("uuid") 

2020 vm_label = vm_rec.get("name_label") 

2021 except: 

2022 util.logException("alert_no_cache") 

2023 

2024 alert_obj = "SR" 

2025 alert_uuid = str(cache_sr_uuid) 

2026 alert_str = "No space left in Local Cache SR %s" % cache_sr_uuid 

2027 if vm_uuid: 

2028 alert_obj = "VM" 

2029 alert_uuid = vm_uuid 

2030 reason = "" 

2031 if err == errno.ENOSPC: 

2032 reason = "because there is no space left" 

2033 alert_str = "The VM \"%s\" is not using IntelliCache %s on the Local Cache SR (\"%s\") on host \"%s\"" % \ 

2034 (vm_label, reason, cache_sr_label, host_label) 

2035 

2036 util.SMlog("Creating alert: (%s, %s, \"%s\")" % \ 

2037 (alert_obj, alert_uuid, alert_str)) 

2038 session.xenapi.message.create("No space left in local cache", "3", 

2039 alert_obj, alert_uuid, alert_str) 

2040 

2041 def _setup_cache(self, session, sr_uuid, vdi_uuid, local_sr_uuid, 

2042 scratch_mode, options): 

2043 import SR 

2044 import EXTSR 

2045 

2046 if self._no_parent(self.target.vdi): 2046 ↛ 2047line 2046 didn't jump to line 2047, because the condition on line 2046 was never true

2047 util.SMlog("ERROR: VDI %s has no parent, not enabling" % 

2048 self.target.vdi.uuid) 

2049 return 

2050 

2051 util.SMlog("Setting up cache") 

2052 shared_target = self.target.vdi.sr.vdi(self.target.vdi.parent) 

2053 

2054 if shared_target.parent: 

2055 util.SMlog("ERROR: Parent VDI %s has parent, not enabling" % 

2056 shared_target.uuid) 

2057 return 

2058 

2059 SR.registerSR(EXTSR.EXTSR) 

2060 local_sr = SR.SR.from_uuid(session, local_sr_uuid) 

2061 

2062 vdi_type = self.target.get_vdi_type() 

2063 tap_type = VDI._tap_type(vdi_type) 

2064 cowutil = getCowUtil(vdi_type) 

2065 

2066 lock = Lock(self.LOCK_CACHE_SETUP, shared_target.uuid) 

2067 lock.acquire() 

2068 

2069 # read cache 

2070 read_cache_path = "%s/%s.vhdcache" % (local_sr.path, shared_target.uuid) 

2071 if util.pathexists(read_cache_path): 2071 ↛ 2075line 2071 didn't jump to line 2075, because the condition on line 2071 was never false

2072 util.SMlog("Read cache node (%s) already exists, not creating" % 

2073 read_cache_path) 

2074 else: 

2075 try: 

2076 cowutil.snapshot(read_cache_path, shared_target.path, False) 

2077 except util.CommandException as e: 

2078 util.SMlog("Error creating parent cache: %s" % e) 

2079 self.alert_no_cache(session, vdi_uuid, local_sr_uuid, e.code) 

2080 return None 

2081 

2082 # local write node 

2083 leaf_size = cowutil.getSizeVirt(self.target.vdi.path) 

2084 local_leaf_path = "%s/%s.vhdcache" % \ 

2085 (local_sr.path, self.target.vdi.uuid) 

2086 if util.pathexists(local_leaf_path): 2086 ↛ 2090line 2086 didn't jump to line 2090, because the condition on line 2086 was never false

2087 util.SMlog("Local leaf node (%s) already exists, deleting" % 

2088 local_leaf_path) 

2089 os.unlink(local_leaf_path) 

2090 try: 

2091 cowutil.snapshot(local_leaf_path, read_cache_path, False, 

2092 msize=leaf_size, checkEmpty=False) 

2093 except util.CommandException as e: 

2094 util.SMlog("Error creating leaf cache: %s" % e) 

2095 self.alert_no_cache(session, vdi_uuid, local_sr_uuid, e.code) 

2096 return None 

2097 

2098 local_leaf_size = cowutil.getSizeVirt(local_leaf_path) 

2099 if leaf_size > local_leaf_size: 2099 ↛ 2100line 2099 didn't jump to line 2100, because the condition on line 2099 was never true

2100 util.SMlog("Leaf size %d > local leaf cache size %d, resizing" % 

2101 (leaf_size, local_leaf_size)) 

2102 cowutil.setSizeVirtFast(local_leaf_path, leaf_size) 

2103 

2104 prt_tapdisk = Tapdisk.find_by_path(read_cache_path) 

2105 if not prt_tapdisk: 

2106 parent_options = copy.deepcopy(options) 

2107 parent_options["rdonly"] = False 

2108 parent_options["lcache"] = True 

2109 

2110 blktap = Blktap.allocate() 

2111 try: 

2112 blktap.set_pool_name("lcache-parent-pool-%s" % blktap.minor) 

2113 # no need to change pool_size since each parent tapdisk is in 

2114 # its own pool 

2115 prt_tapdisk = Tapdisk.launch_on_tap(blktap, read_cache_path, tap_type, parent_options) 

2116 except: 

2117 blktap.free() 

2118 raise 

2119 

2120 secondary = "%s:%s" % (vdi_type, self.PhyLink.from_uuid(sr_uuid, vdi_uuid).readlink()) 

2121 

2122 util.SMlog("Parent tapdisk: %s" % prt_tapdisk) 

2123 leaf_tapdisk = Tapdisk.find_by_path(local_leaf_path) 

2124 if not leaf_tapdisk: 2124 ↛ 2140line 2124 didn't jump to line 2140, because the condition on line 2124 was never false

2125 blktap = Blktap.allocate() 

2126 child_options = copy.deepcopy(options) 

2127 child_options["rdonly"] = False 

2128 child_options["lcache"] = (not scratch_mode) 

2129 child_options["existing_prt"] = prt_tapdisk.minor 

2130 child_options["secondary"] = secondary 

2131 child_options["standby"] = scratch_mode 

2132 # Disable memory read caching 

2133 child_options.pop("o_direct", None) 

2134 try: 

2135 leaf_tapdisk = Tapdisk.launch_on_tap(blktap, local_leaf_path, tap_type, child_options) 

2136 except: 

2137 blktap.free() 

2138 raise 

2139 

2140 lock.release() 

2141 

2142 util.SMlog("Local read cache: %s, local leaf: %s" % 

2143 (read_cache_path, local_leaf_path)) 

2144 

2145 self.tap = leaf_tapdisk 

2146 return leaf_tapdisk.get_devpath() 

2147 

2148 def remove_cache(self, params): 

2149 if not self.target.has_cap("SR_CACHING"): 

2150 return 

2151 

2152 caching = params.get(self.CONF_KEY_ALLOW_CACHING) == "true" 

2153 

2154 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR) 

2155 if caching and not local_sr_uuid: 

2156 util.SMlog("ERROR: Local cache SR not specified, ignore") 

2157 return 

2158 

2159 if caching: 2159 ↛ 2162line 2159 didn't jump to line 2162, because the condition on line 2159 was never false

2160 self._remove_cache(self._session, local_sr_uuid) 

2161 

2162 if self._session is not None: 2162 ↛ exitline 2162 didn't return from function 'remove_cache', because the condition on line 2162 was never false

2163 self._updateCacheRecord(self._session, self.target.vdi.uuid, None, None) 

2164 

2165 def _is_tapdisk_in_use(self, minor): 

2166 retVal, links, sockets = util.findRunningProcessOrOpenFile("tapdisk") 

2167 if not retVal: 

2168 # err on the side of caution 

2169 return True 

2170 

2171 for link in links: 

2172 if link.find("tapdev%d" % minor) != -1: 

2173 return True 

2174 

2175 socket_re = re.compile(r'^/.*/nbd\d+\.%d' % minor) 

2176 for s in sockets: 

2177 if socket_re.match(s): 

2178 return True 

2179 

2180 return False 

2181 

2182 def _remove_cache(self, session, local_sr_uuid): 

2183 import SR 

2184 import EXTSR 

2185 

2186 if self._no_parent(self.target.vdi): 

2187 util.SMlog("ERROR: No parent for VDI %s, ignore" % 

2188 self.target.vdi.uuid) 

2189 return 

2190 

2191 util.SMlog("Tearing down the cache") 

2192 

2193 shared_target = self.target.vdi.sr.vdi(self.target.vdi.parent) 

2194 

2195 SR.registerSR(EXTSR.EXTSR) 

2196 local_sr = SR.SR.from_uuid(session, local_sr_uuid) 

2197 

2198 lock = Lock(self.LOCK_CACHE_SETUP, shared_target.uuid) 

2199 lock.acquire() 

2200 

2201 # local write node 

2202 local_leaf_path = "%s/%s.vhdcache" % \ 

2203 (local_sr.path, self.target.vdi.uuid) 

2204 if util.pathexists(local_leaf_path): 2204 ↛ 2208line 2204 didn't jump to line 2208, because the condition on line 2204 was never false

2205 util.SMlog("Deleting local leaf node %s" % local_leaf_path) 

2206 os.unlink(local_leaf_path) 

2207 

2208 read_cache_path = "%s/%s.vhdcache" % (local_sr.path, shared_target.uuid) 

2209 prt_tapdisk = Tapdisk.find_by_path(read_cache_path) 

2210 if not prt_tapdisk: 2210 ↛ 2211line 2210 didn't jump to line 2211, because the condition on line 2210 was never true

2211 util.SMlog("Parent tapdisk not found") 

2212 elif not self._is_tapdisk_in_use(prt_tapdisk.minor): 2212 ↛ 2220line 2212 didn't jump to line 2220, because the condition on line 2212 was never false

2213 util.SMlog("Parent tapdisk not in use: shutting down %s" % 

2214 read_cache_path) 

2215 try: 

2216 prt_tapdisk.shutdown() 

2217 except: 

2218 util.logException("shutting down parent tapdisk") 

2219 else: 

2220 util.SMlog("Parent tapdisk still in use: %s" % read_cache_path) 

2221 # the parent cache files are removed during the local SR's background 

2222 # GC run 

2223 

2224 lock.release() 

2225 

2226 @staticmethod 

2227 def _no_parent(vdi): 

2228 return vdi.parent is None or vdi.parent == '' 

2229 

2230 

2231PythonKeyError = KeyError 

2232 

2233 

2234class UEventHandler(object): 

2235 

2236 def __init__(self): 

2237 self._action = None 

2238 

2239 class KeyError(PythonKeyError): 

2240 def __init__(self, args): 

2241 super().__init__(args) 

2242 self.key = args[0] 

2243 

2244 @override 

2245 def __str__(self) -> str: 

2246 return \ 

2247 "Key '%s' missing in environment. " % self.key + \ 

2248 "Not called in udev context?" 

2249 

2250 @classmethod 

2251 def getenv(cls, key): 

2252 try: 

2253 return os.environ[key] 

2254 except KeyError as e: 

2255 raise cls.KeyError(e.args[0]) 

2256 

2257 def get_action(self): 

2258 if not self._action: 

2259 self._action = self.getenv('ACTION') 

2260 return self._action 

2261 

2262 class UnhandledEvent(Exception): 

2263 

2264 def __init__(self, event, handler): 

2265 self.event = event 

2266 self.handler = handler 

2267 

2268 @override 

2269 def __str__(self) -> str: 

2270 return "Uevent '%s' not handled by %s" % \ 

2271 (self.event, self.handler.__class__.__name__) 

2272 

2273 ACTIONS: Dict[str, Callable] = {} 

2274 

2275 def run(self): 

2276 

2277 action = self.get_action() 

2278 try: 

2279 fn = self.ACTIONS[action] 

2280 except KeyError: 

2281 raise self.UnhandledEvent(action, self) 

2282 

2283 return fn(self) 

2284 

2285 @override 

2286 def __str__(self) -> str: 

2287 try: 

2288 action = self.get_action() 

2289 except: 

2290 action = None 

2291 return "%s[%s]" % (self.__class__.__name__, action) 

2292 

2293 

2294class __BlktapControl(ClassDevice): 

2295 SYSFS_CLASSTYPE = "misc" 

2296 

2297 def __init__(self): 

2298 ClassDevice.__init__(self) 

2299 self._default_pool = None 

2300 

2301 @override 

2302 def sysfs_devname(self) -> str: 

2303 return "blktap!control" 

2304 

2305 class DefaultPool(Attribute): 

2306 SYSFS_NODENAME = "default_pool" 

2307 

2308 def get_default_pool_attr(self): 

2309 if not self._default_pool: 

2310 self._default_pool = self.DefaultPool.from_kobject(self) 

2311 return self._default_pool 

2312 

2313 def get_default_pool_name(self): 

2314 return self.get_default_pool_attr().readline() 

2315 

2316 def set_default_pool_name(self, name): 

2317 self.get_default_pool_attr().writeline(name) 

2318 

2319 def get_default_pool(self): 

2320 return BlktapControl.get_pool(self.get_default_pool_name()) 

2321 

2322 def set_default_pool(self, pool): 

2323 self.set_default_pool_name(pool.name) 

2324 

2325 class NoSuchPool(Exception): 

2326 def __init__(self, name): 

2327 self.name = name 

2328 

2329 @override 

2330 def __str__(self) -> str: 

2331 return "No such pool: {}".format(self.name) 

2332 

2333 def get_pool(self, name): 

2334 path = "%s/pools/%s" % (self.sysfs_path(), name) 

2335 

2336 if not os.path.isdir(path): 

2337 raise self.NoSuchPool(name) 

2338 

2339 return PagePool(path) 

2340 

2341BlktapControl = __BlktapControl() 

2342 

2343 

2344class PagePool(KObject): 

2345 

2346 def __init__(self, path): 

2347 self.path = path 

2348 self._size = None 

2349 

2350 @override 

2351 def sysfs_devname(self) -> str: 

2352 return '' 

2353 

2354 def sysfs_path(self): 

2355 return self.path 

2356 

2357 class Size(Attribute): 

2358 SYSFS_NODENAME = "size" 

2359 

2360 def get_size_attr(self): 

2361 if not self._size: 

2362 self._size = self.Size.from_kobject(self) 

2363 return self._size 

2364 

2365 def set_size(self, pages): 

2366 pages = str(pages) 

2367 self.get_size_attr().writeline(pages) 

2368 

2369 def get_size(self): 

2370 pages = self.get_size_attr().readline() 

2371 return int(pages) 

2372 

2373 

2374class BusDevice(KObject): 

2375 

2376 SYSFS_BUSTYPE: ClassVar[str] = "" 

2377 

2378 @classmethod 

2379 def sysfs_bus_path(cls): 

2380 return "/sys/bus/%s" % cls.SYSFS_BUSTYPE 

2381 

2382 def sysfs_path(self): 

2383 path = "%s/devices/%s" % (self.sysfs_bus_path(), 

2384 self.sysfs_devname()) 

2385 

2386 return path 

2387 

2388 

2389class XenbusDevice(BusDevice): 

2390 """Xenbus device, in XS and sysfs""" 

2391 

2392 XBT_NIL = "" 

2393 

2394 XENBUS_DEVTYPE: ClassVar[str] = "" 

2395 

2396 def __init__(self, domid, devid): 

2397 self.domid = int(domid) 

2398 self.devid = int(devid) 

2399 self._xbt = XenbusDevice.XBT_NIL 

2400 

2401 import xen.lowlevel.xs # pylint: disable=import-error 

2402 self.xs = xen.lowlevel.xs.xs() 

2403 

2404 def xs_path(self, key=None): 

2405 path = "backend/%s/%d/%d" % (self.XENBUS_DEVTYPE, 

2406 self.domid, 

2407 self.devid) 

2408 if key is not None: 

2409 path = "%s/%s" % (path, key) 

2410 

2411 return path 

2412 

2413 def _log(self, prio, msg): 

2414 syslog(prio, msg) 

2415 

2416 def info(self, msg): 

2417 self._log(_syslog.LOG_INFO, msg) 

2418 

2419 def warn(self, msg): 

2420 self._log(_syslog.LOG_WARNING, "WARNING: " + msg) 

2421 

2422 def _xs_read_path(self, path): 

2423 val = self.xs.read(self._xbt, path) 

2424 #self.info("read %s = '%s'" % (path, val)) 

2425 return val 

2426 

2427 def _xs_write_path(self, path, val): 

2428 self.xs.write(self._xbt, path, val) 

2429 self.info("wrote %s = '%s'" % (path, val)) 

2430 

2431 def _xs_rm_path(self, path): 

2432 self.xs.rm(self._xbt, path) 

2433 self.info("removed %s" % path) 

2434 

2435 def read(self, key): 

2436 return self._xs_read_path(self.xs_path(key)) 

2437 

2438 def has_xs_key(self, key): 

2439 return self.read(key) is not None 

2440 

2441 def write(self, key, val): 

2442 self._xs_write_path(self.xs_path(key), val) 

2443 

2444 def rm(self, key): 

2445 self._xs_rm_path(self.xs_path(key)) 

2446 

2447 def exists(self): 

2448 return self.has_xs_key(None) 

2449 

2450 def begin(self): 

2451 assert(self._xbt == XenbusDevice.XBT_NIL) 

2452 self._xbt = self.xs.transaction_start() 

2453 

2454 def commit(self): 

2455 ok = self.xs.transaction_end(self._xbt, 0) 

2456 self._xbt = XenbusDevice.XBT_NIL 

2457 return ok 

2458 

2459 def abort(self): 

2460 ok = self.xs.transaction_end(self._xbt, 1) 

2461 assert(ok == True) 

2462 self._xbt = XenbusDevice.XBT_NIL 

2463 

2464 def create_physical_device(self): 

2465 """The standard protocol is: toolstack writes 'params', linux hotplug 

2466 script translates this into physical-device=%x:%x""" 

2467 if self.has_xs_key("physical-device"): 

2468 return 

2469 try: 

2470 params = self.read("params") 

2471 frontend = self.read("frontend") 

2472 is_cdrom = self._xs_read_path("%s/device-type") == "cdrom" 

2473 # We don't have PV drivers for CDROM devices, so we prevent blkback 

2474 # from opening the physical-device 

2475 if not(is_cdrom): 

2476 major_minor = os.stat(params).st_rdev 

2477 major, minor = divmod(major_minor, 256) 

2478 self.write("physical-device", "%x:%x" % (major, minor)) 

2479 except: 

2480 util.logException("BLKTAP2:create_physical_device") 

2481 

2482 def signal_hotplug(self, online=True): 

2483 xapi_path = "/xapi/%d/hotplug/%s/%d/hotplug" % (self.domid, 

2484 self.XENBUS_DEVTYPE, 

2485 self.devid) 

2486 upstream_path = self.xs_path("hotplug-status") 

2487 if online: 

2488 self._xs_write_path(xapi_path, "online") 

2489 self._xs_write_path(upstream_path, "connected") 

2490 else: 

2491 self._xs_rm_path(xapi_path) 

2492 self._xs_rm_path(upstream_path) 

2493 

2494 @override 

2495 def sysfs_devname(self) -> str: 

2496 return "%s-%d-%d" % (self.XENBUS_DEVTYPE, 

2497 self.domid, self.devid) 

2498 

2499 @override 

2500 def __str__(self) -> str: 

2501 return self.sysfs_devname() 

2502 

2503 @classmethod 

2504 def find(cls): 

2505 pattern = "/sys/bus/%s/devices/%s*" % (cls.SYSFS_BUSTYPE, 

2506 cls.XENBUS_DEVTYPE) 

2507 for path in glob.glob(pattern): 

2508 

2509 name = os.path.basename(path) 

2510 (_type, domid, devid) = name.split('-') 

2511 

2512 yield cls(domid, devid) 

2513 

2514 

2515class XenBackendDevice(XenbusDevice): 

2516 """Xenbus backend device""" 

2517 SYSFS_BUSTYPE = "xen-backend" 

2518 

2519 @classmethod 

2520 def from_xs_path(cls, _path): 

2521 (_backend, _type, domid, devid) = _path.split('/') 

2522 

2523 assert _backend == 'backend' 

2524 assert _type == cls.XENBUS_DEVTYPE 

2525 

2526 domid = int(domid) 

2527 devid = int(devid) 

2528 

2529 return cls(domid, devid) 

2530 

2531 

2532class Blkback(XenBackendDevice): 

2533 """A blkback VBD""" 

2534 

2535 XENBUS_DEVTYPE = "vbd" 

2536 

2537 def __init__(self, domid, devid): 

2538 XenBackendDevice.__init__(self, domid, devid) 

2539 self._phy = None 

2540 self._vdi_uuid = None 

2541 self._q_state = None 

2542 self._q_events = None 

2543 

2544 class XenstoreValueError(Exception): 

2545 KEY: ClassVar[str] = "" 

2546 

2547 def __init__(self, vbd, _str): 

2548 self.vbd = vbd 

2549 self.str = _str 

2550 

2551 @override 

2552 def __str__(self) -> str: 

2553 return "Backend %s " % self.vbd + \ 

2554 "has %s = %s" % (self.KEY, self.str) 

2555 

2556 class PhysicalDeviceError(XenstoreValueError): 

2557 KEY = "physical-device" 

2558 

2559 class PhysicalDevice(object): 

2560 

2561 def __init__(self, major, minor): 

2562 self.major = int(major) 

2563 self.minor = int(minor) 

2564 

2565 @classmethod 

2566 def from_xbdev(cls, xbdev): 

2567 

2568 phy = xbdev.read("physical-device") 

2569 

2570 try: 

2571 major, minor = phy.split(':') 

2572 major = int(major, 0x10) 

2573 minor = int(minor, 0x10) 

2574 except Exception as e: 

2575 raise xbdev.PhysicalDeviceError(xbdev, phy) 

2576 

2577 return cls(major, minor) 

2578 

2579 def makedev(self): 

2580 return os.makedev(self.major, self.minor) 

2581 

2582 def is_tap(self): 

2583 return self.major == Tapdisk.major() 

2584 

2585 @override 

2586 def __str__(self) -> str: 

2587 return "%s:%s" % (self.major, self.minor) 

2588 

2589 @override 

2590 def __eq__(self, other) -> bool: 

2591 return \ 

2592 self.major == other.major and \ 

2593 self.minor == other.minor 

2594 

2595 def get_physical_device(self): 

2596 if not self._phy: 

2597 self._phy = self.PhysicalDevice.from_xbdev(self) 

2598 return self._phy 

2599 

2600 class QueueEvents(Attribute): 

2601 """Blkback sysfs node to select queue-state event 

2602 notifications emitted.""" 

2603 

2604 SYSFS_NODENAME = "queue_events" 

2605 

2606 QUEUE_RUNNING = (1 << 0) 

2607 QUEUE_PAUSE_DONE = (1 << 1) 

2608 QUEUE_SHUTDOWN_DONE = (1 << 2) 

2609 QUEUE_PAUSE_REQUEST = (1 << 3) 

2610 QUEUE_SHUTDOWN_REQUEST = (1 << 4) 

2611 

2612 def get_mask(self): 

2613 return int(self.readline(), 0x10) 

2614 

2615 def set_mask(self, mask): 

2616 self.writeline("0x%x" % mask) 

2617 

2618 def get_queue_events(self): 

2619 if not self._q_events: 

2620 self._q_events = self.QueueEvents.from_kobject(self) 

2621 return self._q_events 

2622 

2623 def get_vdi_uuid(self): 

2624 if not self._vdi_uuid: 

2625 self._vdi_uuid = self.read("sm-data/vdi-uuid") 

2626 return self._vdi_uuid 

2627 

2628 def pause_requested(self): 

2629 return self.has_xs_key("pause") 

2630 

2631 def shutdown_requested(self): 

2632 return self.has_xs_key("shutdown-request") 

2633 

2634 def shutdown_done(self): 

2635 return self.has_xs_key("shutdown-done") 

2636 

2637 def running(self): 

2638 return self.has_xs_key('queue-0/kthread-pid') 

2639 

2640 @classmethod 

2641 def find_by_physical_device(cls, phy): 

2642 for dev in cls.find(): 

2643 try: 

2644 _phy = dev.get_physical_device() 

2645 except cls.PhysicalDeviceError: 

2646 continue 

2647 

2648 if _phy == phy: 

2649 yield dev 

2650 

2651 @classmethod 

2652 def find_by_tap_minor(cls, minor): 

2653 phy = cls.PhysicalDevice(Tapdisk.major(), minor) 

2654 return cls.find_by_physical_device(phy) 

2655 

2656 @classmethod 

2657 def find_by_tap(cls, tapdisk): 

2658 return cls.find_by_tap_minor(tapdisk.minor) 

2659 

2660 def has_tap(self): 

2661 

2662 if not self.can_tap(): 

2663 return False 

2664 

2665 phy = self.get_physical_device() 

2666 if phy: 

2667 return phy.is_tap() 

2668 

2669 return False 

2670 

2671 def is_bare_hvm(self): 

2672 """File VDIs for bare HVM. These are directly accessible by Qemu.""" 

2673 try: 

2674 self.get_physical_device() 

2675 

2676 except self.PhysicalDeviceError as e: 

2677 vdi_type = self.read("type") 

2678 

2679 self.info("HVM VDI: type=%s" % vdi_type) 

2680 

2681 if e.str is not None or vdi_type != 'file': 

2682 raise 

2683 

2684 return True 

2685 

2686 return False 

2687 

2688 def can_tap(self): 

2689 return not self.is_bare_hvm() 

2690 

2691 

2692class BlkbackEventHandler(UEventHandler): 

2693 

2694 LOG_FACILITY = _syslog.LOG_DAEMON 

2695 

2696 def __init__(self, ident=None, action=None): 

2697 if not ident: 

2698 ident = self.__class__.__name__ 

2699 

2700 self.ident = ident 

2701 self._vbd = None 

2702 self._tapdisk = None 

2703 

2704 UEventHandler.__init__(self) 

2705 

2706 @override 

2707 def run(self) -> None: 

2708 

2709 self.xs_path = self.getenv('XENBUS_PATH') 

2710 openlog(str(self), 0, self.LOG_FACILITY) 

2711 

2712 UEventHandler.run(self) 

2713 

2714 @override 

2715 def __str__(self) -> str: 

2716 

2717 try: 

2718 path = self.xs_path 

2719 except: 

2720 path = None 

2721 

2722 try: 

2723 action = self.get_action() 

2724 except: 

2725 action = None 

2726 

2727 return "%s[%s](%s)" % (self.ident, action, path) 

2728 

2729 def _log(self, prio, msg): 

2730 syslog(prio, msg) 

2731 util.SMlog("%s: " % self + msg) 

2732 

2733 def info(self, msg): 

2734 self._log(_syslog.LOG_INFO, msg) 

2735 

2736 def warn(self, msg): 

2737 self._log(_syslog.LOG_WARNING, "WARNING: " + msg) 

2738 

2739 def error(self, msg): 

2740 self._log(_syslog.LOG_ERR, "ERROR: " + msg) 

2741 

2742 def get_vbd(self): 

2743 if not self._vbd: 

2744 self._vbd = Blkback.from_xs_path(self.xs_path) 

2745 return self._vbd 

2746 

2747 def get_tapdisk(self): 

2748 if not self._tapdisk: 

2749 minor = self.get_vbd().get_physical_device().minor 

2750 self._tapdisk = Tapdisk.from_minor(minor) 

2751 return self._tapdisk 

2752 # 

2753 # Events 

2754 # 

2755 

2756 def __add(self): 

2757 vbd = self.get_vbd() 

2758 # Manage blkback transitions 

2759 # self._manage_vbd() 

2760 

2761 vbd.create_physical_device() 

2762 

2763 vbd.signal_hotplug() 

2764 

2765 @retried(backoff=.5, limit=10) 

2766 def add(self): 

2767 try: 

2768 self.__add() 

2769 except Attribute.NoSuchAttribute as e: 

2770 # 

2771 # FIXME: KOBJ_ADD is racing backend.probe, which 

2772 # registers device attributes. So poll a little. 

2773 # 

2774 self.warn("%s, still trying." % e) 

2775 raise RetryLoop.TransientFailure(e) 

2776 

2777 def __change(self): 

2778 vbd = self.get_vbd() 

2779 

2780 # 1. Pause or resume tapdisk (if there is one) 

2781 

2782 if vbd.has_tap(): 

2783 pass 

2784 #self._pause_update_tap() 

2785 

2786 # 2. Signal Xapi.VBD.pause/resume completion 

2787 

2788 self._signal_xapi() 

2789 

2790 def change(self): 

2791 vbd = self.get_vbd() 

2792 

2793 # NB. Beware of spurious change events between shutdown 

2794 # completion and device removal. Also, Xapi.VM.migrate will 

2795 # hammer a couple extra shutdown-requests into the source VBD. 

2796 

2797 while True: 

2798 vbd.begin() 

2799 

2800 if not vbd.exists() or \ 

2801 vbd.shutdown_done(): 

2802 break 

2803 

2804 self.__change() 

2805 

2806 if vbd.commit(): 

2807 return 

2808 

2809 vbd.abort() 

2810 self.info("spurious uevent, ignored.") 

2811 

2812 def remove(self): 

2813 vbd = self.get_vbd() 

2814 

2815 vbd.signal_hotplug(False) 

2816 

2817 ACTIONS = {'add': add, 

2818 'change': change, 

2819 'remove': remove} 

2820 # 

2821 # VDI.pause 

2822 # 

2823 

2824 def _tap_should_pause(self): 

2825 """Enumerate all VBDs on our tapdisk. Returns true iff any was 

2826 paused""" 

2827 

2828 tapdisk = self.get_tapdisk() 

2829 TapState = Tapdisk.PauseState 

2830 

2831 PAUSED = 'P' 

2832 RUNNING = 'R' 

2833 PAUSED_SHUTDOWN = 'P,S' 

2834 # NB. Shutdown/paused is special. We know it's not going 

2835 # to restart again, so it's a RUNNING. Still better than 

2836 # backtracking a removed device during Vbd.unplug completion. 

2837 

2838 next = TapState.RUNNING 

2839 vbds = {} 

2840 

2841 for vbd in Blkback.find_by_tap(tapdisk): 

2842 name = str(vbd) 

2843 

2844 pausing = vbd.pause_requested() 

2845 closing = vbd.shutdown_requested() 

2846 running = vbd.running() 

2847 

2848 if pausing: 

2849 if closing and not running: 

2850 vbds[name] = PAUSED_SHUTDOWN 

2851 else: 

2852 vbds[name] = PAUSED 

2853 next = TapState.PAUSED 

2854 

2855 else: 

2856 vbds[name] = RUNNING 

2857 

2858 self.info("tapdev%d (%s): %s -> %s" 

2859 % (tapdisk.minor, tapdisk.pause_state(), 

2860 vbds, next)) 

2861 

2862 return next == TapState.PAUSED 

2863 

2864 def _pause_update_tap(self): 

2865 vbd = self.get_vbd() 

2866 

2867 if self._tap_should_pause(): 

2868 self._pause_tap() 

2869 else: 

2870 self._resume_tap() 

2871 

2872 def _pause_tap(self): 

2873 tapdisk = self.get_tapdisk() 

2874 

2875 if not tapdisk.is_paused(): 

2876 self.info("pausing %s" % tapdisk) 

2877 tapdisk.pause() 

2878 

2879 def _resume_tap(self): 

2880 tapdisk = self.get_tapdisk() 

2881 

2882 # NB. Raw VDI snapshots. Refresh the physical path and 

2883 # type while resuming. 

2884 vbd = self.get_vbd() 

2885 vdi_uuid = vbd.get_vdi_uuid() 

2886 

2887 if tapdisk.is_paused(): 

2888 self.info("loading vdi uuid=%s" % vdi_uuid) 

2889 vdi = VDI.from_cli(vdi_uuid) 

2890 _type = vdi.get_tap_type() 

2891 path = vdi.get_phy_path() 

2892 self.info("resuming %s on %s:%s" % (tapdisk, _type, path)) 

2893 tapdisk.unpause(_type, path) 

2894 # 

2895 # VBD.pause/shutdown 

2896 # 

2897 

2898 def _manage_vbd(self): 

2899 vbd = self.get_vbd() 

2900 # NB. Hook into VBD state transitions. 

2901 

2902 events = vbd.get_queue_events() 

2903 

2904 mask = 0 

2905 mask |= events.QUEUE_PAUSE_DONE # pause/unpause 

2906 mask |= events.QUEUE_SHUTDOWN_DONE # shutdown 

2907 # TODO: mask |= events.QUEUE_SHUTDOWN_REQUEST, for shutdown=force 

2908 # TODO: mask |= events.QUEUE_RUNNING, for ionice updates etc 

2909 

2910 events.set_mask(mask) 

2911 self.info("wrote %s = %#02x" % (events.path, mask)) 

2912 

2913 def _signal_xapi(self): 

2914 vbd = self.get_vbd() 

2915 

2916 pausing = vbd.pause_requested() 

2917 closing = vbd.shutdown_requested() 

2918 running = vbd.running() 

2919 

2920 handled = 0 

2921 

2922 if pausing and not running: 

2923 if 'pause-done' not in vbd: 

2924 vbd.write('pause-done', '') 

2925 handled += 1 

2926 

2927 if not pausing: 

2928 if 'pause-done' in vbd: 

2929 vbd.rm('pause-done') 

2930 handled += 1 

2931 

2932 if closing and not running: 

2933 if 'shutdown-done' not in vbd: 

2934 vbd.write('shutdown-done', '') 

2935 handled += 1 

2936 

2937 if handled > 1: 

2938 self.warn("handled %d events, " % handled + 

2939 "pausing=%s closing=%s running=%s" % \ 

2940 (pausing, closing, running)) 

2941 

2942if __name__ == '__main__': 2942 ↛ 2944line 2942 didn't jump to line 2944, because the condition on line 2942 was never true

2943 

2944 import sys 

2945 prog = os.path.basename(sys.argv[0]) 

2946 

2947 # 

2948 # Simple CLI interface for manual operation 

2949 # 

2950 # tap.* level calls go down to local Tapdisk()s (by physical path) 

2951 # vdi.* level calls run the plugin calls across host boundaries. 

2952 # 

2953 

2954 def usage(stream): 

2955 print("usage: %s tap.{list|major}" % prog, file=stream) 

2956 print(" %s tap.{launch|find|get|pause|" % prog + \ 

2957 "unpause|shutdown|stats} {[<tt>:]<path>} | [minor=]<int> | .. }", file=stream) 

2958 print(" %s vbd.uevent" % prog, file=stream) 

2959 

2960 try: 

2961 cmd = sys.argv[1] 

2962 except IndexError: 

2963 usage(sys.stderr) 

2964 sys.exit(1) 

2965 

2966 try: 

2967 _class, method = cmd.split('.') 

2968 except: 

2969 usage(sys.stderr) 

2970 sys.exit(1) 

2971 

2972 # 

2973 # Local Tapdisks 

2974 # 

2975 

2976 if cmd == 'tap.major': 

2977 

2978 print("%d" % Tapdisk.major()) 

2979 

2980 elif cmd == 'tap.launch': 

2981 

2982 tapdisk = Tapdisk.launch_from_arg(sys.argv[2]) 

2983 print("Launched %s" % tapdisk, file=sys.stderr) 

2984 

2985 elif _class == 'tap': 

2986 

2987 attrs: Dict[str, Any] = {} 

2988 for item in sys.argv[2:]: 

2989 try: 

2990 key, val = item.split('=') 

2991 attrs[key] = val 

2992 continue 

2993 except ValueError: 

2994 pass 

2995 

2996 try: 

2997 attrs['minor'] = int(item) 

2998 continue 

2999 except ValueError: 

3000 pass 

3001 

3002 try: 

3003 arg = Tapdisk.Arg.parse(item) 

3004 attrs['_type'] = arg.type 

3005 attrs['path'] = arg.path 

3006 continue 

3007 except Tapdisk.Arg.InvalidArgument: 

3008 pass 

3009 

3010 attrs['path'] = item 

3011 

3012 if cmd == 'tap.list': 

3013 

3014 for tapdisk in Tapdisk.list( ** attrs): 

3015 blktap = tapdisk.get_blktap() 

3016 print(tapdisk, end=' ') 

3017 print("%s: task=%s pool=%s" % \ 

3018 (blktap, 

3019 blktap.get_task_pid(), 

3020 blktap.get_pool_name())) 

3021 

3022 elif cmd == 'tap.vbds': 

3023 # Find all Blkback instances for a given tapdisk 

3024 

3025 for tapdisk in Tapdisk.list( ** attrs): 

3026 print("%s:" % tapdisk, end=' ') 

3027 for vbd in Blkback.find_by_tap(tapdisk): 

3028 print(vbd, end=' ') 

3029 print() 

3030 

3031 else: 

3032 

3033 if not attrs: 

3034 usage(sys.stderr) 

3035 sys.exit(1) 

3036 

3037 try: 

3038 tapdisk = Tapdisk.get( ** attrs) 

3039 except TypeError: 

3040 usage(sys.stderr) 

3041 sys.exit(1) 

3042 

3043 if cmd == 'tap.shutdown': 

3044 # Shutdown a running tapdisk, or raise 

3045 tapdisk.shutdown() 

3046 print("Shut down %s" % tapdisk, file=sys.stderr) 

3047 

3048 elif cmd == 'tap.pause': 

3049 # Pause an unpaused tapdisk, or raise 

3050 tapdisk.pause() 

3051 print("Paused %s" % tapdisk, file=sys.stderr) 

3052 

3053 elif cmd == 'tap.unpause': 

3054 # Unpause a paused tapdisk, or raise 

3055 tapdisk.unpause() 

3056 print("Unpaused %s" % tapdisk, file=sys.stderr) 

3057 

3058 elif cmd == 'tap.stats': 

3059 # Gather tapdisk status 

3060 stats = tapdisk.stats() 

3061 print("%s:" % tapdisk) 

3062 print(json.dumps(stats, indent=True)) 

3063 

3064 else: 

3065 usage(sys.stderr) 

3066 sys.exit(1) 

3067 

3068 elif cmd == 'vbd.uevent': 

3069 

3070 hnd = BlkbackEventHandler(cmd) 

3071 

3072 if not sys.stdin.isatty(): 

3073 try: 

3074 hnd.run() 

3075 except Exception as e: 

3076 hnd.error("Unhandled Exception: %s" % e) 

3077 

3078 import traceback 

3079 _type, value, tb = sys.exc_info() 

3080 trace = traceback.format_exception(_type, value, tb) 

3081 for entry in trace: 

3082 for line in entry.rstrip().split('\n'): 

3083 util.SMlog(line) 

3084 else: 

3085 hnd.run() 

3086 

3087 elif cmd == 'vbd.list': 

3088 

3089 for vbd in Blkback.find(): 

3090 print(vbd, \ 

3091 "physical-device=%s" % vbd.get_physical_device(), \ 

3092 "pause=%s" % vbd.pause_requested()) 

3093 

3094 else: 

3095 usage(sys.stderr) 

3096 sys.exit(1)