Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#!/usr/bin/python3 

2# 

3# Copyright (C) Citrix Systems Inc. 

4# 

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

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

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

8# 

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

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

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

12# GNU Lesser General Public License for more details. 

13# 

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

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

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

17# 

18# blktap2: blktap/tapdisk management layer 

19# 

20 

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

22 

23from abc import abstractmethod 

24 

25import grp 

26import os 

27import re 

28import stat 

29import time 

30import copy 

31from lock import Lock 

32import util 

33import xmlrpc.client 

34import http.client 

35import errno 

36import signal 

37import subprocess 

38import syslog as _syslog 

39import glob 

40import json 

41import xs_errors 

42import XenAPI # pylint: disable=import-error 

43import scsiutil 

44from syslog import openlog, syslog 

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

46import nfs 

47 

48import resetvdis 

49import vhdutil 

50import lvhdutil 

51 

52import VDI as sm 

53 

54# For RRDD Plugin Registration 

55from xmlrpc.client import ServerProxy, Transport 

56from socket import socket, AF_UNIX, SOCK_STREAM 

57 

58try: 

59 from linstorvolumemanager import log_drbd_openers 

60 LINSTOR_AVAILABLE = True 

61except ImportError: 

62 LINSTOR_AVAILABLE = False 

63 

64PLUGIN_TAP_PAUSE = "tapdisk-pause" 

65 

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

67 

68NUM_PAGES_PER_RING = 32 * 11 

69MAX_FULL_RINGS = 8 

70POOL_NAME_KEY = "mem-pool" 

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

72 

73ENABLE_MULTIPLE_ATTACH = "/etc/xensource/allow_multiple_vdi_attach" 

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

75 

76 

77def locking(excType, override=True): 

78 def locking2(op): 

79 def wrapper(self, *args): 

80 self.lock.acquire() 

81 try: 

82 try: 

83 ret = op(self, * args) 

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

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

86 msg = str(e) 

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

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

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

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

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

99 return ret 

100 return wrapper 

101 return locking2 

102 

103 

104class RetryLoop(object): 

105 

106 def __init__(self, backoff, limit): 

107 self.backoff = backoff 

108 self.limit = limit 

109 

110 def __call__(self, f): 

111 

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

113 attempt = 0 

114 

115 while True: 

116 attempt += 1 

117 

118 try: 

119 return f( * __t, ** __d) 

120 

121 except self.TransientFailure as e: 

122 e = e.exception 

123 

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

125 raise e 

126 

127 time.sleep(self.backoff) 

128 

129 return loop 

130 

131 class TransientFailure(Exception): 

132 def __init__(self, exception): 

133 self.exception = exception 

134 

135 

136def retried(**args): 

137 return RetryLoop( ** args) 

138 

139 

140class TapCtl(object): 

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

142 

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

144 

145 def __init__(self, cmd, p): 

146 self.cmd = cmd 

147 self._p = p 

148 self.stdout = p.stdout 

149 

150 class CommandFailure(Exception): 

151 """TapCtl cmd failure.""" 

152 

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

154 self.cmd = cmd 

155 self.info = info 

156 

157 @override 

158 def __str__(self) -> str: 

159 items = self.info.items() 

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

161 for item in items) 

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

163 

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

165 # exception 

166 def __getattr__(self, key): 

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

168 return self.info[key] 

169 return object.__getattribute__(self, key) 

170 

171 @property 

172 def has_status(self): 

173 return 'status' in self.info 

174 

175 @property 

176 def has_signal(self): 

177 return 'signal' in self.info 

178 

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

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

181 def get_error_code(self): 

182 key = 'status' 

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

184 return self.info[key] 

185 else: 

186 return 0 

187 

188 @classmethod 

189 def __mkcmd_real(cls, args): 

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

191 

192 __next_mkcmd = __mkcmd_real 

193 

194 @classmethod 

195 def _mkcmd(cls, args): 

196 

197 __next_mkcmd = cls.__next_mkcmd 

198 cls.__next_mkcmd = cls.__mkcmd_real 

199 

200 return __next_mkcmd(args) 

201 

202 @classmethod 

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

204 """ 

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

206 Raises a TapCtl.CommandFailure if subprocess creation failed. 

207 """ 

208 cmd = cls._mkcmd(args) 

209 

210 if not quiet: 

211 util.SMlog(cmd) 

212 try: 

213 p = subprocess.Popen(cmd, 

214 stdin=subprocess.PIPE, 

215 stdout=subprocess.PIPE, 

216 stderr=subprocess.PIPE, 

217 close_fds=True, 

218 universal_newlines=text_mode) 

219 if input: 

220 p.stdin.write(input) 

221 p.stdin.close() 

222 except OSError as e: 

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

224 

225 return cls(cmd, p) 

226 

227 def _errmsg(self): 

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

229 return "; ".join(output) 

230 

231 def _wait(self, quiet=False): 

232 """ 

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

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

235 """ 

236 status = self._p.wait() 

237 if not quiet: 

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

239 

240 if status == 0: 

241 return 

242 

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

244 'pid': self._p.pid} 

245 

246 if status < 0: 

247 info['signal'] = -status 

248 else: 

249 info['status'] = status 

250 

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

252 

253 @classmethod 

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

255 """ 

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

257 """ 

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

259 text_mode=text_mode) 

260 

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

262 

263 tapctl._wait(quiet) 

264 return output 

265 

266 @staticmethod 

267 def _maybe(opt, parm): 

268 if parm is not None: 

269 return [opt, parm] 

270 return [] 

271 

272 @classmethod 

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

274 args = ["list"] 

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

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

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

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

279 

280 tapctl = cls._call(args, True) 

281 

282 for stdout_line in tapctl.stdout: 

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

284 # confuses this parser 

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

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

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

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

289 row = {} 

290 

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

292 bits = field.split('=') 

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

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

295 

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

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

298 

299 elif key in ('state'): 

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

301 

302 else: 

303 row[key] = val 

304 else: 

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

306 yield row 

307 

308 tapctl._wait(True) 

309 

310 @classmethod 

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

312 def list(cls, **args): 

313 

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

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

316 # be fixed in SM. 

317 

318 try: 

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

320 

321 except cls.CommandFailure as e: 

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

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

324 raise RetryLoop.TransientFailure(e) 

325 raise 

326 

327 @classmethod 

328 def allocate(cls, devpath=None): 

329 args = ["allocate"] 

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

331 return cls._pread(args) 

332 

333 @classmethod 

334 def free(cls, minor): 

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

336 cls._pread(args) 

337 

338 @classmethod 

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

340 def spawn(cls): 

341 args = ["spawn"] 

342 try: 

343 pid = cls._pread(args) 

344 return int(pid) 

345 except cls.CommandFailure as ce: 

346 # intermittent failures to spawn. CA-292268 

347 if ce.status == 1: 

348 raise RetryLoop.TransientFailure(ce) 

349 raise 

350 

351 @classmethod 

352 def attach(cls, pid, minor): 

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

354 cls._pread(args) 

355 

356 @classmethod 

357 def detach(cls, pid, minor): 

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

359 cls._pread(args) 

360 

361 @classmethod 

362 def _load_key(cls, key_hash, vdi_uuid): 

363 import plugins 

364 

365 return plugins.load_key(key_hash, vdi_uuid) 

366 

367 @classmethod 

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

369 params = Tapdisk.Arg(_type, _file) 

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

371 text_mode = True 

372 input = None 

373 if options.get("rdonly"): 

374 args.append('-R') 

375 if options.get("lcache"): 

376 args.append("-r") 

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

378 args.append("-e") 

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

380 if options.get("secondary"): 

381 args.append("-2") 

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

383 if options.get("standby"): 

384 args.append("-s") 

385 if options.get("timeout"): 

386 args.append("-t") 

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

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

389 args.append("-D") 

390 if options.get('cbtlog'): 

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

392 if options.get('key_hash'): 

393 key_hash = options['key_hash'] 

394 vdi_uuid = options['vdi_uuid'] 

395 key = cls._load_key(key_hash, vdi_uuid) 

396 

397 if not key: 

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

399 input = key 

400 text_mode = False 

401 args.append('-E') 

402 

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

404 

405 @classmethod 

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

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

408 if force: 

409 args += ["-f"] 

410 cls._pread(args) 

411 

412 @classmethod 

413 def pause(cls, pid, minor): 

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

415 cls._pread(args) 

416 

417 @classmethod 

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

419 cbtlog=None): 

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

421 if mirror: 

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

423 if _type and _file: 

424 params = Tapdisk.Arg(_type, _file) 

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

426 if cbtlog: 

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

428 cls._pread(args) 

429 

430 @classmethod 

431 def shutdown(cls, pid): 

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

433 os.kill(pid, signal.SIGTERM) 

434 os.waitpid(pid, 0) 

435 

436 @classmethod 

437 def stats(cls, pid, minor): 

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

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

440 

441 @classmethod 

442 def major(cls): 

443 args = ["major"] 

444 major = cls._pread(args) 

445 return int(major) 

446 

447 

448class TapdiskExists(Exception): 

449 """Tapdisk already running.""" 

450 

451 def __init__(self, tapdisk): 

452 self.tapdisk = tapdisk 

453 

454 @override 

455 def __str__(self) -> str: 

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

457 

458 

459class TapdiskNotRunning(Exception): 

460 """No such Tapdisk.""" 

461 

462 def __init__(self, **attrs): 

463 self.attrs = attrs 

464 

465 @override 

466 def __str__(self) -> str: 

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

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

469 for attr in items) 

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

471 

472 

473class TapdiskNotUnique(Exception): 

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

475 

476 def __init__(self, tapdisks): 

477 self.tapdisks = tapdisks 

478 

479 @override 

480 def __str__(self) -> str: 

481 tapdisks = map(str, self.tapdisks) 

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

483 

484 

485class TapdiskFailed(Exception): 

486 """Tapdisk launch failure.""" 

487 

488 def __init__(self, arg, err): 

489 self.arg = arg 

490 self.err = err 

491 

492 @override 

493 def __str__(self) -> str: 

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

495 

496 def get_error(self): 

497 return self.err 

498 

499 

500class TapdiskInvalidState(Exception): 

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

502 

503 def __init__(self, tapdisk): 

504 self.tapdisk = tapdisk 

505 

506 @override 

507 def __str__(self) -> str: 

508 return str(self.tapdisk) 

509 

510 

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

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

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

514 assert parent != path 

515 try: 

516 if parent: 

517 mkdirs(parent, mode) 

518 if subdir: 

519 os.mkdir(path, mode) 

520 except OSError as e: 

521 if e.errno != errno.EEXIST: 

522 raise 

523 

524 

525class KObject(object): 

526 

527 SYSFS_CLASSTYPE: ClassVar[str] = "" 

528 

529 @abstractmethod 

530 def sysfs_devname(self) -> str: 

531 pass 

532 

533 

534class Attribute(object): 

535 

536 SYSFS_NODENAME: ClassVar[str] = "" 

537 

538 def __init__(self, path): 

539 self.path = path 

540 

541 @classmethod 

542 def from_kobject(cls, kobj): 

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

544 return cls(path) 

545 

546 class NoSuchAttribute(Exception): 

547 def __init__(self, name): 

548 self.name = name 

549 

550 @override 

551 def __str__(self) -> str: 

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

553 

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

555 try: 

556 return open(self.path, mode) 

557 except IOError as e: 

558 if e.errno == errno.ENOENT: 

559 raise self.NoSuchAttribute(self) 

560 raise 

561 

562 def readline(self): 

563 f = self._open('r') 

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

565 f.close() 

566 return s 

567 

568 def writeline(self, val): 

569 f = self._open('w') 

570 f.write(val) 

571 f.close() 

572 

573 

574class ClassDevice(KObject): 

575 

576 @classmethod 

577 def sysfs_class_path(cls): 

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

579 

580 def sysfs_path(self): 

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

582 self.sysfs_devname()) 

583 

584 

585class Blktap(ClassDevice): 

586 

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

588 

589 SYSFS_CLASSTYPE = "blktap2" 

590 

591 def __init__(self, minor): 

592 self.minor = minor 

593 self._pool = None 

594 self._task = None 

595 

596 @classmethod 

597 def allocate(cls): 

598 # FIXME. Should rather go into init. 

599 mkdirs(cls.DEV_BASEDIR) 

600 

601 devname = TapCtl.allocate() 

602 minor = Tapdisk._parse_minor(devname) 

603 return cls(minor) 

604 

605 def free(self): 

606 TapCtl.free(self.minor) 

607 

608 @override 

609 def __str__(self) -> str: 

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

611 

612 @override 

613 def sysfs_devname(self) -> str: 

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

615 

616 class Pool(Attribute): 

617 SYSFS_NODENAME = "pool" 

618 

619 def get_pool_attr(self): 

620 if not self._pool: 

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

622 return self._pool 

623 

624 def get_pool_name(self): 

625 return self.get_pool_attr().readline() 

626 

627 def set_pool_name(self, name): 

628 self.get_pool_attr().writeline(name) 

629 

630 def set_pool_size(self, pages): 

631 self.get_pool().set_size(pages) 

632 

633 def get_pool(self): 

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

635 

636 def set_pool(self, pool): 

637 self.set_pool_name(pool.name) 

638 

639 class Task(Attribute): 

640 SYSFS_NODENAME = "task" 

641 

642 def get_task_attr(self): 

643 if not self._task: 

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

645 return self._task 

646 

647 def get_task_pid(self): 

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

649 try: 

650 return int(pid) 

651 except ValueError: 

652 return None 

653 

654 def find_tapdisk(self): 

655 pid = self.get_task_pid() 

656 if pid is None: 

657 return None 

658 

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

660 

661 def get_tapdisk(self): 

662 tapdisk = self.find_tapdisk() 

663 if not tapdisk: 

664 raise TapdiskNotRunning(minor=self.minor) 

665 return tapdisk 

666 

667 

668class Tapdisk(object): 

669 

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

671 

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

673 self.pid = pid 

674 self.minor = minor 

675 self.type = _type 

676 self.path = path 

677 self.state = state 

678 self._dirty = False 

679 self._blktap = None 

680 

681 @override 

682 def __str__(self) -> str: 

683 state = self.pause_state() 

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

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

686 

687 @classmethod 

688 def list(cls, **args): 

689 

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

691 

692 args = {'pid': None, 

693 'minor': None, 

694 'state': None, 

695 '_type': None, 

696 'path': None} 

697 

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

699 if key in args: 

700 args[key] = val 

701 

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

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

704 args['_type'] = image.type 

705 args['path'] = image.path 

706 

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

708 continue 

709 

710 yield Tapdisk( ** args) 

711 

712 @classmethod 

713 def find(cls, **args): 

714 

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

716 

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

718 raise TapdiskNotUnique(found) 

719 

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

721 return found[0] 

722 

723 return None 

724 

725 @classmethod 

726 def find_by_path(cls, path): 

727 return cls.find(path=path) 

728 

729 @classmethod 

730 def find_by_minor(cls, minor): 

731 return cls.find(minor=minor) 

732 

733 @classmethod 

734 def get(cls, **attrs): 

735 

736 tapdisk = cls.find( ** attrs) 

737 

738 if not tapdisk: 

739 raise TapdiskNotRunning( ** attrs) 

740 

741 return tapdisk 

742 

743 @classmethod 

744 def from_path(cls, path): 

745 return cls.get(path=path) 

746 

747 @classmethod 

748 def from_minor(cls, minor): 

749 return cls.get(minor=minor) 

750 

751 @classmethod 

752 def __from_blktap(cls, blktap): 

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

754 tapdisk._blktap = blktap 

755 return tapdisk 

756 

757 def get_blktap(self): 

758 if not self._blktap: 

759 self._blktap = Blktap(self.minor) 

760 return self._blktap 

761 

762 class Arg: 

763 

764 def __init__(self, _type, path): 

765 self.type = _type 

766 self.path = path 

767 

768 @override 

769 def __str__(self) -> str: 

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

771 

772 @classmethod 

773 def parse(cls, arg): 

774 

775 try: 

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

777 except ValueError: 

778 raise cls.InvalidArgument(arg) 

779 

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

781 raise cls.InvalidType(_type) 

782 

783 return cls(_type, path) 

784 

785 class InvalidType(Exception): 

786 def __init__(self, _type): 

787 self.type = _type 

788 

789 @override 

790 def __str__(self) -> str: 

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

792 

793 class InvalidArgument(Exception): 

794 def __init__(self, arg): 

795 self.arg = arg 

796 

797 @override 

798 def __str__(self) -> str: 

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

800 

801 def get_arg(self): 

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

803 

804 def get_devpath(self): 

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

806 

807 @classmethod 

808 def launch_from_arg(cls, arg): 

809 arg = cls.Arg.parse(arg) 

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

811 

812 @staticmethod 

813 def cgclassify(pid): 

814 

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

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

817 # we have configured in the spec file. 

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

819 try: 

820 util.pread2(cmd) 

821 except util.CommandException as e: 

822 util.logException(e) 

823 

824 @classmethod 

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

826 

827 tapdisk = cls.find_by_path(path) 

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

829 raise TapdiskExists(tapdisk) 

830 

831 minor = blktap.minor 

832 try: 

833 pid = TapCtl.spawn() 

834 cls.cgclassify(pid) 

835 try: 

836 TapCtl.attach(pid, minor) 

837 

838 try: 

839 retry_open = 0 

840 while True: 

841 try: 

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

843 break 

844 except TapCtl.CommandFailure as e: 

845 err = ( 

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

847 ) or None 

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

849 if retry_open < 5: 

850 retry_open += 1 

851 time.sleep(1) 

852 continue 

853 if LINSTOR_AVAILABLE and err == errno.EROFS: 

854 log_drbd_openers(path) 

855 raise 

856 try: 

857 tapdisk = cls.__from_blktap(blktap) 

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

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

860 return tapdisk 

861 except: 

862 TapCtl.close(pid, minor) 

863 raise 

864 

865 except: 

866 TapCtl.detach(pid, minor) 

867 raise 

868 

869 except: 

870 try: 

871 TapCtl.shutdown(pid) 

872 except: 

873 # Best effort to shutdown 

874 pass 

875 raise 

876 

877 except TapCtl.CommandFailure as ctl: 

878 util.logException(ctl) 

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

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

881 raise xs_errors.XenError('TapdiskDriveEmpty') 

882 else: 

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

884 

885 @classmethod 

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

887 blktap = Blktap.allocate() 

888 try: 

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

890 except: 

891 blktap.free() 

892 raise 

893 

894 def shutdown(self, force=False): 

895 

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

897 

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

899 

900 self.get_blktap().free() 

901 

902 def pause(self): 

903 

904 if not self.is_running(): 

905 raise TapdiskInvalidState(self) 

906 

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

908 

909 self._set_dirty() 

910 

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

912 

913 if not self.is_paused(): 

914 raise TapdiskInvalidState(self) 

915 

916 # FIXME: should the arguments be optional? 

917 if _type is None: 

918 _type = self.type 

919 if path is None: 

920 path = self.path 

921 

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

923 cbtlog=cbtlog) 

924 

925 self._set_dirty() 

926 

927 def stats(self): 

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

929 # 

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

931 # 

932 

933 def _set_dirty(self): 

934 self._dirty = True 

935 

936 def _refresh(self, __get): 

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

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

939 

940 @override 

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

942 def __get(name): 

943 # NB. avoid(rec(ursion) 

944 return object.__getattribute__(self, name) 

945 

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

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

948 self._refresh(__get) 

949 self._dirty = False 

950 

951 return __get(name) 

952 

953 class PauseState: 

954 RUNNING = 'R' 

955 PAUSING = 'r' 

956 PAUSED = 'P' 

957 

958 class Flags: 

959 DEAD = 0x0001 

960 CLOSED = 0x0002 

961 QUIESCE_REQUESTED = 0x0004 

962 QUIESCED = 0x0008 

963 PAUSE_REQUESTED = 0x0010 

964 PAUSED = 0x0020 

965 SHUTDOWN_REQUESTED = 0x0040 

966 LOCKING = 0x0080 

967 RETRY_NEEDED = 0x0100 

968 LOG_DROPPED = 0x0200 

969 

970 PAUSE_MASK = PAUSE_REQUESTED | PAUSED 

971 

972 def is_paused(self): 

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

974 

975 def is_running(self): 

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

977 

978 def pause_state(self): 

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

980 return self.PauseState.PAUSED 

981 

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

983 return self.PauseState.PAUSING 

984 

985 return self.PauseState.RUNNING 

986 

987 @staticmethod 

988 def _parse_minor(devpath): 

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

990 pattern = re.compile(regex) 

991 groups = pattern.search(devpath) 

992 if not groups: 

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

994 

995 minor = groups.group(2) 

996 return int(minor) 

997 

998 _major = None 

999 

1000 @classmethod 

1001 def major(cls): 

1002 if cls._major: 

1003 return cls._major 

1004 

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

1006 for line in devices: 

1007 

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

1009 if len(row) != 2: 

1010 continue 

1011 

1012 major, name = row 

1013 if name != 'tapdev': 

1014 continue 

1015 

1016 cls._major = int(major) 

1017 break 

1018 

1019 devices.close() 

1020 return cls._major 

1021 

1022 

1023class VDI(object): 

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

1025 

1026 CONF_KEY_ALLOW_CACHING = "vdi_allow_caching" 

1027 CONF_KEY_MODE_ON_BOOT = "vdi_on_boot" 

1028 CONF_KEY_CACHE_SR = "local_cache_sr" 

1029 CONF_KEY_O_DIRECT = "o_direct" 

1030 LOCK_CACHE_SETUP = "cachesetup" 

1031 

1032 ATTACH_DETACH_RETRY_SECS = 120 

1033 

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

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

1036 self._vdi_uuid = uuid 

1037 self._session = target.session 

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

1039 self.__o_direct = None 

1040 self.__o_direct_reason = None 

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

1042 self.tap = None 

1043 

1044 def get_o_direct_capability(self, options): 

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

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

1047 return self.__o_direct, self.__o_direct_reason 

1048 

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

1050 self.__o_direct = True 

1051 self.__o_direct_reason = "LICENSE_RESTRICTION" 

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

1053 self.__o_direct = True 

1054 self.__o_direct_reason = "SR_NOT_SUPPORTED" 

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

1056 self.__o_direct = True 

1057 self.__o_direct_reason = "RO_WITH_NO_PARENT" 

1058 elif options.get(self.CONF_KEY_O_DIRECT): 

1059 self.__o_direct = True 

1060 self.__o_direct_reason = "SR_OVERRIDE" 

1061 

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

1063 self.__o_direct = False 

1064 self.__o_direct_reason = "" 

1065 

1066 return self.__o_direct, self.__o_direct_reason 

1067 

1068 @classmethod 

1069 def from_cli(cls, uuid): 

1070 import VDI as sm 

1071 

1072 session = XenAPI.xapi_local() 

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

1074 

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

1076 driver_info = target.sr.srcmd.driver_info 

1077 

1078 session.xenapi.session.logout() 

1079 

1080 return cls(uuid, target, driver_info) 

1081 

1082 @staticmethod 

1083 def _tap_type(vdi_type): 

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

1085 return { 

1086 'raw': 'aio', 

1087 'vhd': 'vhd', 

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

1089 'aio': 'aio', # for LVHD 

1090 'file': 'aio', 

1091 'phy': 'aio' 

1092 }[vdi_type] 

1093 

1094 def get_tap_type(self): 

1095 vdi_type = self.target.get_vdi_type() 

1096 return VDI._tap_type(vdi_type) 

1097 

1098 def get_phy_path(self): 

1099 return self.target.get_vdi_path() 

1100 

1101 class UnexpectedVDIType(Exception): 

1102 

1103 def __init__(self, vdi_type, target): 

1104 self.vdi_type = vdi_type 

1105 self.target = target 

1106 

1107 @override 

1108 def __str__(self) -> str: 

1109 return \ 

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

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

1112 

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

1114 'raw': 'phy', 

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

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

1117 'file': 'tap', 

1118 'vhd': 'tap'} 

1119 

1120 def tap_wanted(self): 

1121 # 1. Let the target vdi_type decide 

1122 

1123 vdi_type = self.target.get_vdi_type() 

1124 

1125 try: 

1126 plug_type = self.VDI_PLUG_TYPE[vdi_type] 

1127 except KeyError: 

1128 raise self.UnexpectedVDIType(vdi_type, 

1129 self.target.vdi) 

1130 

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

1132 return True 

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

1134 return True 

1135 # 2. Otherwise, there may be more reasons 

1136 # 

1137 # .. TBD 

1138 

1139 return False 

1140 

1141 class TargetDriver: 

1142 """Safe target driver access.""" 

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

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

1145 # try/except would risk breaking compatibility. 

1146 

1147 def __init__(self, vdi, driver_info): 

1148 self.vdi = vdi 

1149 self._caps = driver_info['capabilities'] 

1150 

1151 def has_cap(self, cap): 

1152 """Determine if target has given capability""" 

1153 return cap in self._caps 

1154 

1155 def attach(self, sr_uuid, vdi_uuid): 

1156 #assert self.has_cap("VDI_ATTACH") 

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

1158 

1159 def detach(self, sr_uuid, vdi_uuid): 

1160 #assert self.has_cap("VDI_DETACH") 

1161 self.vdi.detach(sr_uuid, vdi_uuid) 

1162 

1163 def activate(self, sr_uuid, vdi_uuid): 

1164 if self.has_cap("VDI_ACTIVATE"): 

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

1166 

1167 def deactivate(self, sr_uuid, vdi_uuid): 

1168 if self.has_cap("VDI_DEACTIVATE"): 

1169 self.vdi.deactivate(sr_uuid, vdi_uuid) 

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

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

1172 

1173 def get_vdi_type(self): 

1174 _type = self.vdi.vdi_type 

1175 if not _type: 

1176 _type = self.vdi.sr.sr_vditype 

1177 if not _type: 

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

1179 return _type 

1180 

1181 def get_vdi_path(self): 

1182 return self.vdi.path 

1183 

1184 class Link(object): 

1185 """Relink a node under a common name""" 

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

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

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

1189 # relink existing devices under deterministic path names. 

1190 

1191 BASEDIR: ClassVar[str] = "" 

1192 

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

1194 pass 

1195 

1196 @abstractmethod 

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

1198 pass 

1199 

1200 def __init__(self, path): 

1201 self._path = path 

1202 

1203 @classmethod 

1204 def from_name(cls, name): 

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

1206 return cls(path) 

1207 

1208 @classmethod 

1209 def from_uuid(cls, sr_uuid, vdi_uuid): 

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

1211 return cls.from_name(name) 

1212 

1213 def path(self): 

1214 return self._path 

1215 

1216 def stat(self): 

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

1218 

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

1220 

1221 path = self.path() 

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

1223 

1224 mkdirs(os.path.dirname(path)) 

1225 try: 

1226 self._mklink(target) 

1227 except OSError as e: 

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

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

1230 # be seen. 

1231 if e.errno != errno.EEXIST: 

1232 raise 

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

1234 

1235 def unlink(self): 

1236 try: 

1237 os.unlink(self.path()) 

1238 except OSError as e: 

1239 if e.errno != errno.ENOENT: 

1240 raise 

1241 

1242 @override 

1243 def __str__(self) -> str: 

1244 path = self.path() 

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

1246 

1247 class SymLink(Link): 

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

1249 

1250 def readlink(self): 

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

1252 

1253 def symlink(self): 

1254 return self.path() 

1255 

1256 @override 

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

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

1259 

1260 @override 

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

1262 return self.readlink() == target 

1263 

1264 class DeviceNode(Link): 

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

1266 

1267 @classmethod 

1268 def _real_stat(cls, target): 

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

1270 _target = os.path.realpath(target) 

1271 return os.stat(_target) 

1272 

1273 @classmethod 

1274 def is_block(cls, target): 

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

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

1277 

1278 @override 

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

1280 

1281 st = self._real_stat(target) 

1282 if not S_ISBLK(st.st_mode): 

1283 raise self.NotABlockDevice(target, st) 

1284 

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

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

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

1288 

1289 @override 

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

1291 target_rdev = self._real_stat(target).st_rdev 

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

1293 

1294 def rdev(self): 

1295 st = self.stat() 

1296 assert S_ISBLK(st.st_mode) 

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

1298 

1299 class NotABlockDevice(Exception): 

1300 

1301 def __init__(self, path, st): 

1302 self.path = path 

1303 self.st = st 

1304 

1305 @override 

1306 def __str__(self) -> str: 

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

1308 

1309 class Hybrid(Link): 

1310 

1311 def __init__(self, path): 

1312 VDI.Link.__init__(self, path) 

1313 self._devnode = VDI.DeviceNode(path) 

1314 self._symlink = VDI.SymLink(path) 

1315 

1316 def rdev(self): 

1317 st = self.stat() 

1318 if S_ISBLK(st.st_mode): 

1319 return self._devnode.rdev() 

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

1321 

1322 @override 

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

1324 if self._devnode.is_block(target): 

1325 self._obj = self._devnode 

1326 else: 

1327 self._obj = self._symlink 

1328 self._obj.mklink(target) 

1329 

1330 @override 

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

1332 return self._obj._equals(target) 

1333 

1334 class PhyLink(SymLink): 

1335 BASEDIR = "/dev/sm/phy" 

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

1337 

1338 class NBDLink(SymLink): 

1339 

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

1341 

1342 class BackendLink(Hybrid): 

1343 BASEDIR = "/dev/sm/backend" 

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

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

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

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

1348 # soon as ISOs are tapdisks. 

1349 

1350 @staticmethod 

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

1352 

1353 tapdisk = Tapdisk.find_by_path(phy_path) 

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

1355 blktap = Blktap.allocate() 

1356 blktap.set_pool_name(sr_uuid) 

1357 if pool_size: 

1358 blktap.set_pool_size(pool_size) 

1359 

1360 try: 

1361 tapdisk = \ 

1362 Tapdisk.launch_on_tap(blktap, 

1363 phy_path, 

1364 VDI._tap_type(vdi_type), 

1365 options) 

1366 except: 

1367 blktap.free() 

1368 raise 

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

1370 

1371 else: 

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

1373 

1374 return tapdisk.get_devpath(), tapdisk 

1375 

1376 @staticmethod 

1377 def _tap_deactivate(minor): 

1378 

1379 try: 

1380 tapdisk = Tapdisk.from_minor(minor) 

1381 except TapdiskNotRunning as e: 

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

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

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

1385 # the recorded minor. 

1386 else: 

1387 tapdisk.shutdown() 

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

1389 

1390 @classmethod 

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

1392 """ 

1393 Pauses the tapdisk. 

1394 

1395 session: a XAPI session 

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

1397 vdi_uuid: the UUID of the VDI to pause 

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

1399 non-blocking manner 

1400 """ 

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

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

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

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

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

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

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

1408 if not cls.call_pluginhandler(session, host_ref, 

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

1410 # Failed to pause node 

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

1412 return False 

1413 return True 

1414 

1415 @classmethod 

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

1417 activate_parents=False): 

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

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

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

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

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

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

1424 if not cls.call_pluginhandler(session, host_ref, 

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

1426 # Failed to unpause node 

1427 return False 

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

1429 return True 

1430 

1431 @classmethod 

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

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

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

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

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

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

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

1439 if not cls.call_pluginhandler(session, host_ref, 

1440 sr_uuid, vdi_uuid, "refresh", None, 

1441 activate_parents=activate_parents): 

1442 # Failed to refresh node 

1443 return False 

1444 return True 

1445 

1446 @classmethod 

1447 def tap_status(cls, session, vdi_uuid): 

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

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

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

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

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

1453 return True 

1454 return False 

1455 

1456 @classmethod 

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

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

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

1460 try: 

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

1462 "failfast": str(failfast)} 

1463 if secondary: 

1464 args["secondary"] = secondary 

1465 if activate_parents: 

1466 args["activate_parents"] = "true" 

1467 ret = session.xenapi.host.call_plugin( 

1468 host_ref, PLUGIN_TAP_PAUSE, action, 

1469 args) 

1470 return ret == "True" 

1471 except Exception as e: 

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

1473 return False 

1474 

1475 def _add_tag(self, vdi_uuid, writable): 

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

1477 attach_mode = "RO" 

1478 if writable: 

1479 attach_mode = "RW" 

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

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

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

1483 attached_as = util.attached_as(sm_config) 

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

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

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

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

1488 term_output=False, writable=writable): 

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

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

1491 if 'relinking' in sm_config: 

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

1493 return False 

1494 if 'paused' in sm_config: 

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

1496 return False 

1497 try: 

1498 self._session.xenapi.VDI.add_to_sm_config( 

1499 vdi_ref, 'activating', 'True') 

1500 except XenAPI.Failure as e: 

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

1502 # Someone else is activating - a retry might succeed 

1503 return False 

1504 raise 

1505 host_key = "host_%s" % host_ref 

1506 assert host_key not in sm_config 

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

1508 attach_mode) 

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

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

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

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

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

1514 self._session.xenapi.VDI.remove_from_sm_config( 

1515 vdi_ref, 'activating') 

1516 return False 

1517 util.SMlog("Activate lock succeeded") 

1518 return True 

1519 

1520 def _check_tag(self, vdi_uuid): 

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

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

1523 if 'paused' in sm_config: 

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

1525 return False 

1526 return True 

1527 

1528 def _remove_tag(self, vdi_uuid): 

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

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

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

1532 host_key = "host_%s" % host_ref 

1533 if host_key in sm_config: 

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

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

1536 else: 

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

1538 

1539 def _get_pool_config(self, pool_name): 

1540 pool_info = dict() 

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

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

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

1544 # special pool 

1545 return pool_info 

1546 

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

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

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

1550 pool_size_str = sr_config.get(POOL_SIZE_KEY) 

1551 pool_name_override = vdi_config.get(POOL_NAME_KEY) 

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

1553 pool_name = pool_name_override 

1554 pool_size_override = vdi_config.get(POOL_SIZE_KEY) 

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

1556 pool_size_str = pool_size_override 

1557 pool_size = 0 

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

1559 try: 

1560 pool_size = int(pool_size_str) 

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

1562 raise ValueError("outside of range") 

1563 pool_size = NUM_PAGES_PER_RING * pool_size 

1564 except ValueError: 

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

1566 pool_size = 0 

1567 

1568 pool_info["mem-pool"] = pool_name 

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

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

1571 

1572 return pool_info 

1573 

1574 def linkNBD(self, sr_uuid, vdi_uuid): 

1575 if self.tap: 

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

1577 int(self.tap.minor)) 

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

1579 

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

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

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

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

1584 util.SMlog("Attach & activate") 

1585 self._attach(sr_uuid, vdi_uuid) 

1586 dev_path = self._activate(sr_uuid, vdi_uuid, 

1587 {"rdonly": not writable}) 

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

1589 self.linkNBD(sr_uuid, vdi_uuid) 

1590 

1591 # Return backend/ link 

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

1593 if self.tap_wanted(): 

1594 # Only have NBD if we also have a tap 

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

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

1597 vdi_uuid) 

1598 else: 

1599 nbd_path = "" 

1600 

1601 options = {"rdonly": not writable} 

1602 options.update(caching_params) 

1603 o_direct, o_direct_reason = self.get_o_direct_capability(options) 

1604 struct = {'params': back_path, 

1605 'params_nbd': nbd_path, 

1606 'o_direct': o_direct, 

1607 'o_direct_reason': o_direct_reason, 

1608 'xenstore_data': self.xenstore_data} 

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

1610 

1611 try: 

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

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

1614 f.close() 

1615 except: 

1616 pass 

1617 

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

1619 

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

1621 util.SMlog("blktap2.activate") 

1622 options = {"rdonly": not writable} 

1623 options.update(caching_params) 

1624 

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

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

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

1628 try: 

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

1630 return 

1631 except util.SRBusyException: 

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

1633 time.sleep(1) 

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

1635 

1636 @locking("VDIUnavailable") 

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

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

1639 

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

1641 refresh = False 

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

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

1644 return False 

1645 refresh = True 

1646 

1647 try: 

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

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

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

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

1652 # object completely 

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

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

1655 target.sr.srcmd.params = params 

1656 driver_info = target.sr.srcmd.driver_info 

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

1658 

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

1660 "blktap_activate_inject_failure", 

1661 lambda: util.inject_failure()) 

1662 

1663 # Attach the physical node 

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

1665 self._attach(sr_uuid, vdi_uuid) 

1666 

1667 vdi_type = self.target.get_vdi_type() 

1668 

1669 # Take lvchange-p Lock before running 

1670 # tap-ctl open 

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

1672 # now taking the same lock 

1673 # This is a fix for CA-155766 

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

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

1676 vdi_type == vhdutil.VDI_TYPE_VHD: 

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

1678 lock.acquire() 

1679 

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

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

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

1683 session = self.target.vdi.session 

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

1685 # pylint: disable=used-before-assignment 

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

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

1688 key_hash = sm_config['key_hash'] 

1689 options['key_hash'] = key_hash 

1690 options['vdi_uuid'] = vdi_uuid 

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

1692 # Activate the physical node 

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

1694 

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

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

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

1698 lock.release() 

1699 except: 

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

1701 if self.tap_wanted(): 

1702 util.fistpoint.activate_custom_fn( 

1703 "blktap_activate_error_handling", 

1704 lambda: time.sleep(30)) 

1705 while True: 

1706 try: 

1707 self._remove_tag(vdi_uuid) 

1708 break 

1709 except xmlrpc.client.ProtocolError as e: 

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

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

1712 continue 

1713 else: 

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

1715 break 

1716 except Exception as e: 

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

1718 break 

1719 raise 

1720 finally: 

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

1722 self._session.xenapi.VDI.remove_from_sm_config( 

1723 vdi_ref, 'activating') 

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

1725 

1726 # Link result to backend/ 

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

1728 self.linkNBD(sr_uuid, vdi_uuid) 

1729 return True 

1730 

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

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

1733 

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

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

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

1737 # Maybe launch a tapdisk on the physical link 

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

1739 vdi_type = self.target.get_vdi_type() 

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

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

1742 options.update(vdi_options) 

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

1744 sr_uuid, options, 

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

1746 else: 

1747 dev_path = phy_path # Just reuse phy 

1748 

1749 return dev_path 

1750 

1751 def _attach(self, sr_uuid, vdi_uuid): 

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

1753 params = attach_info['params'] 

1754 xenstore_data = attach_info['xenstore_data'] 

1755 phy_path = util.to_plain_string(params) 

1756 self.xenstore_data.update(xenstore_data) 

1757 # Save it to phy/ 

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

1759 

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

1761 util.SMlog("blktap2.deactivate") 

1762 for i in range(self.ATTACH_DETACH_RETRY_SECS): 

1763 try: 

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

1765 return 

1766 except util.SRBusyException as e: 

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

1768 time.sleep(1) 

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

1770 

1771 @locking("VDIUnavailable") 

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

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

1774 

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

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

1777 return False 

1778 

1779 self._deactivate(sr_uuid, vdi_uuid, caching_params) 

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

1781 self._detach(sr_uuid, vdi_uuid) 

1782 if self.tap_wanted(): 

1783 self._remove_tag(vdi_uuid) 

1784 

1785 return True 

1786 

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

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

1789 

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

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

1792 util.SMlog("Deactivate & detach") 

1793 self._deactivate(sr_uuid, vdi_uuid, caching_params) 

1794 self._detach(sr_uuid, vdi_uuid) 

1795 else: 

1796 pass # nothing to do 

1797 

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

1799 import VDI as sm 

1800 

1801 # Shutdown tapdisk 

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

1803 

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

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

1806 return 

1807 

1808 try: 

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

1810 os.unlink(attach_info_path) 

1811 except: 

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

1813 

1814 try: 

1815 major, minor = back_link.rdev() 

1816 except self.DeviceNode.NotABlockDevice: 

1817 pass 

1818 else: 

1819 if major == Tapdisk.major(): 

1820 self._tap_deactivate(minor) 

1821 self.remove_cache(sr_uuid, vdi_uuid, caching_params) 

1822 

1823 # Remove the backend link 

1824 back_link.unlink() 

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

1826 

1827 # Deactivate & detach the physical node 

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

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

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

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

1832 # object completely 

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

1834 driver_info = target.sr.srcmd.driver_info 

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

1836 

1837 self.target.deactivate(sr_uuid, vdi_uuid) 

1838 

1839 def _detach(self, sr_uuid, vdi_uuid): 

1840 self.target.detach(sr_uuid, vdi_uuid) 

1841 

1842 # Remove phy/ 

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

1844 

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

1846 # Remove existing VDI.sm_config fields 

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

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

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

1850 if not on_boot is None: 

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

1852 if not caching is None: 

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

1854 

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

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

1857 return 

1858 

1859 util.SMlog("Requested local caching") 

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

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

1862 return 

1863 

1864 scratch_mode = False 

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

1866 scratch_mode = True 

1867 util.SMlog("Requested scratch mode") 

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

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

1870 return 

1871 

1872 dev_path = None 

1873 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR) 

1874 if not local_sr_uuid: 

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

1876 return 

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

1878 local_sr_uuid, scratch_mode, params) 

1879 

1880 if dev_path: 

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

1882 params.get(self.CONF_KEY_MODE_ON_BOOT), 

1883 params.get(self.CONF_KEY_ALLOW_CACHING)) 

1884 

1885 return dev_path 

1886 

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

1888 vm_uuid = None 

1889 vm_label = "" 

1890 try: 

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

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

1893 cache_sr_label = cache_sr_rec.get("name_label") 

1894 

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

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

1897 host_label = host_rec.get("name_label") 

1898 

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

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

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

1902 for vbd_rec in vbds.values(): 

1903 vm_ref = vbd_rec.get("VM") 

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

1905 vm_uuid = vm_rec.get("uuid") 

1906 vm_label = vm_rec.get("name_label") 

1907 except: 

1908 util.logException("alert_no_cache") 

1909 

1910 alert_obj = "SR" 

1911 alert_uuid = str(cache_sr_uuid) 

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

1913 if vm_uuid: 

1914 alert_obj = "VM" 

1915 alert_uuid = vm_uuid 

1916 reason = "" 

1917 if err == errno.ENOSPC: 

1918 reason = "because there is no space left" 

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

1920 (vm_label, reason, cache_sr_label, host_label) 

1921 

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

1923 (alert_obj, alert_uuid, alert_str)) 

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

1925 alert_obj, alert_uuid, alert_str) 

1926 

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

1928 scratch_mode, options): 

1929 import SR 

1930 import EXTSR 

1931 import NFSSR 

1932 from lock import Lock 

1933 from FileSR import FileVDI 

1934 

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

1936 FileVDI.extractUuid) 

1937 if not parent_uuid: 

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

1939 self.target.vdi.uuid) 

1940 return 

1941 

1942 util.SMlog("Setting up cache") 

1943 parent_uuid = parent_uuid.strip() 

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

1945 

1946 if shared_target.parent: 

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

1948 shared_target.uuid) 

1949 return 

1950 

1951 SR.registerSR(EXTSR.EXTSR) 

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

1953 

1954 lock = Lock(self.LOCK_CACHE_SETUP, parent_uuid) 

1955 lock.acquire() 

1956 

1957 # read cache 

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

1959 if util.pathexists(read_cache_path): 

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

1961 read_cache_path) 

1962 else: 

1963 try: 

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

1965 except util.CommandException as e: 

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

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

1968 return None 

1969 

1970 # local write node 

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

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

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

1974 if util.pathexists(local_leaf_path): 

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

1976 local_leaf_path) 

1977 os.unlink(local_leaf_path) 

1978 try: 

1979 vhdutil.snapshot(local_leaf_path, read_cache_path, False, 

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

1981 except util.CommandException as e: 

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

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

1984 return None 

1985 

1986 local_leaf_size = vhdutil.getSizeVirt(local_leaf_path) 

1987 if leaf_size > local_leaf_size: 

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

1989 (leaf_size, local_leaf_size)) 

1990 vhdutil.setSizeVirtFast(local_leaf_path, leaf_size) 

1991 

1992 vdi_type = self.target.get_vdi_type() 

1993 

1994 prt_tapdisk = Tapdisk.find_by_path(read_cache_path) 

1995 if not prt_tapdisk: 

1996 parent_options = copy.deepcopy(options) 

1997 parent_options["rdonly"] = False 

1998 parent_options["lcache"] = True 

1999 

2000 blktap = Blktap.allocate() 

2001 try: 

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

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

2004 # its own pool 

2005 prt_tapdisk = \ 

2006 Tapdisk.launch_on_tap(blktap, read_cache_path, 

2007 'vhd', parent_options) 

2008 except: 

2009 blktap.free() 

2010 raise 

2011 

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

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

2014 

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

2016 leaf_tapdisk = Tapdisk.find_by_path(local_leaf_path) 

2017 if not leaf_tapdisk: 

2018 blktap = Blktap.allocate() 

2019 child_options = copy.deepcopy(options) 

2020 child_options["rdonly"] = False 

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

2022 child_options["existing_prt"] = prt_tapdisk.minor 

2023 child_options["secondary"] = secondary 

2024 child_options["standby"] = scratch_mode 

2025 try: 

2026 leaf_tapdisk = \ 

2027 Tapdisk.launch_on_tap(blktap, local_leaf_path, 

2028 'vhd', child_options) 

2029 except: 

2030 blktap.free() 

2031 raise 

2032 

2033 lock.release() 

2034 

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

2036 (read_cache_path, local_leaf_path)) 

2037 

2038 self.tap = leaf_tapdisk 

2039 return leaf_tapdisk.get_devpath() 

2040 

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

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

2043 return 

2044 

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

2046 

2047 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR) 

2048 if caching and not local_sr_uuid: 

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

2050 return 

2051 

2052 if caching: 

2053 self._remove_cache(self._session, local_sr_uuid) 

2054 

2055 if self._session is not None: 

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

2057 

2058 def _is_tapdisk_in_use(self, minor): 

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

2060 if not retVal: 

2061 # err on the side of caution 

2062 return True 

2063 

2064 for link in links: 

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

2066 return True 

2067 

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

2069 for s in sockets: 

2070 if socket_re.match(s): 

2071 return True 

2072 

2073 return False 

2074 

2075 def _remove_cache(self, session, local_sr_uuid): 

2076 import SR 

2077 import EXTSR 

2078 import NFSSR 

2079 from lock import Lock 

2080 from FileSR import FileVDI 

2081 

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

2083 FileVDI.extractUuid) 

2084 if not parent_uuid: 

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

2086 self.target.vdi.uuid) 

2087 return 

2088 

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

2090 

2091 parent_uuid = parent_uuid.strip() 

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

2093 

2094 SR.registerSR(EXTSR.EXTSR) 

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

2096 

2097 lock = Lock(self.LOCK_CACHE_SETUP, parent_uuid) 

2098 lock.acquire() 

2099 

2100 # local write node 

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

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

2103 if util.pathexists(local_leaf_path): 

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

2105 os.unlink(local_leaf_path) 

2106 

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

2108 prt_tapdisk = Tapdisk.find_by_path(read_cache_path) 

2109 if not prt_tapdisk: 

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

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

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

2113 read_cache_path) 

2114 try: 

2115 prt_tapdisk.shutdown() 

2116 except: 

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

2118 else: 

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

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

2121 # GC run 

2122 

2123 lock.release() 

2124 

2125PythonKeyError = KeyError 

2126 

2127 

2128class UEventHandler(object): 

2129 

2130 def __init__(self): 

2131 self._action = None 

2132 

2133 class KeyError(PythonKeyError): 

2134 def __init__(self, args): 

2135 super().__init__(args) 

2136 self.key = args[0] 

2137 

2138 @override 

2139 def __str__(self) -> str: 

2140 return \ 

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

2142 "Not called in udev context?" 

2143 

2144 @classmethod 

2145 def getenv(cls, key): 

2146 try: 

2147 return os.environ[key] 

2148 except KeyError as e: 

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

2150 

2151 def get_action(self): 

2152 if not self._action: 

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

2154 return self._action 

2155 

2156 class UnhandledEvent(Exception): 

2157 

2158 def __init__(self, event, handler): 

2159 self.event = event 

2160 self.handler = handler 

2161 

2162 @override 

2163 def __str__(self) -> str: 

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

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

2166 

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

2168 

2169 def run(self): 

2170 

2171 action = self.get_action() 

2172 try: 

2173 fn = self.ACTIONS[action] 

2174 except KeyError: 

2175 raise self.UnhandledEvent(action, self) 

2176 

2177 return fn(self) 

2178 

2179 @override 

2180 def __str__(self) -> str: 

2181 try: 

2182 action = self.get_action() 

2183 except: 

2184 action = None 

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

2186 

2187 

2188class __BlktapControl(ClassDevice): 

2189 SYSFS_CLASSTYPE = "misc" 

2190 

2191 def __init__(self): 

2192 ClassDevice.__init__(self) 

2193 self._default_pool = None 

2194 

2195 @override 

2196 def sysfs_devname(self) -> str: 

2197 return "blktap!control" 

2198 

2199 class DefaultPool(Attribute): 

2200 SYSFS_NODENAME = "default_pool" 

2201 

2202 def get_default_pool_attr(self): 

2203 if not self._default_pool: 

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

2205 return self._default_pool 

2206 

2207 def get_default_pool_name(self): 

2208 return self.get_default_pool_attr().readline() 

2209 

2210 def set_default_pool_name(self, name): 

2211 self.get_default_pool_attr().writeline(name) 

2212 

2213 def get_default_pool(self): 

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

2215 

2216 def set_default_pool(self, pool): 

2217 self.set_default_pool_name(pool.name) 

2218 

2219 class NoSuchPool(Exception): 

2220 def __init__(self, name): 

2221 self.name = name 

2222 

2223 @override 

2224 def __str__(self) -> str: 

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

2226 

2227 def get_pool(self, name): 

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

2229 

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

2231 raise self.NoSuchPool(name) 

2232 

2233 return PagePool(path) 

2234 

2235BlktapControl = __BlktapControl() 

2236 

2237 

2238class PagePool(KObject): 

2239 

2240 def __init__(self, path): 

2241 self.path = path 

2242 self._size = None 

2243 

2244 @override 

2245 def sysfs_devname(self) -> str: 

2246 return '' 

2247 

2248 def sysfs_path(self): 

2249 return self.path 

2250 

2251 class Size(Attribute): 

2252 SYSFS_NODENAME = "size" 

2253 

2254 def get_size_attr(self): 

2255 if not self._size: 

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

2257 return self._size 

2258 

2259 def set_size(self, pages): 

2260 pages = str(pages) 

2261 self.get_size_attr().writeline(pages) 

2262 

2263 def get_size(self): 

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

2265 return int(pages) 

2266 

2267 

2268class BusDevice(KObject): 

2269 

2270 SYSFS_BUSTYPE: ClassVar[str] = "" 

2271 

2272 @classmethod 

2273 def sysfs_bus_path(cls): 

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

2275 

2276 def sysfs_path(self): 

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

2278 self.sysfs_devname()) 

2279 

2280 return path 

2281 

2282 

2283class XenbusDevice(BusDevice): 

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

2285 

2286 XBT_NIL = "" 

2287 

2288 XENBUS_DEVTYPE: ClassVar[str] = "" 

2289 

2290 def __init__(self, domid, devid): 

2291 self.domid = int(domid) 

2292 self.devid = int(devid) 

2293 self._xbt = XenbusDevice.XBT_NIL 

2294 

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

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

2297 

2298 def xs_path(self, key=None): 

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

2300 self.domid, 

2301 self.devid) 

2302 if key is not None: 

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

2304 

2305 return path 

2306 

2307 def _log(self, prio, msg): 

2308 syslog(prio, msg) 

2309 

2310 def info(self, msg): 

2311 self._log(_syslog.LOG_INFO, msg) 

2312 

2313 def warn(self, msg): 

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

2315 

2316 def _xs_read_path(self, path): 

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

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

2319 return val 

2320 

2321 def _xs_write_path(self, path, val): 

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

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

2324 

2325 def _xs_rm_path(self, path): 

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

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

2328 

2329 def read(self, key): 

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

2331 

2332 def has_xs_key(self, key): 

2333 return self.read(key) is not None 

2334 

2335 def write(self, key, val): 

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

2337 

2338 def rm(self, key): 

2339 self._xs_rm_path(self.xs_path(key)) 

2340 

2341 def exists(self): 

2342 return self.has_xs_key(None) 

2343 

2344 def begin(self): 

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

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

2347 

2348 def commit(self): 

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

2350 self._xbt = XenbusDevice.XBT_NIL 

2351 return ok 

2352 

2353 def abort(self): 

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

2355 assert(ok == True) 

2356 self._xbt = XenbusDevice.XBT_NIL 

2357 

2358 def create_physical_device(self): 

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

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

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

2362 return 

2363 try: 

2364 params = self.read("params") 

2365 frontend = self.read("frontend") 

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

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

2368 # from opening the physical-device 

2369 if not(is_cdrom): 

2370 major_minor = os.stat(params).st_rdev 

2371 major, minor = divmod(major_minor, 256) 

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

2373 except: 

2374 util.logException("BLKTAP2:create_physical_device") 

2375 

2376 def signal_hotplug(self, online=True): 

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

2378 self.XENBUS_DEVTYPE, 

2379 self.devid) 

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

2381 if online: 

2382 self._xs_write_path(xapi_path, "online") 

2383 self._xs_write_path(upstream_path, "connected") 

2384 else: 

2385 self._xs_rm_path(xapi_path) 

2386 self._xs_rm_path(upstream_path) 

2387 

2388 @override 

2389 def sysfs_devname(self) -> str: 

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

2391 self.domid, self.devid) 

2392 

2393 @override 

2394 def __str__(self) -> str: 

2395 return self.sysfs_devname() 

2396 

2397 @classmethod 

2398 def find(cls): 

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

2400 cls.XENBUS_DEVTYPE) 

2401 for path in glob.glob(pattern): 

2402 

2403 name = os.path.basename(path) 

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

2405 

2406 yield cls(domid, devid) 

2407 

2408 

2409class XenBackendDevice(XenbusDevice): 

2410 """Xenbus backend device""" 

2411 SYSFS_BUSTYPE = "xen-backend" 

2412 

2413 @classmethod 

2414 def from_xs_path(cls, _path): 

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

2416 

2417 assert _backend == 'backend' 

2418 assert _type == cls.XENBUS_DEVTYPE 

2419 

2420 domid = int(domid) 

2421 devid = int(devid) 

2422 

2423 return cls(domid, devid) 

2424 

2425 

2426class Blkback(XenBackendDevice): 

2427 """A blkback VBD""" 

2428 

2429 XENBUS_DEVTYPE = "vbd" 

2430 

2431 def __init__(self, domid, devid): 

2432 XenBackendDevice.__init__(self, domid, devid) 

2433 self._phy = None 

2434 self._vdi_uuid = None 

2435 self._q_state = None 

2436 self._q_events = None 

2437 

2438 class XenstoreValueError(Exception): 

2439 KEY: ClassVar[str] = "" 

2440 

2441 def __init__(self, vbd, _str): 

2442 self.vbd = vbd 

2443 self.str = _str 

2444 

2445 @override 

2446 def __str__(self) -> str: 

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

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

2449 

2450 class PhysicalDeviceError(XenstoreValueError): 

2451 KEY = "physical-device" 

2452 

2453 class PhysicalDevice(object): 

2454 

2455 def __init__(self, major, minor): 

2456 self.major = int(major) 

2457 self.minor = int(minor) 

2458 

2459 @classmethod 

2460 def from_xbdev(cls, xbdev): 

2461 

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

2463 

2464 try: 

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

2466 major = int(major, 0x10) 

2467 minor = int(minor, 0x10) 

2468 except Exception as e: 

2469 raise xbdev.PhysicalDeviceError(xbdev, phy) 

2470 

2471 return cls(major, minor) 

2472 

2473 def makedev(self): 

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

2475 

2476 def is_tap(self): 

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

2478 

2479 @override 

2480 def __str__(self) -> str: 

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

2482 

2483 @override 

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

2485 return \ 

2486 self.major == other.major and \ 

2487 self.minor == other.minor 

2488 

2489 def get_physical_device(self): 

2490 if not self._phy: 

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

2492 return self._phy 

2493 

2494 class QueueEvents(Attribute): 

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

2496 notifications emitted.""" 

2497 

2498 SYSFS_NODENAME = "queue_events" 

2499 

2500 QUEUE_RUNNING = (1 << 0) 

2501 QUEUE_PAUSE_DONE = (1 << 1) 

2502 QUEUE_SHUTDOWN_DONE = (1 << 2) 

2503 QUEUE_PAUSE_REQUEST = (1 << 3) 

2504 QUEUE_SHUTDOWN_REQUEST = (1 << 4) 

2505 

2506 def get_mask(self): 

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

2508 

2509 def set_mask(self, mask): 

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

2511 

2512 def get_queue_events(self): 

2513 if not self._q_events: 

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

2515 return self._q_events 

2516 

2517 def get_vdi_uuid(self): 

2518 if not self._vdi_uuid: 

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

2520 return self._vdi_uuid 

2521 

2522 def pause_requested(self): 

2523 return self.has_xs_key("pause") 

2524 

2525 def shutdown_requested(self): 

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

2527 

2528 def shutdown_done(self): 

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

2530 

2531 def running(self): 

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

2533 

2534 @classmethod 

2535 def find_by_physical_device(cls, phy): 

2536 for dev in cls.find(): 

2537 try: 

2538 _phy = dev.get_physical_device() 

2539 except cls.PhysicalDeviceError: 

2540 continue 

2541 

2542 if _phy == phy: 

2543 yield dev 

2544 

2545 @classmethod 

2546 def find_by_tap_minor(cls, minor): 

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

2548 return cls.find_by_physical_device(phy) 

2549 

2550 @classmethod 

2551 def find_by_tap(cls, tapdisk): 

2552 return cls.find_by_tap_minor(tapdisk.minor) 

2553 

2554 def has_tap(self): 

2555 

2556 if not self.can_tap(): 

2557 return False 

2558 

2559 phy = self.get_physical_device() 

2560 if phy: 

2561 return phy.is_tap() 

2562 

2563 return False 

2564 

2565 def is_bare_hvm(self): 

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

2567 try: 

2568 self.get_physical_device() 

2569 

2570 except self.PhysicalDeviceError as e: 

2571 vdi_type = self.read("type") 

2572 

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

2574 

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

2576 raise 

2577 

2578 return True 

2579 

2580 return False 

2581 

2582 def can_tap(self): 

2583 return not self.is_bare_hvm() 

2584 

2585 

2586class BlkbackEventHandler(UEventHandler): 

2587 

2588 LOG_FACILITY = _syslog.LOG_DAEMON 

2589 

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

2591 if not ident: 

2592 ident = self.__class__.__name__ 

2593 

2594 self.ident = ident 

2595 self._vbd = None 

2596 self._tapdisk = None 

2597 

2598 UEventHandler.__init__(self) 

2599 

2600 @override 

2601 def run(self) -> None: 

2602 

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

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

2605 

2606 UEventHandler.run(self) 

2607 

2608 @override 

2609 def __str__(self) -> str: 

2610 

2611 try: 

2612 path = self.xs_path 

2613 except: 

2614 path = None 

2615 

2616 try: 

2617 action = self.get_action() 

2618 except: 

2619 action = None 

2620 

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

2622 

2623 def _log(self, prio, msg): 

2624 syslog(prio, msg) 

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

2626 

2627 def info(self, msg): 

2628 self._log(_syslog.LOG_INFO, msg) 

2629 

2630 def warn(self, msg): 

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

2632 

2633 def error(self, msg): 

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

2635 

2636 def get_vbd(self): 

2637 if not self._vbd: 

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

2639 return self._vbd 

2640 

2641 def get_tapdisk(self): 

2642 if not self._tapdisk: 

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

2644 self._tapdisk = Tapdisk.from_minor(minor) 

2645 return self._tapdisk 

2646 # 

2647 # Events 

2648 # 

2649 

2650 def __add(self): 

2651 vbd = self.get_vbd() 

2652 # Manage blkback transitions 

2653 # self._manage_vbd() 

2654 

2655 vbd.create_physical_device() 

2656 

2657 vbd.signal_hotplug() 

2658 

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

2660 def add(self): 

2661 try: 

2662 self.__add() 

2663 except Attribute.NoSuchAttribute as e: 

2664 # 

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

2666 # registers device attributes. So poll a little. 

2667 # 

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

2669 raise RetryLoop.TransientFailure(e) 

2670 

2671 def __change(self): 

2672 vbd = self.get_vbd() 

2673 

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

2675 

2676 if vbd.has_tap(): 

2677 pass 

2678 #self._pause_update_tap() 

2679 

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

2681 

2682 self._signal_xapi() 

2683 

2684 def change(self): 

2685 vbd = self.get_vbd() 

2686 

2687 # NB. Beware of spurious change events between shutdown 

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

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

2690 

2691 while True: 

2692 vbd.begin() 

2693 

2694 if not vbd.exists() or \ 

2695 vbd.shutdown_done(): 

2696 break 

2697 

2698 self.__change() 

2699 

2700 if vbd.commit(): 

2701 return 

2702 

2703 vbd.abort() 

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

2705 

2706 def remove(self): 

2707 vbd = self.get_vbd() 

2708 

2709 vbd.signal_hotplug(False) 

2710 

2711 ACTIONS = {'add': add, 

2712 'change': change, 

2713 'remove': remove} 

2714 # 

2715 # VDI.pause 

2716 # 

2717 

2718 def _tap_should_pause(self): 

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

2720 paused""" 

2721 

2722 tapdisk = self.get_tapdisk() 

2723 TapState = Tapdisk.PauseState 

2724 

2725 PAUSED = 'P' 

2726 RUNNING = 'R' 

2727 PAUSED_SHUTDOWN = 'P,S' 

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

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

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

2731 

2732 next = TapState.RUNNING 

2733 vbds = {} 

2734 

2735 for vbd in Blkback.find_by_tap(tapdisk): 

2736 name = str(vbd) 

2737 

2738 pausing = vbd.pause_requested() 

2739 closing = vbd.shutdown_requested() 

2740 running = vbd.running() 

2741 

2742 if pausing: 

2743 if closing and not running: 

2744 vbds[name] = PAUSED_SHUTDOWN 

2745 else: 

2746 vbds[name] = PAUSED 

2747 next = TapState.PAUSED 

2748 

2749 else: 

2750 vbds[name] = RUNNING 

2751 

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

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

2754 vbds, next)) 

2755 

2756 return next == TapState.PAUSED 

2757 

2758 def _pause_update_tap(self): 

2759 vbd = self.get_vbd() 

2760 

2761 if self._tap_should_pause(): 

2762 self._pause_tap() 

2763 else: 

2764 self._resume_tap() 

2765 

2766 def _pause_tap(self): 

2767 tapdisk = self.get_tapdisk() 

2768 

2769 if not tapdisk.is_paused(): 

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

2771 tapdisk.pause() 

2772 

2773 def _resume_tap(self): 

2774 tapdisk = self.get_tapdisk() 

2775 

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

2777 # type while resuming. 

2778 vbd = self.get_vbd() 

2779 vdi_uuid = vbd.get_vdi_uuid() 

2780 

2781 if tapdisk.is_paused(): 

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

2783 vdi = VDI.from_cli(vdi_uuid) 

2784 _type = vdi.get_tap_type() 

2785 path = vdi.get_phy_path() 

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

2787 tapdisk.unpause(_type, path) 

2788 # 

2789 # VBD.pause/shutdown 

2790 # 

2791 

2792 def _manage_vbd(self): 

2793 vbd = self.get_vbd() 

2794 # NB. Hook into VBD state transitions. 

2795 

2796 events = vbd.get_queue_events() 

2797 

2798 mask = 0 

2799 mask |= events.QUEUE_PAUSE_DONE # pause/unpause 

2800 mask |= events.QUEUE_SHUTDOWN_DONE # shutdown 

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

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

2803 

2804 events.set_mask(mask) 

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

2806 

2807 def _signal_xapi(self): 

2808 vbd = self.get_vbd() 

2809 

2810 pausing = vbd.pause_requested() 

2811 closing = vbd.shutdown_requested() 

2812 running = vbd.running() 

2813 

2814 handled = 0 

2815 

2816 if pausing and not running: 

2817 if 'pause-done' not in vbd: 

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

2819 handled += 1 

2820 

2821 if not pausing: 

2822 if 'pause-done' in vbd: 

2823 vbd.rm('pause-done') 

2824 handled += 1 

2825 

2826 if closing and not running: 

2827 if 'shutdown-done' not in vbd: 

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

2829 handled += 1 

2830 

2831 if handled > 1: 

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

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

2834 (pausing, closing, running)) 

2835 

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

2837 

2838 import sys 

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

2840 

2841 # 

2842 # Simple CLI interface for manual operation 

2843 # 

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

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

2846 # 

2847 

2848 def usage(stream): 

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

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

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

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

2853 

2854 try: 

2855 cmd = sys.argv[1] 

2856 except IndexError: 

2857 usage(sys.stderr) 

2858 sys.exit(1) 

2859 

2860 try: 

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

2862 except: 

2863 usage(sys.stderr) 

2864 sys.exit(1) 

2865 

2866 # 

2867 # Local Tapdisks 

2868 # 

2869 

2870 if cmd == 'tap.major': 

2871 

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

2873 

2874 elif cmd == 'tap.launch': 

2875 

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

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

2878 

2879 elif _class == 'tap': 

2880 

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

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

2883 try: 

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

2885 attrs[key] = val 

2886 continue 

2887 except ValueError: 

2888 pass 

2889 

2890 try: 

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

2892 continue 

2893 except ValueError: 

2894 pass 

2895 

2896 try: 

2897 arg = Tapdisk.Arg.parse(item) 

2898 attrs['_type'] = arg.type 

2899 attrs['path'] = arg.path 

2900 continue 

2901 except Tapdisk.Arg.InvalidArgument: 

2902 pass 

2903 

2904 attrs['path'] = item 

2905 

2906 if cmd == 'tap.list': 

2907 

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

2909 blktap = tapdisk.get_blktap() 

2910 print(tapdisk, end=' ') 

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

2912 (blktap, 

2913 blktap.get_task_pid(), 

2914 blktap.get_pool_name())) 

2915 

2916 elif cmd == 'tap.vbds': 

2917 # Find all Blkback instances for a given tapdisk 

2918 

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

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

2921 for vbd in Blkback.find_by_tap(tapdisk): 

2922 print(vbd, end=' ') 

2923 print() 

2924 

2925 else: 

2926 

2927 if not attrs: 

2928 usage(sys.stderr) 

2929 sys.exit(1) 

2930 

2931 try: 

2932 tapdisk = Tapdisk.get( ** attrs) 

2933 except TypeError: 

2934 usage(sys.stderr) 

2935 sys.exit(1) 

2936 

2937 if cmd == 'tap.shutdown': 

2938 # Shutdown a running tapdisk, or raise 

2939 tapdisk.shutdown() 

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

2941 

2942 elif cmd == 'tap.pause': 

2943 # Pause an unpaused tapdisk, or raise 

2944 tapdisk.pause() 

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

2946 

2947 elif cmd == 'tap.unpause': 

2948 # Unpause a paused tapdisk, or raise 

2949 tapdisk.unpause() 

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

2951 

2952 elif cmd == 'tap.stats': 

2953 # Gather tapdisk status 

2954 stats = tapdisk.stats() 

2955 print("%s:" % tapdisk) 

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

2957 

2958 else: 

2959 usage(sys.stderr) 

2960 sys.exit(1) 

2961 

2962 elif cmd == 'vbd.uevent': 

2963 

2964 hnd = BlkbackEventHandler(cmd) 

2965 

2966 if not sys.stdin.isatty(): 

2967 try: 

2968 hnd.run() 

2969 except Exception as e: 

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

2971 

2972 import traceback 

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

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

2975 for entry in trace: 

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

2977 util.SMlog(line) 

2978 else: 

2979 hnd.run() 

2980 

2981 elif cmd == 'vbd.list': 

2982 

2983 for vbd in Blkback.find(): 

2984 print(vbd, \ 

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

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

2987 

2988 else: 

2989 usage(sys.stderr) 

2990 sys.exit(1)