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 

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 syslog import openlog, syslog 

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

46 

47import resetvdis 

48import vhdutil 

49import lvhdutil 

50 

51import VDI as sm 

52 

53# For RRDD Plugin Registration 

54from xmlrpc.client import ServerProxy, Transport 

55from socket import socket, AF_UNIX, SOCK_STREAM 

56 

57try: 

58 from linstorvolumemanager import log_drbd_openers 

59 LINSTOR_AVAILABLE = True 

60except ImportError: 

61 LINSTOR_AVAILABLE = False 

62 

63PLUGIN_TAP_PAUSE = "tapdisk-pause" 

64 

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

66 

67NUM_PAGES_PER_RING = 32 * 11 

68MAX_FULL_RINGS = 8 

69POOL_NAME_KEY = "mem-pool" 

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

71 

72ENABLE_MULTIPLE_ATTACH = "/etc/xensource/allow_multiple_vdi_attach" 

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

74 

75 

76def locking(excType, override=True): 

77 def locking2(op): 

78 def wrapper(self, *args): 

79 self.lock.acquire() 

80 try: 

81 try: 

82 ret = op(self, * args) 

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

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

85 msg = str(e) 

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

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

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

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

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

91 else: 

92 raise 

93 except: 

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

95 raise 

96 finally: 

97 self.lock.release() 

98 return ret 

99 return wrapper 

100 return locking2 

101 

102 

103class RetryLoop(object): 

104 

105 def __init__(self, backoff, limit): 

106 self.backoff = backoff 

107 self.limit = limit 

108 

109 def __call__(self, f): 

110 

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

112 attempt = 0 

113 

114 while True: 

115 attempt += 1 

116 

117 try: 

118 return f( * __t, ** __d) 

119 

120 except self.TransientFailure as e: 

121 e = e.exception 

122 

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

124 raise e 

125 

126 time.sleep(self.backoff) 

127 

128 return loop 

129 

130 class TransientFailure(Exception): 

131 def __init__(self, exception): 

132 self.exception = exception 

133 

134 

135def retried(**args): 

136 return RetryLoop( ** args) 

137 

138 

139class TapCtl(object): 

140 """Tapdisk IPC utility calls.""" 

141 

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

143 

144 def __init__(self, cmd, p): 

145 self.cmd = cmd 

146 self._p = p 

147 self.stdout = p.stdout 

148 

149 class CommandFailure(Exception): 

150 """TapCtl cmd failure.""" 

151 

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

153 self.cmd = cmd 

154 self.info = info 

155 

156 @override 

157 def __str__(self) -> str: 

158 items = self.info.items() 

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

160 for item in items) 

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

162 

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

164 # exception 

165 def __getattr__(self, key): 

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

167 return self.info[key] 

168 return object.__getattribute__(self, key) 

169 

170 @property 

171 def has_status(self): 

172 return 'status' in self.info 

173 

174 @property 

175 def has_signal(self): 

176 return 'signal' in self.info 

177 

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

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

180 def get_error_code(self): 

181 key = 'status' 

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

183 return self.info[key] 

184 else: 

185 return 0 

186 

187 @classmethod 

188 def __mkcmd_real(cls, args): 

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

190 

191 __next_mkcmd = __mkcmd_real 

192 

193 @classmethod 

194 def _mkcmd(cls, args): 

195 

196 __next_mkcmd = cls.__next_mkcmd 

197 cls.__next_mkcmd = cls.__mkcmd_real 

198 

199 return __next_mkcmd(args) 

200 

201 @classmethod 

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

203 """ 

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

205 Raises a TapCtl.CommandFailure if subprocess creation failed. 

206 """ 

207 cmd = cls._mkcmd(args) 

208 

209 if not quiet: 

210 util.SMlog(cmd) 

211 try: 

212 p = subprocess.Popen(cmd, 

213 stdin=subprocess.PIPE, 

214 stdout=subprocess.PIPE, 

215 stderr=subprocess.PIPE, 

216 close_fds=True, 

217 universal_newlines=text_mode) 

218 if input: 

219 p.stdin.write(input) 

220 p.stdin.close() 

221 except OSError as e: 

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

223 

224 return cls(cmd, p) 

225 

226 def _errmsg(self): 

227 output = map(str.rstrip, self._p.stderr) 

228 return "; ".join(output) 

229 

230 def _wait(self, quiet=False): 

231 """ 

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

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

234 """ 

235 status = self._p.wait() 

236 if not quiet: 

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

238 

239 if status == 0: 

240 return 

241 

242 info = {'errmsg': self._errmsg(), 

243 'pid': self._p.pid} 

244 

245 if status < 0: 

246 info['signal'] = -status 

247 else: 

248 info['status'] = status 

249 

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

251 

252 @classmethod 

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

254 """ 

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

256 """ 

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

258 text_mode=text_mode) 

259 

260 output = tapctl.stdout.readline().rstrip() 

261 

262 tapctl._wait(quiet) 

263 return output 

264 

265 @staticmethod 

266 def _maybe(opt, parm): 

267 if parm is not None: 

268 return [opt, parm] 

269 return [] 

270 

271 @classmethod 

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

273 args = ["list"] 

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

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

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

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

278 

279 tapctl = cls._call(args, True) 

280 

281 for stdout_line in tapctl.stdout: 

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

283 # confuses this parser 

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

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

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

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

288 row = {} 

289 

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

291 bits = field.split('=') 

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

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

294 

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

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

297 

298 elif key in ('state'): 

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

300 

301 else: 

302 row[key] = val 

303 else: 

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

305 yield row 

306 

307 tapctl._wait(True) 

308 

309 @classmethod 

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

311 def list(cls, **args): 

312 

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

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

315 # be fixed in SM. 

316 

317 try: 

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

319 

320 except cls.CommandFailure as e: 

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

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

323 raise RetryLoop.TransientFailure(e) 

324 raise 

325 

326 @classmethod 

327 def allocate(cls, devpath=None): 

328 args = ["allocate"] 

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

330 return cls._pread(args) 

331 

332 @classmethod 

333 def free(cls, minor): 

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

335 cls._pread(args) 

336 

337 @classmethod 

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

339 def spawn(cls): 

340 args = ["spawn"] 

341 try: 

342 pid = cls._pread(args) 

343 return int(pid) 

344 except cls.CommandFailure as ce: 

345 # intermittent failures to spawn. CA-292268 

346 if ce.status == 1: 

347 raise RetryLoop.TransientFailure(ce) 

348 raise 

349 

350 @classmethod 

351 def attach(cls, pid, minor): 

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

353 cls._pread(args) 

354 

355 @classmethod 

356 def detach(cls, pid, minor): 

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

358 cls._pread(args) 

359 

360 @classmethod 

361 def _load_key(cls, key_hash, vdi_uuid): 

362 import plugins 

363 

364 return plugins.load_key(key_hash, vdi_uuid) 

365 

366 @classmethod 

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

368 params = Tapdisk.Arg(_type, _file) 

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

370 text_mode = True 

371 input = None 

372 if options.get("rdonly"): 

373 args.append('-R') 

374 if options.get("lcache"): 

375 args.append("-r") 

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

377 args.append("-e") 

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

379 if options.get("secondary"): 

380 args.append("-2") 

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

382 if options.get("standby"): 

383 args.append("-s") 

384 if options.get("timeout"): 

385 args.append("-t") 

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

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

388 args.append("-D") 

389 if options.get('cbtlog'): 

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

391 if options.get('key_hash'): 

392 key_hash = options['key_hash'] 

393 vdi_uuid = options['vdi_uuid'] 

394 key = cls._load_key(key_hash, vdi_uuid) 

395 

396 if not key: 

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

398 input = key 

399 text_mode = False 

400 args.append('-E') 

401 

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

403 

404 @classmethod 

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

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

407 if force: 

408 args += ["-f"] 

409 cls._pread(args) 

410 

411 @classmethod 

412 def pause(cls, pid, minor): 

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

414 cls._pread(args) 

415 

416 @classmethod 

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

418 cbtlog=None): 

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

420 if mirror: 

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

422 if _type and _file: 

423 params = Tapdisk.Arg(_type, _file) 

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

425 if cbtlog: 

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

427 cls._pread(args) 

428 

429 @classmethod 

430 def shutdown(cls, pid): 

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

432 os.kill(pid, signal.SIGTERM) 

433 os.waitpid(pid, 0) 

434 

435 @classmethod 

436 def stats(cls, pid, minor): 

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

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

439 

440 @classmethod 

441 def major(cls): 

442 args = ["major"] 

443 major = cls._pread(args) 

444 return int(major) 

445 

446 

447class TapdiskExists(Exception): 

448 """Tapdisk already running.""" 

449 

450 def __init__(self, tapdisk): 

451 self.tapdisk = tapdisk 

452 

453 @override 

454 def __str__(self) -> str: 

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

456 

457 

458class TapdiskNotRunning(Exception): 

459 """No such Tapdisk.""" 

460 

461 def __init__(self, **attrs): 

462 self.attrs = attrs 

463 

464 @override 

465 def __str__(self) -> str: 

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

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

468 for attr in items) 

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

470 

471 

472class TapdiskNotUnique(Exception): 

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

474 

475 def __init__(self, tapdisks): 

476 self.tapdisks = tapdisks 

477 

478 @override 

479 def __str__(self) -> str: 

480 tapdisks = map(str, self.tapdisks) 

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

482 

483 

484class TapdiskFailed(Exception): 

485 """Tapdisk launch failure.""" 

486 

487 def __init__(self, arg, err): 

488 self.arg = arg 

489 self.err = err 

490 

491 @override 

492 def __str__(self) -> str: 

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

494 

495 def get_error(self): 

496 return self.err 

497 

498 

499class TapdiskInvalidState(Exception): 

500 """Tapdisk pause/unpause failure""" 

501 

502 def __init__(self, tapdisk): 

503 self.tapdisk = tapdisk 

504 

505 @override 

506 def __str__(self) -> str: 

507 return str(self.tapdisk) 

508 

509 

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

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

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

513 assert parent != path 

514 try: 

515 if parent: 

516 mkdirs(parent, mode) 

517 if subdir: 

518 os.mkdir(path, mode) 

519 except OSError as e: 

520 if e.errno != errno.EEXIST: 

521 raise 

522 

523 

524class KObject(object): 

525 

526 SYSFS_CLASSTYPE: ClassVar[str] = "" 

527 

528 @abstractmethod 

529 def sysfs_devname(self) -> str: 

530 pass 

531 

532 

533class Attribute(object): 

534 

535 SYSFS_NODENAME: ClassVar[str] = "" 

536 

537 def __init__(self, path): 

538 self.path = path 

539 

540 @classmethod 

541 def from_kobject(cls, kobj): 

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

543 return cls(path) 

544 

545 class NoSuchAttribute(Exception): 

546 def __init__(self, name): 

547 self.name = name 

548 

549 @override 

550 def __str__(self) -> str: 

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

552 

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

554 try: 

555 return open(self.path, mode) 

556 except IOError as e: 

557 if e.errno == errno.ENOENT: 

558 raise self.NoSuchAttribute(self) 

559 raise 

560 

561 def readline(self): 

562 f = self._open('r') 

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

564 f.close() 

565 return s 

566 

567 def writeline(self, val): 

568 f = self._open('w') 

569 f.write(val) 

570 f.close() 

571 

572 

573class ClassDevice(KObject): 

574 

575 @classmethod 

576 def sysfs_class_path(cls): 

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

578 

579 def sysfs_path(self): 

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

581 self.sysfs_devname()) 

582 

583 

584class Blktap(ClassDevice): 

585 

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

587 

588 SYSFS_CLASSTYPE = "blktap2" 

589 

590 def __init__(self, minor): 

591 self.minor = minor 

592 self._pool = None 

593 self._task = None 

594 

595 @classmethod 

596 def allocate(cls): 

597 # FIXME. Should rather go into init. 

598 mkdirs(cls.DEV_BASEDIR) 

599 

600 devname = TapCtl.allocate() 

601 minor = Tapdisk._parse_minor(devname) 

602 return cls(minor) 

603 

604 def free(self): 

605 TapCtl.free(self.minor) 

606 

607 @override 

608 def __str__(self) -> str: 

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

610 

611 @override 

612 def sysfs_devname(self) -> str: 

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

614 

615 class Pool(Attribute): 

616 SYSFS_NODENAME = "pool" 

617 

618 def get_pool_attr(self): 

619 if not self._pool: 

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

621 return self._pool 

622 

623 def get_pool_name(self): 

624 return self.get_pool_attr().readline() 

625 

626 def set_pool_name(self, name): 

627 self.get_pool_attr().writeline(name) 

628 

629 def set_pool_size(self, pages): 

630 self.get_pool().set_size(pages) 

631 

632 def get_pool(self): 

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

634 

635 def set_pool(self, pool): 

636 self.set_pool_name(pool.name) 

637 

638 class Task(Attribute): 

639 SYSFS_NODENAME = "task" 

640 

641 def get_task_attr(self): 

642 if not self._task: 

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

644 return self._task 

645 

646 def get_task_pid(self): 

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

648 try: 

649 return int(pid) 

650 except ValueError: 

651 return None 

652 

653 def find_tapdisk(self): 

654 pid = self.get_task_pid() 

655 if pid is None: 

656 return None 

657 

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

659 

660 def get_tapdisk(self): 

661 tapdisk = self.find_tapdisk() 

662 if not tapdisk: 

663 raise TapdiskNotRunning(minor=self.minor) 

664 return tapdisk 

665 

666 

667class Tapdisk(object): 

668 

669 TYPES = ['aio', 'vhd'] 

670 

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

672 self.pid = pid 

673 self.minor = minor 

674 self.type = _type 

675 self.path = path 

676 self.state = state 

677 self._dirty = False 

678 self._blktap = None 

679 

680 @override 

681 def __str__(self) -> str: 

682 state = self.pause_state() 

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

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

685 

686 @classmethod 

687 def list(cls, **args): 

688 

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

690 

691 args = {'pid': None, 

692 'minor': None, 

693 'state': None, 

694 '_type': None, 

695 'path': None} 

696 

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

698 if key in args: 

699 args[key] = val 

700 

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

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

703 args['_type'] = image.type 

704 args['path'] = image.path 

705 

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

707 continue 

708 

709 yield Tapdisk( ** args) 

710 

711 @classmethod 

712 def find(cls, **args): 

713 

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

715 

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

717 raise TapdiskNotUnique(found) 

718 

719 if found: 719 ↛ 720line 719 didn't jump to line 720, because the condition on line 719 was never true

720 return found[0] 

721 

722 return None 

723 

724 @classmethod 

725 def find_by_path(cls, path): 

726 return cls.find(path=path) 

727 

728 @classmethod 

729 def find_by_minor(cls, minor): 

730 return cls.find(minor=minor) 

731 

732 @classmethod 

733 def get(cls, **attrs): 

734 

735 tapdisk = cls.find( ** attrs) 

736 

737 if not tapdisk: 

738 raise TapdiskNotRunning( ** attrs) 

739 

740 return tapdisk 

741 

742 @classmethod 

743 def from_path(cls, path): 

744 return cls.get(path=path) 

745 

746 @classmethod 

747 def from_minor(cls, minor): 

748 return cls.get(minor=minor) 

749 

750 @classmethod 

751 def __from_blktap(cls, blktap): 

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

753 tapdisk._blktap = blktap 

754 return tapdisk 

755 

756 def get_blktap(self): 

757 if not self._blktap: 

758 self._blktap = Blktap(self.minor) 

759 return self._blktap 

760 

761 class Arg: 

762 

763 def __init__(self, _type, path): 

764 self.type = _type 

765 self.path = path 

766 

767 @override 

768 def __str__(self) -> str: 

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

770 

771 @classmethod 

772 def parse(cls, arg): 

773 

774 try: 

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

776 except ValueError: 

777 raise cls.InvalidArgument(arg) 

778 

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

780 raise cls.InvalidType(_type) 

781 

782 return cls(_type, path) 

783 

784 class InvalidType(Exception): 

785 def __init__(self, _type): 

786 self.type = _type 

787 

788 @override 

789 def __str__(self) -> str: 

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

791 

792 class InvalidArgument(Exception): 

793 def __init__(self, arg): 

794 self.arg = arg 

795 

796 @override 

797 def __str__(self) -> str: 

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

799 

800 def get_arg(self): 

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

802 

803 def get_devpath(self): 

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

805 

806 @classmethod 

807 def launch_from_arg(cls, arg): 

808 arg = cls.Arg.parse(arg) 

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

810 

811 @staticmethod 

812 def cgclassify(pid): 

813 

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

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

816 # we have configured in the spec file. 

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

818 try: 

819 util.pread2(cmd) 

820 except util.CommandException as e: 

821 util.logException(e) 

822 

823 @classmethod 

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

825 

826 tapdisk = cls.find_by_path(path) 

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

828 raise TapdiskExists(tapdisk) 

829 

830 minor = blktap.minor 

831 try: 

832 pid = TapCtl.spawn() 

833 cls.cgclassify(pid) 

834 try: 

835 TapCtl.attach(pid, minor) 

836 

837 try: 

838 retry_open = 0 

839 while True: 

840 try: 

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

842 break 

843 except TapCtl.CommandFailure as e: 

844 err = ( 

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

846 ) or None 

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

848 if retry_open < 5: 

849 retry_open += 1 

850 time.sleep(1) 

851 continue 

852 if LINSTOR_AVAILABLE and err == errno.EROFS: 

853 log_drbd_openers(path) 

854 raise 

855 try: 

856 tapdisk = cls.__from_blktap(blktap) 

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

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

859 return tapdisk 

860 except: 

861 TapCtl.close(pid, minor) 

862 raise 

863 

864 except: 

865 TapCtl.detach(pid, minor) 

866 raise 

867 

868 except: 

869 try: 

870 TapCtl.shutdown(pid) 

871 except: 

872 # Best effort to shutdown 

873 pass 

874 raise 

875 

876 except TapCtl.CommandFailure as ctl: 

877 util.logException(ctl) 

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

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

880 raise xs_errors.XenError('TapdiskDriveEmpty') 

881 else: 

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

883 

884 @classmethod 

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

886 blktap = Blktap.allocate() 

887 try: 

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

889 except: 

890 blktap.free() 

891 raise 

892 

893 def shutdown(self, force=False): 

894 

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

896 

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

898 

899 self.get_blktap().free() 

900 

901 def pause(self): 

902 

903 if not self.is_running(): 

904 raise TapdiskInvalidState(self) 

905 

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

907 

908 self._set_dirty() 

909 

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

911 

912 if not self.is_paused(): 

913 raise TapdiskInvalidState(self) 

914 

915 # FIXME: should the arguments be optional? 

916 if _type is None: 

917 _type = self.type 

918 if path is None: 

919 path = self.path 

920 

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

922 cbtlog=cbtlog) 

923 

924 self._set_dirty() 

925 

926 def stats(self): 

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

928 # 

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

930 # 

931 

932 def _set_dirty(self): 

933 self._dirty = True 

934 

935 def _refresh(self, __get): 

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

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

938 

939 @override 

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

941 def __get(name): 

942 # NB. avoid(rec(ursion) 

943 return object.__getattribute__(self, name) 

944 

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

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

947 self._refresh(__get) 

948 self._dirty = False 

949 

950 return __get(name) 

951 

952 class PauseState: 

953 RUNNING = 'R' 

954 PAUSING = 'r' 

955 PAUSED = 'P' 

956 

957 class Flags: 

958 DEAD = 0x0001 

959 CLOSED = 0x0002 

960 QUIESCE_REQUESTED = 0x0004 

961 QUIESCED = 0x0008 

962 PAUSE_REQUESTED = 0x0010 

963 PAUSED = 0x0020 

964 SHUTDOWN_REQUESTED = 0x0040 

965 LOCKING = 0x0080 

966 RETRY_NEEDED = 0x0100 

967 LOG_DROPPED = 0x0200 

968 

969 PAUSE_MASK = PAUSE_REQUESTED | PAUSED 

970 

971 def is_paused(self): 

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

973 

974 def is_running(self): 

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

976 

977 def pause_state(self): 

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

979 return self.PauseState.PAUSED 

980 

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

982 return self.PauseState.PAUSING 

983 

984 return self.PauseState.RUNNING 

985 

986 @staticmethod 

987 def _parse_minor(devpath): 

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

989 pattern = re.compile(regex) 

990 groups = pattern.search(devpath) 

991 if not groups: 

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

993 

994 minor = groups.group(2) 

995 return int(minor) 

996 

997 _major = None 

998 

999 @classmethod 

1000 def major(cls): 

1001 if cls._major: 

1002 return cls._major 

1003 

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

1005 for line in devices: 

1006 

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

1008 if len(row) != 2: 

1009 continue 

1010 

1011 major, name = row 

1012 if name != 'tapdev': 

1013 continue 

1014 

1015 cls._major = int(major) 

1016 break 

1017 

1018 devices.close() 

1019 return cls._major 

1020 

1021 

1022class VDI(object): 

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

1024 

1025 CONF_KEY_ALLOW_CACHING = "vdi_allow_caching" 

1026 CONF_KEY_MODE_ON_BOOT = "vdi_on_boot" 

1027 CONF_KEY_CACHE_SR = "local_cache_sr" 

1028 CONF_KEY_O_DIRECT = "o_direct" 

1029 LOCK_CACHE_SETUP = "cachesetup" 

1030 

1031 ATTACH_DETACH_RETRY_SECS = 120 

1032 

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

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

1035 self._vdi_uuid = uuid 

1036 self._session = target.session 

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

1038 self.__o_direct = None 

1039 self.__o_direct_reason = None 

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

1041 self.tap = None 

1042 

1043 def get_o_direct_capability(self, options): 

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

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

1046 return self.__o_direct, self.__o_direct_reason 

1047 

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

1049 self.__o_direct = True 

1050 self.__o_direct_reason = "LICENSE_RESTRICTION" 

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

1052 self.__o_direct = True 

1053 self.__o_direct_reason = "SR_NOT_SUPPORTED" 

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

1055 self.__o_direct = True 

1056 self.__o_direct_reason = "RO_WITH_NO_PARENT" 

1057 elif options.get(self.CONF_KEY_O_DIRECT): 

1058 self.__o_direct = True 

1059 self.__o_direct_reason = "SR_OVERRIDE" 

1060 

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

1062 self.__o_direct = False 

1063 self.__o_direct_reason = "" 

1064 

1065 return self.__o_direct, self.__o_direct_reason 

1066 

1067 @classmethod 

1068 def from_cli(cls, uuid): 

1069 session = XenAPI.xapi_local() 

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

1071 

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

1073 driver_info = target.sr.srcmd.driver_info 

1074 

1075 session.xenapi.session.logout() 

1076 

1077 return cls(uuid, target, driver_info) 

1078 

1079 @staticmethod 

1080 def _tap_type(vdi_type): 

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

1082 return { 

1083 'raw': 'aio', 

1084 'vhd': 'vhd', 

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

1086 'aio': 'aio', # for LVHD 

1087 'file': 'aio', 

1088 'phy': 'aio' 

1089 }[vdi_type] 

1090 

1091 def get_tap_type(self): 

1092 vdi_type = self.target.get_vdi_type() 

1093 return VDI._tap_type(vdi_type) 

1094 

1095 def get_phy_path(self): 

1096 return self.target.get_vdi_path() 

1097 

1098 class UnexpectedVDIType(Exception): 

1099 

1100 def __init__(self, vdi_type, target): 

1101 self.vdi_type = vdi_type 

1102 self.target = target 

1103 

1104 @override 

1105 def __str__(self) -> str: 

1106 return \ 

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

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

1109 

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

1111 'raw': 'phy', 

1112 'aio': 'tap', # for LVHD raw nodes 

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

1114 'file': 'tap', 

1115 'vhd': 'tap'} 

1116 

1117 def tap_wanted(self): 

1118 # 1. Let the target vdi_type decide 

1119 

1120 vdi_type = self.target.get_vdi_type() 

1121 

1122 try: 

1123 plug_type = self.VDI_PLUG_TYPE[vdi_type] 

1124 except KeyError: 

1125 raise self.UnexpectedVDIType(vdi_type, 

1126 self.target.vdi) 

1127 

1128 if plug_type == 'tap': 1128 ↛ 1129line 1128 didn't jump to line 1129, because the condition on line 1128 was never true

1129 return True 

1130 elif self.target.vdi.sr.handles('udev'): 1130 ↛ 1136line 1130 didn't jump to line 1136, because the condition on line 1130 was never false

1131 return True 

1132 # 2. Otherwise, there may be more reasons 

1133 # 

1134 # .. TBD 

1135 

1136 return False 

1137 

1138 class TargetDriver: 

1139 """Safe target driver access.""" 

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

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

1142 # try/except would risk breaking compatibility. 

1143 

1144 def __init__(self, vdi, driver_info): 

1145 self.vdi = vdi 

1146 self._caps = driver_info['capabilities'] 

1147 

1148 def has_cap(self, cap): 

1149 """Determine if target has given capability""" 

1150 return cap in self._caps 

1151 

1152 def attach(self, sr_uuid, vdi_uuid): 

1153 #assert self.has_cap("VDI_ATTACH") 

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

1155 

1156 def detach(self, sr_uuid, vdi_uuid): 

1157 #assert self.has_cap("VDI_DETACH") 

1158 self.vdi.detach(sr_uuid, vdi_uuid) 

1159 

1160 def activate(self, sr_uuid, vdi_uuid): 

1161 if self.has_cap("VDI_ACTIVATE"): 

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

1163 

1164 def deactivate(self, sr_uuid, vdi_uuid): 

1165 if self.has_cap("VDI_DEACTIVATE"): 

1166 self.vdi.deactivate(sr_uuid, vdi_uuid) 

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

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

1169 

1170 def get_vdi_type(self): 

1171 _type = self.vdi.vdi_type 

1172 if not _type: 

1173 _type = self.vdi.sr.sr_vditype 

1174 if not _type: 

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

1176 return _type 

1177 

1178 def get_vdi_path(self): 

1179 return self.vdi.path 

1180 

1181 class Link(object): 

1182 """Relink a node under a common name""" 

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

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

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

1186 # relink existing devices under deterministic path names. 

1187 

1188 BASEDIR: ClassVar[str] = "" 

1189 

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

1191 pass 

1192 

1193 @abstractmethod 

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

1195 pass 

1196 

1197 def __init__(self, path): 

1198 self._path = path 

1199 

1200 @classmethod 

1201 def from_name(cls, name): 

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

1203 return cls(path) 

1204 

1205 @classmethod 

1206 def from_uuid(cls, sr_uuid, vdi_uuid): 

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

1208 return cls.from_name(name) 

1209 

1210 def path(self): 

1211 return self._path 

1212 

1213 def stat(self): 

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

1215 

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

1217 

1218 path = self.path() 

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

1220 

1221 mkdirs(os.path.dirname(path)) 

1222 try: 

1223 self._mklink(target) 

1224 except OSError as e: 

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

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

1227 # be seen. 

1228 if e.errno != errno.EEXIST: 

1229 raise 

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

1231 

1232 def unlink(self): 

1233 try: 

1234 os.unlink(self.path()) 

1235 except OSError as e: 

1236 if e.errno != errno.ENOENT: 

1237 raise 

1238 

1239 @override 

1240 def __str__(self) -> str: 

1241 path = self.path() 

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

1243 

1244 class SymLink(Link): 

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

1246 

1247 def readlink(self): 

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

1249 

1250 def symlink(self): 

1251 return self.path() 

1252 

1253 @override 

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

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

1256 

1257 @override 

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

1259 return self.readlink() == target 

1260 

1261 class DeviceNode(Link): 

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

1263 

1264 @classmethod 

1265 def _real_stat(cls, target): 

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

1267 _target = os.path.realpath(target) 

1268 return os.stat(_target) 

1269 

1270 @classmethod 

1271 def is_block(cls, target): 

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

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

1274 

1275 @override 

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

1277 

1278 st = self._real_stat(target) 

1279 if not S_ISBLK(st.st_mode): 

1280 raise self.NotABlockDevice(target, st) 

1281 

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

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

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

1285 

1286 @override 

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

1288 target_rdev = self._real_stat(target).st_rdev 

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

1290 

1291 def rdev(self): 

1292 st = self.stat() 

1293 assert S_ISBLK(st.st_mode) 

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

1295 

1296 class NotABlockDevice(Exception): 

1297 

1298 def __init__(self, path, st): 

1299 self.path = path 

1300 self.st = st 

1301 

1302 @override 

1303 def __str__(self) -> str: 

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

1305 

1306 class Hybrid(Link): 

1307 

1308 def __init__(self, path): 

1309 VDI.Link.__init__(self, path) 

1310 self._devnode = VDI.DeviceNode(path) 

1311 self._symlink = VDI.SymLink(path) 

1312 

1313 def rdev(self): 

1314 st = self.stat() 

1315 if S_ISBLK(st.st_mode): 

1316 return self._devnode.rdev() 

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

1318 

1319 @override 

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

1321 if self._devnode.is_block(target): 

1322 self._obj = self._devnode 

1323 else: 

1324 self._obj = self._symlink 

1325 self._obj.mklink(target) 

1326 

1327 @override 

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

1329 return self._obj._equals(target) 

1330 

1331 class PhyLink(SymLink): 

1332 BASEDIR = "/dev/sm/phy" 

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

1334 

1335 class NBDLink(SymLink): 

1336 

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

1338 

1339 class BackendLink(Hybrid): 

1340 BASEDIR = "/dev/sm/backend" 

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

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

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

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

1345 # soon as ISOs are tapdisks. 

1346 

1347 @staticmethod 

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

1349 

1350 tapdisk = Tapdisk.find_by_path(phy_path) 

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

1352 blktap = Blktap.allocate() 

1353 blktap.set_pool_name(sr_uuid) 

1354 if pool_size: 

1355 blktap.set_pool_size(pool_size) 

1356 

1357 try: 

1358 tapdisk = \ 

1359 Tapdisk.launch_on_tap(blktap, 

1360 phy_path, 

1361 VDI._tap_type(vdi_type), 

1362 options) 

1363 except: 

1364 blktap.free() 

1365 raise 

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

1367 

1368 else: 

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

1370 

1371 return tapdisk.get_devpath(), tapdisk 

1372 

1373 @staticmethod 

1374 def _tap_deactivate(minor): 

1375 

1376 try: 

1377 tapdisk = Tapdisk.from_minor(minor) 

1378 except TapdiskNotRunning as e: 

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

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

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

1382 # the recorded minor. 

1383 else: 

1384 tapdisk.shutdown() 

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

1386 

1387 @classmethod 

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

1389 """ 

1390 Pauses the tapdisk. 

1391 

1392 session: a XAPI session 

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

1394 vdi_uuid: the UUID of the VDI to pause 

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

1396 non-blocking manner 

1397 """ 

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

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

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

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

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

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

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

1405 if not cls.call_pluginhandler(session, host_ref, 

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

1407 # Failed to pause node 

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

1409 return False 

1410 return True 

1411 

1412 @classmethod 

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

1414 activate_parents=False): 

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

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

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

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

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

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

1421 if not cls.call_pluginhandler(session, host_ref, 

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

1423 # Failed to unpause node 

1424 return False 

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

1426 return True 

1427 

1428 @classmethod 

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

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

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

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

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

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

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

1436 if not cls.call_pluginhandler(session, host_ref, 

1437 sr_uuid, vdi_uuid, "refresh", None, 

1438 activate_parents=activate_parents): 

1439 # Failed to refresh node 

1440 return False 

1441 return True 

1442 

1443 @classmethod 

1444 def tap_status(cls, session, vdi_uuid): 

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

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

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

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

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

1450 return True 

1451 return False 

1452 

1453 @classmethod 

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

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

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

1457 try: 

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

1459 "failfast": str(failfast)} 

1460 if secondary: 

1461 args["secondary"] = secondary 

1462 if activate_parents: 

1463 args["activate_parents"] = "true" 

1464 ret = session.xenapi.host.call_plugin( 

1465 host_ref, PLUGIN_TAP_PAUSE, action, 

1466 args) 

1467 return ret == "True" 

1468 except Exception as e: 

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

1470 return False 

1471 

1472 def _add_tag(self, vdi_uuid, writable): 

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

1474 attach_mode = "RO" 

1475 if writable: 

1476 attach_mode = "RW" 

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

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

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

1480 attached_as = util.attached_as(sm_config) 

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

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

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

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

1485 term_output=False, writable=writable): 

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

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

1488 if 'relinking' in sm_config: 

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

1490 return False 

1491 if 'paused' in sm_config: 

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

1493 return False 

1494 try: 

1495 self._session.xenapi.VDI.add_to_sm_config( 

1496 vdi_ref, 'activating', 'True') 

1497 except XenAPI.Failure as e: 

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

1499 # Someone else is activating - a retry might succeed 

1500 return False 

1501 raise 

1502 host_key = "host_%s" % host_ref 

1503 assert host_key not in sm_config 

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

1505 attach_mode) 

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

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

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

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

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

1511 self._session.xenapi.VDI.remove_from_sm_config( 

1512 vdi_ref, 'activating') 

1513 return False 

1514 util.SMlog("Activate lock succeeded") 

1515 return True 

1516 

1517 def _check_tag(self, vdi_uuid): 

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

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

1520 if 'paused' in sm_config: 

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

1522 return False 

1523 return True 

1524 

1525 def _remove_tag(self, vdi_uuid): 

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

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

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

1529 host_key = "host_%s" % host_ref 

1530 if host_key in sm_config: 

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

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

1533 else: 

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

1535 

1536 def _get_pool_config(self, pool_name): 

1537 pool_info = dict() 

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

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

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

1541 # special pool 

1542 return pool_info 

1543 

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

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

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

1547 pool_size_str = sr_config.get(POOL_SIZE_KEY) 

1548 pool_name_override = vdi_config.get(POOL_NAME_KEY) 

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

1550 pool_name = pool_name_override 

1551 pool_size_override = vdi_config.get(POOL_SIZE_KEY) 

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

1553 pool_size_str = pool_size_override 

1554 pool_size = 0 

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

1556 try: 

1557 pool_size = int(pool_size_str) 

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

1559 raise ValueError("outside of range") 

1560 pool_size = NUM_PAGES_PER_RING * pool_size 

1561 except ValueError: 

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

1563 pool_size = 0 

1564 

1565 pool_info["mem-pool"] = pool_name 

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

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

1568 

1569 return pool_info 

1570 

1571 def linkNBD(self, sr_uuid, vdi_uuid): 

1572 if self.tap: 

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

1574 int(self.tap.minor)) 

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

1576 

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

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

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

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

1581 util.SMlog("Attach & activate") 

1582 self._attach(sr_uuid, vdi_uuid) 

1583 dev_path = self._activate(sr_uuid, vdi_uuid, 

1584 {"rdonly": not writable}) 

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

1586 self.linkNBD(sr_uuid, vdi_uuid) 

1587 

1588 # Return backend/ link 

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

1590 if self.tap_wanted(): 

1591 # Only have NBD if we also have a tap 

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

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

1594 vdi_uuid) 

1595 else: 

1596 nbd_path = "" 

1597 

1598 options = {"rdonly": not writable} 

1599 options.update(caching_params) 

1600 o_direct, o_direct_reason = self.get_o_direct_capability(options) 

1601 struct = {'params': back_path, 

1602 'params_nbd': nbd_path, 

1603 'o_direct': o_direct, 

1604 'o_direct_reason': o_direct_reason, 

1605 'xenstore_data': self.xenstore_data} 

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

1607 

1608 try: 

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

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

1611 f.close() 

1612 except: 

1613 pass 

1614 

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

1616 

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

1618 util.SMlog("blktap2.activate") 

1619 options = {"rdonly": not writable} 

1620 options.update(caching_params) 

1621 

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

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

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

1625 try: 

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

1627 return 

1628 except util.SRBusyException: 

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

1630 time.sleep(1) 

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

1632 

1633 @locking("VDIUnavailable") 

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

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

1636 

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

1638 refresh = False 

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

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

1641 return False 

1642 refresh = True 

1643 

1644 try: 

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

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

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

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

1649 # object completely 

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

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

1652 target.sr.srcmd.params = params 

1653 driver_info = target.sr.srcmd.driver_info 

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

1655 

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

1657 "blktap_activate_inject_failure", 

1658 lambda: util.inject_failure()) 

1659 

1660 # Attach the physical node 

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

1662 self._attach(sr_uuid, vdi_uuid) 

1663 

1664 vdi_type = self.target.get_vdi_type() 

1665 

1666 # Take lvchange-p Lock before running 

1667 # tap-ctl open 

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

1669 # now taking the same lock 

1670 # This is a fix for CA-155766 

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

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

1673 vdi_type == vhdutil.VDI_TYPE_VHD: 

1674 lock = Lock("lvchange-p", lvhdutil.NS_PREFIX_LVM + sr_uuid) 

1675 lock.acquire() 

1676 

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

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

1679 if vdi_type != vhdutil.VDI_TYPE_RAW: 1679 ↛ 1690line 1679 didn't jump to line 1690, because the condition on line 1679 was never false

1680 session = self.target.vdi.session 

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

1682 # pylint: disable=used-before-assignment 

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

1684 if 'key_hash' in sm_config: 1684 ↛ 1685line 1684 didn't jump to line 1685, because the condition on line 1684 was never true

1685 key_hash = sm_config['key_hash'] 

1686 options['key_hash'] = key_hash 

1687 options['vdi_uuid'] = vdi_uuid 

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

1689 # Activate the physical node 

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

1691 

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

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

1694 self.target.get_vdi_type() == vhdutil.VDI_TYPE_VHD: 

1695 lock.release() 

1696 except: 

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

1698 if self.tap_wanted(): 

1699 util.fistpoint.activate_custom_fn( 

1700 "blktap_activate_error_handling", 

1701 lambda: time.sleep(30)) 

1702 while True: 

1703 try: 

1704 self._remove_tag(vdi_uuid) 

1705 break 

1706 except xmlrpc.client.ProtocolError as e: 

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

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

1709 continue 

1710 else: 

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

1712 break 

1713 except Exception as e: 

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

1715 break 

1716 raise 

1717 finally: 

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

1719 self._session.xenapi.VDI.remove_from_sm_config( 

1720 vdi_ref, 'activating') 

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

1722 

1723 # Link result to backend/ 

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

1725 self.linkNBD(sr_uuid, vdi_uuid) 

1726 return True 

1727 

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

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

1730 

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

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

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

1734 # Maybe launch a tapdisk on the physical link 

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

1736 vdi_type = self.target.get_vdi_type() 

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

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

1739 options.update(vdi_options) 

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

1741 sr_uuid, options, 

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

1743 else: 

1744 dev_path = phy_path # Just reuse phy 

1745 

1746 return dev_path 

1747 

1748 def _attach(self, sr_uuid, vdi_uuid): 

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

1750 params = attach_info['params'] 

1751 xenstore_data = attach_info['xenstore_data'] 

1752 phy_path = util.to_plain_string(params) 

1753 self.xenstore_data.update(xenstore_data) 

1754 # Save it to phy/ 

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

1756 

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

1758 util.SMlog("blktap2.deactivate") 

1759 for i in range(self.ATTACH_DETACH_RETRY_SECS): 

1760 try: 

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

1762 return 

1763 except util.SRBusyException as e: 

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

1765 time.sleep(1) 

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

1767 

1768 @locking("VDIUnavailable") 

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

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

1771 

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

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

1774 return False 

1775 

1776 self._deactivate(sr_uuid, vdi_uuid, caching_params) 

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

1778 self._detach(sr_uuid, vdi_uuid) 

1779 if self.tap_wanted(): 

1780 self._remove_tag(vdi_uuid) 

1781 

1782 return True 

1783 

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

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

1786 

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

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

1789 util.SMlog("Deactivate & detach") 

1790 self._deactivate(sr_uuid, vdi_uuid, caching_params) 

1791 self._detach(sr_uuid, vdi_uuid) 

1792 else: 

1793 pass # nothing to do 

1794 

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

1796 # Shutdown tapdisk 

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

1798 

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

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

1801 return 

1802 

1803 try: 

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

1805 os.unlink(attach_info_path) 

1806 except: 

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

1808 

1809 try: 

1810 major, minor = back_link.rdev() 

1811 except self.DeviceNode.NotABlockDevice: 

1812 pass 

1813 else: 

1814 if major == Tapdisk.major(): 

1815 self._tap_deactivate(minor) 

1816 self.remove_cache(caching_params) 

1817 

1818 # Remove the backend link 

1819 back_link.unlink() 

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

1821 

1822 # Deactivate & detach the physical node 

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

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

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

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

1827 # object completely 

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

1829 driver_info = target.sr.srcmd.driver_info 

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

1831 

1832 self.target.deactivate(sr_uuid, vdi_uuid) 

1833 

1834 def _detach(self, sr_uuid, vdi_uuid): 

1835 self.target.detach(sr_uuid, vdi_uuid) 

1836 

1837 # Remove phy/ 

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

1839 

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

1841 # Remove existing VDI.sm_config fields 

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

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

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

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

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

1847 if not caching is None: 

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

1849 

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

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

1852 return 

1853 

1854 util.SMlog("Requested local caching") 

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

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

1857 return 

1858 

1859 scratch_mode = False 

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

1861 scratch_mode = True 

1862 util.SMlog("Requested scratch mode") 

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

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

1865 return 

1866 

1867 dev_path = None 

1868 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR) 

1869 if not local_sr_uuid: 

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

1871 return 

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

1873 local_sr_uuid, scratch_mode, params) 

1874 

1875 if dev_path: 

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

1877 params.get(self.CONF_KEY_MODE_ON_BOOT), 

1878 params.get(self.CONF_KEY_ALLOW_CACHING)) 

1879 

1880 return dev_path 

1881 

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

1883 vm_uuid = None 

1884 vm_label = "" 

1885 try: 

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

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

1888 cache_sr_label = cache_sr_rec.get("name_label") 

1889 

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

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

1892 host_label = host_rec.get("name_label") 

1893 

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

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

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

1897 for vbd_rec in vbds.values(): 

1898 vm_ref = vbd_rec.get("VM") 

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

1900 vm_uuid = vm_rec.get("uuid") 

1901 vm_label = vm_rec.get("name_label") 

1902 except: 

1903 util.logException("alert_no_cache") 

1904 

1905 alert_obj = "SR" 

1906 alert_uuid = str(cache_sr_uuid) 

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

1908 if vm_uuid: 

1909 alert_obj = "VM" 

1910 alert_uuid = vm_uuid 

1911 reason = "" 

1912 if err == errno.ENOSPC: 

1913 reason = "because there is no space left" 

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

1915 (vm_label, reason, cache_sr_label, host_label) 

1916 

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

1918 (alert_obj, alert_uuid, alert_str)) 

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

1920 alert_obj, alert_uuid, alert_str) 

1921 

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

1923 scratch_mode, options): 

1924 import SR 

1925 import EXTSR 

1926 

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

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

1929 self.target.vdi.uuid) 

1930 return 

1931 

1932 util.SMlog("Setting up cache") 

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

1934 

1935 if shared_target.parent: 

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

1937 shared_target.uuid) 

1938 return 

1939 

1940 SR.registerSR(EXTSR.EXTSR) 

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

1942 

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

1944 lock.acquire() 

1945 

1946 # read cache 

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

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

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

1950 read_cache_path) 

1951 else: 

1952 try: 

1953 vhdutil.snapshot(read_cache_path, shared_target.path, False) 

1954 except util.CommandException as e: 

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

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

1957 return None 

1958 

1959 # local write node 

1960 leaf_size = vhdutil.getSizeVirt(self.target.vdi.path) 

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

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

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

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

1965 local_leaf_path) 

1966 os.unlink(local_leaf_path) 

1967 try: 

1968 vhdutil.snapshot(local_leaf_path, read_cache_path, False, 

1969 msize=leaf_size // 1024 // 1024, checkEmpty=False) 

1970 except util.CommandException as e: 

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

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

1973 return None 

1974 

1975 local_leaf_size = vhdutil.getSizeVirt(local_leaf_path) 

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

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

1978 (leaf_size, local_leaf_size)) 

1979 vhdutil.setSizeVirtFast(local_leaf_path, leaf_size) 

1980 

1981 prt_tapdisk = Tapdisk.find_by_path(read_cache_path) 

1982 if not prt_tapdisk: 

1983 parent_options = copy.deepcopy(options) 

1984 parent_options["rdonly"] = False 

1985 parent_options["lcache"] = True 

1986 

1987 blktap = Blktap.allocate() 

1988 try: 

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

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

1991 # its own pool 

1992 prt_tapdisk = \ 

1993 Tapdisk.launch_on_tap(blktap, read_cache_path, 

1994 'vhd', parent_options) 

1995 except: 

1996 blktap.free() 

1997 raise 

1998 

1999 secondary = "%s:%s" % (self.target.get_vdi_type(), 

2000 (self.PhyLink.from_uuid(sr_uuid, vdi_uuid).readlink())) 

2001 

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

2003 leaf_tapdisk = Tapdisk.find_by_path(local_leaf_path) 

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

2005 blktap = Blktap.allocate() 

2006 child_options = copy.deepcopy(options) 

2007 child_options["rdonly"] = False 

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

2009 child_options["existing_prt"] = prt_tapdisk.minor 

2010 child_options["secondary"] = secondary 

2011 child_options["standby"] = scratch_mode 

2012 # Disable memory read caching 

2013 child_options.pop("o_direct", None) 

2014 try: 

2015 leaf_tapdisk = \ 

2016 Tapdisk.launch_on_tap(blktap, local_leaf_path, 

2017 'vhd', child_options) 

2018 except: 

2019 blktap.free() 

2020 raise 

2021 

2022 lock.release() 

2023 

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

2025 (read_cache_path, local_leaf_path)) 

2026 

2027 self.tap = leaf_tapdisk 

2028 return leaf_tapdisk.get_devpath() 

2029 

2030 def remove_cache(self, params): 

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

2032 return 

2033 

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

2035 

2036 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR) 

2037 if caching and not local_sr_uuid: 

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

2039 return 

2040 

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

2042 self._remove_cache(self._session, local_sr_uuid) 

2043 

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

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

2046 

2047 def _is_tapdisk_in_use(self, minor): 

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

2049 if not retVal: 

2050 # err on the side of caution 

2051 return True 

2052 

2053 for link in links: 

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

2055 return True 

2056 

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

2058 for s in sockets: 

2059 if socket_re.match(s): 

2060 return True 

2061 

2062 return False 

2063 

2064 def _remove_cache(self, session, local_sr_uuid): 

2065 import SR 

2066 import EXTSR 

2067 

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

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

2070 self.target.vdi.uuid) 

2071 return 

2072 

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

2074 

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

2076 

2077 SR.registerSR(EXTSR.EXTSR) 

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

2079 

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

2081 lock.acquire() 

2082 

2083 # local write node 

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("Deleting local leaf node %s" % local_leaf_path) 

2088 os.unlink(local_leaf_path) 

2089 

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

2091 prt_tapdisk = Tapdisk.find_by_path(read_cache_path) 

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

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

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

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

2096 read_cache_path) 

2097 try: 

2098 prt_tapdisk.shutdown() 

2099 except: 

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

2101 else: 

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

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

2104 # GC run 

2105 

2106 lock.release() 

2107 

2108 @staticmethod 

2109 def _no_parent(vdi): 

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

2111 

2112 

2113PythonKeyError = KeyError 

2114 

2115 

2116class UEventHandler(object): 

2117 

2118 def __init__(self): 

2119 self._action = None 

2120 

2121 class KeyError(PythonKeyError): 

2122 def __init__(self, args): 

2123 super().__init__(args) 

2124 self.key = args[0] 

2125 

2126 @override 

2127 def __str__(self) -> str: 

2128 return \ 

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

2130 "Not called in udev context?" 

2131 

2132 @classmethod 

2133 def getenv(cls, key): 

2134 try: 

2135 return os.environ[key] 

2136 except KeyError as e: 

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

2138 

2139 def get_action(self): 

2140 if not self._action: 

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

2142 return self._action 

2143 

2144 class UnhandledEvent(Exception): 

2145 

2146 def __init__(self, event, handler): 

2147 self.event = event 

2148 self.handler = handler 

2149 

2150 @override 

2151 def __str__(self) -> str: 

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

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

2154 

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

2156 

2157 def run(self): 

2158 

2159 action = self.get_action() 

2160 try: 

2161 fn = self.ACTIONS[action] 

2162 except KeyError: 

2163 raise self.UnhandledEvent(action, self) 

2164 

2165 return fn(self) 

2166 

2167 @override 

2168 def __str__(self) -> str: 

2169 try: 

2170 action = self.get_action() 

2171 except: 

2172 action = None 

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

2174 

2175 

2176class __BlktapControl(ClassDevice): 

2177 SYSFS_CLASSTYPE = "misc" 

2178 

2179 def __init__(self): 

2180 ClassDevice.__init__(self) 

2181 self._default_pool = None 

2182 

2183 @override 

2184 def sysfs_devname(self) -> str: 

2185 return "blktap!control" 

2186 

2187 class DefaultPool(Attribute): 

2188 SYSFS_NODENAME = "default_pool" 

2189 

2190 def get_default_pool_attr(self): 

2191 if not self._default_pool: 

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

2193 return self._default_pool 

2194 

2195 def get_default_pool_name(self): 

2196 return self.get_default_pool_attr().readline() 

2197 

2198 def set_default_pool_name(self, name): 

2199 self.get_default_pool_attr().writeline(name) 

2200 

2201 def get_default_pool(self): 

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

2203 

2204 def set_default_pool(self, pool): 

2205 self.set_default_pool_name(pool.name) 

2206 

2207 class NoSuchPool(Exception): 

2208 def __init__(self, name): 

2209 self.name = name 

2210 

2211 @override 

2212 def __str__(self) -> str: 

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

2214 

2215 def get_pool(self, name): 

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

2217 

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

2219 raise self.NoSuchPool(name) 

2220 

2221 return PagePool(path) 

2222 

2223BlktapControl = __BlktapControl() 

2224 

2225 

2226class PagePool(KObject): 

2227 

2228 def __init__(self, path): 

2229 self.path = path 

2230 self._size = None 

2231 

2232 @override 

2233 def sysfs_devname(self) -> str: 

2234 return '' 

2235 

2236 def sysfs_path(self): 

2237 return self.path 

2238 

2239 class Size(Attribute): 

2240 SYSFS_NODENAME = "size" 

2241 

2242 def get_size_attr(self): 

2243 if not self._size: 

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

2245 return self._size 

2246 

2247 def set_size(self, pages): 

2248 pages = str(pages) 

2249 self.get_size_attr().writeline(pages) 

2250 

2251 def get_size(self): 

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

2253 return int(pages) 

2254 

2255 

2256class BusDevice(KObject): 

2257 

2258 SYSFS_BUSTYPE: ClassVar[str] = "" 

2259 

2260 @classmethod 

2261 def sysfs_bus_path(cls): 

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

2263 

2264 def sysfs_path(self): 

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

2266 self.sysfs_devname()) 

2267 

2268 return path 

2269 

2270 

2271class XenbusDevice(BusDevice): 

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

2273 

2274 XBT_NIL = "" 

2275 

2276 XENBUS_DEVTYPE: ClassVar[str] = "" 

2277 

2278 def __init__(self, domid, devid): 

2279 self.domid = int(domid) 

2280 self.devid = int(devid) 

2281 self._xbt = XenbusDevice.XBT_NIL 

2282 

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

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

2285 

2286 def xs_path(self, key=None): 

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

2288 self.domid, 

2289 self.devid) 

2290 if key is not None: 

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

2292 

2293 return path 

2294 

2295 def _log(self, prio, msg): 

2296 syslog(prio, msg) 

2297 

2298 def info(self, msg): 

2299 self._log(_syslog.LOG_INFO, msg) 

2300 

2301 def warn(self, msg): 

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

2303 

2304 def _xs_read_path(self, path): 

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

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

2307 return val 

2308 

2309 def _xs_write_path(self, path, val): 

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

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

2312 

2313 def _xs_rm_path(self, path): 

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

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

2316 

2317 def read(self, key): 

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

2319 

2320 def has_xs_key(self, key): 

2321 return self.read(key) is not None 

2322 

2323 def write(self, key, val): 

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

2325 

2326 def rm(self, key): 

2327 self._xs_rm_path(self.xs_path(key)) 

2328 

2329 def exists(self): 

2330 return self.has_xs_key(None) 

2331 

2332 def begin(self): 

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

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

2335 

2336 def commit(self): 

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

2338 self._xbt = XenbusDevice.XBT_NIL 

2339 return ok 

2340 

2341 def abort(self): 

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

2343 assert(ok == True) 

2344 self._xbt = XenbusDevice.XBT_NIL 

2345 

2346 def create_physical_device(self): 

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

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

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

2350 return 

2351 try: 

2352 params = self.read("params") 

2353 frontend = self.read("frontend") 

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

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

2356 # from opening the physical-device 

2357 if not(is_cdrom): 

2358 major_minor = os.stat(params).st_rdev 

2359 major, minor = divmod(major_minor, 256) 

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

2361 except: 

2362 util.logException("BLKTAP2:create_physical_device") 

2363 

2364 def signal_hotplug(self, online=True): 

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

2366 self.XENBUS_DEVTYPE, 

2367 self.devid) 

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

2369 if online: 

2370 self._xs_write_path(xapi_path, "online") 

2371 self._xs_write_path(upstream_path, "connected") 

2372 else: 

2373 self._xs_rm_path(xapi_path) 

2374 self._xs_rm_path(upstream_path) 

2375 

2376 @override 

2377 def sysfs_devname(self) -> str: 

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

2379 self.domid, self.devid) 

2380 

2381 @override 

2382 def __str__(self) -> str: 

2383 return self.sysfs_devname() 

2384 

2385 @classmethod 

2386 def find(cls): 

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

2388 cls.XENBUS_DEVTYPE) 

2389 for path in glob.glob(pattern): 

2390 

2391 name = os.path.basename(path) 

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

2393 

2394 yield cls(domid, devid) 

2395 

2396 

2397class XenBackendDevice(XenbusDevice): 

2398 """Xenbus backend device""" 

2399 SYSFS_BUSTYPE = "xen-backend" 

2400 

2401 @classmethod 

2402 def from_xs_path(cls, _path): 

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

2404 

2405 assert _backend == 'backend' 

2406 assert _type == cls.XENBUS_DEVTYPE 

2407 

2408 domid = int(domid) 

2409 devid = int(devid) 

2410 

2411 return cls(domid, devid) 

2412 

2413 

2414class Blkback(XenBackendDevice): 

2415 """A blkback VBD""" 

2416 

2417 XENBUS_DEVTYPE = "vbd" 

2418 

2419 def __init__(self, domid, devid): 

2420 XenBackendDevice.__init__(self, domid, devid) 

2421 self._phy = None 

2422 self._vdi_uuid = None 

2423 self._q_state = None 

2424 self._q_events = None 

2425 

2426 class XenstoreValueError(Exception): 

2427 KEY: ClassVar[str] = "" 

2428 

2429 def __init__(self, vbd, _str): 

2430 self.vbd = vbd 

2431 self.str = _str 

2432 

2433 @override 

2434 def __str__(self) -> str: 

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

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

2437 

2438 class PhysicalDeviceError(XenstoreValueError): 

2439 KEY = "physical-device" 

2440 

2441 class PhysicalDevice(object): 

2442 

2443 def __init__(self, major, minor): 

2444 self.major = int(major) 

2445 self.minor = int(minor) 

2446 

2447 @classmethod 

2448 def from_xbdev(cls, xbdev): 

2449 

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

2451 

2452 try: 

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

2454 major = int(major, 0x10) 

2455 minor = int(minor, 0x10) 

2456 except Exception as e: 

2457 raise xbdev.PhysicalDeviceError(xbdev, phy) 

2458 

2459 return cls(major, minor) 

2460 

2461 def makedev(self): 

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

2463 

2464 def is_tap(self): 

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

2466 

2467 @override 

2468 def __str__(self) -> str: 

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

2470 

2471 @override 

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

2473 return \ 

2474 self.major == other.major and \ 

2475 self.minor == other.minor 

2476 

2477 def get_physical_device(self): 

2478 if not self._phy: 

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

2480 return self._phy 

2481 

2482 class QueueEvents(Attribute): 

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

2484 notifications emitted.""" 

2485 

2486 SYSFS_NODENAME = "queue_events" 

2487 

2488 QUEUE_RUNNING = (1 << 0) 

2489 QUEUE_PAUSE_DONE = (1 << 1) 

2490 QUEUE_SHUTDOWN_DONE = (1 << 2) 

2491 QUEUE_PAUSE_REQUEST = (1 << 3) 

2492 QUEUE_SHUTDOWN_REQUEST = (1 << 4) 

2493 

2494 def get_mask(self): 

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

2496 

2497 def set_mask(self, mask): 

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

2499 

2500 def get_queue_events(self): 

2501 if not self._q_events: 

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

2503 return self._q_events 

2504 

2505 def get_vdi_uuid(self): 

2506 if not self._vdi_uuid: 

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

2508 return self._vdi_uuid 

2509 

2510 def pause_requested(self): 

2511 return self.has_xs_key("pause") 

2512 

2513 def shutdown_requested(self): 

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

2515 

2516 def shutdown_done(self): 

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

2518 

2519 def running(self): 

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

2521 

2522 @classmethod 

2523 def find_by_physical_device(cls, phy): 

2524 for dev in cls.find(): 

2525 try: 

2526 _phy = dev.get_physical_device() 

2527 except cls.PhysicalDeviceError: 

2528 continue 

2529 

2530 if _phy == phy: 

2531 yield dev 

2532 

2533 @classmethod 

2534 def find_by_tap_minor(cls, minor): 

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

2536 return cls.find_by_physical_device(phy) 

2537 

2538 @classmethod 

2539 def find_by_tap(cls, tapdisk): 

2540 return cls.find_by_tap_minor(tapdisk.minor) 

2541 

2542 def has_tap(self): 

2543 

2544 if not self.can_tap(): 

2545 return False 

2546 

2547 phy = self.get_physical_device() 

2548 if phy: 

2549 return phy.is_tap() 

2550 

2551 return False 

2552 

2553 def is_bare_hvm(self): 

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

2555 try: 

2556 self.get_physical_device() 

2557 

2558 except self.PhysicalDeviceError as e: 

2559 vdi_type = self.read("type") 

2560 

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

2562 

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

2564 raise 

2565 

2566 return True 

2567 

2568 return False 

2569 

2570 def can_tap(self): 

2571 return not self.is_bare_hvm() 

2572 

2573 

2574class BlkbackEventHandler(UEventHandler): 

2575 

2576 LOG_FACILITY = _syslog.LOG_DAEMON 

2577 

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

2579 if not ident: 

2580 ident = self.__class__.__name__ 

2581 

2582 self.ident = ident 

2583 self._vbd = None 

2584 self._tapdisk = None 

2585 

2586 UEventHandler.__init__(self) 

2587 

2588 @override 

2589 def run(self) -> None: 

2590 

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

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

2593 

2594 UEventHandler.run(self) 

2595 

2596 @override 

2597 def __str__(self) -> str: 

2598 

2599 try: 

2600 path = self.xs_path 

2601 except: 

2602 path = None 

2603 

2604 try: 

2605 action = self.get_action() 

2606 except: 

2607 action = None 

2608 

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

2610 

2611 def _log(self, prio, msg): 

2612 syslog(prio, msg) 

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

2614 

2615 def info(self, msg): 

2616 self._log(_syslog.LOG_INFO, msg) 

2617 

2618 def warn(self, msg): 

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

2620 

2621 def error(self, msg): 

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

2623 

2624 def get_vbd(self): 

2625 if not self._vbd: 

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

2627 return self._vbd 

2628 

2629 def get_tapdisk(self): 

2630 if not self._tapdisk: 

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

2632 self._tapdisk = Tapdisk.from_minor(minor) 

2633 return self._tapdisk 

2634 # 

2635 # Events 

2636 # 

2637 

2638 def __add(self): 

2639 vbd = self.get_vbd() 

2640 # Manage blkback transitions 

2641 # self._manage_vbd() 

2642 

2643 vbd.create_physical_device() 

2644 

2645 vbd.signal_hotplug() 

2646 

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

2648 def add(self): 

2649 try: 

2650 self.__add() 

2651 except Attribute.NoSuchAttribute as e: 

2652 # 

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

2654 # registers device attributes. So poll a little. 

2655 # 

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

2657 raise RetryLoop.TransientFailure(e) 

2658 

2659 def __change(self): 

2660 vbd = self.get_vbd() 

2661 

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

2663 

2664 if vbd.has_tap(): 

2665 pass 

2666 #self._pause_update_tap() 

2667 

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

2669 

2670 self._signal_xapi() 

2671 

2672 def change(self): 

2673 vbd = self.get_vbd() 

2674 

2675 # NB. Beware of spurious change events between shutdown 

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

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

2678 

2679 while True: 

2680 vbd.begin() 

2681 

2682 if not vbd.exists() or \ 

2683 vbd.shutdown_done(): 

2684 break 

2685 

2686 self.__change() 

2687 

2688 if vbd.commit(): 

2689 return 

2690 

2691 vbd.abort() 

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

2693 

2694 def remove(self): 

2695 vbd = self.get_vbd() 

2696 

2697 vbd.signal_hotplug(False) 

2698 

2699 ACTIONS = {'add': add, 

2700 'change': change, 

2701 'remove': remove} 

2702 # 

2703 # VDI.pause 

2704 # 

2705 

2706 def _tap_should_pause(self): 

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

2708 paused""" 

2709 

2710 tapdisk = self.get_tapdisk() 

2711 TapState = Tapdisk.PauseState 

2712 

2713 PAUSED = 'P' 

2714 RUNNING = 'R' 

2715 PAUSED_SHUTDOWN = 'P,S' 

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

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

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

2719 

2720 next = TapState.RUNNING 

2721 vbds = {} 

2722 

2723 for vbd in Blkback.find_by_tap(tapdisk): 

2724 name = str(vbd) 

2725 

2726 pausing = vbd.pause_requested() 

2727 closing = vbd.shutdown_requested() 

2728 running = vbd.running() 

2729 

2730 if pausing: 

2731 if closing and not running: 

2732 vbds[name] = PAUSED_SHUTDOWN 

2733 else: 

2734 vbds[name] = PAUSED 

2735 next = TapState.PAUSED 

2736 

2737 else: 

2738 vbds[name] = RUNNING 

2739 

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

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

2742 vbds, next)) 

2743 

2744 return next == TapState.PAUSED 

2745 

2746 def _pause_update_tap(self): 

2747 vbd = self.get_vbd() 

2748 

2749 if self._tap_should_pause(): 

2750 self._pause_tap() 

2751 else: 

2752 self._resume_tap() 

2753 

2754 def _pause_tap(self): 

2755 tapdisk = self.get_tapdisk() 

2756 

2757 if not tapdisk.is_paused(): 

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

2759 tapdisk.pause() 

2760 

2761 def _resume_tap(self): 

2762 tapdisk = self.get_tapdisk() 

2763 

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

2765 # type while resuming. 

2766 vbd = self.get_vbd() 

2767 vdi_uuid = vbd.get_vdi_uuid() 

2768 

2769 if tapdisk.is_paused(): 

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

2771 vdi = VDI.from_cli(vdi_uuid) 

2772 _type = vdi.get_tap_type() 

2773 path = vdi.get_phy_path() 

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

2775 tapdisk.unpause(_type, path) 

2776 # 

2777 # VBD.pause/shutdown 

2778 # 

2779 

2780 def _manage_vbd(self): 

2781 vbd = self.get_vbd() 

2782 # NB. Hook into VBD state transitions. 

2783 

2784 events = vbd.get_queue_events() 

2785 

2786 mask = 0 

2787 mask |= events.QUEUE_PAUSE_DONE # pause/unpause 

2788 mask |= events.QUEUE_SHUTDOWN_DONE # shutdown 

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

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

2791 

2792 events.set_mask(mask) 

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

2794 

2795 def _signal_xapi(self): 

2796 vbd = self.get_vbd() 

2797 

2798 pausing = vbd.pause_requested() 

2799 closing = vbd.shutdown_requested() 

2800 running = vbd.running() 

2801 

2802 handled = 0 

2803 

2804 if pausing and not running: 

2805 if 'pause-done' not in vbd: 

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

2807 handled += 1 

2808 

2809 if not pausing: 

2810 if 'pause-done' in vbd: 

2811 vbd.rm('pause-done') 

2812 handled += 1 

2813 

2814 if closing and not running: 

2815 if 'shutdown-done' not in vbd: 

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

2817 handled += 1 

2818 

2819 if handled > 1: 

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

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

2822 (pausing, closing, running)) 

2823 

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

2825 

2826 import sys 

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

2828 

2829 # 

2830 # Simple CLI interface for manual operation 

2831 # 

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

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

2834 # 

2835 

2836 def usage(stream): 

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

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

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

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

2841 

2842 try: 

2843 cmd = sys.argv[1] 

2844 except IndexError: 

2845 usage(sys.stderr) 

2846 sys.exit(1) 

2847 

2848 try: 

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

2850 except: 

2851 usage(sys.stderr) 

2852 sys.exit(1) 

2853 

2854 # 

2855 # Local Tapdisks 

2856 # 

2857 

2858 if cmd == 'tap.major': 

2859 

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

2861 

2862 elif cmd == 'tap.launch': 

2863 

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

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

2866 

2867 elif _class == 'tap': 

2868 

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

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

2871 try: 

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

2873 attrs[key] = val 

2874 continue 

2875 except ValueError: 

2876 pass 

2877 

2878 try: 

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

2880 continue 

2881 except ValueError: 

2882 pass 

2883 

2884 try: 

2885 arg = Tapdisk.Arg.parse(item) 

2886 attrs['_type'] = arg.type 

2887 attrs['path'] = arg.path 

2888 continue 

2889 except Tapdisk.Arg.InvalidArgument: 

2890 pass 

2891 

2892 attrs['path'] = item 

2893 

2894 if cmd == 'tap.list': 

2895 

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

2897 blktap = tapdisk.get_blktap() 

2898 print(tapdisk, end=' ') 

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

2900 (blktap, 

2901 blktap.get_task_pid(), 

2902 blktap.get_pool_name())) 

2903 

2904 elif cmd == 'tap.vbds': 

2905 # Find all Blkback instances for a given tapdisk 

2906 

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

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

2909 for vbd in Blkback.find_by_tap(tapdisk): 

2910 print(vbd, end=' ') 

2911 print() 

2912 

2913 else: 

2914 

2915 if not attrs: 

2916 usage(sys.stderr) 

2917 sys.exit(1) 

2918 

2919 try: 

2920 tapdisk = Tapdisk.get( ** attrs) 

2921 except TypeError: 

2922 usage(sys.stderr) 

2923 sys.exit(1) 

2924 

2925 if cmd == 'tap.shutdown': 

2926 # Shutdown a running tapdisk, or raise 

2927 tapdisk.shutdown() 

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

2929 

2930 elif cmd == 'tap.pause': 

2931 # Pause an unpaused tapdisk, or raise 

2932 tapdisk.pause() 

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

2934 

2935 elif cmd == 'tap.unpause': 

2936 # Unpause a paused tapdisk, or raise 

2937 tapdisk.unpause() 

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

2939 

2940 elif cmd == 'tap.stats': 

2941 # Gather tapdisk status 

2942 stats = tapdisk.stats() 

2943 print("%s:" % tapdisk) 

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

2945 

2946 else: 

2947 usage(sys.stderr) 

2948 sys.exit(1) 

2949 

2950 elif cmd == 'vbd.uevent': 

2951 

2952 hnd = BlkbackEventHandler(cmd) 

2953 

2954 if not sys.stdin.isatty(): 

2955 try: 

2956 hnd.run() 

2957 except Exception as e: 

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

2959 

2960 import traceback 

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

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

2963 for entry in trace: 

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

2965 util.SMlog(line) 

2966 else: 

2967 hnd.run() 

2968 

2969 elif cmd == 'vbd.list': 

2970 

2971 for vbd in Blkback.find(): 

2972 print(vbd, \ 

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

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

2975 

2976 else: 

2977 usage(sys.stderr) 

2978 sys.exit(1)