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(), ... 

46import nfs 

47 

48import resetvdis 

49import vhdutil 

50import lvhdutil 

51 

52import VDI as sm 

53 

54# For RRDD Plugin Registration 

55from xmlrpc.client import ServerProxy, Transport 

56from socket import socket, AF_UNIX, SOCK_STREAM 

57 

58try: 

59 from linstorvolumemanager import log_drbd_openers 

60 LINSTOR_AVAILABLE = True 

61except ImportError: 

62 LINSTOR_AVAILABLE = False 

63 

64PLUGIN_TAP_PAUSE = "tapdisk-pause" 

65 

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

67 

68NUM_PAGES_PER_RING = 32 * 11 

69MAX_FULL_RINGS = 8 

70POOL_NAME_KEY = "mem-pool" 

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

72 

73ENABLE_MULTIPLE_ATTACH = "/etc/xensource/allow_multiple_vdi_attach" 

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

75 

76 

77def locking(excType, override=True): 

78 def locking2(op): 

79 def wrapper(self, *args): 

80 self.lock.acquire() 

81 try: 

82 try: 

83 ret = op(self, * args) 

84 except (util.CommandException, util.SMException, XenAPI.Failure) as e: 

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

86 msg = str(e) 

87 if isinstance(e, util.CommandException): 

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

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

90 if override: 

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

92 else: 

93 raise 

94 except: 

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

96 raise 

97 finally: 

98 self.lock.release() 98 ↛ exitline 98 didn't except from function 'wrapper', because the raise on line 91 wasn't executed or the raise on line 93 wasn't executed or the raise on line 96 wasn't executed

99 return ret 

100 return wrapper 

101 return locking2 

102 

103 

104class RetryLoop(object): 

105 

106 def __init__(self, backoff, limit): 

107 self.backoff = backoff 

108 self.limit = limit 

109 

110 def __call__(self, f): 

111 

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

113 attempt = 0 

114 

115 while True: 

116 attempt += 1 

117 

118 try: 

119 return f( * __t, ** __d) 

120 

121 except self.TransientFailure as e: 

122 e = e.exception 

123 

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

125 raise e 

126 

127 time.sleep(self.backoff) 

128 

129 return loop 

130 

131 class TransientFailure(Exception): 

132 def __init__(self, exception): 

133 self.exception = exception 

134 

135 

136def retried(**args): 

137 return RetryLoop( ** args) 

138 

139 

140class TapCtl(object): 

141 """Tapdisk IPC utility calls.""" 

142 

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

144 

145 def __init__(self, cmd, p): 

146 self.cmd = cmd 

147 self._p = p 

148 self.stdout = p.stdout 

149 

150 class CommandFailure(Exception): 

151 """TapCtl cmd failure.""" 

152 

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

154 self.cmd = cmd 

155 self.info = info 

156 

157 @override 

158 def __str__(self) -> str: 

159 items = self.info.items() 

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

161 for item in items) 

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

163 

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

165 # exception 

166 def __getattr__(self, key): 

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

168 return self.info[key] 

169 return object.__getattribute__(self, key) 

170 

171 @property 

172 def has_status(self): 

173 return 'status' in self.info 

174 

175 @property 

176 def has_signal(self): 

177 return 'signal' in self.info 

178 

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

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

181 def get_error_code(self): 

182 key = 'status' 

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

184 return self.info[key] 

185 else: 

186 return 0 

187 

188 @classmethod 

189 def __mkcmd_real(cls, args): 

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

191 

192 __next_mkcmd = __mkcmd_real 

193 

194 @classmethod 

195 def _mkcmd(cls, args): 

196 

197 __next_mkcmd = cls.__next_mkcmd 

198 cls.__next_mkcmd = cls.__mkcmd_real 

199 

200 return __next_mkcmd(args) 

201 

202 @classmethod 

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

204 """ 

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

206 Raises a TapCtl.CommandFailure if subprocess creation failed. 

207 """ 

208 cmd = cls._mkcmd(args) 

209 

210 if not quiet: 

211 util.SMlog(cmd) 

212 try: 

213 p = subprocess.Popen(cmd, 

214 stdin=subprocess.PIPE, 

215 stdout=subprocess.PIPE, 

216 stderr=subprocess.PIPE, 

217 close_fds=True, 

218 universal_newlines=text_mode) 

219 if input: 

220 p.stdin.write(input) 

221 p.stdin.close() 

222 except OSError as e: 

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

224 

225 return cls(cmd, p) 

226 

227 def _errmsg(self): 

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

229 return "; ".join(output) 

230 

231 def _wait(self, quiet=False): 

232 """ 

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

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

235 """ 

236 status = self._p.wait() 

237 if not quiet: 

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

239 

240 if status == 0: 

241 return 

242 

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

244 'pid': self._p.pid} 

245 

246 if status < 0: 

247 info['signal'] = -status 

248 else: 

249 info['status'] = status 

250 

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

252 

253 @classmethod 

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

255 """ 

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

257 """ 

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

259 text_mode=text_mode) 

260 

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

262 

263 tapctl._wait(quiet) 

264 return output 

265 

266 @staticmethod 

267 def _maybe(opt, parm): 

268 if parm is not None: 

269 return [opt, parm] 

270 return [] 

271 

272 @classmethod 

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

274 args = ["list"] 

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

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

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

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

279 

280 tapctl = cls._call(args, True) 

281 

282 for stdout_line in tapctl.stdout: 

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

284 # confuses this parser 

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

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

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

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

289 row = {} 

290 

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

292 bits = field.split('=') 

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

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

295 

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

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

298 

299 elif key in ('state'): 

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

301 

302 else: 

303 row[key] = val 

304 else: 

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

306 yield row 

307 

308 tapctl._wait(True) 

309 

310 @classmethod 

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

312 def list(cls, **args): 

313 

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

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

316 # be fixed in SM. 

317 

318 try: 

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

320 

321 except cls.CommandFailure as e: 

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

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

324 raise RetryLoop.TransientFailure(e) 

325 raise 

326 

327 @classmethod 

328 def allocate(cls, devpath=None): 

329 args = ["allocate"] 

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

331 return cls._pread(args) 

332 

333 @classmethod 

334 def free(cls, minor): 

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

336 cls._pread(args) 

337 

338 @classmethod 

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

340 def spawn(cls): 

341 args = ["spawn"] 

342 try: 

343 pid = cls._pread(args) 

344 return int(pid) 

345 except cls.CommandFailure as ce: 

346 # intermittent failures to spawn. CA-292268 

347 if ce.status == 1: 

348 raise RetryLoop.TransientFailure(ce) 

349 raise 

350 

351 @classmethod 

352 def attach(cls, pid, minor): 

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

354 cls._pread(args) 

355 

356 @classmethod 

357 def detach(cls, pid, minor): 

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

359 cls._pread(args) 

360 

361 @classmethod 

362 def _load_key(cls, key_hash, vdi_uuid): 

363 import plugins 

364 

365 return plugins.load_key(key_hash, vdi_uuid) 

366 

367 @classmethod 

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

369 params = Tapdisk.Arg(_type, _file) 

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

371 text_mode = True 

372 input = None 

373 if options.get("rdonly"): 

374 args.append('-R') 

375 if options.get("lcache"): 

376 args.append("-r") 

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

378 args.append("-e") 

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

380 if options.get("secondary"): 

381 args.append("-2") 

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

383 if options.get("standby"): 

384 args.append("-s") 

385 if options.get("timeout"): 

386 args.append("-t") 

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

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

389 args.append("-D") 

390 if options.get('cbtlog'): 

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

392 if options.get('key_hash'): 

393 key_hash = options['key_hash'] 

394 vdi_uuid = options['vdi_uuid'] 

395 key = cls._load_key(key_hash, vdi_uuid) 

396 

397 if not key: 

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

399 input = key 

400 text_mode = False 

401 args.append('-E') 

402 

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

404 

405 @classmethod 

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

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

408 if force: 

409 args += ["-f"] 

410 cls._pread(args) 

411 

412 @classmethod 

413 def pause(cls, pid, minor): 

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

415 cls._pread(args) 

416 

417 @classmethod 

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

419 cbtlog=None): 

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

421 if mirror: 

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

423 if _type and _file: 

424 params = Tapdisk.Arg(_type, _file) 

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

426 if cbtlog: 

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

428 cls._pread(args) 

429 

430 @classmethod 

431 def shutdown(cls, pid): 

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

433 os.kill(pid, signal.SIGTERM) 

434 os.waitpid(pid, 0) 

435 

436 @classmethod 

437 def stats(cls, pid, minor): 

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

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

440 

441 @classmethod 

442 def major(cls): 

443 args = ["major"] 

444 major = cls._pread(args) 

445 return int(major) 

446 

447 

448class TapdiskExists(Exception): 

449 """Tapdisk already running.""" 

450 

451 def __init__(self, tapdisk): 

452 self.tapdisk = tapdisk 

453 

454 @override 

455 def __str__(self) -> str: 

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

457 

458 

459class TapdiskNotRunning(Exception): 

460 """No such Tapdisk.""" 

461 

462 def __init__(self, **attrs): 

463 self.attrs = attrs 

464 

465 @override 

466 def __str__(self) -> str: 

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

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

469 for attr in items) 

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

471 

472 

473class TapdiskNotUnique(Exception): 

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

475 

476 def __init__(self, tapdisks): 

477 self.tapdisks = tapdisks 

478 

479 @override 

480 def __str__(self) -> str: 

481 tapdisks = map(str, self.tapdisks) 

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

483 

484 

485class TapdiskFailed(Exception): 

486 """Tapdisk launch failure.""" 

487 

488 def __init__(self, arg, err): 

489 self.arg = arg 

490 self.err = err 

491 

492 @override 

493 def __str__(self) -> str: 

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

495 

496 def get_error(self): 

497 return self.err 

498 

499 

500class TapdiskInvalidState(Exception): 

501 """Tapdisk pause/unpause failure""" 

502 

503 def __init__(self, tapdisk): 

504 self.tapdisk = tapdisk 

505 

506 @override 

507 def __str__(self) -> str: 

508 return str(self.tapdisk) 

509 

510 

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

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

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

514 assert parent != path 

515 try: 

516 if parent: 

517 mkdirs(parent, mode) 

518 if subdir: 

519 os.mkdir(path, mode) 

520 except OSError as e: 

521 if e.errno != errno.EEXIST: 

522 raise 

523 

524 

525class KObject(object): 

526 

527 SYSFS_CLASSTYPE: ClassVar[str] 

528 

529 @abstractmethod 

530 def sysfs_devname(self) -> str: 

531 pass 

532 

533 

534class Attribute(object): 

535 

536 SYSFS_NODENAME: ClassVar[str] 

537 

538 def __init__(self, path): 

539 self.path = path 

540 

541 @classmethod 

542 def from_kobject(cls, kobj): 

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

544 return cls(path) 

545 

546 class NoSuchAttribute(Exception): 

547 def __init__(self, name): 

548 self.name = name 

549 

550 @override 

551 def __str__(self) -> str: 

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

553 

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

555 try: 

556 return open(self.path, mode) 

557 except IOError as e: 

558 if e.errno == errno.ENOENT: 

559 raise self.NoSuchAttribute(self) 

560 raise 

561 

562 def readline(self): 

563 f = self._open('r') 

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

565 f.close() 

566 return s 

567 

568 def writeline(self, val): 

569 f = self._open('w') 

570 f.write(val) 

571 f.close() 

572 

573 

574class ClassDevice(KObject): 

575 

576 @classmethod 

577 def sysfs_class_path(cls): 

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

579 

580 def sysfs_path(self): 

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

582 self.sysfs_devname()) 

583 

584 

585class Blktap(ClassDevice): 

586 

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

588 

589 SYSFS_CLASSTYPE = "blktap2" 

590 

591 def __init__(self, minor): 

592 self.minor = minor 

593 self._pool = None 

594 self._task = None 

595 

596 @classmethod 

597 def allocate(cls): 

598 # FIXME. Should rather go into init. 

599 mkdirs(cls.DEV_BASEDIR) 

600 

601 devname = TapCtl.allocate() 

602 minor = Tapdisk._parse_minor(devname) 

603 return cls(minor) 

604 

605 def free(self): 

606 TapCtl.free(self.minor) 

607 

608 @override 

609 def __str__(self) -> str: 

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

611 

612 @override 

613 def sysfs_devname(self) -> str: 

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

615 

616 class Pool(Attribute): 

617 SYSFS_NODENAME = "pool" 

618 

619 def get_pool_attr(self): 

620 if not self._pool: 

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

622 return self._pool 

623 

624 def get_pool_name(self): 

625 return self.get_pool_attr().readline() 

626 

627 def set_pool_name(self, name): 

628 self.get_pool_attr().writeline(name) 

629 

630 def set_pool_size(self, pages): 

631 self.get_pool().set_size(pages) 

632 

633 def get_pool(self): 

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

635 

636 def set_pool(self, pool): 

637 self.set_pool_name(pool.name) 

638 

639 class Task(Attribute): 

640 SYSFS_NODENAME = "task" 

641 

642 def get_task_attr(self): 

643 if not self._task: 

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

645 return self._task 

646 

647 def get_task_pid(self): 

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

649 try: 

650 return int(pid) 

651 except ValueError: 

652 return None 

653 

654 def find_tapdisk(self): 

655 pid = self.get_task_pid() 

656 if pid is None: 

657 return None 

658 

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

660 

661 def get_tapdisk(self): 

662 tapdisk = self.find_tapdisk() 

663 if not tapdisk: 

664 raise TapdiskNotRunning(minor=self.minor) 

665 return tapdisk 

666 

667 

668class Tapdisk(object): 

669 

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

671 

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

673 self.pid = pid 

674 self.minor = minor 

675 self.type = _type 

676 self.path = path 

677 self.state = state 

678 self._dirty = False 

679 self._blktap = None 

680 

681 @override 

682 def __str__(self) -> str: 

683 state = self.pause_state() 

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

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

686 

687 @classmethod 

688 def list(cls, **args): 

689 

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

691 

692 args = {'pid': None, 

693 'minor': None, 

694 'state': None, 

695 '_type': None, 

696 'path': None} 

697 

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

699 if key in args: 

700 args[key] = val 

701 

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

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

704 args['_type'] = image.type 

705 args['path'] = image.path 

706 

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

708 continue 

709 

710 yield Tapdisk( ** args) 

711 

712 @classmethod 

713 def find(cls, **args): 

714 

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

716 

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

718 raise TapdiskNotUnique(found) 

719 

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

721 return found[0] 

722 

723 return None 

724 

725 @classmethod 

726 def find_by_path(cls, path): 

727 return cls.find(path=path) 

728 

729 @classmethod 

730 def find_by_minor(cls, minor): 

731 return cls.find(minor=minor) 

732 

733 @classmethod 

734 def get(cls, **attrs): 

735 

736 tapdisk = cls.find( ** attrs) 

737 

738 if not tapdisk: 

739 raise TapdiskNotRunning( ** attrs) 

740 

741 return tapdisk 

742 

743 @classmethod 

744 def from_path(cls, path): 

745 return cls.get(path=path) 

746 

747 @classmethod 

748 def from_minor(cls, minor): 

749 return cls.get(minor=minor) 

750 

751 @classmethod 

752 def __from_blktap(cls, blktap): 

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

754 tapdisk._blktap = blktap 

755 return tapdisk 

756 

757 def get_blktap(self): 

758 if not self._blktap: 

759 self._blktap = Blktap(self.minor) 

760 return self._blktap 

761 

762 class Arg: 

763 

764 def __init__(self, _type, path): 

765 self.type = _type 

766 self.path = path 

767 

768 @override 

769 def __str__(self) -> str: 

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

771 

772 @classmethod 

773 def parse(cls, arg): 

774 

775 try: 

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

777 except ValueError: 

778 raise cls.InvalidArgument(arg) 

779 

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

781 raise cls.InvalidType(_type) 

782 

783 return cls(_type, path) 

784 

785 class InvalidType(Exception): 

786 def __init__(self, _type): 

787 self.type = _type 

788 

789 @override 

790 def __str__(self) -> str: 

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

792 

793 class InvalidArgument(Exception): 

794 def __init__(self, arg): 

795 self.arg = arg 

796 

797 @override 

798 def __str__(self) -> str: 

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

800 

801 def get_arg(self): 

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

803 

804 def get_devpath(self): 

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

806 

807 @classmethod 

808 def launch_from_arg(cls, arg): 

809 arg = cls.Arg.parse(arg) 

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

811 

812 @staticmethod 

813 def cgclassify(pid): 

814 

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

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

817 # we have configured in the spec file. 

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

819 try: 

820 util.pread2(cmd) 

821 except util.CommandException as e: 

822 util.logException(e) 

823 

824 @classmethod 

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

826 

827 tapdisk = cls.find_by_path(path) 

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

829 raise TapdiskExists(tapdisk) 

830 

831 minor = blktap.minor 

832 try: 

833 pid = TapCtl.spawn() 

834 cls.cgclassify(pid) 

835 try: 

836 TapCtl.attach(pid, minor) 

837 

838 try: 

839 retry_open = 0 

840 while True: 

841 try: 

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

843 break 

844 except TapCtl.CommandFailure as e: 

845 err = ( 

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

847 ) or None 

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

849 if retry_open < 5: 

850 retry_open += 1 

851 time.sleep(1) 

852 continue 

853 if LINSTOR_AVAILABLE and err == errno.EROFS: 

854 log_drbd_openers(path) 

855 raise 

856 try: 

857 tapdisk = cls.__from_blktap(blktap) 

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

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

860 return tapdisk 

861 except: 

862 TapCtl.close(pid, minor) 

863 raise 

864 

865 except: 

866 TapCtl.detach(pid, minor) 

867 raise 

868 

869 except: 

870 try: 

871 TapCtl.shutdown(pid) 

872 except: 

873 # Best effort to shutdown 

874 pass 

875 raise 

876 

877 except TapCtl.CommandFailure as ctl: 

878 util.logException(ctl) 

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

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

881 raise xs_errors.XenError('TapdiskDriveEmpty') 

882 else: 

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

884 

885 @classmethod 

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

887 blktap = Blktap.allocate() 

888 try: 

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

890 except: 

891 blktap.free() 

892 raise 

893 

894 def shutdown(self, force=False): 

895 

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

897 

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

899 

900 self.get_blktap().free() 

901 

902 def pause(self): 

903 

904 if not self.is_running(): 

905 raise TapdiskInvalidState(self) 

906 

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

908 

909 self._set_dirty() 

910 

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

912 

913 if not self.is_paused(): 

914 raise TapdiskInvalidState(self) 

915 

916 # FIXME: should the arguments be optional? 

917 if _type is None: 

918 _type = self.type 

919 if path is None: 

920 path = self.path 

921 

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

923 cbtlog=cbtlog) 

924 

925 self._set_dirty() 

926 

927 def stats(self): 

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

929 # 

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

931 # 

932 

933 def _set_dirty(self): 

934 self._dirty = True 

935 

936 def _refresh(self, __get): 

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

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

939 

940 @override 

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

942 def __get(name): 

943 # NB. avoid(rec(ursion) 

944 return object.__getattribute__(self, name) 

945 

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

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

948 self._refresh(__get) 

949 self._dirty = False 

950 

951 return __get(name) 

952 

953 class PauseState: 

954 RUNNING = 'R' 

955 PAUSING = 'r' 

956 PAUSED = 'P' 

957 

958 class Flags: 

959 DEAD = 0x0001 

960 CLOSED = 0x0002 

961 QUIESCE_REQUESTED = 0x0004 

962 QUIESCED = 0x0008 

963 PAUSE_REQUESTED = 0x0010 

964 PAUSED = 0x0020 

965 SHUTDOWN_REQUESTED = 0x0040 

966 LOCKING = 0x0080 

967 RETRY_NEEDED = 0x0100 

968 LOG_DROPPED = 0x0200 

969 

970 PAUSE_MASK = PAUSE_REQUESTED | PAUSED 

971 

972 def is_paused(self): 

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

974 

975 def is_running(self): 

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

977 

978 def pause_state(self): 

979 if self.state & self.Flags.PAUSED: 

980 return self.PauseState.PAUSED 

981 

982 if self.state & self.Flags.PAUSE_REQUESTED: 

983 return self.PauseState.PAUSING 

984 

985 return self.PauseState.RUNNING 

986 

987 @staticmethod 

988 def _parse_minor(devpath): 

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

990 pattern = re.compile(regex) 

991 groups = pattern.search(devpath) 

992 if not groups: 

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

994 

995 minor = groups.group(2) 

996 return int(minor) 

997 

998 _major = None 

999 

1000 @classmethod 

1001 def major(cls): 

1002 if cls._major: 

1003 return cls._major 

1004 

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

1006 for line in devices: 

1007 

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

1009 if len(row) != 2: 

1010 continue 

1011 

1012 major, name = row 

1013 if name != 'tapdev': 

1014 continue 

1015 

1016 cls._major = int(major) 

1017 break 

1018 

1019 devices.close() 

1020 return cls._major 

1021 

1022 

1023class VDI(object): 

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

1025 

1026 CONF_KEY_ALLOW_CACHING = "vdi_allow_caching" 

1027 CONF_KEY_MODE_ON_BOOT = "vdi_on_boot" 

1028 CONF_KEY_CACHE_SR = "local_cache_sr" 

1029 CONF_KEY_O_DIRECT = "o_direct" 

1030 LOCK_CACHE_SETUP = "cachesetup" 

1031 

1032 ATTACH_DETACH_RETRY_SECS = 120 

1033 

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

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

1036 self._vdi_uuid = uuid 

1037 self._session = target.session 

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

1039 self.__o_direct = None 

1040 self.__o_direct_reason = None 

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

1042 self.tap = None 

1043 

1044 def get_o_direct_capability(self, options): 

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

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

1047 return self.__o_direct, self.__o_direct_reason 

1048 

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

1050 self.__o_direct = True 

1051 self.__o_direct_reason = "LICENSE_RESTRICTION" 

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

1053 self.__o_direct = True 

1054 self.__o_direct_reason = "SR_NOT_SUPPORTED" 

1055 elif not (options.get("rdonly") or self.target.vdi.parent): 

1056 util.SMlog(self.target.vdi) 

1057 self.__o_direct = True 

1058 self.__o_direct_reason = "NO_RO_IMAGE" 

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

1060 self.__o_direct = True 

1061 self.__o_direct_reason = "RO_WITH_NO_PARENT" 

1062 elif options.get(self.CONF_KEY_O_DIRECT): 

1063 self.__o_direct = True 

1064 self.__o_direct_reason = "SR_OVERRIDE" 

1065 

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

1067 self.__o_direct = False 

1068 self.__o_direct_reason = "" 

1069 

1070 return self.__o_direct, self.__o_direct_reason 

1071 

1072 @classmethod 

1073 def from_cli(cls, uuid): 

1074 import VDI as sm 

1075 

1076 session = XenAPI.xapi_local() 

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

1078 

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

1080 driver_info = target.sr.srcmd.driver_info 

1081 

1082 session.xenapi.session.logout() 

1083 

1084 return cls(uuid, target, driver_info) 

1085 

1086 @staticmethod 

1087 def _tap_type(vdi_type): 

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

1089 return { 

1090 'raw': 'aio', 

1091 'vhd': 'vhd', 

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

1093 'aio': 'aio', # for LVHD 

1094 'file': 'aio', 

1095 'phy': 'aio' 

1096 }[vdi_type] 

1097 

1098 def get_tap_type(self): 

1099 vdi_type = self.target.get_vdi_type() 

1100 return VDI._tap_type(vdi_type) 

1101 

1102 def get_phy_path(self): 

1103 return self.target.get_vdi_path() 

1104 

1105 class UnexpectedVDIType(Exception): 

1106 

1107 def __init__(self, vdi_type, target): 

1108 self.vdi_type = vdi_type 

1109 self.target = target 

1110 

1111 @override 

1112 def __str__(self) -> str: 

1113 return \ 

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

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

1116 

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

1118 'raw': 'phy', 

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

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

1121 'file': 'tap', 

1122 'vhd': 'tap'} 

1123 

1124 def tap_wanted(self): 

1125 # 1. Let the target vdi_type decide 

1126 

1127 vdi_type = self.target.get_vdi_type() 

1128 

1129 try: 

1130 plug_type = self.VDI_PLUG_TYPE[vdi_type] 

1131 except KeyError: 

1132 raise self.UnexpectedVDIType(vdi_type, 

1133 self.target.vdi) 

1134 

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

1136 return True 

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

1138 return True 

1139 # 2. Otherwise, there may be more reasons 

1140 # 

1141 # .. TBD 

1142 

1143 return False 

1144 

1145 class TargetDriver: 

1146 """Safe target driver access.""" 

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

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

1149 # try/except would risk breaking compatibility. 

1150 

1151 def __init__(self, vdi, driver_info): 

1152 self.vdi = vdi 

1153 self._caps = driver_info['capabilities'] 

1154 

1155 def has_cap(self, cap): 

1156 """Determine if target has given capability""" 

1157 return cap in self._caps 

1158 

1159 def attach(self, sr_uuid, vdi_uuid): 

1160 #assert self.has_cap("VDI_ATTACH") 

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

1162 

1163 def detach(self, sr_uuid, vdi_uuid): 

1164 #assert self.has_cap("VDI_DETACH") 

1165 self.vdi.detach(sr_uuid, vdi_uuid) 

1166 

1167 def activate(self, sr_uuid, vdi_uuid): 

1168 if self.has_cap("VDI_ACTIVATE"): 

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

1170 

1171 def deactivate(self, sr_uuid, vdi_uuid): 

1172 if self.has_cap("VDI_DEACTIVATE"): 

1173 self.vdi.deactivate(sr_uuid, vdi_uuid) 

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

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

1176 

1177 def get_vdi_type(self): 

1178 _type = self.vdi.vdi_type 

1179 if not _type: 

1180 _type = self.vdi.sr.sr_vditype 

1181 if not _type: 

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

1183 return _type 

1184 

1185 def get_vdi_path(self): 

1186 return self.vdi.path 

1187 

1188 class Link(object): 

1189 """Relink a node under a common name""" 

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

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

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

1193 # relink existing devices under deterministic path names. 

1194 

1195 BASEDIR: ClassVar[str] 

1196 

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

1198 pass 

1199 

1200 @abstractmethod 

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

1202 pass 

1203 

1204 def __init__(self, path): 

1205 self._path = path 

1206 

1207 @classmethod 

1208 def from_name(cls, name): 

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

1210 return cls(path) 

1211 

1212 @classmethod 

1213 def from_uuid(cls, sr_uuid, vdi_uuid): 

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

1215 return cls.from_name(name) 

1216 

1217 def path(self): 

1218 return self._path 

1219 

1220 def stat(self): 

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

1222 

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

1224 

1225 path = self.path() 

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

1227 

1228 mkdirs(os.path.dirname(path)) 

1229 try: 

1230 self._mklink(target) 

1231 except OSError as e: 

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

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

1234 # be seen. 

1235 if e.errno != errno.EEXIST: 

1236 raise 

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

1238 

1239 def unlink(self): 

1240 try: 

1241 os.unlink(self.path()) 

1242 except OSError as e: 

1243 if e.errno != errno.ENOENT: 

1244 raise 

1245 

1246 @override 

1247 def __str__(self) -> str: 

1248 path = self.path() 

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

1250 

1251 class SymLink(Link): 

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

1253 

1254 def readlink(self): 

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

1256 

1257 def symlink(self): 

1258 return self.path() 

1259 

1260 @override 

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

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

1263 

1264 @override 

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

1266 return self.readlink() == target 

1267 

1268 class DeviceNode(Link): 

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

1270 

1271 @classmethod 

1272 def _real_stat(cls, target): 

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

1274 _target = os.path.realpath(target) 

1275 return os.stat(_target) 

1276 

1277 @classmethod 

1278 def is_block(cls, target): 

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

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

1281 

1282 @override 

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

1284 

1285 st = self._real_stat(target) 

1286 if not S_ISBLK(st.st_mode): 

1287 raise self.NotABlockDevice(target, st) 

1288 

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

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

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

1292 

1293 @override 

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

1295 target_rdev = self._real_stat(target).st_rdev 

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

1297 

1298 def rdev(self): 

1299 st = self.stat() 

1300 assert S_ISBLK(st.st_mode) 

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

1302 

1303 class NotABlockDevice(Exception): 

1304 

1305 def __init__(self, path, st): 

1306 self.path = path 

1307 self.st = st 

1308 

1309 @override 

1310 def __str__(self) -> str: 

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

1312 

1313 class Hybrid(Link): 

1314 

1315 def __init__(self, path): 

1316 VDI.Link.__init__(self, path) 

1317 self._devnode = VDI.DeviceNode(path) 

1318 self._symlink = VDI.SymLink(path) 

1319 

1320 def rdev(self): 

1321 st = self.stat() 

1322 if S_ISBLK(st.st_mode): 

1323 return self._devnode.rdev() 

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

1325 

1326 @override 

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

1328 if self._devnode.is_block(target): 

1329 self._obj = self._devnode 

1330 else: 

1331 self._obj = self._symlink 

1332 self._obj.mklink(target) 

1333 

1334 @override 

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

1336 return self._obj._equals(target) 

1337 

1338 class PhyLink(SymLink): 

1339 BASEDIR = "/dev/sm/phy" 

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

1341 

1342 class NBDLink(SymLink): 

1343 

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

1345 

1346 class BackendLink(Hybrid): 

1347 BASEDIR = "/dev/sm/backend" 

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

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

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

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

1352 # soon as ISOs are tapdisks. 

1353 

1354 @staticmethod 

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

1356 

1357 tapdisk = Tapdisk.find_by_path(phy_path) 

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

1359 blktap = Blktap.allocate() 

1360 blktap.set_pool_name(sr_uuid) 

1361 if pool_size: 

1362 blktap.set_pool_size(pool_size) 

1363 

1364 try: 

1365 tapdisk = \ 

1366 Tapdisk.launch_on_tap(blktap, 

1367 phy_path, 

1368 VDI._tap_type(vdi_type), 

1369 options) 

1370 except: 

1371 blktap.free() 

1372 raise 

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

1374 

1375 else: 

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

1377 

1378 return tapdisk.get_devpath(), tapdisk 

1379 

1380 @staticmethod 

1381 def _tap_deactivate(minor): 

1382 

1383 try: 

1384 tapdisk = Tapdisk.from_minor(minor) 

1385 except TapdiskNotRunning as e: 

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

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

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

1389 # the recorded minor. 

1390 else: 

1391 tapdisk.shutdown() 

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

1393 

1394 @classmethod 

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

1396 """ 

1397 Pauses the tapdisk. 

1398 

1399 session: a XAPI session 

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

1401 vdi_uuid: the UUID of the VDI to pause 

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

1403 non-blocking manner 

1404 """ 

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

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

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

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

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

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

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

1412 if not cls.call_pluginhandler(session, host_ref, 

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

1414 # Failed to pause node 

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

1416 return False 

1417 return True 

1418 

1419 @classmethod 

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

1421 activate_parents=False): 

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

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

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

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

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

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

1428 if not cls.call_pluginhandler(session, host_ref, 

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

1430 # Failed to unpause node 

1431 return False 

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

1433 return True 

1434 

1435 @classmethod 

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

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

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

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

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

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

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

1443 if not cls.call_pluginhandler(session, host_ref, 

1444 sr_uuid, vdi_uuid, "refresh", None, 

1445 activate_parents=activate_parents): 

1446 # Failed to refresh node 

1447 return False 

1448 return True 

1449 

1450 @classmethod 

1451 def tap_status(cls, session, vdi_uuid): 

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

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

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

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

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

1457 return True 

1458 return False 

1459 

1460 @classmethod 

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

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

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

1464 try: 

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

1466 "failfast": str(failfast)} 

1467 if secondary: 

1468 args["secondary"] = secondary 

1469 if activate_parents: 

1470 args["activate_parents"] = "true" 

1471 ret = session.xenapi.host.call_plugin( 

1472 host_ref, PLUGIN_TAP_PAUSE, action, 

1473 args) 

1474 return ret == "True" 

1475 except Exception as e: 

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

1477 return False 

1478 

1479 def _add_tag(self, vdi_uuid, writable): 

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

1481 attach_mode = "RO" 

1482 if writable: 1482 ↛ 1484line 1482 didn't jump to line 1484, because the condition on line 1482 was never false

1483 attach_mode = "RW" 

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

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

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

1487 attached_as = util.attached_as(sm_config) 

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

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

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

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

1492 term_output=False, writable=writable): 

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

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

1495 if 'relinking' in sm_config: 

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

1497 return False 

1498 if 'paused' in sm_config: 

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

1500 return False 

1501 self._session.xenapi.VDI.add_to_sm_config( 

1502 vdi_ref, 'activating', 'True') 

1503 host_key = "host_%s" % host_ref 

1504 assert host_key not in sm_config 

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

1506 attach_mode) 

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

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

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

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

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

1512 self._session.xenapi.VDI.remove_from_sm_config( 

1513 vdi_ref, 'activating') 

1514 return False 

1515 util.SMlog("Activate lock succeeded") 

1516 return True 

1517 

1518 def _check_tag(self, vdi_uuid): 

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

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

1521 if 'paused' in sm_config: 

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

1523 return False 

1524 return True 

1525 

1526 def _remove_tag(self, vdi_uuid): 

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

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

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

1530 host_key = "host_%s" % host_ref 

1531 if host_key in sm_config: 

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

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

1534 else: 

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

1536 

1537 def _get_pool_config(self, pool_name): 

1538 pool_info = dict() 

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

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

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

1542 # special pool 

1543 return pool_info 

1544 

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

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

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

1548 pool_size_str = sr_config.get(POOL_SIZE_KEY) 

1549 pool_name_override = vdi_config.get(POOL_NAME_KEY) 

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

1551 pool_name = pool_name_override 

1552 pool_size_override = vdi_config.get(POOL_SIZE_KEY) 

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

1554 pool_size_str = pool_size_override 

1555 pool_size = 0 

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

1557 try: 

1558 pool_size = int(pool_size_str) 

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

1560 raise ValueError("outside of range") 

1561 pool_size = NUM_PAGES_PER_RING * pool_size 

1562 except ValueError: 

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

1564 pool_size = 0 

1565 

1566 pool_info["mem-pool"] = pool_name 

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

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

1569 

1570 return pool_info 

1571 

1572 def linkNBD(self, sr_uuid, vdi_uuid): 

1573 if self.tap: 

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

1575 int(self.tap.minor)) 

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

1577 

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

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

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

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

1582 util.SMlog("Attach & activate") 

1583 self._attach(sr_uuid, vdi_uuid) 

1584 dev_path = self._activate(sr_uuid, vdi_uuid, 

1585 {"rdonly": not writable}) 

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

1587 self.linkNBD(sr_uuid, vdi_uuid) 

1588 

1589 # Return backend/ link 

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

1591 if self.tap_wanted(): 

1592 # Only have NBD if we also have a tap 

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

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

1595 vdi_uuid) 

1596 else: 

1597 nbd_path = "" 

1598 

1599 options = {"rdonly": not writable} 

1600 options.update(caching_params) 

1601 o_direct, o_direct_reason = self.get_o_direct_capability(options) 

1602 struct = {'params': back_path, 

1603 'params_nbd': nbd_path, 

1604 'o_direct': o_direct, 

1605 'o_direct_reason': o_direct_reason, 

1606 'xenstore_data': self.xenstore_data} 

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

1608 

1609 try: 

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

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

1612 f.close() 

1613 except: 

1614 pass 

1615 

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

1617 

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

1619 util.SMlog("blktap2.activate") 

1620 options = {"rdonly": not writable} 

1621 options.update(caching_params) 

1622 

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

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

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

1626 try: 

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

1628 return 

1629 except util.SRBusyException: 

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

1631 time.sleep(1) 

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

1633 

1634 @locking("VDIUnavailable") 

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

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

1637 

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

1639 refresh = False 

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

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

1642 return False 

1643 refresh = True 

1644 

1645 try: 

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

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

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

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

1650 # object completely 

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

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

1653 target.sr.srcmd.params = params 

1654 driver_info = target.sr.srcmd.driver_info 

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

1656 

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

1658 "blktap_activate_inject_failure", 

1659 lambda: util.inject_failure()) 

1660 

1661 # Attach the physical node 

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

1663 self._attach(sr_uuid, vdi_uuid) 

1664 

1665 vdi_type = self.target.get_vdi_type() 

1666 

1667 # Take lvchange-p Lock before running 

1668 # tap-ctl open 

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

1670 # now taking the same lock 

1671 # This is a fix for CA-155766 

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

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

1674 vdi_type == vhdutil.VDI_TYPE_VHD: 

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

1676 lock.acquire() 

1677 

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

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

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

1681 session = self.target.vdi.session 

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

1683 # pylint: disable=used-before-assignment 

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

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

1686 key_hash = sm_config['key_hash'] 

1687 options['key_hash'] = key_hash 

1688 options['vdi_uuid'] = vdi_uuid 

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

1690 # Activate the physical node 

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

1692 

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

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

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

1696 lock.release() 

1697 except: 

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

1699 if self.tap_wanted(): 

1700 util.fistpoint.activate_custom_fn( 

1701 "blktap_activate_error_handling", 

1702 lambda: time.sleep(30)) 

1703 while True: 

1704 try: 

1705 self._remove_tag(vdi_uuid) 

1706 break 

1707 except xmlrpc.client.ProtocolError as e: 

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

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

1710 continue 

1711 else: 

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

1713 break 

1714 except Exception as e: 

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

1716 break 

1717 raise 

1718 finally: 

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

1720 self._session.xenapi.VDI.remove_from_sm_config( 

1721 vdi_ref, 'activating') 

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

1723 

1724 # Link result to backend/ 

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

1726 self.linkNBD(sr_uuid, vdi_uuid) 

1727 return True 

1728 

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

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

1731 

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

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

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

1735 # Maybe launch a tapdisk on the physical link 

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

1737 vdi_type = self.target.get_vdi_type() 

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

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

1740 options.update(vdi_options) 

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

1742 sr_uuid, options, 

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

1744 else: 

1745 dev_path = phy_path # Just reuse phy 

1746 

1747 return dev_path 

1748 

1749 def _attach(self, sr_uuid, vdi_uuid): 

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

1751 params = attach_info['params'] 

1752 xenstore_data = attach_info['xenstore_data'] 

1753 phy_path = util.to_plain_string(params) 

1754 self.xenstore_data.update(xenstore_data) 

1755 # Save it to phy/ 

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

1757 

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

1759 util.SMlog("blktap2.deactivate") 

1760 for i in range(self.ATTACH_DETACH_RETRY_SECS): 

1761 try: 

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

1763 return 

1764 except util.SRBusyException as e: 

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

1766 time.sleep(1) 

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

1768 

1769 @locking("VDIUnavailable") 

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

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

1772 

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

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

1775 return False 

1776 

1777 self._deactivate(sr_uuid, vdi_uuid, caching_params) 

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

1779 self._detach(sr_uuid, vdi_uuid) 

1780 if self.tap_wanted(): 

1781 self._remove_tag(vdi_uuid) 

1782 

1783 return True 

1784 

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

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

1787 

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

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

1790 util.SMlog("Deactivate & detach") 

1791 self._deactivate(sr_uuid, vdi_uuid, caching_params) 

1792 self._detach(sr_uuid, vdi_uuid) 

1793 else: 

1794 pass # nothing to do 

1795 

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

1797 import VDI as sm 

1798 

1799 # Shutdown tapdisk 

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

1801 

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

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

1804 return 

1805 

1806 try: 

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

1808 os.unlink(attach_info_path) 

1809 except: 

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

1811 

1812 try: 

1813 major, minor = back_link.rdev() 

1814 except self.DeviceNode.NotABlockDevice: 

1815 pass 

1816 else: 

1817 if major == Tapdisk.major(): 

1818 self._tap_deactivate(minor) 

1819 self.remove_cache(sr_uuid, vdi_uuid, caching_params) 

1820 

1821 # Remove the backend link 

1822 back_link.unlink() 

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

1824 

1825 # Deactivate & detach the physical node 

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

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

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

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

1830 # object completely 

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

1832 driver_info = target.sr.srcmd.driver_info 

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

1834 

1835 self.target.deactivate(sr_uuid, vdi_uuid) 

1836 

1837 def _detach(self, sr_uuid, vdi_uuid): 

1838 self.target.detach(sr_uuid, vdi_uuid) 

1839 

1840 # Remove phy/ 

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

1842 

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

1844 # Remove existing VDI.sm_config fields 

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

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

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

1848 if not on_boot is None: 

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

1850 if not caching is None: 

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

1852 

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

1854 if params.get(self.CONF_KEY_ALLOW_CACHING) != "true": 1854 ↛ 1857line 1854 didn't jump to line 1857, because the condition on line 1854 was never false

1855 return 

1856 

1857 util.SMlog("Requested local caching") 

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

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

1860 return 

1861 

1862 scratch_mode = False 

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

1864 scratch_mode = True 

1865 util.SMlog("Requested scratch mode") 

1866 if not self.target.has_cap("VDI_RESET_ON_BOOT/2"): 

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

1868 return 

1869 

1870 dev_path = None 

1871 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR) 

1872 if not local_sr_uuid: 

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

1874 return 

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

1876 local_sr_uuid, scratch_mode, params) 

1877 

1878 if dev_path: 

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

1880 params.get(self.CONF_KEY_MODE_ON_BOOT), 

1881 params.get(self.CONF_KEY_ALLOW_CACHING)) 

1882 

1883 return dev_path 

1884 

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

1886 vm_uuid = None 

1887 vm_label = "" 

1888 try: 

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

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

1891 cache_sr_label = cache_sr_rec.get("name_label") 

1892 

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

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

1895 host_label = host_rec.get("name_label") 

1896 

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

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

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

1900 for vbd_rec in vbds.values(): 

1901 vm_ref = vbd_rec.get("VM") 

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

1903 vm_uuid = vm_rec.get("uuid") 

1904 vm_label = vm_rec.get("name_label") 

1905 except: 

1906 util.logException("alert_no_cache") 

1907 

1908 alert_obj = "SR" 

1909 alert_uuid = str(cache_sr_uuid) 

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

1911 if vm_uuid: 

1912 alert_obj = "VM" 

1913 alert_uuid = vm_uuid 

1914 reason = "" 

1915 if err == errno.ENOSPC: 

1916 reason = "because there is no space left" 

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

1918 (vm_label, reason, cache_sr_label, host_label) 

1919 

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

1921 (alert_obj, alert_uuid, alert_str)) 

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

1923 alert_obj, alert_uuid, alert_str) 

1924 

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

1926 scratch_mode, options): 

1927 import SR 

1928 import EXTSR 

1929 import NFSSR 

1930 from lock import Lock 

1931 from FileSR import FileVDI 

1932 

1933 parent_uuid = vhdutil.getParent(self.target.vdi.path, 

1934 FileVDI.extractUuid) 

1935 if not parent_uuid: 

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

1937 self.target.vdi.uuid) 

1938 return 

1939 

1940 util.SMlog("Setting up cache") 

1941 parent_uuid = parent_uuid.strip() 

1942 shared_target = NFSSR.NFSFileVDI(self.target.vdi.sr, parent_uuid) 

1943 

1944 if shared_target.parent: 

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

1946 shared_target.uuid) 

1947 return 

1948 

1949 SR.registerSR(EXTSR.EXTSR) 

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

1951 

1952 lock = Lock(self.LOCK_CACHE_SETUP, parent_uuid) 

1953 lock.acquire() 

1954 

1955 # read cache 

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

1957 if util.pathexists(read_cache_path): 

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

1959 read_cache_path) 

1960 else: 

1961 try: 

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

1963 except util.CommandException as e: 

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

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

1966 return None 

1967 

1968 # local write node 

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

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

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

1972 if util.pathexists(local_leaf_path): 

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

1974 local_leaf_path) 

1975 os.unlink(local_leaf_path) 

1976 try: 

1977 vhdutil.snapshot(local_leaf_path, read_cache_path, False, 

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

1979 except util.CommandException as e: 

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

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

1982 return None 

1983 

1984 local_leaf_size = vhdutil.getSizeVirt(local_leaf_path) 

1985 if leaf_size > local_leaf_size: 

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

1987 (leaf_size, local_leaf_size)) 

1988 vhdutil.setSizeVirtFast(local_leaf_path, leaf_size) 

1989 

1990 vdi_type = self.target.get_vdi_type() 

1991 

1992 prt_tapdisk = Tapdisk.find_by_path(read_cache_path) 

1993 if not prt_tapdisk: 

1994 parent_options = copy.deepcopy(options) 

1995 parent_options["rdonly"] = False 

1996 parent_options["lcache"] = True 

1997 

1998 blktap = Blktap.allocate() 

1999 try: 

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

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

2002 # its own pool 

2003 prt_tapdisk = \ 

2004 Tapdisk.launch_on_tap(blktap, read_cache_path, 

2005 'vhd', parent_options) 

2006 except: 

2007 blktap.free() 

2008 raise 

2009 

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

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

2012 

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

2014 leaf_tapdisk = Tapdisk.find_by_path(local_leaf_path) 

2015 if not leaf_tapdisk: 

2016 blktap = Blktap.allocate() 

2017 child_options = copy.deepcopy(options) 

2018 child_options["rdonly"] = False 

2019 child_options["lcache"] = False 

2020 child_options["existing_prt"] = prt_tapdisk.minor 

2021 child_options["secondary"] = secondary 

2022 child_options["standby"] = scratch_mode 

2023 try: 

2024 leaf_tapdisk = \ 

2025 Tapdisk.launch_on_tap(blktap, local_leaf_path, 

2026 'vhd', child_options) 

2027 except: 

2028 blktap.free() 

2029 raise 

2030 

2031 lock.release() 

2032 

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

2034 (read_cache_path, local_leaf_path)) 

2035 

2036 self.tap = leaf_tapdisk 

2037 return leaf_tapdisk.get_devpath() 

2038 

2039 def remove_cache(self, sr_uuid, vdi_uuid, params): 

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

2041 return 

2042 

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

2044 

2045 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR) 

2046 if caching and not local_sr_uuid: 

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

2048 return 

2049 

2050 if caching: 

2051 self._remove_cache(self._session, local_sr_uuid) 

2052 

2053 if self._session is not None: 

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

2055 

2056 def _is_tapdisk_in_use(self, minor): 

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

2058 if not retVal: 

2059 # err on the side of caution 

2060 return True 

2061 

2062 for link in links: 

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

2064 return True 

2065 

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

2067 for s in sockets: 

2068 if socket_re.match(s): 

2069 return True 

2070 

2071 return False 

2072 

2073 def _remove_cache(self, session, local_sr_uuid): 

2074 import SR 

2075 import EXTSR 

2076 import NFSSR 

2077 from lock import Lock 

2078 from FileSR import FileVDI 

2079 

2080 parent_uuid = vhdutil.getParent(self.target.vdi.path, 

2081 FileVDI.extractUuid) 

2082 if not parent_uuid: 

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

2084 self.target.vdi.uuid) 

2085 return 

2086 

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

2088 

2089 parent_uuid = parent_uuid.strip() 

2090 shared_target = NFSSR.NFSFileVDI(self.target.vdi.sr, parent_uuid) 

2091 

2092 SR.registerSR(EXTSR.EXTSR) 

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

2094 

2095 lock = Lock(self.LOCK_CACHE_SETUP, parent_uuid) 

2096 lock.acquire() 

2097 

2098 # local write node 

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

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

2101 if util.pathexists(local_leaf_path): 

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

2103 os.unlink(local_leaf_path) 

2104 

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

2106 prt_tapdisk = Tapdisk.find_by_path(read_cache_path) 

2107 if not prt_tapdisk: 

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

2109 elif not self._is_tapdisk_in_use(prt_tapdisk.minor): 

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

2111 read_cache_path) 

2112 try: 

2113 prt_tapdisk.shutdown() 

2114 except: 

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

2116 else: 

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

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

2119 # GC run 

2120 

2121 lock.release() 

2122 

2123PythonKeyError = KeyError 

2124 

2125 

2126class UEventHandler(object): 

2127 

2128 def __init__(self): 

2129 self._action = None 

2130 

2131 class KeyError(PythonKeyError): 

2132 def __init__(self, args): 

2133 super().__init__(args) 

2134 self.key = args[0] 

2135 

2136 @override 

2137 def __str__(self) -> str: 

2138 return \ 

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

2140 "Not called in udev context?" 

2141 

2142 @classmethod 

2143 def getenv(cls, key): 

2144 try: 

2145 return os.environ[key] 

2146 except KeyError as e: 

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

2148 

2149 def get_action(self): 

2150 if not self._action: 

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

2152 return self._action 

2153 

2154 class UnhandledEvent(Exception): 

2155 

2156 def __init__(self, event, handler): 

2157 self.event = event 

2158 self.handler = handler 

2159 

2160 @override 

2161 def __str__(self) -> str: 

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

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

2164 

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

2166 

2167 def run(self): 

2168 

2169 action = self.get_action() 

2170 try: 

2171 fn = self.ACTIONS[action] 

2172 except KeyError: 

2173 raise self.UnhandledEvent(action, self) 

2174 

2175 return fn(self) 

2176 

2177 @override 

2178 def __str__(self) -> str: 

2179 try: 

2180 action = self.get_action() 

2181 except: 

2182 action = None 

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

2184 

2185 

2186class __BlktapControl(ClassDevice): 

2187 SYSFS_CLASSTYPE = "misc" 

2188 

2189 def __init__(self): 

2190 ClassDevice.__init__(self) 

2191 self._default_pool = None 

2192 

2193 @override 

2194 def sysfs_devname(self) -> str: 

2195 return "blktap!control" 

2196 

2197 class DefaultPool(Attribute): 

2198 SYSFS_NODENAME = "default_pool" 

2199 

2200 def get_default_pool_attr(self): 

2201 if not self._default_pool: 

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

2203 return self._default_pool 

2204 

2205 def get_default_pool_name(self): 

2206 return self.get_default_pool_attr().readline() 

2207 

2208 def set_default_pool_name(self, name): 

2209 self.get_default_pool_attr().writeline(name) 

2210 

2211 def get_default_pool(self): 

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

2213 

2214 def set_default_pool(self, pool): 

2215 self.set_default_pool_name(pool.name) 

2216 

2217 class NoSuchPool(Exception): 

2218 def __init__(self, name): 

2219 self.name = name 

2220 

2221 @override 

2222 def __str__(self) -> str: 

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

2224 

2225 def get_pool(self, name): 

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

2227 

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

2229 raise self.NoSuchPool(name) 

2230 

2231 return PagePool(path) 

2232 

2233BlktapControl = __BlktapControl() 

2234 

2235 

2236class PagePool(KObject): 

2237 

2238 def __init__(self, path): 

2239 self.path = path 

2240 self._size = None 

2241 

2242 @override 

2243 def sysfs_devname(self) -> str: 

2244 return '' 

2245 

2246 def sysfs_path(self): 

2247 return self.path 

2248 

2249 class Size(Attribute): 

2250 SYSFS_NODENAME = "size" 

2251 

2252 def get_size_attr(self): 

2253 if not self._size: 

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

2255 return self._size 

2256 

2257 def set_size(self, pages): 

2258 pages = str(pages) 

2259 self.get_size_attr().writeline(pages) 

2260 

2261 def get_size(self): 

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

2263 return int(pages) 

2264 

2265 

2266class BusDevice(KObject): 

2267 

2268 SYSFS_BUSTYPE: ClassVar[str] 

2269 

2270 @classmethod 

2271 def sysfs_bus_path(cls): 

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

2273 

2274 def sysfs_path(self): 

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

2276 self.sysfs_devname()) 

2277 

2278 return path 

2279 

2280 

2281class XenbusDevice(BusDevice): 

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

2283 

2284 XBT_NIL = "" 

2285 

2286 XENBUS_DEVTYPE: ClassVar[str] 

2287 

2288 def __init__(self, domid, devid): 

2289 self.domid = int(domid) 

2290 self.devid = int(devid) 

2291 self._xbt = XenbusDevice.XBT_NIL 

2292 

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

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

2295 

2296 def xs_path(self, key=None): 

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

2298 self.domid, 

2299 self.devid) 

2300 if key is not None: 

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

2302 

2303 return path 

2304 

2305 def _log(self, prio, msg): 

2306 syslog(prio, msg) 

2307 

2308 def info(self, msg): 

2309 self._log(_syslog.LOG_INFO, msg) 

2310 

2311 def warn(self, msg): 

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

2313 

2314 def _xs_read_path(self, path): 

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

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

2317 return val 

2318 

2319 def _xs_write_path(self, path, val): 

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

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

2322 

2323 def _xs_rm_path(self, path): 

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

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

2326 

2327 def read(self, key): 

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

2329 

2330 def has_xs_key(self, key): 

2331 return self.read(key) is not None 

2332 

2333 def write(self, key, val): 

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

2335 

2336 def rm(self, key): 

2337 self._xs_rm_path(self.xs_path(key)) 

2338 

2339 def exists(self): 

2340 return self.has_xs_key(None) 

2341 

2342 def begin(self): 

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

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

2345 

2346 def commit(self): 

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

2348 self._xbt = XenbusDevice.XBT_NIL 

2349 return ok 

2350 

2351 def abort(self): 

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

2353 assert(ok == True) 

2354 self._xbt = XenbusDevice.XBT_NIL 

2355 

2356 def create_physical_device(self): 

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

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

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

2360 return 

2361 try: 

2362 params = self.read("params") 

2363 frontend = self.read("frontend") 

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

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

2366 # from opening the physical-device 

2367 if not(is_cdrom): 

2368 major_minor = os.stat(params).st_rdev 

2369 major, minor = divmod(major_minor, 256) 

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

2371 except: 

2372 util.logException("BLKTAP2:create_physical_device") 

2373 

2374 def signal_hotplug(self, online=True): 

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

2376 self.XENBUS_DEVTYPE, 

2377 self.devid) 

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

2379 if online: 

2380 self._xs_write_path(xapi_path, "online") 

2381 self._xs_write_path(upstream_path, "connected") 

2382 else: 

2383 self._xs_rm_path(xapi_path) 

2384 self._xs_rm_path(upstream_path) 

2385 

2386 @override 

2387 def sysfs_devname(self) -> str: 

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

2389 self.domid, self.devid) 

2390 

2391 @override 

2392 def __str__(self) -> str: 

2393 return self.sysfs_devname() 

2394 

2395 @classmethod 

2396 def find(cls): 

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

2398 cls.XENBUS_DEVTYPE) 

2399 for path in glob.glob(pattern): 

2400 

2401 name = os.path.basename(path) 

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

2403 

2404 yield cls(domid, devid) 

2405 

2406 

2407class XenBackendDevice(XenbusDevice): 

2408 """Xenbus backend device""" 

2409 SYSFS_BUSTYPE = "xen-backend" 

2410 

2411 @classmethod 

2412 def from_xs_path(cls, _path): 

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

2414 

2415 assert _backend == 'backend' 

2416 assert _type == cls.XENBUS_DEVTYPE 

2417 

2418 domid = int(domid) 

2419 devid = int(devid) 

2420 

2421 return cls(domid, devid) 

2422 

2423 

2424class Blkback(XenBackendDevice): 

2425 """A blkback VBD""" 

2426 

2427 XENBUS_DEVTYPE = "vbd" 

2428 

2429 def __init__(self, domid, devid): 

2430 XenBackendDevice.__init__(self, domid, devid) 

2431 self._phy = None 

2432 self._vdi_uuid = None 

2433 self._q_state = None 

2434 self._q_events = None 

2435 

2436 class XenstoreValueError(Exception): 

2437 KEY: ClassVar[str] 

2438 

2439 def __init__(self, vbd, _str): 

2440 self.vbd = vbd 

2441 self.str = _str 

2442 

2443 @override 

2444 def __str__(self) -> str: 

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

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

2447 

2448 class PhysicalDeviceError(XenstoreValueError): 

2449 KEY = "physical-device" 

2450 

2451 class PhysicalDevice(object): 

2452 

2453 def __init__(self, major, minor): 

2454 self.major = int(major) 

2455 self.minor = int(minor) 

2456 

2457 @classmethod 

2458 def from_xbdev(cls, xbdev): 

2459 

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

2461 

2462 try: 

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

2464 major = int(major, 0x10) 

2465 minor = int(minor, 0x10) 

2466 except Exception as e: 

2467 raise xbdev.PhysicalDeviceError(xbdev, phy) 

2468 

2469 return cls(major, minor) 

2470 

2471 def makedev(self): 

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

2473 

2474 def is_tap(self): 

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

2476 

2477 @override 

2478 def __str__(self) -> str: 

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

2480 

2481 @override 

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

2483 return \ 

2484 self.major == other.major and \ 

2485 self.minor == other.minor 

2486 

2487 def get_physical_device(self): 

2488 if not self._phy: 

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

2490 return self._phy 

2491 

2492 class QueueEvents(Attribute): 

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

2494 notifications emitted.""" 

2495 

2496 SYSFS_NODENAME = "queue_events" 

2497 

2498 QUEUE_RUNNING = (1 << 0) 

2499 QUEUE_PAUSE_DONE = (1 << 1) 

2500 QUEUE_SHUTDOWN_DONE = (1 << 2) 

2501 QUEUE_PAUSE_REQUEST = (1 << 3) 

2502 QUEUE_SHUTDOWN_REQUEST = (1 << 4) 

2503 

2504 def get_mask(self): 

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

2506 

2507 def set_mask(self, mask): 

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

2509 

2510 def get_queue_events(self): 

2511 if not self._q_events: 

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

2513 return self._q_events 

2514 

2515 def get_vdi_uuid(self): 

2516 if not self._vdi_uuid: 

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

2518 return self._vdi_uuid 

2519 

2520 def pause_requested(self): 

2521 return self.has_xs_key("pause") 

2522 

2523 def shutdown_requested(self): 

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

2525 

2526 def shutdown_done(self): 

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

2528 

2529 def running(self): 

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

2531 

2532 @classmethod 

2533 def find_by_physical_device(cls, phy): 

2534 for dev in cls.find(): 

2535 try: 

2536 _phy = dev.get_physical_device() 

2537 except cls.PhysicalDeviceError: 

2538 continue 

2539 

2540 if _phy == phy: 

2541 yield dev 

2542 

2543 @classmethod 

2544 def find_by_tap_minor(cls, minor): 

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

2546 return cls.find_by_physical_device(phy) 

2547 

2548 @classmethod 

2549 def find_by_tap(cls, tapdisk): 

2550 return cls.find_by_tap_minor(tapdisk.minor) 

2551 

2552 def has_tap(self): 

2553 

2554 if not self.can_tap(): 

2555 return False 

2556 

2557 phy = self.get_physical_device() 

2558 if phy: 

2559 return phy.is_tap() 

2560 

2561 return False 

2562 

2563 def is_bare_hvm(self): 

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

2565 try: 

2566 self.get_physical_device() 

2567 

2568 except self.PhysicalDeviceError as e: 

2569 vdi_type = self.read("type") 

2570 

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

2572 

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

2574 raise 

2575 

2576 return True 

2577 

2578 return False 

2579 

2580 def can_tap(self): 

2581 return not self.is_bare_hvm() 

2582 

2583 

2584class BlkbackEventHandler(UEventHandler): 

2585 

2586 LOG_FACILITY = _syslog.LOG_DAEMON 

2587 

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

2589 if not ident: 

2590 ident = self.__class__.__name__ 

2591 

2592 self.ident = ident 

2593 self._vbd = None 

2594 self._tapdisk = None 

2595 

2596 UEventHandler.__init__(self) 

2597 

2598 @override 

2599 def run(self) -> None: 

2600 

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

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

2603 

2604 UEventHandler.run(self) 

2605 

2606 @override 

2607 def __str__(self) -> str: 

2608 

2609 try: 

2610 path = self.xs_path 

2611 except: 

2612 path = None 

2613 

2614 try: 

2615 action = self.get_action() 

2616 except: 

2617 action = None 

2618 

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

2620 

2621 def _log(self, prio, msg): 

2622 syslog(prio, msg) 

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

2624 

2625 def info(self, msg): 

2626 self._log(_syslog.LOG_INFO, msg) 

2627 

2628 def warn(self, msg): 

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

2630 

2631 def error(self, msg): 

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

2633 

2634 def get_vbd(self): 

2635 if not self._vbd: 

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

2637 return self._vbd 

2638 

2639 def get_tapdisk(self): 

2640 if not self._tapdisk: 

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

2642 self._tapdisk = Tapdisk.from_minor(minor) 

2643 return self._tapdisk 

2644 # 

2645 # Events 

2646 # 

2647 

2648 def __add(self): 

2649 vbd = self.get_vbd() 

2650 # Manage blkback transitions 

2651 # self._manage_vbd() 

2652 

2653 vbd.create_physical_device() 

2654 

2655 vbd.signal_hotplug() 

2656 

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

2658 def add(self): 

2659 try: 

2660 self.__add() 

2661 except Attribute.NoSuchAttribute as e: 

2662 # 

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

2664 # registers device attributes. So poll a little. 

2665 # 

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

2667 raise RetryLoop.TransientFailure(e) 

2668 

2669 def __change(self): 

2670 vbd = self.get_vbd() 

2671 

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

2673 

2674 if vbd.has_tap(): 

2675 pass 

2676 #self._pause_update_tap() 

2677 

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

2679 

2680 self._signal_xapi() 

2681 

2682 def change(self): 

2683 vbd = self.get_vbd() 

2684 

2685 # NB. Beware of spurious change events between shutdown 

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

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

2688 

2689 while True: 

2690 vbd.begin() 

2691 

2692 if not vbd.exists() or \ 

2693 vbd.shutdown_done(): 

2694 break 

2695 

2696 self.__change() 

2697 

2698 if vbd.commit(): 

2699 return 

2700 

2701 vbd.abort() 

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

2703 

2704 def remove(self): 

2705 vbd = self.get_vbd() 

2706 

2707 vbd.signal_hotplug(False) 

2708 

2709 ACTIONS = {'add': add, 

2710 'change': change, 

2711 'remove': remove} 

2712 # 

2713 # VDI.pause 

2714 # 

2715 

2716 def _tap_should_pause(self): 

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

2718 paused""" 

2719 

2720 tapdisk = self.get_tapdisk() 

2721 TapState = Tapdisk.PauseState 

2722 

2723 PAUSED = 'P' 

2724 RUNNING = 'R' 

2725 PAUSED_SHUTDOWN = 'P,S' 

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

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

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

2729 

2730 next = TapState.RUNNING 

2731 vbds = {} 

2732 

2733 for vbd in Blkback.find_by_tap(tapdisk): 

2734 name = str(vbd) 

2735 

2736 pausing = vbd.pause_requested() 

2737 closing = vbd.shutdown_requested() 

2738 running = vbd.running() 

2739 

2740 if pausing: 

2741 if closing and not running: 

2742 vbds[name] = PAUSED_SHUTDOWN 

2743 else: 

2744 vbds[name] = PAUSED 

2745 next = TapState.PAUSED 

2746 

2747 else: 

2748 vbds[name] = RUNNING 

2749 

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

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

2752 vbds, next)) 

2753 

2754 return next == TapState.PAUSED 

2755 

2756 def _pause_update_tap(self): 

2757 vbd = self.get_vbd() 

2758 

2759 if self._tap_should_pause(): 

2760 self._pause_tap() 

2761 else: 

2762 self._resume_tap() 

2763 

2764 def _pause_tap(self): 

2765 tapdisk = self.get_tapdisk() 

2766 

2767 if not tapdisk.is_paused(): 

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

2769 tapdisk.pause() 

2770 

2771 def _resume_tap(self): 

2772 tapdisk = self.get_tapdisk() 

2773 

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

2775 # type while resuming. 

2776 vbd = self.get_vbd() 

2777 vdi_uuid = vbd.get_vdi_uuid() 

2778 

2779 if tapdisk.is_paused(): 

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

2781 vdi = VDI.from_cli(vdi_uuid) 

2782 _type = vdi.get_tap_type() 

2783 path = vdi.get_phy_path() 

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

2785 tapdisk.unpause(_type, path) 

2786 # 

2787 # VBD.pause/shutdown 

2788 # 

2789 

2790 def _manage_vbd(self): 

2791 vbd = self.get_vbd() 

2792 # NB. Hook into VBD state transitions. 

2793 

2794 events = vbd.get_queue_events() 

2795 

2796 mask = 0 

2797 mask |= events.QUEUE_PAUSE_DONE # pause/unpause 

2798 mask |= events.QUEUE_SHUTDOWN_DONE # shutdown 

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

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

2801 

2802 events.set_mask(mask) 

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

2804 

2805 def _signal_xapi(self): 

2806 vbd = self.get_vbd() 

2807 

2808 pausing = vbd.pause_requested() 

2809 closing = vbd.shutdown_requested() 

2810 running = vbd.running() 

2811 

2812 handled = 0 

2813 

2814 if pausing and not running: 

2815 if 'pause-done' not in vbd: 

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

2817 handled += 1 

2818 

2819 if not pausing: 

2820 if 'pause-done' in vbd: 

2821 vbd.rm('pause-done') 

2822 handled += 1 

2823 

2824 if closing and not running: 

2825 if 'shutdown-done' not in vbd: 

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

2827 handled += 1 

2828 

2829 if handled > 1: 

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

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

2832 (pausing, closing, running)) 

2833 

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

2835 

2836 import sys 

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

2838 

2839 # 

2840 # Simple CLI interface for manual operation 

2841 # 

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

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

2844 # 

2845 

2846 def usage(stream): 

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

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

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

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

2851 

2852 try: 

2853 cmd = sys.argv[1] 

2854 except IndexError: 

2855 usage(sys.stderr) 

2856 sys.exit(1) 

2857 

2858 try: 

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

2860 except: 

2861 usage(sys.stderr) 

2862 sys.exit(1) 

2863 

2864 # 

2865 # Local Tapdisks 

2866 # 

2867 

2868 if cmd == 'tap.major': 

2869 

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

2871 

2872 elif cmd == 'tap.launch': 

2873 

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

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

2876 

2877 elif _class == 'tap': 

2878 

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

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

2881 try: 

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

2883 attrs[key] = val 

2884 continue 

2885 except ValueError: 

2886 pass 

2887 

2888 try: 

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

2890 continue 

2891 except ValueError: 

2892 pass 

2893 

2894 try: 

2895 arg = Tapdisk.Arg.parse(item) 

2896 attrs['_type'] = arg.type 

2897 attrs['path'] = arg.path 

2898 continue 

2899 except Tapdisk.Arg.InvalidArgument: 

2900 pass 

2901 

2902 attrs['path'] = item 

2903 

2904 if cmd == 'tap.list': 

2905 

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

2907 blktap = tapdisk.get_blktap() 

2908 print(tapdisk, end=' ') 

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

2910 (blktap, 

2911 blktap.get_task_pid(), 

2912 blktap.get_pool_name())) 

2913 

2914 elif cmd == 'tap.vbds': 

2915 # Find all Blkback instances for a given tapdisk 

2916 

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

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

2919 for vbd in Blkback.find_by_tap(tapdisk): 

2920 print(vbd, end=' ') 

2921 print() 

2922 

2923 else: 

2924 

2925 if not attrs: 

2926 usage(sys.stderr) 

2927 sys.exit(1) 

2928 

2929 try: 

2930 tapdisk = Tapdisk.get( ** attrs) 

2931 except TypeError: 

2932 usage(sys.stderr) 

2933 sys.exit(1) 

2934 

2935 if cmd == 'tap.shutdown': 

2936 # Shutdown a running tapdisk, or raise 

2937 tapdisk.shutdown() 

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

2939 

2940 elif cmd == 'tap.pause': 

2941 # Pause an unpaused tapdisk, or raise 

2942 tapdisk.pause() 

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

2944 

2945 elif cmd == 'tap.unpause': 

2946 # Unpause a paused tapdisk, or raise 

2947 tapdisk.unpause() 

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

2949 

2950 elif cmd == 'tap.stats': 

2951 # Gather tapdisk status 

2952 stats = tapdisk.stats() 

2953 print("%s:" % tapdisk) 

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

2955 

2956 else: 

2957 usage(sys.stderr) 

2958 sys.exit(1) 

2959 

2960 elif cmd == 'vbd.uevent': 

2961 

2962 hnd = BlkbackEventHandler(cmd) 

2963 

2964 if not sys.stdin.isatty(): 

2965 try: 

2966 hnd.run() 

2967 except Exception as e: 

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

2969 

2970 import traceback 

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

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

2973 for entry in trace: 

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

2975 util.SMlog(line) 

2976 else: 

2977 hnd.run() 

2978 

2979 elif cmd == 'vbd.list': 

2980 

2981 for vbd in Blkback.find(): 

2982 print(vbd, \ 

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

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

2985 

2986 else: 

2987 usage(sys.stderr) 

2988 sys.exit(1)