Coverage for drivers/blktap2.py : 39%

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#
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
42import resetvdis
43import vhdutil
44import lvhdutil
46import VDI as sm
48# For RRDD Plugin Registration
49from xmlrpc.client import ServerProxy, Transport
50from socket import socket, AF_UNIX, SOCK_STREAM
52try:
53 from linstorvolumemanager import log_drbd_openers
54 LINSTOR_AVAILABLE = True
55except ImportError:
56 LINSTOR_AVAILABLE = False
58PLUGIN_TAP_PAUSE = "tapdisk-pause"
60SOCKPATH = "/var/xapi/xcp-rrdd"
62NUM_PAGES_PER_RING = 32 * 11
63MAX_FULL_RINGS = 8
64POOL_NAME_KEY = "mem-pool"
65POOL_SIZE_KEY = "mem-pool-size-rings"
67ENABLE_MULTIPLE_ATTACH = "/etc/xensource/allow_multiple_vdi_attach"
68NO_MULTIPLE_ATTACH = not (os.path.exists(ENABLE_MULTIPLE_ATTACH))
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
98class RetryLoop(object):
100 def __init__(self, backoff, limit):
101 self.backoff = backoff
102 self.limit = limit
104 def __call__(self, f):
106 def loop(*__t, **__d):
107 attempt = 0
109 while True:
110 attempt += 1
112 try:
113 return f( * __t, ** __d)
115 except self.TransientFailure as e:
116 e = e.exception
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
121 time.sleep(self.backoff)
123 return loop
125 class TransientFailure(Exception):
126 def __init__(self, exception):
127 self.exception = exception
130def retried(**args):
131 return RetryLoop( ** args)
134class TapCtl(object):
135 """Tapdisk IPC utility calls."""
137 PATH = "/usr/sbin/tap-ctl"
139 def __init__(self, cmd, p):
140 self.cmd = cmd
141 self._p = p
142 self.stdout = p.stdout
144 class CommandFailure(Exception):
145 """TapCtl cmd failure."""
147 def __init__(self, cmd, **info):
148 self.cmd = cmd
149 self.info = info
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)
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)
164 @property
165 def has_status(self):
166 return 'status' in self.info
168 @property
169 def has_signal(self):
170 return 'signal' in self.info
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
181 @classmethod
182 def __mkcmd_real(cls, args):
183 return [cls.PATH] + [str(x) for x in args]
185 __next_mkcmd = __mkcmd_real
187 @classmethod
188 def _mkcmd(cls, args):
190 __next_mkcmd = cls.__next_mkcmd
191 cls.__next_mkcmd = cls.__mkcmd_real
193 return __next_mkcmd(args)
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)
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)
218 return cls(cmd, p)
220 def _errmsg(self):
221 output = map(str.rstrip, self._p.stderr)
222 return "; ".join(output)
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)
233 if status == 0:
234 return
236 info = {'errmsg': self._errmsg(),
237 'pid': self._p.pid}
239 if status < 0:
240 info['signal'] = -status
241 else:
242 info['status'] = status
244 raise self.CommandFailure(self.cmd, ** info)
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)
254 output = tapctl.stdout.readline().rstrip()
256 tapctl._wait(quiet)
257 return output
259 @staticmethod
260 def _maybe(opt, parm):
261 if parm is not None:
262 return [opt, parm]
263 return []
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)
273 tapctl = cls._call(args, True)
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 = {}
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('=')
289 if key in ('pid', 'minor'):
290 row[key] = int(val, 10)
292 elif key in ('state'):
293 row[key] = int(val, 0x10)
295 else:
296 row[key] = val
297 else:
298 util.SMlog("Ignoring unexpected tap-ctl output: %s" % repr(field))
299 yield row
301 tapctl._wait(True)
303 @classmethod
304 @retried(backoff=.5, limit=10)
305 def list(cls, **args):
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.
311 try:
312 return list(cls.__list( ** args))
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
320 @classmethod
321 def allocate(cls, devpath=None):
322 args = ["allocate"]
323 args += cls._maybe("-d", devpath)
324 return cls._pread(args)
326 @classmethod
327 def free(cls, minor):
328 args = ["free", "-m", minor]
329 cls._pread(args)
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
344 @classmethod
345 def attach(cls, pid, minor):
346 args = ["attach", "-p", pid, "-m", minor]
347 cls._pread(args)
349 @classmethod
350 def detach(cls, pid, minor):
351 args = ["detach", "-p", pid, "-m", minor]
352 cls._pread(args)
354 @classmethod
355 def _load_key(cls, key_hash, vdi_uuid):
356 import plugins
358 return plugins.load_key(key_hash, vdi_uuid)
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)
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')
396 cls._pread(args=args, input=input, text_mode=text_mode)
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)
405 @classmethod
406 def pause(cls, pid, minor):
407 args = ["pause", "-p", pid, "-m", minor]
408 cls._pread(args)
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)
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)
429 @classmethod
430 def stats(cls, pid, minor):
431 args = ["stats", "-p", pid, "-m", minor]
432 return cls._pread(args, quiet=True)
434 @classmethod
435 def major(cls):
436 args = ["major"]
437 major = cls._pread(args)
438 return int(major)
441class TapdiskExists(Exception):
442 """Tapdisk already running."""
444 def __init__(self, tapdisk):
445 self.tapdisk = tapdisk
447 def __str__(self):
448 return "%s already running" % self.tapdisk
451class TapdiskNotRunning(Exception):
452 """No such Tapdisk."""
454 def __init__(self, **attrs):
455 self.attrs = attrs
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
464class TapdiskNotUnique(Exception):
465 """More than one tapdisk on one path."""
467 def __init__(self, tapdisks):
468 self.tapdisks = tapdisks
470 def __str__(self):
471 tapdisks = map(str, self.tapdisks)
472 return "Found multiple tapdisks: %s" % tapdisks
475class TapdiskFailed(Exception):
476 """Tapdisk launch failure."""
478 def __init__(self, arg, err):
479 self.arg = arg
480 self.err = err
482 def __str__(self):
483 return "Tapdisk(%s): %s" % (self.arg, self.err)
485 def get_error(self):
486 return self.err
489class TapdiskInvalidState(Exception):
490 """Tapdisk pause/unpause failure"""
492 def __init__(self, tapdisk):
493 self.tapdisk = tapdisk
495 def __str__(self):
496 return str(self.tapdisk)
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
513class KObject(object):
515 SYSFS_CLASSTYPE = None
517 def sysfs_devname(self):
518 raise NotImplementedError("sysfs_devname is undefined")
521class Attribute(object):
523 SYSFS_NODENAME = None
525 def __init__(self, path):
526 self.path = path
528 @classmethod
529 def from_kobject(cls, kobj):
530 path = "%s/%s" % (kobj.sysfs_path(), cls.SYSFS_NODENAME)
531 return cls(path)
533 class NoSuchAttribute(Exception):
534 def __init__(self, name):
535 self.name = name
537 def __str__(self):
538 return "No such attribute: %s" % self.name
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
548 def readline(self):
549 f = self._open('r')
550 s = f.readline().rstrip()
551 f.close()
552 return s
554 def writeline(self, val):
555 f = self._open('w')
556 f.write(val)
557 f.close()
560class ClassDevice(KObject):
562 @classmethod
563 def sysfs_class_path(cls):
564 return "/sys/class/%s" % cls.SYSFS_CLASSTYPE
566 def sysfs_path(self):
567 return "%s/%s" % (self.sysfs_class_path(),
568 self.sysfs_devname())
571class Blktap(ClassDevice):
573 DEV_BASEDIR = '/dev/xen/blktap-2'
575 SYSFS_CLASSTYPE = "blktap2"
577 def __init__(self, minor):
578 self.minor = minor
579 self._pool = None
580 self._task = None
582 @classmethod
583 def allocate(cls):
584 # FIXME. Should rather go into init.
585 mkdirs(cls.DEV_BASEDIR)
587 devname = TapCtl.allocate()
588 minor = Tapdisk._parse_minor(devname)
589 return cls(minor)
591 def free(self):
592 TapCtl.free(self.minor)
594 def __str__(self):
595 return "%s(minor=%d)" % (self.__class__.__name__, self.minor)
597 def sysfs_devname(self):
598 return "blktap!blktap%d" % self.minor
600 class Pool(Attribute):
601 SYSFS_NODENAME = "pool"
603 def get_pool_attr(self):
604 if not self._pool:
605 self._pool = self.Pool.from_kobject(self)
606 return self._pool
608 def get_pool_name(self):
609 return self.get_pool_attr().readline()
611 def set_pool_name(self, name):
612 self.get_pool_attr().writeline(name)
614 def set_pool_size(self, pages):
615 self.get_pool().set_size(pages)
617 def get_pool(self):
618 return BlktapControl.get_pool(self.get_pool_name())
620 def set_pool(self, pool):
621 self.set_pool_name(pool.name)
623 class Task(Attribute):
624 SYSFS_NODENAME = "task"
626 def get_task_attr(self):
627 if not self._task:
628 self._task = self.Task.from_kobject(self)
629 return self._task
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
638 def find_tapdisk(self):
639 pid = self.get_task_pid()
640 if pid is None:
641 return None
643 return Tapdisk.find(pid=pid, minor=self.minor)
645 def get_tapdisk(self):
646 tapdisk = self.find_tapdisk()
647 if not tapdisk:
648 raise TapdiskNotRunning(minor=self.minor)
649 return tapdisk
652class Tapdisk(object):
654 TYPES = ['aio', 'vhd']
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
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)
670 @classmethod
671 def list(cls, **args):
673 for row in TapCtl.list( ** args):
675 args = {'pid': None,
676 'minor': None,
677 'state': None,
678 '_type': None,
679 'path': None}
681 for key, val in row.items():
682 if key in args:
683 args[key] = val
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
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
693 yield Tapdisk( ** args)
695 @classmethod
696 def find(cls, **args):
698 found = list(cls.list( ** args))
700 if len(found) > 1:
701 raise TapdiskNotUnique(found)
703 if found:
704 return found[0]
706 return None
708 @classmethod
709 def find_by_path(cls, path):
710 return cls.find(path=path)
712 @classmethod
713 def find_by_minor(cls, minor):
714 return cls.find(minor=minor)
716 @classmethod
717 def get(cls, **attrs):
719 tapdisk = cls.find( ** attrs)
721 if not tapdisk:
722 raise TapdiskNotRunning( ** attrs)
724 return tapdisk
726 @classmethod
727 def from_path(cls, path):
728 return cls.get(path=path)
730 @classmethod
731 def from_minor(cls, minor):
732 return cls.get(minor=minor)
734 @classmethod
735 def __from_blktap(cls, blktap):
736 tapdisk = cls.from_minor(minor=blktap.minor)
737 tapdisk._blktap = blktap
738 return tapdisk
740 def get_blktap(self):
741 if not self._blktap:
742 self._blktap = Blktap(self.minor)
743 return self._blktap
745 class Arg:
747 def __init__(self, _type, path):
748 self.type = _type
749 self.path = path
751 def __str__(self):
752 return "%s:%s" % (self.type, self.path)
754 @classmethod
755 def parse(cls, arg):
757 try:
758 _type, path = arg.split(":", 1)
759 except ValueError:
760 raise cls.InvalidArgument(arg)
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)
765 return cls(_type, path)
767 class InvalidType(Exception):
768 def __init__(self, _type):
769 self.type = _type
771 def __str__(self):
772 return "Not a Tapdisk type: %s" % self.type
774 class InvalidArgument(Exception):
775 def __init__(self, arg):
776 self.arg = arg
778 def __str__(self):
779 return "Not a Tapdisk image: %s" % self.arg
781 def get_arg(self):
782 return self.Arg(self.type, self.path)
784 def get_devpath(self):
785 return "%s/tapdev%d" % (Blktap.DEV_BASEDIR, self.minor)
787 @classmethod
788 def launch_from_arg(cls, arg):
789 arg = cls.Arg.parse(arg)
790 return cls.launch(arg.path, arg.type, False)
792 @classmethod
793 def cgclassify(cls, pid):
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)
804 @classmethod
805 def spawn(cls):
806 return TapCtl.spawn()
808 @classmethod
809 def launch_on_tap(cls, blktap, path, _type, options):
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)
815 minor = blktap.minor
817 try:
818 pid = cls.spawn()
819 cls.cgclassify(pid)
820 try:
821 TapCtl.attach(pid, minor)
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
849 except:
850 TapCtl.detach(pid, minor)
851 raise
853 except:
854 try:
855 TapCtl.shutdown(pid)
856 except:
857 # Best effort to shutdown
858 pass
859 raise
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)
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
879 def shutdown(self, force=False):
881 TapCtl.close(self.pid, self.minor, force)
883 TapCtl.detach(self.pid, self.minor)
885 self.get_blktap().free()
887 def pause(self):
889 if not self.is_running():
890 raise TapdiskInvalidState(self)
892 TapCtl.pause(self.pid, self.minor)
894 self._set_dirty()
896 def unpause(self, _type=None, path=None, mirror=None, cbtlog=None):
898 if not self.is_paused():
899 raise TapdiskInvalidState(self)
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
907 TapCtl.unpause(self.pid, self.minor, _type, path, mirror=mirror,
908 cbtlog=cbtlog)
910 self._set_dirty()
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 #
918 def _set_dirty(self):
919 self._dirty = True
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)
925 def __getattribute__(self, name):
926 def __get(name):
927 # NB. avoid(rec(ursion)
928 return object.__getattribute__(self, name)
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
935 return __get(name)
937 class PauseState:
938 RUNNING = 'R'
939 PAUSING = 'r'
940 PAUSED = 'P'
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
954 PAUSE_MASK = PAUSE_REQUESTED | PAUSED
956 def is_paused(self):
957 return not not (self.state & self.Flags.PAUSED)
959 def is_running(self):
960 return not (self.state & self.Flags.PAUSE_MASK)
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
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
969 return self.PauseState.RUNNING
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))
979 minor = groups.group(2)
980 return int(minor)
982 _major = None
984 @classmethod
985 def major(cls):
986 if cls._major:
987 return cls._major
989 devices = open("/proc/devices")
990 for line in devices:
992 row = line.rstrip().split(' ')
993 if len(row) != 2:
994 continue
996 major, name = row
997 if name != 'tapdev':
998 continue
1000 cls._major = int(major)
1001 break
1003 devices.close()
1004 return cls._major
1007class VDI(object):
1008 """SR.vdi driver decorator for blktap2"""
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"
1016 ATTACH_DETACH_RETRY_SECS = 120
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
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
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
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"
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 = ""
1061 return self.__o_direct, self.__o_direct_reason
1063 @classmethod
1064 def from_cli(cls, uuid):
1065 import VDI as sm
1067 session = XenAPI.xapi_local()
1068 session.xenapi.login_with_password('root', '', '', 'SM')
1070 target = sm.VDI.from_uuid(session, uuid)
1071 driver_info = target.sr.srcmd.driver_info
1073 session.xenapi.session.logout()
1075 return cls(uuid, target, driver_info)
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]
1089 def get_tap_type(self):
1090 vdi_type = self.target.get_vdi_type()
1091 return VDI._tap_type(vdi_type)
1093 def get_phy_path(self):
1094 return self.target.get_vdi_path()
1096 class UnexpectedVDIType(Exception):
1098 def __init__(self, vdi_type, target):
1099 self.vdi_type = vdi_type
1100 self.target = target
1102 def __str__(self):
1103 return \
1104 "Target %s has unexpected VDI type '%s'" % \
1105 (type(self.target), self.vdi_type)
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'}
1114 def tap_wanted(self):
1115 # 1. Let the target vdi_type decide
1117 vdi_type = self.target.get_vdi_type()
1119 try:
1120 plug_type = self.VDI_PLUG_TYPE[vdi_type]
1121 except KeyError:
1122 raise self.UnexpectedVDIType(vdi_type,
1123 self.target.vdi)
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
1133 return False
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.
1141 def __init__(self, vdi, driver_info):
1142 self.vdi = vdi
1143 self._caps = driver_info['capabilities']
1145 def has_cap(self, cap):
1146 """Determine if target has given capability"""
1147 return cap in self._caps
1149 def attach(self, sr_uuid, vdi_uuid):
1150 #assert self.has_cap("VDI_ATTACH")
1151 return self.vdi.attach(sr_uuid, vdi_uuid)
1153 def detach(self, sr_uuid, vdi_uuid):
1154 #assert self.has_cap("VDI_DETACH")
1155 self.vdi.detach(sr_uuid, vdi_uuid)
1157 def activate(self, sr_uuid, vdi_uuid):
1158 if self.has_cap("VDI_ACTIVATE"):
1159 return self.vdi.activate(sr_uuid, vdi_uuid)
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)
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
1175 def get_vdi_path(self):
1176 return self.vdi.path
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.
1185 BASEDIR = None
1187 def _mklink(self, target):
1188 raise NotImplementedError("_mklink is not defined")
1190 def _equals(self, target):
1191 raise NotImplementedError("_equals is not defined")
1193 def __init__(self, path):
1194 self._path = path
1196 @classmethod
1197 def from_name(cls, name):
1198 path = "%s/%s" % (cls.BASEDIR, name)
1199 return cls(path)
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)
1206 def path(self):
1207 return self._path
1209 def stat(self):
1210 return os.stat(self.path())
1212 def mklink(self, target):
1214 path = self.path()
1215 util.SMlog("%s -> %s" % (self, target))
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)
1228 def unlink(self):
1229 try:
1230 os.unlink(self.path())
1231 except OSError as e:
1232 if e.errno != errno.ENOENT:
1233 raise
1235 def __str__(self):
1236 path = self.path()
1237 return "%s(%s)" % (self.__class__.__name__, path)
1239 class SymLink(Link):
1240 """Symlink some file to a common name"""
1242 def readlink(self):
1243 return os.readlink(self.path())
1245 def symlink(self):
1246 return self.path()
1248 def _mklink(self, target):
1249 os.symlink(target, self.path())
1251 def _equals(self, target):
1252 return self.readlink() == target
1254 class DeviceNode(Link):
1255 """Relink a block device node to a common name"""
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)
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)
1268 def _mklink(self, target):
1270 st = self._real_stat(target)
1271 if not S_ISBLK(st.st_mode):
1272 raise self.NotABlockDevice(target, st)
1274 os.mknod(self.path(), st.st_mode, st.st_rdev)
1276 def _equals(self, target):
1277 target_rdev = self._real_stat(target).st_rdev
1278 return self.stat().st_rdev == target_rdev
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)
1285 class NotABlockDevice(Exception):
1287 def __init__(self, path, st):
1288 self.path = path
1289 self.st = st
1291 def __str__(self):
1292 return "%s is not a block device: %s" % (self.path, self.st)
1294 class Hybrid(Link):
1296 def __init__(self, path):
1297 VDI.Link.__init__(self, path)
1298 self._devnode = VDI.DeviceNode(path)
1299 self._symlink = VDI.SymLink(path)
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)
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)
1314 def _equals(self, target):
1315 return self._obj._equals(target)
1317 class PhyLink(SymLink):
1318 BASEDIR = "/dev/sm/phy"
1319 # NB. Cannot use DeviceNodes, e.g. FileVDIs aren't bdevs.
1321 class NBDLink(SymLink):
1323 BASEDIR = "/run/blktap-control/nbd"
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.
1333 @staticmethod
1334 def _tap_activate(phy_path, vdi_type, sr_uuid, options, pool_size=None):
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)
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)
1354 else:
1355 util.SMlog("tap.activate: Found %s" % tapdisk)
1357 return tapdisk.get_devpath(), tapdisk
1359 @staticmethod
1360 def _tap_deactivate(minor):
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)
1373 @classmethod
1374 def tap_pause(cls, session, sr_uuid, vdi_uuid, failfast=False):
1375 """
1376 Pauses the tapdisk.
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
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
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
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
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
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
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
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)
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
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
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)
1549 return pool_info
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)
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)
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 = ""
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)
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
1595 return xmlrpc.client.dumps((struct, ), "", True)
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)
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)
1618 @locking("VDIUnavailable")
1619 def _activate_locked(self, sr_uuid, vdi_uuid, options):
1620 """Wraps target.activate and adds a tapdisk"""
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)
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())
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)
1645 vdi_type = self.target.get_vdi_type()
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()
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)
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
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
1709 def _activate(self, sr_uuid, vdi_uuid, options):
1710 vdi_options = self.target.activate(sr_uuid, vdi_uuid)
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
1727 return dev_path
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)
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)
1749 @locking("VDIUnavailable")
1750 def _deactivate_locked(self, sr_uuid, vdi_uuid, caching_params):
1751 """Wraps target.deactivate and removes a tapdisk"""
1753 #util.SMlog("VDI.deactivate %s" % vdi_uuid)
1754 if self.tap_wanted() and not self._check_tag(vdi_uuid):
1755 return False
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)
1763 return True
1765 def _resetPhylink(self, sr_uuid, vdi_uuid, path):
1766 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).mklink(path)
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
1776 def _deactivate(self, sr_uuid, vdi_uuid, caching_params):
1777 import VDI as sm
1779 # Shutdown tapdisk
1780 back_link = self.BackendLink.from_uuid(sr_uuid, vdi_uuid)
1782 if not util.pathexists(back_link.path()):
1783 util.SMlog("Backend path %s does not exist" % back_link.path())
1784 return
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")
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)
1801 # Remove the backend link
1802 back_link.unlink()
1803 VDI.NBDLink.from_uuid(sr_uuid, vdi_uuid).unlink()
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)
1815 self.target.deactivate(sr_uuid, vdi_uuid)
1817 def _detach(self, sr_uuid, vdi_uuid):
1818 self.target.detach(sr_uuid, vdi_uuid)
1820 # Remove phy/
1821 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).unlink()
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)
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
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
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
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)
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))
1863 return dev_path
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")
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")
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")
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)
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)
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
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
1920 util.SMlog("Setting up cache")
1921 parent_uuid = parent_uuid.strip()
1922 shared_target = NFSSR.NFSFileVDI(self.target.vdi.sr, parent_uuid)
1924 if shared_target.parent:
1925 util.SMlog("ERROR: Parent VDI %s has parent, not enabling" %
1926 shared_target.uuid)
1927 return
1929 SR.registerSR(EXTSR.EXTSR)
1930 local_sr = SR.SR.from_uuid(session, local_sr_uuid)
1932 lock = Lock(self.LOCK_CACHE_SETUP, parent_uuid)
1933 lock.acquire()
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
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
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)
1970 vdi_type = self.target.get_vdi_type()
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
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
1990 secondary = "%s:%s" % (self.target.get_vdi_type(),
1991 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).readlink())
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
2011 lock.release()
2013 util.SMlog("Local read cache: %s, local leaf: %s" % \
2014 (read_cache_path, local_leaf_path))
2016 self.tap = leaf_tapdisk
2017 return leaf_tapdisk.get_devpath()
2019 def remove_cache(self, sr_uuid, vdi_uuid, params):
2020 if not self.target.has_cap("SR_CACHING"):
2021 return
2023 caching = params.get(self.CONF_KEY_ALLOW_CACHING) == "true"
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
2030 if caching:
2031 self._remove_cache(self._session, local_sr_uuid)
2033 if self._session is not None:
2034 self._updateCacheRecord(self._session, self.target.vdi.uuid, None, None)
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
2042 for link in links:
2043 if link.find("tapdev%d" % minor) != -1:
2044 return True
2046 socket_re = re.compile(r'^/.*/nbd\d+\.%d' % minor)
2047 for s in sockets:
2048 if socket_re.match(s):
2049 return True
2051 return False
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
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
2067 util.SMlog("Tearing down the cache")
2069 parent_uuid = parent_uuid.strip()
2070 shared_target = NFSSR.NFSFileVDI(self.target.vdi.sr, parent_uuid)
2072 SR.registerSR(EXTSR.EXTSR)
2073 local_sr = SR.SR.from_uuid(session, local_sr_uuid)
2075 lock = Lock(self.LOCK_CACHE_SETUP, parent_uuid)
2076 lock.acquire()
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)
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
2101 lock.release()
2103PythonKeyError = KeyError
2106class UEventHandler(object):
2108 def __init__(self):
2109 self._action = None
2111 class KeyError(PythonKeyError):
2112 def __init__(self, args):
2113 super().__init__(args)
2114 self.key = args[0]
2116 def __str__(self):
2117 return \
2118 "Key '%s' missing in environment. " % self.key + \
2119 "Not called in udev context?"
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])
2128 def get_action(self):
2129 if not self._action:
2130 self._action = self.getenv('ACTION')
2131 return self._action
2133 class UnhandledEvent(Exception):
2135 def __init__(self, event, handler):
2136 self.event = event
2137 self.handler = handler
2139 def __str__(self):
2140 return "Uevent '%s' not handled by %s" % \
2141 (self.event, self.handler.__class__.__name__)
2143 ACTIONS = {}
2145 def run(self):
2147 action = self.get_action()
2148 try:
2149 fn = self.ACTIONS[action]
2150 except KeyError:
2151 raise self.UnhandledEvent(action, self)
2153 return fn(self)
2155 def __str__(self):
2156 try:
2157 action = self.get_action()
2158 except:
2159 action = None
2160 return "%s[%s]" % (self.__class__.__name__, action)
2163class __BlktapControl(ClassDevice):
2164 SYSFS_CLASSTYPE = "misc"
2166 def __init__(self):
2167 ClassDevice.__init__(self)
2168 self._default_pool = None
2170 def sysfs_devname(self):
2171 return "blktap!control"
2173 class DefaultPool(Attribute):
2174 SYSFS_NODENAME = "default_pool"
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
2181 def get_default_pool_name(self):
2182 return self.get_default_pool_attr().readline()
2184 def set_default_pool_name(self, name):
2185 self.get_default_pool_attr().writeline(name)
2187 def get_default_pool(self):
2188 return BlktapControl.get_pool(self.get_default_pool_name())
2190 def set_default_pool(self, pool):
2191 self.set_default_pool_name(pool.name)
2193 class NoSuchPool(Exception):
2194 def __init__(self, name):
2195 self.name = name
2197 def __str__(self):
2198 return "No such pool: %s", self.name
2200 def get_pool(self, name):
2201 path = "%s/pools/%s" % (self.sysfs_path(), name)
2203 if not os.path.isdir(path):
2204 raise self.NoSuchPool(name)
2206 return PagePool(path)
2208BlktapControl = __BlktapControl()
2211class PagePool(KObject):
2213 def __init__(self, path):
2214 self.path = path
2215 self._size = None
2217 def sysfs_path(self):
2218 return self.path
2220 class Size(Attribute):
2221 SYSFS_NODENAME = "size"
2223 def get_size_attr(self):
2224 if not self._size:
2225 self._size = self.Size.from_kobject(self)
2226 return self._size
2228 def set_size(self, pages):
2229 pages = str(pages)
2230 self.get_size_attr().writeline(pages)
2232 def get_size(self):
2233 pages = self.get_size_attr().readline()
2234 return int(pages)
2237class BusDevice(KObject):
2239 SYSFS_BUSTYPE = None
2241 @classmethod
2242 def sysfs_bus_path(cls):
2243 return "/sys/bus/%s" % cls.SYSFS_BUSTYPE
2245 def sysfs_path(self):
2246 path = "%s/devices/%s" % (self.sysfs_bus_path(),
2247 self.sysfs_devname())
2249 return path
2252class XenbusDevice(BusDevice):
2253 """Xenbus device, in XS and sysfs"""
2255 XBT_NIL = ""
2257 XENBUS_DEVTYPE = None
2259 def __init__(self, domid, devid):
2260 self.domid = int(domid)
2261 self.devid = int(devid)
2262 self._xbt = XenbusDevice.XBT_NIL
2264 import xen.lowlevel.xs # pylint: disable=import-error
2265 self.xs = xen.lowlevel.xs.xs()
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)
2274 return path
2276 def _log(self, prio, msg):
2277 syslog(prio, msg)
2279 def info(self, msg):
2280 self._log(_syslog.LOG_INFO, msg)
2282 def warn(self, msg):
2283 self._log(_syslog.LOG_WARNING, "WARNING: " + msg)
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
2290 def _xs_write_path(self, path, val):
2291 self.xs.write(self._xbt, path, val)
2292 self.info("wrote %s = '%s'" % (path, val))
2294 def _xs_rm_path(self, path):
2295 self.xs.rm(self._xbt, path)
2296 self.info("removed %s" % path)
2298 def read(self, key):
2299 return self._xs_read_path(self.xs_path(key))
2301 def has_xs_key(self, key):
2302 return self.read(key) is not None
2304 def write(self, key, val):
2305 self._xs_write_path(self.xs_path(key), val)
2307 def rm(self, key):
2308 self._xs_rm_path(self.xs_path(key))
2310 def exists(self):
2311 return self.has_xs_key(None)
2313 def begin(self):
2314 assert(self._xbt == XenbusDevice.XBT_NIL)
2315 self._xbt = self.xs.transaction_start()
2317 def commit(self):
2318 ok = self.xs.transaction_end(self._xbt, 0)
2319 self._xbt = XenbusDevice.XBT_NIL
2320 return ok
2322 def abort(self):
2323 ok = self.xs.transaction_end(self._xbt, 1)
2324 assert(ok == True)
2325 self._xbt = XenbusDevice.XBT_NIL
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")
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)
2357 def sysfs_devname(self):
2358 return "%s-%d-%d" % (self.XENBUS_DEVTYPE,
2359 self.domid, self.devid)
2361 def __str__(self):
2362 return self.sysfs_devname()
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):
2370 name = os.path.basename(path)
2371 (_type, domid, devid) = name.split('-')
2373 yield cls(domid, devid)
2376class XenBackendDevice(XenbusDevice):
2377 """Xenbus backend device"""
2378 SYSFS_BUSTYPE = "xen-backend"
2380 @classmethod
2381 def from_xs_path(cls, _path):
2382 (_backend, _type, domid, devid) = _path.split('/')
2384 assert _backend == 'backend'
2385 assert _type == cls.XENBUS_DEVTYPE
2387 domid = int(domid)
2388 devid = int(devid)
2390 return cls(domid, devid)
2393class Blkback(XenBackendDevice):
2394 """A blkback VBD"""
2396 XENBUS_DEVTYPE = "vbd"
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
2405 class XenstoreValueError(Exception):
2406 KEY = None
2408 def __init__(self, vbd, _str):
2409 self.vbd = vbd
2410 self.str = _str
2412 def __str__(self):
2413 return "Backend %s " % self.vbd + \
2414 "has %s = %s" % (self.KEY, self.str)
2416 class PhysicalDeviceError(XenstoreValueError):
2417 KEY = "physical-device"
2419 class PhysicalDevice(object):
2421 def __init__(self, major, minor):
2422 self.major = int(major)
2423 self.minor = int(minor)
2425 @classmethod
2426 def from_xbdev(cls, xbdev):
2428 phy = xbdev.read("physical-device")
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)
2437 return cls(major, minor)
2439 def makedev(self):
2440 return os.makedev(self.major, self.minor)
2442 def is_tap(self):
2443 return self.major == Tapdisk.major()
2445 def __str__(self):
2446 return "%s:%s" % (self.major, self.minor)
2448 def __eq__(self, other):
2449 return \
2450 self.major == other.major and \
2451 self.minor == other.minor
2453 def get_physical_device(self):
2454 if not self._phy:
2455 self._phy = self.PhysicalDevice.from_xbdev(self)
2456 return self._phy
2458 class QueueEvents(Attribute):
2459 """Blkback sysfs node to select queue-state event
2460 notifications emitted."""
2462 SYSFS_NODENAME = "queue_events"
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)
2470 def get_mask(self):
2471 return int(self.readline(), 0x10)
2473 def set_mask(self, mask):
2474 self.writeline("0x%x" % mask)
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
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
2486 def pause_requested(self):
2487 return self.has_xs_key("pause")
2489 def shutdown_requested(self):
2490 return self.has_xs_key("shutdown-request")
2492 def shutdown_done(self):
2493 return self.has_xs_key("shutdown-done")
2495 def running(self):
2496 return self.has_xs_key('queue-0/kthread-pid')
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
2506 if _phy == phy:
2507 yield dev
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)
2514 @classmethod
2515 def find_by_tap(cls, tapdisk):
2516 return cls.find_by_tap_minor(tapdisk.minor)
2518 def has_tap(self):
2520 if not self.can_tap():
2521 return False
2523 phy = self.get_physical_device()
2524 if phy:
2525 return phy.is_tap()
2527 return False
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()
2534 except self.PhysicalDeviceError as e:
2535 vdi_type = self.read("type")
2537 self.info("HVM VDI: type=%s" % vdi_type)
2539 if e.str is not None or vdi_type != 'file':
2540 raise
2542 return True
2544 return False
2546 def can_tap(self):
2547 return not self.is_bare_hvm()
2550class BlkbackEventHandler(UEventHandler):
2552 LOG_FACILITY = _syslog.LOG_DAEMON
2554 def __init__(self, ident=None, action=None):
2555 if not ident:
2556 ident = self.__class__.__name__
2558 self.ident = ident
2559 self._vbd = None
2560 self._tapdisk = None
2562 UEventHandler.__init__(self)
2564 def run(self):
2566 self.xs_path = self.getenv('XENBUS_PATH')
2567 openlog(str(self), 0, self.LOG_FACILITY)
2569 UEventHandler.run(self)
2571 def __str__(self):
2573 try:
2574 path = self.xs_path
2575 except:
2576 path = None
2578 try:
2579 action = self.get_action()
2580 except:
2581 action = None
2583 return "%s[%s](%s)" % (self.ident, action, path)
2585 def _log(self, prio, msg):
2586 syslog(prio, msg)
2587 util.SMlog("%s: " % self + msg)
2589 def info(self, msg):
2590 self._log(_syslog.LOG_INFO, msg)
2592 def warn(self, msg):
2593 self._log(_syslog.LOG_WARNING, "WARNING: " + msg)
2595 def error(self, msg):
2596 self._log(_syslog.LOG_ERR, "ERROR: " + msg)
2598 def get_vbd(self):
2599 if not self._vbd:
2600 self._vbd = Blkback.from_xs_path(self.xs_path)
2601 return self._vbd
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 #
2612 def __add(self):
2613 vbd = self.get_vbd()
2614 # Manage blkback transitions
2615 # self._manage_vbd()
2617 vbd.create_physical_device()
2619 vbd.signal_hotplug()
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)
2633 def __change(self):
2634 vbd = self.get_vbd()
2636 # 1. Pause or resume tapdisk (if there is one)
2638 if vbd.has_tap():
2639 pass
2640 #self._pause_update_tap()
2642 # 2. Signal Xapi.VBD.pause/resume completion
2644 self._signal_xapi()
2646 def change(self):
2647 vbd = self.get_vbd()
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.
2653 while True:
2654 vbd.begin()
2656 if not vbd.exists() or \
2657 vbd.shutdown_done():
2658 break
2660 self.__change()
2662 if vbd.commit():
2663 return
2665 vbd.abort()
2666 self.info("spurious uevent, ignored.")
2668 def remove(self):
2669 vbd = self.get_vbd()
2671 vbd.signal_hotplug(False)
2673 ACTIONS = {'add': add,
2674 'change': change,
2675 'remove': remove}
2676 #
2677 # VDI.pause
2678 #
2680 def _tap_should_pause(self):
2681 """Enumerate all VBDs on our tapdisk. Returns true iff any was
2682 paused"""
2684 tapdisk = self.get_tapdisk()
2685 TapState = Tapdisk.PauseState
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.
2694 next = TapState.RUNNING
2695 vbds = {}
2697 for vbd in Blkback.find_by_tap(tapdisk):
2698 name = str(vbd)
2700 pausing = vbd.pause_requested()
2701 closing = vbd.shutdown_requested()
2702 running = vbd.running()
2704 if pausing:
2705 if closing and not running:
2706 vbds[name] = PAUSED_SHUTDOWN
2707 else:
2708 vbds[name] = PAUSED
2709 next = TapState.PAUSED
2711 else:
2712 vbds[name] = RUNNING
2714 self.info("tapdev%d (%s): %s -> %s"
2715 % (tapdisk.minor, tapdisk.pause_state(),
2716 vbds, next))
2718 return next == TapState.PAUSED
2720 def _pause_update_tap(self):
2721 vbd = self.get_vbd()
2723 if self._tap_should_pause():
2724 self._pause_tap()
2725 else:
2726 self._resume_tap()
2728 def _pause_tap(self):
2729 tapdisk = self.get_tapdisk()
2731 if not tapdisk.is_paused():
2732 self.info("pausing %s" % tapdisk)
2733 tapdisk.pause()
2735 def _resume_tap(self):
2736 tapdisk = self.get_tapdisk()
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()
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 #
2754 def _manage_vbd(self):
2755 vbd = self.get_vbd()
2756 # NB. Hook into VBD state transitions.
2758 events = vbd.get_queue_events()
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
2766 events.set_mask(mask)
2767 self.info("wrote %s = %#02x" % (events.path, mask))
2769 def _signal_xapi(self):
2770 vbd = self.get_vbd()
2772 pausing = vbd.pause_requested()
2773 closing = vbd.shutdown_requested()
2774 running = vbd.running()
2776 handled = 0
2778 if pausing and not running:
2779 if 'pause-done' not in vbd:
2780 vbd.write('pause-done', '')
2781 handled += 1
2783 if not pausing:
2784 if 'pause-done' in vbd:
2785 vbd.rm('pause-done')
2786 handled += 1
2788 if closing and not running:
2789 if 'shutdown-done' not in vbd:
2790 vbd.write('shutdown-done', '')
2791 handled += 1
2793 if handled > 1:
2794 self.warn("handled %d events, " % handled +
2795 "pausing=%s closing=%s running=%s" % \
2796 (pausing, closing, running))
2798if __name__ == '__main__': 2798 ↛ 2800line 2798 didn't jump to line 2800, because the condition on line 2798 was never true
2800 import sys
2801 prog = os.path.basename(sys.argv[0])
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 #
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)
2816 try:
2817 cmd = sys.argv[1]
2818 except IndexError:
2819 usage(sys.stderr)
2820 sys.exit(1)
2822 try:
2823 _class, method = cmd.split('.')
2824 except:
2825 usage(sys.stderr)
2826 sys.exit(1)
2828 #
2829 # Local Tapdisks
2830 #
2832 if cmd == 'tap.major':
2834 print("%d" % Tapdisk.major())
2836 elif cmd == 'tap.launch':
2838 tapdisk = Tapdisk.launch_from_arg(sys.argv[2])
2839 print("Launched %s" % tapdisk, file=sys.stderr)
2841 elif _class == 'tap':
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
2852 try:
2853 attrs['minor'] = int(item)
2854 continue
2855 except ValueError:
2856 pass
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
2866 attrs['path'] = item
2868 if cmd == 'tap.list':
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()))
2878 elif cmd == 'tap.vbds':
2879 # Find all Blkback instances for a given tapdisk
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()
2887 else:
2889 if not attrs:
2890 usage(sys.stderr)
2891 sys.exit(1)
2893 try:
2894 tapdisk = Tapdisk.get( ** attrs)
2895 except TypeError:
2896 usage(sys.stderr)
2897 sys.exit(1)
2899 if cmd == 'tap.shutdown':
2900 # Shutdown a running tapdisk, or raise
2901 tapdisk.shutdown()
2902 print("Shut down %s" % tapdisk, file=sys.stderr)
2904 elif cmd == 'tap.pause':
2905 # Pause an unpaused tapdisk, or raise
2906 tapdisk.pause()
2907 print("Paused %s" % tapdisk, file=sys.stderr)
2909 elif cmd == 'tap.unpause':
2910 # Unpause a paused tapdisk, or raise
2911 tapdisk.unpause()
2912 print("Unpaused %s" % tapdisk, file=sys.stderr)
2914 elif cmd == 'tap.stats':
2915 # Gather tapdisk status
2916 stats = tapdisk.stats()
2917 print("%s:" % tapdisk)
2918 print(json.dumps(stats, indent=True))
2920 else:
2921 usage(sys.stderr)
2922 sys.exit(1)
2924 elif cmd == 'vbd.uevent':
2926 hnd = BlkbackEventHandler(cmd)
2928 if not sys.stdin.isatty():
2929 try:
2930 hnd.run()
2931 except Exception as e:
2932 hnd.error("Unhandled Exception: %s" % e)
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()
2943 elif cmd == 'vbd.list':
2945 for vbd in Blkback.find():
2946 print(vbd, \
2947 "physical-device=%s" % vbd.get_physical_device(), \
2948 "pause=%s" % vbd.pause_requested())
2950 else:
2951 usage(sys.stderr)
2952 sys.exit(1)