Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#!/usr/bin/python3 

2# 

3# Copyright (C) Citrix Systems Inc. 

4# 

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

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

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

8# 

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

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

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

12# GNU Lesser General Public License for more details. 

13# 

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

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

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

17# 

18# blktap2: blktap/tapdisk management layer 

19# 

20 

21import os 

22import re 

23import time 

24import copy 

25from lock import Lock 

26import util 

27import xmlrpc.client 

28import http.client 

29import errno 

30import signal 

31import subprocess 

32import syslog as _syslog 

33import glob 

34import json 

35import xs_errors 

36import XenAPI # pylint: disable=import-error 

37import scsiutil 

38from syslog import openlog, syslog 

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

40import nfs 

41 

42import resetvdis 

43import vhdutil 

44import lvhdutil 

45 

46import VDI as sm 

47 

48# For RRDD Plugin Registration 

49from xmlrpc.client import ServerProxy, Transport 

50from socket import socket, AF_UNIX, SOCK_STREAM 

51 

52try: 

53 from linstorvolumemanager import log_drbd_openers 

54 LINSTOR_AVAILABLE = True 

55except ImportError: 

56 LINSTOR_AVAILABLE = False 

57 

58PLUGIN_TAP_PAUSE = "tapdisk-pause" 

59 

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

61 

62NUM_PAGES_PER_RING = 32 * 11 

63MAX_FULL_RINGS = 8 

64POOL_NAME_KEY = "mem-pool" 

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

66 

67ENABLE_MULTIPLE_ATTACH = "/etc/xensource/allow_multiple_vdi_attach" 

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

69 

70 

71def locking(excType, override=True): 

72 def locking2(op): 

73 def wrapper(self, *args): 

74 self.lock.acquire() 

75 try: 

76 try: 

77 ret = op(self, * args) 

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

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

80 msg = str(e) 

81 if isinstance(e, util.CommandException): 

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

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

84 if override: 

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

86 else: 

87 raise 

88 except: 

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

90 raise 

91 finally: 

92 self.lock.release() 92 ↛ exitline 92 didn't except from function 'wrapper', because the raise on line 85 wasn't executed or the raise on line 87 wasn't executed or the raise on line 90 wasn't executed

93 return ret 

94 return wrapper 

95 return locking2 

96 

97 

98class RetryLoop(object): 

99 

100 def __init__(self, backoff, limit): 

101 self.backoff = backoff 

102 self.limit = limit 

103 

104 def __call__(self, f): 

105 

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

107 attempt = 0 

108 

109 while True: 

110 attempt += 1 

111 

112 try: 

113 return f( * __t, ** __d) 

114 

115 except self.TransientFailure as e: 

116 e = e.exception 

117 

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

119 raise e 

120 

121 time.sleep(self.backoff) 

122 

123 return loop 

124 

125 class TransientFailure(Exception): 

126 def __init__(self, exception): 

127 self.exception = exception 

128 

129 

130def retried(**args): 

131 return RetryLoop( ** args) 

132 

133 

134class TapCtl(object): 

135 """Tapdisk IPC utility calls.""" 

136 

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

138 

139 def __init__(self, cmd, p): 

140 self.cmd = cmd 

141 self._p = p 

142 self.stdout = p.stdout 

143 

144 class CommandFailure(Exception): 

145 """TapCtl cmd failure.""" 

146 

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

148 self.cmd = cmd 

149 self.info = info 

150 

151 def __str__(self): 

152 items = self.info.items() 

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

154 for item in items) 

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

156 

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

158 # exception 

159 def __getattr__(self, key): 

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

161 return self.info[key] 

162 return object.__getattribute__(self, key) 

163 

164 @property 

165 def has_status(self): 

166 return 'status' in self.info 

167 

168 @property 

169 def has_signal(self): 

170 return 'signal' in self.info 

171 

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

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

174 def get_error_code(self): 

175 key = 'status' 

176 if key in self.info: 

177 return self.info[key] 

178 else: 

179 return 0 

180 

181 @classmethod 

182 def __mkcmd_real(cls, args): 

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

184 

185 __next_mkcmd = __mkcmd_real 

186 

187 @classmethod 

188 def _mkcmd(cls, args): 

189 

190 __next_mkcmd = cls.__next_mkcmd 

191 cls.__next_mkcmd = cls.__mkcmd_real 

192 

193 return __next_mkcmd(args) 

194 

195 @classmethod 

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

197 """ 

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

199 Raises a TapCtl.CommandFailure if subprocess creation failed. 

200 """ 

201 cmd = cls._mkcmd(args) 

202 

203 if not quiet: 

204 util.SMlog(cmd) 

205 try: 

206 p = subprocess.Popen(cmd, 

207 stdin=subprocess.PIPE, 

208 stdout=subprocess.PIPE, 

209 stderr=subprocess.PIPE, 

210 close_fds=True, 

211 universal_newlines=text_mode) 

212 if input: 

213 p.stdin.write(input) 

214 p.stdin.close() 

215 except OSError as e: 

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

217 

218 return cls(cmd, p) 

219 

220 def _errmsg(self): 

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

222 return "; ".join(output) 

223 

224 def _wait(self, quiet=False): 

225 """ 

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

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

228 """ 

229 status = self._p.wait() 

230 if not quiet: 

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

232 

233 if status == 0: 

234 return 

235 

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

237 'pid': self._p.pid} 

238 

239 if status < 0: 

240 info['signal'] = -status 

241 else: 

242 info['status'] = status 

243 

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

245 

246 @classmethod 

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

248 """ 

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

250 """ 

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

252 text_mode=text_mode) 

253 

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

255 

256 tapctl._wait(quiet) 

257 return output 

258 

259 @staticmethod 

260 def _maybe(opt, parm): 

261 if parm is not None: 

262 return [opt, parm] 

263 return [] 

264 

265 @classmethod 

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

267 args = ["list"] 

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

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

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

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

272 

273 tapctl = cls._call(args, True) 

274 

275 for stdout_line in tapctl.stdout: 

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

277 # confuses this parser 

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

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

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

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

282 row = {} 

283 

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

285 bits = field.split('=') 

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

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

288 

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

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

291 

292 elif key in ('state'): 

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

294 

295 else: 

296 row[key] = val 

297 else: 

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

299 yield row 

300 

301 tapctl._wait(True) 

302 

303 @classmethod 

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

305 def list(cls, **args): 

306 

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

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

309 # be fixed in SM. 

310 

311 try: 

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

313 

314 except cls.CommandFailure as e: 

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

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

317 raise RetryLoop.TransientFailure(e) 

318 raise 

319 

320 @classmethod 

321 def allocate(cls, devpath=None): 

322 args = ["allocate"] 

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

324 return cls._pread(args) 

325 

326 @classmethod 

327 def free(cls, minor): 

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

329 cls._pread(args) 

330 

331 @classmethod 

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

333 def spawn(cls): 

334 args = ["spawn"] 

335 try: 

336 pid = cls._pread(args) 

337 return int(pid) 

338 except cls.CommandFailure as ce: 

339 # intermittent failures to spawn. CA-292268 

340 if ce.status == 1: 

341 raise RetryLoop.TransientFailure(ce) 

342 raise 

343 

344 @classmethod 

345 def attach(cls, pid, minor): 

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

347 cls._pread(args) 

348 

349 @classmethod 

350 def detach(cls, pid, minor): 

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

352 cls._pread(args) 

353 

354 @classmethod 

355 def _load_key(cls, key_hash, vdi_uuid): 

356 import plugins 

357 

358 return plugins.load_key(key_hash, vdi_uuid) 

359 

360 @classmethod 

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

362 params = Tapdisk.Arg(_type, _file) 

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

364 text_mode = True 

365 input = None 

366 if options.get("rdonly"): 

367 args.append('-R') 

368 if options.get("lcache"): 

369 args.append("-r") 

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

371 args.append("-e") 

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

373 if options.get("secondary"): 

374 args.append("-2") 

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

376 if options.get("standby"): 

377 args.append("-s") 

378 if options.get("timeout"): 

379 args.append("-t") 

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

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

382 args.append("-D") 

383 if options.get('cbtlog'): 

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

385 if options.get('key_hash'): 

386 key_hash = options['key_hash'] 

387 vdi_uuid = options['vdi_uuid'] 

388 key = cls._load_key(key_hash, vdi_uuid) 

389 

390 if not key: 

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

392 input = key 

393 text_mode = False 

394 args.append('-E') 

395 

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

397 

398 @classmethod 

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

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

401 if force: 

402 args += ["-f"] 

403 cls._pread(args) 

404 

405 @classmethod 

406 def pause(cls, pid, minor): 

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

408 cls._pread(args) 

409 

410 @classmethod 

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

412 cbtlog=None): 

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

414 if mirror: 

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

416 if _type and _file: 

417 params = Tapdisk.Arg(_type, _file) 

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

419 if cbtlog: 

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

421 cls._pread(args) 

422 

423 @classmethod 

424 def shutdown(cls, pid): 

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

426 os.kill(pid, signal.SIGTERM) 

427 os.waitpid(pid, 0) 

428 

429 @classmethod 

430 def stats(cls, pid, minor): 

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

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

433 

434 @classmethod 

435 def major(cls): 

436 args = ["major"] 

437 major = cls._pread(args) 

438 return int(major) 

439 

440 

441class TapdiskExists(Exception): 

442 """Tapdisk already running.""" 

443 

444 def __init__(self, tapdisk): 

445 self.tapdisk = tapdisk 

446 

447 def __str__(self): 

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

449 

450 

451class TapdiskNotRunning(Exception): 

452 """No such Tapdisk.""" 

453 

454 def __init__(self, **attrs): 

455 self.attrs = attrs 

456 

457 def __str__(self): 

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

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

460 for attr in items) 

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

462 

463 

464class TapdiskNotUnique(Exception): 

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

466 

467 def __init__(self, tapdisks): 

468 self.tapdisks = tapdisks 

469 

470 def __str__(self): 

471 tapdisks = map(str, self.tapdisks) 

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

473 

474 

475class TapdiskFailed(Exception): 

476 """Tapdisk launch failure.""" 

477 

478 def __init__(self, arg, err): 

479 self.arg = arg 

480 self.err = err 

481 

482 def __str__(self): 

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

484 

485 def get_error(self): 

486 return self.err 

487 

488 

489class TapdiskInvalidState(Exception): 

490 """Tapdisk pause/unpause failure""" 

491 

492 def __init__(self, tapdisk): 

493 self.tapdisk = tapdisk 

494 

495 def __str__(self): 

496 return str(self.tapdisk) 

497 

498 

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

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

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

502 assert parent != path 

503 try: 

504 if parent: 

505 mkdirs(parent, mode) 

506 if subdir: 

507 os.mkdir(path, mode) 

508 except OSError as e: 

509 if e.errno != errno.EEXIST: 

510 raise 

511 

512 

513class KObject(object): 

514 

515 SYSFS_CLASSTYPE = None 

516 

517 def sysfs_devname(self): 

518 raise NotImplementedError("sysfs_devname is undefined") 

519 

520 

521class Attribute(object): 

522 

523 SYSFS_NODENAME = None 

524 

525 def __init__(self, path): 

526 self.path = path 

527 

528 @classmethod 

529 def from_kobject(cls, kobj): 

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

531 return cls(path) 

532 

533 class NoSuchAttribute(Exception): 

534 def __init__(self, name): 

535 self.name = name 

536 

537 def __str__(self): 

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

539 

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

541 try: 

542 return open(self.path, mode) 

543 except IOError as e: 

544 if e.errno == errno.ENOENT: 

545 raise self.NoSuchAttribute(self) 

546 raise 

547 

548 def readline(self): 

549 f = self._open('r') 

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

551 f.close() 

552 return s 

553 

554 def writeline(self, val): 

555 f = self._open('w') 

556 f.write(val) 

557 f.close() 

558 

559 

560class ClassDevice(KObject): 

561 

562 @classmethod 

563 def sysfs_class_path(cls): 

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

565 

566 def sysfs_path(self): 

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

568 self.sysfs_devname()) 

569 

570 

571class Blktap(ClassDevice): 

572 

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

574 

575 SYSFS_CLASSTYPE = "blktap2" 

576 

577 def __init__(self, minor): 

578 self.minor = minor 

579 self._pool = None 

580 self._task = None 

581 

582 @classmethod 

583 def allocate(cls): 

584 # FIXME. Should rather go into init. 

585 mkdirs(cls.DEV_BASEDIR) 

586 

587 devname = TapCtl.allocate() 

588 minor = Tapdisk._parse_minor(devname) 

589 return cls(minor) 

590 

591 def free(self): 

592 TapCtl.free(self.minor) 

593 

594 def __str__(self): 

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

596 

597 def sysfs_devname(self): 

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

599 

600 class Pool(Attribute): 

601 SYSFS_NODENAME = "pool" 

602 

603 def get_pool_attr(self): 

604 if not self._pool: 

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

606 return self._pool 

607 

608 def get_pool_name(self): 

609 return self.get_pool_attr().readline() 

610 

611 def set_pool_name(self, name): 

612 self.get_pool_attr().writeline(name) 

613 

614 def set_pool_size(self, pages): 

615 self.get_pool().set_size(pages) 

616 

617 def get_pool(self): 

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

619 

620 def set_pool(self, pool): 

621 self.set_pool_name(pool.name) 

622 

623 class Task(Attribute): 

624 SYSFS_NODENAME = "task" 

625 

626 def get_task_attr(self): 

627 if not self._task: 

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

629 return self._task 

630 

631 def get_task_pid(self): 

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

633 try: 

634 return int(pid) 

635 except ValueError: 

636 return None 

637 

638 def find_tapdisk(self): 

639 pid = self.get_task_pid() 

640 if pid is None: 

641 return None 

642 

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

644 

645 def get_tapdisk(self): 

646 tapdisk = self.find_tapdisk() 

647 if not tapdisk: 

648 raise TapdiskNotRunning(minor=self.minor) 

649 return tapdisk 

650 

651 

652class Tapdisk(object): 

653 

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

655 

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

657 self.pid = pid 

658 self.minor = minor 

659 self.type = _type 

660 self.path = path 

661 self.state = state 

662 self._dirty = False 

663 self._blktap = None 

664 

665 def __str__(self): 

666 state = self.pause_state() 

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

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

669 

670 @classmethod 

671 def list(cls, **args): 

672 

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

674 

675 args = {'pid': None, 

676 'minor': None, 

677 'state': None, 

678 '_type': None, 

679 'path': None} 

680 

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

682 if key in args: 

683 args[key] = val 

684 

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

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

687 args['_type'] = image.type 

688 args['path'] = image.path 

689 

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

691 continue 

692 

693 yield Tapdisk( ** args) 

694 

695 @classmethod 

696 def find(cls, **args): 

697 

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

699 

700 if len(found) > 1: 

701 raise TapdiskNotUnique(found) 

702 

703 if found: 

704 return found[0] 

705 

706 return None 

707 

708 @classmethod 

709 def find_by_path(cls, path): 

710 return cls.find(path=path) 

711 

712 @classmethod 

713 def find_by_minor(cls, minor): 

714 return cls.find(minor=minor) 

715 

716 @classmethod 

717 def get(cls, **attrs): 

718 

719 tapdisk = cls.find( ** attrs) 

720 

721 if not tapdisk: 

722 raise TapdiskNotRunning( ** attrs) 

723 

724 return tapdisk 

725 

726 @classmethod 

727 def from_path(cls, path): 

728 return cls.get(path=path) 

729 

730 @classmethod 

731 def from_minor(cls, minor): 

732 return cls.get(minor=minor) 

733 

734 @classmethod 

735 def __from_blktap(cls, blktap): 

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

737 tapdisk._blktap = blktap 

738 return tapdisk 

739 

740 def get_blktap(self): 

741 if not self._blktap: 

742 self._blktap = Blktap(self.minor) 

743 return self._blktap 

744 

745 class Arg: 

746 

747 def __init__(self, _type, path): 

748 self.type = _type 

749 self.path = path 

750 

751 def __str__(self): 

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

753 

754 @classmethod 

755 def parse(cls, arg): 

756 

757 try: 

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

759 except ValueError: 

760 raise cls.InvalidArgument(arg) 

761 

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

763 raise cls.InvalidType(_type) 

764 

765 return cls(_type, path) 

766 

767 class InvalidType(Exception): 

768 def __init__(self, _type): 

769 self.type = _type 

770 

771 def __str__(self): 

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

773 

774 class InvalidArgument(Exception): 

775 def __init__(self, arg): 

776 self.arg = arg 

777 

778 def __str__(self): 

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

780 

781 def get_arg(self): 

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

783 

784 def get_devpath(self): 

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

786 

787 @classmethod 

788 def launch_from_arg(cls, arg): 

789 arg = cls.Arg.parse(arg) 

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

791 

792 @classmethod 

793 def cgclassify(cls, pid): 

794 

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

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

797 # we have configured in the spec file. 

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

799 try: 

800 util.pread2(cmd) 

801 except util.CommandException as e: 

802 util.logException(e) 

803 

804 @classmethod 

805 def spawn(cls): 

806 return TapCtl.spawn() 

807 

808 @classmethod 

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

810 

811 tapdisk = cls.find_by_path(path) 

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

813 raise TapdiskExists(tapdisk) 

814 

815 minor = blktap.minor 

816 

817 try: 

818 pid = cls.spawn() 

819 cls.cgclassify(pid) 

820 try: 

821 TapCtl.attach(pid, minor) 

822 

823 try: 

824 retry_open = 0 

825 while True: 

826 try: 

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

828 except TapCtl.CommandFailure as e: 

829 err = ( 

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

831 ) or None 

832 if err in (errno.EIO, errno.EROFS, errno.EAGAIN): 

833 if retry_open < 5: 

834 retry_open += 1 

835 time.sleep(1) 

836 continue 

837 if LINSTOR_AVAILABLE and err == errno.EROFS: 

838 log_drbd_openers(path) 

839 break 

840 try: 

841 tapdisk = cls.__from_blktap(blktap) 

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

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

844 return tapdisk 

845 except: 

846 TapCtl.close(pid, minor) 

847 raise 

848 

849 except: 

850 TapCtl.detach(pid, minor) 

851 raise 

852 

853 except: 

854 try: 

855 TapCtl.shutdown(pid) 

856 except: 

857 # Best effort to shutdown 

858 pass 

859 raise 

860 

861 except TapCtl.CommandFailure as ctl: 

862 util.logException(ctl) 

863 if ('/dev/xapi/cd/' in path and 

864 'status' in ctl.info and 

865 ctl.info['status'] == 123): # ENOMEDIUM (No medium found) 

866 raise xs_errors.XenError('TapdiskDriveEmpty') 

867 else: 

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

869 

870 @classmethod 

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

872 blktap = Blktap.allocate() 

873 try: 

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

875 except: 

876 blktap.free() 

877 raise 

878 

879 def shutdown(self, force=False): 

880 

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

882 

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

884 

885 self.get_blktap().free() 

886 

887 def pause(self): 

888 

889 if not self.is_running(): 

890 raise TapdiskInvalidState(self) 

891 

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

893 

894 self._set_dirty() 

895 

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

897 

898 if not self.is_paused(): 

899 raise TapdiskInvalidState(self) 

900 

901 # FIXME: should the arguments be optional? 

902 if _type is None: 

903 _type = self.type 

904 if path is None: 

905 path = self.path 

906 

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

908 cbtlog=cbtlog) 

909 

910 self._set_dirty() 

911 

912 def stats(self): 

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

914 # 

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

916 # 

917 

918 def _set_dirty(self): 

919 self._dirty = True 

920 

921 def _refresh(self, __get): 

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

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

924 

925 def __getattribute__(self, name): 

926 def __get(name): 

927 # NB. avoid(rec(ursion) 

928 return object.__getattribute__(self, name) 

929 

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

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

932 self._refresh(__get) 

933 self._dirty = False 

934 

935 return __get(name) 

936 

937 class PauseState: 

938 RUNNING = 'R' 

939 PAUSING = 'r' 

940 PAUSED = 'P' 

941 

942 class Flags: 

943 DEAD = 0x0001 

944 CLOSED = 0x0002 

945 QUIESCE_REQUESTED = 0x0004 

946 QUIESCED = 0x0008 

947 PAUSE_REQUESTED = 0x0010 

948 PAUSED = 0x0020 

949 SHUTDOWN_REQUESTED = 0x0040 

950 LOCKING = 0x0080 

951 RETRY_NEEDED = 0x0100 

952 LOG_DROPPED = 0x0200 

953 

954 PAUSE_MASK = PAUSE_REQUESTED | PAUSED 

955 

956 def is_paused(self): 

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

958 

959 def is_running(self): 

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

961 

962 def pause_state(self): 

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

964 return self.PauseState.PAUSED 

965 

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

967 return self.PauseState.PAUSING 

968 

969 return self.PauseState.RUNNING 

970 

971 @staticmethod 

972 def _parse_minor(devpath): 

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

974 pattern = re.compile(regex) 

975 groups = pattern.search(devpath) 

976 if not groups: 

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

978 

979 minor = groups.group(2) 

980 return int(minor) 

981 

982 _major = None 

983 

984 @classmethod 

985 def major(cls): 

986 if cls._major: 

987 return cls._major 

988 

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

990 for line in devices: 

991 

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

993 if len(row) != 2: 

994 continue 

995 

996 major, name = row 

997 if name != 'tapdev': 

998 continue 

999 

1000 cls._major = int(major) 

1001 break 

1002 

1003 devices.close() 

1004 return cls._major 

1005 

1006 

1007class VDI(object): 

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

1009 

1010 CONF_KEY_ALLOW_CACHING = "vdi_allow_caching" 

1011 CONF_KEY_MODE_ON_BOOT = "vdi_on_boot" 

1012 CONF_KEY_CACHE_SR = "local_cache_sr" 

1013 CONF_KEY_O_DIRECT = "o_direct" 

1014 LOCK_CACHE_SETUP = "cachesetup" 

1015 

1016 ATTACH_DETACH_RETRY_SECS = 120 

1017 

1018 # number of seconds on top of NFS timeo mount option the tapdisk should 

1019 # wait before reporting errors. This is to allow a retry to succeed in case 

1020 # packets were lost the first time around, which prevented the NFS client 

1021 # from returning before the timeo is reached even if the NFS server did 

1022 # come back earlier 

1023 TAPDISK_TIMEOUT_MARGIN = 30 

1024 

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

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

1027 self._vdi_uuid = uuid 

1028 self._session = target.session 

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

1030 self.__o_direct = None 

1031 self.__o_direct_reason = None 

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

1033 self.tap = None 

1034 

1035 def get_o_direct_capability(self, options): 

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

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

1038 return self.__o_direct, self.__o_direct_reason 

1039 

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

1041 self.__o_direct = True 

1042 self.__o_direct_reason = "LICENSE_RESTRICTION" 

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

1044 self.__o_direct = True 

1045 self.__o_direct_reason = "SR_NOT_SUPPORTED" 

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

1047 util.SMlog(self.target.vdi) 

1048 self.__o_direct = True 

1049 self.__o_direct_reason = "NO_RO_IMAGE" 

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

1051 self.__o_direct = True 

1052 self.__o_direct_reason = "RO_WITH_NO_PARENT" 

1053 elif options.get(self.CONF_KEY_O_DIRECT): 

1054 self.__o_direct = True 

1055 self.__o_direct_reason = "SR_OVERRIDE" 

1056 

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

1058 self.__o_direct = False 

1059 self.__o_direct_reason = "" 

1060 

1061 return self.__o_direct, self.__o_direct_reason 

1062 

1063 @classmethod 

1064 def from_cli(cls, uuid): 

1065 import VDI as sm 

1066 

1067 session = XenAPI.xapi_local() 

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

1069 

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

1071 driver_info = target.sr.srcmd.driver_info 

1072 

1073 session.xenapi.session.logout() 

1074 

1075 return cls(uuid, target, driver_info) 

1076 

1077 @staticmethod 

1078 def _tap_type(vdi_type): 

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

1080 return { 

1081 'raw': 'aio', 

1082 'vhd': 'vhd', 

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

1084 'aio': 'aio', # for LVHD 

1085 'file': 'aio', 

1086 'phy': 'aio' 

1087 }[vdi_type] 

1088 

1089 def get_tap_type(self): 

1090 vdi_type = self.target.get_vdi_type() 

1091 return VDI._tap_type(vdi_type) 

1092 

1093 def get_phy_path(self): 

1094 return self.target.get_vdi_path() 

1095 

1096 class UnexpectedVDIType(Exception): 

1097 

1098 def __init__(self, vdi_type, target): 

1099 self.vdi_type = vdi_type 

1100 self.target = target 

1101 

1102 def __str__(self): 

1103 return \ 

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

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

1106 

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

1108 'raw': 'phy', 

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

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

1111 'file': 'tap', 

1112 'vhd': 'tap'} 

1113 

1114 def tap_wanted(self): 

1115 # 1. Let the target vdi_type decide 

1116 

1117 vdi_type = self.target.get_vdi_type() 

1118 

1119 try: 

1120 plug_type = self.VDI_PLUG_TYPE[vdi_type] 

1121 except KeyError: 

1122 raise self.UnexpectedVDIType(vdi_type, 

1123 self.target.vdi) 

1124 

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

1126 return True 

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

1128 return True 

1129 # 2. Otherwise, there may be more reasons 

1130 # 

1131 # .. TBD 

1132 

1133 return False 

1134 

1135 class TargetDriver: 

1136 """Safe target driver access.""" 

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

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

1139 # try/except would risk breaking compatibility. 

1140 

1141 def __init__(self, vdi, driver_info): 

1142 self.vdi = vdi 

1143 self._caps = driver_info['capabilities'] 

1144 

1145 def has_cap(self, cap): 

1146 """Determine if target has given capability""" 

1147 return cap in self._caps 

1148 

1149 def attach(self, sr_uuid, vdi_uuid): 

1150 #assert self.has_cap("VDI_ATTACH") 

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

1152 

1153 def detach(self, sr_uuid, vdi_uuid): 

1154 #assert self.has_cap("VDI_DETACH") 

1155 self.vdi.detach(sr_uuid, vdi_uuid) 

1156 

1157 def activate(self, sr_uuid, vdi_uuid): 

1158 if self.has_cap("VDI_ACTIVATE"): 

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

1160 

1161 def deactivate(self, sr_uuid, vdi_uuid): 

1162 if self.has_cap("VDI_DEACTIVATE"): 

1163 self.vdi.deactivate(sr_uuid, vdi_uuid) 

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

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

1166 

1167 def get_vdi_type(self): 

1168 _type = self.vdi.vdi_type 

1169 if not _type: 

1170 _type = self.vdi.sr.sr_vditype 

1171 if not _type: 

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

1173 return _type 

1174 

1175 def get_vdi_path(self): 

1176 return self.vdi.path 

1177 

1178 class Link(object): 

1179 """Relink a node under a common name""" 

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

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

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

1183 # relink existing devices under deterministic path names. 

1184 

1185 BASEDIR = None 

1186 

1187 def _mklink(self, target): 

1188 raise NotImplementedError("_mklink is not defined") 

1189 

1190 def _equals(self, target): 

1191 raise NotImplementedError("_equals is not defined") 

1192 

1193 def __init__(self, path): 

1194 self._path = path 

1195 

1196 @classmethod 

1197 def from_name(cls, name): 

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

1199 return cls(path) 

1200 

1201 @classmethod 

1202 def from_uuid(cls, sr_uuid, vdi_uuid): 

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

1204 return cls.from_name(name) 

1205 

1206 def path(self): 

1207 return self._path 

1208 

1209 def stat(self): 

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

1211 

1212 def mklink(self, target): 

1213 

1214 path = self.path() 

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

1216 

1217 mkdirs(os.path.dirname(path)) 

1218 try: 

1219 self._mklink(target) 

1220 except OSError as e: 

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

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

1223 # be seen. 

1224 if e.errno != errno.EEXIST: 

1225 raise 

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

1227 

1228 def unlink(self): 

1229 try: 

1230 os.unlink(self.path()) 

1231 except OSError as e: 

1232 if e.errno != errno.ENOENT: 

1233 raise 

1234 

1235 def __str__(self): 

1236 path = self.path() 

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

1238 

1239 class SymLink(Link): 

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

1241 

1242 def readlink(self): 

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

1244 

1245 def symlink(self): 

1246 return self.path() 

1247 

1248 def _mklink(self, target): 

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

1250 

1251 def _equals(self, target): 

1252 return self.readlink() == target 

1253 

1254 class DeviceNode(Link): 

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

1256 

1257 @classmethod 

1258 def _real_stat(cls, target): 

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

1260 _target = os.path.realpath(target) 

1261 return os.stat(_target) 

1262 

1263 @classmethod 

1264 def is_block(cls, target): 

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

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

1267 

1268 def _mklink(self, target): 

1269 

1270 st = self._real_stat(target) 

1271 if not S_ISBLK(st.st_mode): 

1272 raise self.NotABlockDevice(target, st) 

1273 

1274 os.mknod(self.path(), st.st_mode, st.st_rdev) 

1275 

1276 def _equals(self, target): 

1277 target_rdev = self._real_stat(target).st_rdev 

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

1279 

1280 def rdev(self): 

1281 st = self.stat() 

1282 assert S_ISBLK(st.st_mode) 

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

1284 

1285 class NotABlockDevice(Exception): 

1286 

1287 def __init__(self, path, st): 

1288 self.path = path 

1289 self.st = st 

1290 

1291 def __str__(self): 

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

1293 

1294 class Hybrid(Link): 

1295 

1296 def __init__(self, path): 

1297 VDI.Link.__init__(self, path) 

1298 self._devnode = VDI.DeviceNode(path) 

1299 self._symlink = VDI.SymLink(path) 

1300 

1301 def rdev(self): 

1302 st = self.stat() 

1303 if S_ISBLK(st.st_mode): 

1304 return self._devnode.rdev() 

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

1306 

1307 def mklink(self, target): 

1308 if self._devnode.is_block(target): 

1309 self._obj = self._devnode 

1310 else: 

1311 self._obj = self._symlink 

1312 self._obj.mklink(target) 

1313 

1314 def _equals(self, target): 

1315 return self._obj._equals(target) 

1316 

1317 class PhyLink(SymLink): 

1318 BASEDIR = "/dev/sm/phy" 

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

1320 

1321 class NBDLink(SymLink): 

1322 

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

1324 

1325 class BackendLink(Hybrid): 

1326 BASEDIR = "/dev/sm/backend" 

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

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

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

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

1331 # soon as ISOs are tapdisks. 

1332 

1333 @staticmethod 

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

1335 

1336 tapdisk = Tapdisk.find_by_path(phy_path) 

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

1338 blktap = Blktap.allocate() 

1339 blktap.set_pool_name(sr_uuid) 

1340 if pool_size: 

1341 blktap.set_pool_size(pool_size) 

1342 

1343 try: 

1344 tapdisk = \ 

1345 Tapdisk.launch_on_tap(blktap, 

1346 phy_path, 

1347 VDI._tap_type(vdi_type), 

1348 options) 

1349 except: 

1350 blktap.free() 

1351 raise 

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

1353 

1354 else: 

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

1356 

1357 return tapdisk.get_devpath(), tapdisk 

1358 

1359 @staticmethod 

1360 def _tap_deactivate(minor): 

1361 

1362 try: 

1363 tapdisk = Tapdisk.from_minor(minor) 

1364 except TapdiskNotRunning as e: 

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

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

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

1368 # the recorded minor. 

1369 else: 

1370 tapdisk.shutdown() 

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

1372 

1373 @classmethod 

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

1375 """ 

1376 Pauses the tapdisk. 

1377 

1378 session: a XAPI session 

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

1380 vdi_uuid: the UUID of the VDI to pause 

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

1382 non-blocking manner 

1383 """ 

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

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

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

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

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

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

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

1391 if not cls.call_pluginhandler(session, host_ref, 

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

1393 # Failed to pause node 

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

1395 return False 

1396 return True 

1397 

1398 @classmethod 

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

1400 activate_parents=False): 

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

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

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

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

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

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

1407 if not cls.call_pluginhandler(session, host_ref, 

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

1409 # Failed to unpause node 

1410 return False 

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

1412 return True 

1413 

1414 @classmethod 

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

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

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

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

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

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

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

1422 if not cls.call_pluginhandler(session, host_ref, 

1423 sr_uuid, vdi_uuid, "refresh", None, 

1424 activate_parents=activate_parents): 

1425 # Failed to refresh node 

1426 return False 

1427 return True 

1428 

1429 @classmethod 

1430 def tap_status(cls, session, vdi_uuid): 

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

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

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

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

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

1436 return True 

1437 return False 

1438 

1439 @classmethod 

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

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

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

1443 try: 

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

1445 "failfast": str(failfast)} 

1446 if secondary: 

1447 args["secondary"] = secondary 

1448 if activate_parents: 

1449 args["activate_parents"] = "true" 

1450 ret = session.xenapi.host.call_plugin( 

1451 host_ref, PLUGIN_TAP_PAUSE, action, 

1452 args) 

1453 return ret == "True" 

1454 except Exception as e: 

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

1456 return False 

1457 

1458 def _add_tag(self, vdi_uuid, writable): 

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

1460 attach_mode = "RO" 

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

1462 attach_mode = "RW" 

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

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

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

1466 attached_as = util.attached_as(sm_config) 

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

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

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

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

1471 term_output=False, writable=writable): 

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

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

1474 if 'relinking' in sm_config: 

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

1476 return False 

1477 if 'paused' in sm_config: 

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

1479 return False 

1480 self._session.xenapi.VDI.add_to_sm_config( 

1481 vdi_ref, 'activating', 'True') 

1482 host_key = "host_%s" % host_ref 

1483 assert host_key not in sm_config 

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

1485 attach_mode) 

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

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

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

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

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

1491 self._session.xenapi.VDI.remove_from_sm_config( 

1492 vdi_ref, 'activating') 

1493 return False 

1494 util.SMlog("Activate lock succeeded") 

1495 return True 

1496 

1497 def _check_tag(self, vdi_uuid): 

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

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

1500 if 'paused' in sm_config: 

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

1502 return False 

1503 return True 

1504 

1505 def _remove_tag(self, vdi_uuid): 

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

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

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

1509 host_key = "host_%s" % host_ref 

1510 if host_key in sm_config: 

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

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

1513 else: 

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

1515 

1516 def _get_pool_config(self, pool_name): 

1517 pool_info = dict() 

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

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

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

1521 # special pool 

1522 return pool_info 

1523 

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

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

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

1527 pool_size_str = sr_config.get(POOL_SIZE_KEY) 

1528 pool_name_override = vdi_config.get(POOL_NAME_KEY) 

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

1530 pool_name = pool_name_override 

1531 pool_size_override = vdi_config.get(POOL_SIZE_KEY) 

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

1533 pool_size_str = pool_size_override 

1534 pool_size = 0 

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

1536 try: 

1537 pool_size = int(pool_size_str) 

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

1539 raise ValueError("outside of range") 

1540 pool_size = NUM_PAGES_PER_RING * pool_size 

1541 except ValueError: 

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

1543 pool_size = 0 

1544 

1545 pool_info["mem-pool"] = pool_name 

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

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

1548 

1549 return pool_info 

1550 

1551 def linkNBD(self, sr_uuid, vdi_uuid): 

1552 if self.tap: 

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

1554 int(self.tap.minor)) 

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

1556 

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

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

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

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

1561 util.SMlog("Attach & activate") 

1562 self._attach(sr_uuid, vdi_uuid) 

1563 dev_path = self._activate(sr_uuid, vdi_uuid, 

1564 {"rdonly": not writable}) 

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

1566 self.linkNBD(sr_uuid, vdi_uuid) 

1567 

1568 # Return backend/ link 

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

1570 if self.tap_wanted(): 

1571 # Only have NBD if we also have a tap 

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

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

1574 vdi_uuid) 

1575 else: 

1576 nbd_path = "" 

1577 

1578 options = {"rdonly": not writable} 

1579 options.update(caching_params) 

1580 o_direct, o_direct_reason = self.get_o_direct_capability(options) 

1581 struct = {'params': back_path, 

1582 'params_nbd': nbd_path, 

1583 'o_direct': o_direct, 

1584 'o_direct_reason': o_direct_reason, 

1585 'xenstore_data': self.xenstore_data} 

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

1587 

1588 try: 

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

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

1591 f.close() 

1592 except: 

1593 pass 

1594 

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

1596 

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

1598 util.SMlog("blktap2.activate") 

1599 options = {"rdonly": not writable} 

1600 options.update(caching_params) 

1601 

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

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

1604 timeout = nfs.get_nfs_timeout(sr_other_config) 

1605 if timeout: 1605 ↛ 1609line 1605 didn't jump to line 1609, because the condition on line 1605 was never false

1606 # Note NFS timeout values are in deciseconds 

1607 timeout = int((timeout + 5) / 10) 

1608 options["timeout"] = timeout + self.TAPDISK_TIMEOUT_MARGIN 

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

1610 try: 

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

1612 return 

1613 except util.SRBusyException: 

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

1615 time.sleep(1) 

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

1617 

1618 @locking("VDIUnavailable") 

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

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

1621 

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

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

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

1625 return False 

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

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

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

1629 # object completely 

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

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

1632 target.sr.srcmd.params = params 

1633 driver_info = target.sr.srcmd.driver_info 

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

1635 

1636 try: 

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

1638 "blktap_activate_inject_failure", 

1639 lambda: util.inject_failure()) 

1640 

1641 # Attach the physical node 

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

1643 self._attach(sr_uuid, vdi_uuid) 

1644 

1645 vdi_type = self.target.get_vdi_type() 

1646 

1647 # Take lvchange-p Lock before running 

1648 # tap-ctl open 

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

1650 # now taking the same lock 

1651 # This is a fix for CA-155766 

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

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

1654 vdi_type == vhdutil.VDI_TYPE_VHD: 

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

1656 lock.acquire() 

1657 

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

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

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

1661 session = self.target.vdi.session 

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

1663 # pylint: disable=used-before-assignment 

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

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

1666 key_hash = sm_config['key_hash'] 

1667 options['key_hash'] = key_hash 

1668 options['vdi_uuid'] = vdi_uuid 

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

1670 # Activate the physical node 

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

1672 

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

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

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

1676 lock.release() 

1677 except: 

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

1679 if self.tap_wanted(): 

1680 util.fistpoint.activate_custom_fn( 

1681 "blktap_activate_error_handling", 

1682 lambda: time.sleep(30)) 

1683 while True: 

1684 try: 

1685 self._remove_tag(vdi_uuid) 

1686 break 

1687 except xmlrpc.client.ProtocolError as e: 

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

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

1690 continue 

1691 else: 

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

1693 break 

1694 except Exception as e: 

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

1696 break 

1697 raise 

1698 finally: 

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

1700 self._session.xenapi.VDI.remove_from_sm_config( 

1701 vdi_ref, 'activating') 

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

1703 

1704 # Link result to backend/ 

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

1706 self.linkNBD(sr_uuid, vdi_uuid) 

1707 return True 

1708 

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

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

1711 

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

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

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

1715 # Maybe launch a tapdisk on the physical link 

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

1717 vdi_type = self.target.get_vdi_type() 

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

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

1720 options.update(vdi_options) 

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

1722 sr_uuid, options, 

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

1724 else: 

1725 dev_path = phy_path # Just reuse phy 

1726 

1727 return dev_path 

1728 

1729 def _attach(self, sr_uuid, vdi_uuid): 

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

1731 params = attach_info['params'] 

1732 xenstore_data = attach_info['xenstore_data'] 

1733 phy_path = util.to_plain_string(params) 

1734 self.xenstore_data.update(xenstore_data) 

1735 # Save it to phy/ 

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

1737 

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

1739 util.SMlog("blktap2.deactivate") 

1740 for i in range(self.ATTACH_DETACH_RETRY_SECS): 

1741 try: 

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

1743 return 

1744 except util.SRBusyException as e: 

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

1746 time.sleep(1) 

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

1748 

1749 @locking("VDIUnavailable") 

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

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

1752 

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

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

1755 return False 

1756 

1757 self._deactivate(sr_uuid, vdi_uuid, caching_params) 

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

1759 self._detach(sr_uuid, vdi_uuid) 

1760 if self.tap_wanted(): 

1761 self._remove_tag(vdi_uuid) 

1762 

1763 return True 

1764 

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

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

1767 

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

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

1770 util.SMlog("Deactivate & detach") 

1771 self._deactivate(sr_uuid, vdi_uuid, caching_params) 

1772 self._detach(sr_uuid, vdi_uuid) 

1773 else: 

1774 pass # nothing to do 

1775 

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

1777 import VDI as sm 

1778 

1779 # Shutdown tapdisk 

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

1781 

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

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

1784 return 

1785 

1786 try: 

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

1788 os.unlink(attach_info_path) 

1789 except: 

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

1791 

1792 try: 

1793 major, minor = back_link.rdev() 

1794 except self.DeviceNode.NotABlockDevice: 

1795 pass 

1796 else: 

1797 if major == Tapdisk.major(): 

1798 self._tap_deactivate(minor) 

1799 self.remove_cache(sr_uuid, vdi_uuid, caching_params) 

1800 

1801 # Remove the backend link 

1802 back_link.unlink() 

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

1804 

1805 # Deactivate & detach the physical node 

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

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

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

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

1810 # object completely 

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

1812 driver_info = target.sr.srcmd.driver_info 

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

1814 

1815 self.target.deactivate(sr_uuid, vdi_uuid) 

1816 

1817 def _detach(self, sr_uuid, vdi_uuid): 

1818 self.target.detach(sr_uuid, vdi_uuid) 

1819 

1820 # Remove phy/ 

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

1822 

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

1824 # Remove existing VDI.sm_config fields 

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

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

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

1828 if not on_boot is None: 

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

1830 if not caching is None: 

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

1832 

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

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

1835 return 

1836 

1837 util.SMlog("Requested local caching") 

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

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

1840 return 

1841 

1842 scratch_mode = False 

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

1844 scratch_mode = True 

1845 util.SMlog("Requested scratch mode") 

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

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

1848 return 

1849 

1850 dev_path = None 

1851 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR) 

1852 if not local_sr_uuid: 

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

1854 return 

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

1856 local_sr_uuid, scratch_mode, params) 

1857 

1858 if dev_path: 

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

1860 params.get(self.CONF_KEY_MODE_ON_BOOT), 

1861 params.get(self.CONF_KEY_ALLOW_CACHING)) 

1862 

1863 return dev_path 

1864 

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

1866 vm_uuid = None 

1867 vm_label = "" 

1868 try: 

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

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

1871 cache_sr_label = cache_sr_rec.get("name_label") 

1872 

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

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

1875 host_label = host_rec.get("name_label") 

1876 

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

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

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

1880 for vbd_rec in vbds.values(): 

1881 vm_ref = vbd_rec.get("VM") 

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

1883 vm_uuid = vm_rec.get("uuid") 

1884 vm_label = vm_rec.get("name_label") 

1885 except: 

1886 util.logException("alert_no_cache") 

1887 

1888 alert_obj = "SR" 

1889 alert_uuid = str(cache_sr_uuid) 

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

1891 if vm_uuid: 

1892 alert_obj = "VM" 

1893 alert_uuid = vm_uuid 

1894 reason = "" 

1895 if err == errno.ENOSPC: 

1896 reason = "because there is no space left" 

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

1898 (vm_label, reason, cache_sr_label, host_label) 

1899 

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

1901 (alert_obj, alert_uuid, alert_str)) 

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

1903 alert_obj, alert_uuid, alert_str) 

1904 

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

1906 scratch_mode, options): 

1907 import SR 

1908 import EXTSR 

1909 import NFSSR 

1910 from lock import Lock 

1911 from FileSR import FileVDI 

1912 

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

1914 FileVDI.extractUuid) 

1915 if not parent_uuid: 

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

1917 self.target.vdi.uuid) 

1918 return 

1919 

1920 util.SMlog("Setting up cache") 

1921 parent_uuid = parent_uuid.strip() 

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

1923 

1924 if shared_target.parent: 

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

1926 shared_target.uuid) 

1927 return 

1928 

1929 SR.registerSR(EXTSR.EXTSR) 

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

1931 

1932 lock = Lock(self.LOCK_CACHE_SETUP, parent_uuid) 

1933 lock.acquire() 

1934 

1935 # read cache 

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

1937 if util.pathexists(read_cache_path): 

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

1939 read_cache_path) 

1940 else: 

1941 try: 

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

1943 except util.CommandException as e: 

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

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

1946 return None 

1947 

1948 # local write node 

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

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

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

1952 if util.pathexists(local_leaf_path): 

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

1954 local_leaf_path) 

1955 os.unlink(local_leaf_path) 

1956 try: 

1957 vhdutil.snapshot(local_leaf_path, read_cache_path, False, 

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

1959 except util.CommandException as e: 

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

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

1962 return None 

1963 

1964 local_leaf_size = vhdutil.getSizeVirt(local_leaf_path) 

1965 if leaf_size > local_leaf_size: 

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

1967 (leaf_size, local_leaf_size)) 

1968 vhdutil.setSizeVirtFast(local_leaf_path, leaf_size) 

1969 

1970 vdi_type = self.target.get_vdi_type() 

1971 

1972 prt_tapdisk = Tapdisk.find_by_path(read_cache_path) 

1973 if not prt_tapdisk: 

1974 parent_options = copy.deepcopy(options) 

1975 parent_options["rdonly"] = False 

1976 parent_options["lcache"] = True 

1977 

1978 blktap = Blktap.allocate() 

1979 try: 

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

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

1982 # its own pool 

1983 prt_tapdisk = \ 

1984 Tapdisk.launch_on_tap(blktap, read_cache_path, 

1985 'vhd', parent_options) 

1986 except: 

1987 blktap.free() 

1988 raise 

1989 

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

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

1992 

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

1994 leaf_tapdisk = Tapdisk.find_by_path(local_leaf_path) 

1995 if not leaf_tapdisk: 

1996 blktap = Blktap.allocate() 

1997 child_options = copy.deepcopy(options) 

1998 child_options["rdonly"] = False 

1999 child_options["lcache"] = False 

2000 child_options["existing_prt"] = prt_tapdisk.minor 

2001 child_options["secondary"] = secondary 

2002 child_options["standby"] = scratch_mode 

2003 try: 

2004 leaf_tapdisk = \ 

2005 Tapdisk.launch_on_tap(blktap, local_leaf_path, 

2006 'vhd', child_options) 

2007 except: 

2008 blktap.free() 

2009 raise 

2010 

2011 lock.release() 

2012 

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

2014 (read_cache_path, local_leaf_path)) 

2015 

2016 self.tap = leaf_tapdisk 

2017 return leaf_tapdisk.get_devpath() 

2018 

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

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

2021 return 

2022 

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

2024 

2025 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR) 

2026 if caching and not local_sr_uuid: 

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

2028 return 

2029 

2030 if caching: 

2031 self._remove_cache(self._session, local_sr_uuid) 

2032 

2033 if self._session is not None: 

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

2035 

2036 def _is_tapdisk_in_use(self, minor): 

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

2038 if not retVal: 

2039 # err on the side of caution 

2040 return True 

2041 

2042 for link in links: 

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

2044 return True 

2045 

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

2047 for s in sockets: 

2048 if socket_re.match(s): 

2049 return True 

2050 

2051 return False 

2052 

2053 def _remove_cache(self, session, local_sr_uuid): 

2054 import SR 

2055 import EXTSR 

2056 import NFSSR 

2057 from lock import Lock 

2058 from FileSR import FileVDI 

2059 

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

2061 FileVDI.extractUuid) 

2062 if not parent_uuid: 

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

2064 self.target.vdi.uuid) 

2065 return 

2066 

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

2068 

2069 parent_uuid = parent_uuid.strip() 

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

2071 

2072 SR.registerSR(EXTSR.EXTSR) 

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

2074 

2075 lock = Lock(self.LOCK_CACHE_SETUP, parent_uuid) 

2076 lock.acquire() 

2077 

2078 # local write node 

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

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

2081 if util.pathexists(local_leaf_path): 

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

2083 os.unlink(local_leaf_path) 

2084 

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

2086 prt_tapdisk = Tapdisk.find_by_path(read_cache_path) 

2087 if not prt_tapdisk: 

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

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

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

2091 read_cache_path) 

2092 try: 

2093 prt_tapdisk.shutdown() 

2094 except: 

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

2096 else: 

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

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

2099 # GC run 

2100 

2101 lock.release() 

2102 

2103PythonKeyError = KeyError 

2104 

2105 

2106class UEventHandler(object): 

2107 

2108 def __init__(self): 

2109 self._action = None 

2110 

2111 class KeyError(PythonKeyError): 

2112 def __init__(self, args): 

2113 super().__init__(args) 

2114 self.key = args[0] 

2115 

2116 def __str__(self): 

2117 return \ 

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

2119 "Not called in udev context?" 

2120 

2121 @classmethod 

2122 def getenv(cls, key): 

2123 try: 

2124 return os.environ[key] 

2125 except KeyError as e: 

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

2127 

2128 def get_action(self): 

2129 if not self._action: 

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

2131 return self._action 

2132 

2133 class UnhandledEvent(Exception): 

2134 

2135 def __init__(self, event, handler): 

2136 self.event = event 

2137 self.handler = handler 

2138 

2139 def __str__(self): 

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

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

2142 

2143 ACTIONS = {} 

2144 

2145 def run(self): 

2146 

2147 action = self.get_action() 

2148 try: 

2149 fn = self.ACTIONS[action] 

2150 except KeyError: 

2151 raise self.UnhandledEvent(action, self) 

2152 

2153 return fn(self) 

2154 

2155 def __str__(self): 

2156 try: 

2157 action = self.get_action() 

2158 except: 

2159 action = None 

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

2161 

2162 

2163class __BlktapControl(ClassDevice): 

2164 SYSFS_CLASSTYPE = "misc" 

2165 

2166 def __init__(self): 

2167 ClassDevice.__init__(self) 

2168 self._default_pool = None 

2169 

2170 def sysfs_devname(self): 

2171 return "blktap!control" 

2172 

2173 class DefaultPool(Attribute): 

2174 SYSFS_NODENAME = "default_pool" 

2175 

2176 def get_default_pool_attr(self): 

2177 if not self._default_pool: 

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

2179 return self._default_pool 

2180 

2181 def get_default_pool_name(self): 

2182 return self.get_default_pool_attr().readline() 

2183 

2184 def set_default_pool_name(self, name): 

2185 self.get_default_pool_attr().writeline(name) 

2186 

2187 def get_default_pool(self): 

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

2189 

2190 def set_default_pool(self, pool): 

2191 self.set_default_pool_name(pool.name) 

2192 

2193 class NoSuchPool(Exception): 

2194 def __init__(self, name): 

2195 self.name = name 

2196 

2197 def __str__(self): 

2198 return "No such pool: %s", self.name 

2199 

2200 def get_pool(self, name): 

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

2202 

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

2204 raise self.NoSuchPool(name) 

2205 

2206 return PagePool(path) 

2207 

2208BlktapControl = __BlktapControl() 

2209 

2210 

2211class PagePool(KObject): 

2212 

2213 def __init__(self, path): 

2214 self.path = path 

2215 self._size = None 

2216 

2217 def sysfs_path(self): 

2218 return self.path 

2219 

2220 class Size(Attribute): 

2221 SYSFS_NODENAME = "size" 

2222 

2223 def get_size_attr(self): 

2224 if not self._size: 

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

2226 return self._size 

2227 

2228 def set_size(self, pages): 

2229 pages = str(pages) 

2230 self.get_size_attr().writeline(pages) 

2231 

2232 def get_size(self): 

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

2234 return int(pages) 

2235 

2236 

2237class BusDevice(KObject): 

2238 

2239 SYSFS_BUSTYPE = None 

2240 

2241 @classmethod 

2242 def sysfs_bus_path(cls): 

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

2244 

2245 def sysfs_path(self): 

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

2247 self.sysfs_devname()) 

2248 

2249 return path 

2250 

2251 

2252class XenbusDevice(BusDevice): 

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

2254 

2255 XBT_NIL = "" 

2256 

2257 XENBUS_DEVTYPE = None 

2258 

2259 def __init__(self, domid, devid): 

2260 self.domid = int(domid) 

2261 self.devid = int(devid) 

2262 self._xbt = XenbusDevice.XBT_NIL 

2263 

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

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

2266 

2267 def xs_path(self, key=None): 

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

2269 self.domid, 

2270 self.devid) 

2271 if key is not None: 

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

2273 

2274 return path 

2275 

2276 def _log(self, prio, msg): 

2277 syslog(prio, msg) 

2278 

2279 def info(self, msg): 

2280 self._log(_syslog.LOG_INFO, msg) 

2281 

2282 def warn(self, msg): 

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

2284 

2285 def _xs_read_path(self, path): 

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

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

2288 return val 

2289 

2290 def _xs_write_path(self, path, val): 

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

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

2293 

2294 def _xs_rm_path(self, path): 

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

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

2297 

2298 def read(self, key): 

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

2300 

2301 def has_xs_key(self, key): 

2302 return self.read(key) is not None 

2303 

2304 def write(self, key, val): 

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

2306 

2307 def rm(self, key): 

2308 self._xs_rm_path(self.xs_path(key)) 

2309 

2310 def exists(self): 

2311 return self.has_xs_key(None) 

2312 

2313 def begin(self): 

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

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

2316 

2317 def commit(self): 

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

2319 self._xbt = XenbusDevice.XBT_NIL 

2320 return ok 

2321 

2322 def abort(self): 

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

2324 assert(ok == True) 

2325 self._xbt = XenbusDevice.XBT_NIL 

2326 

2327 def create_physical_device(self): 

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

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

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

2331 return 

2332 try: 

2333 params = self.read("params") 

2334 frontend = self.read("frontend") 

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

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

2337 # from opening the physical-device 

2338 if not(is_cdrom): 

2339 major_minor = os.stat(params).st_rdev 

2340 major, minor = divmod(major_minor, 256) 

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

2342 except: 

2343 util.logException("BLKTAP2:create_physical_device") 

2344 

2345 def signal_hotplug(self, online=True): 

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

2347 self.XENBUS_DEVTYPE, 

2348 self.devid) 

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

2350 if online: 

2351 self._xs_write_path(xapi_path, "online") 

2352 self._xs_write_path(upstream_path, "connected") 

2353 else: 

2354 self._xs_rm_path(xapi_path) 

2355 self._xs_rm_path(upstream_path) 

2356 

2357 def sysfs_devname(self): 

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

2359 self.domid, self.devid) 

2360 

2361 def __str__(self): 

2362 return self.sysfs_devname() 

2363 

2364 @classmethod 

2365 def find(cls): 

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

2367 cls.XENBUS_DEVTYPE) 

2368 for path in glob.glob(pattern): 

2369 

2370 name = os.path.basename(path) 

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

2372 

2373 yield cls(domid, devid) 

2374 

2375 

2376class XenBackendDevice(XenbusDevice): 

2377 """Xenbus backend device""" 

2378 SYSFS_BUSTYPE = "xen-backend" 

2379 

2380 @classmethod 

2381 def from_xs_path(cls, _path): 

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

2383 

2384 assert _backend == 'backend' 

2385 assert _type == cls.XENBUS_DEVTYPE 

2386 

2387 domid = int(domid) 

2388 devid = int(devid) 

2389 

2390 return cls(domid, devid) 

2391 

2392 

2393class Blkback(XenBackendDevice): 

2394 """A blkback VBD""" 

2395 

2396 XENBUS_DEVTYPE = "vbd" 

2397 

2398 def __init__(self, domid, devid): 

2399 XenBackendDevice.__init__(self, domid, devid) 

2400 self._phy = None 

2401 self._vdi_uuid = None 

2402 self._q_state = None 

2403 self._q_events = None 

2404 

2405 class XenstoreValueError(Exception): 

2406 KEY = None 

2407 

2408 def __init__(self, vbd, _str): 

2409 self.vbd = vbd 

2410 self.str = _str 

2411 

2412 def __str__(self): 

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

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

2415 

2416 class PhysicalDeviceError(XenstoreValueError): 

2417 KEY = "physical-device" 

2418 

2419 class PhysicalDevice(object): 

2420 

2421 def __init__(self, major, minor): 

2422 self.major = int(major) 

2423 self.minor = int(minor) 

2424 

2425 @classmethod 

2426 def from_xbdev(cls, xbdev): 

2427 

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

2429 

2430 try: 

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

2432 major = int(major, 0x10) 

2433 minor = int(minor, 0x10) 

2434 except Exception as e: 

2435 raise xbdev.PhysicalDeviceError(xbdev, phy) 

2436 

2437 return cls(major, minor) 

2438 

2439 def makedev(self): 

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

2441 

2442 def is_tap(self): 

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

2444 

2445 def __str__(self): 

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

2447 

2448 def __eq__(self, other): 

2449 return \ 

2450 self.major == other.major and \ 

2451 self.minor == other.minor 

2452 

2453 def get_physical_device(self): 

2454 if not self._phy: 

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

2456 return self._phy 

2457 

2458 class QueueEvents(Attribute): 

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

2460 notifications emitted.""" 

2461 

2462 SYSFS_NODENAME = "queue_events" 

2463 

2464 QUEUE_RUNNING = (1 << 0) 

2465 QUEUE_PAUSE_DONE = (1 << 1) 

2466 QUEUE_SHUTDOWN_DONE = (1 << 2) 

2467 QUEUE_PAUSE_REQUEST = (1 << 3) 

2468 QUEUE_SHUTDOWN_REQUEST = (1 << 4) 

2469 

2470 def get_mask(self): 

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

2472 

2473 def set_mask(self, mask): 

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

2475 

2476 def get_queue_events(self): 

2477 if not self._q_events: 

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

2479 return self._q_events 

2480 

2481 def get_vdi_uuid(self): 

2482 if not self._vdi_uuid: 

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

2484 return self._vdi_uuid 

2485 

2486 def pause_requested(self): 

2487 return self.has_xs_key("pause") 

2488 

2489 def shutdown_requested(self): 

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

2491 

2492 def shutdown_done(self): 

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

2494 

2495 def running(self): 

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

2497 

2498 @classmethod 

2499 def find_by_physical_device(cls, phy): 

2500 for dev in cls.find(): 

2501 try: 

2502 _phy = dev.get_physical_device() 

2503 except cls.PhysicalDeviceError: 

2504 continue 

2505 

2506 if _phy == phy: 

2507 yield dev 

2508 

2509 @classmethod 

2510 def find_by_tap_minor(cls, minor): 

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

2512 return cls.find_by_physical_device(phy) 

2513 

2514 @classmethod 

2515 def find_by_tap(cls, tapdisk): 

2516 return cls.find_by_tap_minor(tapdisk.minor) 

2517 

2518 def has_tap(self): 

2519 

2520 if not self.can_tap(): 

2521 return False 

2522 

2523 phy = self.get_physical_device() 

2524 if phy: 

2525 return phy.is_tap() 

2526 

2527 return False 

2528 

2529 def is_bare_hvm(self): 

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

2531 try: 

2532 self.get_physical_device() 

2533 

2534 except self.PhysicalDeviceError as e: 

2535 vdi_type = self.read("type") 

2536 

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

2538 

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

2540 raise 

2541 

2542 return True 

2543 

2544 return False 

2545 

2546 def can_tap(self): 

2547 return not self.is_bare_hvm() 

2548 

2549 

2550class BlkbackEventHandler(UEventHandler): 

2551 

2552 LOG_FACILITY = _syslog.LOG_DAEMON 

2553 

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

2555 if not ident: 

2556 ident = self.__class__.__name__ 

2557 

2558 self.ident = ident 

2559 self._vbd = None 

2560 self._tapdisk = None 

2561 

2562 UEventHandler.__init__(self) 

2563 

2564 def run(self): 

2565 

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

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

2568 

2569 UEventHandler.run(self) 

2570 

2571 def __str__(self): 

2572 

2573 try: 

2574 path = self.xs_path 

2575 except: 

2576 path = None 

2577 

2578 try: 

2579 action = self.get_action() 

2580 except: 

2581 action = None 

2582 

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

2584 

2585 def _log(self, prio, msg): 

2586 syslog(prio, msg) 

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

2588 

2589 def info(self, msg): 

2590 self._log(_syslog.LOG_INFO, msg) 

2591 

2592 def warn(self, msg): 

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

2594 

2595 def error(self, msg): 

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

2597 

2598 def get_vbd(self): 

2599 if not self._vbd: 

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

2601 return self._vbd 

2602 

2603 def get_tapdisk(self): 

2604 if not self._tapdisk: 

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

2606 self._tapdisk = Tapdisk.from_minor(minor) 

2607 return self._tapdisk 

2608 # 

2609 # Events 

2610 # 

2611 

2612 def __add(self): 

2613 vbd = self.get_vbd() 

2614 # Manage blkback transitions 

2615 # self._manage_vbd() 

2616 

2617 vbd.create_physical_device() 

2618 

2619 vbd.signal_hotplug() 

2620 

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

2622 def add(self): 

2623 try: 

2624 self.__add() 

2625 except Attribute.NoSuchAttribute as e: 

2626 # 

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

2628 # registers device attributes. So poll a little. 

2629 # 

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

2631 raise RetryLoop.TransientFailure(e) 

2632 

2633 def __change(self): 

2634 vbd = self.get_vbd() 

2635 

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

2637 

2638 if vbd.has_tap(): 

2639 pass 

2640 #self._pause_update_tap() 

2641 

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

2643 

2644 self._signal_xapi() 

2645 

2646 def change(self): 

2647 vbd = self.get_vbd() 

2648 

2649 # NB. Beware of spurious change events between shutdown 

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

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

2652 

2653 while True: 

2654 vbd.begin() 

2655 

2656 if not vbd.exists() or \ 

2657 vbd.shutdown_done(): 

2658 break 

2659 

2660 self.__change() 

2661 

2662 if vbd.commit(): 

2663 return 

2664 

2665 vbd.abort() 

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

2667 

2668 def remove(self): 

2669 vbd = self.get_vbd() 

2670 

2671 vbd.signal_hotplug(False) 

2672 

2673 ACTIONS = {'add': add, 

2674 'change': change, 

2675 'remove': remove} 

2676 # 

2677 # VDI.pause 

2678 # 

2679 

2680 def _tap_should_pause(self): 

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

2682 paused""" 

2683 

2684 tapdisk = self.get_tapdisk() 

2685 TapState = Tapdisk.PauseState 

2686 

2687 PAUSED = 'P' 

2688 RUNNING = 'R' 

2689 PAUSED_SHUTDOWN = 'P,S' 

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

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

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

2693 

2694 next = TapState.RUNNING 

2695 vbds = {} 

2696 

2697 for vbd in Blkback.find_by_tap(tapdisk): 

2698 name = str(vbd) 

2699 

2700 pausing = vbd.pause_requested() 

2701 closing = vbd.shutdown_requested() 

2702 running = vbd.running() 

2703 

2704 if pausing: 

2705 if closing and not running: 

2706 vbds[name] = PAUSED_SHUTDOWN 

2707 else: 

2708 vbds[name] = PAUSED 

2709 next = TapState.PAUSED 

2710 

2711 else: 

2712 vbds[name] = RUNNING 

2713 

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

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

2716 vbds, next)) 

2717 

2718 return next == TapState.PAUSED 

2719 

2720 def _pause_update_tap(self): 

2721 vbd = self.get_vbd() 

2722 

2723 if self._tap_should_pause(): 

2724 self._pause_tap() 

2725 else: 

2726 self._resume_tap() 

2727 

2728 def _pause_tap(self): 

2729 tapdisk = self.get_tapdisk() 

2730 

2731 if not tapdisk.is_paused(): 

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

2733 tapdisk.pause() 

2734 

2735 def _resume_tap(self): 

2736 tapdisk = self.get_tapdisk() 

2737 

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

2739 # type while resuming. 

2740 vbd = self.get_vbd() 

2741 vdi_uuid = vbd.get_vdi_uuid() 

2742 

2743 if tapdisk.is_paused(): 

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

2745 vdi = VDI.from_cli(vdi_uuid) 

2746 _type = vdi.get_tap_type() 

2747 path = vdi.get_phy_path() 

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

2749 tapdisk.unpause(_type, path) 

2750 # 

2751 # VBD.pause/shutdown 

2752 # 

2753 

2754 def _manage_vbd(self): 

2755 vbd = self.get_vbd() 

2756 # NB. Hook into VBD state transitions. 

2757 

2758 events = vbd.get_queue_events() 

2759 

2760 mask = 0 

2761 mask |= events.QUEUE_PAUSE_DONE # pause/unpause 

2762 mask |= events.QUEUE_SHUTDOWN_DONE # shutdown 

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

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

2765 

2766 events.set_mask(mask) 

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

2768 

2769 def _signal_xapi(self): 

2770 vbd = self.get_vbd() 

2771 

2772 pausing = vbd.pause_requested() 

2773 closing = vbd.shutdown_requested() 

2774 running = vbd.running() 

2775 

2776 handled = 0 

2777 

2778 if pausing and not running: 

2779 if 'pause-done' not in vbd: 

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

2781 handled += 1 

2782 

2783 if not pausing: 

2784 if 'pause-done' in vbd: 

2785 vbd.rm('pause-done') 

2786 handled += 1 

2787 

2788 if closing and not running: 

2789 if 'shutdown-done' not in vbd: 

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

2791 handled += 1 

2792 

2793 if handled > 1: 

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

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

2796 (pausing, closing, running)) 

2797 

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

2799 

2800 import sys 

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

2802 

2803 # 

2804 # Simple CLI interface for manual operation 

2805 # 

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

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

2808 # 

2809 

2810 def usage(stream): 

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

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

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

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

2815 

2816 try: 

2817 cmd = sys.argv[1] 

2818 except IndexError: 

2819 usage(sys.stderr) 

2820 sys.exit(1) 

2821 

2822 try: 

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

2824 except: 

2825 usage(sys.stderr) 

2826 sys.exit(1) 

2827 

2828 # 

2829 # Local Tapdisks 

2830 # 

2831 

2832 if cmd == 'tap.major': 

2833 

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

2835 

2836 elif cmd == 'tap.launch': 

2837 

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

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

2840 

2841 elif _class == 'tap': 

2842 

2843 attrs = {} 

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

2845 try: 

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

2847 attrs[key] = val 

2848 continue 

2849 except ValueError: 

2850 pass 

2851 

2852 try: 

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

2854 continue 

2855 except ValueError: 

2856 pass 

2857 

2858 try: 

2859 arg = Tapdisk.Arg.parse(item) 

2860 attrs['_type'] = arg.type 

2861 attrs['path'] = arg.path 

2862 continue 

2863 except Tapdisk.Arg.InvalidArgument: 

2864 pass 

2865 

2866 attrs['path'] = item 

2867 

2868 if cmd == 'tap.list': 

2869 

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

2871 blktap = tapdisk.get_blktap() 

2872 print(tapdisk, end=' ') 

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

2874 (blktap, 

2875 blktap.get_task_pid(), 

2876 blktap.get_pool_name())) 

2877 

2878 elif cmd == 'tap.vbds': 

2879 # Find all Blkback instances for a given tapdisk 

2880 

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

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

2883 for vbd in Blkback.find_by_tap(tapdisk): 

2884 print(vbd, end=' ') 

2885 print() 

2886 

2887 else: 

2888 

2889 if not attrs: 

2890 usage(sys.stderr) 

2891 sys.exit(1) 

2892 

2893 try: 

2894 tapdisk = Tapdisk.get( ** attrs) 

2895 except TypeError: 

2896 usage(sys.stderr) 

2897 sys.exit(1) 

2898 

2899 if cmd == 'tap.shutdown': 

2900 # Shutdown a running tapdisk, or raise 

2901 tapdisk.shutdown() 

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

2903 

2904 elif cmd == 'tap.pause': 

2905 # Pause an unpaused tapdisk, or raise 

2906 tapdisk.pause() 

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

2908 

2909 elif cmd == 'tap.unpause': 

2910 # Unpause a paused tapdisk, or raise 

2911 tapdisk.unpause() 

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

2913 

2914 elif cmd == 'tap.stats': 

2915 # Gather tapdisk status 

2916 stats = tapdisk.stats() 

2917 print("%s:" % tapdisk) 

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

2919 

2920 else: 

2921 usage(sys.stderr) 

2922 sys.exit(1) 

2923 

2924 elif cmd == 'vbd.uevent': 

2925 

2926 hnd = BlkbackEventHandler(cmd) 

2927 

2928 if not sys.stdin.isatty(): 

2929 try: 

2930 hnd.run() 

2931 except Exception as e: 

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

2933 

2934 import traceback 

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

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

2937 for entry in trace: 

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

2939 util.SMlog(line) 

2940 else: 

2941 hnd.run() 

2942 

2943 elif cmd == 'vbd.list': 

2944 

2945 for vbd in Blkback.find(): 

2946 print(vbd, \ 

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

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

2949 

2950 else: 

2951 usage(sys.stderr) 

2952 sys.exit(1)