Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#!/usr/bin/python3 

2# 

3# Copyright (C) Citrix Systems Inc. 

4# 

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

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

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

8# 

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

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

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

12# GNU Lesser General Public License for more details. 

13# 

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

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

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

17# 

18# blktap2: blktap/tapdisk management layer 

19# 

20 

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

22 

23from abc import abstractmethod 

24 

25import grp 

26import os 

27import re 

28import stat 

29import time 

30import copy 

31from lock import Lock 

32import util 

33import xmlrpc.client 

34import http.client 

35import errno 

36import signal 

37import subprocess 

38import syslog as _syslog 

39import glob 

40import json 

41import xs_errors 

42import XenAPI # pylint: disable=import-error 

43import scsiutil 

44from constants import NS_PREFIX_LVM 

45from syslog import openlog, syslog 

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

47from vditype import VdiType 

48 

49import resetvdis 

50 

51import VDI as sm 

52 

53from cowutil import getCowUtil 

54 

55# For RRDD Plugin Registration 

56from xmlrpc.client import ServerProxy, Transport 

57from socket import socket, AF_UNIX, SOCK_STREAM 

58 

59 

60try: 

61 from linstorvolumemanager import log_drbd_openers 

62 LINSTOR_AVAILABLE = True 

63except ImportError: 

64 LINSTOR_AVAILABLE = False 

65 

66PLUGIN_TAP_PAUSE = "tapdisk-pause" 

67PLUGIN_ON_SLAVE = "on-slave" 

68 

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

70 

71NUM_PAGES_PER_RING = 32 * 11 

72MAX_FULL_RINGS = 8 

73POOL_NAME_KEY = "mem-pool" 

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

75 

76ENABLE_MULTIPLE_ATTACH = "/etc/xensource/allow_multiple_vdi_attach" 

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

78 

79 

80def locking(excType, override=True): 

81 def locking2(op): 

82 def wrapper(self, *args): 

83 self.lock.acquire() 

84 try: 

85 try: 

86 ret = op(self, * args) 

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

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

89 msg = str(e) 

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

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

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

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

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

95 else: 

96 raise 

97 except: 

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

99 raise 

100 finally: 

101 self.lock.release() 

102 return ret 

103 return wrapper 

104 return locking2 

105 

106 

107class RetryLoop(object): 

108 

109 def __init__(self, backoff, limit): 

110 self.backoff = backoff 

111 self.limit = limit 

112 

113 def __call__(self, f): 

114 

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

116 attempt = 0 

117 

118 while True: 

119 attempt += 1 

120 

121 try: 

122 return f( * __t, ** __d) 

123 

124 except self.TransientFailure as e: 

125 e = e.exception 

126 

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

128 raise e 

129 

130 time.sleep(self.backoff) 

131 

132 return loop 

133 

134 class TransientFailure(Exception): 

135 def __init__(self, exception): 

136 self.exception = exception 

137 

138 

139def retried(**args): 

140 return RetryLoop( ** args) 

141 

142 

143class TapCtl(object): 

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

145 

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

147 

148 def __init__(self, cmd, p): 

149 self.cmd = cmd 

150 self._p = p 

151 self.stdout = p.stdout 

152 

153 class CommandFailure(Exception): 

154 """TapCtl cmd failure.""" 

155 

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

157 self.cmd = cmd 

158 self.info = info 

159 

160 @override 

161 def __str__(self) -> str: 

162 items = self.info.items() 

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

164 for item in items) 

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

166 

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

168 # exception 

169 def __getattr__(self, key): 

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

171 return self.info[key] 

172 return object.__getattribute__(self, key) 

173 

174 @property 

175 def has_status(self): 

176 return 'status' in self.info 

177 

178 @property 

179 def has_signal(self): 

180 return 'signal' in self.info 

181 

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

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

184 def get_error_code(self): 

185 key = 'status' 

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

187 return self.info[key] 

188 else: 

189 return 0 

190 

191 @classmethod 

192 def __mkcmd_real(cls, args): 

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

194 

195 __next_mkcmd = __mkcmd_real 

196 

197 @classmethod 

198 def _mkcmd(cls, args): 

199 

200 __next_mkcmd = cls.__next_mkcmd 

201 cls.__next_mkcmd = cls.__mkcmd_real 

202 

203 return __next_mkcmd(args) 

204 

205 @classmethod 

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

207 """ 

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

209 Raises a TapCtl.CommandFailure if subprocess creation failed. 

210 """ 

211 cmd = cls._mkcmd(args) 

212 

213 if not quiet: 

214 util.SMlog(cmd) 

215 try: 

216 p = subprocess.Popen(cmd, 

217 stdin=subprocess.PIPE, 

218 stdout=subprocess.PIPE, 

219 stderr=subprocess.PIPE, 

220 close_fds=True, 

221 universal_newlines=text_mode) 

222 if input: 

223 p.stdin.write(input) 

224 p.stdin.close() 

225 except OSError as e: 

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

227 

228 return cls(cmd, p) 

229 

230 def _errmsg(self): 

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

232 return "; ".join(output) 

233 

234 def _wait(self, quiet=False): 

235 """ 

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

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

238 """ 

239 status = self._p.wait() 

240 if not quiet: 

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

242 

243 if status == 0: 

244 return 

245 

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

247 'pid': self._p.pid} 

248 

249 if status < 0: 

250 info['signal'] = -status 

251 else: 

252 info['status'] = status 

253 

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

255 

256 @classmethod 

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

258 """ 

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

260 """ 

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

262 text_mode=text_mode) 

263 

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

265 

266 tapctl._wait(quiet) 

267 return output 

268 

269 @staticmethod 

270 def _maybe(opt, parm): 

271 if parm is not None: 

272 return [opt, parm] 

273 return [] 

274 

275 @classmethod 

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

277 args = ["list"] 

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

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

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

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

282 

283 tapctl = cls._call(args, True) 

284 

285 for stdout_line in tapctl.stdout: 

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

287 # confuses this parser 

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

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

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

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

292 row = {} 

293 

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

295 bits = field.split('=') 

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

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

298 

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

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

301 

302 elif key in ('state'): 

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

304 

305 else: 

306 row[key] = val 

307 else: 

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

309 yield row 

310 

311 tapctl._wait(True) 

312 

313 @classmethod 

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

315 def list(cls, **args): 

316 

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

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

319 # be fixed in SM. 

320 

321 try: 

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

323 

324 except cls.CommandFailure as e: 

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

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

327 raise RetryLoop.TransientFailure(e) 

328 raise 

329 

330 @classmethod 

331 def allocate(cls, devpath=None): 

332 args = ["allocate"] 

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

334 return cls._pread(args) 

335 

336 @classmethod 

337 def free(cls, minor): 

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

339 cls._pread(args) 

340 

341 @classmethod 

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

343 def spawn(cls): 

344 args = ["spawn"] 

345 try: 

346 pid = cls._pread(args) 

347 return int(pid) 

348 except cls.CommandFailure as ce: 

349 # intermittent failures to spawn. CA-292268 

350 if ce.status == 1: 

351 raise RetryLoop.TransientFailure(ce) 

352 raise 

353 

354 @classmethod 

355 def attach(cls, pid, minor): 

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

357 cls._pread(args) 

358 

359 @classmethod 

360 def detach(cls, pid, minor): 

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

362 cls._pread(args) 

363 

364 @classmethod 

365 def _load_key(cls, key_hash, vdi_uuid): 

366 import plugins 

367 

368 return plugins.load_key(key_hash, vdi_uuid) 

369 

370 @classmethod 

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

372 params = Tapdisk.Arg(_type, _file) 

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

374 text_mode = True 

375 input = None 

376 if options.get("rdonly"): 

377 args.append('-R') 

378 if options.get("lcache"): 

379 args.append("-r") 

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

381 args.append("-e") 

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

383 if options.get("secondary"): 

384 args.append("-2") 

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

386 if options.get("standby"): 

387 args.append("-s") 

388 if options.get("timeout"): 

389 args.append("-t") 

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

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

392 args.append("-D") 

393 if options.get('cbtlog'): 

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

395 if options.get('key_hash'): 

396 key_hash = options['key_hash'] 

397 vdi_uuid = options['vdi_uuid'] 

398 key = cls._load_key(key_hash, vdi_uuid) 

399 

400 if not key: 

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

402 input = key 

403 text_mode = False 

404 args.append('-E') 

405 

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

407 

408 @classmethod 

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

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

411 if force: 

412 args += ["-f"] 

413 cls._pread(args) 

414 

415 @classmethod 

416 def pause(cls, pid, minor): 

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

418 cls._pread(args) 

419 

420 @classmethod 

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

422 cbtlog=None): 

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

424 if mirror: 

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

426 if _type and _file: 

427 params = Tapdisk.Arg(_type, _file) 

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

429 if cbtlog: 

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

431 cls._pread(args) 

432 

433 @classmethod 

434 def shutdown(cls, pid): 

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

436 os.kill(pid, signal.SIGTERM) 

437 os.waitpid(pid, 0) 

438 

439 @classmethod 

440 def stats(cls, pid, minor): 

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

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

443 

444 @classmethod 

445 def major(cls): 

446 args = ["major"] 

447 major = cls._pread(args) 

448 return int(major) 

449 

450 @classmethod 

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

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

453 cls._pread(args) 

454 

455 @classmethod 

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

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

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

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

460 status = m.group(1) 

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

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

463 return (status, coalesced, total_coalesce) 

464 

465 @classmethod 

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

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

468 if wait: 

469 args.append("-w") 

470 cls._pread(args) 

471 

472class TapdiskExists(Exception): 

473 """Tapdisk already running.""" 

474 

475 def __init__(self, tapdisk): 

476 self.tapdisk = tapdisk 

477 

478 @override 

479 def __str__(self) -> str: 

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

481 

482 

483class TapdiskNotRunning(Exception): 

484 """No such Tapdisk.""" 

485 

486 def __init__(self, **attrs): 

487 self.attrs = attrs 

488 

489 @override 

490 def __str__(self) -> str: 

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

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

493 for attr in items) 

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

495 

496 

497class TapdiskNotUnique(Exception): 

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

499 

500 def __init__(self, tapdisks): 

501 self.tapdisks = tapdisks 

502 

503 @override 

504 def __str__(self) -> str: 

505 tapdisks = map(str, self.tapdisks) 

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

507 

508 

509class TapdiskFailed(Exception): 

510 """Tapdisk launch failure.""" 

511 

512 def __init__(self, arg, err): 

513 self.arg = arg 

514 self.err = err 

515 

516 @override 

517 def __str__(self) -> str: 

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

519 

520 def get_error(self): 

521 return self.err 

522 

523 

524class TapdiskInvalidState(Exception): 

525 """Tapdisk pause/unpause failure""" 

526 

527 def __init__(self, tapdisk): 

528 self.tapdisk = tapdisk 

529 

530 @override 

531 def __str__(self) -> str: 

532 return str(self.tapdisk) 

533 

534 

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

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

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

538 assert parent != path 

539 try: 

540 if parent: 

541 mkdirs(parent, mode) 

542 if subdir: 

543 os.mkdir(path, mode) 

544 except OSError as e: 

545 if e.errno != errno.EEXIST: 

546 raise 

547 

548 

549class KObject(object): 

550 

551 SYSFS_CLASSTYPE: ClassVar[str] = "" 

552 

553 @abstractmethod 

554 def sysfs_devname(self) -> str: 

555 pass 

556 

557 

558class Attribute(object): 

559 

560 SYSFS_NODENAME: ClassVar[str] = "" 

561 

562 def __init__(self, path): 

563 self.path = path 

564 

565 @classmethod 

566 def from_kobject(cls, kobj): 

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

568 return cls(path) 

569 

570 class NoSuchAttribute(Exception): 

571 def __init__(self, name): 

572 self.name = name 

573 

574 @override 

575 def __str__(self) -> str: 

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

577 

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

579 try: 

580 return open(self.path, mode) 

581 except IOError as e: 

582 if e.errno == errno.ENOENT: 

583 raise self.NoSuchAttribute(self) 

584 raise 

585 

586 def readline(self): 

587 f = self._open('r') 

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

589 f.close() 

590 return s 

591 

592 def writeline(self, val): 

593 f = self._open('w') 

594 f.write(val) 

595 f.close() 

596 

597 

598class ClassDevice(KObject): 

599 

600 @classmethod 

601 def sysfs_class_path(cls): 

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

603 

604 def sysfs_path(self): 

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

606 self.sysfs_devname()) 

607 

608 

609class Blktap(ClassDevice): 

610 

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

612 

613 SYSFS_CLASSTYPE = "blktap2" 

614 

615 def __init__(self, minor): 

616 self.minor = minor 

617 self._pool = None 

618 self._task = None 

619 

620 @classmethod 

621 def allocate(cls): 

622 # FIXME. Should rather go into init. 

623 mkdirs(cls.DEV_BASEDIR) 

624 

625 devname = TapCtl.allocate() 

626 minor = Tapdisk._parse_minor(devname) 

627 return cls(minor) 

628 

629 def free(self): 

630 TapCtl.free(self.minor) 

631 

632 @override 

633 def __str__(self) -> str: 

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

635 

636 @override 

637 def sysfs_devname(self) -> str: 

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

639 

640 class Pool(Attribute): 

641 SYSFS_NODENAME = "pool" 

642 

643 def get_pool_attr(self): 

644 if not self._pool: 

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

646 return self._pool 

647 

648 def get_pool_name(self): 

649 return self.get_pool_attr().readline() 

650 

651 def set_pool_name(self, name): 

652 self.get_pool_attr().writeline(name) 

653 

654 def set_pool_size(self, pages): 

655 self.get_pool().set_size(pages) 

656 

657 def get_pool(self): 

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

659 

660 def set_pool(self, pool): 

661 self.set_pool_name(pool.name) 

662 

663 class Task(Attribute): 

664 SYSFS_NODENAME = "task" 

665 

666 def get_task_attr(self): 

667 if not self._task: 

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

669 return self._task 

670 

671 def get_task_pid(self): 

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

673 try: 

674 return int(pid) 

675 except ValueError: 

676 return None 

677 

678 def find_tapdisk(self): 

679 pid = self.get_task_pid() 

680 if pid is None: 

681 return None 

682 

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

684 

685 def get_tapdisk(self): 

686 tapdisk = self.find_tapdisk() 

687 if not tapdisk: 

688 raise TapdiskNotRunning(minor=self.minor) 

689 return tapdisk 

690 

691 

692class Tapdisk(object): 

693 

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

695 

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

697 self.pid = pid 

698 self.minor = minor 

699 self.type = _type 

700 self.path = path 

701 self.state = state 

702 self._dirty = False 

703 self._blktap = None 

704 

705 @override 

706 def __str__(self) -> str: 

707 state = self.pause_state() 

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

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

710 

711 @classmethod 

712 def list(cls, **args): 

713 

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

715 

716 args = {'pid': None, 

717 'minor': None, 

718 'state': None, 

719 '_type': None, 

720 'path': None} 

721 

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

723 if key in args: 

724 args[key] = val 

725 

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

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

728 args['_type'] = image.type 

729 args['path'] = image.path 

730 

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

732 continue 

733 

734 yield Tapdisk( ** args) 

735 

736 @classmethod 

737 def find(cls, **args): 

738 

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

740 

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

742 raise TapdiskNotUnique(found) 

743 

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

745 return found[0] 

746 

747 return None 

748 

749 @classmethod 

750 def find_by_path(cls, path): 

751 return cls.find(path=path) 

752 

753 @classmethod 

754 def find_by_minor(cls, minor): 

755 return cls.find(minor=minor) 

756 

757 @classmethod 

758 def get(cls, **attrs): 

759 

760 tapdisk = cls.find( ** attrs) 

761 

762 if not tapdisk: 

763 raise TapdiskNotRunning( ** attrs) 

764 

765 return tapdisk 

766 

767 @classmethod 

768 def from_path(cls, path): 

769 return cls.get(path=path) 

770 

771 @classmethod 

772 def from_minor(cls, minor): 

773 return cls.get(minor=minor) 

774 

775 @classmethod 

776 def __from_blktap(cls, blktap): 

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

778 tapdisk._blktap = blktap 

779 return tapdisk 

780 

781 def get_blktap(self): 

782 if not self._blktap: 

783 self._blktap = Blktap(self.minor) 

784 return self._blktap 

785 

786 class Arg: 

787 

788 def __init__(self, _type, path): 

789 self.type = _type 

790 self.path = path 

791 

792 @override 

793 def __str__(self) -> str: 

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

795 

796 @classmethod 

797 def parse(cls, arg): 

798 

799 try: 

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

801 except ValueError: 

802 raise cls.InvalidArgument(arg) 

803 

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

805 raise cls.InvalidType(_type) 

806 

807 return cls(_type, path) 

808 

809 class InvalidType(Exception): 

810 def __init__(self, _type): 

811 self.type = _type 

812 

813 @override 

814 def __str__(self) -> str: 

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

816 

817 class InvalidArgument(Exception): 

818 def __init__(self, arg): 

819 self.arg = arg 

820 

821 @override 

822 def __str__(self) -> str: 

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

824 

825 def get_arg(self): 

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

827 

828 def get_devpath(self): 

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

830 

831 @classmethod 

832 def launch_from_arg(cls, arg): 

833 arg = cls.Arg.parse(arg) 

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

835 

836 @staticmethod 

837 def cgclassify(pid): 

838 

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

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

841 # we have configured in the spec file. 

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

843 try: 

844 util.pread2(cmd) 

845 except util.CommandException as e: 

846 util.logException(e) 

847 

848 @classmethod 

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

850 

851 tapdisk = cls.find_by_path(path) 

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

853 raise TapdiskExists(tapdisk) 

854 

855 minor = blktap.minor 

856 try: 

857 pid = TapCtl.spawn() 

858 cls.cgclassify(pid) 

859 try: 

860 TapCtl.attach(pid, minor) 

861 

862 try: 

863 retry_open = 0 

864 while True: 

865 try: 

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

867 break 

868 except TapCtl.CommandFailure as e: 

869 err = ( 

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

871 ) or None 

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

873 if retry_open < 5: 

874 retry_open += 1 

875 time.sleep(1) 

876 continue 

877 if LINSTOR_AVAILABLE and err == errno.EROFS: 

878 log_drbd_openers(path) 

879 raise 

880 try: 

881 tapdisk = cls.__from_blktap(blktap) 

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

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

884 return tapdisk 

885 except: 

886 TapCtl.close(pid, minor) 

887 raise 

888 

889 except: 

890 TapCtl.detach(pid, minor) 

891 raise 

892 

893 except: 

894 try: 

895 TapCtl.shutdown(pid) 

896 except: 

897 # Best effort to shutdown 

898 pass 

899 raise 

900 

901 except TapCtl.CommandFailure as ctl: 

902 util.logException(ctl) 

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

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

905 raise xs_errors.XenError('TapdiskDriveEmpty') 

906 else: 

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

908 

909 @classmethod 

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

911 blktap = Blktap.allocate() 

912 try: 

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

914 except: 

915 blktap.free() 

916 raise 

917 

918 def shutdown(self, force=False): 

919 

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

921 

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

923 

924 self.get_blktap().free() 

925 

926 def pause(self): 

927 

928 if not self.is_running(): 

929 raise TapdiskInvalidState(self) 

930 

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

932 

933 self._set_dirty() 

934 

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

936 

937 if not self.is_paused(): 

938 raise TapdiskInvalidState(self) 

939 

940 # FIXME: should the arguments be optional? 

941 if _type is None: 

942 _type = self.type 

943 if path is None: 

944 path = self.path 

945 

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

947 cbtlog=cbtlog) 

948 

949 self._set_dirty() 

950 

951 def stats(self): 

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

953 # 

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

955 # 

956 

957 def _set_dirty(self): 

958 self._dirty = True 

959 

960 def _refresh(self, __get): 

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

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

963 

964 @override 

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

966 def __get(name): 

967 # NB. avoid(rec(ursion) 

968 return object.__getattribute__(self, name) 

969 

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

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

972 self._refresh(__get) 

973 self._dirty = False 

974 

975 return __get(name) 

976 

977 class PauseState: 

978 RUNNING = 'R' 

979 PAUSING = 'r' 

980 PAUSED = 'P' 

981 

982 class Flags: 

983 DEAD = 0x0001 

984 CLOSED = 0x0002 

985 QUIESCE_REQUESTED = 0x0004 

986 QUIESCED = 0x0008 

987 PAUSE_REQUESTED = 0x0010 

988 PAUSED = 0x0020 

989 SHUTDOWN_REQUESTED = 0x0040 

990 LOCKING = 0x0080 

991 RETRY_NEEDED = 0x0100 

992 LOG_DROPPED = 0x0200 

993 

994 PAUSE_MASK = PAUSE_REQUESTED | PAUSED 

995 

996 def is_paused(self): 

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

998 

999 def is_running(self): 

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

1001 

1002 def pause_state(self): 

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

1004 return self.PauseState.PAUSED 

1005 

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

1007 return self.PauseState.PAUSING 

1008 

1009 return self.PauseState.RUNNING 

1010 

1011 @staticmethod 

1012 def _parse_minor(devpath): 

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

1014 pattern = re.compile(regex) 

1015 groups = pattern.search(devpath) 

1016 if not groups: 

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

1018 

1019 minor = groups.group(2) 

1020 return int(minor) 

1021 

1022 _major = None 

1023 

1024 @classmethod 

1025 def major(cls): 

1026 if cls._major: 

1027 return cls._major 

1028 

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

1030 for line in devices: 

1031 

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

1033 if len(row) != 2: 

1034 continue 

1035 

1036 major, name = row 

1037 if name != 'tapdev': 

1038 continue 

1039 

1040 cls._major = int(major) 

1041 break 

1042 

1043 devices.close() 

1044 return cls._major 

1045 

1046 

1047class VDI(object): 

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

1049 

1050 CONF_KEY_ALLOW_CACHING = "vdi_allow_caching" 

1051 CONF_KEY_MODE_ON_BOOT = "vdi_on_boot" 

1052 CONF_KEY_CACHE_SR = "local_cache_sr" 

1053 CONF_KEY_O_DIRECT = "o_direct" 

1054 LOCK_CACHE_SETUP = "cachesetup" 

1055 

1056 ATTACH_DETACH_RETRY_SECS = 120 

1057 

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

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

1060 self._vdi_uuid = uuid 

1061 self._session = target.session 

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

1063 self.__o_direct = None 

1064 self.__o_direct_reason = None 

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

1066 self.tap = None 

1067 

1068 def get_o_direct_capability(self, options): 

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

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

1071 return self.__o_direct, self.__o_direct_reason 

1072 

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

1074 self.__o_direct = True 

1075 self.__o_direct_reason = "LICENSE_RESTRICTION" 

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

1077 self.__o_direct = True 

1078 self.__o_direct_reason = "SR_NOT_SUPPORTED" 

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

1080 self.__o_direct = True 

1081 self.__o_direct_reason = "RO_WITH_NO_PARENT" 

1082 elif options.get(self.CONF_KEY_O_DIRECT): 

1083 self.__o_direct = True 

1084 self.__o_direct_reason = "SR_OVERRIDE" 

1085 

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

1087 self.__o_direct = False 

1088 self.__o_direct_reason = "" 

1089 

1090 return self.__o_direct, self.__o_direct_reason 

1091 

1092 @classmethod 

1093 def from_cli(cls, uuid): 

1094 session = XenAPI.xapi_local() 

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

1096 

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

1098 driver_info = target.sr.srcmd.driver_info 

1099 

1100 session.xenapi.session.logout() 

1101 

1102 return cls(uuid, target, driver_info) 

1103 

1104 @staticmethod 

1105 def _tap_type(vdi_type): 

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

1107 return { 

1108 'raw': 'aio', 

1109 'vhd': 'vhd', 

1110 'qcow2': 'qcow2', 

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

1112 'aio': 'aio', # for LVHD 

1113 'file': 'aio', 

1114 'phy': 'aio' 

1115 }[vdi_type] 

1116 

1117 def get_tap_type(self): 

1118 vdi_type = self.target.get_vdi_type() 

1119 return VDI._tap_type(vdi_type) 

1120 

1121 def get_phy_path(self): 

1122 return self.target.get_vdi_path() 

1123 

1124 class UnexpectedVDIType(Exception): 

1125 

1126 def __init__(self, vdi_type, target): 

1127 self.vdi_type = vdi_type 

1128 self.target = target 

1129 

1130 @override 

1131 def __str__(self) -> str: 

1132 return \ 

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

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

1135 

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

1137 'raw': 'phy', 

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

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

1140 'file': 'tap', 

1141 'vhd': 'tap', 

1142 'qcow2': 'tap'} 

1143 

1144 def tap_wanted(self): 

1145 # 1. Let the target vdi_type decide 

1146 

1147 vdi_type = self.target.get_vdi_type() 

1148 

1149 try: 

1150 plug_type = self.VDI_PLUG_TYPE[vdi_type] 

1151 except KeyError: 

1152 raise self.UnexpectedVDIType(vdi_type, 

1153 self.target.vdi) 

1154 

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

1156 return True 

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

1158 return True 

1159 # 2. Otherwise, there may be more reasons 

1160 # 

1161 # .. TBD 

1162 

1163 return False 

1164 

1165 class TargetDriver: 

1166 """Safe target driver access.""" 

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

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

1169 # try/except would risk breaking compatibility. 

1170 

1171 def __init__(self, vdi, driver_info): 

1172 self.vdi = vdi 

1173 self._caps = driver_info['capabilities'] 

1174 

1175 def has_cap(self, cap): 

1176 """Determine if target has given capability""" 

1177 return cap in self._caps 

1178 

1179 def attach(self, sr_uuid, vdi_uuid): 

1180 #assert self.has_cap("VDI_ATTACH") 

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

1182 

1183 def detach(self, sr_uuid, vdi_uuid): 

1184 #assert self.has_cap("VDI_DETACH") 

1185 self.vdi.detach(sr_uuid, vdi_uuid) 

1186 

1187 def activate(self, sr_uuid, vdi_uuid): 

1188 if self.has_cap("VDI_ACTIVATE"): 

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

1190 

1191 def deactivate(self, sr_uuid, vdi_uuid): 

1192 if self.has_cap("VDI_DEACTIVATE"): 

1193 self.vdi.deactivate(sr_uuid, vdi_uuid) 

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

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

1196 

1197 def get_vdi_type(self): 

1198 _type = self.vdi.vdi_type 

1199 if not _type: 

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

1201 return _type 

1202 

1203 def get_vdi_path(self): 

1204 return self.vdi.path 

1205 

1206 class Link(object): 

1207 """Relink a node under a common name""" 

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

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

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

1211 # relink existing devices under deterministic path names. 

1212 

1213 BASEDIR: ClassVar[str] = "" 

1214 

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

1216 pass 

1217 

1218 @abstractmethod 

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

1220 pass 

1221 

1222 def __init__(self, path): 

1223 self._path = path 

1224 

1225 @classmethod 

1226 def from_name(cls, name): 

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

1228 return cls(path) 

1229 

1230 @classmethod 

1231 def from_uuid(cls, sr_uuid, vdi_uuid): 

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

1233 return cls.from_name(name) 

1234 

1235 def path(self): 

1236 return self._path 

1237 

1238 def stat(self): 

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

1240 

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

1242 

1243 path = self.path() 

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

1245 

1246 mkdirs(os.path.dirname(path)) 

1247 try: 

1248 self._mklink(target) 

1249 except OSError as e: 

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

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

1252 # be seen. 

1253 if e.errno != errno.EEXIST: 

1254 raise 

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

1256 

1257 def unlink(self): 

1258 try: 

1259 os.unlink(self.path()) 

1260 except OSError as e: 

1261 if e.errno != errno.ENOENT: 

1262 raise 

1263 

1264 @override 

1265 def __str__(self) -> str: 

1266 path = self.path() 

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

1268 

1269 class SymLink(Link): 

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

1271 

1272 def readlink(self): 

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

1274 

1275 def symlink(self): 

1276 return self.path() 

1277 

1278 @override 

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

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

1281 

1282 @override 

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

1284 return self.readlink() == target 

1285 

1286 class DeviceNode(Link): 

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

1288 

1289 @classmethod 

1290 def _real_stat(cls, target): 

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

1292 _target = os.path.realpath(target) 

1293 return os.stat(_target) 

1294 

1295 @classmethod 

1296 def is_block(cls, target): 

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

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

1299 

1300 @override 

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

1302 

1303 st = self._real_stat(target) 

1304 if not S_ISBLK(st.st_mode): 

1305 raise self.NotABlockDevice(target, st) 

1306 

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

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

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

1310 

1311 @override 

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

1313 target_rdev = self._real_stat(target).st_rdev 

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

1315 

1316 def rdev(self): 

1317 st = self.stat() 

1318 assert S_ISBLK(st.st_mode) 

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

1320 

1321 class NotABlockDevice(Exception): 

1322 

1323 def __init__(self, path, st): 

1324 self.path = path 

1325 self.st = st 

1326 

1327 @override 

1328 def __str__(self) -> str: 

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

1330 

1331 class Hybrid(Link): 

1332 

1333 def __init__(self, path): 

1334 VDI.Link.__init__(self, path) 

1335 self._devnode = VDI.DeviceNode(path) 

1336 self._symlink = VDI.SymLink(path) 

1337 

1338 def rdev(self): 

1339 st = self.stat() 

1340 if S_ISBLK(st.st_mode): 

1341 return self._devnode.rdev() 

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

1343 

1344 @override 

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

1346 if self._devnode.is_block(target): 

1347 self._obj = self._devnode 

1348 else: 

1349 self._obj = self._symlink 

1350 self._obj.mklink(target) 

1351 

1352 @override 

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

1354 return self._obj._equals(target) 

1355 

1356 class PhyLink(SymLink): 

1357 BASEDIR = "/dev/sm/phy" 

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

1359 

1360 class NBDLink(SymLink): 

1361 

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

1363 

1364 class BackendLink(Hybrid): 

1365 BASEDIR = "/dev/sm/backend" 

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

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

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

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

1370 # soon as ISOs are tapdisks. 

1371 

1372 @staticmethod 

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

1374 

1375 tapdisk = Tapdisk.find_by_path(phy_path) 

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

1377 blktap = Blktap.allocate() 

1378 blktap.set_pool_name(sr_uuid) 

1379 if pool_size: 

1380 blktap.set_pool_size(pool_size) 

1381 

1382 try: 

1383 tapdisk = \ 

1384 Tapdisk.launch_on_tap(blktap, 

1385 phy_path, 

1386 VDI._tap_type(vdi_type), 

1387 options) 

1388 except: 

1389 blktap.free() 

1390 raise 

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

1392 

1393 else: 

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

1395 

1396 return tapdisk.get_devpath(), tapdisk 

1397 

1398 @staticmethod 

1399 def _tap_deactivate(minor): 

1400 

1401 try: 

1402 tapdisk = Tapdisk.from_minor(minor) 

1403 except TapdiskNotRunning as e: 

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

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

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

1407 # the recorded minor. 

1408 else: 

1409 tapdisk.shutdown() 

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

1411 

1412 @classmethod 

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

1414 """ 

1415 Pauses the tapdisk. 

1416 

1417 session: a XAPI session 

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

1419 vdi_uuid: the UUID of the VDI to pause 

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

1421 non-blocking manner 

1422 """ 

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

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

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

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

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

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

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

1430 if not cls.call_pluginhandler(session, host_ref, 

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

1432 # Failed to pause node 

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

1434 return False 

1435 return True 

1436 

1437 @classmethod 

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

1439 activate_parents=False): 

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

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

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

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

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

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

1446 if not cls.call_pluginhandler(session, host_ref, 

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

1448 # Failed to unpause node 

1449 return False 

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

1451 return True 

1452 

1453 @classmethod 

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

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

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

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

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

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

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

1461 if not cls.call_pluginhandler(session, host_ref, 

1462 sr_uuid, vdi_uuid, "refresh", None, 

1463 activate_parents=activate_parents): 

1464 # Failed to refresh node 

1465 return False 

1466 return True 

1467 

1468 @classmethod 

1469 def tap_status(cls, session, vdi_uuid): 

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

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

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

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

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

1475 return True 

1476 return False 

1477 

1478 @classmethod 

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

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

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

1482 try: 

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

1484 "failfast": str(failfast)} 

1485 if secondary: 

1486 args["secondary"] = secondary 

1487 if activate_parents: 

1488 args["activate_parents"] = "true" 

1489 ret = session.xenapi.host.call_plugin( 

1490 host_ref, PLUGIN_TAP_PAUSE, action, 

1491 args) 

1492 return ret == "True" 

1493 except Exception as e: 

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

1495 return False 

1496 

1497 def _add_tag(self, vdi_uuid, writable): 

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

1499 attach_mode = "RO" 

1500 if writable: 

1501 attach_mode = "RW" 

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

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

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

1505 attached_as = util.attached_as(sm_config) 

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

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

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

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

1510 term_output=False, writable=writable): 

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

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

1513 if 'relinking' in sm_config: 

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

1515 return False 

1516 if 'paused' in sm_config: 

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

1518 return False 

1519 try: 

1520 self._session.xenapi.VDI.add_to_sm_config( 

1521 vdi_ref, 'activating', 'True') 

1522 except XenAPI.Failure as e: 

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

1524 # Someone else is activating - a retry might succeed 

1525 return False 

1526 raise 

1527 host_key = "host_%s" % host_ref 

1528 assert host_key not in sm_config 

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

1530 attach_mode) 

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

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

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

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

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

1536 self._session.xenapi.VDI.remove_from_sm_config( 

1537 vdi_ref, 'activating') 

1538 return False 

1539 util.SMlog("Activate lock succeeded") 

1540 return True 

1541 

1542 def _check_tag(self, vdi_uuid): 

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

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

1545 if 'paused' in sm_config: 

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

1547 return False 

1548 return True 

1549 

1550 def _remove_tag(self, vdi_uuid): 

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

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

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

1554 host_key = "host_%s" % host_ref 

1555 if host_key in sm_config: 

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

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

1558 else: 

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

1560 

1561 def _get_pool_config(self, pool_name): 

1562 pool_info = dict() 

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

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

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

1566 # special pool 

1567 return pool_info 

1568 

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

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

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

1572 pool_size_str = sr_config.get(POOL_SIZE_KEY) 

1573 pool_name_override = vdi_config.get(POOL_NAME_KEY) 

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

1575 pool_name = pool_name_override 

1576 pool_size_override = vdi_config.get(POOL_SIZE_KEY) 

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

1578 pool_size_str = pool_size_override 

1579 pool_size = 0 

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

1581 try: 

1582 pool_size = int(pool_size_str) 

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

1584 raise ValueError("outside of range") 

1585 pool_size = NUM_PAGES_PER_RING * pool_size 

1586 except ValueError: 

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

1588 pool_size = 0 

1589 

1590 pool_info["mem-pool"] = pool_name 

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

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

1593 

1594 return pool_info 

1595 

1596 def linkNBD(self, sr_uuid, vdi_uuid): 

1597 if self.tap: 

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

1599 int(self.tap.minor)) 

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

1601 

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

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

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

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

1606 util.SMlog("Attach & activate") 

1607 self._attach(sr_uuid, vdi_uuid) 

1608 dev_path = self._activate(sr_uuid, vdi_uuid, 

1609 {"rdonly": not writable}) 

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

1611 self.linkNBD(sr_uuid, vdi_uuid) 

1612 

1613 # Return backend/ link 

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

1615 if self.tap_wanted(): 

1616 # Only have NBD if we also have a tap 

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

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

1619 vdi_uuid) 

1620 else: 

1621 nbd_path = "" 

1622 

1623 options = {"rdonly": not writable} 

1624 options.update(caching_params) 

1625 o_direct, o_direct_reason = self.get_o_direct_capability(options) 

1626 struct = {'params': back_path, 

1627 'params_nbd': nbd_path, 

1628 'o_direct': o_direct, 

1629 'o_direct_reason': o_direct_reason, 

1630 'xenstore_data': self.xenstore_data} 

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

1632 

1633 try: 

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

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

1636 f.close() 

1637 except: 

1638 pass 

1639 

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

1641 

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

1643 util.SMlog("blktap2.activate") 

1644 options = {"rdonly": not writable} 

1645 options.update(caching_params) 

1646 

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

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

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

1650 try: 

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

1652 return 

1653 except util.SRBusyException: 

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

1655 time.sleep(1) 

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

1657 

1658 def _get_sr_master_host_ref(self) -> str: 

1659 """ 

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

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

1662 """ 

1663 sr = self.target.vdi.sr 

1664 if sr.is_shared(): 

1665 host_ref = util.get_master_ref(self._session) 

1666 else: 

1667 host_ref = sr.host_ref 

1668 return host_ref 

1669 

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

1671 vdi_chain = [] 

1672 path = self.target.get_vdi_path() 

1673 

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

1675 vdi_chain.append(extractUuid(path)) 

1676 parent = cowutil.getParentNoCheck(path) 

1677 while parent: 

1678 vdi_chain.append(extractUuid(parent)) 

1679 parent = cowutil.getParentNoCheck(parent) 

1680 vdi_chain.reverse() 

1681 return vdi_chain 

1682 

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

1684 vdi_type = self.target.get_vdi_type() 

1685 cowutil = getCowUtil(vdi_type) 

1686 

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

1688 return True 

1689 

1690 path = self.target.get_vdi_path() 

1691 

1692 import fjournaler 

1693 import journaler 

1694 from lvmcowutil import LvmCowUtil 

1695 from FileSR import FileVDI 

1696 import lvmcache 

1697 

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

1699 # Different extractUUID & journaler function for LVMSR and FileSR 

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

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

1702 lvmCache = lvmcache.LVMCache(vgName) 

1703 journal = journaler.Journaler(lvmCache) 

1704 

1705 extractUuid = LvmCowUtil.extractUuid 

1706 else: 

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

1708 extractUuid = FileVDI.extractUuid 

1709 

1710 # Get the VDI chain 

1711 vdi_chain = self._get_vdi_chain(cowutil, extractUuid) 

1712 

1713 if len(vdi_chain) == 1: 

1714 # We only have a leaf, do nothing 

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

1716 return True 

1717 

1718 # Log the chain of active VDI 

1719 level = 0 

1720 util.SMlog("VDI chain:") 

1721 for vdi in vdi_chain: 

1722 prefix = " " * level 

1723 level += 1 

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

1725 

1726 vdi_to_cancel = [] 

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

1728 if entry in vdi_chain: 

1729 vdi_to_cancel.append(entry) 

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

1731 

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

1733 host_ref = self._get_sr_master_host_ref() 

1734 for vdi in vdi_to_cancel: 

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

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

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

1738 host_ref, PLUGIN_ON_SLAVE, "cancel_coalesce_master", args) 

1739 

1740 return True 

1741 

1742 @locking("VDIUnavailable") 

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

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

1745 

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

1747 refresh = False 

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

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

1750 return False 

1751 refresh = True 

1752 

1753 try: 

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

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

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

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

1758 # object completely 

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

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

1761 target.sr.srcmd.params = params 

1762 driver_info = target.sr.srcmd.driver_info 

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

1764 

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

1766 "blktap_activate_inject_failure", 

1767 lambda: util.inject_failure()) 

1768 

1769 # Attach the physical node 

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

1771 self._attach(sr_uuid, vdi_uuid) 

1772 

1773 vdi_type = self.target.get_vdi_type() 

1774 

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

1776 return False 

1777 

1778 # Take lvchange-p Lock before running 

1779 # tap-ctl open 

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

1781 # now taking the same lock 

1782 # This is a fix for CA-155766 

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

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

1785 VdiType.isCowImage(vdi_type): 

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

1787 lock.acquire() 

1788 

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

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

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

1792 session = self.target.vdi.session 

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

1794 # pylint: disable=used-before-assignment 

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

1796 if 'key_hash' in sm_config: 

1797 key_hash = sm_config['key_hash'] 

1798 options['key_hash'] = key_hash 

1799 options['vdi_uuid'] = vdi_uuid 

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

1801 # Activate the physical node 

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

1803 

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

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

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

1807 lock.release() 

1808 except: 

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

1810 if self.tap_wanted(): 

1811 util.fistpoint.activate_custom_fn( 

1812 "blktap_activate_error_handling", 

1813 lambda: time.sleep(30)) 

1814 while True: 

1815 try: 

1816 self._remove_tag(vdi_uuid) 

1817 break 

1818 except xmlrpc.client.ProtocolError as e: 

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

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

1821 continue 

1822 else: 

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

1824 break 

1825 except Exception as e: 

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

1827 break 

1828 raise 

1829 finally: 

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

1831 self._session.xenapi.VDI.remove_from_sm_config( 

1832 vdi_ref, 'activating') 

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

1834 

1835 # Link result to backend/ 

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

1837 self.linkNBD(sr_uuid, vdi_uuid) 

1838 return True 

1839 

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

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

1842 

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

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

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

1846 # Maybe launch a tapdisk on the physical link 

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

1848 vdi_type = self.target.get_vdi_type() 

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

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

1851 options.update(vdi_options) 

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

1853 sr_uuid, options, 

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

1855 else: 

1856 dev_path = phy_path # Just reuse phy 

1857 

1858 return dev_path 

1859 

1860 def _attach(self, sr_uuid, vdi_uuid): 

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

1862 params = attach_info['params'] 

1863 xenstore_data = attach_info['xenstore_data'] 

1864 phy_path = util.to_plain_string(params) 

1865 self.xenstore_data.update(xenstore_data) 

1866 # Save it to phy/ 

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

1868 

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

1870 util.SMlog("blktap2.deactivate") 

1871 for i in range(self.ATTACH_DETACH_RETRY_SECS): 

1872 try: 

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

1874 return 

1875 except util.SRBusyException as e: 

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

1877 time.sleep(1) 

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

1879 

1880 @locking("VDIUnavailable") 

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

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

1883 

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

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

1886 return False 

1887 

1888 self._deactivate(sr_uuid, vdi_uuid, caching_params) 

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

1890 self._detach(sr_uuid, vdi_uuid) 

1891 if self.tap_wanted(): 

1892 self._remove_tag(vdi_uuid) 

1893 

1894 return True 

1895 

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

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

1898 

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

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

1901 util.SMlog("Deactivate & detach") 

1902 self._deactivate(sr_uuid, vdi_uuid, caching_params) 

1903 self._detach(sr_uuid, vdi_uuid) 

1904 else: 

1905 pass # nothing to do 

1906 

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

1908 # Shutdown tapdisk 

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

1910 

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

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

1913 return 

1914 

1915 try: 

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

1917 os.unlink(attach_info_path) 

1918 except: 

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

1920 

1921 try: 

1922 major, minor = back_link.rdev() 

1923 except self.DeviceNode.NotABlockDevice: 

1924 pass 

1925 else: 

1926 if major == Tapdisk.major(): 

1927 self._tap_deactivate(minor) 

1928 self.remove_cache(caching_params) 

1929 

1930 # Remove the backend link 

1931 back_link.unlink() 

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

1933 

1934 # Deactivate & detach the physical node 

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

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

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

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

1939 # object completely 

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

1941 driver_info = target.sr.srcmd.driver_info 

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

1943 

1944 self.target.deactivate(sr_uuid, vdi_uuid) 

1945 

1946 def _detach(self, sr_uuid, vdi_uuid): 

1947 self.target.detach(sr_uuid, vdi_uuid) 

1948 

1949 # Remove phy/ 

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

1951 

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

1953 # Remove existing VDI.sm_config fields 

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

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

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

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

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

1959 if not caching is None: 

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

1961 

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

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

1964 return 

1965 

1966 util.SMlog("Requested local caching") 

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

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

1969 return 

1970 

1971 scratch_mode = False 

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

1973 scratch_mode = True 

1974 util.SMlog("Requested scratch mode") 

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

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

1977 return 

1978 

1979 dev_path = None 

1980 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR) 

1981 if not local_sr_uuid: 

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

1983 return 

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

1985 local_sr_uuid, scratch_mode, params) 

1986 

1987 if dev_path: 

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

1989 params.get(self.CONF_KEY_MODE_ON_BOOT), 

1990 params.get(self.CONF_KEY_ALLOW_CACHING)) 

1991 

1992 return dev_path 

1993 

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

1995 vm_uuid = None 

1996 vm_label = "" 

1997 try: 

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

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

2000 cache_sr_label = cache_sr_rec.get("name_label") 

2001 

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

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

2004 host_label = host_rec.get("name_label") 

2005 

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

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

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

2009 for vbd_rec in vbds.values(): 

2010 vm_ref = vbd_rec.get("VM") 

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

2012 vm_uuid = vm_rec.get("uuid") 

2013 vm_label = vm_rec.get("name_label") 

2014 except: 

2015 util.logException("alert_no_cache") 

2016 

2017 alert_obj = "SR" 

2018 alert_uuid = str(cache_sr_uuid) 

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

2020 if vm_uuid: 

2021 alert_obj = "VM" 

2022 alert_uuid = vm_uuid 

2023 reason = "" 

2024 if err == errno.ENOSPC: 

2025 reason = "because there is no space left" 

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

2027 (vm_label, reason, cache_sr_label, host_label) 

2028 

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

2030 (alert_obj, alert_uuid, alert_str)) 

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

2032 alert_obj, alert_uuid, alert_str) 

2033 

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

2035 scratch_mode, options): 

2036 import SR 

2037 import EXTSR 

2038 

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

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

2041 self.target.vdi.uuid) 

2042 return 

2043 

2044 util.SMlog("Setting up cache") 

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

2046 

2047 if shared_target.parent: 

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

2049 shared_target.uuid) 

2050 return 

2051 

2052 SR.registerSR(EXTSR.EXTSR) 

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

2054 

2055 vdi_type = self.target.get_vdi_type() 

2056 tap_type = VDI._tap_type(vdi_type) 

2057 cowutil = getCowUtil(vdi_type) 

2058 

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

2060 lock.acquire() 

2061 

2062 # read cache 

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

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

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

2066 read_cache_path) 

2067 else: 

2068 try: 

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

2070 except util.CommandException as e: 

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

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

2073 return None 

2074 

2075 # local write node 

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

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

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

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

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

2081 local_leaf_path) 

2082 os.unlink(local_leaf_path) 

2083 try: 

2084 cowutil.snapshot(local_leaf_path, read_cache_path, False, 

2085 msize=leaf_size, checkEmpty=False) 

2086 except util.CommandException as e: 

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

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

2089 return None 

2090 

2091 local_leaf_size = cowutil.getSizeVirt(local_leaf_path) 

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

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

2094 (leaf_size, local_leaf_size)) 

2095 cowutil.setSizeVirtFast(local_leaf_path, leaf_size) 

2096 

2097 prt_tapdisk = Tapdisk.find_by_path(read_cache_path) 

2098 if not prt_tapdisk: 

2099 parent_options = copy.deepcopy(options) 

2100 parent_options["rdonly"] = False 

2101 parent_options["lcache"] = True 

2102 

2103 blktap = Blktap.allocate() 

2104 try: 

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

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

2107 # its own pool 

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

2109 except: 

2110 blktap.free() 

2111 raise 

2112 

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

2114 

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

2116 leaf_tapdisk = Tapdisk.find_by_path(local_leaf_path) 

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

2118 blktap = Blktap.allocate() 

2119 child_options = copy.deepcopy(options) 

2120 child_options["rdonly"] = False 

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

2122 child_options["existing_prt"] = prt_tapdisk.minor 

2123 child_options["secondary"] = secondary 

2124 child_options["standby"] = scratch_mode 

2125 # Disable memory read caching 

2126 child_options.pop("o_direct", None) 

2127 try: 

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

2129 except: 

2130 blktap.free() 

2131 raise 

2132 

2133 lock.release() 

2134 

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

2136 (read_cache_path, local_leaf_path)) 

2137 

2138 self.tap = leaf_tapdisk 

2139 return leaf_tapdisk.get_devpath() 

2140 

2141 def remove_cache(self, params): 

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

2143 return 

2144 

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

2146 

2147 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR) 

2148 if caching and not local_sr_uuid: 

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

2150 return 

2151 

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

2153 self._remove_cache(self._session, local_sr_uuid) 

2154 

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

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

2157 

2158 def _is_tapdisk_in_use(self, minor): 

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

2160 if not retVal: 

2161 # err on the side of caution 

2162 return True 

2163 

2164 for link in links: 

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

2166 return True 

2167 

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

2169 for s in sockets: 

2170 if socket_re.match(s): 

2171 return True 

2172 

2173 return False 

2174 

2175 def _remove_cache(self, session, local_sr_uuid): 

2176 import SR 

2177 import EXTSR 

2178 

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

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

2181 self.target.vdi.uuid) 

2182 return 

2183 

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

2185 

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

2187 

2188 SR.registerSR(EXTSR.EXTSR) 

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

2190 

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

2192 lock.acquire() 

2193 

2194 # local write node 

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

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

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

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

2199 os.unlink(local_leaf_path) 

2200 

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

2202 prt_tapdisk = Tapdisk.find_by_path(read_cache_path) 

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

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

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

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

2207 read_cache_path) 

2208 try: 

2209 prt_tapdisk.shutdown() 

2210 except: 

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

2212 else: 

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

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

2215 # GC run 

2216 

2217 lock.release() 

2218 

2219 @staticmethod 

2220 def _no_parent(vdi): 

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

2222 

2223 

2224PythonKeyError = KeyError 

2225 

2226 

2227class UEventHandler(object): 

2228 

2229 def __init__(self): 

2230 self._action = None 

2231 

2232 class KeyError(PythonKeyError): 

2233 def __init__(self, args): 

2234 super().__init__(args) 

2235 self.key = args[0] 

2236 

2237 @override 

2238 def __str__(self) -> str: 

2239 return \ 

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

2241 "Not called in udev context?" 

2242 

2243 @classmethod 

2244 def getenv(cls, key): 

2245 try: 

2246 return os.environ[key] 

2247 except KeyError as e: 

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

2249 

2250 def get_action(self): 

2251 if not self._action: 

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

2253 return self._action 

2254 

2255 class UnhandledEvent(Exception): 

2256 

2257 def __init__(self, event, handler): 

2258 self.event = event 

2259 self.handler = handler 

2260 

2261 @override 

2262 def __str__(self) -> str: 

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

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

2265 

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

2267 

2268 def run(self): 

2269 

2270 action = self.get_action() 

2271 try: 

2272 fn = self.ACTIONS[action] 

2273 except KeyError: 

2274 raise self.UnhandledEvent(action, self) 

2275 

2276 return fn(self) 

2277 

2278 @override 

2279 def __str__(self) -> str: 

2280 try: 

2281 action = self.get_action() 

2282 except: 

2283 action = None 

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

2285 

2286 

2287class __BlktapControl(ClassDevice): 

2288 SYSFS_CLASSTYPE = "misc" 

2289 

2290 def __init__(self): 

2291 ClassDevice.__init__(self) 

2292 self._default_pool = None 

2293 

2294 @override 

2295 def sysfs_devname(self) -> str: 

2296 return "blktap!control" 

2297 

2298 class DefaultPool(Attribute): 

2299 SYSFS_NODENAME = "default_pool" 

2300 

2301 def get_default_pool_attr(self): 

2302 if not self._default_pool: 

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

2304 return self._default_pool 

2305 

2306 def get_default_pool_name(self): 

2307 return self.get_default_pool_attr().readline() 

2308 

2309 def set_default_pool_name(self, name): 

2310 self.get_default_pool_attr().writeline(name) 

2311 

2312 def get_default_pool(self): 

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

2314 

2315 def set_default_pool(self, pool): 

2316 self.set_default_pool_name(pool.name) 

2317 

2318 class NoSuchPool(Exception): 

2319 def __init__(self, name): 

2320 self.name = name 

2321 

2322 @override 

2323 def __str__(self) -> str: 

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

2325 

2326 def get_pool(self, name): 

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

2328 

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

2330 raise self.NoSuchPool(name) 

2331 

2332 return PagePool(path) 

2333 

2334BlktapControl = __BlktapControl() 

2335 

2336 

2337class PagePool(KObject): 

2338 

2339 def __init__(self, path): 

2340 self.path = path 

2341 self._size = None 

2342 

2343 @override 

2344 def sysfs_devname(self) -> str: 

2345 return '' 

2346 

2347 def sysfs_path(self): 

2348 return self.path 

2349 

2350 class Size(Attribute): 

2351 SYSFS_NODENAME = "size" 

2352 

2353 def get_size_attr(self): 

2354 if not self._size: 

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

2356 return self._size 

2357 

2358 def set_size(self, pages): 

2359 pages = str(pages) 

2360 self.get_size_attr().writeline(pages) 

2361 

2362 def get_size(self): 

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

2364 return int(pages) 

2365 

2366 

2367class BusDevice(KObject): 

2368 

2369 SYSFS_BUSTYPE: ClassVar[str] = "" 

2370 

2371 @classmethod 

2372 def sysfs_bus_path(cls): 

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

2374 

2375 def sysfs_path(self): 

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

2377 self.sysfs_devname()) 

2378 

2379 return path 

2380 

2381 

2382class XenbusDevice(BusDevice): 

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

2384 

2385 XBT_NIL = "" 

2386 

2387 XENBUS_DEVTYPE: ClassVar[str] = "" 

2388 

2389 def __init__(self, domid, devid): 

2390 self.domid = int(domid) 

2391 self.devid = int(devid) 

2392 self._xbt = XenbusDevice.XBT_NIL 

2393 

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

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

2396 

2397 def xs_path(self, key=None): 

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

2399 self.domid, 

2400 self.devid) 

2401 if key is not None: 

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

2403 

2404 return path 

2405 

2406 def _log(self, prio, msg): 

2407 syslog(prio, msg) 

2408 

2409 def info(self, msg): 

2410 self._log(_syslog.LOG_INFO, msg) 

2411 

2412 def warn(self, msg): 

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

2414 

2415 def _xs_read_path(self, path): 

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

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

2418 return val 

2419 

2420 def _xs_write_path(self, path, val): 

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

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

2423 

2424 def _xs_rm_path(self, path): 

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

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

2427 

2428 def read(self, key): 

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

2430 

2431 def has_xs_key(self, key): 

2432 return self.read(key) is not None 

2433 

2434 def write(self, key, val): 

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

2436 

2437 def rm(self, key): 

2438 self._xs_rm_path(self.xs_path(key)) 

2439 

2440 def exists(self): 

2441 return self.has_xs_key(None) 

2442 

2443 def begin(self): 

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

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

2446 

2447 def commit(self): 

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

2449 self._xbt = XenbusDevice.XBT_NIL 

2450 return ok 

2451 

2452 def abort(self): 

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

2454 assert(ok == True) 

2455 self._xbt = XenbusDevice.XBT_NIL 

2456 

2457 def create_physical_device(self): 

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

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

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

2461 return 

2462 try: 

2463 params = self.read("params") 

2464 frontend = self.read("frontend") 

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

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

2467 # from opening the physical-device 

2468 if not(is_cdrom): 

2469 major_minor = os.stat(params).st_rdev 

2470 major, minor = divmod(major_minor, 256) 

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

2472 except: 

2473 util.logException("BLKTAP2:create_physical_device") 

2474 

2475 def signal_hotplug(self, online=True): 

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

2477 self.XENBUS_DEVTYPE, 

2478 self.devid) 

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

2480 if online: 

2481 self._xs_write_path(xapi_path, "online") 

2482 self._xs_write_path(upstream_path, "connected") 

2483 else: 

2484 self._xs_rm_path(xapi_path) 

2485 self._xs_rm_path(upstream_path) 

2486 

2487 @override 

2488 def sysfs_devname(self) -> str: 

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

2490 self.domid, self.devid) 

2491 

2492 @override 

2493 def __str__(self) -> str: 

2494 return self.sysfs_devname() 

2495 

2496 @classmethod 

2497 def find(cls): 

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

2499 cls.XENBUS_DEVTYPE) 

2500 for path in glob.glob(pattern): 

2501 

2502 name = os.path.basename(path) 

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

2504 

2505 yield cls(domid, devid) 

2506 

2507 

2508class XenBackendDevice(XenbusDevice): 

2509 """Xenbus backend device""" 

2510 SYSFS_BUSTYPE = "xen-backend" 

2511 

2512 @classmethod 

2513 def from_xs_path(cls, _path): 

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

2515 

2516 assert _backend == 'backend' 

2517 assert _type == cls.XENBUS_DEVTYPE 

2518 

2519 domid = int(domid) 

2520 devid = int(devid) 

2521 

2522 return cls(domid, devid) 

2523 

2524 

2525class Blkback(XenBackendDevice): 

2526 """A blkback VBD""" 

2527 

2528 XENBUS_DEVTYPE = "vbd" 

2529 

2530 def __init__(self, domid, devid): 

2531 XenBackendDevice.__init__(self, domid, devid) 

2532 self._phy = None 

2533 self._vdi_uuid = None 

2534 self._q_state = None 

2535 self._q_events = None 

2536 

2537 class XenstoreValueError(Exception): 

2538 KEY: ClassVar[str] = "" 

2539 

2540 def __init__(self, vbd, _str): 

2541 self.vbd = vbd 

2542 self.str = _str 

2543 

2544 @override 

2545 def __str__(self) -> str: 

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

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

2548 

2549 class PhysicalDeviceError(XenstoreValueError): 

2550 KEY = "physical-device" 

2551 

2552 class PhysicalDevice(object): 

2553 

2554 def __init__(self, major, minor): 

2555 self.major = int(major) 

2556 self.minor = int(minor) 

2557 

2558 @classmethod 

2559 def from_xbdev(cls, xbdev): 

2560 

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

2562 

2563 try: 

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

2565 major = int(major, 0x10) 

2566 minor = int(minor, 0x10) 

2567 except Exception as e: 

2568 raise xbdev.PhysicalDeviceError(xbdev, phy) 

2569 

2570 return cls(major, minor) 

2571 

2572 def makedev(self): 

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

2574 

2575 def is_tap(self): 

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

2577 

2578 @override 

2579 def __str__(self) -> str: 

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

2581 

2582 @override 

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

2584 return \ 

2585 self.major == other.major and \ 

2586 self.minor == other.minor 

2587 

2588 def get_physical_device(self): 

2589 if not self._phy: 

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

2591 return self._phy 

2592 

2593 class QueueEvents(Attribute): 

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

2595 notifications emitted.""" 

2596 

2597 SYSFS_NODENAME = "queue_events" 

2598 

2599 QUEUE_RUNNING = (1 << 0) 

2600 QUEUE_PAUSE_DONE = (1 << 1) 

2601 QUEUE_SHUTDOWN_DONE = (1 << 2) 

2602 QUEUE_PAUSE_REQUEST = (1 << 3) 

2603 QUEUE_SHUTDOWN_REQUEST = (1 << 4) 

2604 

2605 def get_mask(self): 

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

2607 

2608 def set_mask(self, mask): 

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

2610 

2611 def get_queue_events(self): 

2612 if not self._q_events: 

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

2614 return self._q_events 

2615 

2616 def get_vdi_uuid(self): 

2617 if not self._vdi_uuid: 

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

2619 return self._vdi_uuid 

2620 

2621 def pause_requested(self): 

2622 return self.has_xs_key("pause") 

2623 

2624 def shutdown_requested(self): 

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

2626 

2627 def shutdown_done(self): 

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

2629 

2630 def running(self): 

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

2632 

2633 @classmethod 

2634 def find_by_physical_device(cls, phy): 

2635 for dev in cls.find(): 

2636 try: 

2637 _phy = dev.get_physical_device() 

2638 except cls.PhysicalDeviceError: 

2639 continue 

2640 

2641 if _phy == phy: 

2642 yield dev 

2643 

2644 @classmethod 

2645 def find_by_tap_minor(cls, minor): 

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

2647 return cls.find_by_physical_device(phy) 

2648 

2649 @classmethod 

2650 def find_by_tap(cls, tapdisk): 

2651 return cls.find_by_tap_minor(tapdisk.minor) 

2652 

2653 def has_tap(self): 

2654 

2655 if not self.can_tap(): 

2656 return False 

2657 

2658 phy = self.get_physical_device() 

2659 if phy: 

2660 return phy.is_tap() 

2661 

2662 return False 

2663 

2664 def is_bare_hvm(self): 

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

2666 try: 

2667 self.get_physical_device() 

2668 

2669 except self.PhysicalDeviceError as e: 

2670 vdi_type = self.read("type") 

2671 

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

2673 

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

2675 raise 

2676 

2677 return True 

2678 

2679 return False 

2680 

2681 def can_tap(self): 

2682 return not self.is_bare_hvm() 

2683 

2684 

2685class BlkbackEventHandler(UEventHandler): 

2686 

2687 LOG_FACILITY = _syslog.LOG_DAEMON 

2688 

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

2690 if not ident: 

2691 ident = self.__class__.__name__ 

2692 

2693 self.ident = ident 

2694 self._vbd = None 

2695 self._tapdisk = None 

2696 

2697 UEventHandler.__init__(self) 

2698 

2699 @override 

2700 def run(self) -> None: 

2701 

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

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

2704 

2705 UEventHandler.run(self) 

2706 

2707 @override 

2708 def __str__(self) -> str: 

2709 

2710 try: 

2711 path = self.xs_path 

2712 except: 

2713 path = None 

2714 

2715 try: 

2716 action = self.get_action() 

2717 except: 

2718 action = None 

2719 

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

2721 

2722 def _log(self, prio, msg): 

2723 syslog(prio, msg) 

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

2725 

2726 def info(self, msg): 

2727 self._log(_syslog.LOG_INFO, msg) 

2728 

2729 def warn(self, msg): 

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

2731 

2732 def error(self, msg): 

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

2734 

2735 def get_vbd(self): 

2736 if not self._vbd: 

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

2738 return self._vbd 

2739 

2740 def get_tapdisk(self): 

2741 if not self._tapdisk: 

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

2743 self._tapdisk = Tapdisk.from_minor(minor) 

2744 return self._tapdisk 

2745 # 

2746 # Events 

2747 # 

2748 

2749 def __add(self): 

2750 vbd = self.get_vbd() 

2751 # Manage blkback transitions 

2752 # self._manage_vbd() 

2753 

2754 vbd.create_physical_device() 

2755 

2756 vbd.signal_hotplug() 

2757 

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

2759 def add(self): 

2760 try: 

2761 self.__add() 

2762 except Attribute.NoSuchAttribute as e: 

2763 # 

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

2765 # registers device attributes. So poll a little. 

2766 # 

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

2768 raise RetryLoop.TransientFailure(e) 

2769 

2770 def __change(self): 

2771 vbd = self.get_vbd() 

2772 

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

2774 

2775 if vbd.has_tap(): 

2776 pass 

2777 #self._pause_update_tap() 

2778 

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

2780 

2781 self._signal_xapi() 

2782 

2783 def change(self): 

2784 vbd = self.get_vbd() 

2785 

2786 # NB. Beware of spurious change events between shutdown 

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

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

2789 

2790 while True: 

2791 vbd.begin() 

2792 

2793 if not vbd.exists() or \ 

2794 vbd.shutdown_done(): 

2795 break 

2796 

2797 self.__change() 

2798 

2799 if vbd.commit(): 

2800 return 

2801 

2802 vbd.abort() 

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

2804 

2805 def remove(self): 

2806 vbd = self.get_vbd() 

2807 

2808 vbd.signal_hotplug(False) 

2809 

2810 ACTIONS = {'add': add, 

2811 'change': change, 

2812 'remove': remove} 

2813 # 

2814 # VDI.pause 

2815 # 

2816 

2817 def _tap_should_pause(self): 

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

2819 paused""" 

2820 

2821 tapdisk = self.get_tapdisk() 

2822 TapState = Tapdisk.PauseState 

2823 

2824 PAUSED = 'P' 

2825 RUNNING = 'R' 

2826 PAUSED_SHUTDOWN = 'P,S' 

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

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

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

2830 

2831 next = TapState.RUNNING 

2832 vbds = {} 

2833 

2834 for vbd in Blkback.find_by_tap(tapdisk): 

2835 name = str(vbd) 

2836 

2837 pausing = vbd.pause_requested() 

2838 closing = vbd.shutdown_requested() 

2839 running = vbd.running() 

2840 

2841 if pausing: 

2842 if closing and not running: 

2843 vbds[name] = PAUSED_SHUTDOWN 

2844 else: 

2845 vbds[name] = PAUSED 

2846 next = TapState.PAUSED 

2847 

2848 else: 

2849 vbds[name] = RUNNING 

2850 

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

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

2853 vbds, next)) 

2854 

2855 return next == TapState.PAUSED 

2856 

2857 def _pause_update_tap(self): 

2858 vbd = self.get_vbd() 

2859 

2860 if self._tap_should_pause(): 

2861 self._pause_tap() 

2862 else: 

2863 self._resume_tap() 

2864 

2865 def _pause_tap(self): 

2866 tapdisk = self.get_tapdisk() 

2867 

2868 if not tapdisk.is_paused(): 

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

2870 tapdisk.pause() 

2871 

2872 def _resume_tap(self): 

2873 tapdisk = self.get_tapdisk() 

2874 

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

2876 # type while resuming. 

2877 vbd = self.get_vbd() 

2878 vdi_uuid = vbd.get_vdi_uuid() 

2879 

2880 if tapdisk.is_paused(): 

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

2882 vdi = VDI.from_cli(vdi_uuid) 

2883 _type = vdi.get_tap_type() 

2884 path = vdi.get_phy_path() 

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

2886 tapdisk.unpause(_type, path) 

2887 # 

2888 # VBD.pause/shutdown 

2889 # 

2890 

2891 def _manage_vbd(self): 

2892 vbd = self.get_vbd() 

2893 # NB. Hook into VBD state transitions. 

2894 

2895 events = vbd.get_queue_events() 

2896 

2897 mask = 0 

2898 mask |= events.QUEUE_PAUSE_DONE # pause/unpause 

2899 mask |= events.QUEUE_SHUTDOWN_DONE # shutdown 

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

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

2902 

2903 events.set_mask(mask) 

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

2905 

2906 def _signal_xapi(self): 

2907 vbd = self.get_vbd() 

2908 

2909 pausing = vbd.pause_requested() 

2910 closing = vbd.shutdown_requested() 

2911 running = vbd.running() 

2912 

2913 handled = 0 

2914 

2915 if pausing and not running: 

2916 if 'pause-done' not in vbd: 

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

2918 handled += 1 

2919 

2920 if not pausing: 

2921 if 'pause-done' in vbd: 

2922 vbd.rm('pause-done') 

2923 handled += 1 

2924 

2925 if closing and not running: 

2926 if 'shutdown-done' not in vbd: 

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

2928 handled += 1 

2929 

2930 if handled > 1: 

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

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

2933 (pausing, closing, running)) 

2934 

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

2936 

2937 import sys 

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

2939 

2940 # 

2941 # Simple CLI interface for manual operation 

2942 # 

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

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

2945 # 

2946 

2947 def usage(stream): 

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

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

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

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

2952 

2953 try: 

2954 cmd = sys.argv[1] 

2955 except IndexError: 

2956 usage(sys.stderr) 

2957 sys.exit(1) 

2958 

2959 try: 

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

2961 except: 

2962 usage(sys.stderr) 

2963 sys.exit(1) 

2964 

2965 # 

2966 # Local Tapdisks 

2967 # 

2968 

2969 if cmd == 'tap.major': 

2970 

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

2972 

2973 elif cmd == 'tap.launch': 

2974 

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

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

2977 

2978 elif _class == 'tap': 

2979 

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

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

2982 try: 

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

2984 attrs[key] = val 

2985 continue 

2986 except ValueError: 

2987 pass 

2988 

2989 try: 

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

2991 continue 

2992 except ValueError: 

2993 pass 

2994 

2995 try: 

2996 arg = Tapdisk.Arg.parse(item) 

2997 attrs['_type'] = arg.type 

2998 attrs['path'] = arg.path 

2999 continue 

3000 except Tapdisk.Arg.InvalidArgument: 

3001 pass 

3002 

3003 attrs['path'] = item 

3004 

3005 if cmd == 'tap.list': 

3006 

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

3008 blktap = tapdisk.get_blktap() 

3009 print(tapdisk, end=' ') 

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

3011 (blktap, 

3012 blktap.get_task_pid(), 

3013 blktap.get_pool_name())) 

3014 

3015 elif cmd == 'tap.vbds': 

3016 # Find all Blkback instances for a given tapdisk 

3017 

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

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

3020 for vbd in Blkback.find_by_tap(tapdisk): 

3021 print(vbd, end=' ') 

3022 print() 

3023 

3024 else: 

3025 

3026 if not attrs: 

3027 usage(sys.stderr) 

3028 sys.exit(1) 

3029 

3030 try: 

3031 tapdisk = Tapdisk.get( ** attrs) 

3032 except TypeError: 

3033 usage(sys.stderr) 

3034 sys.exit(1) 

3035 

3036 if cmd == 'tap.shutdown': 

3037 # Shutdown a running tapdisk, or raise 

3038 tapdisk.shutdown() 

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

3040 

3041 elif cmd == 'tap.pause': 

3042 # Pause an unpaused tapdisk, or raise 

3043 tapdisk.pause() 

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

3045 

3046 elif cmd == 'tap.unpause': 

3047 # Unpause a paused tapdisk, or raise 

3048 tapdisk.unpause() 

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

3050 

3051 elif cmd == 'tap.stats': 

3052 # Gather tapdisk status 

3053 stats = tapdisk.stats() 

3054 print("%s:" % tapdisk) 

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

3056 

3057 else: 

3058 usage(sys.stderr) 

3059 sys.exit(1) 

3060 

3061 elif cmd == 'vbd.uevent': 

3062 

3063 hnd = BlkbackEventHandler(cmd) 

3064 

3065 if not sys.stdin.isatty(): 

3066 try: 

3067 hnd.run() 

3068 except Exception as e: 

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

3070 

3071 import traceback 

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

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

3074 for entry in trace: 

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

3076 util.SMlog(line) 

3077 else: 

3078 hnd.run() 

3079 

3080 elif cmd == 'vbd.list': 

3081 

3082 for vbd in Blkback.find(): 

3083 print(vbd, \ 

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

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

3086 

3087 else: 

3088 usage(sys.stderr) 

3089 sys.exit(1)