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 

48import nfs 

49 

50import resetvdis 

51 

52import VDI as sm 

53 

54from cowutil import getCowUtil 

55 

56# For RRDD Plugin Registration 

57from xmlrpc.client import ServerProxy, Transport 

58from socket import socket, AF_UNIX, SOCK_STREAM 

59 

60 

61try: 

62 from linstorvolumemanager import log_drbd_openers 

63 LINSTOR_AVAILABLE = True 

64except ImportError: 

65 LINSTOR_AVAILABLE = False 

66 

67PLUGIN_TAP_PAUSE = "tapdisk-pause" 

68PLUGIN_ON_SLAVE = "on-slave" 

69 

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

71 

72NUM_PAGES_PER_RING = 32 * 11 

73MAX_FULL_RINGS = 8 

74POOL_NAME_KEY = "mem-pool" 

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

76 

77ENABLE_MULTIPLE_ATTACH = "/etc/xensource/allow_multiple_vdi_attach" 

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

79 

80 

81def locking(excType, override=True): 

82 def locking2(op): 

83 def wrapper(self, *args): 

84 self.lock.acquire() 

85 try: 

86 try: 

87 ret = op(self, * args) 

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

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

90 msg = str(e) 

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

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

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

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

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

96 else: 

97 raise 

98 except: 

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

100 raise 

101 finally: 

102 self.lock.release() 

103 return ret 

104 return wrapper 

105 return locking2 

106 

107 

108class RetryLoop(object): 

109 

110 def __init__(self, backoff, limit): 

111 self.backoff = backoff 

112 self.limit = limit 

113 

114 def __call__(self, f): 

115 

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

117 attempt = 0 

118 

119 while True: 

120 attempt += 1 

121 

122 try: 

123 return f( * __t, ** __d) 

124 

125 except self.TransientFailure as e: 

126 e = e.exception 

127 

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

129 raise e 

130 

131 time.sleep(self.backoff) 

132 

133 return loop 

134 

135 class TransientFailure(Exception): 

136 def __init__(self, exception): 

137 self.exception = exception 

138 

139 

140def retried(**args): 

141 return RetryLoop( ** args) 

142 

143 

144class TapCtl(object): 

145 """Tapdisk IPC utility calls.""" 

146 

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

148 

149 def __init__(self, cmd, p): 

150 self.cmd = cmd 

151 self._p = p 

152 self.stdout = p.stdout 

153 

154 class CommandFailure(Exception): 

155 """TapCtl cmd failure.""" 

156 

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

158 self.cmd = cmd 

159 self.info = info 

160 

161 @override 

162 def __str__(self) -> str: 

163 items = self.info.items() 

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

165 for item in items) 

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

167 

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

169 # exception 

170 def __getattr__(self, key): 

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

172 return self.info[key] 

173 return object.__getattribute__(self, key) 

174 

175 @property 

176 def has_status(self): 

177 return 'status' in self.info 

178 

179 @property 

180 def has_signal(self): 

181 return 'signal' in self.info 

182 

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

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

185 def get_error_code(self): 

186 key = 'status' 

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

188 return self.info[key] 

189 else: 

190 return 0 

191 

192 @classmethod 

193 def __mkcmd_real(cls, args): 

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

195 

196 __next_mkcmd = __mkcmd_real 

197 

198 @classmethod 

199 def _mkcmd(cls, args): 

200 

201 __next_mkcmd = cls.__next_mkcmd 

202 cls.__next_mkcmd = cls.__mkcmd_real 

203 

204 return __next_mkcmd(args) 

205 

206 @classmethod 

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

208 """ 

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

210 Raises a TapCtl.CommandFailure if subprocess creation failed. 

211 """ 

212 cmd = cls._mkcmd(args) 

213 

214 if not quiet: 

215 util.SMlog(cmd) 

216 try: 

217 p = subprocess.Popen(cmd, 

218 stdin=subprocess.PIPE, 

219 stdout=subprocess.PIPE, 

220 stderr=subprocess.PIPE, 

221 close_fds=True, 

222 universal_newlines=text_mode) 

223 if input: 

224 p.stdin.write(input) 

225 p.stdin.close() 

226 except OSError as e: 

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

228 

229 return cls(cmd, p) 

230 

231 def _errmsg(self): 

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

233 return "; ".join(output) 

234 

235 def _wait(self, quiet=False): 

236 """ 

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

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

239 """ 

240 status = self._p.wait() 

241 if not quiet: 

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

243 

244 if status == 0: 

245 return 

246 

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

248 'pid': self._p.pid} 

249 

250 if status < 0: 

251 info['signal'] = -status 

252 else: 

253 info['status'] = status 

254 

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

256 

257 @classmethod 

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

259 """ 

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

261 """ 

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

263 text_mode=text_mode) 

264 

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

266 

267 tapctl._wait(quiet) 

268 return output 

269 

270 @staticmethod 

271 def _maybe(opt, parm): 

272 if parm is not None: 

273 return [opt, parm] 

274 return [] 

275 

276 @classmethod 

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

278 args = ["list"] 

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

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

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

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

283 

284 tapctl = cls._call(args, True) 

285 

286 for stdout_line in tapctl.stdout: 

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

288 # confuses this parser 

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

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

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

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

293 row = {} 

294 

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

296 bits = field.split('=') 

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

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

299 

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

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

302 

303 elif key in ('state'): 

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

305 

306 else: 

307 row[key] = val 

308 else: 

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

310 yield row 

311 

312 tapctl._wait(True) 

313 

314 @classmethod 

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

316 def list(cls, **args): 

317 

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

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

320 # be fixed in SM. 

321 

322 try: 

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

324 

325 except cls.CommandFailure as e: 

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

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

328 raise RetryLoop.TransientFailure(e) 

329 raise 

330 

331 @classmethod 

332 def allocate(cls, devpath=None): 

333 args = ["allocate"] 

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

335 return cls._pread(args) 

336 

337 @classmethod 

338 def free(cls, minor): 

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

340 cls._pread(args) 

341 

342 @classmethod 

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

344 def spawn(cls): 

345 args = ["spawn"] 

346 try: 

347 pid = cls._pread(args) 

348 return int(pid) 

349 except cls.CommandFailure as ce: 

350 # intermittent failures to spawn. CA-292268 

351 if ce.status == 1: 

352 raise RetryLoop.TransientFailure(ce) 

353 raise 

354 

355 @classmethod 

356 def attach(cls, pid, minor): 

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

358 cls._pread(args) 

359 

360 @classmethod 

361 def detach(cls, pid, minor): 

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

363 cls._pread(args) 

364 

365 @classmethod 

366 def _load_key(cls, key_hash, vdi_uuid): 

367 import plugins 

368 

369 return plugins.load_key(key_hash, vdi_uuid) 

370 

371 @classmethod 

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

373 params = Tapdisk.Arg(_type, _file) 

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

375 text_mode = True 

376 input = None 

377 if options.get("rdonly"): 

378 args.append('-R') 

379 if options.get("lcache"): 

380 args.append("-r") 

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

382 args.append("-e") 

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

384 if options.get("secondary"): 

385 args.append("-2") 

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

387 if options.get("standby"): 

388 args.append("-s") 

389 if options.get("timeout"): 

390 args.append("-t") 

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

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

393 args.append("-D") 

394 if options.get('cbtlog'): 

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

396 if options.get('key_hash'): 

397 key_hash = options['key_hash'] 

398 vdi_uuid = options['vdi_uuid'] 

399 key = cls._load_key(key_hash, vdi_uuid) 

400 

401 if not key: 

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

403 input = key 

404 text_mode = False 

405 args.append('-E') 

406 

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

408 

409 @classmethod 

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

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

412 if force: 

413 args += ["-f"] 

414 cls._pread(args) 

415 

416 @classmethod 

417 def pause(cls, pid, minor): 

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

419 cls._pread(args) 

420 

421 @classmethod 

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

423 cbtlog=None): 

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

425 if mirror: 

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

427 if _type and _file: 

428 params = Tapdisk.Arg(_type, _file) 

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

430 if cbtlog: 

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

432 cls._pread(args) 

433 

434 @classmethod 

435 def shutdown(cls, pid): 

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

437 os.kill(pid, signal.SIGTERM) 

438 os.waitpid(pid, 0) 

439 

440 @classmethod 

441 def stats(cls, pid, minor): 

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

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

444 

445 @classmethod 

446 def major(cls): 

447 args = ["major"] 

448 major = cls._pread(args) 

449 return int(major) 

450 

451 @classmethod 

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

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

454 cls._pread(args) 

455 

456 @classmethod 

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

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

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

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

461 status = m.group(1) 

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

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

464 return (status, coalesced, total_coalesce) 

465 

466 @classmethod 

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

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

469 if wait: 

470 args.append("-w") 

471 cls._pread(args) 

472 

473class TapdiskExists(Exception): 

474 """Tapdisk already running.""" 

475 

476 def __init__(self, tapdisk): 

477 self.tapdisk = tapdisk 

478 

479 @override 

480 def __str__(self) -> str: 

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

482 

483 

484class TapdiskNotRunning(Exception): 

485 """No such Tapdisk.""" 

486 

487 def __init__(self, **attrs): 

488 self.attrs = attrs 

489 

490 @override 

491 def __str__(self) -> str: 

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

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

494 for attr in items) 

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

496 

497 

498class TapdiskNotUnique(Exception): 

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

500 

501 def __init__(self, tapdisks): 

502 self.tapdisks = tapdisks 

503 

504 @override 

505 def __str__(self) -> str: 

506 tapdisks = map(str, self.tapdisks) 

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

508 

509 

510class TapdiskFailed(Exception): 

511 """Tapdisk launch failure.""" 

512 

513 def __init__(self, arg, err): 

514 self.arg = arg 

515 self.err = err 

516 

517 @override 

518 def __str__(self) -> str: 

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

520 

521 def get_error(self): 

522 return self.err 

523 

524 

525class TapdiskInvalidState(Exception): 

526 """Tapdisk pause/unpause failure""" 

527 

528 def __init__(self, tapdisk): 

529 self.tapdisk = tapdisk 

530 

531 @override 

532 def __str__(self) -> str: 

533 return str(self.tapdisk) 

534 

535 

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

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

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

539 assert parent != path 

540 try: 

541 if parent: 

542 mkdirs(parent, mode) 

543 if subdir: 

544 os.mkdir(path, mode) 

545 except OSError as e: 

546 if e.errno != errno.EEXIST: 

547 raise 

548 

549 

550class KObject(object): 

551 

552 SYSFS_CLASSTYPE: ClassVar[str] = "" 

553 

554 @abstractmethod 

555 def sysfs_devname(self) -> str: 

556 pass 

557 

558 

559class Attribute(object): 

560 

561 SYSFS_NODENAME: ClassVar[str] = "" 

562 

563 def __init__(self, path): 

564 self.path = path 

565 

566 @classmethod 

567 def from_kobject(cls, kobj): 

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

569 return cls(path) 

570 

571 class NoSuchAttribute(Exception): 

572 def __init__(self, name): 

573 self.name = name 

574 

575 @override 

576 def __str__(self) -> str: 

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

578 

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

580 try: 

581 return open(self.path, mode) 

582 except IOError as e: 

583 if e.errno == errno.ENOENT: 

584 raise self.NoSuchAttribute(self) 

585 raise 

586 

587 def readline(self): 

588 f = self._open('r') 

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

590 f.close() 

591 return s 

592 

593 def writeline(self, val): 

594 f = self._open('w') 

595 f.write(val) 

596 f.close() 

597 

598 

599class ClassDevice(KObject): 

600 

601 @classmethod 

602 def sysfs_class_path(cls): 

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

604 

605 def sysfs_path(self): 

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

607 self.sysfs_devname()) 

608 

609 

610class Blktap(ClassDevice): 

611 

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

613 

614 SYSFS_CLASSTYPE = "blktap2" 

615 

616 def __init__(self, minor): 

617 self.minor = minor 

618 self._pool = None 

619 self._task = None 

620 

621 @classmethod 

622 def allocate(cls): 

623 # FIXME. Should rather go into init. 

624 mkdirs(cls.DEV_BASEDIR) 

625 

626 devname = TapCtl.allocate() 

627 minor = Tapdisk._parse_minor(devname) 

628 return cls(minor) 

629 

630 def free(self): 

631 TapCtl.free(self.minor) 

632 

633 @override 

634 def __str__(self) -> str: 

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

636 

637 @override 

638 def sysfs_devname(self) -> str: 

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

640 

641 class Pool(Attribute): 

642 SYSFS_NODENAME = "pool" 

643 

644 def get_pool_attr(self): 

645 if not self._pool: 

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

647 return self._pool 

648 

649 def get_pool_name(self): 

650 return self.get_pool_attr().readline() 

651 

652 def set_pool_name(self, name): 

653 self.get_pool_attr().writeline(name) 

654 

655 def set_pool_size(self, pages): 

656 self.get_pool().set_size(pages) 

657 

658 def get_pool(self): 

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

660 

661 def set_pool(self, pool): 

662 self.set_pool_name(pool.name) 

663 

664 class Task(Attribute): 

665 SYSFS_NODENAME = "task" 

666 

667 def get_task_attr(self): 

668 if not self._task: 

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

670 return self._task 

671 

672 def get_task_pid(self): 

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

674 try: 

675 return int(pid) 

676 except ValueError: 

677 return None 

678 

679 def find_tapdisk(self): 

680 pid = self.get_task_pid() 

681 if pid is None: 

682 return None 

683 

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

685 

686 def get_tapdisk(self): 

687 tapdisk = self.find_tapdisk() 

688 if not tapdisk: 

689 raise TapdiskNotRunning(minor=self.minor) 

690 return tapdisk 

691 

692 

693class Tapdisk(object): 

694 

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

696 

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

698 self.pid = pid 

699 self.minor = minor 

700 self.type = _type 

701 self.path = path 

702 self.state = state 

703 self._dirty = False 

704 self._blktap = None 

705 

706 @override 

707 def __str__(self) -> str: 

708 state = self.pause_state() 

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

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

711 

712 @classmethod 

713 def list(cls, **args): 

714 

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

716 

717 args = {'pid': None, 

718 'minor': None, 

719 'state': None, 

720 '_type': None, 

721 'path': None} 

722 

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

724 if key in args: 

725 args[key] = val 

726 

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

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

729 args['_type'] = image.type 

730 args['path'] = image.path 

731 

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

733 continue 

734 

735 yield Tapdisk( ** args) 

736 

737 @classmethod 

738 def find(cls, **args): 

739 

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

741 

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

743 raise TapdiskNotUnique(found) 

744 

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

746 return found[0] 

747 

748 return None 

749 

750 @classmethod 

751 def find_by_path(cls, path): 

752 return cls.find(path=path) 

753 

754 @classmethod 

755 def find_by_minor(cls, minor): 

756 return cls.find(minor=minor) 

757 

758 @classmethod 

759 def get(cls, **attrs): 

760 

761 tapdisk = cls.find( ** attrs) 

762 

763 if not tapdisk: 

764 raise TapdiskNotRunning( ** attrs) 

765 

766 return tapdisk 

767 

768 @classmethod 

769 def from_path(cls, path): 

770 return cls.get(path=path) 

771 

772 @classmethod 

773 def from_minor(cls, minor): 

774 return cls.get(minor=minor) 

775 

776 @classmethod 

777 def __from_blktap(cls, blktap): 

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

779 tapdisk._blktap = blktap 

780 return tapdisk 

781 

782 def get_blktap(self): 

783 if not self._blktap: 

784 self._blktap = Blktap(self.minor) 

785 return self._blktap 

786 

787 class Arg: 

788 

789 def __init__(self, _type, path): 

790 self.type = _type 

791 self.path = path 

792 

793 @override 

794 def __str__(self) -> str: 

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

796 

797 @classmethod 

798 def parse(cls, arg): 

799 

800 try: 

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

802 except ValueError: 

803 raise cls.InvalidArgument(arg) 

804 

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

806 raise cls.InvalidType(_type) 

807 

808 return cls(_type, path) 

809 

810 class InvalidType(Exception): 

811 def __init__(self, _type): 

812 self.type = _type 

813 

814 @override 

815 def __str__(self) -> str: 

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

817 

818 class InvalidArgument(Exception): 

819 def __init__(self, arg): 

820 self.arg = arg 

821 

822 @override 

823 def __str__(self) -> str: 

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

825 

826 def get_arg(self): 

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

828 

829 def get_devpath(self): 

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

831 

832 @classmethod 

833 def launch_from_arg(cls, arg): 

834 arg = cls.Arg.parse(arg) 

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

836 

837 @staticmethod 

838 def cgclassify(pid): 

839 

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

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

842 # we have configured in the spec file. 

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

844 try: 

845 util.pread2(cmd) 

846 except util.CommandException as e: 

847 util.logException(e) 

848 

849 @classmethod 

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

851 

852 tapdisk = cls.find_by_path(path) 

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

854 raise TapdiskExists(tapdisk) 

855 

856 minor = blktap.minor 

857 try: 

858 pid = TapCtl.spawn() 

859 cls.cgclassify(pid) 

860 try: 

861 TapCtl.attach(pid, minor) 

862 

863 try: 

864 retry_open = 0 

865 while True: 

866 try: 

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

868 break 

869 except TapCtl.CommandFailure as e: 

870 err = ( 

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

872 ) or None 

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

874 if retry_open < 5: 

875 retry_open += 1 

876 time.sleep(1) 

877 continue 

878 if LINSTOR_AVAILABLE and err == errno.EROFS: 

879 log_drbd_openers(path) 

880 raise 

881 try: 

882 tapdisk = cls.__from_blktap(blktap) 

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

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

885 return tapdisk 

886 except: 

887 TapCtl.close(pid, minor) 

888 raise 

889 

890 except: 

891 TapCtl.detach(pid, minor) 

892 raise 

893 

894 except: 

895 try: 

896 TapCtl.shutdown(pid) 

897 except: 

898 # Best effort to shutdown 

899 pass 

900 raise 

901 

902 except TapCtl.CommandFailure as ctl: 

903 util.logException(ctl) 

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

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

906 raise xs_errors.XenError('TapdiskDriveEmpty') 

907 else: 

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

909 

910 @classmethod 

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

912 blktap = Blktap.allocate() 

913 try: 

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

915 except: 

916 blktap.free() 

917 raise 

918 

919 def shutdown(self, force=False): 

920 

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

922 

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

924 

925 self.get_blktap().free() 

926 

927 def pause(self): 

928 

929 if not self.is_running(): 

930 raise TapdiskInvalidState(self) 

931 

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

933 

934 self._set_dirty() 

935 

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

937 

938 if not self.is_paused(): 

939 raise TapdiskInvalidState(self) 

940 

941 # FIXME: should the arguments be optional? 

942 if _type is None: 

943 _type = self.type 

944 if path is None: 

945 path = self.path 

946 

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

948 cbtlog=cbtlog) 

949 

950 self._set_dirty() 

951 

952 def stats(self): 

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

954 # 

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

956 # 

957 

958 def _set_dirty(self): 

959 self._dirty = True 

960 

961 def _refresh(self, __get): 

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

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

964 

965 @override 

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

967 def __get(name): 

968 # NB. avoid(rec(ursion) 

969 return object.__getattribute__(self, name) 

970 

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

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

973 self._refresh(__get) 

974 self._dirty = False 

975 

976 return __get(name) 

977 

978 class PauseState: 

979 RUNNING = 'R' 

980 PAUSING = 'r' 

981 PAUSED = 'P' 

982 

983 class Flags: 

984 DEAD = 0x0001 

985 CLOSED = 0x0002 

986 QUIESCE_REQUESTED = 0x0004 

987 QUIESCED = 0x0008 

988 PAUSE_REQUESTED = 0x0010 

989 PAUSED = 0x0020 

990 SHUTDOWN_REQUESTED = 0x0040 

991 LOCKING = 0x0080 

992 RETRY_NEEDED = 0x0100 

993 LOG_DROPPED = 0x0200 

994 

995 PAUSE_MASK = PAUSE_REQUESTED | PAUSED 

996 

997 def is_paused(self): 

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

999 

1000 def is_running(self): 

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

1002 

1003 def pause_state(self): 

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

1005 return self.PauseState.PAUSED 

1006 

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

1008 return self.PauseState.PAUSING 

1009 

1010 return self.PauseState.RUNNING 

1011 

1012 @staticmethod 

1013 def _parse_minor(devpath): 

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

1015 pattern = re.compile(regex) 

1016 groups = pattern.search(devpath) 

1017 if not groups: 

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

1019 

1020 minor = groups.group(2) 

1021 return int(minor) 

1022 

1023 _major = None 

1024 

1025 @classmethod 

1026 def major(cls): 

1027 if cls._major: 

1028 return cls._major 

1029 

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

1031 for line in devices: 

1032 

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

1034 if len(row) != 2: 

1035 continue 

1036 

1037 major, name = row 

1038 if name != 'tapdev': 

1039 continue 

1040 

1041 cls._major = int(major) 

1042 break 

1043 

1044 devices.close() 

1045 return cls._major 

1046 

1047 

1048class VDI(object): 

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

1050 

1051 CONF_KEY_ALLOW_CACHING = "vdi_allow_caching" 

1052 CONF_KEY_MODE_ON_BOOT = "vdi_on_boot" 

1053 CONF_KEY_CACHE_SR = "local_cache_sr" 

1054 CONF_KEY_O_DIRECT = "o_direct" 

1055 LOCK_CACHE_SETUP = "cachesetup" 

1056 

1057 ATTACH_DETACH_RETRY_SECS = 120 

1058 

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

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

1061 self._vdi_uuid = uuid 

1062 self._session = target.session 

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

1064 self.__o_direct = None 

1065 self.__o_direct_reason = None 

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

1067 self.tap = None 

1068 

1069 def get_o_direct_capability(self, options): 

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

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

1072 return self.__o_direct, self.__o_direct_reason 

1073 

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

1075 self.__o_direct = True 

1076 self.__o_direct_reason = "LICENSE_RESTRICTION" 

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

1078 self.__o_direct = True 

1079 self.__o_direct_reason = "SR_NOT_SUPPORTED" 

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

1081 self.__o_direct = True 

1082 self.__o_direct_reason = "RO_WITH_NO_PARENT" 

1083 elif options.get(self.CONF_KEY_O_DIRECT): 

1084 self.__o_direct = True 

1085 self.__o_direct_reason = "SR_OVERRIDE" 

1086 

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

1088 self.__o_direct = False 

1089 self.__o_direct_reason = "" 

1090 

1091 return self.__o_direct, self.__o_direct_reason 

1092 

1093 @classmethod 

1094 def from_cli(cls, uuid): 

1095 import VDI as sm 

1096 

1097 session = XenAPI.xapi_local() 

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

1099 

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

1101 driver_info = target.sr.srcmd.driver_info 

1102 

1103 session.xenapi.session.logout() 

1104 

1105 return cls(uuid, target, driver_info) 

1106 

1107 @staticmethod 

1108 def _tap_type(vdi_type): 

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

1110 return { 

1111 'raw': 'aio', 

1112 'vhd': 'vhd', 

1113 'qcow2': 'qcow2', 

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

1115 'aio': 'aio', # for LVHD 

1116 'file': 'aio', 

1117 'phy': 'aio' 

1118 }[vdi_type] 

1119 

1120 def get_tap_type(self): 

1121 vdi_type = self.target.get_vdi_type() 

1122 return VDI._tap_type(vdi_type) 

1123 

1124 def get_phy_path(self): 

1125 return self.target.get_vdi_path() 

1126 

1127 class UnexpectedVDIType(Exception): 

1128 

1129 def __init__(self, vdi_type, target): 

1130 self.vdi_type = vdi_type 

1131 self.target = target 

1132 

1133 @override 

1134 def __str__(self) -> str: 

1135 return \ 

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

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

1138 

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

1140 'raw': 'phy', 

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

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

1143 'file': 'tap', 

1144 'vhd': 'tap', 

1145 'qcow2': 'tap'} 

1146 

1147 def tap_wanted(self): 

1148 # 1. Let the target vdi_type decide 

1149 

1150 vdi_type = self.target.get_vdi_type() 

1151 

1152 try: 

1153 plug_type = self.VDI_PLUG_TYPE[vdi_type] 

1154 except KeyError: 

1155 raise self.UnexpectedVDIType(vdi_type, 

1156 self.target.vdi) 

1157 

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

1159 return True 

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

1161 return True 

1162 # 2. Otherwise, there may be more reasons 

1163 # 

1164 # .. TBD 

1165 

1166 return False 

1167 

1168 class TargetDriver: 

1169 """Safe target driver access.""" 

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

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

1172 # try/except would risk breaking compatibility. 

1173 

1174 def __init__(self, vdi, driver_info): 

1175 self.vdi = vdi 

1176 self._caps = driver_info['capabilities'] 

1177 

1178 def has_cap(self, cap): 

1179 """Determine if target has given capability""" 

1180 return cap in self._caps 

1181 

1182 def attach(self, sr_uuid, vdi_uuid): 

1183 #assert self.has_cap("VDI_ATTACH") 

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

1185 

1186 def detach(self, sr_uuid, vdi_uuid): 

1187 #assert self.has_cap("VDI_DETACH") 

1188 self.vdi.detach(sr_uuid, vdi_uuid) 

1189 

1190 def activate(self, sr_uuid, vdi_uuid): 

1191 if self.has_cap("VDI_ACTIVATE"): 

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

1193 

1194 def deactivate(self, sr_uuid, vdi_uuid): 

1195 if self.has_cap("VDI_DEACTIVATE"): 

1196 self.vdi.deactivate(sr_uuid, vdi_uuid) 

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

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

1199 

1200 def get_vdi_type(self): 

1201 _type = self.vdi.vdi_type 

1202 if not _type: 

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

1204 return _type 

1205 

1206 def get_vdi_path(self): 

1207 return self.vdi.path 

1208 

1209 class Link(object): 

1210 """Relink a node under a common name""" 

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

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

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

1214 # relink existing devices under deterministic path names. 

1215 

1216 BASEDIR: ClassVar[str] = "" 

1217 

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

1219 pass 

1220 

1221 @abstractmethod 

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

1223 pass 

1224 

1225 def __init__(self, path): 

1226 self._path = path 

1227 

1228 @classmethod 

1229 def from_name(cls, name): 

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

1231 return cls(path) 

1232 

1233 @classmethod 

1234 def from_uuid(cls, sr_uuid, vdi_uuid): 

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

1236 return cls.from_name(name) 

1237 

1238 def path(self): 

1239 return self._path 

1240 

1241 def stat(self): 

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

1243 

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

1245 

1246 path = self.path() 

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

1248 

1249 mkdirs(os.path.dirname(path)) 

1250 try: 

1251 self._mklink(target) 

1252 except OSError as e: 

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

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

1255 # be seen. 

1256 if e.errno != errno.EEXIST: 

1257 raise 

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

1259 

1260 def unlink(self): 

1261 try: 

1262 os.unlink(self.path()) 

1263 except OSError as e: 

1264 if e.errno != errno.ENOENT: 

1265 raise 

1266 

1267 @override 

1268 def __str__(self) -> str: 

1269 path = self.path() 

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

1271 

1272 class SymLink(Link): 

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

1274 

1275 def readlink(self): 

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

1277 

1278 def symlink(self): 

1279 return self.path() 

1280 

1281 @override 

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

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

1284 

1285 @override 

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

1287 return self.readlink() == target 

1288 

1289 class DeviceNode(Link): 

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

1291 

1292 @classmethod 

1293 def _real_stat(cls, target): 

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

1295 _target = os.path.realpath(target) 

1296 return os.stat(_target) 

1297 

1298 @classmethod 

1299 def is_block(cls, target): 

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

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

1302 

1303 @override 

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

1305 

1306 st = self._real_stat(target) 

1307 if not S_ISBLK(st.st_mode): 

1308 raise self.NotABlockDevice(target, st) 

1309 

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

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

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

1313 

1314 @override 

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

1316 target_rdev = self._real_stat(target).st_rdev 

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

1318 

1319 def rdev(self): 

1320 st = self.stat() 

1321 assert S_ISBLK(st.st_mode) 

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

1323 

1324 class NotABlockDevice(Exception): 

1325 

1326 def __init__(self, path, st): 

1327 self.path = path 

1328 self.st = st 

1329 

1330 @override 

1331 def __str__(self) -> str: 

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

1333 

1334 class Hybrid(Link): 

1335 

1336 def __init__(self, path): 

1337 VDI.Link.__init__(self, path) 

1338 self._devnode = VDI.DeviceNode(path) 

1339 self._symlink = VDI.SymLink(path) 

1340 

1341 def rdev(self): 

1342 st = self.stat() 

1343 if S_ISBLK(st.st_mode): 

1344 return self._devnode.rdev() 

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

1346 

1347 @override 

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

1349 if self._devnode.is_block(target): 

1350 self._obj = self._devnode 

1351 else: 

1352 self._obj = self._symlink 

1353 self._obj.mklink(target) 

1354 

1355 @override 

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

1357 return self._obj._equals(target) 

1358 

1359 class PhyLink(SymLink): 

1360 BASEDIR = "/dev/sm/phy" 

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

1362 

1363 class NBDLink(SymLink): 

1364 

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

1366 

1367 class BackendLink(Hybrid): 

1368 BASEDIR = "/dev/sm/backend" 

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

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

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

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

1373 # soon as ISOs are tapdisks. 

1374 

1375 @staticmethod 

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

1377 

1378 tapdisk = Tapdisk.find_by_path(phy_path) 

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

1380 blktap = Blktap.allocate() 

1381 blktap.set_pool_name(sr_uuid) 

1382 if pool_size: 

1383 blktap.set_pool_size(pool_size) 

1384 

1385 try: 

1386 tapdisk = \ 

1387 Tapdisk.launch_on_tap(blktap, 

1388 phy_path, 

1389 VDI._tap_type(vdi_type), 

1390 options) 

1391 except: 

1392 blktap.free() 

1393 raise 

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

1395 

1396 else: 

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

1398 

1399 return tapdisk.get_devpath(), tapdisk 

1400 

1401 @staticmethod 

1402 def _tap_deactivate(minor): 

1403 

1404 try: 

1405 tapdisk = Tapdisk.from_minor(minor) 

1406 except TapdiskNotRunning as e: 

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

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

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

1410 # the recorded minor. 

1411 else: 

1412 tapdisk.shutdown() 

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

1414 

1415 @classmethod 

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

1417 """ 

1418 Pauses the tapdisk. 

1419 

1420 session: a XAPI session 

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

1422 vdi_uuid: the UUID of the VDI to pause 

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

1424 non-blocking manner 

1425 """ 

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

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

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

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

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

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

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

1433 if not cls.call_pluginhandler(session, host_ref, 

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

1435 # Failed to pause node 

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

1437 return False 

1438 return True 

1439 

1440 @classmethod 

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

1442 activate_parents=False): 

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

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

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

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

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

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

1449 if not cls.call_pluginhandler(session, host_ref, 

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

1451 # Failed to unpause node 

1452 return False 

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

1454 return True 

1455 

1456 @classmethod 

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

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

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

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

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

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

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

1464 if not cls.call_pluginhandler(session, host_ref, 

1465 sr_uuid, vdi_uuid, "refresh", None, 

1466 activate_parents=activate_parents): 

1467 # Failed to refresh node 

1468 return False 

1469 return True 

1470 

1471 @classmethod 

1472 def tap_status(cls, session, vdi_uuid): 

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

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

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

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

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

1478 return True 

1479 return False 

1480 

1481 @classmethod 

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

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

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

1485 try: 

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

1487 "failfast": str(failfast)} 

1488 if secondary: 

1489 args["secondary"] = secondary 

1490 if activate_parents: 

1491 args["activate_parents"] = "true" 

1492 ret = session.xenapi.host.call_plugin( 

1493 host_ref, PLUGIN_TAP_PAUSE, action, 

1494 args) 

1495 return ret == "True" 

1496 except Exception as e: 

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

1498 return False 

1499 

1500 def _add_tag(self, vdi_uuid, writable): 

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

1502 attach_mode = "RO" 

1503 if writable: 

1504 attach_mode = "RW" 

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

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

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

1508 attached_as = util.attached_as(sm_config) 

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

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

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

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

1513 term_output=False, writable=writable): 

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

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

1516 if 'relinking' in sm_config: 

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

1518 return False 

1519 if 'paused' in sm_config: 

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

1521 return False 

1522 try: 

1523 self._session.xenapi.VDI.add_to_sm_config( 

1524 vdi_ref, 'activating', 'True') 

1525 except XenAPI.Failure as e: 

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

1527 # Someone else is activating - a retry might succeed 

1528 return False 

1529 raise 

1530 host_key = "host_%s" % host_ref 

1531 assert host_key not in sm_config 

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

1533 attach_mode) 

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

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

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

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

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

1539 self._session.xenapi.VDI.remove_from_sm_config( 

1540 vdi_ref, 'activating') 

1541 return False 

1542 util.SMlog("Activate lock succeeded") 

1543 return True 

1544 

1545 def _check_tag(self, vdi_uuid): 

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

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

1548 if 'paused' in sm_config: 

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

1550 return False 

1551 return True 

1552 

1553 def _remove_tag(self, vdi_uuid): 

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

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

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

1557 host_key = "host_%s" % host_ref 

1558 if host_key in sm_config: 

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

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

1561 else: 

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

1563 

1564 def _get_pool_config(self, pool_name): 

1565 pool_info = dict() 

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

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

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

1569 # special pool 

1570 return pool_info 

1571 

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

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

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

1575 pool_size_str = sr_config.get(POOL_SIZE_KEY) 

1576 pool_name_override = vdi_config.get(POOL_NAME_KEY) 

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

1578 pool_name = pool_name_override 

1579 pool_size_override = vdi_config.get(POOL_SIZE_KEY) 

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

1581 pool_size_str = pool_size_override 

1582 pool_size = 0 

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

1584 try: 

1585 pool_size = int(pool_size_str) 

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

1587 raise ValueError("outside of range") 

1588 pool_size = NUM_PAGES_PER_RING * pool_size 

1589 except ValueError: 

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

1591 pool_size = 0 

1592 

1593 pool_info["mem-pool"] = pool_name 

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

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

1596 

1597 return pool_info 

1598 

1599 def linkNBD(self, sr_uuid, vdi_uuid): 

1600 if self.tap: 

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

1602 int(self.tap.minor)) 

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

1604 

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

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

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

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

1609 util.SMlog("Attach & activate") 

1610 self._attach(sr_uuid, vdi_uuid) 

1611 dev_path = self._activate(sr_uuid, vdi_uuid, 

1612 {"rdonly": not writable}) 

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

1614 self.linkNBD(sr_uuid, vdi_uuid) 

1615 

1616 # Return backend/ link 

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

1618 if self.tap_wanted(): 

1619 # Only have NBD if we also have a tap 

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

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

1622 vdi_uuid) 

1623 else: 

1624 nbd_path = "" 

1625 

1626 options = {"rdonly": not writable} 

1627 options.update(caching_params) 

1628 o_direct, o_direct_reason = self.get_o_direct_capability(options) 

1629 struct = {'params': back_path, 

1630 'params_nbd': nbd_path, 

1631 'o_direct': o_direct, 

1632 'o_direct_reason': o_direct_reason, 

1633 'xenstore_data': self.xenstore_data} 

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

1635 

1636 try: 

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

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

1639 f.close() 

1640 except: 

1641 pass 

1642 

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

1644 

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

1646 util.SMlog("blktap2.activate") 

1647 options = {"rdonly": not writable} 

1648 options.update(caching_params) 

1649 

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

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

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

1653 try: 

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

1655 return 

1656 except util.SRBusyException: 

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

1658 time.sleep(1) 

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

1660 

1661 def _get_sr_master_host_ref(self) -> str: 

1662 """ 

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

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

1665 """ 

1666 sr = self.target.vdi.sr 

1667 if sr.is_shared(): 

1668 host_ref = util.get_master_ref(self._session) 

1669 else: 

1670 host_ref = sr.host_ref 

1671 return host_ref 

1672 

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

1674 vdi_chain = [] 

1675 path = self.target.get_vdi_path() 

1676 

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

1678 vdi_chain.append(extractUuid(path)) 

1679 parent = cowutil.getParentNoCheck(path) 

1680 while parent: 

1681 vdi_chain.append(extractUuid(parent)) 

1682 parent = cowutil.getParentNoCheck(parent) 

1683 vdi_chain.reverse() 

1684 return vdi_chain 

1685 

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

1687 vdi_type = self.target.get_vdi_type() 

1688 cowutil = getCowUtil(vdi_type) 

1689 

1690 if not cowutil.isCoalesceableOnRemote(): #We only need to stop the coalesce in case of QCOW2 1690 ↛ 1693line 1690 didn't jump to line 1693, because the condition on line 1690 was never false

1691 return True 

1692 

1693 path = self.target.get_vdi_path() 

1694 

1695 import fjournaler 

1696 import journaler 

1697 from lvmcowutil import LvmCowUtil 

1698 from FileSR import FileVDI 

1699 import lvmcache 

1700 

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

1702 # Different extractUUID & journaler function for LVMSR and FileSR 

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

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

1705 lvmCache = lvmcache.LVMCache(vgName) 

1706 journal = journaler.Journaler(lvmCache) 

1707 

1708 extractUuid = LvmCowUtil.extractUuid 

1709 else: 

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

1711 extractUuid = FileVDI.extractUuid 

1712 

1713 # Get the VDI chain 

1714 vdi_chain = self._get_vdi_chain(cowutil, extractUuid) 

1715 

1716 if len(vdi_chain) == 1: 

1717 # We only have a leaf, do nothing 

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

1719 return True 

1720 

1721 # Log the chain of active VDI 

1722 level = 0 

1723 util.SMlog("VDI chain:") 

1724 for vdi in vdi_chain: 

1725 prefix = " " * level 

1726 level += 1 

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

1728 

1729 vdi_to_cancel = [] 

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

1731 if entry in vdi_chain: 

1732 vdi_to_cancel.append(entry) 

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

1734 

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

1736 host_ref = self._get_sr_master_host_ref() 

1737 for vdi in vdi_to_cancel: 

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

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

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

1741 host_ref, PLUGIN_ON_SLAVE, "cancel_coalesce_master", args) 

1742 

1743 return True 

1744 

1745 @locking("VDIUnavailable") 

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

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

1748 

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

1750 refresh = False 

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

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

1753 return False 

1754 refresh = True 

1755 

1756 try: 

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

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

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

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

1761 # object completely 

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

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

1764 target.sr.srcmd.params = params 

1765 driver_info = target.sr.srcmd.driver_info 

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

1767 

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

1769 "blktap_activate_inject_failure", 

1770 lambda: util.inject_failure()) 

1771 

1772 # Attach the physical node 

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

1774 self._attach(sr_uuid, vdi_uuid) 

1775 

1776 vdi_type = self.target.get_vdi_type() 

1777 

1778 

1779 # Take lvchange-p Lock before running 

1780 # tap-ctl open 

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

1782 # now taking the same lock 

1783 # This is a fix for CA-155766 

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

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

1786 VdiType.isCowImage(vdi_type): 

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

1788 lock.acquire() 

1789 

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

1791 return False 

1792 # we could return false from here if we need to retry after relink 

1793 # #TODO: handling error here 

1794 

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

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

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

1798 session = self.target.vdi.session 

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

1800 # pylint: disable=used-before-assignment 

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

1802 if 'key_hash' in sm_config: 

1803 key_hash = sm_config['key_hash'] 

1804 options['key_hash'] = key_hash 

1805 options['vdi_uuid'] = vdi_uuid 

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

1807 # Activate the physical node 

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

1809 

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

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

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

1813 lock.release() 

1814 except: 

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

1816 if self.tap_wanted(): 

1817 util.fistpoint.activate_custom_fn( 

1818 "blktap_activate_error_handling", 

1819 lambda: time.sleep(30)) 

1820 while True: 

1821 try: 

1822 self._remove_tag(vdi_uuid) 

1823 break 

1824 except xmlrpc.client.ProtocolError as e: 

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

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

1827 continue 

1828 else: 

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

1830 break 

1831 except Exception as e: 

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

1833 break 

1834 raise 

1835 finally: 

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

1837 self._session.xenapi.VDI.remove_from_sm_config( 

1838 vdi_ref, 'activating') 

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

1840 

1841 # Link result to backend/ 

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

1843 self.linkNBD(sr_uuid, vdi_uuid) 

1844 return True 

1845 

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

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

1848 

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

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

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

1852 # Maybe launch a tapdisk on the physical link 

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

1854 vdi_type = self.target.get_vdi_type() 

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

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

1857 options.update(vdi_options) 

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

1859 sr_uuid, options, 

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

1861 else: 

1862 dev_path = phy_path # Just reuse phy 

1863 

1864 return dev_path 

1865 

1866 def _attach(self, sr_uuid, vdi_uuid): 

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

1868 params = attach_info['params'] 

1869 xenstore_data = attach_info['xenstore_data'] 

1870 phy_path = util.to_plain_string(params) 

1871 self.xenstore_data.update(xenstore_data) 

1872 # Save it to phy/ 

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

1874 

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

1876 util.SMlog("blktap2.deactivate") 

1877 for i in range(self.ATTACH_DETACH_RETRY_SECS): 

1878 try: 

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

1880 return 

1881 except util.SRBusyException as e: 

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

1883 time.sleep(1) 

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

1885 

1886 @locking("VDIUnavailable") 

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

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

1889 

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

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

1892 return False 

1893 

1894 self._deactivate(sr_uuid, vdi_uuid, caching_params) 

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

1896 self._detach(sr_uuid, vdi_uuid) 

1897 if self.tap_wanted(): 

1898 self._remove_tag(vdi_uuid) 

1899 

1900 return True 

1901 

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

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

1904 

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

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

1907 util.SMlog("Deactivate & detach") 

1908 self._deactivate(sr_uuid, vdi_uuid, caching_params) 

1909 self._detach(sr_uuid, vdi_uuid) 

1910 else: 

1911 pass # nothing to do 

1912 

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

1914 import VDI as sm 

1915 

1916 # Shutdown tapdisk 

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

1918 

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

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

1921 return 

1922 

1923 try: 

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

1925 os.unlink(attach_info_path) 

1926 except: 

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

1928 

1929 try: 

1930 major, minor = back_link.rdev() 

1931 except self.DeviceNode.NotABlockDevice: 

1932 pass 

1933 else: 

1934 if major == Tapdisk.major(): 

1935 self._tap_deactivate(minor) 

1936 self.remove_cache(sr_uuid, vdi_uuid, caching_params) 

1937 

1938 # Remove the backend link 

1939 back_link.unlink() 

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

1941 

1942 # Deactivate & detach the physical node 

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

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

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

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

1947 # object completely 

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

1949 driver_info = target.sr.srcmd.driver_info 

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

1951 

1952 self.target.deactivate(sr_uuid, vdi_uuid) 

1953 

1954 def _detach(self, sr_uuid, vdi_uuid): 

1955 self.target.detach(sr_uuid, vdi_uuid) 

1956 

1957 # Remove phy/ 

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

1959 

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

1961 # Remove existing VDI.sm_config fields 

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

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

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

1965 if not on_boot is None: 

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

1967 if not caching is None: 

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

1969 

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

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

1972 return 

1973 

1974 util.SMlog("Requested local caching") 

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

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

1977 return 

1978 

1979 scratch_mode = False 

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

1981 scratch_mode = True 

1982 util.SMlog("Requested scratch mode") 

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

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

1985 return 

1986 

1987 dev_path = None 

1988 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR) 

1989 if not local_sr_uuid: 

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

1991 return 

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

1993 local_sr_uuid, scratch_mode, params) 

1994 

1995 if dev_path: 

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

1997 params.get(self.CONF_KEY_MODE_ON_BOOT), 

1998 params.get(self.CONF_KEY_ALLOW_CACHING)) 

1999 

2000 return dev_path 

2001 

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

2003 vm_uuid = None 

2004 vm_label = "" 

2005 try: 

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

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

2008 cache_sr_label = cache_sr_rec.get("name_label") 

2009 

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

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

2012 host_label = host_rec.get("name_label") 

2013 

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

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

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

2017 for vbd_rec in vbds.values(): 

2018 vm_ref = vbd_rec.get("VM") 

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

2020 vm_uuid = vm_rec.get("uuid") 

2021 vm_label = vm_rec.get("name_label") 

2022 except: 

2023 util.logException("alert_no_cache") 

2024 

2025 alert_obj = "SR" 

2026 alert_uuid = str(cache_sr_uuid) 

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

2028 if vm_uuid: 

2029 alert_obj = "VM" 

2030 alert_uuid = vm_uuid 

2031 reason = "" 

2032 if err == errno.ENOSPC: 

2033 reason = "because there is no space left" 

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

2035 (vm_label, reason, cache_sr_label, host_label) 

2036 

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

2038 (alert_obj, alert_uuid, alert_str)) 

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

2040 alert_obj, alert_uuid, alert_str) 

2041 

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

2043 scratch_mode, options): 

2044 import SR 

2045 import EXTSR 

2046 import NFSSR 

2047 from lock import Lock 

2048 from FileSR import FileVDI 

2049 

2050 vdi_type = self.target.get_vdi_type() 

2051 tap_type = VDI._tap_type(vdi_type) 

2052 cowutil = getCowUtil(vdi_type) 

2053 

2054 parent_uuid = cowutil.getParent(self.target.vdi.path, FileVDI.extractUuid) 

2055 if not parent_uuid: 

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

2057 self.target.vdi.uuid) 

2058 return 

2059 

2060 util.SMlog("Setting up cache") 

2061 parent_uuid = parent_uuid.strip() 

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

2063 

2064 if shared_target.parent: 

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

2066 shared_target.uuid) 

2067 return 

2068 

2069 SR.registerSR(EXTSR.EXTSR) 

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

2071 

2072 lock = Lock(self.LOCK_CACHE_SETUP, parent_uuid) 

2073 lock.acquire() 

2074 

2075 # read cache 

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

2077 if util.pathexists(read_cache_path): 

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

2079 read_cache_path) 

2080 else: 

2081 try: 

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

2083 except util.CommandException as e: 

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

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

2086 return None 

2087 

2088 # local write node 

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

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

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

2092 if util.pathexists(local_leaf_path): 

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

2094 local_leaf_path) 

2095 os.unlink(local_leaf_path) 

2096 try: 

2097 cowutil.snapshot(local_leaf_path, read_cache_path, False, 

2098 msize=leaf_size, checkEmpty=False) 

2099 except util.CommandException as e: 

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

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

2102 return None 

2103 

2104 local_leaf_size = cowutil.getSizeVirt(local_leaf_path) 

2105 if leaf_size > local_leaf_size: 

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

2107 (leaf_size, local_leaf_size)) 

2108 cowutil.setSizeVirtFast(local_leaf_path, leaf_size) 

2109 

2110 prt_tapdisk = Tapdisk.find_by_path(read_cache_path) 

2111 if not prt_tapdisk: 

2112 parent_options = copy.deepcopy(options) 

2113 parent_options["rdonly"] = False 

2114 parent_options["lcache"] = True 

2115 

2116 blktap = Blktap.allocate() 

2117 try: 

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

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

2120 # its own pool 

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

2122 except: 

2123 blktap.free() 

2124 raise 

2125 

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

2127 

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

2129 leaf_tapdisk = Tapdisk.find_by_path(local_leaf_path) 

2130 if not leaf_tapdisk: 

2131 blktap = Blktap.allocate() 

2132 child_options = copy.deepcopy(options) 

2133 child_options["rdonly"] = False 

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

2135 child_options["existing_prt"] = prt_tapdisk.minor 

2136 child_options["secondary"] = secondary 

2137 child_options["standby"] = scratch_mode 

2138 try: 

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

2140 except: 

2141 blktap.free() 

2142 raise 

2143 

2144 lock.release() 

2145 

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

2147 (read_cache_path, local_leaf_path)) 

2148 

2149 self.tap = leaf_tapdisk 

2150 return leaf_tapdisk.get_devpath() 

2151 

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

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

2154 return 

2155 

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

2157 

2158 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR) 

2159 if caching and not local_sr_uuid: 

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

2161 return 

2162 

2163 if caching: 

2164 self._remove_cache(self._session, local_sr_uuid) 

2165 

2166 if self._session is not None: 

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

2168 

2169 def _is_tapdisk_in_use(self, minor): 

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

2171 if not retVal: 

2172 # err on the side of caution 

2173 return True 

2174 

2175 for link in links: 

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

2177 return True 

2178 

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

2180 for s in sockets: 

2181 if socket_re.match(s): 

2182 return True 

2183 

2184 return False 

2185 

2186 def _remove_cache(self, session, local_sr_uuid): 

2187 import SR 

2188 import EXTSR 

2189 import NFSSR 

2190 from lock import Lock 

2191 from FileSR import FileVDI 

2192 

2193 vdi_type = self.target.get_vdi_type() 

2194 parent_uuid = getCowUtil(vdi_type).getParent(self.target.vdi.path, FileVDI.extractUuid) 

2195 if not parent_uuid: 

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

2197 self.target.vdi.uuid) 

2198 return 

2199 

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

2201 

2202 parent_uuid = parent_uuid.strip() 

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

2204 

2205 SR.registerSR(EXTSR.EXTSR) 

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

2207 

2208 lock = Lock(self.LOCK_CACHE_SETUP, parent_uuid) 

2209 lock.acquire() 

2210 

2211 # local write node 

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

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

2214 if util.pathexists(local_leaf_path): 

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

2216 os.unlink(local_leaf_path) 

2217 

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

2219 prt_tapdisk = Tapdisk.find_by_path(read_cache_path) 

2220 if not prt_tapdisk: 

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

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

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

2224 read_cache_path) 

2225 try: 

2226 prt_tapdisk.shutdown() 

2227 except: 

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

2229 else: 

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

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

2232 # GC run 

2233 

2234 lock.release() 

2235 

2236PythonKeyError = KeyError 

2237 

2238 

2239class UEventHandler(object): 

2240 

2241 def __init__(self): 

2242 self._action = None 

2243 

2244 class KeyError(PythonKeyError): 

2245 def __init__(self, args): 

2246 super().__init__(args) 

2247 self.key = args[0] 

2248 

2249 @override 

2250 def __str__(self) -> str: 

2251 return \ 

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

2253 "Not called in udev context?" 

2254 

2255 @classmethod 

2256 def getenv(cls, key): 

2257 try: 

2258 return os.environ[key] 

2259 except KeyError as e: 

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

2261 

2262 def get_action(self): 

2263 if not self._action: 

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

2265 return self._action 

2266 

2267 class UnhandledEvent(Exception): 

2268 

2269 def __init__(self, event, handler): 

2270 self.event = event 

2271 self.handler = handler 

2272 

2273 @override 

2274 def __str__(self) -> str: 

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

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

2277 

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

2279 

2280 def run(self): 

2281 

2282 action = self.get_action() 

2283 try: 

2284 fn = self.ACTIONS[action] 

2285 except KeyError: 

2286 raise self.UnhandledEvent(action, self) 

2287 

2288 return fn(self) 

2289 

2290 @override 

2291 def __str__(self) -> str: 

2292 try: 

2293 action = self.get_action() 

2294 except: 

2295 action = None 

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

2297 

2298 

2299class __BlktapControl(ClassDevice): 

2300 SYSFS_CLASSTYPE = "misc" 

2301 

2302 def __init__(self): 

2303 ClassDevice.__init__(self) 

2304 self._default_pool = None 

2305 

2306 @override 

2307 def sysfs_devname(self) -> str: 

2308 return "blktap!control" 

2309 

2310 class DefaultPool(Attribute): 

2311 SYSFS_NODENAME = "default_pool" 

2312 

2313 def get_default_pool_attr(self): 

2314 if not self._default_pool: 

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

2316 return self._default_pool 

2317 

2318 def get_default_pool_name(self): 

2319 return self.get_default_pool_attr().readline() 

2320 

2321 def set_default_pool_name(self, name): 

2322 self.get_default_pool_attr().writeline(name) 

2323 

2324 def get_default_pool(self): 

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

2326 

2327 def set_default_pool(self, pool): 

2328 self.set_default_pool_name(pool.name) 

2329 

2330 class NoSuchPool(Exception): 

2331 def __init__(self, name): 

2332 self.name = name 

2333 

2334 @override 

2335 def __str__(self) -> str: 

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

2337 

2338 def get_pool(self, name): 

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

2340 

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

2342 raise self.NoSuchPool(name) 

2343 

2344 return PagePool(path) 

2345 

2346BlktapControl = __BlktapControl() 

2347 

2348 

2349class PagePool(KObject): 

2350 

2351 def __init__(self, path): 

2352 self.path = path 

2353 self._size = None 

2354 

2355 @override 

2356 def sysfs_devname(self) -> str: 

2357 return '' 

2358 

2359 def sysfs_path(self): 

2360 return self.path 

2361 

2362 class Size(Attribute): 

2363 SYSFS_NODENAME = "size" 

2364 

2365 def get_size_attr(self): 

2366 if not self._size: 

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

2368 return self._size 

2369 

2370 def set_size(self, pages): 

2371 pages = str(pages) 

2372 self.get_size_attr().writeline(pages) 

2373 

2374 def get_size(self): 

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

2376 return int(pages) 

2377 

2378 

2379class BusDevice(KObject): 

2380 

2381 SYSFS_BUSTYPE: ClassVar[str] = "" 

2382 

2383 @classmethod 

2384 def sysfs_bus_path(cls): 

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

2386 

2387 def sysfs_path(self): 

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

2389 self.sysfs_devname()) 

2390 

2391 return path 

2392 

2393 

2394class XenbusDevice(BusDevice): 

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

2396 

2397 XBT_NIL = "" 

2398 

2399 XENBUS_DEVTYPE: ClassVar[str] = "" 

2400 

2401 def __init__(self, domid, devid): 

2402 self.domid = int(domid) 

2403 self.devid = int(devid) 

2404 self._xbt = XenbusDevice.XBT_NIL 

2405 

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

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

2408 

2409 def xs_path(self, key=None): 

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

2411 self.domid, 

2412 self.devid) 

2413 if key is not None: 

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

2415 

2416 return path 

2417 

2418 def _log(self, prio, msg): 

2419 syslog(prio, msg) 

2420 

2421 def info(self, msg): 

2422 self._log(_syslog.LOG_INFO, msg) 

2423 

2424 def warn(self, msg): 

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

2426 

2427 def _xs_read_path(self, path): 

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

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

2430 return val 

2431 

2432 def _xs_write_path(self, path, val): 

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

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

2435 

2436 def _xs_rm_path(self, path): 

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

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

2439 

2440 def read(self, key): 

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

2442 

2443 def has_xs_key(self, key): 

2444 return self.read(key) is not None 

2445 

2446 def write(self, key, val): 

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

2448 

2449 def rm(self, key): 

2450 self._xs_rm_path(self.xs_path(key)) 

2451 

2452 def exists(self): 

2453 return self.has_xs_key(None) 

2454 

2455 def begin(self): 

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

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

2458 

2459 def commit(self): 

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

2461 self._xbt = XenbusDevice.XBT_NIL 

2462 return ok 

2463 

2464 def abort(self): 

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

2466 assert(ok == True) 

2467 self._xbt = XenbusDevice.XBT_NIL 

2468 

2469 def create_physical_device(self): 

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

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

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

2473 return 

2474 try: 

2475 params = self.read("params") 

2476 frontend = self.read("frontend") 

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

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

2479 # from opening the physical-device 

2480 if not(is_cdrom): 

2481 major_minor = os.stat(params).st_rdev 

2482 major, minor = divmod(major_minor, 256) 

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

2484 except: 

2485 util.logException("BLKTAP2:create_physical_device") 

2486 

2487 def signal_hotplug(self, online=True): 

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

2489 self.XENBUS_DEVTYPE, 

2490 self.devid) 

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

2492 if online: 

2493 self._xs_write_path(xapi_path, "online") 

2494 self._xs_write_path(upstream_path, "connected") 

2495 else: 

2496 self._xs_rm_path(xapi_path) 

2497 self._xs_rm_path(upstream_path) 

2498 

2499 @override 

2500 def sysfs_devname(self) -> str: 

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

2502 self.domid, self.devid) 

2503 

2504 @override 

2505 def __str__(self) -> str: 

2506 return self.sysfs_devname() 

2507 

2508 @classmethod 

2509 def find(cls): 

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

2511 cls.XENBUS_DEVTYPE) 

2512 for path in glob.glob(pattern): 

2513 

2514 name = os.path.basename(path) 

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

2516 

2517 yield cls(domid, devid) 

2518 

2519 

2520class XenBackendDevice(XenbusDevice): 

2521 """Xenbus backend device""" 

2522 SYSFS_BUSTYPE = "xen-backend" 

2523 

2524 @classmethod 

2525 def from_xs_path(cls, _path): 

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

2527 

2528 assert _backend == 'backend' 

2529 assert _type == cls.XENBUS_DEVTYPE 

2530 

2531 domid = int(domid) 

2532 devid = int(devid) 

2533 

2534 return cls(domid, devid) 

2535 

2536 

2537class Blkback(XenBackendDevice): 

2538 """A blkback VBD""" 

2539 

2540 XENBUS_DEVTYPE = "vbd" 

2541 

2542 def __init__(self, domid, devid): 

2543 XenBackendDevice.__init__(self, domid, devid) 

2544 self._phy = None 

2545 self._vdi_uuid = None 

2546 self._q_state = None 

2547 self._q_events = None 

2548 

2549 class XenstoreValueError(Exception): 

2550 KEY: ClassVar[str] = "" 

2551 

2552 def __init__(self, vbd, _str): 

2553 self.vbd = vbd 

2554 self.str = _str 

2555 

2556 @override 

2557 def __str__(self) -> str: 

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

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

2560 

2561 class PhysicalDeviceError(XenstoreValueError): 

2562 KEY = "physical-device" 

2563 

2564 class PhysicalDevice(object): 

2565 

2566 def __init__(self, major, minor): 

2567 self.major = int(major) 

2568 self.minor = int(minor) 

2569 

2570 @classmethod 

2571 def from_xbdev(cls, xbdev): 

2572 

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

2574 

2575 try: 

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

2577 major = int(major, 0x10) 

2578 minor = int(minor, 0x10) 

2579 except Exception as e: 

2580 raise xbdev.PhysicalDeviceError(xbdev, phy) 

2581 

2582 return cls(major, minor) 

2583 

2584 def makedev(self): 

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

2586 

2587 def is_tap(self): 

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

2589 

2590 @override 

2591 def __str__(self) -> str: 

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

2593 

2594 @override 

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

2596 return \ 

2597 self.major == other.major and \ 

2598 self.minor == other.minor 

2599 

2600 def get_physical_device(self): 

2601 if not self._phy: 

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

2603 return self._phy 

2604 

2605 class QueueEvents(Attribute): 

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

2607 notifications emitted.""" 

2608 

2609 SYSFS_NODENAME = "queue_events" 

2610 

2611 QUEUE_RUNNING = (1 << 0) 

2612 QUEUE_PAUSE_DONE = (1 << 1) 

2613 QUEUE_SHUTDOWN_DONE = (1 << 2) 

2614 QUEUE_PAUSE_REQUEST = (1 << 3) 

2615 QUEUE_SHUTDOWN_REQUEST = (1 << 4) 

2616 

2617 def get_mask(self): 

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

2619 

2620 def set_mask(self, mask): 

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

2622 

2623 def get_queue_events(self): 

2624 if not self._q_events: 

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

2626 return self._q_events 

2627 

2628 def get_vdi_uuid(self): 

2629 if not self._vdi_uuid: 

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

2631 return self._vdi_uuid 

2632 

2633 def pause_requested(self): 

2634 return self.has_xs_key("pause") 

2635 

2636 def shutdown_requested(self): 

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

2638 

2639 def shutdown_done(self): 

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

2641 

2642 def running(self): 

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

2644 

2645 @classmethod 

2646 def find_by_physical_device(cls, phy): 

2647 for dev in cls.find(): 

2648 try: 

2649 _phy = dev.get_physical_device() 

2650 except cls.PhysicalDeviceError: 

2651 continue 

2652 

2653 if _phy == phy: 

2654 yield dev 

2655 

2656 @classmethod 

2657 def find_by_tap_minor(cls, minor): 

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

2659 return cls.find_by_physical_device(phy) 

2660 

2661 @classmethod 

2662 def find_by_tap(cls, tapdisk): 

2663 return cls.find_by_tap_minor(tapdisk.minor) 

2664 

2665 def has_tap(self): 

2666 

2667 if not self.can_tap(): 

2668 return False 

2669 

2670 phy = self.get_physical_device() 

2671 if phy: 

2672 return phy.is_tap() 

2673 

2674 return False 

2675 

2676 def is_bare_hvm(self): 

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

2678 try: 

2679 self.get_physical_device() 

2680 

2681 except self.PhysicalDeviceError as e: 

2682 vdi_type = self.read("type") 

2683 

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

2685 

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

2687 raise 

2688 

2689 return True 

2690 

2691 return False 

2692 

2693 def can_tap(self): 

2694 return not self.is_bare_hvm() 

2695 

2696 

2697class BlkbackEventHandler(UEventHandler): 

2698 

2699 LOG_FACILITY = _syslog.LOG_DAEMON 

2700 

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

2702 if not ident: 

2703 ident = self.__class__.__name__ 

2704 

2705 self.ident = ident 

2706 self._vbd = None 

2707 self._tapdisk = None 

2708 

2709 UEventHandler.__init__(self) 

2710 

2711 @override 

2712 def run(self) -> None: 

2713 

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

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

2716 

2717 UEventHandler.run(self) 

2718 

2719 @override 

2720 def __str__(self) -> str: 

2721 

2722 try: 

2723 path = self.xs_path 

2724 except: 

2725 path = None 

2726 

2727 try: 

2728 action = self.get_action() 

2729 except: 

2730 action = None 

2731 

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

2733 

2734 def _log(self, prio, msg): 

2735 syslog(prio, msg) 

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

2737 

2738 def info(self, msg): 

2739 self._log(_syslog.LOG_INFO, msg) 

2740 

2741 def warn(self, msg): 

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

2743 

2744 def error(self, msg): 

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

2746 

2747 def get_vbd(self): 

2748 if not self._vbd: 

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

2750 return self._vbd 

2751 

2752 def get_tapdisk(self): 

2753 if not self._tapdisk: 

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

2755 self._tapdisk = Tapdisk.from_minor(minor) 

2756 return self._tapdisk 

2757 # 

2758 # Events 

2759 # 

2760 

2761 def __add(self): 

2762 vbd = self.get_vbd() 

2763 # Manage blkback transitions 

2764 # self._manage_vbd() 

2765 

2766 vbd.create_physical_device() 

2767 

2768 vbd.signal_hotplug() 

2769 

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

2771 def add(self): 

2772 try: 

2773 self.__add() 

2774 except Attribute.NoSuchAttribute as e: 

2775 # 

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

2777 # registers device attributes. So poll a little. 

2778 # 

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

2780 raise RetryLoop.TransientFailure(e) 

2781 

2782 def __change(self): 

2783 vbd = self.get_vbd() 

2784 

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

2786 

2787 if vbd.has_tap(): 

2788 pass 

2789 #self._pause_update_tap() 

2790 

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

2792 

2793 self._signal_xapi() 

2794 

2795 def change(self): 

2796 vbd = self.get_vbd() 

2797 

2798 # NB. Beware of spurious change events between shutdown 

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

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

2801 

2802 while True: 

2803 vbd.begin() 

2804 

2805 if not vbd.exists() or \ 

2806 vbd.shutdown_done(): 

2807 break 

2808 

2809 self.__change() 

2810 

2811 if vbd.commit(): 

2812 return 

2813 

2814 vbd.abort() 

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

2816 

2817 def remove(self): 

2818 vbd = self.get_vbd() 

2819 

2820 vbd.signal_hotplug(False) 

2821 

2822 ACTIONS = {'add': add, 

2823 'change': change, 

2824 'remove': remove} 

2825 # 

2826 # VDI.pause 

2827 # 

2828 

2829 def _tap_should_pause(self): 

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

2831 paused""" 

2832 

2833 tapdisk = self.get_tapdisk() 

2834 TapState = Tapdisk.PauseState 

2835 

2836 PAUSED = 'P' 

2837 RUNNING = 'R' 

2838 PAUSED_SHUTDOWN = 'P,S' 

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

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

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

2842 

2843 next = TapState.RUNNING 

2844 vbds = {} 

2845 

2846 for vbd in Blkback.find_by_tap(tapdisk): 

2847 name = str(vbd) 

2848 

2849 pausing = vbd.pause_requested() 

2850 closing = vbd.shutdown_requested() 

2851 running = vbd.running() 

2852 

2853 if pausing: 

2854 if closing and not running: 

2855 vbds[name] = PAUSED_SHUTDOWN 

2856 else: 

2857 vbds[name] = PAUSED 

2858 next = TapState.PAUSED 

2859 

2860 else: 

2861 vbds[name] = RUNNING 

2862 

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

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

2865 vbds, next)) 

2866 

2867 return next == TapState.PAUSED 

2868 

2869 def _pause_update_tap(self): 

2870 vbd = self.get_vbd() 

2871 

2872 if self._tap_should_pause(): 

2873 self._pause_tap() 

2874 else: 

2875 self._resume_tap() 

2876 

2877 def _pause_tap(self): 

2878 tapdisk = self.get_tapdisk() 

2879 

2880 if not tapdisk.is_paused(): 

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

2882 tapdisk.pause() 

2883 

2884 def _resume_tap(self): 

2885 tapdisk = self.get_tapdisk() 

2886 

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

2888 # type while resuming. 

2889 vbd = self.get_vbd() 

2890 vdi_uuid = vbd.get_vdi_uuid() 

2891 

2892 if tapdisk.is_paused(): 

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

2894 vdi = VDI.from_cli(vdi_uuid) 

2895 _type = vdi.get_tap_type() 

2896 path = vdi.get_phy_path() 

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

2898 tapdisk.unpause(_type, path) 

2899 # 

2900 # VBD.pause/shutdown 

2901 # 

2902 

2903 def _manage_vbd(self): 

2904 vbd = self.get_vbd() 

2905 # NB. Hook into VBD state transitions. 

2906 

2907 events = vbd.get_queue_events() 

2908 

2909 mask = 0 

2910 mask |= events.QUEUE_PAUSE_DONE # pause/unpause 

2911 mask |= events.QUEUE_SHUTDOWN_DONE # shutdown 

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

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

2914 

2915 events.set_mask(mask) 

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

2917 

2918 def _signal_xapi(self): 

2919 vbd = self.get_vbd() 

2920 

2921 pausing = vbd.pause_requested() 

2922 closing = vbd.shutdown_requested() 

2923 running = vbd.running() 

2924 

2925 handled = 0 

2926 

2927 if pausing and not running: 

2928 if 'pause-done' not in vbd: 

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

2930 handled += 1 

2931 

2932 if not pausing: 

2933 if 'pause-done' in vbd: 

2934 vbd.rm('pause-done') 

2935 handled += 1 

2936 

2937 if closing and not running: 

2938 if 'shutdown-done' not in vbd: 

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

2940 handled += 1 

2941 

2942 if handled > 1: 

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

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

2945 (pausing, closing, running)) 

2946 

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

2948 

2949 import sys 

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

2951 

2952 # 

2953 # Simple CLI interface for manual operation 

2954 # 

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

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

2957 # 

2958 

2959 def usage(stream): 

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

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

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

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

2964 

2965 try: 

2966 cmd = sys.argv[1] 

2967 except IndexError: 

2968 usage(sys.stderr) 

2969 sys.exit(1) 

2970 

2971 try: 

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

2973 except: 

2974 usage(sys.stderr) 

2975 sys.exit(1) 

2976 

2977 # 

2978 # Local Tapdisks 

2979 # 

2980 

2981 if cmd == 'tap.major': 

2982 

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

2984 

2985 elif cmd == 'tap.launch': 

2986 

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

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

2989 

2990 elif _class == 'tap': 

2991 

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

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

2994 try: 

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

2996 attrs[key] = val 

2997 continue 

2998 except ValueError: 

2999 pass 

3000 

3001 try: 

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

3003 continue 

3004 except ValueError: 

3005 pass 

3006 

3007 try: 

3008 arg = Tapdisk.Arg.parse(item) 

3009 attrs['_type'] = arg.type 

3010 attrs['path'] = arg.path 

3011 continue 

3012 except Tapdisk.Arg.InvalidArgument: 

3013 pass 

3014 

3015 attrs['path'] = item 

3016 

3017 if cmd == 'tap.list': 

3018 

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

3020 blktap = tapdisk.get_blktap() 

3021 print(tapdisk, end=' ') 

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

3023 (blktap, 

3024 blktap.get_task_pid(), 

3025 blktap.get_pool_name())) 

3026 

3027 elif cmd == 'tap.vbds': 

3028 # Find all Blkback instances for a given tapdisk 

3029 

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

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

3032 for vbd in Blkback.find_by_tap(tapdisk): 

3033 print(vbd, end=' ') 

3034 print() 

3035 

3036 else: 

3037 

3038 if not attrs: 

3039 usage(sys.stderr) 

3040 sys.exit(1) 

3041 

3042 try: 

3043 tapdisk = Tapdisk.get( ** attrs) 

3044 except TypeError: 

3045 usage(sys.stderr) 

3046 sys.exit(1) 

3047 

3048 if cmd == 'tap.shutdown': 

3049 # Shutdown a running tapdisk, or raise 

3050 tapdisk.shutdown() 

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

3052 

3053 elif cmd == 'tap.pause': 

3054 # Pause an unpaused tapdisk, or raise 

3055 tapdisk.pause() 

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

3057 

3058 elif cmd == 'tap.unpause': 

3059 # Unpause a paused tapdisk, or raise 

3060 tapdisk.unpause() 

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

3062 

3063 elif cmd == 'tap.stats': 

3064 # Gather tapdisk status 

3065 stats = tapdisk.stats() 

3066 print("%s:" % tapdisk) 

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

3068 

3069 else: 

3070 usage(sys.stderr) 

3071 sys.exit(1) 

3072 

3073 elif cmd == 'vbd.uevent': 

3074 

3075 hnd = BlkbackEventHandler(cmd) 

3076 

3077 if not sys.stdin.isatty(): 

3078 try: 

3079 hnd.run() 

3080 except Exception as e: 

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

3082 

3083 import traceback 

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

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

3086 for entry in trace: 

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

3088 util.SMlog(line) 

3089 else: 

3090 hnd.run() 

3091 

3092 elif cmd == 'vbd.list': 

3093 

3094 for vbd in Blkback.find(): 

3095 print(vbd, \ 

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

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

3098 

3099 else: 

3100 usage(sys.stderr) 

3101 sys.exit(1)