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# 

20import grp 

21import os 

22import re 

23import stat 

24import time 

25import copy 

26from lock import Lock 

27import util 

28import xmlrpc.client 

29import http.client 

30import errno 

31import signal 

32import subprocess 

33import syslog as _syslog 

34import glob 

35import json 

36import xs_errors 

37import XenAPI # pylint: disable=import-error 

38import scsiutil 

39from syslog import openlog, syslog 

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

41import nfs 

42 

43import resetvdis 

44import vhdutil 

45import lvhdutil 

46 

47import VDI as sm 

48 

49# For RRDD Plugin Registration 

50from xmlrpc.client import ServerProxy, Transport 

51from socket import socket, AF_UNIX, SOCK_STREAM 

52 

53try: 

54 from linstorvolumemanager import log_drbd_openers 

55 LINSTOR_AVAILABLE = True 

56except ImportError: 

57 LINSTOR_AVAILABLE = False 

58 

59PLUGIN_TAP_PAUSE = "tapdisk-pause" 

60 

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

62 

63NUM_PAGES_PER_RING = 32 * 11 

64MAX_FULL_RINGS = 8 

65POOL_NAME_KEY = "mem-pool" 

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

67 

68ENABLE_MULTIPLE_ATTACH = "/etc/xensource/allow_multiple_vdi_attach" 

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

70 

71 

72def locking(excType, override=True): 

73 def locking2(op): 

74 def wrapper(self, *args): 

75 self.lock.acquire() 

76 try: 

77 try: 

78 ret = op(self, * args) 

79 except (util.CommandException, util.SMException, XenAPI.Failure) as e: 

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

81 msg = str(e) 

82 if isinstance(e, util.CommandException): 

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

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

85 if override: 

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

87 else: 

88 raise 

89 except: 

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

91 raise 

92 finally: 

93 self.lock.release() 93 ↛ exitline 93 didn't except from function 'wrapper', because the raise on line 86 wasn't executed or the raise on line 88 wasn't executed or the raise on line 91 wasn't executed

94 return ret 

95 return wrapper 

96 return locking2 

97 

98 

99class RetryLoop(object): 

100 

101 def __init__(self, backoff, limit): 

102 self.backoff = backoff 

103 self.limit = limit 

104 

105 def __call__(self, f): 

106 

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

108 attempt = 0 

109 

110 while True: 

111 attempt += 1 

112 

113 try: 

114 return f( * __t, ** __d) 

115 

116 except self.TransientFailure as e: 

117 e = e.exception 

118 

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

120 raise e 

121 

122 time.sleep(self.backoff) 

123 

124 return loop 

125 

126 class TransientFailure(Exception): 

127 def __init__(self, exception): 

128 self.exception = exception 

129 

130 

131def retried(**args): 

132 return RetryLoop( ** args) 

133 

134 

135class TapCtl(object): 

136 """Tapdisk IPC utility calls.""" 

137 

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

139 

140 def __init__(self, cmd, p): 

141 self.cmd = cmd 

142 self._p = p 

143 self.stdout = p.stdout 

144 

145 class CommandFailure(Exception): 

146 """TapCtl cmd failure.""" 

147 

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

149 self.cmd = cmd 

150 self.info = info 

151 

152 def __str__(self): 

153 items = self.info.items() 

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

155 for item in items) 

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

157 

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

159 # exception 

160 def __getattr__(self, key): 

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

162 return self.info[key] 

163 return object.__getattribute__(self, key) 

164 

165 @property 

166 def has_status(self): 

167 return 'status' in self.info 

168 

169 @property 

170 def has_signal(self): 

171 return 'signal' in self.info 

172 

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

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

175 def get_error_code(self): 

176 key = 'status' 

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

178 return self.info[key] 

179 else: 

180 return 0 

181 

182 @classmethod 

183 def __mkcmd_real(cls, args): 

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

185 

186 __next_mkcmd = __mkcmd_real 

187 

188 @classmethod 

189 def _mkcmd(cls, args): 

190 

191 __next_mkcmd = cls.__next_mkcmd 

192 cls.__next_mkcmd = cls.__mkcmd_real 

193 

194 return __next_mkcmd(args) 

195 

196 @classmethod 

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

198 """ 

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

200 Raises a TapCtl.CommandFailure if subprocess creation failed. 

201 """ 

202 cmd = cls._mkcmd(args) 

203 

204 if not quiet: 

205 util.SMlog(cmd) 

206 try: 

207 p = subprocess.Popen(cmd, 

208 stdin=subprocess.PIPE, 

209 stdout=subprocess.PIPE, 

210 stderr=subprocess.PIPE, 

211 close_fds=True, 

212 universal_newlines=text_mode) 

213 if input: 

214 p.stdin.write(input) 

215 p.stdin.close() 

216 except OSError as e: 

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

218 

219 return cls(cmd, p) 

220 

221 def _errmsg(self): 

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

223 return "; ".join(output) 

224 

225 def _wait(self, quiet=False): 

226 """ 

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

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

229 """ 

230 status = self._p.wait() 

231 if not quiet: 

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

233 

234 if status == 0: 

235 return 

236 

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

238 'pid': self._p.pid} 

239 

240 if status < 0: 

241 info['signal'] = -status 

242 else: 

243 info['status'] = status 

244 

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

246 

247 @classmethod 

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

249 """ 

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

251 """ 

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

253 text_mode=text_mode) 

254 

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

256 

257 tapctl._wait(quiet) 

258 return output 

259 

260 @staticmethod 

261 def _maybe(opt, parm): 

262 if parm is not None: 

263 return [opt, parm] 

264 return [] 

265 

266 @classmethod 

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

268 args = ["list"] 

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

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

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

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

273 

274 tapctl = cls._call(args, True) 

275 

276 for stdout_line in tapctl.stdout: 

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

278 # confuses this parser 

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

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

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

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

283 row = {} 

284 

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

286 bits = field.split('=') 

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

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

289 

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

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

292 

293 elif key in ('state'): 

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

295 

296 else: 

297 row[key] = val 

298 else: 

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

300 yield row 

301 

302 tapctl._wait(True) 

303 

304 @classmethod 

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

306 def list(cls, **args): 

307 

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

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

310 # be fixed in SM. 

311 

312 try: 

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

314 

315 except cls.CommandFailure as e: 

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

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

318 raise RetryLoop.TransientFailure(e) 

319 raise 

320 

321 @classmethod 

322 def allocate(cls, devpath=None): 

323 args = ["allocate"] 

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

325 return cls._pread(args) 

326 

327 @classmethod 

328 def free(cls, minor): 

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

330 cls._pread(args) 

331 

332 @classmethod 

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

334 def spawn(cls): 

335 args = ["spawn"] 

336 try: 

337 pid = cls._pread(args) 

338 return int(pid) 

339 except cls.CommandFailure as ce: 

340 # intermittent failures to spawn. CA-292268 

341 if ce.status == 1: 

342 raise RetryLoop.TransientFailure(ce) 

343 raise 

344 

345 @classmethod 

346 def attach(cls, pid, minor): 

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

348 cls._pread(args) 

349 

350 @classmethod 

351 def detach(cls, pid, minor): 

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

353 cls._pread(args) 

354 

355 @classmethod 

356 def _load_key(cls, key_hash, vdi_uuid): 

357 import plugins 

358 

359 return plugins.load_key(key_hash, vdi_uuid) 

360 

361 @classmethod 

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

363 params = Tapdisk.Arg(_type, _file) 

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

365 text_mode = True 

366 input = None 

367 if options.get("rdonly"): 

368 args.append('-R') 

369 if options.get("lcache"): 

370 args.append("-r") 

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

372 args.append("-e") 

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

374 if options.get("secondary"): 

375 args.append("-2") 

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

377 if options.get("standby"): 

378 args.append("-s") 

379 if options.get("timeout"): 

380 args.append("-t") 

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

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

383 args.append("-D") 

384 if options.get('cbtlog'): 

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

386 if options.get('key_hash'): 

387 key_hash = options['key_hash'] 

388 vdi_uuid = options['vdi_uuid'] 

389 key = cls._load_key(key_hash, vdi_uuid) 

390 

391 if not key: 

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

393 input = key 

394 text_mode = False 

395 args.append('-E') 

396 

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

398 

399 @classmethod 

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

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

402 if force: 

403 args += ["-f"] 

404 cls._pread(args) 

405 

406 @classmethod 

407 def pause(cls, pid, minor): 

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

409 cls._pread(args) 

410 

411 @classmethod 

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

413 cbtlog=None): 

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

415 if mirror: 

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

417 if _type and _file: 

418 params = Tapdisk.Arg(_type, _file) 

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

420 if cbtlog: 

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

422 cls._pread(args) 

423 

424 @classmethod 

425 def shutdown(cls, pid): 

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

427 os.kill(pid, signal.SIGTERM) 

428 os.waitpid(pid, 0) 

429 

430 @classmethod 

431 def stats(cls, pid, minor): 

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

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

434 

435 @classmethod 

436 def major(cls): 

437 args = ["major"] 

438 major = cls._pread(args) 

439 return int(major) 

440 

441 

442class TapdiskExists(Exception): 

443 """Tapdisk already running.""" 

444 

445 def __init__(self, tapdisk): 

446 self.tapdisk = tapdisk 

447 

448 def __str__(self): 

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

450 

451 

452class TapdiskNotRunning(Exception): 

453 """No such Tapdisk.""" 

454 

455 def __init__(self, **attrs): 

456 self.attrs = attrs 

457 

458 def __str__(self): 

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

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

461 for attr in items) 

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

463 

464 

465class TapdiskNotUnique(Exception): 

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

467 

468 def __init__(self, tapdisks): 

469 self.tapdisks = tapdisks 

470 

471 def __str__(self): 

472 tapdisks = map(str, self.tapdisks) 

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

474 

475 

476class TapdiskFailed(Exception): 

477 """Tapdisk launch failure.""" 

478 

479 def __init__(self, arg, err): 

480 self.arg = arg 

481 self.err = err 

482 

483 def __str__(self): 

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

485 

486 def get_error(self): 

487 return self.err 

488 

489 

490class TapdiskInvalidState(Exception): 

491 """Tapdisk pause/unpause failure""" 

492 

493 def __init__(self, tapdisk): 

494 self.tapdisk = tapdisk 

495 

496 def __str__(self): 

497 return str(self.tapdisk) 

498 

499 

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

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

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

503 assert parent != path 

504 try: 

505 if parent: 

506 mkdirs(parent, mode) 

507 if subdir: 

508 os.mkdir(path, mode) 

509 except OSError as e: 

510 if e.errno != errno.EEXIST: 

511 raise 

512 

513 

514class KObject(object): 

515 

516 SYSFS_CLASSTYPE = None 

517 

518 def sysfs_devname(self): 

519 raise NotImplementedError("sysfs_devname is undefined") 

520 

521 

522class Attribute(object): 

523 

524 SYSFS_NODENAME = None 

525 

526 def __init__(self, path): 

527 self.path = path 

528 

529 @classmethod 

530 def from_kobject(cls, kobj): 

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

532 return cls(path) 

533 

534 class NoSuchAttribute(Exception): 

535 def __init__(self, name): 

536 self.name = name 

537 

538 def __str__(self): 

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

540 

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

542 try: 

543 return open(self.path, mode) 

544 except IOError as e: 

545 if e.errno == errno.ENOENT: 

546 raise self.NoSuchAttribute(self) 

547 raise 

548 

549 def readline(self): 

550 f = self._open('r') 

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

552 f.close() 

553 return s 

554 

555 def writeline(self, val): 

556 f = self._open('w') 

557 f.write(val) 

558 f.close() 

559 

560 

561class ClassDevice(KObject): 

562 

563 @classmethod 

564 def sysfs_class_path(cls): 

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

566 

567 def sysfs_path(self): 

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

569 self.sysfs_devname()) 

570 

571 

572class Blktap(ClassDevice): 

573 

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

575 

576 SYSFS_CLASSTYPE = "blktap2" 

577 

578 def __init__(self, minor): 

579 self.minor = minor 

580 self._pool = None 

581 self._task = None 

582 

583 @classmethod 

584 def allocate(cls): 

585 # FIXME. Should rather go into init. 

586 mkdirs(cls.DEV_BASEDIR) 

587 

588 devname = TapCtl.allocate() 

589 minor = Tapdisk._parse_minor(devname) 

590 return cls(minor) 

591 

592 def free(self): 

593 TapCtl.free(self.minor) 

594 

595 def __str__(self): 

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

597 

598 def sysfs_devname(self): 

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

600 

601 class Pool(Attribute): 

602 SYSFS_NODENAME = "pool" 

603 

604 def get_pool_attr(self): 

605 if not self._pool: 

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

607 return self._pool 

608 

609 def get_pool_name(self): 

610 return self.get_pool_attr().readline() 

611 

612 def set_pool_name(self, name): 

613 self.get_pool_attr().writeline(name) 

614 

615 def set_pool_size(self, pages): 

616 self.get_pool().set_size(pages) 

617 

618 def get_pool(self): 

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

620 

621 def set_pool(self, pool): 

622 self.set_pool_name(pool.name) 

623 

624 class Task(Attribute): 

625 SYSFS_NODENAME = "task" 

626 

627 def get_task_attr(self): 

628 if not self._task: 

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

630 return self._task 

631 

632 def get_task_pid(self): 

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

634 try: 

635 return int(pid) 

636 except ValueError: 

637 return None 

638 

639 def find_tapdisk(self): 

640 pid = self.get_task_pid() 

641 if pid is None: 

642 return None 

643 

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

645 

646 def get_tapdisk(self): 

647 tapdisk = self.find_tapdisk() 

648 if not tapdisk: 

649 raise TapdiskNotRunning(minor=self.minor) 

650 return tapdisk 

651 

652 

653class Tapdisk(object): 

654 

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

656 

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

658 self.pid = pid 

659 self.minor = minor 

660 self.type = _type 

661 self.path = path 

662 self.state = state 

663 self._dirty = False 

664 self._blktap = None 

665 

666 def __str__(self): 

667 state = self.pause_state() 

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

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

670 

671 @classmethod 

672 def list(cls, **args): 

673 

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

675 

676 args = {'pid': None, 

677 'minor': None, 

678 'state': None, 

679 '_type': None, 

680 'path': None} 

681 

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

683 if key in args: 

684 args[key] = val 

685 

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

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

688 args['_type'] = image.type 

689 args['path'] = image.path 

690 

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

692 continue 

693 

694 yield Tapdisk( ** args) 

695 

696 @classmethod 

697 def find(cls, **args): 

698 

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

700 

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

702 raise TapdiskNotUnique(found) 

703 

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

705 return found[0] 

706 

707 return None 

708 

709 @classmethod 

710 def find_by_path(cls, path): 

711 return cls.find(path=path) 

712 

713 @classmethod 

714 def find_by_minor(cls, minor): 

715 return cls.find(minor=minor) 

716 

717 @classmethod 

718 def get(cls, **attrs): 

719 

720 tapdisk = cls.find( ** attrs) 

721 

722 if not tapdisk: 

723 raise TapdiskNotRunning( ** attrs) 

724 

725 return tapdisk 

726 

727 @classmethod 

728 def from_path(cls, path): 

729 return cls.get(path=path) 

730 

731 @classmethod 

732 def from_minor(cls, minor): 

733 return cls.get(minor=minor) 

734 

735 @classmethod 

736 def __from_blktap(cls, blktap): 

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

738 tapdisk._blktap = blktap 

739 return tapdisk 

740 

741 def get_blktap(self): 

742 if not self._blktap: 

743 self._blktap = Blktap(self.minor) 

744 return self._blktap 

745 

746 class Arg: 

747 

748 def __init__(self, _type, path): 

749 self.type = _type 

750 self.path = path 

751 

752 def __str__(self): 

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

754 

755 @classmethod 

756 def parse(cls, arg): 

757 

758 try: 

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

760 except ValueError: 

761 raise cls.InvalidArgument(arg) 

762 

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

764 raise cls.InvalidType(_type) 

765 

766 return cls(_type, path) 

767 

768 class InvalidType(Exception): 

769 def __init__(self, _type): 

770 self.type = _type 

771 

772 def __str__(self): 

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

774 

775 class InvalidArgument(Exception): 

776 def __init__(self, arg): 

777 self.arg = arg 

778 

779 def __str__(self): 

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

781 

782 def get_arg(self): 

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

784 

785 def get_devpath(self): 

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

787 

788 @classmethod 

789 def launch_from_arg(cls, arg): 

790 arg = cls.Arg.parse(arg) 

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

792 

793 @staticmethod 

794 def cgclassify(pid): 

795 

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

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

798 # we have configured in the spec file. 

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

800 try: 

801 util.pread2(cmd) 

802 except util.CommandException as e: 

803 util.logException(e) 

804 

805 @classmethod 

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

807 

808 tapdisk = cls.find_by_path(path) 

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

810 raise TapdiskExists(tapdisk) 

811 

812 minor = blktap.minor 

813 try: 

814 pid = TapCtl.spawn() 

815 cls.cgclassify(pid) 

816 try: 

817 TapCtl.attach(pid, minor) 

818 

819 try: 

820 retry_open = 0 

821 while True: 

822 try: 

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

824 except TapCtl.CommandFailure as e: 

825 err = ( 

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

827 ) or None 

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

829 if retry_open < 5: 

830 retry_open += 1 

831 time.sleep(1) 

832 continue 

833 if LINSTOR_AVAILABLE and err == errno.EROFS: 

834 log_drbd_openers(path) 

835 raise 

836 try: 

837 tapdisk = cls.__from_blktap(blktap) 

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

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

840 return tapdisk 

841 except: 

842 TapCtl.close(pid, minor) 

843 raise 

844 

845 except: 

846 TapCtl.detach(pid, minor) 

847 raise 

848 

849 except: 

850 try: 

851 TapCtl.shutdown(pid) 

852 except: 

853 # Best effort to shutdown 

854 pass 

855 raise 

856 

857 except TapCtl.CommandFailure as ctl: 

858 util.logException(ctl) 

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

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

861 raise xs_errors.XenError('TapdiskDriveEmpty') 

862 else: 

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

864 

865 @classmethod 

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

867 blktap = Blktap.allocate() 

868 try: 

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

870 except: 

871 blktap.free() 

872 raise 

873 

874 def shutdown(self, force=False): 

875 

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

877 

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

879 

880 self.get_blktap().free() 

881 

882 def pause(self): 

883 

884 if not self.is_running(): 

885 raise TapdiskInvalidState(self) 

886 

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

888 

889 self._set_dirty() 

890 

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

892 

893 if not self.is_paused(): 

894 raise TapdiskInvalidState(self) 

895 

896 # FIXME: should the arguments be optional? 

897 if _type is None: 

898 _type = self.type 

899 if path is None: 

900 path = self.path 

901 

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

903 cbtlog=cbtlog) 

904 

905 self._set_dirty() 

906 

907 def stats(self): 

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

909 # 

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

911 # 

912 

913 def _set_dirty(self): 

914 self._dirty = True 

915 

916 def _refresh(self, __get): 

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

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

919 

920 def __getattribute__(self, name): 

921 def __get(name): 

922 # NB. avoid(rec(ursion) 

923 return object.__getattribute__(self, name) 

924 

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

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

927 self._refresh(__get) 

928 self._dirty = False 

929 

930 return __get(name) 

931 

932 class PauseState: 

933 RUNNING = 'R' 

934 PAUSING = 'r' 

935 PAUSED = 'P' 

936 

937 class Flags: 

938 DEAD = 0x0001 

939 CLOSED = 0x0002 

940 QUIESCE_REQUESTED = 0x0004 

941 QUIESCED = 0x0008 

942 PAUSE_REQUESTED = 0x0010 

943 PAUSED = 0x0020 

944 SHUTDOWN_REQUESTED = 0x0040 

945 LOCKING = 0x0080 

946 RETRY_NEEDED = 0x0100 

947 LOG_DROPPED = 0x0200 

948 

949 PAUSE_MASK = PAUSE_REQUESTED | PAUSED 

950 

951 def is_paused(self): 

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

953 

954 def is_running(self): 

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

956 

957 def pause_state(self): 

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

959 return self.PauseState.PAUSED 

960 

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

962 return self.PauseState.PAUSING 

963 

964 return self.PauseState.RUNNING 

965 

966 @staticmethod 

967 def _parse_minor(devpath): 

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

969 pattern = re.compile(regex) 

970 groups = pattern.search(devpath) 

971 if not groups: 

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

973 

974 minor = groups.group(2) 

975 return int(minor) 

976 

977 _major = None 

978 

979 @classmethod 

980 def major(cls): 

981 if cls._major: 

982 return cls._major 

983 

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

985 for line in devices: 

986 

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

988 if len(row) != 2: 

989 continue 

990 

991 major, name = row 

992 if name != 'tapdev': 

993 continue 

994 

995 cls._major = int(major) 

996 break 

997 

998 devices.close() 

999 return cls._major 

1000 

1001 

1002class VDI(object): 

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

1004 

1005 CONF_KEY_ALLOW_CACHING = "vdi_allow_caching" 

1006 CONF_KEY_MODE_ON_BOOT = "vdi_on_boot" 

1007 CONF_KEY_CACHE_SR = "local_cache_sr" 

1008 CONF_KEY_O_DIRECT = "o_direct" 

1009 LOCK_CACHE_SETUP = "cachesetup" 

1010 

1011 ATTACH_DETACH_RETRY_SECS = 120 

1012 

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

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

1015 self._vdi_uuid = uuid 

1016 self._session = target.session 

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

1018 self.__o_direct = None 

1019 self.__o_direct_reason = None 

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

1021 self.tap = None 

1022 

1023 def get_o_direct_capability(self, options): 

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

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

1026 return self.__o_direct, self.__o_direct_reason 

1027 

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

1029 self.__o_direct = True 

1030 self.__o_direct_reason = "LICENSE_RESTRICTION" 

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

1032 self.__o_direct = True 

1033 self.__o_direct_reason = "SR_NOT_SUPPORTED" 

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

1035 util.SMlog(self.target.vdi) 

1036 self.__o_direct = True 

1037 self.__o_direct_reason = "NO_RO_IMAGE" 

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

1039 self.__o_direct = True 

1040 self.__o_direct_reason = "RO_WITH_NO_PARENT" 

1041 elif options.get(self.CONF_KEY_O_DIRECT): 

1042 self.__o_direct = True 

1043 self.__o_direct_reason = "SR_OVERRIDE" 

1044 

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

1046 self.__o_direct = False 

1047 self.__o_direct_reason = "" 

1048 

1049 return self.__o_direct, self.__o_direct_reason 

1050 

1051 @classmethod 

1052 def from_cli(cls, uuid): 

1053 import VDI as sm 

1054 

1055 session = XenAPI.xapi_local() 

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

1057 

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

1059 driver_info = target.sr.srcmd.driver_info 

1060 

1061 session.xenapi.session.logout() 

1062 

1063 return cls(uuid, target, driver_info) 

1064 

1065 @staticmethod 

1066 def _tap_type(vdi_type): 

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

1068 return { 

1069 'raw': 'aio', 

1070 'vhd': 'vhd', 

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

1072 'aio': 'aio', # for LVHD 

1073 'file': 'aio', 

1074 'phy': 'aio' 

1075 }[vdi_type] 

1076 

1077 def get_tap_type(self): 

1078 vdi_type = self.target.get_vdi_type() 

1079 return VDI._tap_type(vdi_type) 

1080 

1081 def get_phy_path(self): 

1082 return self.target.get_vdi_path() 

1083 

1084 class UnexpectedVDIType(Exception): 

1085 

1086 def __init__(self, vdi_type, target): 

1087 self.vdi_type = vdi_type 

1088 self.target = target 

1089 

1090 def __str__(self): 

1091 return \ 

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

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

1094 

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

1096 'raw': 'phy', 

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

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

1099 'file': 'tap', 

1100 'vhd': 'tap'} 

1101 

1102 def tap_wanted(self): 

1103 # 1. Let the target vdi_type decide 

1104 

1105 vdi_type = self.target.get_vdi_type() 

1106 

1107 try: 

1108 plug_type = self.VDI_PLUG_TYPE[vdi_type] 

1109 except KeyError: 

1110 raise self.UnexpectedVDIType(vdi_type, 

1111 self.target.vdi) 

1112 

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

1114 return True 

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

1116 return True 

1117 # 2. Otherwise, there may be more reasons 

1118 # 

1119 # .. TBD 

1120 

1121 return False 

1122 

1123 class TargetDriver: 

1124 """Safe target driver access.""" 

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

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

1127 # try/except would risk breaking compatibility. 

1128 

1129 def __init__(self, vdi, driver_info): 

1130 self.vdi = vdi 

1131 self._caps = driver_info['capabilities'] 

1132 

1133 def has_cap(self, cap): 

1134 """Determine if target has given capability""" 

1135 return cap in self._caps 

1136 

1137 def attach(self, sr_uuid, vdi_uuid): 

1138 #assert self.has_cap("VDI_ATTACH") 

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

1140 

1141 def detach(self, sr_uuid, vdi_uuid): 

1142 #assert self.has_cap("VDI_DETACH") 

1143 self.vdi.detach(sr_uuid, vdi_uuid) 

1144 

1145 def activate(self, sr_uuid, vdi_uuid): 

1146 if self.has_cap("VDI_ACTIVATE"): 

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

1148 

1149 def deactivate(self, sr_uuid, vdi_uuid): 

1150 if self.has_cap("VDI_DEACTIVATE"): 

1151 self.vdi.deactivate(sr_uuid, vdi_uuid) 

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

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

1154 

1155 def get_vdi_type(self): 

1156 _type = self.vdi.vdi_type 

1157 if not _type: 

1158 _type = self.vdi.sr.sr_vditype 

1159 if not _type: 

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

1161 return _type 

1162 

1163 def get_vdi_path(self): 

1164 return self.vdi.path 

1165 

1166 class Link(object): 

1167 """Relink a node under a common name""" 

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

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

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

1171 # relink existing devices under deterministic path names. 

1172 

1173 BASEDIR = None 

1174 

1175 def _mklink(self, target): 

1176 raise NotImplementedError("_mklink is not defined") 

1177 

1178 def _equals(self, target): 

1179 raise NotImplementedError("_equals is not defined") 

1180 

1181 def __init__(self, path): 

1182 self._path = path 

1183 

1184 @classmethod 

1185 def from_name(cls, name): 

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

1187 return cls(path) 

1188 

1189 @classmethod 

1190 def from_uuid(cls, sr_uuid, vdi_uuid): 

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

1192 return cls.from_name(name) 

1193 

1194 def path(self): 

1195 return self._path 

1196 

1197 def stat(self): 

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

1199 

1200 def mklink(self, target): 

1201 

1202 path = self.path() 

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

1204 

1205 mkdirs(os.path.dirname(path)) 

1206 try: 

1207 self._mklink(target) 

1208 except OSError as e: 

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

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

1211 # be seen. 

1212 if e.errno != errno.EEXIST: 

1213 raise 

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

1215 

1216 def unlink(self): 

1217 try: 

1218 os.unlink(self.path()) 

1219 except OSError as e: 

1220 if e.errno != errno.ENOENT: 

1221 raise 

1222 

1223 def __str__(self): 

1224 path = self.path() 

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

1226 

1227 class SymLink(Link): 

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

1229 

1230 def readlink(self): 

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

1232 

1233 def symlink(self): 

1234 return self.path() 

1235 

1236 def _mklink(self, target): 

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

1238 

1239 def _equals(self, target): 

1240 return self.readlink() == target 

1241 

1242 class DeviceNode(Link): 

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

1244 

1245 @classmethod 

1246 def _real_stat(cls, target): 

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

1248 _target = os.path.realpath(target) 

1249 return os.stat(_target) 

1250 

1251 @classmethod 

1252 def is_block(cls, target): 

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

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

1255 

1256 def _mklink(self, target): 

1257 

1258 st = self._real_stat(target) 

1259 if not S_ISBLK(st.st_mode): 

1260 raise self.NotABlockDevice(target, st) 

1261 

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

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

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

1265 

1266 def _equals(self, target): 

1267 target_rdev = self._real_stat(target).st_rdev 

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

1269 

1270 def rdev(self): 

1271 st = self.stat() 

1272 assert S_ISBLK(st.st_mode) 

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

1274 

1275 class NotABlockDevice(Exception): 

1276 

1277 def __init__(self, path, st): 

1278 self.path = path 

1279 self.st = st 

1280 

1281 def __str__(self): 

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

1283 

1284 class Hybrid(Link): 

1285 

1286 def __init__(self, path): 

1287 VDI.Link.__init__(self, path) 

1288 self._devnode = VDI.DeviceNode(path) 

1289 self._symlink = VDI.SymLink(path) 

1290 

1291 def rdev(self): 

1292 st = self.stat() 

1293 if S_ISBLK(st.st_mode): 

1294 return self._devnode.rdev() 

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

1296 

1297 def mklink(self, target): 

1298 if self._devnode.is_block(target): 

1299 self._obj = self._devnode 

1300 else: 

1301 self._obj = self._symlink 

1302 self._obj.mklink(target) 

1303 

1304 def _equals(self, target): 

1305 return self._obj._equals(target) 

1306 

1307 class PhyLink(SymLink): 

1308 BASEDIR = "/dev/sm/phy" 

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

1310 

1311 class NBDLink(SymLink): 

1312 

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

1314 

1315 class BackendLink(Hybrid): 

1316 BASEDIR = "/dev/sm/backend" 

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

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

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

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

1321 # soon as ISOs are tapdisks. 

1322 

1323 @staticmethod 

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

1325 

1326 tapdisk = Tapdisk.find_by_path(phy_path) 

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

1328 blktap = Blktap.allocate() 

1329 blktap.set_pool_name(sr_uuid) 

1330 if pool_size: 

1331 blktap.set_pool_size(pool_size) 

1332 

1333 try: 

1334 tapdisk = \ 

1335 Tapdisk.launch_on_tap(blktap, 

1336 phy_path, 

1337 VDI._tap_type(vdi_type), 

1338 options) 

1339 except: 

1340 blktap.free() 

1341 raise 

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

1343 

1344 else: 

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

1346 

1347 return tapdisk.get_devpath(), tapdisk 

1348 

1349 @staticmethod 

1350 def _tap_deactivate(minor): 

1351 

1352 try: 

1353 tapdisk = Tapdisk.from_minor(minor) 

1354 except TapdiskNotRunning as e: 

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

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

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

1358 # the recorded minor. 

1359 else: 

1360 tapdisk.shutdown() 

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

1362 

1363 @classmethod 

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

1365 """ 

1366 Pauses the tapdisk. 

1367 

1368 session: a XAPI session 

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

1370 vdi_uuid: the UUID of the VDI to pause 

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

1372 non-blocking manner 

1373 """ 

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

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

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

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

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

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

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

1381 if not cls.call_pluginhandler(session, host_ref, 

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

1383 # Failed to pause node 

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

1385 return False 

1386 return True 

1387 

1388 @classmethod 

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

1390 activate_parents=False): 

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

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

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

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

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

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

1397 if not cls.call_pluginhandler(session, host_ref, 

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

1399 # Failed to unpause node 

1400 return False 

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

1402 return True 

1403 

1404 @classmethod 

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

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

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

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

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

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

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

1412 if not cls.call_pluginhandler(session, host_ref, 

1413 sr_uuid, vdi_uuid, "refresh", None, 

1414 activate_parents=activate_parents): 

1415 # Failed to refresh node 

1416 return False 

1417 return True 

1418 

1419 @classmethod 

1420 def tap_status(cls, session, vdi_uuid): 

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

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

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

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

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

1426 return True 

1427 return False 

1428 

1429 @classmethod 

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

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

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

1433 try: 

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

1435 "failfast": str(failfast)} 

1436 if secondary: 

1437 args["secondary"] = secondary 

1438 if activate_parents: 

1439 args["activate_parents"] = "true" 

1440 ret = session.xenapi.host.call_plugin( 

1441 host_ref, PLUGIN_TAP_PAUSE, action, 

1442 args) 

1443 return ret == "True" 

1444 except Exception as e: 

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

1446 return False 

1447 

1448 def _add_tag(self, vdi_uuid, writable): 

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

1450 attach_mode = "RO" 

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

1452 attach_mode = "RW" 

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

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

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

1456 attached_as = util.attached_as(sm_config) 

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

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

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

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

1461 term_output=False, writable=writable): 

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

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

1464 if 'relinking' in sm_config: 

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

1466 return False 

1467 if 'paused' in sm_config: 

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

1469 return False 

1470 self._session.xenapi.VDI.add_to_sm_config( 

1471 vdi_ref, 'activating', 'True') 

1472 host_key = "host_%s" % host_ref 

1473 assert host_key not in sm_config 

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

1475 attach_mode) 

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

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

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

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

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

1481 self._session.xenapi.VDI.remove_from_sm_config( 

1482 vdi_ref, 'activating') 

1483 return False 

1484 util.SMlog("Activate lock succeeded") 

1485 return True 

1486 

1487 def _check_tag(self, vdi_uuid): 

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

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

1490 if 'paused' in sm_config: 

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

1492 return False 

1493 return True 

1494 

1495 def _remove_tag(self, vdi_uuid): 

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

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

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

1499 host_key = "host_%s" % host_ref 

1500 if host_key in sm_config: 

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

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

1503 else: 

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

1505 

1506 def _get_pool_config(self, pool_name): 

1507 pool_info = dict() 

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

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

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

1511 # special pool 

1512 return pool_info 

1513 

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

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

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

1517 pool_size_str = sr_config.get(POOL_SIZE_KEY) 

1518 pool_name_override = vdi_config.get(POOL_NAME_KEY) 

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

1520 pool_name = pool_name_override 

1521 pool_size_override = vdi_config.get(POOL_SIZE_KEY) 

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

1523 pool_size_str = pool_size_override 

1524 pool_size = 0 

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

1526 try: 

1527 pool_size = int(pool_size_str) 

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

1529 raise ValueError("outside of range") 

1530 pool_size = NUM_PAGES_PER_RING * pool_size 

1531 except ValueError: 

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

1533 pool_size = 0 

1534 

1535 pool_info["mem-pool"] = pool_name 

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

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

1538 

1539 return pool_info 

1540 

1541 def linkNBD(self, sr_uuid, vdi_uuid): 

1542 if self.tap: 

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

1544 int(self.tap.minor)) 

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

1546 

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

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

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

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

1551 util.SMlog("Attach & activate") 

1552 self._attach(sr_uuid, vdi_uuid) 

1553 dev_path = self._activate(sr_uuid, vdi_uuid, 

1554 {"rdonly": not writable}) 

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

1556 self.linkNBD(sr_uuid, vdi_uuid) 

1557 

1558 # Return backend/ link 

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

1560 if self.tap_wanted(): 

1561 # Only have NBD if we also have a tap 

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

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

1564 vdi_uuid) 

1565 else: 

1566 nbd_path = "" 

1567 

1568 options = {"rdonly": not writable} 

1569 options.update(caching_params) 

1570 o_direct, o_direct_reason = self.get_o_direct_capability(options) 

1571 struct = {'params': back_path, 

1572 'params_nbd': nbd_path, 

1573 'o_direct': o_direct, 

1574 'o_direct_reason': o_direct_reason, 

1575 'xenstore_data': self.xenstore_data} 

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

1577 

1578 try: 

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

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

1581 f.close() 

1582 except: 

1583 pass 

1584 

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

1586 

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

1588 util.SMlog("blktap2.activate") 

1589 options = {"rdonly": not writable} 

1590 options.update(caching_params) 

1591 

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

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

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

1595 try: 

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

1597 return 

1598 except util.SRBusyException: 

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

1600 time.sleep(1) 

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

1602 

1603 @locking("VDIUnavailable") 

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

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

1606 

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

1608 refresh = False 

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

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

1611 return False 

1612 refresh = True 

1613 

1614 try: 

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

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 params = self.target.vdi.sr.srcmd.params 

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

1622 target.sr.srcmd.params = params 

1623 driver_info = target.sr.srcmd.driver_info 

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

1625 

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

1627 "blktap_activate_inject_failure", 

1628 lambda: util.inject_failure()) 

1629 

1630 # Attach the physical node 

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

1632 self._attach(sr_uuid, vdi_uuid) 

1633 

1634 vdi_type = self.target.get_vdi_type() 

1635 

1636 # Take lvchange-p Lock before running 

1637 # tap-ctl open 

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

1639 # now taking the same lock 

1640 # This is a fix for CA-155766 

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

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

1643 vdi_type == vhdutil.VDI_TYPE_VHD: 

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

1645 lock.acquire() 

1646 

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

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

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

1650 session = self.target.vdi.session 

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

1652 # pylint: disable=used-before-assignment 

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

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

1655 key_hash = sm_config['key_hash'] 

1656 options['key_hash'] = key_hash 

1657 options['vdi_uuid'] = vdi_uuid 

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

1659 # Activate the physical node 

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

1661 

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

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

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

1665 lock.release() 

1666 except: 

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

1668 if self.tap_wanted(): 

1669 util.fistpoint.activate_custom_fn( 

1670 "blktap_activate_error_handling", 

1671 lambda: time.sleep(30)) 

1672 while True: 

1673 try: 

1674 self._remove_tag(vdi_uuid) 

1675 break 

1676 except xmlrpc.client.ProtocolError as e: 

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

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

1679 continue 

1680 else: 

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

1682 break 

1683 except Exception as e: 

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

1685 break 

1686 raise 

1687 finally: 

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

1689 self._session.xenapi.VDI.remove_from_sm_config( 

1690 vdi_ref, 'activating') 

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

1692 

1693 # Link result to backend/ 

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

1695 self.linkNBD(sr_uuid, vdi_uuid) 

1696 return True 

1697 

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

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

1700 

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

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

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

1704 # Maybe launch a tapdisk on the physical link 

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

1706 vdi_type = self.target.get_vdi_type() 

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

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

1709 options.update(vdi_options) 

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

1711 sr_uuid, options, 

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

1713 else: 

1714 dev_path = phy_path # Just reuse phy 

1715 

1716 return dev_path 

1717 

1718 def _attach(self, sr_uuid, vdi_uuid): 

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

1720 params = attach_info['params'] 

1721 xenstore_data = attach_info['xenstore_data'] 

1722 phy_path = util.to_plain_string(params) 

1723 self.xenstore_data.update(xenstore_data) 

1724 # Save it to phy/ 

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

1726 

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

1728 util.SMlog("blktap2.deactivate") 

1729 for i in range(self.ATTACH_DETACH_RETRY_SECS): 

1730 try: 

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

1732 return 

1733 except util.SRBusyException as e: 

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

1735 time.sleep(1) 

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

1737 

1738 @locking("VDIUnavailable") 

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

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

1741 

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

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

1744 return False 

1745 

1746 self._deactivate(sr_uuid, vdi_uuid, caching_params) 

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

1748 self._detach(sr_uuid, vdi_uuid) 

1749 if self.tap_wanted(): 

1750 self._remove_tag(vdi_uuid) 

1751 

1752 return True 

1753 

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

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

1756 

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

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

1759 util.SMlog("Deactivate & detach") 

1760 self._deactivate(sr_uuid, vdi_uuid, caching_params) 

1761 self._detach(sr_uuid, vdi_uuid) 

1762 else: 

1763 pass # nothing to do 

1764 

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

1766 import VDI as sm 

1767 

1768 # Shutdown tapdisk 

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

1770 

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

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

1773 return 

1774 

1775 try: 

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

1777 os.unlink(attach_info_path) 

1778 except: 

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

1780 

1781 try: 

1782 major, minor = back_link.rdev() 

1783 except self.DeviceNode.NotABlockDevice: 

1784 pass 

1785 else: 

1786 if major == Tapdisk.major(): 

1787 self._tap_deactivate(minor) 

1788 self.remove_cache(sr_uuid, vdi_uuid, caching_params) 

1789 

1790 # Remove the backend link 

1791 back_link.unlink() 

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

1793 

1794 # Deactivate & detach the physical node 

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

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

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

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

1799 # object completely 

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

1801 driver_info = target.sr.srcmd.driver_info 

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

1803 

1804 self.target.deactivate(sr_uuid, vdi_uuid) 

1805 

1806 def _detach(self, sr_uuid, vdi_uuid): 

1807 self.target.detach(sr_uuid, vdi_uuid) 

1808 

1809 # Remove phy/ 

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

1811 

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

1813 # Remove existing VDI.sm_config fields 

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

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

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

1817 if not on_boot is None: 

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

1819 if not caching is None: 

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

1821 

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

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

1824 return 

1825 

1826 util.SMlog("Requested local caching") 

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

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

1829 return 

1830 

1831 scratch_mode = False 

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

1833 scratch_mode = True 

1834 util.SMlog("Requested scratch mode") 

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

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

1837 return 

1838 

1839 dev_path = None 

1840 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR) 

1841 if not local_sr_uuid: 

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

1843 return 

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

1845 local_sr_uuid, scratch_mode, params) 

1846 

1847 if dev_path: 

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

1849 params.get(self.CONF_KEY_MODE_ON_BOOT), 

1850 params.get(self.CONF_KEY_ALLOW_CACHING)) 

1851 

1852 return dev_path 

1853 

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

1855 vm_uuid = None 

1856 vm_label = "" 

1857 try: 

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

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

1860 cache_sr_label = cache_sr_rec.get("name_label") 

1861 

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

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

1864 host_label = host_rec.get("name_label") 

1865 

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

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

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

1869 for vbd_rec in vbds.values(): 

1870 vm_ref = vbd_rec.get("VM") 

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

1872 vm_uuid = vm_rec.get("uuid") 

1873 vm_label = vm_rec.get("name_label") 

1874 except: 

1875 util.logException("alert_no_cache") 

1876 

1877 alert_obj = "SR" 

1878 alert_uuid = str(cache_sr_uuid) 

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

1880 if vm_uuid: 

1881 alert_obj = "VM" 

1882 alert_uuid = vm_uuid 

1883 reason = "" 

1884 if err == errno.ENOSPC: 

1885 reason = "because there is no space left" 

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

1887 (vm_label, reason, cache_sr_label, host_label) 

1888 

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

1890 (alert_obj, alert_uuid, alert_str)) 

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

1892 alert_obj, alert_uuid, alert_str) 

1893 

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

1895 scratch_mode, options): 

1896 import SR 

1897 import EXTSR 

1898 import NFSSR 

1899 from lock import Lock 

1900 from FileSR import FileVDI 

1901 

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

1903 FileVDI.extractUuid) 

1904 if not parent_uuid: 

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

1906 self.target.vdi.uuid) 

1907 return 

1908 

1909 util.SMlog("Setting up cache") 

1910 parent_uuid = parent_uuid.strip() 

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

1912 

1913 if shared_target.parent: 

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

1915 shared_target.uuid) 

1916 return 

1917 

1918 SR.registerSR(EXTSR.EXTSR) 

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

1920 

1921 lock = Lock(self.LOCK_CACHE_SETUP, parent_uuid) 

1922 lock.acquire() 

1923 

1924 # read cache 

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

1926 if util.pathexists(read_cache_path): 

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

1928 read_cache_path) 

1929 else: 

1930 try: 

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

1932 except util.CommandException as e: 

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

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

1935 return None 

1936 

1937 # local write node 

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

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

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

1941 if util.pathexists(local_leaf_path): 

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

1943 local_leaf_path) 

1944 os.unlink(local_leaf_path) 

1945 try: 

1946 vhdutil.snapshot(local_leaf_path, read_cache_path, False, 

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

1948 except util.CommandException as e: 

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

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

1951 return None 

1952 

1953 local_leaf_size = vhdutil.getSizeVirt(local_leaf_path) 

1954 if leaf_size > local_leaf_size: 

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

1956 (leaf_size, local_leaf_size)) 

1957 vhdutil.setSizeVirtFast(local_leaf_path, leaf_size) 

1958 

1959 vdi_type = self.target.get_vdi_type() 

1960 

1961 prt_tapdisk = Tapdisk.find_by_path(read_cache_path) 

1962 if not prt_tapdisk: 

1963 parent_options = copy.deepcopy(options) 

1964 parent_options["rdonly"] = False 

1965 parent_options["lcache"] = True 

1966 

1967 blktap = Blktap.allocate() 

1968 try: 

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

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

1971 # its own pool 

1972 prt_tapdisk = \ 

1973 Tapdisk.launch_on_tap(blktap, read_cache_path, 

1974 'vhd', parent_options) 

1975 except: 

1976 blktap.free() 

1977 raise 

1978 

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

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

1981 

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

1983 leaf_tapdisk = Tapdisk.find_by_path(local_leaf_path) 

1984 if not leaf_tapdisk: 

1985 blktap = Blktap.allocate() 

1986 child_options = copy.deepcopy(options) 

1987 child_options["rdonly"] = False 

1988 child_options["lcache"] = False 

1989 child_options["existing_prt"] = prt_tapdisk.minor 

1990 child_options["secondary"] = secondary 

1991 child_options["standby"] = scratch_mode 

1992 try: 

1993 leaf_tapdisk = \ 

1994 Tapdisk.launch_on_tap(blktap, local_leaf_path, 

1995 'vhd', child_options) 

1996 except: 

1997 blktap.free() 

1998 raise 

1999 

2000 lock.release() 

2001 

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

2003 (read_cache_path, local_leaf_path)) 

2004 

2005 self.tap = leaf_tapdisk 

2006 return leaf_tapdisk.get_devpath() 

2007 

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

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

2010 return 

2011 

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

2013 

2014 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR) 

2015 if caching and not local_sr_uuid: 

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

2017 return 

2018 

2019 if caching: 

2020 self._remove_cache(self._session, local_sr_uuid) 

2021 

2022 if self._session is not None: 

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

2024 

2025 def _is_tapdisk_in_use(self, minor): 

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

2027 if not retVal: 

2028 # err on the side of caution 

2029 return True 

2030 

2031 for link in links: 

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

2033 return True 

2034 

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

2036 for s in sockets: 

2037 if socket_re.match(s): 

2038 return True 

2039 

2040 return False 

2041 

2042 def _remove_cache(self, session, local_sr_uuid): 

2043 import SR 

2044 import EXTSR 

2045 import NFSSR 

2046 from lock import Lock 

2047 from FileSR import FileVDI 

2048 

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

2050 FileVDI.extractUuid) 

2051 if not parent_uuid: 

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

2053 self.target.vdi.uuid) 

2054 return 

2055 

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

2057 

2058 parent_uuid = parent_uuid.strip() 

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

2060 

2061 SR.registerSR(EXTSR.EXTSR) 

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

2063 

2064 lock = Lock(self.LOCK_CACHE_SETUP, parent_uuid) 

2065 lock.acquire() 

2066 

2067 # local write node 

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

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

2070 if util.pathexists(local_leaf_path): 

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

2072 os.unlink(local_leaf_path) 

2073 

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

2075 prt_tapdisk = Tapdisk.find_by_path(read_cache_path) 

2076 if not prt_tapdisk: 

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

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

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

2080 read_cache_path) 

2081 try: 

2082 prt_tapdisk.shutdown() 

2083 except: 

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

2085 else: 

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

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

2088 # GC run 

2089 

2090 lock.release() 

2091 

2092PythonKeyError = KeyError 

2093 

2094 

2095class UEventHandler(object): 

2096 

2097 def __init__(self): 

2098 self._action = None 

2099 

2100 class KeyError(PythonKeyError): 

2101 def __init__(self, args): 

2102 super().__init__(args) 

2103 self.key = args[0] 

2104 

2105 def __str__(self): 

2106 return \ 

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

2108 "Not called in udev context?" 

2109 

2110 @classmethod 

2111 def getenv(cls, key): 

2112 try: 

2113 return os.environ[key] 

2114 except KeyError as e: 

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

2116 

2117 def get_action(self): 

2118 if not self._action: 

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

2120 return self._action 

2121 

2122 class UnhandledEvent(Exception): 

2123 

2124 def __init__(self, event, handler): 

2125 self.event = event 

2126 self.handler = handler 

2127 

2128 def __str__(self): 

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

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

2131 

2132 ACTIONS = {} 

2133 

2134 def run(self): 

2135 

2136 action = self.get_action() 

2137 try: 

2138 fn = self.ACTIONS[action] 

2139 except KeyError: 

2140 raise self.UnhandledEvent(action, self) 

2141 

2142 return fn(self) 

2143 

2144 def __str__(self): 

2145 try: 

2146 action = self.get_action() 

2147 except: 

2148 action = None 

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

2150 

2151 

2152class __BlktapControl(ClassDevice): 

2153 SYSFS_CLASSTYPE = "misc" 

2154 

2155 def __init__(self): 

2156 ClassDevice.__init__(self) 

2157 self._default_pool = None 

2158 

2159 def sysfs_devname(self): 

2160 return "blktap!control" 

2161 

2162 class DefaultPool(Attribute): 

2163 SYSFS_NODENAME = "default_pool" 

2164 

2165 def get_default_pool_attr(self): 

2166 if not self._default_pool: 

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

2168 return self._default_pool 

2169 

2170 def get_default_pool_name(self): 

2171 return self.get_default_pool_attr().readline() 

2172 

2173 def set_default_pool_name(self, name): 

2174 self.get_default_pool_attr().writeline(name) 

2175 

2176 def get_default_pool(self): 

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

2178 

2179 def set_default_pool(self, pool): 

2180 self.set_default_pool_name(pool.name) 

2181 

2182 class NoSuchPool(Exception): 

2183 def __init__(self, name): 

2184 self.name = name 

2185 

2186 def __str__(self): 

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

2188 

2189 def get_pool(self, name): 

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

2191 

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

2193 raise self.NoSuchPool(name) 

2194 

2195 return PagePool(path) 

2196 

2197BlktapControl = __BlktapControl() 

2198 

2199 

2200class PagePool(KObject): 

2201 

2202 def __init__(self, path): 

2203 self.path = path 

2204 self._size = None 

2205 

2206 def sysfs_path(self): 

2207 return self.path 

2208 

2209 class Size(Attribute): 

2210 SYSFS_NODENAME = "size" 

2211 

2212 def get_size_attr(self): 

2213 if not self._size: 

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

2215 return self._size 

2216 

2217 def set_size(self, pages): 

2218 pages = str(pages) 

2219 self.get_size_attr().writeline(pages) 

2220 

2221 def get_size(self): 

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

2223 return int(pages) 

2224 

2225 

2226class BusDevice(KObject): 

2227 

2228 SYSFS_BUSTYPE = None 

2229 

2230 @classmethod 

2231 def sysfs_bus_path(cls): 

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

2233 

2234 def sysfs_path(self): 

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

2236 self.sysfs_devname()) 

2237 

2238 return path 

2239 

2240 

2241class XenbusDevice(BusDevice): 

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

2243 

2244 XBT_NIL = "" 

2245 

2246 XENBUS_DEVTYPE = None 

2247 

2248 def __init__(self, domid, devid): 

2249 self.domid = int(domid) 

2250 self.devid = int(devid) 

2251 self._xbt = XenbusDevice.XBT_NIL 

2252 

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

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

2255 

2256 def xs_path(self, key=None): 

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

2258 self.domid, 

2259 self.devid) 

2260 if key is not None: 

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

2262 

2263 return path 

2264 

2265 def _log(self, prio, msg): 

2266 syslog(prio, msg) 

2267 

2268 def info(self, msg): 

2269 self._log(_syslog.LOG_INFO, msg) 

2270 

2271 def warn(self, msg): 

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

2273 

2274 def _xs_read_path(self, path): 

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

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

2277 return val 

2278 

2279 def _xs_write_path(self, path, val): 

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

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

2282 

2283 def _xs_rm_path(self, path): 

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

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

2286 

2287 def read(self, key): 

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

2289 

2290 def has_xs_key(self, key): 

2291 return self.read(key) is not None 

2292 

2293 def write(self, key, val): 

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

2295 

2296 def rm(self, key): 

2297 self._xs_rm_path(self.xs_path(key)) 

2298 

2299 def exists(self): 

2300 return self.has_xs_key(None) 

2301 

2302 def begin(self): 

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

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

2305 

2306 def commit(self): 

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

2308 self._xbt = XenbusDevice.XBT_NIL 

2309 return ok 

2310 

2311 def abort(self): 

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

2313 assert(ok == True) 

2314 self._xbt = XenbusDevice.XBT_NIL 

2315 

2316 def create_physical_device(self): 

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

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

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

2320 return 

2321 try: 

2322 params = self.read("params") 

2323 frontend = self.read("frontend") 

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

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

2326 # from opening the physical-device 

2327 if not(is_cdrom): 

2328 major_minor = os.stat(params).st_rdev 

2329 major, minor = divmod(major_minor, 256) 

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

2331 except: 

2332 util.logException("BLKTAP2:create_physical_device") 

2333 

2334 def signal_hotplug(self, online=True): 

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

2336 self.XENBUS_DEVTYPE, 

2337 self.devid) 

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

2339 if online: 

2340 self._xs_write_path(xapi_path, "online") 

2341 self._xs_write_path(upstream_path, "connected") 

2342 else: 

2343 self._xs_rm_path(xapi_path) 

2344 self._xs_rm_path(upstream_path) 

2345 

2346 def sysfs_devname(self): 

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

2348 self.domid, self.devid) 

2349 

2350 def __str__(self): 

2351 return self.sysfs_devname() 

2352 

2353 @classmethod 

2354 def find(cls): 

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

2356 cls.XENBUS_DEVTYPE) 

2357 for path in glob.glob(pattern): 

2358 

2359 name = os.path.basename(path) 

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

2361 

2362 yield cls(domid, devid) 

2363 

2364 

2365class XenBackendDevice(XenbusDevice): 

2366 """Xenbus backend device""" 

2367 SYSFS_BUSTYPE = "xen-backend" 

2368 

2369 @classmethod 

2370 def from_xs_path(cls, _path): 

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

2372 

2373 assert _backend == 'backend' 

2374 assert _type == cls.XENBUS_DEVTYPE 

2375 

2376 domid = int(domid) 

2377 devid = int(devid) 

2378 

2379 return cls(domid, devid) 

2380 

2381 

2382class Blkback(XenBackendDevice): 

2383 """A blkback VBD""" 

2384 

2385 XENBUS_DEVTYPE = "vbd" 

2386 

2387 def __init__(self, domid, devid): 

2388 XenBackendDevice.__init__(self, domid, devid) 

2389 self._phy = None 

2390 self._vdi_uuid = None 

2391 self._q_state = None 

2392 self._q_events = None 

2393 

2394 class XenstoreValueError(Exception): 

2395 KEY = None 

2396 

2397 def __init__(self, vbd, _str): 

2398 self.vbd = vbd 

2399 self.str = _str 

2400 

2401 def __str__(self): 

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

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

2404 

2405 class PhysicalDeviceError(XenstoreValueError): 

2406 KEY = "physical-device" 

2407 

2408 class PhysicalDevice(object): 

2409 

2410 def __init__(self, major, minor): 

2411 self.major = int(major) 

2412 self.minor = int(minor) 

2413 

2414 @classmethod 

2415 def from_xbdev(cls, xbdev): 

2416 

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

2418 

2419 try: 

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

2421 major = int(major, 0x10) 

2422 minor = int(minor, 0x10) 

2423 except Exception as e: 

2424 raise xbdev.PhysicalDeviceError(xbdev, phy) 

2425 

2426 return cls(major, minor) 

2427 

2428 def makedev(self): 

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

2430 

2431 def is_tap(self): 

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

2433 

2434 def __str__(self): 

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

2436 

2437 def __eq__(self, other): 

2438 return \ 

2439 self.major == other.major and \ 

2440 self.minor == other.minor 

2441 

2442 def get_physical_device(self): 

2443 if not self._phy: 

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

2445 return self._phy 

2446 

2447 class QueueEvents(Attribute): 

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

2449 notifications emitted.""" 

2450 

2451 SYSFS_NODENAME = "queue_events" 

2452 

2453 QUEUE_RUNNING = (1 << 0) 

2454 QUEUE_PAUSE_DONE = (1 << 1) 

2455 QUEUE_SHUTDOWN_DONE = (1 << 2) 

2456 QUEUE_PAUSE_REQUEST = (1 << 3) 

2457 QUEUE_SHUTDOWN_REQUEST = (1 << 4) 

2458 

2459 def get_mask(self): 

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

2461 

2462 def set_mask(self, mask): 

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

2464 

2465 def get_queue_events(self): 

2466 if not self._q_events: 

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

2468 return self._q_events 

2469 

2470 def get_vdi_uuid(self): 

2471 if not self._vdi_uuid: 

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

2473 return self._vdi_uuid 

2474 

2475 def pause_requested(self): 

2476 return self.has_xs_key("pause") 

2477 

2478 def shutdown_requested(self): 

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

2480 

2481 def shutdown_done(self): 

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

2483 

2484 def running(self): 

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

2486 

2487 @classmethod 

2488 def find_by_physical_device(cls, phy): 

2489 for dev in cls.find(): 

2490 try: 

2491 _phy = dev.get_physical_device() 

2492 except cls.PhysicalDeviceError: 

2493 continue 

2494 

2495 if _phy == phy: 

2496 yield dev 

2497 

2498 @classmethod 

2499 def find_by_tap_minor(cls, minor): 

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

2501 return cls.find_by_physical_device(phy) 

2502 

2503 @classmethod 

2504 def find_by_tap(cls, tapdisk): 

2505 return cls.find_by_tap_minor(tapdisk.minor) 

2506 

2507 def has_tap(self): 

2508 

2509 if not self.can_tap(): 

2510 return False 

2511 

2512 phy = self.get_physical_device() 

2513 if phy: 

2514 return phy.is_tap() 

2515 

2516 return False 

2517 

2518 def is_bare_hvm(self): 

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

2520 try: 

2521 self.get_physical_device() 

2522 

2523 except self.PhysicalDeviceError as e: 

2524 vdi_type = self.read("type") 

2525 

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

2527 

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

2529 raise 

2530 

2531 return True 

2532 

2533 return False 

2534 

2535 def can_tap(self): 

2536 return not self.is_bare_hvm() 

2537 

2538 

2539class BlkbackEventHandler(UEventHandler): 

2540 

2541 LOG_FACILITY = _syslog.LOG_DAEMON 

2542 

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

2544 if not ident: 

2545 ident = self.__class__.__name__ 

2546 

2547 self.ident = ident 

2548 self._vbd = None 

2549 self._tapdisk = None 

2550 

2551 UEventHandler.__init__(self) 

2552 

2553 def run(self): 

2554 

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

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

2557 

2558 UEventHandler.run(self) 

2559 

2560 def __str__(self): 

2561 

2562 try: 

2563 path = self.xs_path 

2564 except: 

2565 path = None 

2566 

2567 try: 

2568 action = self.get_action() 

2569 except: 

2570 action = None 

2571 

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

2573 

2574 def _log(self, prio, msg): 

2575 syslog(prio, msg) 

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

2577 

2578 def info(self, msg): 

2579 self._log(_syslog.LOG_INFO, msg) 

2580 

2581 def warn(self, msg): 

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

2583 

2584 def error(self, msg): 

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

2586 

2587 def get_vbd(self): 

2588 if not self._vbd: 

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

2590 return self._vbd 

2591 

2592 def get_tapdisk(self): 

2593 if not self._tapdisk: 

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

2595 self._tapdisk = Tapdisk.from_minor(minor) 

2596 return self._tapdisk 

2597 # 

2598 # Events 

2599 # 

2600 

2601 def __add(self): 

2602 vbd = self.get_vbd() 

2603 # Manage blkback transitions 

2604 # self._manage_vbd() 

2605 

2606 vbd.create_physical_device() 

2607 

2608 vbd.signal_hotplug() 

2609 

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

2611 def add(self): 

2612 try: 

2613 self.__add() 

2614 except Attribute.NoSuchAttribute as e: 

2615 # 

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

2617 # registers device attributes. So poll a little. 

2618 # 

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

2620 raise RetryLoop.TransientFailure(e) 

2621 

2622 def __change(self): 

2623 vbd = self.get_vbd() 

2624 

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

2626 

2627 if vbd.has_tap(): 

2628 pass 

2629 #self._pause_update_tap() 

2630 

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

2632 

2633 self._signal_xapi() 

2634 

2635 def change(self): 

2636 vbd = self.get_vbd() 

2637 

2638 # NB. Beware of spurious change events between shutdown 

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

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

2641 

2642 while True: 

2643 vbd.begin() 

2644 

2645 if not vbd.exists() or \ 

2646 vbd.shutdown_done(): 

2647 break 

2648 

2649 self.__change() 

2650 

2651 if vbd.commit(): 

2652 return 

2653 

2654 vbd.abort() 

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

2656 

2657 def remove(self): 

2658 vbd = self.get_vbd() 

2659 

2660 vbd.signal_hotplug(False) 

2661 

2662 ACTIONS = {'add': add, 

2663 'change': change, 

2664 'remove': remove} 

2665 # 

2666 # VDI.pause 

2667 # 

2668 

2669 def _tap_should_pause(self): 

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

2671 paused""" 

2672 

2673 tapdisk = self.get_tapdisk() 

2674 TapState = Tapdisk.PauseState 

2675 

2676 PAUSED = 'P' 

2677 RUNNING = 'R' 

2678 PAUSED_SHUTDOWN = 'P,S' 

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

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

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

2682 

2683 next = TapState.RUNNING 

2684 vbds = {} 

2685 

2686 for vbd in Blkback.find_by_tap(tapdisk): 

2687 name = str(vbd) 

2688 

2689 pausing = vbd.pause_requested() 

2690 closing = vbd.shutdown_requested() 

2691 running = vbd.running() 

2692 

2693 if pausing: 

2694 if closing and not running: 

2695 vbds[name] = PAUSED_SHUTDOWN 

2696 else: 

2697 vbds[name] = PAUSED 

2698 next = TapState.PAUSED 

2699 

2700 else: 

2701 vbds[name] = RUNNING 

2702 

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

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

2705 vbds, next)) 

2706 

2707 return next == TapState.PAUSED 

2708 

2709 def _pause_update_tap(self): 

2710 vbd = self.get_vbd() 

2711 

2712 if self._tap_should_pause(): 

2713 self._pause_tap() 

2714 else: 

2715 self._resume_tap() 

2716 

2717 def _pause_tap(self): 

2718 tapdisk = self.get_tapdisk() 

2719 

2720 if not tapdisk.is_paused(): 

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

2722 tapdisk.pause() 

2723 

2724 def _resume_tap(self): 

2725 tapdisk = self.get_tapdisk() 

2726 

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

2728 # type while resuming. 

2729 vbd = self.get_vbd() 

2730 vdi_uuid = vbd.get_vdi_uuid() 

2731 

2732 if tapdisk.is_paused(): 

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

2734 vdi = VDI.from_cli(vdi_uuid) 

2735 _type = vdi.get_tap_type() 

2736 path = vdi.get_phy_path() 

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

2738 tapdisk.unpause(_type, path) 

2739 # 

2740 # VBD.pause/shutdown 

2741 # 

2742 

2743 def _manage_vbd(self): 

2744 vbd = self.get_vbd() 

2745 # NB. Hook into VBD state transitions. 

2746 

2747 events = vbd.get_queue_events() 

2748 

2749 mask = 0 

2750 mask |= events.QUEUE_PAUSE_DONE # pause/unpause 

2751 mask |= events.QUEUE_SHUTDOWN_DONE # shutdown 

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

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

2754 

2755 events.set_mask(mask) 

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

2757 

2758 def _signal_xapi(self): 

2759 vbd = self.get_vbd() 

2760 

2761 pausing = vbd.pause_requested() 

2762 closing = vbd.shutdown_requested() 

2763 running = vbd.running() 

2764 

2765 handled = 0 

2766 

2767 if pausing and not running: 

2768 if 'pause-done' not in vbd: 

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

2770 handled += 1 

2771 

2772 if not pausing: 

2773 if 'pause-done' in vbd: 

2774 vbd.rm('pause-done') 

2775 handled += 1 

2776 

2777 if closing and not running: 

2778 if 'shutdown-done' not in vbd: 

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

2780 handled += 1 

2781 

2782 if handled > 1: 

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

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

2785 (pausing, closing, running)) 

2786 

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

2788 

2789 import sys 

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

2791 

2792 # 

2793 # Simple CLI interface for manual operation 

2794 # 

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

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

2797 # 

2798 

2799 def usage(stream): 

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

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

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

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

2804 

2805 try: 

2806 cmd = sys.argv[1] 

2807 except IndexError: 

2808 usage(sys.stderr) 

2809 sys.exit(1) 

2810 

2811 try: 

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

2813 except: 

2814 usage(sys.stderr) 

2815 sys.exit(1) 

2816 

2817 # 

2818 # Local Tapdisks 

2819 # 

2820 

2821 if cmd == 'tap.major': 

2822 

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

2824 

2825 elif cmd == 'tap.launch': 

2826 

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

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

2829 

2830 elif _class == 'tap': 

2831 

2832 attrs = {} 

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

2834 try: 

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

2836 attrs[key] = val 

2837 continue 

2838 except ValueError: 

2839 pass 

2840 

2841 try: 

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

2843 continue 

2844 except ValueError: 

2845 pass 

2846 

2847 try: 

2848 arg = Tapdisk.Arg.parse(item) 

2849 attrs['_type'] = arg.type 

2850 attrs['path'] = arg.path 

2851 continue 

2852 except Tapdisk.Arg.InvalidArgument: 

2853 pass 

2854 

2855 attrs['path'] = item 

2856 

2857 if cmd == 'tap.list': 

2858 

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

2860 blktap = tapdisk.get_blktap() 

2861 print(tapdisk, end=' ') 

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

2863 (blktap, 

2864 blktap.get_task_pid(), 

2865 blktap.get_pool_name())) 

2866 

2867 elif cmd == 'tap.vbds': 

2868 # Find all Blkback instances for a given tapdisk 

2869 

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

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

2872 for vbd in Blkback.find_by_tap(tapdisk): 

2873 print(vbd, end=' ') 

2874 print() 

2875 

2876 else: 

2877 

2878 if not attrs: 

2879 usage(sys.stderr) 

2880 sys.exit(1) 

2881 

2882 try: 

2883 tapdisk = Tapdisk.get( ** attrs) 

2884 except TypeError: 

2885 usage(sys.stderr) 

2886 sys.exit(1) 

2887 

2888 if cmd == 'tap.shutdown': 

2889 # Shutdown a running tapdisk, or raise 

2890 tapdisk.shutdown() 

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

2892 

2893 elif cmd == 'tap.pause': 

2894 # Pause an unpaused tapdisk, or raise 

2895 tapdisk.pause() 

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

2897 

2898 elif cmd == 'tap.unpause': 

2899 # Unpause a paused tapdisk, or raise 

2900 tapdisk.unpause() 

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

2902 

2903 elif cmd == 'tap.stats': 

2904 # Gather tapdisk status 

2905 stats = tapdisk.stats() 

2906 print("%s:" % tapdisk) 

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

2908 

2909 else: 

2910 usage(sys.stderr) 

2911 sys.exit(1) 

2912 

2913 elif cmd == 'vbd.uevent': 

2914 

2915 hnd = BlkbackEventHandler(cmd) 

2916 

2917 if not sys.stdin.isatty(): 

2918 try: 

2919 hnd.run() 

2920 except Exception as e: 

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

2922 

2923 import traceback 

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

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

2926 for entry in trace: 

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

2928 util.SMlog(line) 

2929 else: 

2930 hnd.run() 

2931 

2932 elif cmd == 'vbd.list': 

2933 

2934 for vbd in Blkback.find(): 

2935 print(vbd, \ 

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

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

2938 

2939 else: 

2940 usage(sys.stderr) 

2941 sys.exit(1)