Coverage for drivers/blktap2.py : 40%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#!/usr/bin/python3
2#
3# Copyright (C) Citrix Systems Inc.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published
7# by the Free Software Foundation; version 2.1 only.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program; if not, write to the Free Software Foundation, Inc.,
16# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17#
18# blktap2: blktap/tapdisk management layer
19#
20import grp
21import os
22import re
23import stat
24import time
25import copy
26from lock import Lock
27import util
28import xmlrpc.client
29import http.client
30import errno
31import signal
32import subprocess
33import syslog as _syslog
34import glob
35import json
36import xs_errors
37import XenAPI # pylint: disable=import-error
38import scsiutil
39from syslog import openlog, syslog
40from stat import * # S_ISBLK(), ...
41import nfs
43import resetvdis
44import vhdutil
45import lvhdutil
47import VDI as sm
49# For RRDD Plugin Registration
50from xmlrpc.client import ServerProxy, Transport
51from socket import socket, AF_UNIX, SOCK_STREAM
53try:
54 from linstorvolumemanager import log_drbd_openers
55 LINSTOR_AVAILABLE = True
56except ImportError:
57 LINSTOR_AVAILABLE = False
59PLUGIN_TAP_PAUSE = "tapdisk-pause"
61SOCKPATH = "/var/xapi/xcp-rrdd"
63NUM_PAGES_PER_RING = 32 * 11
64MAX_FULL_RINGS = 8
65POOL_NAME_KEY = "mem-pool"
66POOL_SIZE_KEY = "mem-pool-size-rings"
68ENABLE_MULTIPLE_ATTACH = "/etc/xensource/allow_multiple_vdi_attach"
69NO_MULTIPLE_ATTACH = not (os.path.exists(ENABLE_MULTIPLE_ATTACH))
72def locking(excType, override=True):
73 def locking2(op):
74 def wrapper(self, *args):
75 self.lock.acquire()
76 try:
77 try:
78 ret = op(self, * args)
79 except (util.CommandException, util.SMException, XenAPI.Failure) as e:
80 util.logException("BLKTAP2:%s" % op)
81 msg = str(e)
82 if isinstance(e, util.CommandException):
83 msg = "Command %s failed (%s): %s" % \
84 (e.cmd, e.code, e.reason)
85 if override:
86 raise xs_errors.XenError(excType, opterr=msg)
87 else:
88 raise
89 except:
90 util.logException("BLKTAP2:%s" % op)
91 raise
92 finally:
93 self.lock.release() 93 ↛ exitline 93 didn't except from function 'wrapper', because the raise on line 86 wasn't executed or the raise on line 88 wasn't executed or the raise on line 91 wasn't executed
94 return ret
95 return wrapper
96 return locking2
99class RetryLoop(object):
101 def __init__(self, backoff, limit):
102 self.backoff = backoff
103 self.limit = limit
105 def __call__(self, f):
107 def loop(*__t, **__d):
108 attempt = 0
110 while True:
111 attempt += 1
113 try:
114 return f( * __t, ** __d)
116 except self.TransientFailure as e:
117 e = e.exception
119 if attempt >= self.limit: 119 ↛ 120line 119 didn't jump to line 120, because the condition on line 119 was never true
120 raise e
122 time.sleep(self.backoff)
124 return loop
126 class TransientFailure(Exception):
127 def __init__(self, exception):
128 self.exception = exception
131def retried(**args):
132 return RetryLoop( ** args)
135class TapCtl(object):
136 """Tapdisk IPC utility calls."""
138 PATH = "/usr/sbin/tap-ctl"
140 def __init__(self, cmd, p):
141 self.cmd = cmd
142 self._p = p
143 self.stdout = p.stdout
145 class CommandFailure(Exception):
146 """TapCtl cmd failure."""
148 def __init__(self, cmd, **info):
149 self.cmd = cmd
150 self.info = info
152 def __str__(self):
153 items = self.info.items()
154 info = ", ".join("%s=%s" % item
155 for item in items)
156 return "%s failed: %s" % (self.cmd, info)
158 # Trying to get a non-existent attribute throws an AttributeError
159 # exception
160 def __getattr__(self, key):
161 if key in self.info: 161 ↛ 163line 161 didn't jump to line 163, because the condition on line 161 was never false
162 return self.info[key]
163 return object.__getattribute__(self, key)
165 @property
166 def has_status(self):
167 return 'status' in self.info
169 @property
170 def has_signal(self):
171 return 'signal' in self.info
173 # Retrieves the error code returned by the command. If the error code
174 # was not supplied at object-construction time, zero is returned.
175 def get_error_code(self):
176 key = 'status'
177 if key in self.info: 177 ↛ 180line 177 didn't jump to line 180, because the condition on line 177 was never false
178 return self.info[key]
179 else:
180 return 0
182 @classmethod
183 def __mkcmd_real(cls, args):
184 return [cls.PATH] + [str(x) for x in args]
186 __next_mkcmd = __mkcmd_real
188 @classmethod
189 def _mkcmd(cls, args):
191 __next_mkcmd = cls.__next_mkcmd
192 cls.__next_mkcmd = cls.__mkcmd_real
194 return __next_mkcmd(args)
196 @classmethod
197 def _call(cls, args, quiet=False, input=None, text_mode=True):
198 """
199 Spawn a tap-ctl process. Return a TapCtl invocation.
200 Raises a TapCtl.CommandFailure if subprocess creation failed.
201 """
202 cmd = cls._mkcmd(args)
204 if not quiet:
205 util.SMlog(cmd)
206 try:
207 p = subprocess.Popen(cmd,
208 stdin=subprocess.PIPE,
209 stdout=subprocess.PIPE,
210 stderr=subprocess.PIPE,
211 close_fds=True,
212 universal_newlines=text_mode)
213 if input:
214 p.stdin.write(input)
215 p.stdin.close()
216 except OSError as e:
217 raise cls.CommandFailure(cmd, errno=e.errno)
219 return cls(cmd, p)
221 def _errmsg(self):
222 output = map(str.rstrip, self._p.stderr)
223 return "; ".join(output)
225 def _wait(self, quiet=False):
226 """
227 Reap the child tap-ctl process of this invocation.
228 Raises a TapCtl.CommandFailure on non-zero exit status.
229 """
230 status = self._p.wait()
231 if not quiet:
232 util.SMlog(" = %d" % status)
234 if status == 0:
235 return
237 info = {'errmsg': self._errmsg(),
238 'pid': self._p.pid}
240 if status < 0:
241 info['signal'] = -status
242 else:
243 info['status'] = status
245 raise self.CommandFailure(self.cmd, ** info)
247 @classmethod
248 def _pread(cls, args, quiet=False, input=None, text_mode=True):
249 """
250 Spawn a tap-ctl invocation and read a single line.
251 """
252 tapctl = cls._call(args=args, quiet=quiet, input=input,
253 text_mode=text_mode)
255 output = tapctl.stdout.readline().rstrip()
257 tapctl._wait(quiet)
258 return output
260 @staticmethod
261 def _maybe(opt, parm):
262 if parm is not None:
263 return [opt, parm]
264 return []
266 @classmethod
267 def __list(cls, minor=None, pid=None, _type=None, path=None):
268 args = ["list"]
269 args += cls._maybe("-m", minor)
270 args += cls._maybe("-p", pid)
271 args += cls._maybe("-t", _type)
272 args += cls._maybe("-f", path)
274 tapctl = cls._call(args, True)
276 for stdout_line in tapctl.stdout:
277 # FIXME: tap-ctl writes error messages to stdout and
278 # confuses this parser
279 if stdout_line == "blktap kernel module not installed\n": 279 ↛ 282line 279 didn't jump to line 282, because the condition on line 279 was never true
280 # This isn't pretty but (a) neither is confusing stdout/stderr
281 # and at least causes the error to describe the fix
282 raise Exception("blktap kernel module not installed: try 'modprobe blktap'")
283 row = {}
285 for field in stdout_line.rstrip().split(' ', 3):
286 bits = field.split('=')
287 if len(bits) == 2: 287 ↛ 299line 287 didn't jump to line 299, because the condition on line 287 was never false
288 key, val = field.split('=')
290 if key in ('pid', 'minor'):
291 row[key] = int(val, 10)
293 elif key in ('state'):
294 row[key] = int(val, 0x10)
296 else:
297 row[key] = val
298 else:
299 util.SMlog("Ignoring unexpected tap-ctl output: %s" % repr(field))
300 yield row
302 tapctl._wait(True)
304 @classmethod
305 @retried(backoff=.5, limit=10)
306 def list(cls, **args):
308 # FIXME. We typically get an EPROTO when uevents interleave
309 # with SM ops and a tapdisk shuts down under our feet. Should
310 # be fixed in SM.
312 try:
313 return list(cls.__list( ** args))
315 except cls.CommandFailure as e:
316 transient = [errno.EPROTO, errno.ENOENT]
317 if e.has_status and e.status in transient:
318 raise RetryLoop.TransientFailure(e)
319 raise
321 @classmethod
322 def allocate(cls, devpath=None):
323 args = ["allocate"]
324 args += cls._maybe("-d", devpath)
325 return cls._pread(args)
327 @classmethod
328 def free(cls, minor):
329 args = ["free", "-m", minor]
330 cls._pread(args)
332 @classmethod
333 @retried(backoff=.5, limit=10)
334 def spawn(cls):
335 args = ["spawn"]
336 try:
337 pid = cls._pread(args)
338 return int(pid)
339 except cls.CommandFailure as ce:
340 # intermittent failures to spawn. CA-292268
341 if ce.status == 1:
342 raise RetryLoop.TransientFailure(ce)
343 raise
345 @classmethod
346 def attach(cls, pid, minor):
347 args = ["attach", "-p", pid, "-m", minor]
348 cls._pread(args)
350 @classmethod
351 def detach(cls, pid, minor):
352 args = ["detach", "-p", pid, "-m", minor]
353 cls._pread(args)
355 @classmethod
356 def _load_key(cls, key_hash, vdi_uuid):
357 import plugins
359 return plugins.load_key(key_hash, vdi_uuid)
361 @classmethod
362 def open(cls, pid, minor, _type, _file, options):
363 params = Tapdisk.Arg(_type, _file)
364 args = ["open", "-p", pid, "-m", minor, '-a', str(params)]
365 text_mode = True
366 input = None
367 if options.get("rdonly"):
368 args.append('-R')
369 if options.get("lcache"):
370 args.append("-r")
371 if options.get("existing_prt") is not None:
372 args.append("-e")
373 args.append(str(options["existing_prt"]))
374 if options.get("secondary"):
375 args.append("-2")
376 args.append(options["secondary"])
377 if options.get("standby"):
378 args.append("-s")
379 if options.get("timeout"):
380 args.append("-t")
381 args.append(str(options["timeout"]))
382 if not options.get("o_direct", True):
383 args.append("-D")
384 if options.get('cbtlog'):
385 args.extend(['-C', options['cbtlog']])
386 if options.get('key_hash'):
387 key_hash = options['key_hash']
388 vdi_uuid = options['vdi_uuid']
389 key = cls._load_key(key_hash, vdi_uuid)
391 if not key:
392 raise util.SMException("No key found with key hash {}".format(key_hash))
393 input = key
394 text_mode = False
395 args.append('-E')
397 cls._pread(args=args, input=input, text_mode=text_mode)
399 @classmethod
400 def close(cls, pid, minor, force=False):
401 args = ["close", "-p", pid, "-m", minor, "-t", "120"]
402 if force:
403 args += ["-f"]
404 cls._pread(args)
406 @classmethod
407 def pause(cls, pid, minor):
408 args = ["pause", "-p", pid, "-m", minor]
409 cls._pread(args)
411 @classmethod
412 def unpause(cls, pid, minor, _type=None, _file=None, mirror=None,
413 cbtlog=None):
414 args = ["unpause", "-p", pid, "-m", minor]
415 if mirror:
416 args.extend(["-2", mirror])
417 if _type and _file:
418 params = Tapdisk.Arg(_type, _file)
419 args += ["-a", str(params)]
420 if cbtlog:
421 args.extend(["-c", cbtlog])
422 cls._pread(args)
424 @classmethod
425 def shutdown(cls, pid):
426 # TODO: This should be a real tap-ctl command
427 os.kill(pid, signal.SIGTERM)
428 os.waitpid(pid, 0)
430 @classmethod
431 def stats(cls, pid, minor):
432 args = ["stats", "-p", pid, "-m", minor]
433 return cls._pread(args, quiet=True)
435 @classmethod
436 def major(cls):
437 args = ["major"]
438 major = cls._pread(args)
439 return int(major)
442class TapdiskExists(Exception):
443 """Tapdisk already running."""
445 def __init__(self, tapdisk):
446 self.tapdisk = tapdisk
448 def __str__(self):
449 return "%s already running" % self.tapdisk
452class TapdiskNotRunning(Exception):
453 """No such Tapdisk."""
455 def __init__(self, **attrs):
456 self.attrs = attrs
458 def __str__(self):
459 items = iter(self.attrs.items())
460 attrs = ", ".join("%s=%s" % attr
461 for attr in items)
462 return "No such Tapdisk(%s)" % attrs
465class TapdiskNotUnique(Exception):
466 """More than one tapdisk on one path."""
468 def __init__(self, tapdisks):
469 self.tapdisks = tapdisks
471 def __str__(self):
472 tapdisks = map(str, self.tapdisks)
473 return "Found multiple tapdisks: %s" % tapdisks
476class TapdiskFailed(Exception):
477 """Tapdisk launch failure."""
479 def __init__(self, arg, err):
480 self.arg = arg
481 self.err = err
483 def __str__(self):
484 return "Tapdisk(%s): %s" % (self.arg, self.err)
486 def get_error(self):
487 return self.err
490class TapdiskInvalidState(Exception):
491 """Tapdisk pause/unpause failure"""
493 def __init__(self, tapdisk):
494 self.tapdisk = tapdisk
496 def __str__(self):
497 return str(self.tapdisk)
500def mkdirs(path, mode=0o777):
501 if not os.path.exists(path):
502 parent, subdir = os.path.split(path)
503 assert parent != path
504 try:
505 if parent:
506 mkdirs(parent, mode)
507 if subdir:
508 os.mkdir(path, mode)
509 except OSError as e:
510 if e.errno != errno.EEXIST:
511 raise
514class KObject(object):
516 SYSFS_CLASSTYPE = None
518 def sysfs_devname(self):
519 raise NotImplementedError("sysfs_devname is undefined")
522class Attribute(object):
524 SYSFS_NODENAME = None
526 def __init__(self, path):
527 self.path = path
529 @classmethod
530 def from_kobject(cls, kobj):
531 path = "%s/%s" % (kobj.sysfs_path(), cls.SYSFS_NODENAME)
532 return cls(path)
534 class NoSuchAttribute(Exception):
535 def __init__(self, name):
536 self.name = name
538 def __str__(self):
539 return "No such attribute: %s" % self.name
541 def _open(self, mode='r'):
542 try:
543 return open(self.path, mode)
544 except IOError as e:
545 if e.errno == errno.ENOENT:
546 raise self.NoSuchAttribute(self)
547 raise
549 def readline(self):
550 f = self._open('r')
551 s = f.readline().rstrip()
552 f.close()
553 return s
555 def writeline(self, val):
556 f = self._open('w')
557 f.write(val)
558 f.close()
561class ClassDevice(KObject):
563 @classmethod
564 def sysfs_class_path(cls):
565 return "/sys/class/%s" % cls.SYSFS_CLASSTYPE
567 def sysfs_path(self):
568 return "%s/%s" % (self.sysfs_class_path(),
569 self.sysfs_devname())
572class Blktap(ClassDevice):
574 DEV_BASEDIR = '/dev/xen/blktap-2'
576 SYSFS_CLASSTYPE = "blktap2"
578 def __init__(self, minor):
579 self.minor = minor
580 self._pool = None
581 self._task = None
583 @classmethod
584 def allocate(cls):
585 # FIXME. Should rather go into init.
586 mkdirs(cls.DEV_BASEDIR)
588 devname = TapCtl.allocate()
589 minor = Tapdisk._parse_minor(devname)
590 return cls(minor)
592 def free(self):
593 TapCtl.free(self.minor)
595 def __str__(self):
596 return "%s(minor=%d)" % (self.__class__.__name__, self.minor)
598 def sysfs_devname(self):
599 return "blktap!blktap%d" % self.minor
601 class Pool(Attribute):
602 SYSFS_NODENAME = "pool"
604 def get_pool_attr(self):
605 if not self._pool:
606 self._pool = self.Pool.from_kobject(self)
607 return self._pool
609 def get_pool_name(self):
610 return self.get_pool_attr().readline()
612 def set_pool_name(self, name):
613 self.get_pool_attr().writeline(name)
615 def set_pool_size(self, pages):
616 self.get_pool().set_size(pages)
618 def get_pool(self):
619 return BlktapControl.get_pool(self.get_pool_name())
621 def set_pool(self, pool):
622 self.set_pool_name(pool.name)
624 class Task(Attribute):
625 SYSFS_NODENAME = "task"
627 def get_task_attr(self):
628 if not self._task:
629 self._task = self.Task.from_kobject(self)
630 return self._task
632 def get_task_pid(self):
633 pid = self.get_task_attr().readline()
634 try:
635 return int(pid)
636 except ValueError:
637 return None
639 def find_tapdisk(self):
640 pid = self.get_task_pid()
641 if pid is None:
642 return None
644 return Tapdisk.find(pid=pid, minor=self.minor)
646 def get_tapdisk(self):
647 tapdisk = self.find_tapdisk()
648 if not tapdisk:
649 raise TapdiskNotRunning(minor=self.minor)
650 return tapdisk
653class Tapdisk(object):
655 TYPES = ['aio', 'vhd']
657 def __init__(self, pid, minor, _type, path, state):
658 self.pid = pid
659 self.minor = minor
660 self.type = _type
661 self.path = path
662 self.state = state
663 self._dirty = False
664 self._blktap = None
666 def __str__(self):
667 state = self.pause_state()
668 return "Tapdisk(%s, pid=%d, minor=%s, state=%s)" % \
669 (self.get_arg(), self.pid, self.minor, state)
671 @classmethod
672 def list(cls, **args):
674 for row in TapCtl.list( ** args):
676 args = {'pid': None,
677 'minor': None,
678 'state': None,
679 '_type': None,
680 'path': None}
682 for key, val in row.items():
683 if key in args:
684 args[key] = val
686 if 'args' in row: 686 ↛ 691line 686 didn't jump to line 691, because the condition on line 686 was never false
687 image = Tapdisk.Arg.parse(row['args'])
688 args['_type'] = image.type
689 args['path'] = image.path
691 if None in args.values(): 691 ↛ 692line 691 didn't jump to line 692, because the condition on line 691 was never true
692 continue
694 yield Tapdisk( ** args)
696 @classmethod
697 def find(cls, **args):
699 found = list(cls.list( ** args))
701 if len(found) > 1: 701 ↛ 702line 701 didn't jump to line 702, because the condition on line 701 was never true
702 raise TapdiskNotUnique(found)
704 if found: 704 ↛ 705line 704 didn't jump to line 705, because the condition on line 704 was never true
705 return found[0]
707 return None
709 @classmethod
710 def find_by_path(cls, path):
711 return cls.find(path=path)
713 @classmethod
714 def find_by_minor(cls, minor):
715 return cls.find(minor=minor)
717 @classmethod
718 def get(cls, **attrs):
720 tapdisk = cls.find( ** attrs)
722 if not tapdisk:
723 raise TapdiskNotRunning( ** attrs)
725 return tapdisk
727 @classmethod
728 def from_path(cls, path):
729 return cls.get(path=path)
731 @classmethod
732 def from_minor(cls, minor):
733 return cls.get(minor=minor)
735 @classmethod
736 def __from_blktap(cls, blktap):
737 tapdisk = cls.from_minor(minor=blktap.minor)
738 tapdisk._blktap = blktap
739 return tapdisk
741 def get_blktap(self):
742 if not self._blktap:
743 self._blktap = Blktap(self.minor)
744 return self._blktap
746 class Arg:
748 def __init__(self, _type, path):
749 self.type = _type
750 self.path = path
752 def __str__(self):
753 return "%s:%s" % (self.type, self.path)
755 @classmethod
756 def parse(cls, arg):
758 try:
759 _type, path = arg.split(":", 1)
760 except ValueError:
761 raise cls.InvalidArgument(arg)
763 if _type not in Tapdisk.TYPES: 763 ↛ 764line 763 didn't jump to line 764, because the condition on line 763 was never true
764 raise cls.InvalidType(_type)
766 return cls(_type, path)
768 class InvalidType(Exception):
769 def __init__(self, _type):
770 self.type = _type
772 def __str__(self):
773 return "Not a Tapdisk type: %s" % self.type
775 class InvalidArgument(Exception):
776 def __init__(self, arg):
777 self.arg = arg
779 def __str__(self):
780 return "Not a Tapdisk image: %s" % self.arg
782 def get_arg(self):
783 return self.Arg(self.type, self.path)
785 def get_devpath(self):
786 return "%s/tapdev%d" % (Blktap.DEV_BASEDIR, self.minor)
788 @classmethod
789 def launch_from_arg(cls, arg):
790 arg = cls.Arg.parse(arg)
791 return cls.launch(arg.path, arg.type, False)
793 @staticmethod
794 def cgclassify(pid):
796 # We dont provide any <controllers>:<path>
797 # so cgclassify uses /etc/cgrules.conf which
798 # we have configured in the spec file.
799 cmd = ["cgclassify", str(pid)]
800 try:
801 util.pread2(cmd)
802 except util.CommandException as e:
803 util.logException(e)
805 @classmethod
806 def launch_on_tap(cls, blktap, path, _type, options):
808 tapdisk = cls.find_by_path(path)
809 if tapdisk: 809 ↛ 810line 809 didn't jump to line 810, because the condition on line 809 was never true
810 raise TapdiskExists(tapdisk)
812 minor = blktap.minor
813 try:
814 pid = TapCtl.spawn()
815 cls.cgclassify(pid)
816 try:
817 TapCtl.attach(pid, minor)
819 try:
820 retry_open = 0
821 while True:
822 try:
823 TapCtl.open(pid, minor, _type, path, options)
824 break
825 except TapCtl.CommandFailure as e:
826 err = (
827 'status' in e.info and e.info['status']
828 ) or None
829 if err in (errno.EIO, errno.EROFS, errno.EAGAIN): 829 ↛ 830line 829 didn't jump to line 830, because the condition on line 829 was never true
830 if retry_open < 5:
831 retry_open += 1
832 time.sleep(1)
833 continue
834 if LINSTOR_AVAILABLE and err == errno.EROFS:
835 log_drbd_openers(path)
836 raise
837 try:
838 tapdisk = cls.__from_blktap(blktap)
839 node = '/sys/dev/block/%d:%d' % (tapdisk.major(), tapdisk.minor)
840 util.set_scheduler_sysfs_node(node, ['none', 'noop'])
841 return tapdisk
842 except:
843 TapCtl.close(pid, minor)
844 raise
846 except:
847 TapCtl.detach(pid, minor)
848 raise
850 except:
851 try:
852 TapCtl.shutdown(pid)
853 except:
854 # Best effort to shutdown
855 pass
856 raise
858 except TapCtl.CommandFailure as ctl:
859 util.logException(ctl)
860 if ((path.startswith('/dev/xapi/cd/') or path.startswith('/dev/sr')) and 860 ↛ 864line 860 didn't jump to line 864, because the condition on line 860 was never false
861 ctl.has_status and ctl.get_error_code() == 123): # ENOMEDIUM (No medium found)
862 raise xs_errors.XenError('TapdiskDriveEmpty')
863 else:
864 raise TapdiskFailed(cls.Arg(_type, path), ctl)
866 @classmethod
867 def launch(cls, path, _type, rdonly):
868 blktap = Blktap.allocate()
869 try:
870 return cls.launch_on_tap(blktap, path, _type, {"rdonly": rdonly})
871 except:
872 blktap.free()
873 raise
875 def shutdown(self, force=False):
877 TapCtl.close(self.pid, self.minor, force)
879 TapCtl.detach(self.pid, self.minor)
881 self.get_blktap().free()
883 def pause(self):
885 if not self.is_running():
886 raise TapdiskInvalidState(self)
888 TapCtl.pause(self.pid, self.minor)
890 self._set_dirty()
892 def unpause(self, _type=None, path=None, mirror=None, cbtlog=None):
894 if not self.is_paused():
895 raise TapdiskInvalidState(self)
897 # FIXME: should the arguments be optional?
898 if _type is None:
899 _type = self.type
900 if path is None:
901 path = self.path
903 TapCtl.unpause(self.pid, self.minor, _type, path, mirror=mirror,
904 cbtlog=cbtlog)
906 self._set_dirty()
908 def stats(self):
909 return json.loads(TapCtl.stats(self.pid, self.minor))
910 #
911 # NB. dirty/refresh: reload attributes on next access
912 #
914 def _set_dirty(self):
915 self._dirty = True
917 def _refresh(self, __get):
918 t = self.from_minor(__get('minor'))
919 self.__init__(t.pid, t.minor, t.type, t.path, t.state)
921 def __getattribute__(self, name):
922 def __get(name):
923 # NB. avoid(rec(ursion)
924 return object.__getattribute__(self, name)
926 if __get('_dirty') and \ 926 ↛ 928line 926 didn't jump to line 928, because the condition on line 926 was never true
927 name in ['minor', 'type', 'path', 'state']:
928 self._refresh(__get)
929 self._dirty = False
931 return __get(name)
933 class PauseState:
934 RUNNING = 'R'
935 PAUSING = 'r'
936 PAUSED = 'P'
938 class Flags:
939 DEAD = 0x0001
940 CLOSED = 0x0002
941 QUIESCE_REQUESTED = 0x0004
942 QUIESCED = 0x0008
943 PAUSE_REQUESTED = 0x0010
944 PAUSED = 0x0020
945 SHUTDOWN_REQUESTED = 0x0040
946 LOCKING = 0x0080
947 RETRY_NEEDED = 0x0100
948 LOG_DROPPED = 0x0200
950 PAUSE_MASK = PAUSE_REQUESTED | PAUSED
952 def is_paused(self):
953 return not not (self.state & self.Flags.PAUSED)
955 def is_running(self):
956 return not (self.state & self.Flags.PAUSE_MASK)
958 def pause_state(self):
959 if self.state & self.Flags.PAUSED:
960 return self.PauseState.PAUSED
962 if self.state & self.Flags.PAUSE_REQUESTED:
963 return self.PauseState.PAUSING
965 return self.PauseState.RUNNING
967 @staticmethod
968 def _parse_minor(devpath):
969 regex = r'%s/(blktap|tapdev)(\d+)$' % Blktap.DEV_BASEDIR
970 pattern = re.compile(regex)
971 groups = pattern.search(devpath)
972 if not groups:
973 raise Exception("malformed tap device: '%s' (%s) " % (devpath, regex))
975 minor = groups.group(2)
976 return int(minor)
978 _major = None
980 @classmethod
981 def major(cls):
982 if cls._major:
983 return cls._major
985 devices = open("/proc/devices")
986 for line in devices:
988 row = line.rstrip().split(' ')
989 if len(row) != 2:
990 continue
992 major, name = row
993 if name != 'tapdev':
994 continue
996 cls._major = int(major)
997 break
999 devices.close()
1000 return cls._major
1003class VDI(object):
1004 """SR.vdi driver decorator for blktap2"""
1006 CONF_KEY_ALLOW_CACHING = "vdi_allow_caching"
1007 CONF_KEY_MODE_ON_BOOT = "vdi_on_boot"
1008 CONF_KEY_CACHE_SR = "local_cache_sr"
1009 CONF_KEY_O_DIRECT = "o_direct"
1010 LOCK_CACHE_SETUP = "cachesetup"
1012 ATTACH_DETACH_RETRY_SECS = 120
1014 def __init__(self, uuid, target, driver_info):
1015 self.target = self.TargetDriver(target, driver_info)
1016 self._vdi_uuid = uuid
1017 self._session = target.session
1018 self.xenstore_data = scsiutil.update_XS_SCSIdata(uuid, scsiutil.gen_synthetic_page_data(uuid))
1019 self.__o_direct = None
1020 self.__o_direct_reason = None
1021 self.lock = Lock("vdi", uuid)
1022 self.tap = None
1024 def get_o_direct_capability(self, options):
1025 """Returns True/False based on licensing and caching_params"""
1026 if self.__o_direct is not None: 1026 ↛ 1027line 1026 didn't jump to line 1027, because the condition on line 1026 was never true
1027 return self.__o_direct, self.__o_direct_reason
1029 if util.read_caching_is_restricted(self._session): 1029 ↛ 1030line 1029 didn't jump to line 1030, because the condition on line 1029 was never true
1030 self.__o_direct = True
1031 self.__o_direct_reason = "LICENSE_RESTRICTION"
1032 elif not ((self.target.vdi.sr.handles("nfs") or self.target.vdi.sr.handles("ext") or self.target.vdi.sr.handles("smb"))): 1032 ↛ 1035line 1032 didn't jump to line 1035, because the condition on line 1032 was never false
1033 self.__o_direct = True
1034 self.__o_direct_reason = "SR_NOT_SUPPORTED"
1035 elif not (options.get("rdonly") or self.target.vdi.parent):
1036 util.SMlog(self.target.vdi)
1037 self.__o_direct = True
1038 self.__o_direct_reason = "NO_RO_IMAGE"
1039 elif options.get("rdonly") and not self.target.vdi.parent:
1040 self.__o_direct = True
1041 self.__o_direct_reason = "RO_WITH_NO_PARENT"
1042 elif options.get(self.CONF_KEY_O_DIRECT):
1043 self.__o_direct = True
1044 self.__o_direct_reason = "SR_OVERRIDE"
1046 if self.__o_direct is None: 1046 ↛ 1047line 1046 didn't jump to line 1047, because the condition on line 1046 was never true
1047 self.__o_direct = False
1048 self.__o_direct_reason = ""
1050 return self.__o_direct, self.__o_direct_reason
1052 @classmethod
1053 def from_cli(cls, uuid):
1054 import VDI as sm
1056 session = XenAPI.xapi_local()
1057 session.xenapi.login_with_password('root', '', '', 'SM')
1059 target = sm.VDI.from_uuid(session, uuid)
1060 driver_info = target.sr.srcmd.driver_info
1062 session.xenapi.session.logout()
1064 return cls(uuid, target, driver_info)
1066 @staticmethod
1067 def _tap_type(vdi_type):
1068 """Map a VDI type (e.g. 'raw') to a tapdisk driver type (e.g. 'aio')"""
1069 return {
1070 'raw': 'aio',
1071 'vhd': 'vhd',
1072 'iso': 'aio', # for ISO SR
1073 'aio': 'aio', # for LVHD
1074 'file': 'aio',
1075 'phy': 'aio'
1076 }[vdi_type]
1078 def get_tap_type(self):
1079 vdi_type = self.target.get_vdi_type()
1080 return VDI._tap_type(vdi_type)
1082 def get_phy_path(self):
1083 return self.target.get_vdi_path()
1085 class UnexpectedVDIType(Exception):
1087 def __init__(self, vdi_type, target):
1088 self.vdi_type = vdi_type
1089 self.target = target
1091 def __str__(self):
1092 return \
1093 "Target %s has unexpected VDI type '%s'" % \
1094 (type(self.target), self.vdi_type)
1096 VDI_PLUG_TYPE = {'phy': 'phy', # for NETAPP
1097 'raw': 'phy',
1098 'aio': 'tap', # for LVHD raw nodes
1099 'iso': 'tap', # for ISOSR
1100 'file': 'tap',
1101 'vhd': 'tap'}
1103 def tap_wanted(self):
1104 # 1. Let the target vdi_type decide
1106 vdi_type = self.target.get_vdi_type()
1108 try:
1109 plug_type = self.VDI_PLUG_TYPE[vdi_type]
1110 except KeyError:
1111 raise self.UnexpectedVDIType(vdi_type,
1112 self.target.vdi)
1114 if plug_type == 'tap': 1114 ↛ 1115line 1114 didn't jump to line 1115, because the condition on line 1114 was never true
1115 return True
1116 elif self.target.vdi.sr.handles('udev'): 1116 ↛ 1122line 1116 didn't jump to line 1122, because the condition on line 1116 was never false
1117 return True
1118 # 2. Otherwise, there may be more reasons
1119 #
1120 # .. TBD
1122 return False
1124 class TargetDriver:
1125 """Safe target driver access."""
1126 # NB. *Must* test caps for optional calls. Some targets
1127 # actually implement some slots, but do not enable them. Just
1128 # try/except would risk breaking compatibility.
1130 def __init__(self, vdi, driver_info):
1131 self.vdi = vdi
1132 self._caps = driver_info['capabilities']
1134 def has_cap(self, cap):
1135 """Determine if target has given capability"""
1136 return cap in self._caps
1138 def attach(self, sr_uuid, vdi_uuid):
1139 #assert self.has_cap("VDI_ATTACH")
1140 return self.vdi.attach(sr_uuid, vdi_uuid)
1142 def detach(self, sr_uuid, vdi_uuid):
1143 #assert self.has_cap("VDI_DETACH")
1144 self.vdi.detach(sr_uuid, vdi_uuid)
1146 def activate(self, sr_uuid, vdi_uuid):
1147 if self.has_cap("VDI_ACTIVATE"):
1148 return self.vdi.activate(sr_uuid, vdi_uuid)
1150 def deactivate(self, sr_uuid, vdi_uuid):
1151 if self.has_cap("VDI_DEACTIVATE"):
1152 self.vdi.deactivate(sr_uuid, vdi_uuid)
1153 #def resize(self, sr_uuid, vdi_uuid, size):
1154 # return self.vdi.resize(sr_uuid, vdi_uuid, size)
1156 def get_vdi_type(self):
1157 _type = self.vdi.vdi_type
1158 if not _type:
1159 _type = self.vdi.sr.sr_vditype
1160 if not _type:
1161 raise VDI.UnexpectedVDIType(_type, self.vdi)
1162 return _type
1164 def get_vdi_path(self):
1165 return self.vdi.path
1167 class Link(object):
1168 """Relink a node under a common name"""
1169 # NB. We have to provide the device node path during
1170 # VDI.attach, but currently do not allocate the tapdisk minor
1171 # before VDI.activate. Therefore those link steps where we
1172 # relink existing devices under deterministic path names.
1174 BASEDIR = None
1176 def _mklink(self, target):
1177 raise NotImplementedError("_mklink is not defined")
1179 def _equals(self, target):
1180 raise NotImplementedError("_equals is not defined")
1182 def __init__(self, path):
1183 self._path = path
1185 @classmethod
1186 def from_name(cls, name):
1187 path = "%s/%s" % (cls.BASEDIR, name)
1188 return cls(path)
1190 @classmethod
1191 def from_uuid(cls, sr_uuid, vdi_uuid):
1192 name = "%s/%s" % (sr_uuid, vdi_uuid)
1193 return cls.from_name(name)
1195 def path(self):
1196 return self._path
1198 def stat(self):
1199 return os.stat(self.path())
1201 def mklink(self, target):
1203 path = self.path()
1204 util.SMlog("%s -> %s" % (self, target))
1206 mkdirs(os.path.dirname(path))
1207 try:
1208 self._mklink(target)
1209 except OSError as e:
1210 # We do unlink during teardown, but have to stay
1211 # idempotent. However, a *wrong* target should never
1212 # be seen.
1213 if e.errno != errno.EEXIST:
1214 raise
1215 assert self._equals(target), "'%s' not equal to '%s'" % (path, target)
1217 def unlink(self):
1218 try:
1219 os.unlink(self.path())
1220 except OSError as e:
1221 if e.errno != errno.ENOENT:
1222 raise
1224 def __str__(self):
1225 path = self.path()
1226 return "%s(%s)" % (self.__class__.__name__, path)
1228 class SymLink(Link):
1229 """Symlink some file to a common name"""
1231 def readlink(self):
1232 return os.readlink(self.path())
1234 def symlink(self):
1235 return self.path()
1237 def _mklink(self, target):
1238 os.symlink(target, self.path())
1240 def _equals(self, target):
1241 return self.readlink() == target
1243 class DeviceNode(Link):
1244 """Relink a block device node to a common name"""
1246 @classmethod
1247 def _real_stat(cls, target):
1248 """stat() not on @target, but its realpath()"""
1249 _target = os.path.realpath(target)
1250 return os.stat(_target)
1252 @classmethod
1253 def is_block(cls, target):
1254 """Whether @target refers to a block device."""
1255 return S_ISBLK(cls._real_stat(target).st_mode)
1257 def _mklink(self, target):
1259 st = self._real_stat(target)
1260 if not S_ISBLK(st.st_mode):
1261 raise self.NotABlockDevice(target, st)
1263 # set group read for disk group as well as root
1264 os.mknod(self.path(), st.st_mode | stat.S_IRGRP, st.st_rdev)
1265 os.chown(self.path(), st.st_uid, grp.getgrnam("disk").gr_gid)
1267 def _equals(self, target):
1268 target_rdev = self._real_stat(target).st_rdev
1269 return self.stat().st_rdev == target_rdev
1271 def rdev(self):
1272 st = self.stat()
1273 assert S_ISBLK(st.st_mode)
1274 return os.major(st.st_rdev), os.minor(st.st_rdev)
1276 class NotABlockDevice(Exception):
1278 def __init__(self, path, st):
1279 self.path = path
1280 self.st = st
1282 def __str__(self):
1283 return "%s is not a block device: %s" % (self.path, self.st)
1285 class Hybrid(Link):
1287 def __init__(self, path):
1288 VDI.Link.__init__(self, path)
1289 self._devnode = VDI.DeviceNode(path)
1290 self._symlink = VDI.SymLink(path)
1292 def rdev(self):
1293 st = self.stat()
1294 if S_ISBLK(st.st_mode):
1295 return self._devnode.rdev()
1296 raise self._devnode.NotABlockDevice(self.path(), st)
1298 def mklink(self, target):
1299 if self._devnode.is_block(target):
1300 self._obj = self._devnode
1301 else:
1302 self._obj = self._symlink
1303 self._obj.mklink(target)
1305 def _equals(self, target):
1306 return self._obj._equals(target)
1308 class PhyLink(SymLink):
1309 BASEDIR = "/dev/sm/phy"
1310 # NB. Cannot use DeviceNodes, e.g. FileVDIs aren't bdevs.
1312 class NBDLink(SymLink):
1314 BASEDIR = "/run/blktap-control/nbd"
1316 class BackendLink(Hybrid):
1317 BASEDIR = "/dev/sm/backend"
1318 # NB. Could be SymLinks as well, but saving major,minor pairs in
1319 # Links enables neat state capturing when managing Tapdisks. Note
1320 # that we essentially have a tap-ctl list replacement here. For
1321 # now make it a 'Hybrid'. Likely to collapse into a DeviceNode as
1322 # soon as ISOs are tapdisks.
1324 @staticmethod
1325 def _tap_activate(phy_path, vdi_type, sr_uuid, options, pool_size=None):
1327 tapdisk = Tapdisk.find_by_path(phy_path)
1328 if not tapdisk: 1328 ↛ 1329line 1328 didn't jump to line 1329, because the condition on line 1328 was never true
1329 blktap = Blktap.allocate()
1330 blktap.set_pool_name(sr_uuid)
1331 if pool_size:
1332 blktap.set_pool_size(pool_size)
1334 try:
1335 tapdisk = \
1336 Tapdisk.launch_on_tap(blktap,
1337 phy_path,
1338 VDI._tap_type(vdi_type),
1339 options)
1340 except:
1341 blktap.free()
1342 raise
1343 util.SMlog("tap.activate: Launched %s" % tapdisk)
1345 else:
1346 util.SMlog("tap.activate: Found %s" % tapdisk)
1348 return tapdisk.get_devpath(), tapdisk
1350 @staticmethod
1351 def _tap_deactivate(minor):
1353 try:
1354 tapdisk = Tapdisk.from_minor(minor)
1355 except TapdiskNotRunning as e:
1356 util.SMlog("tap.deactivate: Warning, %s" % e)
1357 # NB. Should not be here unless the agent refcount
1358 # broke. Also, a clean shutdown should not have leaked
1359 # the recorded minor.
1360 else:
1361 tapdisk.shutdown()
1362 util.SMlog("tap.deactivate: Shut down %s" % tapdisk)
1364 @classmethod
1365 def tap_pause(cls, session, sr_uuid, vdi_uuid, failfast=False):
1366 """
1367 Pauses the tapdisk.
1369 session: a XAPI session
1370 sr_uuid: the UUID of the SR on which VDI lives
1371 vdi_uuid: the UUID of the VDI to pause
1372 failfast: controls whether the VDI lock should be acquired in a
1373 non-blocking manner
1374 """
1375 util.SMlog("Pause request for %s" % vdi_uuid)
1376 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1377 session.xenapi.VDI.add_to_sm_config(vdi_ref, 'paused', 'true')
1378 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1379 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 1379 ↛ 1380line 1379 didn't jump to line 1380, because the loop on line 1379 never started
1380 host_ref = key[len('host_'):]
1381 util.SMlog("Calling tap-pause on host %s" % host_ref)
1382 if not cls.call_pluginhandler(session, host_ref,
1383 sr_uuid, vdi_uuid, "pause", failfast=failfast):
1384 # Failed to pause node
1385 session.xenapi.VDI.remove_from_sm_config(vdi_ref, 'paused')
1386 return False
1387 return True
1389 @classmethod
1390 def tap_unpause(cls, session, sr_uuid, vdi_uuid, secondary=None,
1391 activate_parents=False):
1392 util.SMlog("Unpause request for %s secondary=%s" % (vdi_uuid, secondary))
1393 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1394 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1395 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 1395 ↛ 1396line 1395 didn't jump to line 1396, because the loop on line 1395 never started
1396 host_ref = key[len('host_'):]
1397 util.SMlog("Calling tap-unpause on host %s" % host_ref)
1398 if not cls.call_pluginhandler(session, host_ref,
1399 sr_uuid, vdi_uuid, "unpause", secondary, activate_parents):
1400 # Failed to unpause node
1401 return False
1402 session.xenapi.VDI.remove_from_sm_config(vdi_ref, 'paused')
1403 return True
1405 @classmethod
1406 def tap_refresh(cls, session, sr_uuid, vdi_uuid, activate_parents=False):
1407 util.SMlog("Refresh request for %s" % vdi_uuid)
1408 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1409 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1410 for key in [x for x in sm_config.keys() if x.startswith('host_')]:
1411 host_ref = key[len('host_'):]
1412 util.SMlog("Calling tap-refresh on host %s" % host_ref)
1413 if not cls.call_pluginhandler(session, host_ref,
1414 sr_uuid, vdi_uuid, "refresh", None,
1415 activate_parents=activate_parents):
1416 # Failed to refresh node
1417 return False
1418 return True
1420 @classmethod
1421 def tap_status(cls, session, vdi_uuid):
1422 """Return True if disk is attached, false if it isn't"""
1423 util.SMlog("Disk status request for %s" % vdi_uuid)
1424 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1425 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1426 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 1426 ↛ 1427line 1426 didn't jump to line 1427, because the loop on line 1426 never started
1427 return True
1428 return False
1430 @classmethod
1431 def call_pluginhandler(cls, session, host_ref, sr_uuid, vdi_uuid, action,
1432 secondary=None, activate_parents=False, failfast=False):
1433 """Optionally, activate the parent LV before unpausing"""
1434 try:
1435 args = {"sr_uuid": sr_uuid, "vdi_uuid": vdi_uuid,
1436 "failfast": str(failfast)}
1437 if secondary:
1438 args["secondary"] = secondary
1439 if activate_parents:
1440 args["activate_parents"] = "true"
1441 ret = session.xenapi.host.call_plugin(
1442 host_ref, PLUGIN_TAP_PAUSE, action,
1443 args)
1444 return ret == "True"
1445 except Exception as e:
1446 util.logException("BLKTAP2:call_pluginhandler %s" % e)
1447 return False
1449 def _add_tag(self, vdi_uuid, writable):
1450 util.SMlog("Adding tag to: %s" % vdi_uuid)
1451 attach_mode = "RO"
1452 if writable: 1452 ↛ 1454line 1452 didn't jump to line 1454, because the condition on line 1452 was never false
1453 attach_mode = "RW"
1454 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1455 host_ref = self._session.xenapi.host.get_by_uuid(util.get_this_host())
1456 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1457 attached_as = util.attached_as(sm_config)
1458 if NO_MULTIPLE_ATTACH and (attached_as == "RW" or \ 1458 ↛ 1460line 1458 didn't jump to line 1460, because the condition on line 1458 was never true
1459 (attached_as == "RO" and attach_mode == "RW")):
1460 util.SMlog("need to reset VDI %s" % vdi_uuid)
1461 if not resetvdis.reset_vdi(self._session, vdi_uuid, force=False,
1462 term_output=False, writable=writable):
1463 raise util.SMException("VDI %s not detached cleanly" % vdi_uuid)
1464 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1465 if 'relinking' in sm_config:
1466 util.SMlog("Relinking key found, back-off and retry" % sm_config)
1467 return False
1468 if 'paused' in sm_config:
1469 util.SMlog("Paused or host_ref key found [%s]" % sm_config)
1470 return False
1471 self._session.xenapi.VDI.add_to_sm_config(
1472 vdi_ref, 'activating', 'True')
1473 host_key = "host_%s" % host_ref
1474 assert host_key not in sm_config
1475 self._session.xenapi.VDI.add_to_sm_config(vdi_ref, host_key,
1476 attach_mode)
1477 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1478 if 'paused' in sm_config or 'relinking' in sm_config:
1479 util.SMlog("Found %s key, aborting" % (
1480 'paused' if 'paused' in sm_config else 'relinking'))
1481 self._session.xenapi.VDI.remove_from_sm_config(vdi_ref, host_key)
1482 self._session.xenapi.VDI.remove_from_sm_config(
1483 vdi_ref, 'activating')
1484 return False
1485 util.SMlog("Activate lock succeeded")
1486 return True
1488 def _check_tag(self, vdi_uuid):
1489 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1490 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1491 if 'paused' in sm_config:
1492 util.SMlog("Paused key found [%s]" % sm_config)
1493 return False
1494 return True
1496 def _remove_tag(self, vdi_uuid):
1497 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1498 host_ref = self._session.xenapi.host.get_by_uuid(util.get_this_host())
1499 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1500 host_key = "host_%s" % host_ref
1501 if host_key in sm_config:
1502 self._session.xenapi.VDI.remove_from_sm_config(vdi_ref, host_key)
1503 util.SMlog("Removed host key %s for %s" % (host_key, vdi_uuid))
1504 else:
1505 util.SMlog("_remove_tag: host key %s not found, ignore" % host_key)
1507 def _get_pool_config(self, pool_name):
1508 pool_info = dict()
1509 vdi_ref = self.target.vdi.sr.srcmd.params.get('vdi_ref')
1510 if not vdi_ref: 1510 ↛ 1513line 1510 didn't jump to line 1513, because the condition on line 1510 was never true
1511 # attach_from_config context: HA disks don't need to be in any
1512 # special pool
1513 return pool_info
1515 sr_ref = self.target.vdi.sr.srcmd.params.get('sr_ref')
1516 sr_config = self._session.xenapi.SR.get_other_config(sr_ref)
1517 vdi_config = self._session.xenapi.VDI.get_other_config(vdi_ref)
1518 pool_size_str = sr_config.get(POOL_SIZE_KEY)
1519 pool_name_override = vdi_config.get(POOL_NAME_KEY)
1520 if pool_name_override: 1520 ↛ 1525line 1520 didn't jump to line 1525, because the condition on line 1520 was never false
1521 pool_name = pool_name_override
1522 pool_size_override = vdi_config.get(POOL_SIZE_KEY)
1523 if pool_size_override: 1523 ↛ 1525line 1523 didn't jump to line 1525, because the condition on line 1523 was never false
1524 pool_size_str = pool_size_override
1525 pool_size = 0
1526 if pool_size_str: 1526 ↛ 1536line 1526 didn't jump to line 1536, because the condition on line 1526 was never false
1527 try:
1528 pool_size = int(pool_size_str)
1529 if pool_size < 1 or pool_size > MAX_FULL_RINGS: 1529 ↛ 1530line 1529 didn't jump to line 1530, because the condition on line 1529 was never true
1530 raise ValueError("outside of range")
1531 pool_size = NUM_PAGES_PER_RING * pool_size
1532 except ValueError:
1533 util.SMlog("Error: invalid mem-pool-size %s" % pool_size_str)
1534 pool_size = 0
1536 pool_info["mem-pool"] = pool_name
1537 if pool_size: 1537 ↛ 1540line 1537 didn't jump to line 1540, because the condition on line 1537 was never false
1538 pool_info["mem-pool-size"] = str(pool_size)
1540 return pool_info
1542 def linkNBD(self, sr_uuid, vdi_uuid):
1543 if self.tap:
1544 nbd_path = '/run/blktap-control/nbd%d.%d' % (int(self.tap.pid),
1545 int(self.tap.minor))
1546 VDI.NBDLink.from_uuid(sr_uuid, vdi_uuid).mklink(nbd_path)
1548 def attach(self, sr_uuid, vdi_uuid, writable, activate=False, caching_params={}):
1549 """Return/dev/sm/backend symlink path"""
1550 self.xenstore_data.update(self._get_pool_config(sr_uuid))
1551 if not self.target.has_cap("ATOMIC_PAUSE") or activate:
1552 util.SMlog("Attach & activate")
1553 self._attach(sr_uuid, vdi_uuid)
1554 dev_path = self._activate(sr_uuid, vdi_uuid,
1555 {"rdonly": not writable})
1556 self.BackendLink.from_uuid(sr_uuid, vdi_uuid).mklink(dev_path)
1557 self.linkNBD(sr_uuid, vdi_uuid)
1559 # Return backend/ link
1560 back_path = self.BackendLink.from_uuid(sr_uuid, vdi_uuid).path()
1561 if self.tap_wanted():
1562 # Only have NBD if we also have a tap
1563 nbd_path = "nbd:unix:{}:exportname={}".format(
1564 VDI.NBDLink.from_uuid(sr_uuid, vdi_uuid).path(),
1565 vdi_uuid)
1566 else:
1567 nbd_path = ""
1569 options = {"rdonly": not writable}
1570 options.update(caching_params)
1571 o_direct, o_direct_reason = self.get_o_direct_capability(options)
1572 struct = {'params': back_path,
1573 'params_nbd': nbd_path,
1574 'o_direct': o_direct,
1575 'o_direct_reason': o_direct_reason,
1576 'xenstore_data': self.xenstore_data}
1577 util.SMlog('result: %s' % struct)
1579 try:
1580 f = open("%s.attach_info" % back_path, 'a')
1581 f.write(xmlrpc.client.dumps((struct, ), "", True))
1582 f.close()
1583 except:
1584 pass
1586 return xmlrpc.client.dumps((struct, ), "", True)
1588 def activate(self, sr_uuid, vdi_uuid, writable, caching_params):
1589 util.SMlog("blktap2.activate")
1590 options = {"rdonly": not writable}
1591 options.update(caching_params)
1593 sr_ref = self.target.vdi.sr.srcmd.params.get('sr_ref')
1594 sr_other_config = self._session.xenapi.SR.get_other_config(sr_ref)
1595 for i in range(self.ATTACH_DETACH_RETRY_SECS): 1595 ↛ 1602line 1595 didn't jump to line 1602, because the loop on line 1595 didn't complete
1596 try:
1597 if self._activate_locked(sr_uuid, vdi_uuid, options):
1598 return
1599 except util.SRBusyException:
1600 util.SMlog("SR locked, retrying")
1601 time.sleep(1)
1602 raise util.SMException("VDI %s locked" % vdi_uuid)
1604 @locking("VDIUnavailable")
1605 def _activate_locked(self, sr_uuid, vdi_uuid, options):
1606 """Wraps target.activate and adds a tapdisk"""
1608 #util.SMlog("VDI.activate %s" % vdi_uuid)
1609 refresh = False
1610 if self.tap_wanted(): 1610 ↛ 1615line 1610 didn't jump to line 1615, because the condition on line 1610 was never false
1611 if not self._add_tag(vdi_uuid, not options["rdonly"]):
1612 return False
1613 refresh = True
1615 try:
1616 if refresh: 1616 ↛ 1627line 1616 didn't jump to line 1627, because the condition on line 1616 was never false
1617 # it is possible that while the VDI was paused some of its
1618 # attributes have changed (e.g. its size if it was inflated; or its
1619 # path if it was leaf-coalesced onto a raw LV), so refresh the
1620 # object completely
1621 params = self.target.vdi.sr.srcmd.params
1622 target = sm.VDI.from_uuid(self.target.vdi.session, vdi_uuid)
1623 target.sr.srcmd.params = params
1624 driver_info = target.sr.srcmd.driver_info
1625 self.target = self.TargetDriver(target, driver_info)
1627 util.fistpoint.activate_custom_fn( 1627 ↛ exitline 1627 didn't jump to the function exit
1628 "blktap_activate_inject_failure",
1629 lambda: util.inject_failure())
1631 # Attach the physical node
1632 if self.target.has_cap("ATOMIC_PAUSE"): 1632 ↛ 1635line 1632 didn't jump to line 1635, because the condition on line 1632 was never false
1633 self._attach(sr_uuid, vdi_uuid)
1635 vdi_type = self.target.get_vdi_type()
1637 # Take lvchange-p Lock before running
1638 # tap-ctl open
1639 # Needed to avoid race with lvchange -p which is
1640 # now taking the same lock
1641 # This is a fix for CA-155766
1642 if hasattr(self.target.vdi.sr, 'DRIVER_TYPE') and \ 1642 ↛ 1645line 1642 didn't jump to line 1645, because the condition on line 1642 was never true
1643 self.target.vdi.sr.DRIVER_TYPE == 'lvhd' and \
1644 vdi_type == vhdutil.VDI_TYPE_VHD:
1645 lock = Lock("lvchange-p", lvhdutil.NS_PREFIX_LVM + sr_uuid)
1646 lock.acquire()
1648 # When we attach a static VDI for HA, we cannot communicate with
1649 # xapi, because has not started yet. These VDIs are raw.
1650 if vdi_type != vhdutil.VDI_TYPE_RAW: 1650 ↛ 1661line 1650 didn't jump to line 1661, because the condition on line 1650 was never false
1651 session = self.target.vdi.session
1652 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1653 # pylint: disable=used-before-assignment
1654 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1655 if 'key_hash' in sm_config: 1655 ↛ 1656line 1655 didn't jump to line 1656, because the condition on line 1655 was never true
1656 key_hash = sm_config['key_hash']
1657 options['key_hash'] = key_hash
1658 options['vdi_uuid'] = vdi_uuid
1659 util.SMlog('Using key with hash {} for VDI {}'.format(key_hash, vdi_uuid))
1660 # Activate the physical node
1661 dev_path = self._activate(sr_uuid, vdi_uuid, options)
1663 if hasattr(self.target.vdi.sr, 'DRIVER_TYPE') and \ 1663 ↛ 1666line 1663 didn't jump to line 1666, because the condition on line 1663 was never true
1664 self.target.vdi.sr.DRIVER_TYPE == 'lvhd' and \
1665 self.target.get_vdi_type() == vhdutil.VDI_TYPE_VHD:
1666 lock.release()
1667 except:
1668 util.SMlog("Exception in activate/attach")
1669 if self.tap_wanted():
1670 util.fistpoint.activate_custom_fn(
1671 "blktap_activate_error_handling",
1672 lambda: time.sleep(30))
1673 while True:
1674 try:
1675 self._remove_tag(vdi_uuid)
1676 break
1677 except xmlrpc.client.ProtocolError as e:
1678 # If there's a connection error, keep trying forever.
1679 if e.errcode == http.HTTPStatus.INTERNAL_SERVER_ERROR.value:
1680 continue
1681 else:
1682 util.SMlog('failed to remove tag: %s' % e)
1683 break
1684 except Exception as e:
1685 util.SMlog('failed to remove tag: %s' % e)
1686 break
1687 raise
1688 finally:
1689 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1690 self._session.xenapi.VDI.remove_from_sm_config(
1691 vdi_ref, 'activating')
1692 util.SMlog("Removed activating flag from %s" % vdi_uuid) 1692 ↛ exitline 1692 didn't except from function '_activate_locked', because the raise on line 1687 wasn't executed
1694 # Link result to backend/
1695 self.BackendLink.from_uuid(sr_uuid, vdi_uuid).mklink(dev_path)
1696 self.linkNBD(sr_uuid, vdi_uuid)
1697 return True
1699 def _activate(self, sr_uuid, vdi_uuid, options):
1700 vdi_options = self.target.activate(sr_uuid, vdi_uuid)
1702 dev_path = self.setup_cache(sr_uuid, vdi_uuid, options)
1703 if not dev_path: 1703 ↛ 1717line 1703 didn't jump to line 1717, because the condition on line 1703 was never false
1704 phy_path = self.PhyLink.from_uuid(sr_uuid, vdi_uuid).readlink()
1705 # Maybe launch a tapdisk on the physical link
1706 if self.tap_wanted(): 1706 ↛ 1715line 1706 didn't jump to line 1715, because the condition on line 1706 was never false
1707 vdi_type = self.target.get_vdi_type()
1708 options["o_direct"] = self.get_o_direct_capability(options)[0]
1709 if vdi_options: 1709 ↛ 1711line 1709 didn't jump to line 1711, because the condition on line 1709 was never false
1710 options.update(vdi_options)
1711 dev_path, self.tap = self._tap_activate(phy_path, vdi_type,
1712 sr_uuid, options,
1713 self._get_pool_config(sr_uuid).get("mem-pool-size"))
1714 else:
1715 dev_path = phy_path # Just reuse phy
1717 return dev_path
1719 def _attach(self, sr_uuid, vdi_uuid):
1720 attach_info = xmlrpc.client.loads(self.target.attach(sr_uuid, vdi_uuid))[0][0]
1721 params = attach_info['params']
1722 xenstore_data = attach_info['xenstore_data']
1723 phy_path = util.to_plain_string(params)
1724 self.xenstore_data.update(xenstore_data)
1725 # Save it to phy/
1726 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).mklink(phy_path)
1728 def deactivate(self, sr_uuid, vdi_uuid, caching_params):
1729 util.SMlog("blktap2.deactivate")
1730 for i in range(self.ATTACH_DETACH_RETRY_SECS):
1731 try:
1732 if self._deactivate_locked(sr_uuid, vdi_uuid, caching_params):
1733 return
1734 except util.SRBusyException as e:
1735 util.SMlog("SR locked, retrying")
1736 time.sleep(1)
1737 raise util.SMException("VDI %s locked" % vdi_uuid)
1739 @locking("VDIUnavailable")
1740 def _deactivate_locked(self, sr_uuid, vdi_uuid, caching_params):
1741 """Wraps target.deactivate and removes a tapdisk"""
1743 #util.SMlog("VDI.deactivate %s" % vdi_uuid)
1744 if self.tap_wanted() and not self._check_tag(vdi_uuid):
1745 return False
1747 self._deactivate(sr_uuid, vdi_uuid, caching_params)
1748 if self.target.has_cap("ATOMIC_PAUSE"):
1749 self._detach(sr_uuid, vdi_uuid)
1750 if self.tap_wanted():
1751 self._remove_tag(vdi_uuid)
1753 return True
1755 def _resetPhylink(self, sr_uuid, vdi_uuid, path):
1756 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).mklink(path)
1758 def detach(self, sr_uuid, vdi_uuid, deactivate=False, caching_params={}):
1759 if not self.target.has_cap("ATOMIC_PAUSE") or deactivate:
1760 util.SMlog("Deactivate & detach")
1761 self._deactivate(sr_uuid, vdi_uuid, caching_params)
1762 self._detach(sr_uuid, vdi_uuid)
1763 else:
1764 pass # nothing to do
1766 def _deactivate(self, sr_uuid, vdi_uuid, caching_params):
1767 import VDI as sm
1769 # Shutdown tapdisk
1770 back_link = self.BackendLink.from_uuid(sr_uuid, vdi_uuid)
1772 if not util.pathexists(back_link.path()):
1773 util.SMlog("Backend path %s does not exist" % back_link.path())
1774 return
1776 try:
1777 attach_info_path = "%s.attach_info" % (back_link.path())
1778 os.unlink(attach_info_path)
1779 except:
1780 util.SMlog("unlink of attach_info failed")
1782 try:
1783 major, minor = back_link.rdev()
1784 except self.DeviceNode.NotABlockDevice:
1785 pass
1786 else:
1787 if major == Tapdisk.major():
1788 self._tap_deactivate(minor)
1789 self.remove_cache(sr_uuid, vdi_uuid, caching_params)
1791 # Remove the backend link
1792 back_link.unlink()
1793 VDI.NBDLink.from_uuid(sr_uuid, vdi_uuid).unlink()
1795 # Deactivate & detach the physical node
1796 if self.tap_wanted() and self.target.vdi.session is not None:
1797 # it is possible that while the VDI was paused some of its
1798 # attributes have changed (e.g. its size if it was inflated; or its
1799 # path if it was leaf-coalesced onto a raw LV), so refresh the
1800 # object completely
1801 target = sm.VDI.from_uuid(self.target.vdi.session, vdi_uuid)
1802 driver_info = target.sr.srcmd.driver_info
1803 self.target = self.TargetDriver(target, driver_info)
1805 self.target.deactivate(sr_uuid, vdi_uuid)
1807 def _detach(self, sr_uuid, vdi_uuid):
1808 self.target.detach(sr_uuid, vdi_uuid)
1810 # Remove phy/
1811 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).unlink()
1813 def _updateCacheRecord(self, session, vdi_uuid, on_boot, caching):
1814 # Remove existing VDI.sm_config fields
1815 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1816 for key in ["on_boot", "caching"]:
1817 session.xenapi.VDI.remove_from_sm_config(vdi_ref, key)
1818 if not on_boot is None:
1819 session.xenapi.VDI.add_to_sm_config(vdi_ref, 'on_boot', on_boot)
1820 if not caching is None:
1821 session.xenapi.VDI.add_to_sm_config(vdi_ref, 'caching', caching)
1823 def setup_cache(self, sr_uuid, vdi_uuid, params):
1824 if params.get(self.CONF_KEY_ALLOW_CACHING) != "true": 1824 ↛ 1827line 1824 didn't jump to line 1827, because the condition on line 1824 was never false
1825 return
1827 util.SMlog("Requested local caching")
1828 if not self.target.has_cap("SR_CACHING"):
1829 util.SMlog("Error: local caching not supported by this SR")
1830 return
1832 scratch_mode = False
1833 if params.get(self.CONF_KEY_MODE_ON_BOOT) == "reset":
1834 scratch_mode = True
1835 util.SMlog("Requested scratch mode")
1836 if not self.target.has_cap("VDI_RESET_ON_BOOT/2"):
1837 util.SMlog("Error: scratch mode not supported by this SR")
1838 return
1840 dev_path = None
1841 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR)
1842 if not local_sr_uuid:
1843 util.SMlog("ERROR: Local cache SR not specified, not enabling")
1844 return
1845 dev_path = self._setup_cache(self._session, sr_uuid, vdi_uuid,
1846 local_sr_uuid, scratch_mode, params)
1848 if dev_path:
1849 self._updateCacheRecord(self._session, self.target.vdi.uuid,
1850 params.get(self.CONF_KEY_MODE_ON_BOOT),
1851 params.get(self.CONF_KEY_ALLOW_CACHING))
1853 return dev_path
1855 def alert_no_cache(self, session, vdi_uuid, cache_sr_uuid, err):
1856 vm_uuid = None
1857 vm_label = ""
1858 try:
1859 cache_sr_ref = session.xenapi.SR.get_by_uuid(cache_sr_uuid)
1860 cache_sr_rec = session.xenapi.SR.get_record(cache_sr_ref)
1861 cache_sr_label = cache_sr_rec.get("name_label")
1863 host_ref = session.xenapi.host.get_by_uuid(util.get_this_host())
1864 host_rec = session.xenapi.host.get_record(host_ref)
1865 host_label = host_rec.get("name_label")
1867 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1868 vbds = session.xenapi.VBD.get_all_records_where( \
1869 "field \"VDI\" = \"%s\"" % vdi_ref)
1870 for vbd_rec in vbds.values():
1871 vm_ref = vbd_rec.get("VM")
1872 vm_rec = session.xenapi.VM.get_record(vm_ref)
1873 vm_uuid = vm_rec.get("uuid")
1874 vm_label = vm_rec.get("name_label")
1875 except:
1876 util.logException("alert_no_cache")
1878 alert_obj = "SR"
1879 alert_uuid = str(cache_sr_uuid)
1880 alert_str = "No space left in Local Cache SR %s" % cache_sr_uuid
1881 if vm_uuid:
1882 alert_obj = "VM"
1883 alert_uuid = vm_uuid
1884 reason = ""
1885 if err == errno.ENOSPC:
1886 reason = "because there is no space left"
1887 alert_str = "The VM \"%s\" is not using IntelliCache %s on the Local Cache SR (\"%s\") on host \"%s\"" % \
1888 (vm_label, reason, cache_sr_label, host_label)
1890 util.SMlog("Creating alert: (%s, %s, \"%s\")" % \
1891 (alert_obj, alert_uuid, alert_str))
1892 session.xenapi.message.create("No space left in local cache", "3",
1893 alert_obj, alert_uuid, alert_str)
1895 def _setup_cache(self, session, sr_uuid, vdi_uuid, local_sr_uuid,
1896 scratch_mode, options):
1897 import SR
1898 import EXTSR
1899 import NFSSR
1900 from lock import Lock
1901 from FileSR import FileVDI
1903 parent_uuid = vhdutil.getParent(self.target.vdi.path,
1904 FileVDI.extractUuid)
1905 if not parent_uuid:
1906 util.SMlog("ERROR: VDI %s has no parent, not enabling" % \
1907 self.target.vdi.uuid)
1908 return
1910 util.SMlog("Setting up cache")
1911 parent_uuid = parent_uuid.strip()
1912 shared_target = NFSSR.NFSFileVDI(self.target.vdi.sr, parent_uuid)
1914 if shared_target.parent:
1915 util.SMlog("ERROR: Parent VDI %s has parent, not enabling" %
1916 shared_target.uuid)
1917 return
1919 SR.registerSR(EXTSR.EXTSR)
1920 local_sr = SR.SR.from_uuid(session, local_sr_uuid)
1922 lock = Lock(self.LOCK_CACHE_SETUP, parent_uuid)
1923 lock.acquire()
1925 # read cache
1926 read_cache_path = "%s/%s.vhdcache" % (local_sr.path, shared_target.uuid)
1927 if util.pathexists(read_cache_path):
1928 util.SMlog("Read cache node (%s) already exists, not creating" % \
1929 read_cache_path)
1930 else:
1931 try:
1932 vhdutil.snapshot(read_cache_path, shared_target.path, False)
1933 except util.CommandException as e:
1934 util.SMlog("Error creating parent cache: %s" % e)
1935 self.alert_no_cache(session, vdi_uuid, local_sr_uuid, e.code)
1936 return None
1938 # local write node
1939 leaf_size = vhdutil.getSizeVirt(self.target.vdi.path)
1940 local_leaf_path = "%s/%s.vhdcache" % \
1941 (local_sr.path, self.target.vdi.uuid)
1942 if util.pathexists(local_leaf_path):
1943 util.SMlog("Local leaf node (%s) already exists, deleting" % \
1944 local_leaf_path)
1945 os.unlink(local_leaf_path)
1946 try:
1947 vhdutil.snapshot(local_leaf_path, read_cache_path, False,
1948 msize=leaf_size // 1024 // 1024, checkEmpty=False)
1949 except util.CommandException as e:
1950 util.SMlog("Error creating leaf cache: %s" % e)
1951 self.alert_no_cache(session, vdi_uuid, local_sr_uuid, e.code)
1952 return None
1954 local_leaf_size = vhdutil.getSizeVirt(local_leaf_path)
1955 if leaf_size > local_leaf_size:
1956 util.SMlog("Leaf size %d > local leaf cache size %d, resizing" %
1957 (leaf_size, local_leaf_size))
1958 vhdutil.setSizeVirtFast(local_leaf_path, leaf_size)
1960 vdi_type = self.target.get_vdi_type()
1962 prt_tapdisk = Tapdisk.find_by_path(read_cache_path)
1963 if not prt_tapdisk:
1964 parent_options = copy.deepcopy(options)
1965 parent_options["rdonly"] = False
1966 parent_options["lcache"] = True
1968 blktap = Blktap.allocate()
1969 try:
1970 blktap.set_pool_name("lcache-parent-pool-%s" % blktap.minor)
1971 # no need to change pool_size since each parent tapdisk is in
1972 # its own pool
1973 prt_tapdisk = \
1974 Tapdisk.launch_on_tap(blktap, read_cache_path,
1975 'vhd', parent_options)
1976 except:
1977 blktap.free()
1978 raise
1980 secondary = "%s:%s" % (self.target.get_vdi_type(),
1981 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).readlink())
1983 util.SMlog("Parent tapdisk: %s" % prt_tapdisk)
1984 leaf_tapdisk = Tapdisk.find_by_path(local_leaf_path)
1985 if not leaf_tapdisk:
1986 blktap = Blktap.allocate()
1987 child_options = copy.deepcopy(options)
1988 child_options["rdonly"] = False
1989 child_options["lcache"] = False
1990 child_options["existing_prt"] = prt_tapdisk.minor
1991 child_options["secondary"] = secondary
1992 child_options["standby"] = scratch_mode
1993 try:
1994 leaf_tapdisk = \
1995 Tapdisk.launch_on_tap(blktap, local_leaf_path,
1996 'vhd', child_options)
1997 except:
1998 blktap.free()
1999 raise
2001 lock.release()
2003 util.SMlog("Local read cache: %s, local leaf: %s" % \
2004 (read_cache_path, local_leaf_path))
2006 self.tap = leaf_tapdisk
2007 return leaf_tapdisk.get_devpath()
2009 def remove_cache(self, sr_uuid, vdi_uuid, params):
2010 if not self.target.has_cap("SR_CACHING"):
2011 return
2013 caching = params.get(self.CONF_KEY_ALLOW_CACHING) == "true"
2015 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR)
2016 if caching and not local_sr_uuid:
2017 util.SMlog("ERROR: Local cache SR not specified, ignore")
2018 return
2020 if caching:
2021 self._remove_cache(self._session, local_sr_uuid)
2023 if self._session is not None:
2024 self._updateCacheRecord(self._session, self.target.vdi.uuid, None, None)
2026 def _is_tapdisk_in_use(self, minor):
2027 retVal, links, sockets = util.findRunningProcessOrOpenFile("tapdisk")
2028 if not retVal:
2029 # err on the side of caution
2030 return True
2032 for link in links:
2033 if link.find("tapdev%d" % minor) != -1:
2034 return True
2036 socket_re = re.compile(r'^/.*/nbd\d+\.%d' % minor)
2037 for s in sockets:
2038 if socket_re.match(s):
2039 return True
2041 return False
2043 def _remove_cache(self, session, local_sr_uuid):
2044 import SR
2045 import EXTSR
2046 import NFSSR
2047 from lock import Lock
2048 from FileSR import FileVDI
2050 parent_uuid = vhdutil.getParent(self.target.vdi.path,
2051 FileVDI.extractUuid)
2052 if not parent_uuid:
2053 util.SMlog("ERROR: No parent for VDI %s, ignore" % \
2054 self.target.vdi.uuid)
2055 return
2057 util.SMlog("Tearing down the cache")
2059 parent_uuid = parent_uuid.strip()
2060 shared_target = NFSSR.NFSFileVDI(self.target.vdi.sr, parent_uuid)
2062 SR.registerSR(EXTSR.EXTSR)
2063 local_sr = SR.SR.from_uuid(session, local_sr_uuid)
2065 lock = Lock(self.LOCK_CACHE_SETUP, parent_uuid)
2066 lock.acquire()
2068 # local write node
2069 local_leaf_path = "%s/%s.vhdcache" % \
2070 (local_sr.path, self.target.vdi.uuid)
2071 if util.pathexists(local_leaf_path):
2072 util.SMlog("Deleting local leaf node %s" % local_leaf_path)
2073 os.unlink(local_leaf_path)
2075 read_cache_path = "%s/%s.vhdcache" % (local_sr.path, shared_target.uuid)
2076 prt_tapdisk = Tapdisk.find_by_path(read_cache_path)
2077 if not prt_tapdisk:
2078 util.SMlog("Parent tapdisk not found")
2079 elif not self._is_tapdisk_in_use(prt_tapdisk.minor):
2080 util.SMlog("Parent tapdisk not in use: shutting down %s" % \
2081 read_cache_path)
2082 try:
2083 prt_tapdisk.shutdown()
2084 except:
2085 util.logException("shutting down parent tapdisk")
2086 else:
2087 util.SMlog("Parent tapdisk still in use: %s" % read_cache_path)
2088 # the parent cache files are removed during the local SR's background
2089 # GC run
2091 lock.release()
2093PythonKeyError = KeyError
2096class UEventHandler(object):
2098 def __init__(self):
2099 self._action = None
2101 class KeyError(PythonKeyError):
2102 def __init__(self, args):
2103 super().__init__(args)
2104 self.key = args[0]
2106 def __str__(self):
2107 return \
2108 "Key '%s' missing in environment. " % self.key + \
2109 "Not called in udev context?"
2111 @classmethod
2112 def getenv(cls, key):
2113 try:
2114 return os.environ[key]
2115 except KeyError as e:
2116 raise cls.KeyError(e.args[0])
2118 def get_action(self):
2119 if not self._action:
2120 self._action = self.getenv('ACTION')
2121 return self._action
2123 class UnhandledEvent(Exception):
2125 def __init__(self, event, handler):
2126 self.event = event
2127 self.handler = handler
2129 def __str__(self):
2130 return "Uevent '%s' not handled by %s" % \
2131 (self.event, self.handler.__class__.__name__)
2133 ACTIONS = {}
2135 def run(self):
2137 action = self.get_action()
2138 try:
2139 fn = self.ACTIONS[action]
2140 except KeyError:
2141 raise self.UnhandledEvent(action, self)
2143 return fn(self)
2145 def __str__(self):
2146 try:
2147 action = self.get_action()
2148 except:
2149 action = None
2150 return "%s[%s]" % (self.__class__.__name__, action)
2153class __BlktapControl(ClassDevice):
2154 SYSFS_CLASSTYPE = "misc"
2156 def __init__(self):
2157 ClassDevice.__init__(self)
2158 self._default_pool = None
2160 def sysfs_devname(self):
2161 return "blktap!control"
2163 class DefaultPool(Attribute):
2164 SYSFS_NODENAME = "default_pool"
2166 def get_default_pool_attr(self):
2167 if not self._default_pool:
2168 self._default_pool = self.DefaultPool.from_kobject(self)
2169 return self._default_pool
2171 def get_default_pool_name(self):
2172 return self.get_default_pool_attr().readline()
2174 def set_default_pool_name(self, name):
2175 self.get_default_pool_attr().writeline(name)
2177 def get_default_pool(self):
2178 return BlktapControl.get_pool(self.get_default_pool_name())
2180 def set_default_pool(self, pool):
2181 self.set_default_pool_name(pool.name)
2183 class NoSuchPool(Exception):
2184 def __init__(self, name):
2185 self.name = name
2187 def __str__(self):
2188 return "No such pool: {}".format(self.name)
2190 def get_pool(self, name):
2191 path = "%s/pools/%s" % (self.sysfs_path(), name)
2193 if not os.path.isdir(path):
2194 raise self.NoSuchPool(name)
2196 return PagePool(path)
2198BlktapControl = __BlktapControl()
2201class PagePool(KObject):
2203 def __init__(self, path):
2204 self.path = path
2205 self._size = None
2207 def sysfs_path(self):
2208 return self.path
2210 class Size(Attribute):
2211 SYSFS_NODENAME = "size"
2213 def get_size_attr(self):
2214 if not self._size:
2215 self._size = self.Size.from_kobject(self)
2216 return self._size
2218 def set_size(self, pages):
2219 pages = str(pages)
2220 self.get_size_attr().writeline(pages)
2222 def get_size(self):
2223 pages = self.get_size_attr().readline()
2224 return int(pages)
2227class BusDevice(KObject):
2229 SYSFS_BUSTYPE = None
2231 @classmethod
2232 def sysfs_bus_path(cls):
2233 return "/sys/bus/%s" % cls.SYSFS_BUSTYPE
2235 def sysfs_path(self):
2236 path = "%s/devices/%s" % (self.sysfs_bus_path(),
2237 self.sysfs_devname())
2239 return path
2242class XenbusDevice(BusDevice):
2243 """Xenbus device, in XS and sysfs"""
2245 XBT_NIL = ""
2247 XENBUS_DEVTYPE = None
2249 def __init__(self, domid, devid):
2250 self.domid = int(domid)
2251 self.devid = int(devid)
2252 self._xbt = XenbusDevice.XBT_NIL
2254 import xen.lowlevel.xs # pylint: disable=import-error
2255 self.xs = xen.lowlevel.xs.xs()
2257 def xs_path(self, key=None):
2258 path = "backend/%s/%d/%d" % (self.XENBUS_DEVTYPE,
2259 self.domid,
2260 self.devid)
2261 if key is not None:
2262 path = "%s/%s" % (path, key)
2264 return path
2266 def _log(self, prio, msg):
2267 syslog(prio, msg)
2269 def info(self, msg):
2270 self._log(_syslog.LOG_INFO, msg)
2272 def warn(self, msg):
2273 self._log(_syslog.LOG_WARNING, "WARNING: " + msg)
2275 def _xs_read_path(self, path):
2276 val = self.xs.read(self._xbt, path)
2277 #self.info("read %s = '%s'" % (path, val))
2278 return val
2280 def _xs_write_path(self, path, val):
2281 self.xs.write(self._xbt, path, val)
2282 self.info("wrote %s = '%s'" % (path, val))
2284 def _xs_rm_path(self, path):
2285 self.xs.rm(self._xbt, path)
2286 self.info("removed %s" % path)
2288 def read(self, key):
2289 return self._xs_read_path(self.xs_path(key))
2291 def has_xs_key(self, key):
2292 return self.read(key) is not None
2294 def write(self, key, val):
2295 self._xs_write_path(self.xs_path(key), val)
2297 def rm(self, key):
2298 self._xs_rm_path(self.xs_path(key))
2300 def exists(self):
2301 return self.has_xs_key(None)
2303 def begin(self):
2304 assert(self._xbt == XenbusDevice.XBT_NIL)
2305 self._xbt = self.xs.transaction_start()
2307 def commit(self):
2308 ok = self.xs.transaction_end(self._xbt, 0)
2309 self._xbt = XenbusDevice.XBT_NIL
2310 return ok
2312 def abort(self):
2313 ok = self.xs.transaction_end(self._xbt, 1)
2314 assert(ok == True)
2315 self._xbt = XenbusDevice.XBT_NIL
2317 def create_physical_device(self):
2318 """The standard protocol is: toolstack writes 'params', linux hotplug
2319 script translates this into physical-device=%x:%x"""
2320 if self.has_xs_key("physical-device"):
2321 return
2322 try:
2323 params = self.read("params")
2324 frontend = self.read("frontend")
2325 is_cdrom = self._xs_read_path("%s/device-type") == "cdrom"
2326 # We don't have PV drivers for CDROM devices, so we prevent blkback
2327 # from opening the physical-device
2328 if not(is_cdrom):
2329 major_minor = os.stat(params).st_rdev
2330 major, minor = divmod(major_minor, 256)
2331 self.write("physical-device", "%x:%x" % (major, minor))
2332 except:
2333 util.logException("BLKTAP2:create_physical_device")
2335 def signal_hotplug(self, online=True):
2336 xapi_path = "/xapi/%d/hotplug/%s/%d/hotplug" % (self.domid,
2337 self.XENBUS_DEVTYPE,
2338 self.devid)
2339 upstream_path = self.xs_path("hotplug-status")
2340 if online:
2341 self._xs_write_path(xapi_path, "online")
2342 self._xs_write_path(upstream_path, "connected")
2343 else:
2344 self._xs_rm_path(xapi_path)
2345 self._xs_rm_path(upstream_path)
2347 def sysfs_devname(self):
2348 return "%s-%d-%d" % (self.XENBUS_DEVTYPE,
2349 self.domid, self.devid)
2351 def __str__(self):
2352 return self.sysfs_devname()
2354 @classmethod
2355 def find(cls):
2356 pattern = "/sys/bus/%s/devices/%s*" % (cls.SYSFS_BUSTYPE,
2357 cls.XENBUS_DEVTYPE)
2358 for path in glob.glob(pattern):
2360 name = os.path.basename(path)
2361 (_type, domid, devid) = name.split('-')
2363 yield cls(domid, devid)
2366class XenBackendDevice(XenbusDevice):
2367 """Xenbus backend device"""
2368 SYSFS_BUSTYPE = "xen-backend"
2370 @classmethod
2371 def from_xs_path(cls, _path):
2372 (_backend, _type, domid, devid) = _path.split('/')
2374 assert _backend == 'backend'
2375 assert _type == cls.XENBUS_DEVTYPE
2377 domid = int(domid)
2378 devid = int(devid)
2380 return cls(domid, devid)
2383class Blkback(XenBackendDevice):
2384 """A blkback VBD"""
2386 XENBUS_DEVTYPE = "vbd"
2388 def __init__(self, domid, devid):
2389 XenBackendDevice.__init__(self, domid, devid)
2390 self._phy = None
2391 self._vdi_uuid = None
2392 self._q_state = None
2393 self._q_events = None
2395 class XenstoreValueError(Exception):
2396 KEY = None
2398 def __init__(self, vbd, _str):
2399 self.vbd = vbd
2400 self.str = _str
2402 def __str__(self):
2403 return "Backend %s " % self.vbd + \
2404 "has %s = %s" % (self.KEY, self.str)
2406 class PhysicalDeviceError(XenstoreValueError):
2407 KEY = "physical-device"
2409 class PhysicalDevice(object):
2411 def __init__(self, major, minor):
2412 self.major = int(major)
2413 self.minor = int(minor)
2415 @classmethod
2416 def from_xbdev(cls, xbdev):
2418 phy = xbdev.read("physical-device")
2420 try:
2421 major, minor = phy.split(':')
2422 major = int(major, 0x10)
2423 minor = int(minor, 0x10)
2424 except Exception as e:
2425 raise xbdev.PhysicalDeviceError(xbdev, phy)
2427 return cls(major, minor)
2429 def makedev(self):
2430 return os.makedev(self.major, self.minor)
2432 def is_tap(self):
2433 return self.major == Tapdisk.major()
2435 def __str__(self):
2436 return "%s:%s" % (self.major, self.minor)
2438 def __eq__(self, other):
2439 return \
2440 self.major == other.major and \
2441 self.minor == other.minor
2443 def get_physical_device(self):
2444 if not self._phy:
2445 self._phy = self.PhysicalDevice.from_xbdev(self)
2446 return self._phy
2448 class QueueEvents(Attribute):
2449 """Blkback sysfs node to select queue-state event
2450 notifications emitted."""
2452 SYSFS_NODENAME = "queue_events"
2454 QUEUE_RUNNING = (1 << 0)
2455 QUEUE_PAUSE_DONE = (1 << 1)
2456 QUEUE_SHUTDOWN_DONE = (1 << 2)
2457 QUEUE_PAUSE_REQUEST = (1 << 3)
2458 QUEUE_SHUTDOWN_REQUEST = (1 << 4)
2460 def get_mask(self):
2461 return int(self.readline(), 0x10)
2463 def set_mask(self, mask):
2464 self.writeline("0x%x" % mask)
2466 def get_queue_events(self):
2467 if not self._q_events:
2468 self._q_events = self.QueueEvents.from_kobject(self)
2469 return self._q_events
2471 def get_vdi_uuid(self):
2472 if not self._vdi_uuid:
2473 self._vdi_uuid = self.read("sm-data/vdi-uuid")
2474 return self._vdi_uuid
2476 def pause_requested(self):
2477 return self.has_xs_key("pause")
2479 def shutdown_requested(self):
2480 return self.has_xs_key("shutdown-request")
2482 def shutdown_done(self):
2483 return self.has_xs_key("shutdown-done")
2485 def running(self):
2486 return self.has_xs_key('queue-0/kthread-pid')
2488 @classmethod
2489 def find_by_physical_device(cls, phy):
2490 for dev in cls.find():
2491 try:
2492 _phy = dev.get_physical_device()
2493 except cls.PhysicalDeviceError:
2494 continue
2496 if _phy == phy:
2497 yield dev
2499 @classmethod
2500 def find_by_tap_minor(cls, minor):
2501 phy = cls.PhysicalDevice(Tapdisk.major(), minor)
2502 return cls.find_by_physical_device(phy)
2504 @classmethod
2505 def find_by_tap(cls, tapdisk):
2506 return cls.find_by_tap_minor(tapdisk.minor)
2508 def has_tap(self):
2510 if not self.can_tap():
2511 return False
2513 phy = self.get_physical_device()
2514 if phy:
2515 return phy.is_tap()
2517 return False
2519 def is_bare_hvm(self):
2520 """File VDIs for bare HVM. These are directly accessible by Qemu."""
2521 try:
2522 self.get_physical_device()
2524 except self.PhysicalDeviceError as e:
2525 vdi_type = self.read("type")
2527 self.info("HVM VDI: type=%s" % vdi_type)
2529 if e.str is not None or vdi_type != 'file':
2530 raise
2532 return True
2534 return False
2536 def can_tap(self):
2537 return not self.is_bare_hvm()
2540class BlkbackEventHandler(UEventHandler):
2542 LOG_FACILITY = _syslog.LOG_DAEMON
2544 def __init__(self, ident=None, action=None):
2545 if not ident:
2546 ident = self.__class__.__name__
2548 self.ident = ident
2549 self._vbd = None
2550 self._tapdisk = None
2552 UEventHandler.__init__(self)
2554 def run(self):
2556 self.xs_path = self.getenv('XENBUS_PATH')
2557 openlog(str(self), 0, self.LOG_FACILITY)
2559 UEventHandler.run(self)
2561 def __str__(self):
2563 try:
2564 path = self.xs_path
2565 except:
2566 path = None
2568 try:
2569 action = self.get_action()
2570 except:
2571 action = None
2573 return "%s[%s](%s)" % (self.ident, action, path)
2575 def _log(self, prio, msg):
2576 syslog(prio, msg)
2577 util.SMlog("%s: " % self + msg)
2579 def info(self, msg):
2580 self._log(_syslog.LOG_INFO, msg)
2582 def warn(self, msg):
2583 self._log(_syslog.LOG_WARNING, "WARNING: " + msg)
2585 def error(self, msg):
2586 self._log(_syslog.LOG_ERR, "ERROR: " + msg)
2588 def get_vbd(self):
2589 if not self._vbd:
2590 self._vbd = Blkback.from_xs_path(self.xs_path)
2591 return self._vbd
2593 def get_tapdisk(self):
2594 if not self._tapdisk:
2595 minor = self.get_vbd().get_physical_device().minor
2596 self._tapdisk = Tapdisk.from_minor(minor)
2597 return self._tapdisk
2598 #
2599 # Events
2600 #
2602 def __add(self):
2603 vbd = self.get_vbd()
2604 # Manage blkback transitions
2605 # self._manage_vbd()
2607 vbd.create_physical_device()
2609 vbd.signal_hotplug()
2611 @retried(backoff=.5, limit=10)
2612 def add(self):
2613 try:
2614 self.__add()
2615 except Attribute.NoSuchAttribute as e:
2616 #
2617 # FIXME: KOBJ_ADD is racing backend.probe, which
2618 # registers device attributes. So poll a little.
2619 #
2620 self.warn("%s, still trying." % e)
2621 raise RetryLoop.TransientFailure(e)
2623 def __change(self):
2624 vbd = self.get_vbd()
2626 # 1. Pause or resume tapdisk (if there is one)
2628 if vbd.has_tap():
2629 pass
2630 #self._pause_update_tap()
2632 # 2. Signal Xapi.VBD.pause/resume completion
2634 self._signal_xapi()
2636 def change(self):
2637 vbd = self.get_vbd()
2639 # NB. Beware of spurious change events between shutdown
2640 # completion and device removal. Also, Xapi.VM.migrate will
2641 # hammer a couple extra shutdown-requests into the source VBD.
2643 while True:
2644 vbd.begin()
2646 if not vbd.exists() or \
2647 vbd.shutdown_done():
2648 break
2650 self.__change()
2652 if vbd.commit():
2653 return
2655 vbd.abort()
2656 self.info("spurious uevent, ignored.")
2658 def remove(self):
2659 vbd = self.get_vbd()
2661 vbd.signal_hotplug(False)
2663 ACTIONS = {'add': add,
2664 'change': change,
2665 'remove': remove}
2666 #
2667 # VDI.pause
2668 #
2670 def _tap_should_pause(self):
2671 """Enumerate all VBDs on our tapdisk. Returns true iff any was
2672 paused"""
2674 tapdisk = self.get_tapdisk()
2675 TapState = Tapdisk.PauseState
2677 PAUSED = 'P'
2678 RUNNING = 'R'
2679 PAUSED_SHUTDOWN = 'P,S'
2680 # NB. Shutdown/paused is special. We know it's not going
2681 # to restart again, so it's a RUNNING. Still better than
2682 # backtracking a removed device during Vbd.unplug completion.
2684 next = TapState.RUNNING
2685 vbds = {}
2687 for vbd in Blkback.find_by_tap(tapdisk):
2688 name = str(vbd)
2690 pausing = vbd.pause_requested()
2691 closing = vbd.shutdown_requested()
2692 running = vbd.running()
2694 if pausing:
2695 if closing and not running:
2696 vbds[name] = PAUSED_SHUTDOWN
2697 else:
2698 vbds[name] = PAUSED
2699 next = TapState.PAUSED
2701 else:
2702 vbds[name] = RUNNING
2704 self.info("tapdev%d (%s): %s -> %s"
2705 % (tapdisk.minor, tapdisk.pause_state(),
2706 vbds, next))
2708 return next == TapState.PAUSED
2710 def _pause_update_tap(self):
2711 vbd = self.get_vbd()
2713 if self._tap_should_pause():
2714 self._pause_tap()
2715 else:
2716 self._resume_tap()
2718 def _pause_tap(self):
2719 tapdisk = self.get_tapdisk()
2721 if not tapdisk.is_paused():
2722 self.info("pausing %s" % tapdisk)
2723 tapdisk.pause()
2725 def _resume_tap(self):
2726 tapdisk = self.get_tapdisk()
2728 # NB. Raw VDI snapshots. Refresh the physical path and
2729 # type while resuming.
2730 vbd = self.get_vbd()
2731 vdi_uuid = vbd.get_vdi_uuid()
2733 if tapdisk.is_paused():
2734 self.info("loading vdi uuid=%s" % vdi_uuid)
2735 vdi = VDI.from_cli(vdi_uuid)
2736 _type = vdi.get_tap_type()
2737 path = vdi.get_phy_path()
2738 self.info("resuming %s on %s:%s" % (tapdisk, _type, path))
2739 tapdisk.unpause(_type, path)
2740 #
2741 # VBD.pause/shutdown
2742 #
2744 def _manage_vbd(self):
2745 vbd = self.get_vbd()
2746 # NB. Hook into VBD state transitions.
2748 events = vbd.get_queue_events()
2750 mask = 0
2751 mask |= events.QUEUE_PAUSE_DONE # pause/unpause
2752 mask |= events.QUEUE_SHUTDOWN_DONE # shutdown
2753 # TODO: mask |= events.QUEUE_SHUTDOWN_REQUEST, for shutdown=force
2754 # TODO: mask |= events.QUEUE_RUNNING, for ionice updates etc
2756 events.set_mask(mask)
2757 self.info("wrote %s = %#02x" % (events.path, mask))
2759 def _signal_xapi(self):
2760 vbd = self.get_vbd()
2762 pausing = vbd.pause_requested()
2763 closing = vbd.shutdown_requested()
2764 running = vbd.running()
2766 handled = 0
2768 if pausing and not running:
2769 if 'pause-done' not in vbd:
2770 vbd.write('pause-done', '')
2771 handled += 1
2773 if not pausing:
2774 if 'pause-done' in vbd:
2775 vbd.rm('pause-done')
2776 handled += 1
2778 if closing and not running:
2779 if 'shutdown-done' not in vbd:
2780 vbd.write('shutdown-done', '')
2781 handled += 1
2783 if handled > 1:
2784 self.warn("handled %d events, " % handled +
2785 "pausing=%s closing=%s running=%s" % \
2786 (pausing, closing, running))
2788if __name__ == '__main__': 2788 ↛ 2790line 2788 didn't jump to line 2790, because the condition on line 2788 was never true
2790 import sys
2791 prog = os.path.basename(sys.argv[0])
2793 #
2794 # Simple CLI interface for manual operation
2795 #
2796 # tap.* level calls go down to local Tapdisk()s (by physical path)
2797 # vdi.* level calls run the plugin calls across host boundaries.
2798 #
2800 def usage(stream):
2801 print("usage: %s tap.{list|major}" % prog, file=stream)
2802 print(" %s tap.{launch|find|get|pause|" % prog + \
2803 "unpause|shutdown|stats} {[<tt>:]<path>} | [minor=]<int> | .. }", file=stream)
2804 print(" %s vbd.uevent" % prog, file=stream)
2806 try:
2807 cmd = sys.argv[1]
2808 except IndexError:
2809 usage(sys.stderr)
2810 sys.exit(1)
2812 try:
2813 _class, method = cmd.split('.')
2814 except:
2815 usage(sys.stderr)
2816 sys.exit(1)
2818 #
2819 # Local Tapdisks
2820 #
2822 if cmd == 'tap.major':
2824 print("%d" % Tapdisk.major())
2826 elif cmd == 'tap.launch':
2828 tapdisk = Tapdisk.launch_from_arg(sys.argv[2])
2829 print("Launched %s" % tapdisk, file=sys.stderr)
2831 elif _class == 'tap':
2833 attrs = {}
2834 for item in sys.argv[2:]:
2835 try:
2836 key, val = item.split('=')
2837 attrs[key] = val
2838 continue
2839 except ValueError:
2840 pass
2842 try:
2843 attrs['minor'] = int(item)
2844 continue
2845 except ValueError:
2846 pass
2848 try:
2849 arg = Tapdisk.Arg.parse(item)
2850 attrs['_type'] = arg.type
2851 attrs['path'] = arg.path
2852 continue
2853 except Tapdisk.Arg.InvalidArgument:
2854 pass
2856 attrs['path'] = item
2858 if cmd == 'tap.list':
2860 for tapdisk in Tapdisk.list( ** attrs):
2861 blktap = tapdisk.get_blktap()
2862 print(tapdisk, end=' ')
2863 print("%s: task=%s pool=%s" % \
2864 (blktap,
2865 blktap.get_task_pid(),
2866 blktap.get_pool_name()))
2868 elif cmd == 'tap.vbds':
2869 # Find all Blkback instances for a given tapdisk
2871 for tapdisk in Tapdisk.list( ** attrs):
2872 print("%s:" % tapdisk, end=' ')
2873 for vbd in Blkback.find_by_tap(tapdisk):
2874 print(vbd, end=' ')
2875 print()
2877 else:
2879 if not attrs:
2880 usage(sys.stderr)
2881 sys.exit(1)
2883 try:
2884 tapdisk = Tapdisk.get( ** attrs)
2885 except TypeError:
2886 usage(sys.stderr)
2887 sys.exit(1)
2889 if cmd == 'tap.shutdown':
2890 # Shutdown a running tapdisk, or raise
2891 tapdisk.shutdown()
2892 print("Shut down %s" % tapdisk, file=sys.stderr)
2894 elif cmd == 'tap.pause':
2895 # Pause an unpaused tapdisk, or raise
2896 tapdisk.pause()
2897 print("Paused %s" % tapdisk, file=sys.stderr)
2899 elif cmd == 'tap.unpause':
2900 # Unpause a paused tapdisk, or raise
2901 tapdisk.unpause()
2902 print("Unpaused %s" % tapdisk, file=sys.stderr)
2904 elif cmd == 'tap.stats':
2905 # Gather tapdisk status
2906 stats = tapdisk.stats()
2907 print("%s:" % tapdisk)
2908 print(json.dumps(stats, indent=True))
2910 else:
2911 usage(sys.stderr)
2912 sys.exit(1)
2914 elif cmd == 'vbd.uevent':
2916 hnd = BlkbackEventHandler(cmd)
2918 if not sys.stdin.isatty():
2919 try:
2920 hnd.run()
2921 except Exception as e:
2922 hnd.error("Unhandled Exception: %s" % e)
2924 import traceback
2925 _type, value, tb = sys.exc_info()
2926 trace = traceback.format_exception(_type, value, tb)
2927 for entry in trace:
2928 for line in entry.rstrip().split('\n'):
2929 util.SMlog(line)
2930 else:
2931 hnd.run()
2933 elif cmd == 'vbd.list':
2935 for vbd in Blkback.find():
2936 print(vbd, \
2937 "physical-device=%s" % vbd.get_physical_device(), \
2938 "pause=%s" % vbd.pause_requested())
2940 else:
2941 usage(sys.stderr)
2942 sys.exit(1)