Coverage for libs/sm/blktap2.py: 55%

1656 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2026-07-10 07:59 +0000

1# Copyright (C) Citrix Systems Inc. 

2# 

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

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

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

6# 

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

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

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

10# GNU Lesser General Public License for more details. 

11# 

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

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

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

15# 

16# blktap2: blktap/tapdisk management layer 

17# 

18import grp 

19import os 

20import re 

21import stat 

22import time 

23import copy 

24import xmlrpc.client 

25import http.client 

26import errno 

27import signal 

28import subprocess 

29import syslog as _syslog 

30import glob 

31import json 

32from syslog import openlog, syslog 

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

34 

35import XenAPI # pylint: disable=import-error 

36from sm.core.lock import Lock 

37from sm.core import util 

38from sm.core import xs_errors 

39from sm.core import scsiutil 

40from sm import nfs 

41from sm import resetvdis 

42from sm import vhdutil 

43from sm import lvhdutil 

44from sm import VDI as sm 

45 

46# For RRDD Plugin Registration 

47from xmlrpc.client import ServerProxy, Transport 

48from socket import socket, AF_UNIX, SOCK_STREAM 

49 

50PLUGIN_TAP_PAUSE = "tapdisk-pause" 

51 

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

53 

54NUM_PAGES_PER_RING = 32 * 11 

55MAX_FULL_RINGS = 8 

56 

57ENABLE_MULTIPLE_ATTACH = "/etc/xensource/allow_multiple_vdi_attach" 

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

59 

60 

61def locking(excType, override=True): 

62 def locking2(op): 

63 def wrapper(self, *args): 

64 self.lock.acquire() 

65 try: 

66 try: 

67 ret = op(self, * args) 

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

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

70 msg = str(e) 

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

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

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

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

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

76 else: 

77 raise 

78 except: 

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

80 raise 

81 finally: 

82 self.lock.release() 

83 return ret 

84 return wrapper 

85 return locking2 

86 

87 

88class RetryLoop(object): 

89 

90 def __init__(self, backoff, limit): 

91 self.backoff = backoff 

92 self.limit = limit 

93 

94 def __call__(self, f): 

95 

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

97 attempt = 0 

98 

99 while True: 

100 attempt += 1 

101 

102 try: 

103 return f( * __t, ** __d) 

104 

105 except self.TransientFailure as e: 

106 e = e.exception 

107 

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

109 raise e 

110 

111 time.sleep(self.backoff) 

112 

113 return loop 

114 

115 class TransientFailure(Exception): 

116 def __init__(self, exception): 

117 self.exception = exception 

118 

119 

120def retried(**args): 

121 return RetryLoop( ** args) 

122 

123 

124class TapCtl(object): 

125 """Tapdisk IPC utility calls.""" 

126 

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

128 

129 def __init__(self, cmd, p): 

130 self.cmd = cmd 

131 self._p = p 

132 self.stdout = p.stdout 

133 

134 class CommandFailure(Exception): 

135 """TapCtl cmd failure.""" 

136 

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

138 self.cmd = cmd 

139 self.info = info 

140 

141 def __str__(self): 

142 items = self.info.items() 

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

144 for item in items) 

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

146 

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

148 # exception 

149 def __getattr__(self, key): 

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

151 return self.info[key] 

152 return object.__getattribute__(self, key) 

153 

154 @property 

155 def has_status(self): 

156 return 'status' in self.info 

157 

158 @property 

159 def has_signal(self): 

160 return 'signal' in self.info 

161 

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

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

164 def get_error_code(self): 

165 key = 'status' 

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

167 return self.info[key] 

168 else: 

169 return 0 

170 

171 @classmethod 

172 def __mkcmd_real(cls, args): 

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

174 

175 __next_mkcmd = __mkcmd_real 

176 

177 @classmethod 

178 def _mkcmd(cls, args): 

179 

180 __next_mkcmd = cls.__next_mkcmd 

181 cls.__next_mkcmd = cls.__mkcmd_real 

182 

183 return __next_mkcmd(args) 

184 

185 @classmethod 

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

187 """ 

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

189 Raises a TapCtl.CommandFailure if subprocess creation failed. 

190 """ 

191 cmd = cls._mkcmd(args) 

192 

193 if not quiet: 

194 util.SMlog(cmd) 

195 try: 

196 p = subprocess.Popen(cmd, 

197 stdin=subprocess.PIPE, 

198 stdout=subprocess.PIPE, 

199 stderr=subprocess.PIPE, 

200 close_fds=True, 

201 universal_newlines=text_mode) 

202 if input: 

203 p.stdin.write(input) 

204 p.stdin.close() 

205 except OSError as e: 

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

207 

208 return cls(cmd, p) 

209 

210 def _errmsg(self): 

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

212 return "; ".join(output) 

213 

214 def _wait(self, quiet=False): 

215 """ 

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

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

218 """ 

219 status = self._p.wait() 

220 if not quiet: 

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

222 

223 if status == 0: 

224 return 

225 

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

227 'pid': self._p.pid} 

228 

229 if status < 0: 

230 info['signal'] = -status 

231 else: 

232 info['status'] = status 

233 

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

235 

236 @classmethod 

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

238 """ 

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

240 """ 

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

242 text_mode=text_mode) 

243 

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

245 

246 tapctl._wait(quiet) 

247 return output 

248 

249 @staticmethod 

250 def _maybe(opt, parm): 

251 if parm is not None: 

252 return [opt, parm] 

253 return [] 

254 

255 @classmethod 

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

257 args = ["list"] 

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

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

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

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

262 

263 tapctl = cls._call(args, True) 

264 

265 for stdout_line in tapctl.stdout: 

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

267 # confuses this parser 

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

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

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

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

272 row = {} 

273 

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

275 bits = field.split('=') 

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

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

278 

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

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

281 

282 elif key in ('state'): 

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

284 

285 else: 

286 row[key] = val 

287 else: 

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

289 yield row 

290 

291 tapctl._wait(True) 

292 

293 @classmethod 

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

295 def list(cls, **args): 

296 

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

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

299 # be fixed in SM. 

300 

301 try: 

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

303 

304 except cls.CommandFailure as e: 

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

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

307 raise RetryLoop.TransientFailure(e) 

308 raise 

309 

310 @classmethod 

311 def allocate(cls, devpath=None): 

312 args = ["allocate"] 

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

314 return cls._pread(args) 

315 

316 @classmethod 

317 def free(cls, minor): 

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

319 cls._pread(args) 

320 

321 @classmethod 

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

323 def spawn(cls): 

324 args = ["spawn"] 

325 try: 

326 pid = cls._pread(args) 

327 return int(pid) 

328 except cls.CommandFailure as ce: 

329 # intermittent failures to spawn. CA-292268 

330 if ce.status == 1: 

331 raise RetryLoop.TransientFailure(ce) 

332 raise 

333 

334 @classmethod 

335 def attach(cls, pid, minor): 

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

337 cls._pread(args) 

338 

339 @classmethod 

340 def detach(cls, pid, minor): 

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

342 cls._pread(args) 

343 

344 @classmethod 

345 def _load_key(cls, key_hash, vdi_uuid): 

346 from sm import plugins 

347 

348 return plugins.load_key(key_hash, vdi_uuid) 

349 

350 @classmethod 

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

352 params = Tapdisk.Arg(_type, _file) 

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

354 text_mode = True 

355 input = None 

356 if options.get("rdonly"): 

357 args.append('-R') 

358 if options.get("lcache"): 

359 args.append("-r") 

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

361 args.append("-e") 

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

363 if options.get("secondary"): 

364 args.append("-2") 

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

366 if options.get("standby"): 

367 args.append("-s") 

368 if options.get("timeout"): 

369 args.append("-t") 

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

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

372 args.append("-D") 

373 if options.get('cbtlog'): 

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

375 if options.get('key_hash'): 

376 key_hash = options['key_hash'] 

377 vdi_uuid = options['vdi_uuid'] 

378 key = cls._load_key(key_hash, vdi_uuid) 

379 

380 if not key: 

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

382 input = key 

383 text_mode = False 

384 args.append('-E') 

385 

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

387 

388 @classmethod 

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

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

391 if force: 

392 args += ["-f"] 

393 cls._pread(args) 

394 

395 @classmethod 

396 def pause(cls, pid, minor): 

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

398 cls._pread(args) 

399 

400 @classmethod 

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

402 cbtlog=None): 

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

404 if mirror: 

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

406 if _type and _file: 

407 params = Tapdisk.Arg(_type, _file) 

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

409 if cbtlog: 

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

411 cls._pread(args) 

412 

413 @classmethod 

414 def shutdown(cls, pid): 

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

416 os.kill(pid, signal.SIGTERM) 

417 os.waitpid(pid, 0) 

418 

419 @classmethod 

420 def stats(cls, pid, minor): 

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

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

423 

424 @classmethod 

425 def major(cls): 

426 args = ["major"] 

427 major = cls._pread(args) 

428 return int(major) 

429 

430 

431class TapdiskExists(Exception): 

432 """Tapdisk already running.""" 

433 

434 def __init__(self, tapdisk): 

435 self.tapdisk = tapdisk 

436 

437 def __str__(self): 

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

439 

440 

441class TapdiskNotRunning(Exception): 

442 """No such Tapdisk.""" 

443 

444 def __init__(self, **attrs): 

445 self.attrs = attrs 

446 

447 def __str__(self): 

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

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

450 for attr in items) 

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

452 

453 

454class TapdiskNotUnique(Exception): 

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

456 

457 def __init__(self, tapdisks): 

458 self.tapdisks = tapdisks 

459 

460 def __str__(self): 

461 tapdisks = map(str, self.tapdisks) 

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

463 

464 

465class TapdiskFailed(Exception): 

466 """Tapdisk launch failure.""" 

467 

468 def __init__(self, arg, err): 

469 self.arg = arg 

470 self.err = err 

471 

472 def __str__(self): 

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

474 

475 def get_error(self): 

476 return self.err 

477 

478 

479class TapdiskInvalidState(Exception): 

480 """Tapdisk pause/unpause failure""" 

481 

482 def __init__(self, tapdisk): 

483 self.tapdisk = tapdisk 

484 

485 def __str__(self): 

486 return str(self.tapdisk) 

487 

488 

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

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

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

492 assert parent != path 

493 try: 

494 if parent: 

495 mkdirs(parent, mode) 

496 if subdir: 

497 os.mkdir(path, mode) 

498 except OSError as e: 

499 if e.errno != errno.EEXIST: 

500 raise 

501 

502 

503class KObject(object): 

504 

505 SYSFS_CLASSTYPE = None 

506 

507 def sysfs_devname(self): 

508 raise NotImplementedError("sysfs_devname is undefined") 

509 

510 

511class Attribute(object): 

512 

513 SYSFS_NODENAME = None 

514 

515 def __init__(self, path): 

516 self.path = path 

517 

518 @classmethod 

519 def from_kobject(cls, kobj): 

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

521 return cls(path) 

522 

523 class NoSuchAttribute(Exception): 

524 def __init__(self, name): 

525 self.name = name 

526 

527 def __str__(self): 

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

529 

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

531 try: 

532 return open(self.path, mode) 

533 except IOError as e: 

534 if e.errno == errno.ENOENT: 

535 raise self.NoSuchAttribute(self) 

536 raise 

537 

538 def readline(self): 

539 f = self._open('r') 

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

541 f.close() 

542 return s 

543 

544 def writeline(self, val): 

545 f = self._open('w') 

546 f.write(val) 

547 f.close() 

548 

549 

550class ClassDevice(KObject): 

551 

552 @classmethod 

553 def sysfs_class_path(cls): 

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

555 

556 def sysfs_path(self): 

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

558 self.sysfs_devname()) 

559 

560 

561class Blktap(ClassDevice): 

562 

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

564 TAP_MINOR_BASE = '/run/blktap-control/tapdisk' 

565 

566 SYSFS_CLASSTYPE = "blktap2" 

567 

568 def __init__(self, minor): 

569 self.minor = minor 

570 self._task = None 

571 

572 @classmethod 

573 def allocate(cls): 

574 # FIXME. Should rather go into init. 

575 mkdirs(cls.DEV_BASEDIR) 

576 

577 devname = TapCtl.allocate() 

578 minor = Tapdisk._parse_minor(devname) 

579 return cls(minor) 

580 

581 def free(self): 

582 TapCtl.free(self.minor) 

583 

584 def __str__(self): 

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

586 

587 def sysfs_devname(self): 

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

589 

590 class Task(Attribute): 

591 SYSFS_NODENAME = "task" 

592 

593 def get_task_attr(self): 

594 if not self._task: 

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

596 return self._task 

597 

598 def get_task_pid(self): 

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

600 try: 

601 return int(pid) 

602 except ValueError: 

603 return None 

604 

605 def find_tapdisk(self): 

606 pid = self.get_task_pid() 

607 if pid is None: 

608 return None 

609 

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

611 

612 def get_tapdisk(self): 

613 tapdisk = self.find_tapdisk() 

614 if not tapdisk: 

615 raise TapdiskNotRunning(minor=self.minor) 

616 return tapdisk 

617 

618 

619class Tapdisk(object): 

620 

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

622 

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

624 self.pid = pid 

625 self.minor = minor 

626 self.type = _type 

627 self.path = path 

628 self.state = state 

629 self._dirty = False 

630 self._blktap = None 

631 

632 def __str__(self): 

633 state = self.pause_state() 

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

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

636 

637 @classmethod 

638 def list(cls, **args): 

639 

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

641 

642 args = {'pid': None, 

643 'minor': None, 

644 'state': None, 

645 '_type': None, 

646 'path': None} 

647 

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

649 if key in args: 

650 args[key] = val 

651 

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

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

654 args['_type'] = image.type 

655 args['path'] = image.path 

656 

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

658 continue 

659 

660 yield Tapdisk( ** args) 

661 

662 @classmethod 

663 def find(cls, **args): 

664 

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

666 

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

668 raise TapdiskNotUnique(found) 

669 

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

671 return found[0] 

672 

673 return None 

674 

675 @classmethod 

676 def find_by_path(cls, path): 

677 return cls.find(path=path) 

678 

679 @classmethod 

680 def find_by_minor(cls, minor): 

681 return cls.find(minor=minor) 

682 

683 @classmethod 

684 def get(cls, **attrs): 

685 

686 tapdisk = cls.find( ** attrs) 

687 

688 if not tapdisk: 

689 raise TapdiskNotRunning( ** attrs) 

690 

691 return tapdisk 

692 

693 @classmethod 

694 def from_path(cls, path): 

695 return cls.get(path=path) 

696 

697 @classmethod 

698 def from_minor(cls, minor): 

699 return cls.get(minor=minor) 

700 

701 @classmethod 

702 def __from_blktap(cls, blktap): 

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

704 tapdisk._blktap = blktap 

705 return tapdisk 

706 

707 def get_blktap(self): 

708 if not self._blktap: 

709 self._blktap = Blktap(self.minor) 

710 return self._blktap 

711 

712 class Arg: 

713 

714 def __init__(self, _type, path): 

715 self.type = _type 

716 self.path = path 

717 

718 def __str__(self): 

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

720 

721 @classmethod 

722 def parse(cls, arg): 

723 

724 try: 

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

726 except ValueError: 

727 raise cls.InvalidArgument(arg) 

728 

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

730 raise cls.InvalidType(_type) 

731 

732 return cls(_type, path) 

733 

734 class InvalidType(Exception): 

735 def __init__(self, _type): 

736 self.type = _type 

737 

738 def __str__(self): 

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

740 

741 class InvalidArgument(Exception): 

742 def __init__(self, arg): 

743 self.arg = arg 

744 

745 def __str__(self): 

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

747 

748 def get_arg(self): 

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

750 

751 def get_devpath(self): 

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

753 

754 @classmethod 

755 def launch_from_arg(cls, arg): 

756 arg = cls.Arg.parse(arg) 

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

758 

759 @staticmethod 

760 def cgclassify(pid): 

761 

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

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

764 # we have configured in the spec file. 

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

766 try: 

767 util.pread2(cmd) 

768 except util.CommandException as e: 

769 util.logException(e) 

770 

771 @classmethod 

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

773 

774 tapdisk = cls.find_by_path(path) 

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

776 raise TapdiskExists(tapdisk) 

777 

778 minor = blktap.minor 

779 try: 

780 pid = TapCtl.spawn() 

781 cls.cgclassify(pid) 

782 try: 

783 TapCtl.attach(pid, minor) 

784 

785 try: 

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

787 try: 

788 return cls.__from_blktap(blktap) 

789 except: 

790 TapCtl.close(pid, minor) 

791 raise 

792 

793 except: 

794 TapCtl.detach(pid, minor) 

795 raise 

796 

797 except: 

798 try: 

799 TapCtl.shutdown(pid) 

800 except: 

801 # Best effort to shutdown 

802 pass 

803 raise 

804 

805 except TapCtl.CommandFailure as ctl: 

806 util.logException(ctl) 

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

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

809 raise xs_errors.XenError('TapdiskDriveEmpty') 

810 else: 

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

812 

813 @classmethod 

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

815 blktap = Blktap.allocate() 

816 try: 

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

818 except: 

819 blktap.free() 

820 raise 

821 

822 def shutdown(self, force=False): 

823 

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

825 

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

827 

828 self.get_blktap().free() 

829 

830 def pause(self): 

831 

832 if not self.is_running(): 

833 raise TapdiskInvalidState(self) 

834 

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

836 

837 self._set_dirty() 

838 

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

840 

841 if not self.is_paused(): 

842 raise TapdiskInvalidState(self) 

843 

844 # FIXME: should the arguments be optional? 

845 if _type is None: 

846 _type = self.type 

847 if path is None: 

848 path = self.path 

849 

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

851 cbtlog=cbtlog) 

852 

853 self._set_dirty() 

854 

855 def stats(self): 

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

857 # 

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

859 # 

860 

861 def _set_dirty(self): 

862 self._dirty = True 

863 

864 def _refresh(self, __get): 

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

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

867 

868 def __getattribute__(self, name): 

869 def __get(name): 

870 # NB. avoid(rec(ursion) 

871 return object.__getattribute__(self, name) 

872 

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

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

875 self._refresh(__get) 

876 self._dirty = False 

877 

878 return __get(name) 

879 

880 class PauseState: 

881 RUNNING = 'R' 

882 PAUSING = 'r' 

883 PAUSED = 'P' 

884 

885 class Flags: 

886 DEAD = 0x0001 

887 CLOSED = 0x0002 

888 QUIESCE_REQUESTED = 0x0004 

889 QUIESCED = 0x0008 

890 PAUSE_REQUESTED = 0x0010 

891 PAUSED = 0x0020 

892 SHUTDOWN_REQUESTED = 0x0040 

893 LOCKING = 0x0080 

894 RETRY_NEEDED = 0x0100 

895 LOG_DROPPED = 0x0200 

896 

897 PAUSE_MASK = PAUSE_REQUESTED | PAUSED 

898 

899 def is_paused(self): 

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

901 

902 def is_running(self): 

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

904 

905 def pause_state(self): 

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

907 return self.PauseState.PAUSED 

908 

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

910 return self.PauseState.PAUSING 

911 

912 return self.PauseState.RUNNING 

913 

914 @staticmethod 

915 def _parse_minor(devpath): 

916 regex = r'%s/tapdisk-(\d+)$' % Blktap.TAP_MINOR_BASE 

917 pattern = re.compile(regex) 

918 groups = pattern.search(devpath) 

919 if not groups: 

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

921 

922 minor = int(groups.group(1)) 

923 return minor 

924 

925 _major = None 

926 

927 @classmethod 

928 def major(cls): 

929 if cls._major: 

930 return cls._major 

931 

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

933 for line in devices: 

934 

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

936 if len(row) != 2: 

937 continue 

938 

939 major, name = row 

940 if name != 'tapdev': 

941 continue 

942 

943 cls._major = int(major) 

944 break 

945 

946 devices.close() 

947 return cls._major 

948 

949 

950class VDI(object): 

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

952 

953 CONF_KEY_ALLOW_CACHING = "vdi_allow_caching" 

954 CONF_KEY_MODE_ON_BOOT = "vdi_on_boot" 

955 CONF_KEY_CACHE_SR = "local_cache_sr" 

956 CONF_KEY_O_DIRECT = "o_direct" 

957 LOCK_CACHE_SETUP = "cachesetup" 

958 

959 ATTACH_DETACH_RETRY_SECS = 120 

960 

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

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

963 self._vdi_uuid = uuid 

964 self._session = target.session 

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

966 self.__o_direct = None 

967 self.__o_direct_reason = None 

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

969 self.tap = None 

970 

971 def get_o_direct_capability(self, options): 

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

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

974 return self.__o_direct, self.__o_direct_reason 

975 

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

977 self.__o_direct = True 

978 self.__o_direct_reason = "LICENSE_RESTRICTION" 

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

980 self.__o_direct = True 

981 self.__o_direct_reason = "SR_NOT_SUPPORTED" 

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

983 self.__o_direct = True 

984 self.__o_direct_reason = "RO_WITH_NO_PARENT" 

985 elif options.get(self.CONF_KEY_O_DIRECT): 

986 self.__o_direct = True 

987 self.__o_direct_reason = "SR_OVERRIDE" 

988 

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

990 self.__o_direct = False 

991 self.__o_direct_reason = "" 

992 

993 return self.__o_direct, self.__o_direct_reason 

994 

995 @classmethod 

996 def from_cli(cls, uuid): 

997 session = XenAPI.xapi_local() 

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

999 

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

1001 driver_info = target.sr.srcmd.driver_info 

1002 

1003 session.xenapi.session.logout() 

1004 

1005 return cls(uuid, target, driver_info) 

1006 

1007 @staticmethod 

1008 def _tap_type(vdi_type): 

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

1010 return { 

1011 'raw': 'aio', 

1012 'vhd': 'vhd', 

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

1014 'aio': 'aio', # for LVHD 

1015 'file': 'aio', 

1016 'phy': 'aio' 

1017 }[vdi_type] 

1018 

1019 def get_tap_type(self): 

1020 vdi_type = self.target.get_vdi_type() 

1021 return VDI._tap_type(vdi_type) 

1022 

1023 def get_phy_path(self): 

1024 return self.target.get_vdi_path() 

1025 

1026 class UnexpectedVDIType(Exception): 

1027 

1028 def __init__(self, vdi_type, target): 

1029 self.vdi_type = vdi_type 

1030 self.target = target 

1031 

1032 def __str__(self): 

1033 return \ 

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

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

1036 

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

1038 'raw': 'phy', 

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

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

1041 'file': 'tap', 

1042 'vhd': 'tap'} 

1043 

1044 def tap_wanted(self): 

1045 # 1. Let the target vdi_type decide 

1046 

1047 vdi_type = self.target.get_vdi_type() 

1048 

1049 try: 

1050 plug_type = self.VDI_PLUG_TYPE[vdi_type] 

1051 except KeyError: 

1052 raise self.UnexpectedVDIType(vdi_type, 

1053 self.target.vdi) 

1054 

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

1056 return True 

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

1058 return True 

1059 # 2. Otherwise, there may be more reasons 

1060 # 

1061 # .. TBD 

1062 

1063 return False 

1064 

1065 class TargetDriver: 

1066 """Safe target driver access.""" 

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

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

1069 # try/except would risk breaking compatibility. 

1070 

1071 def __init__(self, vdi, driver_info): 

1072 self.vdi = vdi 

1073 self._caps = driver_info['capabilities'] 

1074 

1075 def has_cap(self, cap): 

1076 """Determine if target has given capability""" 

1077 return cap in self._caps 

1078 

1079 def attach(self, sr_uuid, vdi_uuid): 

1080 #assert self.has_cap("VDI_ATTACH") 

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

1082 

1083 def detach(self, sr_uuid, vdi_uuid): 

1084 #assert self.has_cap("VDI_DETACH") 

1085 self.vdi.detach(sr_uuid, vdi_uuid) 

1086 

1087 def activate(self, sr_uuid, vdi_uuid): 

1088 if self.has_cap("VDI_ACTIVATE"): 

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

1090 

1091 def deactivate(self, sr_uuid, vdi_uuid): 

1092 if self.has_cap("VDI_DEACTIVATE"): 

1093 self.vdi.deactivate(sr_uuid, vdi_uuid) 

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

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

1096 

1097 def get_vdi_type(self): 

1098 _type = self.vdi.vdi_type 

1099 if not _type: 

1100 _type = self.vdi.sr.sr_vditype 

1101 if not _type: 

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

1103 return _type 

1104 

1105 def get_vdi_path(self): 

1106 return self.vdi.path 

1107 

1108 class Link(object): 

1109 """Relink a node under a common name""" 

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

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

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

1113 # relink existing devices under deterministic path names. 

1114 

1115 BASEDIR = None 

1116 

1117 def _mklink(self, target): 

1118 raise NotImplementedError("_mklink is not defined") 

1119 

1120 def _equals(self, target): 

1121 raise NotImplementedError("_equals is not defined") 

1122 

1123 def __init__(self, path): 

1124 self._path = path 

1125 

1126 @classmethod 

1127 def from_name(cls, name): 

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

1129 return cls(path) 

1130 

1131 @classmethod 

1132 def from_uuid(cls, sr_uuid, vdi_uuid): 

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

1134 return cls.from_name(name) 

1135 

1136 def path(self): 

1137 return self._path 

1138 

1139 def stat(self): 

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

1141 

1142 def mklink(self, target): 

1143 

1144 path = self.path() 

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

1146 

1147 mkdirs(os.path.dirname(path)) 

1148 try: 

1149 self._mklink(target) 

1150 except OSError as e: 

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

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

1153 # be seen. 

1154 if e.errno != errno.EEXIST: 

1155 raise 

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

1157 

1158 def unlink(self): 

1159 try: 

1160 os.unlink(self.path()) 

1161 except OSError as e: 

1162 if e.errno != errno.ENOENT: 

1163 raise 

1164 

1165 def __str__(self): 

1166 path = self.path() 

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

1168 

1169 class SymLink(Link): 

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

1171 

1172 def readlink(self): 

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

1174 

1175 def symlink(self): 

1176 return self.path() 

1177 

1178 def _mklink(self, target): 

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

1180 

1181 def _equals(self, target): 

1182 return self.readlink() == target 

1183 

1184 

1185 class PhyLink(SymLink): 

1186 BASEDIR = "/dev/sm/phy" 

1187 

1188 

1189 class NBDLink(SymLink): 

1190 

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

1192 

1193 def read_minor_from_path(self): 

1194 regex = r'.*/nbd\d+.(\d+)$' 

1195 match = re.search(regex, os.path.realpath(self._path)) 

1196 if not match: 

1197 raise Exception("Failed to parse minor from %s" % self._path) 

1198 

1199 return int(match.group(1)) 

1200 

1201 

1202 @staticmethod 

1203 def _tap_activate(phy_path, vdi_type, sr_uuid, options): 

1204 

1205 tapdisk = Tapdisk.find_by_path(phy_path) 

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

1207 blktap = Blktap.allocate() 

1208 

1209 try: 

1210 tapdisk = \ 

1211 Tapdisk.launch_on_tap(blktap, 

1212 phy_path, 

1213 VDI._tap_type(vdi_type), 

1214 options) 

1215 except: 

1216 blktap.free() 

1217 raise 

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

1219 

1220 else: 

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

1222 

1223 return tapdisk.get_devpath(), tapdisk 

1224 

1225 @staticmethod 

1226 def _tap_deactivate(minor): 

1227 

1228 try: 

1229 tapdisk = Tapdisk.from_minor(minor) 

1230 except TapdiskNotRunning as e: 

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

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

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

1234 # the recorded minor. 

1235 else: 

1236 tapdisk.shutdown() 

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

1238 

1239 @classmethod 

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

1241 """ 

1242 Pauses the tapdisk. 

1243 

1244 session: a XAPI session 

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

1246 vdi_uuid: the UUID of the VDI to pause 

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

1248 non-blocking manner 

1249 """ 

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

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

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

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

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

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

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

1257 if not cls.call_pluginhandler(session, host_ref, 

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

1259 # Failed to pause node 

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

1261 return False 

1262 return True 

1263 

1264 @classmethod 

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

1266 activate_parents=False): 

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

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

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

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

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

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

1273 if not cls.call_pluginhandler(session, host_ref, 

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

1275 # Failed to unpause node 

1276 return False 

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

1278 return True 

1279 

1280 @classmethod 

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

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

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

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

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

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

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

1288 if not cls.call_pluginhandler(session, host_ref, 

1289 sr_uuid, vdi_uuid, "refresh", None, 

1290 activate_parents=activate_parents): 

1291 # Failed to refresh node 

1292 return False 

1293 return True 

1294 

1295 @classmethod 

1296 def tap_status(cls, session, vdi_uuid): 

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

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

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

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

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

1302 return True 

1303 return False 

1304 

1305 @classmethod 

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

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

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

1309 try: 

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

1311 "failfast": str(failfast)} 

1312 if secondary: 

1313 args["secondary"] = secondary 

1314 if activate_parents: 

1315 args["activate_parents"] = "true" 

1316 ret = session.xenapi.host.call_plugin( 

1317 host_ref, PLUGIN_TAP_PAUSE, action, 

1318 args) 

1319 return ret == "True" 

1320 except Exception as e: 

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

1322 return False 

1323 

1324 def _add_tag(self, vdi_uuid, writable): 

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

1326 attach_mode = "RO" 

1327 if writable: 

1328 attach_mode = "RW" 

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

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

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

1332 attached_as = util.attached_as(sm_config) 

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

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

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

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

1337 term_output=False, writable=writable): 

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

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

1340 if 'relinking' in sm_config: 

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

1342 return False 

1343 if 'paused' in sm_config: 

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

1345 return False 

1346 try: 

1347 self._session.xenapi.VDI.add_to_sm_config( 

1348 vdi_ref, 'activating', 'True') 

1349 except XenAPI.Failure as e: 

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

1351 # Someone else is activating - a retry might succeed 

1352 return False 

1353 raise 

1354 host_key = "host_%s" % host_ref 

1355 assert host_key not in sm_config 

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

1357 attach_mode) 

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

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

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

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

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

1363 self._session.xenapi.VDI.remove_from_sm_config( 

1364 vdi_ref, 'activating') 

1365 return False 

1366 util.SMlog("Activate lock succeeded") 

1367 return True 

1368 

1369 def _check_tag(self, vdi_uuid): 

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

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

1372 if 'paused' in sm_config: 

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

1374 return False 

1375 return True 

1376 

1377 def _remove_tag(self, vdi_uuid): 

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

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

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

1381 host_key = "host_%s" % host_ref 

1382 if host_key in sm_config: 

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

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

1385 else: 

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

1387 

1388 def linkNBD(self, sr_uuid, vdi_uuid): 

1389 if self.tap: 

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

1391 int(self.tap.minor)) 

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

1393 

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

1395 """Return attach details to allow access to storage datapath""" 

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

1397 util.SMlog("Attach & activate") 

1398 self._attach(sr_uuid, vdi_uuid) 

1399 dev_path = self._activate(sr_uuid, vdi_uuid, 

1400 {"rdonly": not writable}) 

1401 self.linkNBD(sr_uuid, vdi_uuid) 

1402 

1403 # Return NBD link 

1404 if self.tap_wanted(): 

1405 # Only have NBD if we also have a tap 

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

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

1408 vdi_uuid) 

1409 else: 

1410 nbd_path = "" 

1411 

1412 options = {"rdonly": not writable} 

1413 options.update(caching_params) 

1414 o_direct, o_direct_reason = self.get_o_direct_capability(options) 

1415 struct = {'params_nbd': nbd_path, 

1416 'o_direct': o_direct, 

1417 'o_direct_reason': o_direct_reason, 

1418 'xenstore_data': self.xenstore_data} 

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

1420 

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

1422 

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

1424 util.SMlog("blktap2.activate") 

1425 options = {"rdonly": not writable} 

1426 options.update(caching_params) 

1427 

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

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

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

1431 try: 

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

1433 return 

1434 except util.SRBusyException: 

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

1436 time.sleep(1) 

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

1438 

1439 @locking("VDIUnavailable") 

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

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

1442 

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

1444 refresh = False 

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

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

1447 return False 

1448 refresh = True 

1449 

1450 try: 

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

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

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

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

1455 # object completely 

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

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

1458 target.sr.srcmd.params = params 

1459 driver_info = target.sr.srcmd.driver_info 

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

1461 

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

1463 "blktap_activate_inject_failure", 

1464 lambda: util.inject_failure()) 

1465 

1466 # Attach the physical node 

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

1468 self._attach(sr_uuid, vdi_uuid) 

1469 

1470 vdi_type = self.target.get_vdi_type() 

1471 

1472 # Take lvchange-p Lock before running 

1473 # tap-ctl open 

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

1475 # now taking the same lock 

1476 # This is a fix for CA-155766 

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

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

1479 vdi_type == vhdutil.VDI_TYPE_VHD: 

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

1481 lock.acquire() 

1482 

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

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

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

1486 session = self.target.vdi.session 

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

1488 # pylint: disable=used-before-assignment 

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

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

1491 key_hash = sm_config['key_hash'] 

1492 options['key_hash'] = key_hash 

1493 options['vdi_uuid'] = vdi_uuid 

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

1495 # Activate the physical node 

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

1497 

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

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

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

1501 lock.release() 

1502 except: 

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

1504 if self.tap_wanted(): 

1505 util.fistpoint.activate_custom_fn( 

1506 "blktap_activate_error_handling", 

1507 lambda: time.sleep(30)) 

1508 while True: 

1509 try: 

1510 self._remove_tag(vdi_uuid) 

1511 break 

1512 except xmlrpc.client.ProtocolError as e: 

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

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

1515 continue 

1516 else: 

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

1518 break 

1519 except Exception as e: 

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

1521 break 

1522 raise 

1523 finally: 

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

1525 self._session.xenapi.VDI.remove_from_sm_config( 

1526 vdi_ref, 'activating') 

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

1528 

1529 # Link result to backend/ 

1530 self.linkNBD(sr_uuid, vdi_uuid) 

1531 return True 

1532 

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

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

1535 

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

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

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

1539 # Maybe launch a tapdisk on the physical link 

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

1541 vdi_type = self.target.get_vdi_type() 

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

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

1544 options.update(vdi_options) 

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

1546 sr_uuid, options) 

1547 else: 

1548 dev_path = phy_path # Just reuse phy 

1549 

1550 return dev_path 

1551 

1552 def _attach(self, sr_uuid, vdi_uuid): 

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

1554 params = attach_info['params'] 

1555 xenstore_data = attach_info['xenstore_data'] 

1556 phy_path = util.to_plain_string(params) 

1557 self.xenstore_data.update(xenstore_data) 

1558 # Save it to phy/ 

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

1560 

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

1562 util.SMlog("blktap2.deactivate") 

1563 for i in range(self.ATTACH_DETACH_RETRY_SECS): 

1564 try: 

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

1566 return 

1567 except util.SRBusyException as e: 

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

1569 time.sleep(1) 

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

1571 

1572 @locking("VDIUnavailable") 

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

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

1575 

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

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

1578 return False 

1579 

1580 self._deactivate(sr_uuid, vdi_uuid, caching_params) 

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

1582 self._detach(sr_uuid, vdi_uuid) 

1583 if self.tap_wanted(): 

1584 self._remove_tag(vdi_uuid) 

1585 

1586 return True 

1587 

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

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

1590 

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

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

1593 util.SMlog("Deactivate & detach") 

1594 self._deactivate(sr_uuid, vdi_uuid, caching_params) 

1595 self._detach(sr_uuid, vdi_uuid) 

1596 else: 

1597 pass # nothing to do 

1598 

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

1600 # Shutdown tapdisk 

1601 nbd_link = self.NBDLink.from_uuid(sr_uuid, vdi_uuid) 

1602 

1603 if not util.pathexists(nbd_link.path()): 

1604 util.SMlog("Nbd path %s does not exist" % nbd_link.path()) 

1605 return 

1606 

1607 minor = nbd_link.read_minor_from_path() 

1608 self._tap_deactivate(minor) 

1609 self.remove_cache(caching_params) 

1610 

1611 # Remove the backend link 

1612 nbd_link.unlink() 

1613 

1614 # Deactivate & detach the physical node 

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

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

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

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

1619 # object completely 

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

1621 driver_info = target.sr.srcmd.driver_info 

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

1623 

1624 self.target.deactivate(sr_uuid, vdi_uuid) 

1625 

1626 def _detach(self, sr_uuid, vdi_uuid): 

1627 self.target.detach(sr_uuid, vdi_uuid) 

1628 

1629 # Remove phy/ 

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

1631 

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

1633 # Remove existing VDI.sm_config fields 

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

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

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

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

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

1639 if not caching is None: 

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

1641 

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

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

1644 return 

1645 

1646 util.SMlog("Requested local caching") 

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

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

1649 return 

1650 

1651 scratch_mode = False 

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

1653 scratch_mode = True 

1654 util.SMlog("Requested scratch mode") 

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

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

1657 return 

1658 

1659 dev_path = None 

1660 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR) 

1661 if not local_sr_uuid: 

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

1663 return 

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

1665 local_sr_uuid, scratch_mode, params) 

1666 

1667 if dev_path: 

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

1669 params.get(self.CONF_KEY_MODE_ON_BOOT), 

1670 params.get(self.CONF_KEY_ALLOW_CACHING)) 

1671 

1672 return dev_path 

1673 

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

1675 vm_uuid = None 

1676 vm_label = "" 

1677 try: 

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

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

1680 cache_sr_label = cache_sr_rec.get("name_label") 

1681 

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

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

1684 host_label = host_rec.get("name_label") 

1685 

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

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

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

1689 for vbd_rec in vbds.values(): 

1690 vm_ref = vbd_rec.get("VM") 

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

1692 vm_uuid = vm_rec.get("uuid") 

1693 vm_label = vm_rec.get("name_label") 

1694 except: 

1695 util.logException("alert_no_cache") 

1696 

1697 alert_obj = "SR" 

1698 alert_uuid = str(cache_sr_uuid) 

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

1700 if vm_uuid: 

1701 alert_obj = "VM" 

1702 alert_uuid = vm_uuid 

1703 reason = "" 

1704 if err == errno.ENOSPC: 

1705 reason = "because there is no space left" 

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

1707 (vm_label, reason, cache_sr_label, host_label) 

1708 

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

1710 (alert_obj, alert_uuid, alert_str)) 

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

1712 alert_obj, alert_uuid, alert_str) 

1713 

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

1715 scratch_mode, options): 

1716 from sm import SR 

1717 from sm.drivers import EXTSR 

1718 

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

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

1721 self.target.vdi.uuid) 

1722 return 

1723 

1724 util.SMlog("Setting up cache") 

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

1726 

1727 if shared_target.parent: 

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

1729 shared_target.uuid) 

1730 return 

1731 

1732 SR.registerSR(EXTSR.EXTSR) 

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

1734 

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

1736 lock.acquire() 

1737 

1738 # read cache 

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

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

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

1742 read_cache_path) 

1743 else: 

1744 try: 

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

1746 except util.CommandException as e: 

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

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

1749 return None 

1750 

1751 # local write node 

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

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

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

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

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

1757 local_leaf_path) 

1758 os.unlink(local_leaf_path) 

1759 try: 

1760 vhdutil.snapshot(local_leaf_path, read_cache_path, False, 

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

1762 except util.CommandException as e: 

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

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

1765 return None 

1766 

1767 local_leaf_size = vhdutil.getSizeVirt(local_leaf_path) 

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

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

1770 (leaf_size, local_leaf_size)) 

1771 vhdutil.setSizeVirtFast(local_leaf_path, leaf_size) 

1772 

1773 prt_tapdisk = Tapdisk.find_by_path(read_cache_path) 

1774 if not prt_tapdisk: 

1775 parent_options = copy.deepcopy(options) 

1776 parent_options["rdonly"] = False 

1777 parent_options["lcache"] = True 

1778 

1779 blktap = Blktap.allocate() 

1780 try: 

1781 prt_tapdisk = \ 

1782 Tapdisk.launch_on_tap(blktap, read_cache_path, 

1783 'vhd', parent_options) 

1784 except: 

1785 blktap.free() 

1786 raise 

1787 

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

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

1790 

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

1792 leaf_tapdisk = Tapdisk.find_by_path(local_leaf_path) 

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

1794 blktap = Blktap.allocate() 

1795 child_options = copy.deepcopy(options) 

1796 child_options["rdonly"] = False 

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

1798 child_options["existing_prt"] = prt_tapdisk.minor 

1799 child_options["secondary"] = secondary 

1800 child_options["standby"] = scratch_mode 

1801 # Disable memory read caching 

1802 child_options.pop("o_direct", None) 

1803 try: 

1804 leaf_tapdisk = \ 

1805 Tapdisk.launch_on_tap(blktap, local_leaf_path, 

1806 'vhd', child_options) 

1807 except: 

1808 blktap.free() 

1809 raise 

1810 

1811 lock.release() 

1812 

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

1814 (read_cache_path, local_leaf_path)) 

1815 

1816 self.tap = leaf_tapdisk 

1817 return leaf_tapdisk.get_devpath() 

1818 

1819 def remove_cache(self, params): 

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

1821 return 

1822 

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

1824 

1825 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR) 

1826 if caching and not local_sr_uuid: 

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

1828 return 

1829 

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

1831 self._remove_cache(self._session, local_sr_uuid) 

1832 

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

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

1835 

1836 def _is_tapdisk_in_use(self, minor): 

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

1838 if not retVal: 

1839 # err on the side of caution 

1840 return True 

1841 

1842 for link in links: 

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

1844 return True 

1845 

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

1847 for s in sockets: 

1848 if socket_re.match(s): 

1849 return True 

1850 

1851 return False 

1852 

1853 def _remove_cache(self, session, local_sr_uuid): 

1854 from sm import SR 

1855 from sm.drivers import EXTSR 

1856 

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

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

1859 self.target.vdi.uuid) 

1860 return 

1861 

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

1863 

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

1865 

1866 SR.registerSR(EXTSR.EXTSR) 

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

1868 

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

1870 lock.acquire() 

1871 

1872 # local write node 

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

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

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

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

1877 os.unlink(local_leaf_path) 

1878 

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

1880 prt_tapdisk = Tapdisk.find_by_path(read_cache_path) 

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

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

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

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

1885 read_cache_path) 

1886 try: 

1887 prt_tapdisk.shutdown() 

1888 except: 

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

1890 else: 

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

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

1893 # GC run 

1894 

1895 lock.release() 

1896 

1897 @staticmethod 

1898 def _no_parent(vdi): 

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

1900 

1901 

1902PythonKeyError = KeyError 

1903 

1904 

1905class UEventHandler(object): 

1906 

1907 def __init__(self): 

1908 self._action = None 

1909 

1910 class KeyError(PythonKeyError): 

1911 def __init__(self, args): 

1912 super().__init__(args) 

1913 self.key = args[0] 

1914 

1915 def __str__(self): 

1916 return \ 

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

1918 "Not called in udev context?" 

1919 

1920 @classmethod 

1921 def getenv(cls, key): 

1922 try: 

1923 return os.environ[key] 

1924 except KeyError as e: 

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

1926 

1927 def get_action(self): 

1928 if not self._action: 

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

1930 return self._action 

1931 

1932 class UnhandledEvent(Exception): 

1933 

1934 def __init__(self, event, handler): 

1935 self.event = event 

1936 self.handler = handler 

1937 

1938 def __str__(self): 

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

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

1941 

1942 ACTIONS = {} 

1943 

1944 def run(self): 

1945 

1946 action = self.get_action() 

1947 try: 

1948 fn = self.ACTIONS[action] 

1949 except KeyError: 

1950 raise self.UnhandledEvent(action, self) 

1951 

1952 return fn(self) 

1953 

1954 def __str__(self): 

1955 try: 

1956 action = self.get_action() 

1957 except: 

1958 action = None 

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

1960 

1961 

1962class __BlktapControl(ClassDevice): 

1963 SYSFS_CLASSTYPE = "misc" 

1964 

1965 def __init__(self): 

1966 ClassDevice.__init__(self) 

1967 

1968 def sysfs_devname(self): 

1969 return "blktap!control" 

1970 

1971 

1972BlktapControl = __BlktapControl() 

1973 

1974 

1975class BusDevice(KObject): 

1976 

1977 SYSFS_BUSTYPE = None 

1978 

1979 @classmethod 

1980 def sysfs_bus_path(cls): 

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

1982 

1983 def sysfs_path(self): 

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

1985 self.sysfs_devname()) 

1986 

1987 return path 

1988 

1989 

1990class XenbusDevice(BusDevice): 

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

1992 

1993 XBT_NIL = "" 

1994 

1995 XENBUS_DEVTYPE = None 

1996 

1997 def __init__(self, domid, devid): 

1998 self.domid = int(domid) 

1999 self.devid = int(devid) 

2000 self._xbt = XenbusDevice.XBT_NIL 

2001 

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

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

2004 

2005 def xs_path(self, key=None): 

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

2007 self.domid, 

2008 self.devid) 

2009 if key is not None: 

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

2011 

2012 return path 

2013 

2014 def _log(self, prio, msg): 

2015 syslog(prio, msg) 

2016 

2017 def info(self, msg): 

2018 self._log(_syslog.LOG_INFO, msg) 

2019 

2020 def warn(self, msg): 

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

2022 

2023 def _xs_read_path(self, path): 

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

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

2026 return val 

2027 

2028 def _xs_write_path(self, path, val): 

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

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

2031 

2032 def _xs_rm_path(self, path): 

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

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

2035 

2036 def read(self, key): 

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

2038 

2039 def has_xs_key(self, key): 

2040 return self.read(key) is not None 

2041 

2042 def write(self, key, val): 

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

2044 

2045 def rm(self, key): 

2046 self._xs_rm_path(self.xs_path(key)) 

2047 

2048 def exists(self): 

2049 return self.has_xs_key(None) 

2050 

2051 def begin(self): 

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

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

2054 

2055 def commit(self): 

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

2057 self._xbt = XenbusDevice.XBT_NIL 

2058 return ok 

2059 

2060 def abort(self): 

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

2062 assert(ok == True) 

2063 self._xbt = XenbusDevice.XBT_NIL 

2064 

2065 def create_physical_device(self): 

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

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

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

2069 return 

2070 try: 

2071 params = self.read("params") 

2072 frontend = self.read("frontend") 

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

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

2075 # from opening the physical-device 

2076 if not(is_cdrom): 

2077 major_minor = os.stat(params).st_rdev 

2078 major, minor = divmod(major_minor, 256) 

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

2080 except: 

2081 util.logException("BLKTAP2:create_physical_device") 

2082 

2083 def signal_hotplug(self, online=True): 

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

2085 self.XENBUS_DEVTYPE, 

2086 self.devid) 

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

2088 if online: 

2089 self._xs_write_path(xapi_path, "online") 

2090 self._xs_write_path(upstream_path, "connected") 

2091 else: 

2092 self._xs_rm_path(xapi_path) 

2093 self._xs_rm_path(upstream_path) 

2094 

2095 def sysfs_devname(self): 

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

2097 self.domid, self.devid) 

2098 

2099 def __str__(self): 

2100 return self.sysfs_devname() 

2101 

2102 @classmethod 

2103 def find(cls): 

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

2105 cls.XENBUS_DEVTYPE) 

2106 for path in glob.glob(pattern): 

2107 

2108 name = os.path.basename(path) 

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

2110 

2111 yield cls(domid, devid) 

2112 

2113 

2114class XenBackendDevice(XenbusDevice): 

2115 """Xenbus backend device""" 

2116 SYSFS_BUSTYPE = "xen-backend" 

2117 

2118 @classmethod 

2119 def from_xs_path(cls, _path): 

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

2121 

2122 assert _backend == 'backend' 

2123 assert _type == cls.XENBUS_DEVTYPE 

2124 

2125 domid = int(domid) 

2126 devid = int(devid) 

2127 

2128 return cls(domid, devid) 

2129 

2130 

2131class Blkback(XenBackendDevice): 

2132 """A blkback VBD""" 

2133 

2134 XENBUS_DEVTYPE = "vbd" 

2135 

2136 def __init__(self, domid, devid): 

2137 XenBackendDevice.__init__(self, domid, devid) 

2138 self._phy = None 

2139 self._vdi_uuid = None 

2140 self._q_state = None 

2141 self._q_events = None 

2142 

2143 class XenstoreValueError(Exception): 

2144 KEY = None 

2145 

2146 def __init__(self, vbd, _str): 

2147 self.vbd = vbd 

2148 self.str = _str 

2149 

2150 def __str__(self): 

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

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

2153 

2154 class PhysicalDeviceError(XenstoreValueError): 

2155 KEY = "physical-device" 

2156 

2157 class PhysicalDevice(object): 

2158 

2159 def __init__(self, major, minor): 

2160 self.major = int(major) 

2161 self.minor = int(minor) 

2162 

2163 @classmethod 

2164 def from_xbdev(cls, xbdev): 

2165 

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

2167 

2168 try: 

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

2170 major = int(major, 0x10) 

2171 minor = int(minor, 0x10) 

2172 except Exception as e: 

2173 raise xbdev.PhysicalDeviceError(xbdev, phy) 

2174 

2175 return cls(major, minor) 

2176 

2177 def makedev(self): 

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

2179 

2180 def is_tap(self): 

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

2182 

2183 def __str__(self): 

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

2185 

2186 def __eq__(self, other): 

2187 return \ 

2188 self.major == other.major and \ 

2189 self.minor == other.minor 

2190 

2191 def get_physical_device(self): 

2192 if not self._phy: 

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

2194 return self._phy 

2195 

2196 class QueueEvents(Attribute): 

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

2198 notifications emitted.""" 

2199 

2200 SYSFS_NODENAME = "queue_events" 

2201 

2202 QUEUE_RUNNING = (1 << 0) 

2203 QUEUE_PAUSE_DONE = (1 << 1) 

2204 QUEUE_SHUTDOWN_DONE = (1 << 2) 

2205 QUEUE_PAUSE_REQUEST = (1 << 3) 

2206 QUEUE_SHUTDOWN_REQUEST = (1 << 4) 

2207 

2208 def get_mask(self): 

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

2210 

2211 def set_mask(self, mask): 

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

2213 

2214 def get_queue_events(self): 

2215 if not self._q_events: 

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

2217 return self._q_events 

2218 

2219 def get_vdi_uuid(self): 

2220 if not self._vdi_uuid: 

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

2222 return self._vdi_uuid 

2223 

2224 def pause_requested(self): 

2225 return self.has_xs_key("pause") 

2226 

2227 def shutdown_requested(self): 

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

2229 

2230 def shutdown_done(self): 

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

2232 

2233 def running(self): 

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

2235 

2236 @classmethod 

2237 def find_by_physical_device(cls, phy): 

2238 for dev in cls.find(): 

2239 try: 

2240 _phy = dev.get_physical_device() 

2241 except cls.PhysicalDeviceError: 

2242 continue 

2243 

2244 if _phy == phy: 

2245 yield dev 

2246 

2247 @classmethod 

2248 def find_by_tap_minor(cls, minor): 

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

2250 return cls.find_by_physical_device(phy) 

2251 

2252 @classmethod 

2253 def find_by_tap(cls, tapdisk): 

2254 return cls.find_by_tap_minor(tapdisk.minor) 

2255 

2256 def has_tap(self): 

2257 

2258 if not self.can_tap(): 

2259 return False 

2260 

2261 phy = self.get_physical_device() 

2262 if phy: 

2263 return phy.is_tap() 

2264 

2265 return False 

2266 

2267 def is_bare_hvm(self): 

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

2269 try: 

2270 self.get_physical_device() 

2271 

2272 except self.PhysicalDeviceError as e: 

2273 vdi_type = self.read("type") 

2274 

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

2276 

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

2278 raise 

2279 

2280 return True 

2281 

2282 return False 

2283 

2284 def can_tap(self): 

2285 return not self.is_bare_hvm() 

2286 

2287 

2288class BlkbackEventHandler(UEventHandler): 

2289 

2290 LOG_FACILITY = _syslog.LOG_DAEMON 

2291 

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

2293 if not ident: 

2294 ident = self.__class__.__name__ 

2295 

2296 self.ident = ident 

2297 self._vbd = None 

2298 self._tapdisk = None 

2299 

2300 UEventHandler.__init__(self) 

2301 

2302 def run(self): 

2303 

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

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

2306 

2307 UEventHandler.run(self) 

2308 

2309 def __str__(self): 

2310 

2311 try: 

2312 path = self.xs_path 

2313 except: 

2314 path = None 

2315 

2316 try: 

2317 action = self.get_action() 

2318 except: 

2319 action = None 

2320 

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

2322 

2323 def _log(self, prio, msg): 

2324 syslog(prio, msg) 

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

2326 

2327 def info(self, msg): 

2328 self._log(_syslog.LOG_INFO, msg) 

2329 

2330 def warn(self, msg): 

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

2332 

2333 def error(self, msg): 

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

2335 

2336 def get_vbd(self): 

2337 if not self._vbd: 

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

2339 return self._vbd 

2340 

2341 def get_tapdisk(self): 

2342 if not self._tapdisk: 

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

2344 self._tapdisk = Tapdisk.from_minor(minor) 

2345 return self._tapdisk 

2346 # 

2347 # Events 

2348 # 

2349 

2350 def __add(self): 

2351 vbd = self.get_vbd() 

2352 # Manage blkback transitions 

2353 # self._manage_vbd() 

2354 

2355 vbd.create_physical_device() 

2356 

2357 vbd.signal_hotplug() 

2358 

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

2360 def add(self): 

2361 try: 

2362 self.__add() 

2363 except Attribute.NoSuchAttribute as e: 

2364 # 

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

2366 # registers device attributes. So poll a little. 

2367 # 

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

2369 raise RetryLoop.TransientFailure(e) 

2370 

2371 def __change(self): 

2372 vbd = self.get_vbd() 

2373 

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

2375 

2376 if vbd.has_tap(): 

2377 pass 

2378 #self._pause_update_tap() 

2379 

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

2381 

2382 self._signal_xapi() 

2383 

2384 def change(self): 

2385 vbd = self.get_vbd() 

2386 

2387 # NB. Beware of spurious change events between shutdown 

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

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

2390 

2391 while True: 

2392 vbd.begin() 

2393 

2394 if not vbd.exists() or \ 

2395 vbd.shutdown_done(): 

2396 break 

2397 

2398 self.__change() 

2399 

2400 if vbd.commit(): 

2401 return 

2402 

2403 vbd.abort() 

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

2405 

2406 def remove(self): 

2407 vbd = self.get_vbd() 

2408 

2409 vbd.signal_hotplug(False) 

2410 

2411 ACTIONS = {'add': add, 

2412 'change': change, 

2413 'remove': remove} 

2414 # 

2415 # VDI.pause 

2416 # 

2417 

2418 def _tap_should_pause(self): 

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

2420 paused""" 

2421 

2422 tapdisk = self.get_tapdisk() 

2423 TapState = Tapdisk.PauseState 

2424 

2425 PAUSED = 'P' 

2426 RUNNING = 'R' 

2427 PAUSED_SHUTDOWN = 'P,S' 

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

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

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

2431 

2432 next = TapState.RUNNING 

2433 vbds = {} 

2434 

2435 for vbd in Blkback.find_by_tap(tapdisk): 

2436 name = str(vbd) 

2437 

2438 pausing = vbd.pause_requested() 

2439 closing = vbd.shutdown_requested() 

2440 running = vbd.running() 

2441 

2442 if pausing: 

2443 if closing and not running: 

2444 vbds[name] = PAUSED_SHUTDOWN 

2445 else: 

2446 vbds[name] = PAUSED 

2447 next = TapState.PAUSED 

2448 

2449 else: 

2450 vbds[name] = RUNNING 

2451 

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

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

2454 vbds, next)) 

2455 

2456 return next == TapState.PAUSED 

2457 

2458 def _pause_update_tap(self): 

2459 vbd = self.get_vbd() 

2460 

2461 if self._tap_should_pause(): 

2462 self._pause_tap() 

2463 else: 

2464 self._resume_tap() 

2465 

2466 def _pause_tap(self): 

2467 tapdisk = self.get_tapdisk() 

2468 

2469 if not tapdisk.is_paused(): 

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

2471 tapdisk.pause() 

2472 

2473 def _resume_tap(self): 

2474 tapdisk = self.get_tapdisk() 

2475 

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

2477 # type while resuming. 

2478 vbd = self.get_vbd() 

2479 vdi_uuid = vbd.get_vdi_uuid() 

2480 

2481 if tapdisk.is_paused(): 

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

2483 vdi = VDI.from_cli(vdi_uuid) 

2484 _type = vdi.get_tap_type() 

2485 path = vdi.get_phy_path() 

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

2487 tapdisk.unpause(_type, path) 

2488 # 

2489 # VBD.pause/shutdown 

2490 # 

2491 

2492 def _manage_vbd(self): 

2493 vbd = self.get_vbd() 

2494 # NB. Hook into VBD state transitions. 

2495 

2496 events = vbd.get_queue_events() 

2497 

2498 mask = 0 

2499 mask |= events.QUEUE_PAUSE_DONE # pause/unpause 

2500 mask |= events.QUEUE_SHUTDOWN_DONE # shutdown 

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

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

2503 

2504 events.set_mask(mask) 

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

2506 

2507 def _signal_xapi(self): 

2508 vbd = self.get_vbd() 

2509 

2510 pausing = vbd.pause_requested() 

2511 closing = vbd.shutdown_requested() 

2512 running = vbd.running() 

2513 

2514 handled = 0 

2515 

2516 if pausing and not running: 

2517 if 'pause-done' not in vbd: 

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

2519 handled += 1 

2520 

2521 if not pausing: 

2522 if 'pause-done' in vbd: 

2523 vbd.rm('pause-done') 

2524 handled += 1 

2525 

2526 if closing and not running: 

2527 if 'shutdown-done' not in vbd: 

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

2529 handled += 1 

2530 

2531 if handled > 1: 

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

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

2534 (pausing, closing, running)) 

2535