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, Dict, override 

22 

23from abc import ABC, ABCMeta, 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(ABC): 

526 @property 

527 @abstractmethod 

528 def SYSFS_CLASSTYPE(self) -> str: 

529 pass 

530 

531 @abstractmethod 

532 def sysfs_devname(self) -> str: 

533 pass 

534 

535 

536class Attribute(ABC): 

537 @property 

538 @abstractmethod 

539 def SYSFS_NODENAME(self) -> str: 

540 pass 

541 

542 def __init__(self, path): 

543 self.path = path 

544 

545 @classmethod 

546 def from_kobject(cls, kobj): 

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

548 return cls(path) 

549 

550 class NoSuchAttribute(Exception): 

551 def __init__(self, name): 

552 self.name = name 

553 

554 @override 

555 def __str__(self) -> str: 

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

557 

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

559 try: 

560 return open(self.path, mode) 

561 except IOError as e: 

562 if e.errno == errno.ENOENT: 

563 raise self.NoSuchAttribute(self) 

564 raise 

565 

566 def readline(self): 

567 f = self._open('r') 

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

569 f.close() 

570 return s 

571 

572 def writeline(self, val): 

573 f = self._open('w') 

574 f.write(val) 

575 f.close() 

576 

577 

578class ClassDevice(KObject): 

579 

580 @classmethod 

581 def sysfs_class_path(cls): 

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

583 

584 def sysfs_path(self): 

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

586 self.sysfs_devname()) 

587 

588 

589class Blktap(ClassDevice): 

590 

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

592 

593 @property 

594 @override 

595 def SYSFS_CLASSTYPE(self) -> str: 

596 return 'blktap2' 

597 

598 def __init__(self, minor): 

599 self.minor = minor 

600 self._pool = None 

601 self._task = None 

602 

603 @classmethod 

604 def allocate(cls): 

605 # FIXME. Should rather go into init. 

606 mkdirs(cls.DEV_BASEDIR) 

607 

608 devname = TapCtl.allocate() 

609 minor = Tapdisk._parse_minor(devname) 

610 return cls(minor) 

611 

612 def free(self): 

613 TapCtl.free(self.minor) 

614 

615 @override 

616 def __str__(self) -> str: 

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

618 

619 @override 

620 def sysfs_devname(self) -> str: 

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

622 

623 class Pool(Attribute): 

624 @property 

625 @override 

626 def SYSFS_NODENAME(self) -> str: 

627 return 'pool' 

628 

629 def get_pool_attr(self): 

630 if not self._pool: 

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

632 return self._pool 

633 

634 def get_pool_name(self): 

635 return self.get_pool_attr().readline() 

636 

637 def set_pool_name(self, name): 

638 self.get_pool_attr().writeline(name) 

639 

640 def set_pool_size(self, pages): 

641 self.get_pool().set_size(pages) 

642 

643 def get_pool(self): 

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

645 

646 def set_pool(self, pool): 

647 self.set_pool_name(pool.name) 

648 

649 class Task(Attribute): 

650 @property 

651 @override 

652 def SYSFS_NODENAME(self) -> str: 

653 return 'task' 

654 

655 def get_task_attr(self): 

656 if not self._task: 

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

658 return self._task 

659 

660 def get_task_pid(self): 

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

662 try: 

663 return int(pid) 

664 except ValueError: 

665 return None 

666 

667 def find_tapdisk(self): 

668 pid = self.get_task_pid() 

669 if pid is None: 

670 return None 

671 

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

673 

674 def get_tapdisk(self): 

675 tapdisk = self.find_tapdisk() 

676 if not tapdisk: 

677 raise TapdiskNotRunning(minor=self.minor) 

678 return tapdisk 

679 

680 

681class Tapdisk(object): 

682 

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

684 

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

686 self.pid = pid 

687 self.minor = minor 

688 self.type = _type 

689 self.path = path 

690 self.state = state 

691 self._dirty = False 

692 self._blktap = None 

693 

694 @override 

695 def __str__(self) -> str: 

696 state = self.pause_state() 

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

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

699 

700 @classmethod 

701 def list(cls, **args): 

702 

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

704 

705 args = {'pid': None, 

706 'minor': None, 

707 'state': None, 

708 '_type': None, 

709 'path': None} 

710 

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

712 if key in args: 

713 args[key] = val 

714 

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

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

717 args['_type'] = image.type 

718 args['path'] = image.path 

719 

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

721 continue 

722 

723 yield Tapdisk( ** args) 

724 

725 @classmethod 

726 def find(cls, **args): 

727 

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

729 

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

731 raise TapdiskNotUnique(found) 

732 

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

734 return found[0] 

735 

736 return None 

737 

738 @classmethod 

739 def find_by_path(cls, path): 

740 return cls.find(path=path) 

741 

742 @classmethod 

743 def find_by_minor(cls, minor): 

744 return cls.find(minor=minor) 

745 

746 @classmethod 

747 def get(cls, **attrs): 

748 

749 tapdisk = cls.find( ** attrs) 

750 

751 if not tapdisk: 

752 raise TapdiskNotRunning( ** attrs) 

753 

754 return tapdisk 

755 

756 @classmethod 

757 def from_path(cls, path): 

758 return cls.get(path=path) 

759 

760 @classmethod 

761 def from_minor(cls, minor): 

762 return cls.get(minor=minor) 

763 

764 @classmethod 

765 def __from_blktap(cls, blktap): 

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

767 tapdisk._blktap = blktap 

768 return tapdisk 

769 

770 def get_blktap(self): 

771 if not self._blktap: 

772 self._blktap = Blktap(self.minor) 

773 return self._blktap 

774 

775 class Arg: 

776 

777 def __init__(self, _type, path): 

778 self.type = _type 

779 self.path = path 

780 

781 @override 

782 def __str__(self) -> str: 

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

784 

785 @classmethod 

786 def parse(cls, arg): 

787 

788 try: 

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

790 except ValueError: 

791 raise cls.InvalidArgument(arg) 

792 

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

794 raise cls.InvalidType(_type) 

795 

796 return cls(_type, path) 

797 

798 class InvalidType(Exception): 

799 def __init__(self, _type): 

800 self.type = _type 

801 

802 @override 

803 def __str__(self) -> str: 

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

805 

806 class InvalidArgument(Exception): 

807 def __init__(self, arg): 

808 self.arg = arg 

809 

810 @override 

811 def __str__(self) -> str: 

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

813 

814 def get_arg(self): 

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

816 

817 def get_devpath(self): 

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

819 

820 @classmethod 

821 def launch_from_arg(cls, arg): 

822 arg = cls.Arg.parse(arg) 

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

824 

825 @staticmethod 

826 def cgclassify(pid): 

827 

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

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

830 # we have configured in the spec file. 

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

832 try: 

833 util.pread2(cmd) 

834 except util.CommandException as e: 

835 util.logException(e) 

836 

837 @classmethod 

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

839 

840 tapdisk = cls.find_by_path(path) 

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

842 raise TapdiskExists(tapdisk) 

843 

844 minor = blktap.minor 

845 try: 

846 pid = TapCtl.spawn() 

847 cls.cgclassify(pid) 

848 try: 

849 TapCtl.attach(pid, minor) 

850 

851 try: 

852 retry_open = 0 

853 while True: 

854 try: 

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

856 break 

857 except TapCtl.CommandFailure as e: 

858 err = ( 

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

860 ) or None 

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

862 if retry_open < 5: 

863 retry_open += 1 

864 time.sleep(1) 

865 continue 

866 if LINSTOR_AVAILABLE and err == errno.EROFS: 

867 log_drbd_openers(path) 

868 raise 

869 try: 

870 tapdisk = cls.__from_blktap(blktap) 

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

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

873 return tapdisk 

874 except: 

875 TapCtl.close(pid, minor) 

876 raise 

877 

878 except: 

879 TapCtl.detach(pid, minor) 

880 raise 

881 

882 except: 

883 try: 

884 TapCtl.shutdown(pid) 

885 except: 

886 # Best effort to shutdown 

887 pass 

888 raise 

889 

890 except TapCtl.CommandFailure as ctl: 

891 util.logException(ctl) 

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

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

894 raise xs_errors.XenError('TapdiskDriveEmpty') 

895 else: 

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

897 

898 @classmethod 

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

900 blktap = Blktap.allocate() 

901 try: 

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

903 except: 

904 blktap.free() 

905 raise 

906 

907 def shutdown(self, force=False): 

908 

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

910 

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

912 

913 self.get_blktap().free() 

914 

915 def pause(self): 

916 

917 if not self.is_running(): 

918 raise TapdiskInvalidState(self) 

919 

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

921 

922 self._set_dirty() 

923 

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

925 

926 if not self.is_paused(): 

927 raise TapdiskInvalidState(self) 

928 

929 # FIXME: should the arguments be optional? 

930 if _type is None: 

931 _type = self.type 

932 if path is None: 

933 path = self.path 

934 

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

936 cbtlog=cbtlog) 

937 

938 self._set_dirty() 

939 

940 def stats(self): 

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

942 # 

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

944 # 

945 

946 def _set_dirty(self): 

947 self._dirty = True 

948 

949 def _refresh(self, __get): 

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

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

952 

953 @override 

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

955 def __get(name): 

956 # NB. avoid(rec(ursion) 

957 return object.__getattribute__(self, name) 

958 

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

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

961 self._refresh(__get) 

962 self._dirty = False 

963 

964 return __get(name) 

965 

966 class PauseState: 

967 RUNNING = 'R' 

968 PAUSING = 'r' 

969 PAUSED = 'P' 

970 

971 class Flags: 

972 DEAD = 0x0001 

973 CLOSED = 0x0002 

974 QUIESCE_REQUESTED = 0x0004 

975 QUIESCED = 0x0008 

976 PAUSE_REQUESTED = 0x0010 

977 PAUSED = 0x0020 

978 SHUTDOWN_REQUESTED = 0x0040 

979 LOCKING = 0x0080 

980 RETRY_NEEDED = 0x0100 

981 LOG_DROPPED = 0x0200 

982 

983 PAUSE_MASK = PAUSE_REQUESTED | PAUSED 

984 

985 def is_paused(self): 

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

987 

988 def is_running(self): 

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

990 

991 def pause_state(self): 

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

993 return self.PauseState.PAUSED 

994 

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

996 return self.PauseState.PAUSING 

997 

998 return self.PauseState.RUNNING 

999 

1000 @staticmethod 

1001 def _parse_minor(devpath): 

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

1003 pattern = re.compile(regex) 

1004 groups = pattern.search(devpath) 

1005 if not groups: 

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

1007 

1008 minor = groups.group(2) 

1009 return int(minor) 

1010 

1011 _major = None 

1012 

1013 @classmethod 

1014 def major(cls): 

1015 if cls._major: 

1016 return cls._major 

1017 

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

1019 for line in devices: 

1020 

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

1022 if len(row) != 2: 

1023 continue 

1024 

1025 major, name = row 

1026 if name != 'tapdev': 

1027 continue 

1028 

1029 cls._major = int(major) 

1030 break 

1031 

1032 devices.close() 

1033 return cls._major 

1034 

1035 

1036class VDI(object): 

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

1038 

1039 CONF_KEY_ALLOW_CACHING = "vdi_allow_caching" 

1040 CONF_KEY_MODE_ON_BOOT = "vdi_on_boot" 

1041 CONF_KEY_CACHE_SR = "local_cache_sr" 

1042 CONF_KEY_O_DIRECT = "o_direct" 

1043 LOCK_CACHE_SETUP = "cachesetup" 

1044 

1045 ATTACH_DETACH_RETRY_SECS = 120 

1046 

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

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

1049 self._vdi_uuid = uuid 

1050 self._session = target.session 

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

1052 self.__o_direct = None 

1053 self.__o_direct_reason = None 

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

1055 self.tap = None 

1056 

1057 def get_o_direct_capability(self, options): 

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

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

1060 return self.__o_direct, self.__o_direct_reason 

1061 

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

1063 self.__o_direct = True 

1064 self.__o_direct_reason = "LICENSE_RESTRICTION" 

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

1066 self.__o_direct = True 

1067 self.__o_direct_reason = "SR_NOT_SUPPORTED" 

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

1069 util.SMlog(self.target.vdi) 

1070 self.__o_direct = True 

1071 self.__o_direct_reason = "NO_RO_IMAGE" 

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

1073 self.__o_direct = True 

1074 self.__o_direct_reason = "RO_WITH_NO_PARENT" 

1075 elif options.get(self.CONF_KEY_O_DIRECT): 

1076 self.__o_direct = True 

1077 self.__o_direct_reason = "SR_OVERRIDE" 

1078 

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

1080 self.__o_direct = False 

1081 self.__o_direct_reason = "" 

1082 

1083 return self.__o_direct, self.__o_direct_reason 

1084 

1085 @classmethod 

1086 def from_cli(cls, uuid): 

1087 import VDI as sm 

1088 

1089 session = XenAPI.xapi_local() 

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

1091 

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

1093 driver_info = target.sr.srcmd.driver_info 

1094 

1095 session.xenapi.session.logout() 

1096 

1097 return cls(uuid, target, driver_info) 

1098 

1099 @staticmethod 

1100 def _tap_type(vdi_type): 

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

1102 return { 

1103 'raw': 'aio', 

1104 'vhd': 'vhd', 

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

1106 'aio': 'aio', # for LVHD 

1107 'file': 'aio', 

1108 'phy': 'aio' 

1109 }[vdi_type] 

1110 

1111 def get_tap_type(self): 

1112 vdi_type = self.target.get_vdi_type() 

1113 return VDI._tap_type(vdi_type) 

1114 

1115 def get_phy_path(self): 

1116 return self.target.get_vdi_path() 

1117 

1118 class UnexpectedVDIType(Exception): 

1119 

1120 def __init__(self, vdi_type, target): 

1121 self.vdi_type = vdi_type 

1122 self.target = target 

1123 

1124 @override 

1125 def __str__(self) -> str: 

1126 return \ 

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

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

1129 

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

1131 'raw': 'phy', 

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

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

1134 'file': 'tap', 

1135 'vhd': 'tap'} 

1136 

1137 def tap_wanted(self): 

1138 # 1. Let the target vdi_type decide 

1139 

1140 vdi_type = self.target.get_vdi_type() 

1141 

1142 try: 

1143 plug_type = self.VDI_PLUG_TYPE[vdi_type] 

1144 except KeyError: 

1145 raise self.UnexpectedVDIType(vdi_type, 

1146 self.target.vdi) 

1147 

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

1149 return True 

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

1151 return True 

1152 # 2. Otherwise, there may be more reasons 

1153 # 

1154 # .. TBD 

1155 

1156 return False 

1157 

1158 class TargetDriver: 

1159 """Safe target driver access.""" 

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

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

1162 # try/except would risk breaking compatibility. 

1163 

1164 def __init__(self, vdi, driver_info): 

1165 self.vdi = vdi 

1166 self._caps = driver_info['capabilities'] 

1167 

1168 def has_cap(self, cap): 

1169 """Determine if target has given capability""" 

1170 return cap in self._caps 

1171 

1172 def attach(self, sr_uuid, vdi_uuid): 

1173 #assert self.has_cap("VDI_ATTACH") 

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

1175 

1176 def detach(self, sr_uuid, vdi_uuid): 

1177 #assert self.has_cap("VDI_DETACH") 

1178 self.vdi.detach(sr_uuid, vdi_uuid) 

1179 

1180 def activate(self, sr_uuid, vdi_uuid): 

1181 if self.has_cap("VDI_ACTIVATE"): 

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

1183 

1184 def deactivate(self, sr_uuid, vdi_uuid): 

1185 if self.has_cap("VDI_DEACTIVATE"): 

1186 self.vdi.deactivate(sr_uuid, vdi_uuid) 

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

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

1189 

1190 def get_vdi_type(self): 

1191 _type = self.vdi.vdi_type 

1192 if not _type: 

1193 _type = self.vdi.sr.sr_vditype 

1194 if not _type: 

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

1196 return _type 

1197 

1198 def get_vdi_path(self): 

1199 return self.vdi.path 

1200 

1201 class Link(ABC): 

1202 """Relink a node under a common name""" 

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

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

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

1206 # relink existing devices under deterministic path names. 

1207 

1208 @property 

1209 @abstractmethod 

1210 def BASEDIR(self) -> str: 

1211 pass 

1212 

1213 @abstractmethod 

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

1215 pass 

1216 

1217 @abstractmethod 

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

1219 pass 

1220 

1221 def __init__(self, path): 

1222 self._path = path 

1223 

1224 @classmethod 

1225 def from_name(cls, name): 

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

1227 return cls(path) 

1228 

1229 @classmethod 

1230 def from_uuid(cls, sr_uuid, vdi_uuid): 

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

1232 return cls.from_name(name) 

1233 

1234 def path(self): 

1235 return self._path 

1236 

1237 def stat(self): 

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

1239 

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

1241 

1242 path = self.path() 

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

1244 

1245 mkdirs(os.path.dirname(path)) 

1246 try: 

1247 self._mklink(target) 

1248 except OSError as e: 

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

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

1251 # be seen. 

1252 if e.errno != errno.EEXIST: 

1253 raise 

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

1255 

1256 def unlink(self): 

1257 try: 

1258 os.unlink(self.path()) 

1259 except OSError as e: 

1260 if e.errno != errno.ENOENT: 

1261 raise 

1262 

1263 @override 

1264 def __str__(self) -> str: 

1265 path = self.path() 

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

1267 

1268 class SymLink(Link): 

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

1270 

1271 @property 

1272 @override 

1273 def BASEDIR(self) -> str: 

1274 return '' 

1275 

1276 def readlink(self): 

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

1278 

1279 def symlink(self): 

1280 return self.path() 

1281 

1282 @override 

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

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

1285 

1286 @override 

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

1288 return self.readlink() == target 

1289 

1290 class DeviceNode(Link): 

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

1292 

1293 @property 

1294 @override 

1295 def BASEDIR(self) -> str: 

1296 return '' 

1297 

1298 @classmethod 

1299 def _real_stat(cls, target): 

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

1301 _target = os.path.realpath(target) 

1302 return os.stat(_target) 

1303 

1304 @classmethod 

1305 def is_block(cls, target): 

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

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

1308 

1309 @override 

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

1311 

1312 st = self._real_stat(target) 

1313 if not S_ISBLK(st.st_mode): 

1314 raise self.NotABlockDevice(target, st) 

1315 

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

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

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

1319 

1320 @override 

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

1322 target_rdev = self._real_stat(target).st_rdev 

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

1324 

1325 def rdev(self): 

1326 st = self.stat() 

1327 assert S_ISBLK(st.st_mode) 

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

1329 

1330 class NotABlockDevice(Exception): 

1331 

1332 def __init__(self, path, st): 

1333 self.path = path 

1334 self.st = st 

1335 

1336 @override 

1337 def __str__(self) -> str: 

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

1339 

1340 class Hybrid(Link): 

1341 

1342 def __init__(self, path): 

1343 VDI.Link.__init__(self, path) 

1344 self._devnode = VDI.DeviceNode(path) 

1345 self._symlink = VDI.SymLink(path) 

1346 

1347 def rdev(self): 

1348 st = self.stat() 

1349 if S_ISBLK(st.st_mode): 

1350 return self._devnode.rdev() 

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

1352 

1353 @override 

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

1355 if self._devnode.is_block(target): 

1356 self._obj = self._devnode 

1357 else: 

1358 self._obj = self._symlink 

1359 self._obj.mklink(target) 

1360 

1361 @override 

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

1363 return self._obj._equals(target) 

1364 

1365 class PhyLink(SymLink): 

1366 @property 

1367 @override 

1368 def BASEDIR(self) -> str: 

1369 return '/dev/sm/phy' 

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

1371 

1372 class NBDLink(SymLink): 

1373 @property 

1374 @override 

1375 def BASEDIR(self) -> str: 

1376 return '/run/blktap-control/nbd' 

1377 

1378 class BackendLink(Hybrid): 

1379 @property 

1380 @override 

1381 def BASEDIR(self) -> str: 

1382 return '/dev/sm/backend' 

1383 

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

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

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

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

1388 # soon as ISOs are tapdisks. 

1389 

1390 @staticmethod 

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

1392 

1393 tapdisk = Tapdisk.find_by_path(phy_path) 

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

1395 blktap = Blktap.allocate() 

1396 blktap.set_pool_name(sr_uuid) 

1397 if pool_size: 

1398 blktap.set_pool_size(pool_size) 

1399 

1400 try: 

1401 tapdisk = \ 

1402 Tapdisk.launch_on_tap(blktap, 

1403 phy_path, 

1404 VDI._tap_type(vdi_type), 

1405 options) 

1406 except: 

1407 blktap.free() 

1408 raise 

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

1410 

1411 else: 

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

1413 

1414 return tapdisk.get_devpath(), tapdisk 

1415 

1416 @staticmethod 

1417 def _tap_deactivate(minor): 

1418 

1419 try: 

1420 tapdisk = Tapdisk.from_minor(minor) 

1421 except TapdiskNotRunning as e: 

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

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

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

1425 # the recorded minor. 

1426 else: 

1427 tapdisk.shutdown() 

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

1429 

1430 @classmethod 

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

1432 """ 

1433 Pauses the tapdisk. 

1434 

1435 session: a XAPI session 

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

1437 vdi_uuid: the UUID of the VDI to pause 

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

1439 non-blocking manner 

1440 """ 

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

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

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

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

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

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

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

1448 if not cls.call_pluginhandler(session, host_ref, 

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

1450 # Failed to pause node 

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

1452 return False 

1453 return True 

1454 

1455 @classmethod 

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

1457 activate_parents=False): 

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

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

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

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

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

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

1464 if not cls.call_pluginhandler(session, host_ref, 

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

1466 # Failed to unpause node 

1467 return False 

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

1469 return True 

1470 

1471 @classmethod 

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

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

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

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

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

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

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

1479 if not cls.call_pluginhandler(session, host_ref, 

1480 sr_uuid, vdi_uuid, "refresh", None, 

1481 activate_parents=activate_parents): 

1482 # Failed to refresh node 

1483 return False 

1484 return True 

1485 

1486 @classmethod 

1487 def tap_status(cls, session, vdi_uuid): 

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

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

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

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

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

1493 return True 

1494 return False 

1495 

1496 @classmethod 

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

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

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

1500 try: 

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

1502 "failfast": str(failfast)} 

1503 if secondary: 

1504 args["secondary"] = secondary 

1505 if activate_parents: 

1506 args["activate_parents"] = "true" 

1507 ret = session.xenapi.host.call_plugin( 

1508 host_ref, PLUGIN_TAP_PAUSE, action, 

1509 args) 

1510 return ret == "True" 

1511 except Exception as e: 

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

1513 return False 

1514 

1515 def _add_tag(self, vdi_uuid, writable): 

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

1517 attach_mode = "RO" 

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

1519 attach_mode = "RW" 

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

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

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

1523 attached_as = util.attached_as(sm_config) 

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

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

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

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

1528 term_output=False, writable=writable): 

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

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

1531 if 'relinking' in sm_config: 

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

1533 return False 

1534 if 'paused' in sm_config: 

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

1536 return False 

1537 self._session.xenapi.VDI.add_to_sm_config( 

1538 vdi_ref, 'activating', 'True') 

1539 host_key = "host_%s" % host_ref 

1540 assert host_key not in sm_config 

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

1542 attach_mode) 

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

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

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

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

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

1548 self._session.xenapi.VDI.remove_from_sm_config( 

1549 vdi_ref, 'activating') 

1550 return False 

1551 util.SMlog("Activate lock succeeded") 

1552 return True 

1553 

1554 def _check_tag(self, vdi_uuid): 

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

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

1557 if 'paused' in sm_config: 

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

1559 return False 

1560 return True 

1561 

1562 def _remove_tag(self, vdi_uuid): 

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

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

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

1566 host_key = "host_%s" % host_ref 

1567 if host_key in sm_config: 

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

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

1570 else: 

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

1572 

1573 def _get_pool_config(self, pool_name): 

1574 pool_info = dict() 

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

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

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

1578 # special pool 

1579 return pool_info 

1580 

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

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

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

1584 pool_size_str = sr_config.get(POOL_SIZE_KEY) 

1585 pool_name_override = vdi_config.get(POOL_NAME_KEY) 

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

1587 pool_name = pool_name_override 

1588 pool_size_override = vdi_config.get(POOL_SIZE_KEY) 

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

1590 pool_size_str = pool_size_override 

1591 pool_size = 0 

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

1593 try: 

1594 pool_size = int(pool_size_str) 

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

1596 raise ValueError("outside of range") 

1597 pool_size = NUM_PAGES_PER_RING * pool_size 

1598 except ValueError: 

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

1600 pool_size = 0 

1601 

1602 pool_info["mem-pool"] = pool_name 

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

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

1605 

1606 return pool_info 

1607 

1608 def linkNBD(self, sr_uuid, vdi_uuid): 

1609 if self.tap: 

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

1611 int(self.tap.minor)) 

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

1613 

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

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

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

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

1618 util.SMlog("Attach & activate") 

1619 self._attach(sr_uuid, vdi_uuid) 

1620 dev_path = self._activate(sr_uuid, vdi_uuid, 

1621 {"rdonly": not writable}) 

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

1623 self.linkNBD(sr_uuid, vdi_uuid) 

1624 

1625 # Return backend/ link 

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

1627 if self.tap_wanted(): 

1628 # Only have NBD if we also have a tap 

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

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

1631 vdi_uuid) 

1632 else: 

1633 nbd_path = "" 

1634 

1635 options = {"rdonly": not writable} 

1636 options.update(caching_params) 

1637 o_direct, o_direct_reason = self.get_o_direct_capability(options) 

1638 struct = {'params': back_path, 

1639 'params_nbd': nbd_path, 

1640 'o_direct': o_direct, 

1641 'o_direct_reason': o_direct_reason, 

1642 'xenstore_data': self.xenstore_data} 

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

1644 

1645 try: 

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

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

1648 f.close() 

1649 except: 

1650 pass 

1651 

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

1653 

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

1655 util.SMlog("blktap2.activate") 

1656 options = {"rdonly": not writable} 

1657 options.update(caching_params) 

1658 

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

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

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

1662 try: 

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

1664 return 

1665 except util.SRBusyException: 

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

1667 time.sleep(1) 

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

1669 

1670 @locking("VDIUnavailable") 

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

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

1673 

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

1675 refresh = False 

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

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

1678 return False 

1679 refresh = True 

1680 

1681 try: 

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

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

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

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

1686 # object completely 

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

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

1689 target.sr.srcmd.params = params 

1690 driver_info = target.sr.srcmd.driver_info 

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

1692 

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

1694 "blktap_activate_inject_failure", 

1695 lambda: util.inject_failure()) 

1696 

1697 # Attach the physical node 

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

1699 self._attach(sr_uuid, vdi_uuid) 

1700 

1701 vdi_type = self.target.get_vdi_type() 

1702 

1703 # Take lvchange-p Lock before running 

1704 # tap-ctl open 

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

1706 # now taking the same lock 

1707 # This is a fix for CA-155766 

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

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

1710 vdi_type == vhdutil.VDI_TYPE_VHD: 

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

1712 lock.acquire() 

1713 

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

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

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

1717 session = self.target.vdi.session 

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

1719 # pylint: disable=used-before-assignment 

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

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

1722 key_hash = sm_config['key_hash'] 

1723 options['key_hash'] = key_hash 

1724 options['vdi_uuid'] = vdi_uuid 

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

1726 # Activate the physical node 

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

1728 

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

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

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

1732 lock.release() 

1733 except: 

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

1735 if self.tap_wanted(): 

1736 util.fistpoint.activate_custom_fn( 

1737 "blktap_activate_error_handling", 

1738 lambda: time.sleep(30)) 

1739 while True: 

1740 try: 

1741 self._remove_tag(vdi_uuid) 

1742 break 

1743 except xmlrpc.client.ProtocolError as e: 

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

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

1746 continue 

1747 else: 

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

1749 break 

1750 except Exception as e: 

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

1752 break 

1753 raise 

1754 finally: 

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

1756 self._session.xenapi.VDI.remove_from_sm_config( 

1757 vdi_ref, 'activating') 

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

1759 

1760 # Link result to backend/ 

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

1762 self.linkNBD(sr_uuid, vdi_uuid) 

1763 return True 

1764 

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

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

1767 

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

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

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

1771 # Maybe launch a tapdisk on the physical link 

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

1773 vdi_type = self.target.get_vdi_type() 

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

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

1776 options.update(vdi_options) 

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

1778 sr_uuid, options, 

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

1780 else: 

1781 dev_path = phy_path # Just reuse phy 

1782 

1783 return dev_path 

1784 

1785 def _attach(self, sr_uuid, vdi_uuid): 

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

1787 params = attach_info['params'] 

1788 xenstore_data = attach_info['xenstore_data'] 

1789 phy_path = util.to_plain_string(params) 

1790 self.xenstore_data.update(xenstore_data) 

1791 # Save it to phy/ 

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

1793 

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

1795 util.SMlog("blktap2.deactivate") 

1796 for i in range(self.ATTACH_DETACH_RETRY_SECS): 

1797 try: 

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

1799 return 

1800 except util.SRBusyException as e: 

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

1802 time.sleep(1) 

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

1804 

1805 @locking("VDIUnavailable") 

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

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

1808 

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

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

1811 return False 

1812 

1813 self._deactivate(sr_uuid, vdi_uuid, caching_params) 

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

1815 self._detach(sr_uuid, vdi_uuid) 

1816 if self.tap_wanted(): 

1817 self._remove_tag(vdi_uuid) 

1818 

1819 return True 

1820 

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

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

1823 

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

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

1826 util.SMlog("Deactivate & detach") 

1827 self._deactivate(sr_uuid, vdi_uuid, caching_params) 

1828 self._detach(sr_uuid, vdi_uuid) 

1829 else: 

1830 pass # nothing to do 

1831 

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

1833 import VDI as sm 

1834 

1835 # Shutdown tapdisk 

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

1837 

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

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

1840 return 

1841 

1842 try: 

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

1844 os.unlink(attach_info_path) 

1845 except: 

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

1847 

1848 try: 

1849 major, minor = back_link.rdev() 

1850 except self.DeviceNode.NotABlockDevice: 

1851 pass 

1852 else: 

1853 if major == Tapdisk.major(): 

1854 self._tap_deactivate(minor) 

1855 self.remove_cache(sr_uuid, vdi_uuid, caching_params) 

1856 

1857 # Remove the backend link 

1858 back_link.unlink() 

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

1860 

1861 # Deactivate & detach the physical node 

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

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

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

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

1866 # object completely 

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

1868 driver_info = target.sr.srcmd.driver_info 

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

1870 

1871 self.target.deactivate(sr_uuid, vdi_uuid) 

1872 

1873 def _detach(self, sr_uuid, vdi_uuid): 

1874 self.target.detach(sr_uuid, vdi_uuid) 

1875 

1876 # Remove phy/ 

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

1878 

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

1880 # Remove existing VDI.sm_config fields 

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

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

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

1884 if not on_boot is None: 

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

1886 if not caching is None: 

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

1888 

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

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

1891 return 

1892 

1893 util.SMlog("Requested local caching") 

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

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

1896 return 

1897 

1898 scratch_mode = False 

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

1900 scratch_mode = True 

1901 util.SMlog("Requested scratch mode") 

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

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

1904 return 

1905 

1906 dev_path = None 

1907 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR) 

1908 if not local_sr_uuid: 

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

1910 return 

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

1912 local_sr_uuid, scratch_mode, params) 

1913 

1914 if dev_path: 

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

1916 params.get(self.CONF_KEY_MODE_ON_BOOT), 

1917 params.get(self.CONF_KEY_ALLOW_CACHING)) 

1918 

1919 return dev_path 

1920 

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

1922 vm_uuid = None 

1923 vm_label = "" 

1924 try: 

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

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

1927 cache_sr_label = cache_sr_rec.get("name_label") 

1928 

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

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

1931 host_label = host_rec.get("name_label") 

1932 

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

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

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

1936 for vbd_rec in vbds.values(): 

1937 vm_ref = vbd_rec.get("VM") 

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

1939 vm_uuid = vm_rec.get("uuid") 

1940 vm_label = vm_rec.get("name_label") 

1941 except: 

1942 util.logException("alert_no_cache") 

1943 

1944 alert_obj = "SR" 

1945 alert_uuid = str(cache_sr_uuid) 

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

1947 if vm_uuid: 

1948 alert_obj = "VM" 

1949 alert_uuid = vm_uuid 

1950 reason = "" 

1951 if err == errno.ENOSPC: 

1952 reason = "because there is no space left" 

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

1954 (vm_label, reason, cache_sr_label, host_label) 

1955 

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

1957 (alert_obj, alert_uuid, alert_str)) 

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

1959 alert_obj, alert_uuid, alert_str) 

1960 

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

1962 scratch_mode, options): 

1963 import SR 

1964 import EXTSR 

1965 import NFSSR 

1966 from lock import Lock 

1967 from FileSR import FileVDI 

1968 

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

1970 FileVDI.extractUuid) 

1971 if not parent_uuid: 

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

1973 self.target.vdi.uuid) 

1974 return 

1975 

1976 util.SMlog("Setting up cache") 

1977 parent_uuid = parent_uuid.strip() 

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

1979 

1980 if shared_target.parent: 

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

1982 shared_target.uuid) 

1983 return 

1984 

1985 SR.registerSR(EXTSR.EXTSR) 

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

1987 

1988 lock = Lock(self.LOCK_CACHE_SETUP, parent_uuid) 

1989 lock.acquire() 

1990 

1991 # read cache 

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

1993 if util.pathexists(read_cache_path): 

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

1995 read_cache_path) 

1996 else: 

1997 try: 

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

1999 except util.CommandException as e: 

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

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

2002 return None 

2003 

2004 # local write node 

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

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

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

2008 if util.pathexists(local_leaf_path): 

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

2010 local_leaf_path) 

2011 os.unlink(local_leaf_path) 

2012 try: 

2013 vhdutil.snapshot(local_leaf_path, read_cache_path, False, 

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

2015 except util.CommandException as e: 

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

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

2018 return None 

2019 

2020 local_leaf_size = vhdutil.getSizeVirt(local_leaf_path) 

2021 if leaf_size > local_leaf_size: 

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

2023 (leaf_size, local_leaf_size)) 

2024 vhdutil.setSizeVirtFast(local_leaf_path, leaf_size) 

2025 

2026 vdi_type = self.target.get_vdi_type() 

2027 

2028 prt_tapdisk = Tapdisk.find_by_path(read_cache_path) 

2029 if not prt_tapdisk: 

2030 parent_options = copy.deepcopy(options) 

2031 parent_options["rdonly"] = False 

2032 parent_options["lcache"] = True 

2033 

2034 blktap = Blktap.allocate() 

2035 try: 

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

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

2038 # its own pool 

2039 prt_tapdisk = \ 

2040 Tapdisk.launch_on_tap(blktap, read_cache_path, 

2041 'vhd', parent_options) 

2042 except: 

2043 blktap.free() 

2044 raise 

2045 

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

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

2048 

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

2050 leaf_tapdisk = Tapdisk.find_by_path(local_leaf_path) 

2051 if not leaf_tapdisk: 

2052 blktap = Blktap.allocate() 

2053 child_options = copy.deepcopy(options) 

2054 child_options["rdonly"] = False 

2055 child_options["lcache"] = False 

2056 child_options["existing_prt"] = prt_tapdisk.minor 

2057 child_options["secondary"] = secondary 

2058 child_options["standby"] = scratch_mode 

2059 try: 

2060 leaf_tapdisk = \ 

2061 Tapdisk.launch_on_tap(blktap, local_leaf_path, 

2062 'vhd', child_options) 

2063 except: 

2064 blktap.free() 

2065 raise 

2066 

2067 lock.release() 

2068 

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

2070 (read_cache_path, local_leaf_path)) 

2071 

2072 self.tap = leaf_tapdisk 

2073 return leaf_tapdisk.get_devpath() 

2074 

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

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

2077 return 

2078 

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

2080 

2081 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR) 

2082 if caching and not local_sr_uuid: 

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

2084 return 

2085 

2086 if caching: 

2087 self._remove_cache(self._session, local_sr_uuid) 

2088 

2089 if self._session is not None: 

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

2091 

2092 def _is_tapdisk_in_use(self, minor): 

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

2094 if not retVal: 

2095 # err on the side of caution 

2096 return True 

2097 

2098 for link in links: 

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

2100 return True 

2101 

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

2103 for s in sockets: 

2104 if socket_re.match(s): 

2105 return True 

2106 

2107 return False 

2108 

2109 def _remove_cache(self, session, local_sr_uuid): 

2110 import SR 

2111 import EXTSR 

2112 import NFSSR 

2113 from lock import Lock 

2114 from FileSR import FileVDI 

2115 

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

2117 FileVDI.extractUuid) 

2118 if not parent_uuid: 

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

2120 self.target.vdi.uuid) 

2121 return 

2122 

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

2124 

2125 parent_uuid = parent_uuid.strip() 

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

2127 

2128 SR.registerSR(EXTSR.EXTSR) 

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

2130 

2131 lock = Lock(self.LOCK_CACHE_SETUP, parent_uuid) 

2132 lock.acquire() 

2133 

2134 # local write node 

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

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

2137 if util.pathexists(local_leaf_path): 

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

2139 os.unlink(local_leaf_path) 

2140 

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

2142 prt_tapdisk = Tapdisk.find_by_path(read_cache_path) 

2143 if not prt_tapdisk: 

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

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

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

2147 read_cache_path) 

2148 try: 

2149 prt_tapdisk.shutdown() 

2150 except: 

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

2152 else: 

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

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

2155 # GC run 

2156 

2157 lock.release() 

2158 

2159PythonKeyError = KeyError 

2160 

2161 

2162class UEventHandler(object): 

2163 

2164 def __init__(self): 

2165 self._action = None 

2166 

2167 class KeyError(PythonKeyError): 

2168 def __init__(self, args): 

2169 super().__init__(args) 

2170 self.key = args[0] 

2171 

2172 @override 

2173 def __str__(self) -> str: 

2174 return \ 

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

2176 "Not called in udev context?" 

2177 

2178 @classmethod 

2179 def getenv(cls, key): 

2180 try: 

2181 return os.environ[key] 

2182 except KeyError as e: 

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

2184 

2185 def get_action(self): 

2186 if not self._action: 

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

2188 return self._action 

2189 

2190 class UnhandledEvent(Exception): 

2191 

2192 def __init__(self, event, handler): 

2193 self.event = event 

2194 self.handler = handler 

2195 

2196 @override 

2197 def __str__(self) -> str: 

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

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

2200 

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

2202 

2203 def run(self): 

2204 

2205 action = self.get_action() 

2206 try: 

2207 fn = self.ACTIONS[action] 

2208 except KeyError: 

2209 raise self.UnhandledEvent(action, self) 

2210 

2211 return fn(self) 

2212 

2213 @override 

2214 def __str__(self) -> str: 

2215 try: 

2216 action = self.get_action() 

2217 except: 

2218 action = None 

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

2220 

2221 

2222class __BlktapControl(ClassDevice): 

2223 @property 

2224 @override 

2225 def SYSFS_CLASSTYPE(self) -> str: 

2226 return 'misc' 

2227 

2228 def __init__(self): 

2229 ClassDevice.__init__(self) 

2230 self._default_pool = None 

2231 

2232 @override 

2233 def sysfs_devname(self) -> str: 

2234 return "blktap!control" 

2235 

2236 class DefaultPool(Attribute): 

2237 @property 

2238 @override 

2239 def SYSFS_NODENAME(self) -> str: 

2240 return 'default_pool' 

2241 

2242 def get_default_pool_attr(self): 

2243 if not self._default_pool: 

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

2245 return self._default_pool 

2246 

2247 def get_default_pool_name(self): 

2248 return self.get_default_pool_attr().readline() 

2249 

2250 def set_default_pool_name(self, name): 

2251 self.get_default_pool_attr().writeline(name) 

2252 

2253 def get_default_pool(self): 

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

2255 

2256 def set_default_pool(self, pool): 

2257 self.set_default_pool_name(pool.name) 

2258 

2259 class NoSuchPool(Exception): 

2260 def __init__(self, name): 

2261 self.name = name 

2262 

2263 @override 

2264 def __str__(self) -> str: 

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

2266 

2267 def get_pool(self, name): 

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

2269 

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

2271 raise self.NoSuchPool(name) 

2272 

2273 return PagePool(path) 

2274 

2275BlktapControl = __BlktapControl() 

2276 

2277 

2278class PagePool(KObject): 

2279 @property 

2280 @override 

2281 def SYSFS_CLASSTYPE(self) -> str: 

2282 return '' 

2283 

2284 def __init__(self, path): 

2285 self.path = path 

2286 self._size = None 

2287 

2288 @override 

2289 def sysfs_devname(self) -> str: 

2290 return '' 

2291 

2292 def sysfs_path(self): 

2293 return self.path 

2294 

2295 class Size(Attribute): 

2296 @property 

2297 @override 

2298 def SYSFS_NODENAME(self) -> str: 

2299 return 'size' 

2300 

2301 def get_size_attr(self): 

2302 if not self._size: 

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

2304 return self._size 

2305 

2306 def set_size(self, pages): 

2307 pages = str(pages) 

2308 self.get_size_attr().writeline(pages) 

2309 

2310 def get_size(self): 

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

2312 return int(pages) 

2313 

2314 

2315class BusDevice(KObject): 

2316 @property 

2317 @abstractmethod 

2318 def SYSFS_BUSTYPE(self) -> str: 

2319 pass 

2320 

2321 @classmethod 

2322 def sysfs_bus_path(cls): 

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

2324 

2325 def sysfs_path(self): 

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

2327 self.sysfs_devname()) 

2328 

2329 return path 

2330 

2331 

2332class XenbusDevice(BusDevice): 

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

2334 

2335 XBT_NIL = "" 

2336 

2337 @property 

2338 @abstractmethod 

2339 def XENBUS_DEVTYPE(self) -> str: 

2340 pass 

2341 

2342 def __init__(self, domid, devid): 

2343 self.domid = int(domid) 

2344 self.devid = int(devid) 

2345 self._xbt = XenbusDevice.XBT_NIL 

2346 

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

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

2349 

2350 def xs_path(self, key=None): 

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

2352 self.domid, 

2353 self.devid) 

2354 if key is not None: 

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

2356 

2357 return path 

2358 

2359 def _log(self, prio, msg): 

2360 syslog(prio, msg) 

2361 

2362 def info(self, msg): 

2363 self._log(_syslog.LOG_INFO, msg) 

2364 

2365 def warn(self, msg): 

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

2367 

2368 def _xs_read_path(self, path): 

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

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

2371 return val 

2372 

2373 def _xs_write_path(self, path, val): 

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

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

2376 

2377 def _xs_rm_path(self, path): 

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

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

2380 

2381 def read(self, key): 

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

2383 

2384 def has_xs_key(self, key): 

2385 return self.read(key) is not None 

2386 

2387 def write(self, key, val): 

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

2389 

2390 def rm(self, key): 

2391 self._xs_rm_path(self.xs_path(key)) 

2392 

2393 def exists(self): 

2394 return self.has_xs_key(None) 

2395 

2396 def begin(self): 

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

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

2399 

2400 def commit(self): 

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

2402 self._xbt = XenbusDevice.XBT_NIL 

2403 return ok 

2404 

2405 def abort(self): 

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

2407 assert(ok == True) 

2408 self._xbt = XenbusDevice.XBT_NIL 

2409 

2410 def create_physical_device(self): 

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

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

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

2414 return 

2415 try: 

2416 params = self.read("params") 

2417 frontend = self.read("frontend") 

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

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

2420 # from opening the physical-device 

2421 if not(is_cdrom): 

2422 major_minor = os.stat(params).st_rdev 

2423 major, minor = divmod(major_minor, 256) 

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

2425 except: 

2426 util.logException("BLKTAP2:create_physical_device") 

2427 

2428 def signal_hotplug(self, online=True): 

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

2430 self.XENBUS_DEVTYPE, 

2431 self.devid) 

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

2433 if online: 

2434 self._xs_write_path(xapi_path, "online") 

2435 self._xs_write_path(upstream_path, "connected") 

2436 else: 

2437 self._xs_rm_path(xapi_path) 

2438 self._xs_rm_path(upstream_path) 

2439 

2440 @override 

2441 def sysfs_devname(self) -> str: 

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

2443 self.domid, self.devid) 

2444 

2445 @override 

2446 def __str__(self) -> str: 

2447 return self.sysfs_devname() 

2448 

2449 @classmethod 

2450 def find(cls): 

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

2452 cls.XENBUS_DEVTYPE) 

2453 for path in glob.glob(pattern): 

2454 

2455 name = os.path.basename(path) 

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

2457 

2458 yield cls(domid, devid) 

2459 

2460 

2461class XenBackendDevice(XenbusDevice): 

2462 """Xenbus backend device""" 

2463 

2464 @property 

2465 @override 

2466 def SYSFS_BUSTYPE(self) -> str: 

2467 return 'xen-backend' 

2468 

2469 @classmethod 

2470 def from_xs_path(cls, _path): 

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

2472 

2473 assert _backend == 'backend' 

2474 assert _type == cls.XENBUS_DEVTYPE 

2475 

2476 domid = int(domid) 

2477 devid = int(devid) 

2478 

2479 return cls(domid, devid) 

2480 

2481 

2482class Blkback(XenBackendDevice): 

2483 """A blkback VBD""" 

2484 

2485 @property 

2486 @override 

2487 def XENBUS_DEVTYPE(self) -> str: 

2488 return 'vbd' 

2489 

2490 def __init__(self, domid, devid): 

2491 XenBackendDevice.__init__(self, domid, devid) 

2492 self._phy = None 

2493 self._vdi_uuid = None 

2494 self._q_state = None 

2495 self._q_events = None 

2496 

2497 class XenstoreValueError(Exception, metaclass=ABCMeta): 

2498 @property 

2499 @abstractmethod 

2500 def KEY(self) -> str: 

2501 pass 

2502 

2503 def __init__(self, vbd, _str): 

2504 self.vbd = vbd 

2505 self.str = _str 

2506 

2507 @override 

2508 def __str__(self) -> str: 

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

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

2511 

2512 class PhysicalDeviceError(XenstoreValueError): 

2513 @property 

2514 @override 

2515 def KEY(self) -> str: 

2516 return 'physical-device' 

2517 

2518 class PhysicalDevice(object): 

2519 

2520 def __init__(self, major, minor): 

2521 self.major = int(major) 

2522 self.minor = int(minor) 

2523 

2524 @classmethod 

2525 def from_xbdev(cls, xbdev): 

2526 

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

2528 

2529 try: 

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

2531 major = int(major, 0x10) 

2532 minor = int(minor, 0x10) 

2533 except Exception as e: 

2534 raise xbdev.PhysicalDeviceError(xbdev, phy) 

2535 

2536 return cls(major, minor) 

2537 

2538 def makedev(self): 

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

2540 

2541 def is_tap(self): 

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

2543 

2544 @override 

2545 def __str__(self) -> str: 

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

2547 

2548 @override 

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

2550 return \ 

2551 self.major == other.major and \ 

2552 self.minor == other.minor 

2553 

2554 def get_physical_device(self): 

2555 if not self._phy: 

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

2557 return self._phy 

2558 

2559 class QueueEvents(Attribute): 

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

2561 notifications emitted.""" 

2562 

2563 @property 

2564 @override 

2565 def SYSFS_NODENAME(self) -> str: 

2566 return 'queue_events' 

2567 

2568 QUEUE_RUNNING = (1 << 0) 

2569 QUEUE_PAUSE_DONE = (1 << 1) 

2570 QUEUE_SHUTDOWN_DONE = (1 << 2) 

2571 QUEUE_PAUSE_REQUEST = (1 << 3) 

2572 QUEUE_SHUTDOWN_REQUEST = (1 << 4) 

2573 

2574 def get_mask(self): 

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

2576 

2577 def set_mask(self, mask): 

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

2579 

2580 def get_queue_events(self): 

2581 if not self._q_events: 

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

2583 return self._q_events 

2584 

2585 def get_vdi_uuid(self): 

2586 if not self._vdi_uuid: 

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

2588 return self._vdi_uuid 

2589 

2590 def pause_requested(self): 

2591 return self.has_xs_key("pause") 

2592 

2593 def shutdown_requested(self): 

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

2595 

2596 def shutdown_done(self): 

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

2598 

2599 def running(self): 

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

2601 

2602 @classmethod 

2603 def find_by_physical_device(cls, phy): 

2604 for dev in cls.find(): 

2605 try: 

2606 _phy = dev.get_physical_device() 

2607 except cls.PhysicalDeviceError: 

2608 continue 

2609 

2610 if _phy == phy: 

2611 yield dev 

2612 

2613 @classmethod 

2614 def find_by_tap_minor(cls, minor): 

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

2616 return cls.find_by_physical_device(phy) 

2617 

2618 @classmethod 

2619 def find_by_tap(cls, tapdisk): 

2620 return cls.find_by_tap_minor(tapdisk.minor) 

2621 

2622 def has_tap(self): 

2623 

2624 if not self.can_tap(): 

2625 return False 

2626 

2627 phy = self.get_physical_device() 

2628 if phy: 

2629 return phy.is_tap() 

2630 

2631 return False 

2632 

2633 def is_bare_hvm(self): 

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

2635 try: 

2636 self.get_physical_device() 

2637 

2638 except self.PhysicalDeviceError as e: 

2639 vdi_type = self.read("type") 

2640 

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

2642 

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

2644 raise 

2645 

2646 return True 

2647 

2648 return False 

2649 

2650 def can_tap(self): 

2651 return not self.is_bare_hvm() 

2652 

2653 

2654class BlkbackEventHandler(UEventHandler): 

2655 

2656 LOG_FACILITY = _syslog.LOG_DAEMON 

2657 

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

2659 if not ident: 

2660 ident = self.__class__.__name__ 

2661 

2662 self.ident = ident 

2663 self._vbd = None 

2664 self._tapdisk = None 

2665 

2666 UEventHandler.__init__(self) 

2667 

2668 @override 

2669 def run(self) -> None: 

2670 

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

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

2673 

2674 UEventHandler.run(self) 

2675 

2676 @override 

2677 def __str__(self) -> str: 

2678 

2679 try: 

2680 path = self.xs_path 

2681 except: 

2682 path = None 

2683 

2684 try: 

2685 action = self.get_action() 

2686 except: 

2687 action = None 

2688 

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

2690 

2691 def _log(self, prio, msg): 

2692 syslog(prio, msg) 

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

2694 

2695 def info(self, msg): 

2696 self._log(_syslog.LOG_INFO, msg) 

2697 

2698 def warn(self, msg): 

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

2700 

2701 def error(self, msg): 

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

2703 

2704 def get_vbd(self): 

2705 if not self._vbd: 

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

2707 return self._vbd 

2708 

2709 def get_tapdisk(self): 

2710 if not self._tapdisk: 

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

2712 self._tapdisk = Tapdisk.from_minor(minor) 

2713 return self._tapdisk 

2714 # 

2715 # Events 

2716 # 

2717 

2718 def __add(self): 

2719 vbd = self.get_vbd() 

2720 # Manage blkback transitions 

2721 # self._manage_vbd() 

2722 

2723 vbd.create_physical_device() 

2724 

2725 vbd.signal_hotplug() 

2726 

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

2728 def add(self): 

2729 try: 

2730 self.__add() 

2731 except Attribute.NoSuchAttribute as e: 

2732 # 

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

2734 # registers device attributes. So poll a little. 

2735 # 

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

2737 raise RetryLoop.TransientFailure(e) 

2738 

2739 def __change(self): 

2740 vbd = self.get_vbd() 

2741 

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

2743 

2744 if vbd.has_tap(): 

2745 pass 

2746 #self._pause_update_tap() 

2747 

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

2749 

2750 self._signal_xapi() 

2751 

2752 def change(self): 

2753 vbd = self.get_vbd() 

2754 

2755 # NB. Beware of spurious change events between shutdown 

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

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

2758 

2759 while True: 

2760 vbd.begin() 

2761 

2762 if not vbd.exists() or \ 

2763 vbd.shutdown_done(): 

2764 break 

2765 

2766 self.__change() 

2767 

2768 if vbd.commit(): 

2769 return 

2770 

2771 vbd.abort() 

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

2773 

2774 def remove(self): 

2775 vbd = self.get_vbd() 

2776 

2777 vbd.signal_hotplug(False) 

2778 

2779 ACTIONS = {'add': add, 

2780 'change': change, 

2781 'remove': remove} 

2782 # 

2783 # VDI.pause 

2784 # 

2785 

2786 def _tap_should_pause(self): 

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

2788 paused""" 

2789 

2790 tapdisk = self.get_tapdisk() 

2791 TapState = Tapdisk.PauseState 

2792 

2793 PAUSED = 'P' 

2794 RUNNING = 'R' 

2795 PAUSED_SHUTDOWN = 'P,S' 

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

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

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

2799 

2800 next = TapState.RUNNING 

2801 vbds = {} 

2802 

2803 for vbd in Blkback.find_by_tap(tapdisk): 

2804 name = str(vbd) 

2805 

2806 pausing = vbd.pause_requested() 

2807 closing = vbd.shutdown_requested() 

2808 running = vbd.running() 

2809 

2810 if pausing: 

2811 if closing and not running: 

2812 vbds[name] = PAUSED_SHUTDOWN 

2813 else: 

2814 vbds[name] = PAUSED 

2815 next = TapState.PAUSED 

2816 

2817 else: 

2818 vbds[name] = RUNNING 

2819 

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

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

2822 vbds, next)) 

2823 

2824 return next == TapState.PAUSED 

2825 

2826 def _pause_update_tap(self): 

2827 vbd = self.get_vbd() 

2828 

2829 if self._tap_should_pause(): 

2830 self._pause_tap() 

2831 else: 

2832 self._resume_tap() 

2833 

2834 def _pause_tap(self): 

2835 tapdisk = self.get_tapdisk() 

2836 

2837 if not tapdisk.is_paused(): 

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

2839 tapdisk.pause() 

2840 

2841 def _resume_tap(self): 

2842 tapdisk = self.get_tapdisk() 

2843 

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

2845 # type while resuming. 

2846 vbd = self.get_vbd() 

2847 vdi_uuid = vbd.get_vdi_uuid() 

2848 

2849 if tapdisk.is_paused(): 

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

2851 vdi = VDI.from_cli(vdi_uuid) 

2852 _type = vdi.get_tap_type() 

2853 path = vdi.get_phy_path() 

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

2855 tapdisk.unpause(_type, path) 

2856 # 

2857 # VBD.pause/shutdown 

2858 # 

2859 

2860 def _manage_vbd(self): 

2861 vbd = self.get_vbd() 

2862 # NB. Hook into VBD state transitions. 

2863 

2864 events = vbd.get_queue_events() 

2865 

2866 mask = 0 

2867 mask |= events.QUEUE_PAUSE_DONE # pause/unpause 

2868 mask |= events.QUEUE_SHUTDOWN_DONE # shutdown 

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

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

2871 

2872 events.set_mask(mask) 

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

2874 

2875 def _signal_xapi(self): 

2876 vbd = self.get_vbd() 

2877 

2878 pausing = vbd.pause_requested() 

2879 closing = vbd.shutdown_requested() 

2880 running = vbd.running() 

2881 

2882 handled = 0 

2883 

2884 if pausing and not running: 

2885 if 'pause-done' not in vbd: 

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

2887 handled += 1 

2888 

2889 if not pausing: 

2890 if 'pause-done' in vbd: 

2891 vbd.rm('pause-done') 

2892 handled += 1 

2893 

2894 if closing and not running: 

2895 if 'shutdown-done' not in vbd: 

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

2897 handled += 1 

2898 

2899 if handled > 1: 

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

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

2902 (pausing, closing, running)) 

2903 

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

2905 

2906 import sys 

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

2908 

2909 # 

2910 # Simple CLI interface for manual operation 

2911 # 

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

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

2914 # 

2915 

2916 def usage(stream): 

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

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

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

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

2921 

2922 try: 

2923 cmd = sys.argv[1] 

2924 except IndexError: 

2925 usage(sys.stderr) 

2926 sys.exit(1) 

2927 

2928 try: 

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

2930 except: 

2931 usage(sys.stderr) 

2932 sys.exit(1) 

2933 

2934 # 

2935 # Local Tapdisks 

2936 # 

2937 

2938 if cmd == 'tap.major': 

2939 

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

2941 

2942 elif cmd == 'tap.launch': 

2943 

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

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

2946 

2947 elif _class == 'tap': 

2948 

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

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

2951 try: 

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

2953 attrs[key] = val 

2954 continue 

2955 except ValueError: 

2956 pass 

2957 

2958 try: 

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

2960 continue 

2961 except ValueError: 

2962 pass 

2963 

2964 try: 

2965 arg = Tapdisk.Arg.parse(item) 

2966 attrs['_type'] = arg.type 

2967 attrs['path'] = arg.path 

2968 continue 

2969 except Tapdisk.Arg.InvalidArgument: 

2970 pass 

2971 

2972 attrs['path'] = item 

2973 

2974 if cmd == 'tap.list': 

2975 

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

2977 blktap = tapdisk.get_blktap() 

2978 print(tapdisk, end=' ') 

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

2980 (blktap, 

2981 blktap.get_task_pid(), 

2982 blktap.get_pool_name())) 

2983 

2984 elif cmd == 'tap.vbds': 

2985 # Find all Blkback instances for a given tapdisk 

2986 

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

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

2989 for vbd in Blkback.find_by_tap(tapdisk): 

2990 print(vbd, end=' ') 

2991 print() 

2992 

2993 else: 

2994 

2995 if not attrs: 

2996 usage(sys.stderr) 

2997 sys.exit(1) 

2998 

2999 try: 

3000 tapdisk = Tapdisk.get( ** attrs) 

3001 except TypeError: 

3002 usage(sys.stderr) 

3003 sys.exit(1) 

3004 

3005 if cmd == 'tap.shutdown': 

3006 # Shutdown a running tapdisk, or raise 

3007 tapdisk.shutdown() 

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

3009 

3010 elif cmd == 'tap.pause': 

3011 # Pause an unpaused tapdisk, or raise 

3012 tapdisk.pause() 

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

3014 

3015 elif cmd == 'tap.unpause': 

3016 # Unpause a paused tapdisk, or raise 

3017 tapdisk.unpause() 

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

3019 

3020 elif cmd == 'tap.stats': 

3021 # Gather tapdisk status 

3022 stats = tapdisk.stats() 

3023 print("%s:" % tapdisk) 

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

3025 

3026 else: 

3027 usage(sys.stderr) 

3028 sys.exit(1) 

3029 

3030 elif cmd == 'vbd.uevent': 

3031 

3032 hnd = BlkbackEventHandler(cmd) 

3033 

3034 if not sys.stdin.isatty(): 

3035 try: 

3036 hnd.run() 

3037 except Exception as e: 

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

3039 

3040 import traceback 

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

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

3043 for entry in trace: 

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

3045 util.SMlog(line) 

3046 else: 

3047 hnd.run() 

3048 

3049 elif cmd == 'vbd.list': 

3050 

3051 for vbd in Blkback.find(): 

3052 print(vbd, \ 

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

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

3055 

3056 else: 

3057 usage(sys.stderr) 

3058 sys.exit(1)