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 break 

825 except TapCtl.CommandFailure as e: 

826 err = ( 

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

828 ) or None 

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

830 if retry_open < 5: 

831 retry_open += 1 

832 time.sleep(1) 

833 continue 

834 if LINSTOR_AVAILABLE and err == errno.EROFS: 

835 log_drbd_openers(path) 

836 raise 

837 try: 

838 tapdisk = cls.__from_blktap(blktap) 

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

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

841 return tapdisk 

842 except: 

843 TapCtl.close(pid, minor) 

844 raise 

845 

846 except: 

847 TapCtl.detach(pid, minor) 

848 raise 

849 

850 except: 

851 try: 

852 TapCtl.shutdown(pid) 

853 except: 

854 # Best effort to shutdown 

855 pass 

856 raise 

857 

858 except TapCtl.CommandFailure as ctl: 

859 util.logException(ctl) 

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

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

862 raise xs_errors.XenError('TapdiskDriveEmpty') 

863 else: 

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

865 

866 @classmethod 

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

868 blktap = Blktap.allocate() 

869 try: 

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

871 except: 

872 blktap.free() 

873 raise 

874 

875 def shutdown(self, force=False): 

876 

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

878 

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

880 

881 self.get_blktap().free() 

882 

883 def pause(self): 

884 

885 if not self.is_running(): 

886 raise TapdiskInvalidState(self) 

887 

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

889 

890 self._set_dirty() 

891 

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

893 

894 if not self.is_paused(): 

895 raise TapdiskInvalidState(self) 

896 

897 # FIXME: should the arguments be optional? 

898 if _type is None: 

899 _type = self.type 

900 if path is None: 

901 path = self.path 

902 

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

904 cbtlog=cbtlog) 

905 

906 self._set_dirty() 

907 

908 def stats(self): 

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

910 # 

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

912 # 

913 

914 def _set_dirty(self): 

915 self._dirty = True 

916 

917 def _refresh(self, __get): 

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

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

920 

921 def __getattribute__(self, name): 

922 def __get(name): 

923 # NB. avoid(rec(ursion) 

924 return object.__getattribute__(self, name) 

925 

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

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

928 self._refresh(__get) 

929 self._dirty = False 

930 

931 return __get(name) 

932 

933 class PauseState: 

934 RUNNING = 'R' 

935 PAUSING = 'r' 

936 PAUSED = 'P' 

937 

938 class Flags: 

939 DEAD = 0x0001 

940 CLOSED = 0x0002 

941 QUIESCE_REQUESTED = 0x0004 

942 QUIESCED = 0x0008 

943 PAUSE_REQUESTED = 0x0010 

944 PAUSED = 0x0020 

945 SHUTDOWN_REQUESTED = 0x0040 

946 LOCKING = 0x0080 

947 RETRY_NEEDED = 0x0100 

948 LOG_DROPPED = 0x0200 

949 

950 PAUSE_MASK = PAUSE_REQUESTED | PAUSED 

951 

952 def is_paused(self): 

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

954 

955 def is_running(self): 

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

957 

958 def pause_state(self): 

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

960 return self.PauseState.PAUSED 

961 

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

963 return self.PauseState.PAUSING 

964 

965 return self.PauseState.RUNNING 

966 

967 @staticmethod 

968 def _parse_minor(devpath): 

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

970 pattern = re.compile(regex) 

971 groups = pattern.search(devpath) 

972 if not groups: 

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

974 

975 minor = groups.group(2) 

976 return int(minor) 

977 

978 _major = None 

979 

980 @classmethod 

981 def major(cls): 

982 if cls._major: 

983 return cls._major 

984 

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

986 for line in devices: 

987 

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

989 if len(row) != 2: 

990 continue 

991 

992 major, name = row 

993 if name != 'tapdev': 

994 continue 

995 

996 cls._major = int(major) 

997 break 

998 

999 devices.close() 

1000 return cls._major 

1001 

1002 

1003class VDI(object): 

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

1005 

1006 CONF_KEY_ALLOW_CACHING = "vdi_allow_caching" 

1007 CONF_KEY_MODE_ON_BOOT = "vdi_on_boot" 

1008 CONF_KEY_CACHE_SR = "local_cache_sr" 

1009 CONF_KEY_O_DIRECT = "o_direct" 

1010 LOCK_CACHE_SETUP = "cachesetup" 

1011 

1012 ATTACH_DETACH_RETRY_SECS = 120 

1013 

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

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

1016 self._vdi_uuid = uuid 

1017 self._session = target.session 

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

1019 self.__o_direct = None 

1020 self.__o_direct_reason = None 

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

1022 self.tap = None 

1023 

1024 def get_o_direct_capability(self, options): 

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

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

1027 return self.__o_direct, self.__o_direct_reason 

1028 

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

1030 self.__o_direct = True 

1031 self.__o_direct_reason = "LICENSE_RESTRICTION" 

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

1033 self.__o_direct = True 

1034 self.__o_direct_reason = "SR_NOT_SUPPORTED" 

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

1036 util.SMlog(self.target.vdi) 

1037 self.__o_direct = True 

1038 self.__o_direct_reason = "NO_RO_IMAGE" 

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

1040 self.__o_direct = True 

1041 self.__o_direct_reason = "RO_WITH_NO_PARENT" 

1042 elif options.get(self.CONF_KEY_O_DIRECT): 

1043 self.__o_direct = True 

1044 self.__o_direct_reason = "SR_OVERRIDE" 

1045 

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

1047 self.__o_direct = False 

1048 self.__o_direct_reason = "" 

1049 

1050 return self.__o_direct, self.__o_direct_reason 

1051 

1052 @classmethod 

1053 def from_cli(cls, uuid): 

1054 import VDI as sm 

1055 

1056 session = XenAPI.xapi_local() 

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

1058 

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

1060 driver_info = target.sr.srcmd.driver_info 

1061 

1062 session.xenapi.session.logout() 

1063 

1064 return cls(uuid, target, driver_info) 

1065 

1066 @staticmethod 

1067 def _tap_type(vdi_type): 

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

1069 return { 

1070 'raw': 'aio', 

1071 'vhd': 'vhd', 

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

1073 'aio': 'aio', # for LVHD 

1074 'file': 'aio', 

1075 'phy': 'aio' 

1076 }[vdi_type] 

1077 

1078 def get_tap_type(self): 

1079 vdi_type = self.target.get_vdi_type() 

1080 return VDI._tap_type(vdi_type) 

1081 

1082 def get_phy_path(self): 

1083 return self.target.get_vdi_path() 

1084 

1085 class UnexpectedVDIType(Exception): 

1086 

1087 def __init__(self, vdi_type, target): 

1088 self.vdi_type = vdi_type 

1089 self.target = target 

1090 

1091 def __str__(self): 

1092 return \ 

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

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

1095 

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

1097 'raw': 'phy', 

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

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

1100 'file': 'tap', 

1101 'vhd': 'tap'} 

1102 

1103 def tap_wanted(self): 

1104 # 1. Let the target vdi_type decide 

1105 

1106 vdi_type = self.target.get_vdi_type() 

1107 

1108 try: 

1109 plug_type = self.VDI_PLUG_TYPE[vdi_type] 

1110 except KeyError: 

1111 raise self.UnexpectedVDIType(vdi_type, 

1112 self.target.vdi) 

1113 

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

1115 return True 

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

1117 return True 

1118 # 2. Otherwise, there may be more reasons 

1119 # 

1120 # .. TBD 

1121 

1122 return False 

1123 

1124 class TargetDriver: 

1125 """Safe target driver access.""" 

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

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

1128 # try/except would risk breaking compatibility. 

1129 

1130 def __init__(self, vdi, driver_info): 

1131 self.vdi = vdi 

1132 self._caps = driver_info['capabilities'] 

1133 

1134 def has_cap(self, cap): 

1135 """Determine if target has given capability""" 

1136 return cap in self._caps 

1137 

1138 def attach(self, sr_uuid, vdi_uuid): 

1139 #assert self.has_cap("VDI_ATTACH") 

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

1141 

1142 def detach(self, sr_uuid, vdi_uuid): 

1143 #assert self.has_cap("VDI_DETACH") 

1144 self.vdi.detach(sr_uuid, vdi_uuid) 

1145 

1146 def activate(self, sr_uuid, vdi_uuid): 

1147 if self.has_cap("VDI_ACTIVATE"): 

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

1149 

1150 def deactivate(self, sr_uuid, vdi_uuid): 

1151 if self.has_cap("VDI_DEACTIVATE"): 

1152 self.vdi.deactivate(sr_uuid, vdi_uuid) 

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

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

1155 

1156 def get_vdi_type(self): 

1157 _type = self.vdi.vdi_type 

1158 if not _type: 

1159 _type = self.vdi.sr.sr_vditype 

1160 if not _type: 

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

1162 return _type 

1163 

1164 def get_vdi_path(self): 

1165 return self.vdi.path 

1166 

1167 class Link(object): 

1168 """Relink a node under a common name""" 

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

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

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

1172 # relink existing devices under deterministic path names. 

1173 

1174 BASEDIR = None 

1175 

1176 def _mklink(self, target): 

1177 raise NotImplementedError("_mklink is not defined") 

1178 

1179 def _equals(self, target): 

1180 raise NotImplementedError("_equals is not defined") 

1181 

1182 def __init__(self, path): 

1183 self._path = path 

1184 

1185 @classmethod 

1186 def from_name(cls, name): 

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

1188 return cls(path) 

1189 

1190 @classmethod 

1191 def from_uuid(cls, sr_uuid, vdi_uuid): 

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

1193 return cls.from_name(name) 

1194 

1195 def path(self): 

1196 return self._path 

1197 

1198 def stat(self): 

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

1200 

1201 def mklink(self, target): 

1202 

1203 path = self.path() 

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

1205 

1206 mkdirs(os.path.dirname(path)) 

1207 try: 

1208 self._mklink(target) 

1209 except OSError as e: 

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

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

1212 # be seen. 

1213 if e.errno != errno.EEXIST: 

1214 raise 

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

1216 

1217 def unlink(self): 

1218 try: 

1219 os.unlink(self.path()) 

1220 except OSError as e: 

1221 if e.errno != errno.ENOENT: 

1222 raise 

1223 

1224 def __str__(self): 

1225 path = self.path() 

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

1227 

1228 class SymLink(Link): 

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

1230 

1231 def readlink(self): 

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

1233 

1234 def symlink(self): 

1235 return self.path() 

1236 

1237 def _mklink(self, target): 

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

1239 

1240 def _equals(self, target): 

1241 return self.readlink() == target 

1242 

1243 class DeviceNode(Link): 

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

1245 

1246 @classmethod 

1247 def _real_stat(cls, target): 

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

1249 _target = os.path.realpath(target) 

1250 return os.stat(_target) 

1251 

1252 @classmethod 

1253 def is_block(cls, target): 

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

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

1256 

1257 def _mklink(self, target): 

1258 

1259 st = self._real_stat(target) 

1260 if not S_ISBLK(st.st_mode): 

1261 raise self.NotABlockDevice(target, st) 

1262 

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

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

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

1266 

1267 def _equals(self, target): 

1268 target_rdev = self._real_stat(target).st_rdev 

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

1270 

1271 def rdev(self): 

1272 st = self.stat() 

1273 assert S_ISBLK(st.st_mode) 

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

1275 

1276 class NotABlockDevice(Exception): 

1277 

1278 def __init__(self, path, st): 

1279 self.path = path 

1280 self.st = st 

1281 

1282 def __str__(self): 

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

1284 

1285 class Hybrid(Link): 

1286 

1287 def __init__(self, path): 

1288 VDI.Link.__init__(self, path) 

1289 self._devnode = VDI.DeviceNode(path) 

1290 self._symlink = VDI.SymLink(path) 

1291 

1292 def rdev(self): 

1293 st = self.stat() 

1294 if S_ISBLK(st.st_mode): 

1295 return self._devnode.rdev() 

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

1297 

1298 def mklink(self, target): 

1299 if self._devnode.is_block(target): 

1300 self._obj = self._devnode 

1301 else: 

1302 self._obj = self._symlink 

1303 self._obj.mklink(target) 

1304 

1305 def _equals(self, target): 

1306 return self._obj._equals(target) 

1307 

1308 class PhyLink(SymLink): 

1309 BASEDIR = "/dev/sm/phy" 

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

1311 

1312 class NBDLink(SymLink): 

1313 

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

1315 

1316 class BackendLink(Hybrid): 

1317 BASEDIR = "/dev/sm/backend" 

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

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

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

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

1322 # soon as ISOs are tapdisks. 

1323 

1324 @staticmethod 

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

1326 

1327 tapdisk = Tapdisk.find_by_path(phy_path) 

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

1329 blktap = Blktap.allocate() 

1330 blktap.set_pool_name(sr_uuid) 

1331 if pool_size: 

1332 blktap.set_pool_size(pool_size) 

1333 

1334 try: 

1335 tapdisk = \ 

1336 Tapdisk.launch_on_tap(blktap, 

1337 phy_path, 

1338 VDI._tap_type(vdi_type), 

1339 options) 

1340 except: 

1341 blktap.free() 

1342 raise 

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

1344 

1345 else: 

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

1347 

1348 return tapdisk.get_devpath(), tapdisk 

1349 

1350 @staticmethod 

1351 def _tap_deactivate(minor): 

1352 

1353 try: 

1354 tapdisk = Tapdisk.from_minor(minor) 

1355 except TapdiskNotRunning as e: 

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

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

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

1359 # the recorded minor. 

1360 else: 

1361 tapdisk.shutdown() 

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

1363 

1364 @classmethod 

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

1366 """ 

1367 Pauses the tapdisk. 

1368 

1369 session: a XAPI session 

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

1371 vdi_uuid: the UUID of the VDI to pause 

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

1373 non-blocking manner 

1374 """ 

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

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

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

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

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

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

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

1382 if not cls.call_pluginhandler(session, host_ref, 

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

1384 # Failed to pause node 

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

1386 return False 

1387 return True 

1388 

1389 @classmethod 

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

1391 activate_parents=False): 

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

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

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

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

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

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

1398 if not cls.call_pluginhandler(session, host_ref, 

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

1400 # Failed to unpause node 

1401 return False 

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

1403 return True 

1404 

1405 @classmethod 

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

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

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

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

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

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

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

1413 if not cls.call_pluginhandler(session, host_ref, 

1414 sr_uuid, vdi_uuid, "refresh", None, 

1415 activate_parents=activate_parents): 

1416 # Failed to refresh node 

1417 return False 

1418 return True 

1419 

1420 @classmethod 

1421 def tap_status(cls, session, vdi_uuid): 

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

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

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

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

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

1427 return True 

1428 return False 

1429 

1430 @classmethod 

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

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

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

1434 try: 

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

1436 "failfast": str(failfast)} 

1437 if secondary: 

1438 args["secondary"] = secondary 

1439 if activate_parents: 

1440 args["activate_parents"] = "true" 

1441 ret = session.xenapi.host.call_plugin( 

1442 host_ref, PLUGIN_TAP_PAUSE, action, 

1443 args) 

1444 return ret == "True" 

1445 except Exception as e: 

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

1447 return False 

1448 

1449 def _add_tag(self, vdi_uuid, writable): 

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

1451 attach_mode = "RO" 

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

1453 attach_mode = "RW" 

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

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

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

1457 attached_as = util.attached_as(sm_config) 

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

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

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

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

1462 term_output=False, writable=writable): 

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

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

1465 if 'relinking' in sm_config: 

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

1467 return False 

1468 if 'paused' in sm_config: 

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

1470 return False 

1471 self._session.xenapi.VDI.add_to_sm_config( 

1472 vdi_ref, 'activating', 'True') 

1473 host_key = "host_%s" % host_ref 

1474 assert host_key not in sm_config 

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

1476 attach_mode) 

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

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

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

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

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

1482 self._session.xenapi.VDI.remove_from_sm_config( 

1483 vdi_ref, 'activating') 

1484 return False 

1485 util.SMlog("Activate lock succeeded") 

1486 return True 

1487 

1488 def _check_tag(self, vdi_uuid): 

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

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

1491 if 'paused' in sm_config: 

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

1493 return False 

1494 return True 

1495 

1496 def _remove_tag(self, vdi_uuid): 

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

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

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

1500 host_key = "host_%s" % host_ref 

1501 if host_key in sm_config: 

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

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

1504 else: 

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

1506 

1507 def _get_pool_config(self, pool_name): 

1508 pool_info = dict() 

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

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

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

1512 # special pool 

1513 return pool_info 

1514 

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

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

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

1518 pool_size_str = sr_config.get(POOL_SIZE_KEY) 

1519 pool_name_override = vdi_config.get(POOL_NAME_KEY) 

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

1521 pool_name = pool_name_override 

1522 pool_size_override = vdi_config.get(POOL_SIZE_KEY) 

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

1524 pool_size_str = pool_size_override 

1525 pool_size = 0 

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

1527 try: 

1528 pool_size = int(pool_size_str) 

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

1530 raise ValueError("outside of range") 

1531 pool_size = NUM_PAGES_PER_RING * pool_size 

1532 except ValueError: 

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

1534 pool_size = 0 

1535 

1536 pool_info["mem-pool"] = pool_name 

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

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

1539 

1540 return pool_info 

1541 

1542 def linkNBD(self, sr_uuid, vdi_uuid): 

1543 if self.tap: 

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

1545 int(self.tap.minor)) 

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

1547 

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

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

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

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

1552 util.SMlog("Attach & activate") 

1553 self._attach(sr_uuid, vdi_uuid) 

1554 dev_path = self._activate(sr_uuid, vdi_uuid, 

1555 {"rdonly": not writable}) 

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

1557 self.linkNBD(sr_uuid, vdi_uuid) 

1558 

1559 # Return backend/ link 

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

1561 if self.tap_wanted(): 

1562 # Only have NBD if we also have a tap 

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

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

1565 vdi_uuid) 

1566 else: 

1567 nbd_path = "" 

1568 

1569 options = {"rdonly": not writable} 

1570 options.update(caching_params) 

1571 o_direct, o_direct_reason = self.get_o_direct_capability(options) 

1572 struct = {'params': back_path, 

1573 'params_nbd': nbd_path, 

1574 'o_direct': o_direct, 

1575 'o_direct_reason': o_direct_reason, 

1576 'xenstore_data': self.xenstore_data} 

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

1578 

1579 try: 

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

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

1582 f.close() 

1583 except: 

1584 pass 

1585 

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

1587 

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

1589 util.SMlog("blktap2.activate") 

1590 options = {"rdonly": not writable} 

1591 options.update(caching_params) 

1592 

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

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

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

1596 try: 

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

1598 return 

1599 except util.SRBusyException: 

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

1601 time.sleep(1) 

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

1603 

1604 @locking("VDIUnavailable") 

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

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

1607 

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

1609 refresh = False 

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

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

1612 return False 

1613 refresh = True 

1614 

1615 try: 

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

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

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

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

1620 # object completely 

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

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

1623 target.sr.srcmd.params = params 

1624 driver_info = target.sr.srcmd.driver_info 

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

1626 

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

1628 "blktap_activate_inject_failure", 

1629 lambda: util.inject_failure()) 

1630 

1631 # Attach the physical node 

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

1633 self._attach(sr_uuid, vdi_uuid) 

1634 

1635 vdi_type = self.target.get_vdi_type() 

1636 

1637 # Take lvchange-p Lock before running 

1638 # tap-ctl open 

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

1640 # now taking the same lock 

1641 # This is a fix for CA-155766 

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

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

1644 vdi_type == vhdutil.VDI_TYPE_VHD: 

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

1646 lock.acquire() 

1647 

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

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

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

1651 session = self.target.vdi.session 

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

1653 # pylint: disable=used-before-assignment 

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

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

1656 key_hash = sm_config['key_hash'] 

1657 options['key_hash'] = key_hash 

1658 options['vdi_uuid'] = vdi_uuid 

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

1660 # Activate the physical node 

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

1662 

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

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

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

1666 lock.release() 

1667 except: 

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

1669 if self.tap_wanted(): 

1670 util.fistpoint.activate_custom_fn( 

1671 "blktap_activate_error_handling", 

1672 lambda: time.sleep(30)) 

1673 while True: 

1674 try: 

1675 self._remove_tag(vdi_uuid) 

1676 break 

1677 except xmlrpc.client.ProtocolError as e: 

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

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

1680 continue 

1681 else: 

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

1683 break 

1684 except Exception as e: 

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

1686 break 

1687 raise 

1688 finally: 

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

1690 self._session.xenapi.VDI.remove_from_sm_config( 

1691 vdi_ref, 'activating') 

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

1693 

1694 # Link result to backend/ 

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

1696 self.linkNBD(sr_uuid, vdi_uuid) 

1697 return True 

1698 

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

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

1701 

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

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

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

1705 # Maybe launch a tapdisk on the physical link 

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

1707 vdi_type = self.target.get_vdi_type() 

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

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

1710 options.update(vdi_options) 

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

1712 sr_uuid, options, 

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

1714 else: 

1715 dev_path = phy_path # Just reuse phy 

1716 

1717 return dev_path 

1718 

1719 def _attach(self, sr_uuid, vdi_uuid): 

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

1721 params = attach_info['params'] 

1722 xenstore_data = attach_info['xenstore_data'] 

1723 phy_path = util.to_plain_string(params) 

1724 self.xenstore_data.update(xenstore_data) 

1725 # Save it to phy/ 

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

1727 

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

1729 util.SMlog("blktap2.deactivate") 

1730 for i in range(self.ATTACH_DETACH_RETRY_SECS): 

1731 try: 

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

1733 return 

1734 except util.SRBusyException as e: 

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

1736 time.sleep(1) 

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

1738 

1739 @locking("VDIUnavailable") 

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

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

1742 

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

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

1745 return False 

1746 

1747 self._deactivate(sr_uuid, vdi_uuid, caching_params) 

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

1749 self._detach(sr_uuid, vdi_uuid) 

1750 if self.tap_wanted(): 

1751 self._remove_tag(vdi_uuid) 

1752 

1753 return True 

1754 

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

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

1757 

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

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

1760 util.SMlog("Deactivate & detach") 

1761 self._deactivate(sr_uuid, vdi_uuid, caching_params) 

1762 self._detach(sr_uuid, vdi_uuid) 

1763 else: 

1764 pass # nothing to do 

1765 

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

1767 import VDI as sm 

1768 

1769 # Shutdown tapdisk 

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

1771 

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

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

1774 return 

1775 

1776 try: 

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

1778 os.unlink(attach_info_path) 

1779 except: 

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

1781 

1782 try: 

1783 major, minor = back_link.rdev() 

1784 except self.DeviceNode.NotABlockDevice: 

1785 pass 

1786 else: 

1787 if major == Tapdisk.major(): 

1788 self._tap_deactivate(minor) 

1789 self.remove_cache(sr_uuid, vdi_uuid, caching_params) 

1790 

1791 # Remove the backend link 

1792 back_link.unlink() 

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

1794 

1795 # Deactivate & detach the physical node 

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

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

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

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

1800 # object completely 

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

1802 driver_info = target.sr.srcmd.driver_info 

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

1804 

1805 self.target.deactivate(sr_uuid, vdi_uuid) 

1806 

1807 def _detach(self, sr_uuid, vdi_uuid): 

1808 self.target.detach(sr_uuid, vdi_uuid) 

1809 

1810 # Remove phy/ 

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

1812 

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

1814 # Remove existing VDI.sm_config fields 

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

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

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

1818 if not on_boot is None: 

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

1820 if not caching is None: 

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

1822 

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

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

1825 return 

1826 

1827 util.SMlog("Requested local caching") 

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

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

1830 return 

1831 

1832 scratch_mode = False 

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

1834 scratch_mode = True 

1835 util.SMlog("Requested scratch mode") 

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

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

1838 return 

1839 

1840 dev_path = None 

1841 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR) 

1842 if not local_sr_uuid: 

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

1844 return 

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

1846 local_sr_uuid, scratch_mode, params) 

1847 

1848 if dev_path: 

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

1850 params.get(self.CONF_KEY_MODE_ON_BOOT), 

1851 params.get(self.CONF_KEY_ALLOW_CACHING)) 

1852 

1853 return dev_path 

1854 

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

1856 vm_uuid = None 

1857 vm_label = "" 

1858 try: 

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

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

1861 cache_sr_label = cache_sr_rec.get("name_label") 

1862 

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

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

1865 host_label = host_rec.get("name_label") 

1866 

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

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

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

1870 for vbd_rec in vbds.values(): 

1871 vm_ref = vbd_rec.get("VM") 

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

1873 vm_uuid = vm_rec.get("uuid") 

1874 vm_label = vm_rec.get("name_label") 

1875 except: 

1876 util.logException("alert_no_cache") 

1877 

1878 alert_obj = "SR" 

1879 alert_uuid = str(cache_sr_uuid) 

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

1881 if vm_uuid: 

1882 alert_obj = "VM" 

1883 alert_uuid = vm_uuid 

1884 reason = "" 

1885 if err == errno.ENOSPC: 

1886 reason = "because there is no space left" 

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

1888 (vm_label, reason, cache_sr_label, host_label) 

1889 

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

1891 (alert_obj, alert_uuid, alert_str)) 

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

1893 alert_obj, alert_uuid, alert_str) 

1894 

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

1896 scratch_mode, options): 

1897 import SR 

1898 import EXTSR 

1899 import NFSSR 

1900 from lock import Lock 

1901 from FileSR import FileVDI 

1902 

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

1904 FileVDI.extractUuid) 

1905 if not parent_uuid: 

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

1907 self.target.vdi.uuid) 

1908 return 

1909 

1910 util.SMlog("Setting up cache") 

1911 parent_uuid = parent_uuid.strip() 

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

1913 

1914 if shared_target.parent: 

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

1916 shared_target.uuid) 

1917 return 

1918 

1919 SR.registerSR(EXTSR.EXTSR) 

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

1921 

1922 lock = Lock(self.LOCK_CACHE_SETUP, parent_uuid) 

1923 lock.acquire() 

1924 

1925 # read cache 

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

1927 if util.pathexists(read_cache_path): 

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

1929 read_cache_path) 

1930 else: 

1931 try: 

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

1933 except util.CommandException as e: 

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

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

1936 return None 

1937 

1938 # local write node 

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

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

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

1942 if util.pathexists(local_leaf_path): 

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

1944 local_leaf_path) 

1945 os.unlink(local_leaf_path) 

1946 try: 

1947 vhdutil.snapshot(local_leaf_path, read_cache_path, False, 

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

1949 except util.CommandException as e: 

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

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

1952 return None 

1953 

1954 local_leaf_size = vhdutil.getSizeVirt(local_leaf_path) 

1955 if leaf_size > local_leaf_size: 

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

1957 (leaf_size, local_leaf_size)) 

1958 vhdutil.setSizeVirtFast(local_leaf_path, leaf_size) 

1959 

1960 vdi_type = self.target.get_vdi_type() 

1961 

1962 prt_tapdisk = Tapdisk.find_by_path(read_cache_path) 

1963 if not prt_tapdisk: 

1964 parent_options = copy.deepcopy(options) 

1965 parent_options["rdonly"] = False 

1966 parent_options["lcache"] = True 

1967 

1968 blktap = Blktap.allocate() 

1969 try: 

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

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

1972 # its own pool 

1973 prt_tapdisk = \ 

1974 Tapdisk.launch_on_tap(blktap, read_cache_path, 

1975 'vhd', parent_options) 

1976 except: 

1977 blktap.free() 

1978 raise 

1979 

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

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

1982 

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

1984 leaf_tapdisk = Tapdisk.find_by_path(local_leaf_path) 

1985 if not leaf_tapdisk: 

1986 blktap = Blktap.allocate() 

1987 child_options = copy.deepcopy(options) 

1988 child_options["rdonly"] = False 

1989 child_options["lcache"] = False 

1990 child_options["existing_prt"] = prt_tapdisk.minor 

1991 child_options["secondary"] = secondary 

1992 child_options["standby"] = scratch_mode 

1993 try: 

1994 leaf_tapdisk = \ 

1995 Tapdisk.launch_on_tap(blktap, local_leaf_path, 

1996 'vhd', child_options) 

1997 except: 

1998 blktap.free() 

1999 raise 

2000 

2001 lock.release() 

2002 

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

2004 (read_cache_path, local_leaf_path)) 

2005 

2006 self.tap = leaf_tapdisk 

2007 return leaf_tapdisk.get_devpath() 

2008 

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

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

2011 return 

2012 

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

2014 

2015 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR) 

2016 if caching and not local_sr_uuid: 

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

2018 return 

2019 

2020 if caching: 

2021 self._remove_cache(self._session, local_sr_uuid) 

2022 

2023 if self._session is not None: 

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

2025 

2026 def _is_tapdisk_in_use(self, minor): 

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

2028 if not retVal: 

2029 # err on the side of caution 

2030 return True 

2031 

2032 for link in links: 

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

2034 return True 

2035 

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

2037 for s in sockets: 

2038 if socket_re.match(s): 

2039 return True 

2040 

2041 return False 

2042 

2043 def _remove_cache(self, session, local_sr_uuid): 

2044 import SR 

2045 import EXTSR 

2046 import NFSSR 

2047 from lock import Lock 

2048 from FileSR import FileVDI 

2049 

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

2051 FileVDI.extractUuid) 

2052 if not parent_uuid: 

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

2054 self.target.vdi.uuid) 

2055 return 

2056 

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

2058 

2059 parent_uuid = parent_uuid.strip() 

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

2061 

2062 SR.registerSR(EXTSR.EXTSR) 

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

2064 

2065 lock = Lock(self.LOCK_CACHE_SETUP, parent_uuid) 

2066 lock.acquire() 

2067 

2068 # local write node 

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

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

2071 if util.pathexists(local_leaf_path): 

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

2073 os.unlink(local_leaf_path) 

2074 

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

2076 prt_tapdisk = Tapdisk.find_by_path(read_cache_path) 

2077 if not prt_tapdisk: 

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

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

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

2081 read_cache_path) 

2082 try: 

2083 prt_tapdisk.shutdown() 

2084 except: 

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

2086 else: 

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

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

2089 # GC run 

2090 

2091 lock.release() 

2092 

2093PythonKeyError = KeyError 

2094 

2095 

2096class UEventHandler(object): 

2097 

2098 def __init__(self): 

2099 self._action = None 

2100 

2101 class KeyError(PythonKeyError): 

2102 def __init__(self, args): 

2103 super().__init__(args) 

2104 self.key = args[0] 

2105 

2106 def __str__(self): 

2107 return \ 

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

2109 "Not called in udev context?" 

2110 

2111 @classmethod 

2112 def getenv(cls, key): 

2113 try: 

2114 return os.environ[key] 

2115 except KeyError as e: 

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

2117 

2118 def get_action(self): 

2119 if not self._action: 

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

2121 return self._action 

2122 

2123 class UnhandledEvent(Exception): 

2124 

2125 def __init__(self, event, handler): 

2126 self.event = event 

2127 self.handler = handler 

2128 

2129 def __str__(self): 

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

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

2132 

2133 ACTIONS = {} 

2134 

2135 def run(self): 

2136 

2137 action = self.get_action() 

2138 try: 

2139 fn = self.ACTIONS[action] 

2140 except KeyError: 

2141 raise self.UnhandledEvent(action, self) 

2142 

2143 return fn(self) 

2144 

2145 def __str__(self): 

2146 try: 

2147 action = self.get_action() 

2148 except: 

2149 action = None 

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

2151 

2152 

2153class __BlktapControl(ClassDevice): 

2154 SYSFS_CLASSTYPE = "misc" 

2155 

2156 def __init__(self): 

2157 ClassDevice.__init__(self) 

2158 self._default_pool = None 

2159 

2160 def sysfs_devname(self): 

2161 return "blktap!control" 

2162 

2163 class DefaultPool(Attribute): 

2164 SYSFS_NODENAME = "default_pool" 

2165 

2166 def get_default_pool_attr(self): 

2167 if not self._default_pool: 

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

2169 return self._default_pool 

2170 

2171 def get_default_pool_name(self): 

2172 return self.get_default_pool_attr().readline() 

2173 

2174 def set_default_pool_name(self, name): 

2175 self.get_default_pool_attr().writeline(name) 

2176 

2177 def get_default_pool(self): 

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

2179 

2180 def set_default_pool(self, pool): 

2181 self.set_default_pool_name(pool.name) 

2182 

2183 class NoSuchPool(Exception): 

2184 def __init__(self, name): 

2185 self.name = name 

2186 

2187 def __str__(self): 

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

2189 

2190 def get_pool(self, name): 

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

2192 

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

2194 raise self.NoSuchPool(name) 

2195 

2196 return PagePool(path) 

2197 

2198BlktapControl = __BlktapControl() 

2199 

2200 

2201class PagePool(KObject): 

2202 

2203 def __init__(self, path): 

2204 self.path = path 

2205 self._size = None 

2206 

2207 def sysfs_path(self): 

2208 return self.path 

2209 

2210 class Size(Attribute): 

2211 SYSFS_NODENAME = "size" 

2212 

2213 def get_size_attr(self): 

2214 if not self._size: 

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

2216 return self._size 

2217 

2218 def set_size(self, pages): 

2219 pages = str(pages) 

2220 self.get_size_attr().writeline(pages) 

2221 

2222 def get_size(self): 

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

2224 return int(pages) 

2225 

2226 

2227class BusDevice(KObject): 

2228 

2229 SYSFS_BUSTYPE = None 

2230 

2231 @classmethod 

2232 def sysfs_bus_path(cls): 

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

2234 

2235 def sysfs_path(self): 

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

2237 self.sysfs_devname()) 

2238 

2239 return path 

2240 

2241 

2242class XenbusDevice(BusDevice): 

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

2244 

2245 XBT_NIL = "" 

2246 

2247 XENBUS_DEVTYPE = None 

2248 

2249 def __init__(self, domid, devid): 

2250 self.domid = int(domid) 

2251 self.devid = int(devid) 

2252 self._xbt = XenbusDevice.XBT_NIL 

2253 

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

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

2256 

2257 def xs_path(self, key=None): 

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

2259 self.domid, 

2260 self.devid) 

2261 if key is not None: 

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

2263 

2264 return path 

2265 

2266 def _log(self, prio, msg): 

2267 syslog(prio, msg) 

2268 

2269 def info(self, msg): 

2270 self._log(_syslog.LOG_INFO, msg) 

2271 

2272 def warn(self, msg): 

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

2274 

2275 def _xs_read_path(self, path): 

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

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

2278 return val 

2279 

2280 def _xs_write_path(self, path, val): 

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

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

2283 

2284 def _xs_rm_path(self, path): 

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

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

2287 

2288 def read(self, key): 

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

2290 

2291 def has_xs_key(self, key): 

2292 return self.read(key) is not None 

2293 

2294 def write(self, key, val): 

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

2296 

2297 def rm(self, key): 

2298 self._xs_rm_path(self.xs_path(key)) 

2299 

2300 def exists(self): 

2301 return self.has_xs_key(None) 

2302 

2303 def begin(self): 

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

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

2306 

2307 def commit(self): 

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

2309 self._xbt = XenbusDevice.XBT_NIL 

2310 return ok 

2311 

2312 def abort(self): 

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

2314 assert(ok == True) 

2315 self._xbt = XenbusDevice.XBT_NIL 

2316 

2317 def create_physical_device(self): 

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

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

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

2321 return 

2322 try: 

2323 params = self.read("params") 

2324 frontend = self.read("frontend") 

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

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

2327 # from opening the physical-device 

2328 if not(is_cdrom): 

2329 major_minor = os.stat(params).st_rdev 

2330 major, minor = divmod(major_minor, 256) 

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

2332 except: 

2333 util.logException("BLKTAP2:create_physical_device") 

2334 

2335 def signal_hotplug(self, online=True): 

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

2337 self.XENBUS_DEVTYPE, 

2338 self.devid) 

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

2340 if online: 

2341 self._xs_write_path(xapi_path, "online") 

2342 self._xs_write_path(upstream_path, "connected") 

2343 else: 

2344 self._xs_rm_path(xapi_path) 

2345 self._xs_rm_path(upstream_path) 

2346 

2347 def sysfs_devname(self): 

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

2349 self.domid, self.devid) 

2350 

2351 def __str__(self): 

2352 return self.sysfs_devname() 

2353 

2354 @classmethod 

2355 def find(cls): 

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

2357 cls.XENBUS_DEVTYPE) 

2358 for path in glob.glob(pattern): 

2359 

2360 name = os.path.basename(path) 

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

2362 

2363 yield cls(domid, devid) 

2364 

2365 

2366class XenBackendDevice(XenbusDevice): 

2367 """Xenbus backend device""" 

2368 SYSFS_BUSTYPE = "xen-backend" 

2369 

2370 @classmethod 

2371 def from_xs_path(cls, _path): 

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

2373 

2374 assert _backend == 'backend' 

2375 assert _type == cls.XENBUS_DEVTYPE 

2376 

2377 domid = int(domid) 

2378 devid = int(devid) 

2379 

2380 return cls(domid, devid) 

2381 

2382 

2383class Blkback(XenBackendDevice): 

2384 """A blkback VBD""" 

2385 

2386 XENBUS_DEVTYPE = "vbd" 

2387 

2388 def __init__(self, domid, devid): 

2389 XenBackendDevice.__init__(self, domid, devid) 

2390 self._phy = None 

2391 self._vdi_uuid = None 

2392 self._q_state = None 

2393 self._q_events = None 

2394 

2395 class XenstoreValueError(Exception): 

2396 KEY = None 

2397 

2398 def __init__(self, vbd, _str): 

2399 self.vbd = vbd 

2400 self.str = _str 

2401 

2402 def __str__(self): 

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

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

2405 

2406 class PhysicalDeviceError(XenstoreValueError): 

2407 KEY = "physical-device" 

2408 

2409 class PhysicalDevice(object): 

2410 

2411 def __init__(self, major, minor): 

2412 self.major = int(major) 

2413 self.minor = int(minor) 

2414 

2415 @classmethod 

2416 def from_xbdev(cls, xbdev): 

2417 

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

2419 

2420 try: 

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

2422 major = int(major, 0x10) 

2423 minor = int(minor, 0x10) 

2424 except Exception as e: 

2425 raise xbdev.PhysicalDeviceError(xbdev, phy) 

2426 

2427 return cls(major, minor) 

2428 

2429 def makedev(self): 

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

2431 

2432 def is_tap(self): 

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

2434 

2435 def __str__(self): 

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

2437 

2438 def __eq__(self, other): 

2439 return \ 

2440 self.major == other.major and \ 

2441 self.minor == other.minor 

2442 

2443 def get_physical_device(self): 

2444 if not self._phy: 

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

2446 return self._phy 

2447 

2448 class QueueEvents(Attribute): 

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

2450 notifications emitted.""" 

2451 

2452 SYSFS_NODENAME = "queue_events" 

2453 

2454 QUEUE_RUNNING = (1 << 0) 

2455 QUEUE_PAUSE_DONE = (1 << 1) 

2456 QUEUE_SHUTDOWN_DONE = (1 << 2) 

2457 QUEUE_PAUSE_REQUEST = (1 << 3) 

2458 QUEUE_SHUTDOWN_REQUEST = (1 << 4) 

2459 

2460 def get_mask(self): 

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

2462 

2463 def set_mask(self, mask): 

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

2465 

2466 def get_queue_events(self): 

2467 if not self._q_events: 

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

2469 return self._q_events 

2470 

2471 def get_vdi_uuid(self): 

2472 if not self._vdi_uuid: 

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

2474 return self._vdi_uuid 

2475 

2476 def pause_requested(self): 

2477 return self.has_xs_key("pause") 

2478 

2479 def shutdown_requested(self): 

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

2481 

2482 def shutdown_done(self): 

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

2484 

2485 def running(self): 

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

2487 

2488 @classmethod 

2489 def find_by_physical_device(cls, phy): 

2490 for dev in cls.find(): 

2491 try: 

2492 _phy = dev.get_physical_device() 

2493 except cls.PhysicalDeviceError: 

2494 continue 

2495 

2496 if _phy == phy: 

2497 yield dev 

2498 

2499 @classmethod 

2500 def find_by_tap_minor(cls, minor): 

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

2502 return cls.find_by_physical_device(phy) 

2503 

2504 @classmethod 

2505 def find_by_tap(cls, tapdisk): 

2506 return cls.find_by_tap_minor(tapdisk.minor) 

2507 

2508 def has_tap(self): 

2509 

2510 if not self.can_tap(): 

2511 return False 

2512 

2513 phy = self.get_physical_device() 

2514 if phy: 

2515 return phy.is_tap() 

2516 

2517 return False 

2518 

2519 def is_bare_hvm(self): 

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

2521 try: 

2522 self.get_physical_device() 

2523 

2524 except self.PhysicalDeviceError as e: 

2525 vdi_type = self.read("type") 

2526 

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

2528 

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

2530 raise 

2531 

2532 return True 

2533 

2534 return False 

2535 

2536 def can_tap(self): 

2537 return not self.is_bare_hvm() 

2538 

2539 

2540class BlkbackEventHandler(UEventHandler): 

2541 

2542 LOG_FACILITY = _syslog.LOG_DAEMON 

2543 

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

2545 if not ident: 

2546 ident = self.__class__.__name__ 

2547 

2548 self.ident = ident 

2549 self._vbd = None 

2550 self._tapdisk = None 

2551 

2552 UEventHandler.__init__(self) 

2553 

2554 def run(self): 

2555 

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

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

2558 

2559 UEventHandler.run(self) 

2560 

2561 def __str__(self): 

2562 

2563 try: 

2564 path = self.xs_path 

2565 except: 

2566 path = None 

2567 

2568 try: 

2569 action = self.get_action() 

2570 except: 

2571 action = None 

2572 

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

2574 

2575 def _log(self, prio, msg): 

2576 syslog(prio, msg) 

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

2578 

2579 def info(self, msg): 

2580 self._log(_syslog.LOG_INFO, msg) 

2581 

2582 def warn(self, msg): 

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

2584 

2585 def error(self, msg): 

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

2587 

2588 def get_vbd(self): 

2589 if not self._vbd: 

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

2591 return self._vbd 

2592 

2593 def get_tapdisk(self): 

2594 if not self._tapdisk: 

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

2596 self._tapdisk = Tapdisk.from_minor(minor) 

2597 return self._tapdisk 

2598 # 

2599 # Events 

2600 # 

2601 

2602 def __add(self): 

2603 vbd = self.get_vbd() 

2604 # Manage blkback transitions 

2605 # self._manage_vbd() 

2606 

2607 vbd.create_physical_device() 

2608 

2609 vbd.signal_hotplug() 

2610 

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

2612 def add(self): 

2613 try: 

2614 self.__add() 

2615 except Attribute.NoSuchAttribute as e: 

2616 # 

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

2618 # registers device attributes. So poll a little. 

2619 # 

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

2621 raise RetryLoop.TransientFailure(e) 

2622 

2623 def __change(self): 

2624 vbd = self.get_vbd() 

2625 

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

2627 

2628 if vbd.has_tap(): 

2629 pass 

2630 #self._pause_update_tap() 

2631 

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

2633 

2634 self._signal_xapi() 

2635 

2636 def change(self): 

2637 vbd = self.get_vbd() 

2638 

2639 # NB. Beware of spurious change events between shutdown 

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

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

2642 

2643 while True: 

2644 vbd.begin() 

2645 

2646 if not vbd.exists() or \ 

2647 vbd.shutdown_done(): 

2648 break 

2649 

2650 self.__change() 

2651 

2652 if vbd.commit(): 

2653 return 

2654 

2655 vbd.abort() 

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

2657 

2658 def remove(self): 

2659 vbd = self.get_vbd() 

2660 

2661 vbd.signal_hotplug(False) 

2662 

2663 ACTIONS = {'add': add, 

2664 'change': change, 

2665 'remove': remove} 

2666 # 

2667 # VDI.pause 

2668 # 

2669 

2670 def _tap_should_pause(self): 

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

2672 paused""" 

2673 

2674 tapdisk = self.get_tapdisk() 

2675 TapState = Tapdisk.PauseState 

2676 

2677 PAUSED = 'P' 

2678 RUNNING = 'R' 

2679 PAUSED_SHUTDOWN = 'P,S' 

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

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

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

2683 

2684 next = TapState.RUNNING 

2685 vbds = {} 

2686 

2687 for vbd in Blkback.find_by_tap(tapdisk): 

2688 name = str(vbd) 

2689 

2690 pausing = vbd.pause_requested() 

2691 closing = vbd.shutdown_requested() 

2692 running = vbd.running() 

2693 

2694 if pausing: 

2695 if closing and not running: 

2696 vbds[name] = PAUSED_SHUTDOWN 

2697 else: 

2698 vbds[name] = PAUSED 

2699 next = TapState.PAUSED 

2700 

2701 else: 

2702 vbds[name] = RUNNING 

2703 

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

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

2706 vbds, next)) 

2707 

2708 return next == TapState.PAUSED 

2709 

2710 def _pause_update_tap(self): 

2711 vbd = self.get_vbd() 

2712 

2713 if self._tap_should_pause(): 

2714 self._pause_tap() 

2715 else: 

2716 self._resume_tap() 

2717 

2718 def _pause_tap(self): 

2719 tapdisk = self.get_tapdisk() 

2720 

2721 if not tapdisk.is_paused(): 

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

2723 tapdisk.pause() 

2724 

2725 def _resume_tap(self): 

2726 tapdisk = self.get_tapdisk() 

2727 

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

2729 # type while resuming. 

2730 vbd = self.get_vbd() 

2731 vdi_uuid = vbd.get_vdi_uuid() 

2732 

2733 if tapdisk.is_paused(): 

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

2735 vdi = VDI.from_cli(vdi_uuid) 

2736 _type = vdi.get_tap_type() 

2737 path = vdi.get_phy_path() 

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

2739 tapdisk.unpause(_type, path) 

2740 # 

2741 # VBD.pause/shutdown 

2742 # 

2743 

2744 def _manage_vbd(self): 

2745 vbd = self.get_vbd() 

2746 # NB. Hook into VBD state transitions. 

2747 

2748 events = vbd.get_queue_events() 

2749 

2750 mask = 0 

2751 mask |= events.QUEUE_PAUSE_DONE # pause/unpause 

2752 mask |= events.QUEUE_SHUTDOWN_DONE # shutdown 

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

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

2755 

2756 events.set_mask(mask) 

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

2758 

2759 def _signal_xapi(self): 

2760 vbd = self.get_vbd() 

2761 

2762 pausing = vbd.pause_requested() 

2763 closing = vbd.shutdown_requested() 

2764 running = vbd.running() 

2765 

2766 handled = 0 

2767 

2768 if pausing and not running: 

2769 if 'pause-done' not in vbd: 

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

2771 handled += 1 

2772 

2773 if not pausing: 

2774 if 'pause-done' in vbd: 

2775 vbd.rm('pause-done') 

2776 handled += 1 

2777 

2778 if closing and not running: 

2779 if 'shutdown-done' not in vbd: 

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

2781 handled += 1 

2782 

2783 if handled > 1: 

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

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

2786 (pausing, closing, running)) 

2787 

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

2789 

2790 import sys 

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

2792 

2793 # 

2794 # Simple CLI interface for manual operation 

2795 # 

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

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

2798 # 

2799 

2800 def usage(stream): 

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

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

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

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

2805 

2806 try: 

2807 cmd = sys.argv[1] 

2808 except IndexError: 

2809 usage(sys.stderr) 

2810 sys.exit(1) 

2811 

2812 try: 

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

2814 except: 

2815 usage(sys.stderr) 

2816 sys.exit(1) 

2817 

2818 # 

2819 # Local Tapdisks 

2820 # 

2821 

2822 if cmd == 'tap.major': 

2823 

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

2825 

2826 elif cmd == 'tap.launch': 

2827 

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

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

2830 

2831 elif _class == 'tap': 

2832 

2833 attrs = {} 

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

2835 try: 

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

2837 attrs[key] = val 

2838 continue 

2839 except ValueError: 

2840 pass 

2841 

2842 try: 

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

2844 continue 

2845 except ValueError: 

2846 pass 

2847 

2848 try: 

2849 arg = Tapdisk.Arg.parse(item) 

2850 attrs['_type'] = arg.type 

2851 attrs['path'] = arg.path 

2852 continue 

2853 except Tapdisk.Arg.InvalidArgument: 

2854 pass 

2855 

2856 attrs['path'] = item 

2857 

2858 if cmd == 'tap.list': 

2859 

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

2861 blktap = tapdisk.get_blktap() 

2862 print(tapdisk, end=' ') 

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

2864 (blktap, 

2865 blktap.get_task_pid(), 

2866 blktap.get_pool_name())) 

2867 

2868 elif cmd == 'tap.vbds': 

2869 # Find all Blkback instances for a given tapdisk 

2870 

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

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

2873 for vbd in Blkback.find_by_tap(tapdisk): 

2874 print(vbd, end=' ') 

2875 print() 

2876 

2877 else: 

2878 

2879 if not attrs: 

2880 usage(sys.stderr) 

2881 sys.exit(1) 

2882 

2883 try: 

2884 tapdisk = Tapdisk.get( ** attrs) 

2885 except TypeError: 

2886 usage(sys.stderr) 

2887 sys.exit(1) 

2888 

2889 if cmd == 'tap.shutdown': 

2890 # Shutdown a running tapdisk, or raise 

2891 tapdisk.shutdown() 

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

2893 

2894 elif cmd == 'tap.pause': 

2895 # Pause an unpaused tapdisk, or raise 

2896 tapdisk.pause() 

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

2898 

2899 elif cmd == 'tap.unpause': 

2900 # Unpause a paused tapdisk, or raise 

2901 tapdisk.unpause() 

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

2903 

2904 elif cmd == 'tap.stats': 

2905 # Gather tapdisk status 

2906 stats = tapdisk.stats() 

2907 print("%s:" % tapdisk) 

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

2909 

2910 else: 

2911 usage(sys.stderr) 

2912 sys.exit(1) 

2913 

2914 elif cmd == 'vbd.uevent': 

2915 

2916 hnd = BlkbackEventHandler(cmd) 

2917 

2918 if not sys.stdin.isatty(): 

2919 try: 

2920 hnd.run() 

2921 except Exception as e: 

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

2923 

2924 import traceback 

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

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

2927 for entry in trace: 

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

2929 util.SMlog(line) 

2930 else: 

2931 hnd.run() 

2932 

2933 elif cmd == 'vbd.list': 

2934 

2935 for vbd in Blkback.find(): 

2936 print(vbd, \ 

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

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

2939 

2940 else: 

2941 usage(sys.stderr) 

2942 sys.exit(1)