Coverage for drivers/blktap2.py : 41%

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#
21from sm_typing import Any, Callable, ClassVar, Dict, override
23from abc import abstractmethod
25import grp
26import os
27import re
28import stat
29import time
30import copy
31from lock import Lock
32import util
33import xmlrpc.client
34import http.client
35import errno
36import signal
37import subprocess
38import syslog as _syslog
39import glob
40import json
41import xs_errors
42import XenAPI # pylint: disable=import-error
43import scsiutil
44from syslog import openlog, syslog
45from stat import * # S_ISBLK(), ...
46import nfs
48import resetvdis
49import vhdutil
50import lvhdutil
52import VDI as sm
54# For RRDD Plugin Registration
55from xmlrpc.client import ServerProxy, Transport
56from socket import socket, AF_UNIX, SOCK_STREAM
58try:
59 from linstorvolumemanager import log_drbd_openers
60 LINSTOR_AVAILABLE = True
61except ImportError:
62 LINSTOR_AVAILABLE = False
64PLUGIN_TAP_PAUSE = "tapdisk-pause"
66SOCKPATH = "/var/xapi/xcp-rrdd"
68NUM_PAGES_PER_RING = 32 * 11
69MAX_FULL_RINGS = 8
70POOL_NAME_KEY = "mem-pool"
71POOL_SIZE_KEY = "mem-pool-size-rings"
73ENABLE_MULTIPLE_ATTACH = "/etc/xensource/allow_multiple_vdi_attach"
74NO_MULTIPLE_ATTACH = not (os.path.exists(ENABLE_MULTIPLE_ATTACH))
77def locking(excType, override=True):
78 def locking2(op):
79 def wrapper(self, *args):
80 self.lock.acquire()
81 try:
82 try:
83 ret = op(self, * args)
84 except (util.CommandException, util.SMException, XenAPI.Failure) as e:
85 util.logException("BLKTAP2:%s" % op)
86 msg = str(e)
87 if isinstance(e, util.CommandException):
88 msg = "Command %s failed (%s): %s" % \
89 (e.cmd, e.code, e.reason)
90 if override:
91 raise xs_errors.XenError(excType, opterr=msg)
92 else:
93 raise
94 except:
95 util.logException("BLKTAP2:%s" % op)
96 raise
97 finally:
98 self.lock.release() 98 ↛ exitline 98 didn't except from function 'wrapper', because the raise on line 91 wasn't executed or the raise on line 93 wasn't executed or the raise on line 96 wasn't executed
99 return ret
100 return wrapper
101 return locking2
104class RetryLoop(object):
106 def __init__(self, backoff, limit):
107 self.backoff = backoff
108 self.limit = limit
110 def __call__(self, f):
112 def loop(*__t, **__d):
113 attempt = 0
115 while True:
116 attempt += 1
118 try:
119 return f( * __t, ** __d)
121 except self.TransientFailure as e:
122 e = e.exception
124 if attempt >= self.limit: 124 ↛ 125line 124 didn't jump to line 125, because the condition on line 124 was never true
125 raise e
127 time.sleep(self.backoff)
129 return loop
131 class TransientFailure(Exception):
132 def __init__(self, exception):
133 self.exception = exception
136def retried(**args):
137 return RetryLoop( ** args)
140class TapCtl(object):
141 """Tapdisk IPC utility calls."""
143 PATH = "/usr/sbin/tap-ctl"
145 def __init__(self, cmd, p):
146 self.cmd = cmd
147 self._p = p
148 self.stdout = p.stdout
150 class CommandFailure(Exception):
151 """TapCtl cmd failure."""
153 def __init__(self, cmd, **info):
154 self.cmd = cmd
155 self.info = info
157 @override
158 def __str__(self) -> str:
159 items = self.info.items()
160 info = ", ".join("%s=%s" % item
161 for item in items)
162 return "%s failed: %s" % (self.cmd, info)
164 # Trying to get a non-existent attribute throws an AttributeError
165 # exception
166 def __getattr__(self, key):
167 if key in self.info: 167 ↛ 169line 167 didn't jump to line 169, because the condition on line 167 was never false
168 return self.info[key]
169 return object.__getattribute__(self, key)
171 @property
172 def has_status(self):
173 return 'status' in self.info
175 @property
176 def has_signal(self):
177 return 'signal' in self.info
179 # Retrieves the error code returned by the command. If the error code
180 # was not supplied at object-construction time, zero is returned.
181 def get_error_code(self):
182 key = 'status'
183 if key in self.info: 183 ↛ 186line 183 didn't jump to line 186, because the condition on line 183 was never false
184 return self.info[key]
185 else:
186 return 0
188 @classmethod
189 def __mkcmd_real(cls, args):
190 return [cls.PATH] + [str(x) for x in args]
192 __next_mkcmd = __mkcmd_real
194 @classmethod
195 def _mkcmd(cls, args):
197 __next_mkcmd = cls.__next_mkcmd
198 cls.__next_mkcmd = cls.__mkcmd_real
200 return __next_mkcmd(args)
202 @classmethod
203 def _call(cls, args, quiet=False, input=None, text_mode=True):
204 """
205 Spawn a tap-ctl process. Return a TapCtl invocation.
206 Raises a TapCtl.CommandFailure if subprocess creation failed.
207 """
208 cmd = cls._mkcmd(args)
210 if not quiet:
211 util.SMlog(cmd)
212 try:
213 p = subprocess.Popen(cmd,
214 stdin=subprocess.PIPE,
215 stdout=subprocess.PIPE,
216 stderr=subprocess.PIPE,
217 close_fds=True,
218 universal_newlines=text_mode)
219 if input:
220 p.stdin.write(input)
221 p.stdin.close()
222 except OSError as e:
223 raise cls.CommandFailure(cmd, errno=e.errno)
225 return cls(cmd, p)
227 def _errmsg(self):
228 output = map(str.rstrip, self._p.stderr)
229 return "; ".join(output)
231 def _wait(self, quiet=False):
232 """
233 Reap the child tap-ctl process of this invocation.
234 Raises a TapCtl.CommandFailure on non-zero exit status.
235 """
236 status = self._p.wait()
237 if not quiet:
238 util.SMlog(" = %d" % status)
240 if status == 0:
241 return
243 info = {'errmsg': self._errmsg(),
244 'pid': self._p.pid}
246 if status < 0:
247 info['signal'] = -status
248 else:
249 info['status'] = status
251 raise self.CommandFailure(self.cmd, ** info)
253 @classmethod
254 def _pread(cls, args, quiet=False, input=None, text_mode=True):
255 """
256 Spawn a tap-ctl invocation and read a single line.
257 """
258 tapctl = cls._call(args=args, quiet=quiet, input=input,
259 text_mode=text_mode)
261 output = tapctl.stdout.readline().rstrip()
263 tapctl._wait(quiet)
264 return output
266 @staticmethod
267 def _maybe(opt, parm):
268 if parm is not None:
269 return [opt, parm]
270 return []
272 @classmethod
273 def __list(cls, minor=None, pid=None, _type=None, path=None):
274 args = ["list"]
275 args += cls._maybe("-m", minor)
276 args += cls._maybe("-p", pid)
277 args += cls._maybe("-t", _type)
278 args += cls._maybe("-f", path)
280 tapctl = cls._call(args, True)
282 for stdout_line in tapctl.stdout:
283 # FIXME: tap-ctl writes error messages to stdout and
284 # confuses this parser
285 if stdout_line == "blktap kernel module not installed\n": 285 ↛ 288line 285 didn't jump to line 288, because the condition on line 285 was never true
286 # This isn't pretty but (a) neither is confusing stdout/stderr
287 # and at least causes the error to describe the fix
288 raise Exception("blktap kernel module not installed: try 'modprobe blktap'")
289 row = {}
291 for field in stdout_line.rstrip().split(' ', 3):
292 bits = field.split('=')
293 if len(bits) == 2: 293 ↛ 305line 293 didn't jump to line 305, because the condition on line 293 was never false
294 key, val = field.split('=')
296 if key in ('pid', 'minor'):
297 row[key] = int(val, 10)
299 elif key in ('state'):
300 row[key] = int(val, 0x10)
302 else:
303 row[key] = val
304 else:
305 util.SMlog("Ignoring unexpected tap-ctl output: %s" % repr(field))
306 yield row
308 tapctl._wait(True)
310 @classmethod
311 @retried(backoff=.5, limit=10)
312 def list(cls, **args):
314 # FIXME. We typically get an EPROTO when uevents interleave
315 # with SM ops and a tapdisk shuts down under our feet. Should
316 # be fixed in SM.
318 try:
319 return list(cls.__list( ** args))
321 except cls.CommandFailure as e:
322 transient = [errno.EPROTO, errno.ENOENT]
323 if e.has_status and e.status in transient:
324 raise RetryLoop.TransientFailure(e)
325 raise
327 @classmethod
328 def allocate(cls, devpath=None):
329 args = ["allocate"]
330 args += cls._maybe("-d", devpath)
331 return cls._pread(args)
333 @classmethod
334 def free(cls, minor):
335 args = ["free", "-m", minor]
336 cls._pread(args)
338 @classmethod
339 @retried(backoff=.5, limit=10)
340 def spawn(cls):
341 args = ["spawn"]
342 try:
343 pid = cls._pread(args)
344 return int(pid)
345 except cls.CommandFailure as ce:
346 # intermittent failures to spawn. CA-292268
347 if ce.status == 1:
348 raise RetryLoop.TransientFailure(ce)
349 raise
351 @classmethod
352 def attach(cls, pid, minor):
353 args = ["attach", "-p", pid, "-m", minor]
354 cls._pread(args)
356 @classmethod
357 def detach(cls, pid, minor):
358 args = ["detach", "-p", pid, "-m", minor]
359 cls._pread(args)
361 @classmethod
362 def _load_key(cls, key_hash, vdi_uuid):
363 import plugins
365 return plugins.load_key(key_hash, vdi_uuid)
367 @classmethod
368 def open(cls, pid, minor, _type, _file, options):
369 params = Tapdisk.Arg(_type, _file)
370 args = ["open", "-p", pid, "-m", minor, '-a', str(params)]
371 text_mode = True
372 input = None
373 if options.get("rdonly"):
374 args.append('-R')
375 if options.get("lcache"):
376 args.append("-r")
377 if options.get("existing_prt") is not None:
378 args.append("-e")
379 args.append(str(options["existing_prt"]))
380 if options.get("secondary"):
381 args.append("-2")
382 args.append(options["secondary"])
383 if options.get("standby"):
384 args.append("-s")
385 if options.get("timeout"):
386 args.append("-t")
387 args.append(str(options["timeout"]))
388 if not options.get("o_direct", True):
389 args.append("-D")
390 if options.get('cbtlog'):
391 args.extend(['-C', options['cbtlog']])
392 if options.get('key_hash'):
393 key_hash = options['key_hash']
394 vdi_uuid = options['vdi_uuid']
395 key = cls._load_key(key_hash, vdi_uuid)
397 if not key:
398 raise util.SMException("No key found with key hash {}".format(key_hash))
399 input = key
400 text_mode = False
401 args.append('-E')
403 cls._pread(args=args, input=input, text_mode=text_mode)
405 @classmethod
406 def close(cls, pid, minor, force=False):
407 args = ["close", "-p", pid, "-m", minor, "-t", "120"]
408 if force:
409 args += ["-f"]
410 cls._pread(args)
412 @classmethod
413 def pause(cls, pid, minor):
414 args = ["pause", "-p", pid, "-m", minor]
415 cls._pread(args)
417 @classmethod
418 def unpause(cls, pid, minor, _type=None, _file=None, mirror=None,
419 cbtlog=None):
420 args = ["unpause", "-p", pid, "-m", minor]
421 if mirror:
422 args.extend(["-2", mirror])
423 if _type and _file:
424 params = Tapdisk.Arg(_type, _file)
425 args += ["-a", str(params)]
426 if cbtlog:
427 args.extend(["-c", cbtlog])
428 cls._pread(args)
430 @classmethod
431 def shutdown(cls, pid):
432 # TODO: This should be a real tap-ctl command
433 os.kill(pid, signal.SIGTERM)
434 os.waitpid(pid, 0)
436 @classmethod
437 def stats(cls, pid, minor):
438 args = ["stats", "-p", pid, "-m", minor]
439 return cls._pread(args, quiet=True)
441 @classmethod
442 def major(cls):
443 args = ["major"]
444 major = cls._pread(args)
445 return int(major)
448class TapdiskExists(Exception):
449 """Tapdisk already running."""
451 def __init__(self, tapdisk):
452 self.tapdisk = tapdisk
454 @override
455 def __str__(self) -> str:
456 return "%s already running" % self.tapdisk
459class TapdiskNotRunning(Exception):
460 """No such Tapdisk."""
462 def __init__(self, **attrs):
463 self.attrs = attrs
465 @override
466 def __str__(self) -> str:
467 items = iter(self.attrs.items())
468 attrs = ", ".join("%s=%s" % attr
469 for attr in items)
470 return "No such Tapdisk(%s)" % attrs
473class TapdiskNotUnique(Exception):
474 """More than one tapdisk on one path."""
476 def __init__(self, tapdisks):
477 self.tapdisks = tapdisks
479 @override
480 def __str__(self) -> str:
481 tapdisks = map(str, self.tapdisks)
482 return "Found multiple tapdisks: %s" % tapdisks
485class TapdiskFailed(Exception):
486 """Tapdisk launch failure."""
488 def __init__(self, arg, err):
489 self.arg = arg
490 self.err = err
492 @override
493 def __str__(self) -> str:
494 return "Tapdisk(%s): %s" % (self.arg, self.err)
496 def get_error(self):
497 return self.err
500class TapdiskInvalidState(Exception):
501 """Tapdisk pause/unpause failure"""
503 def __init__(self, tapdisk):
504 self.tapdisk = tapdisk
506 @override
507 def __str__(self) -> str:
508 return str(self.tapdisk)
511def mkdirs(path, mode=0o777):
512 if not os.path.exists(path):
513 parent, subdir = os.path.split(path)
514 assert parent != path
515 try:
516 if parent:
517 mkdirs(parent, mode)
518 if subdir:
519 os.mkdir(path, mode)
520 except OSError as e:
521 if e.errno != errno.EEXIST:
522 raise
525class KObject(object):
527 SYSFS_CLASSTYPE: ClassVar[str] = ""
529 @abstractmethod
530 def sysfs_devname(self) -> str:
531 pass
534class Attribute(object):
536 SYSFS_NODENAME: ClassVar[str] = ""
538 def __init__(self, path):
539 self.path = path
541 @classmethod
542 def from_kobject(cls, kobj):
543 path = "%s/%s" % (kobj.sysfs_path(), cls.SYSFS_NODENAME)
544 return cls(path)
546 class NoSuchAttribute(Exception):
547 def __init__(self, name):
548 self.name = name
550 @override
551 def __str__(self) -> str:
552 return "No such attribute: %s" % self.name
554 def _open(self, mode='r'):
555 try:
556 return open(self.path, mode)
557 except IOError as e:
558 if e.errno == errno.ENOENT:
559 raise self.NoSuchAttribute(self)
560 raise
562 def readline(self):
563 f = self._open('r')
564 s = f.readline().rstrip()
565 f.close()
566 return s
568 def writeline(self, val):
569 f = self._open('w')
570 f.write(val)
571 f.close()
574class ClassDevice(KObject):
576 @classmethod
577 def sysfs_class_path(cls):
578 return "/sys/class/%s" % cls.SYSFS_CLASSTYPE
580 def sysfs_path(self):
581 return "%s/%s" % (self.sysfs_class_path(),
582 self.sysfs_devname())
585class Blktap(ClassDevice):
587 DEV_BASEDIR = '/dev/xen/blktap-2'
589 SYSFS_CLASSTYPE = "blktap2"
591 def __init__(self, minor):
592 self.minor = minor
593 self._pool = None
594 self._task = None
596 @classmethod
597 def allocate(cls):
598 # FIXME. Should rather go into init.
599 mkdirs(cls.DEV_BASEDIR)
601 devname = TapCtl.allocate()
602 minor = Tapdisk._parse_minor(devname)
603 return cls(minor)
605 def free(self):
606 TapCtl.free(self.minor)
608 @override
609 def __str__(self) -> str:
610 return "%s(minor=%d)" % (self.__class__.__name__, self.minor)
612 @override
613 def sysfs_devname(self) -> str:
614 return "blktap!blktap%d" % self.minor
616 class Pool(Attribute):
617 SYSFS_NODENAME = "pool"
619 def get_pool_attr(self):
620 if not self._pool:
621 self._pool = self.Pool.from_kobject(self)
622 return self._pool
624 def get_pool_name(self):
625 return self.get_pool_attr().readline()
627 def set_pool_name(self, name):
628 self.get_pool_attr().writeline(name)
630 def set_pool_size(self, pages):
631 self.get_pool().set_size(pages)
633 def get_pool(self):
634 return BlktapControl.get_pool(self.get_pool_name())
636 def set_pool(self, pool):
637 self.set_pool_name(pool.name)
639 class Task(Attribute):
640 SYSFS_NODENAME = "task"
642 def get_task_attr(self):
643 if not self._task:
644 self._task = self.Task.from_kobject(self)
645 return self._task
647 def get_task_pid(self):
648 pid = self.get_task_attr().readline()
649 try:
650 return int(pid)
651 except ValueError:
652 return None
654 def find_tapdisk(self):
655 pid = self.get_task_pid()
656 if pid is None:
657 return None
659 return Tapdisk.find(pid=pid, minor=self.minor)
661 def get_tapdisk(self):
662 tapdisk = self.find_tapdisk()
663 if not tapdisk:
664 raise TapdiskNotRunning(minor=self.minor)
665 return tapdisk
668class Tapdisk(object):
670 TYPES = ['aio', 'vhd']
672 def __init__(self, pid, minor, _type, path, state):
673 self.pid = pid
674 self.minor = minor
675 self.type = _type
676 self.path = path
677 self.state = state
678 self._dirty = False
679 self._blktap = None
681 @override
682 def __str__(self) -> str:
683 state = self.pause_state()
684 return "Tapdisk(%s, pid=%d, minor=%s, state=%s)" % \
685 (self.get_arg(), self.pid, self.minor, state)
687 @classmethod
688 def list(cls, **args):
690 for row in TapCtl.list( ** args):
692 args = {'pid': None,
693 'minor': None,
694 'state': None,
695 '_type': None,
696 'path': None}
698 for key, val in row.items():
699 if key in args:
700 args[key] = val
702 if 'args' in row: 702 ↛ 707line 702 didn't jump to line 707, because the condition on line 702 was never false
703 image = Tapdisk.Arg.parse(row['args'])
704 args['_type'] = image.type
705 args['path'] = image.path
707 if None in args.values(): 707 ↛ 708line 707 didn't jump to line 708, because the condition on line 707 was never true
708 continue
710 yield Tapdisk( ** args)
712 @classmethod
713 def find(cls, **args):
715 found = list(cls.list( ** args))
717 if len(found) > 1: 717 ↛ 718line 717 didn't jump to line 718, because the condition on line 717 was never true
718 raise TapdiskNotUnique(found)
720 if found: 720 ↛ 721line 720 didn't jump to line 721, because the condition on line 720 was never true
721 return found[0]
723 return None
725 @classmethod
726 def find_by_path(cls, path):
727 return cls.find(path=path)
729 @classmethod
730 def find_by_minor(cls, minor):
731 return cls.find(minor=minor)
733 @classmethod
734 def get(cls, **attrs):
736 tapdisk = cls.find( ** attrs)
738 if not tapdisk:
739 raise TapdiskNotRunning( ** attrs)
741 return tapdisk
743 @classmethod
744 def from_path(cls, path):
745 return cls.get(path=path)
747 @classmethod
748 def from_minor(cls, minor):
749 return cls.get(minor=minor)
751 @classmethod
752 def __from_blktap(cls, blktap):
753 tapdisk = cls.from_minor(minor=blktap.minor)
754 tapdisk._blktap = blktap
755 return tapdisk
757 def get_blktap(self):
758 if not self._blktap:
759 self._blktap = Blktap(self.minor)
760 return self._blktap
762 class Arg:
764 def __init__(self, _type, path):
765 self.type = _type
766 self.path = path
768 @override
769 def __str__(self) -> str:
770 return "%s:%s" % (self.type, self.path)
772 @classmethod
773 def parse(cls, arg):
775 try:
776 _type, path = arg.split(":", 1)
777 except ValueError:
778 raise cls.InvalidArgument(arg)
780 if _type not in Tapdisk.TYPES: 780 ↛ 781line 780 didn't jump to line 781, because the condition on line 780 was never true
781 raise cls.InvalidType(_type)
783 return cls(_type, path)
785 class InvalidType(Exception):
786 def __init__(self, _type):
787 self.type = _type
789 @override
790 def __str__(self) -> str:
791 return "Not a Tapdisk type: %s" % self.type
793 class InvalidArgument(Exception):
794 def __init__(self, arg):
795 self.arg = arg
797 @override
798 def __str__(self) -> str:
799 return "Not a Tapdisk image: %s" % self.arg
801 def get_arg(self):
802 return self.Arg(self.type, self.path)
804 def get_devpath(self):
805 return "%s/tapdev%d" % (Blktap.DEV_BASEDIR, self.minor)
807 @classmethod
808 def launch_from_arg(cls, arg):
809 arg = cls.Arg.parse(arg)
810 return cls.launch(arg.path, arg.type, False)
812 @staticmethod
813 def cgclassify(pid):
815 # We dont provide any <controllers>:<path>
816 # so cgclassify uses /etc/cgrules.conf which
817 # we have configured in the spec file.
818 cmd = ["cgclassify", str(pid)]
819 try:
820 util.pread2(cmd)
821 except util.CommandException as e:
822 util.logException(e)
824 @classmethod
825 def launch_on_tap(cls, blktap, path, _type, options):
827 tapdisk = cls.find_by_path(path)
828 if tapdisk: 828 ↛ 829line 828 didn't jump to line 829, because the condition on line 828 was never true
829 raise TapdiskExists(tapdisk)
831 minor = blktap.minor
832 try:
833 pid = TapCtl.spawn()
834 cls.cgclassify(pid)
835 try:
836 TapCtl.attach(pid, minor)
838 try:
839 retry_open = 0
840 while True:
841 try:
842 TapCtl.open(pid, minor, _type, path, options)
843 break
844 except TapCtl.CommandFailure as e:
845 err = (
846 'status' in e.info and e.info['status']
847 ) or None
848 if err in (errno.EIO, errno.EROFS, errno.EAGAIN): 848 ↛ 849line 848 didn't jump to line 849, because the condition on line 848 was never true
849 if retry_open < 5:
850 retry_open += 1
851 time.sleep(1)
852 continue
853 if LINSTOR_AVAILABLE and err == errno.EROFS:
854 log_drbd_openers(path)
855 raise
856 try:
857 tapdisk = cls.__from_blktap(blktap)
858 node = '/sys/dev/block/%d:%d' % (tapdisk.major(), tapdisk.minor)
859 util.set_scheduler_sysfs_node(node, ['none', 'noop'])
860 return tapdisk
861 except:
862 TapCtl.close(pid, minor)
863 raise
865 except:
866 TapCtl.detach(pid, minor)
867 raise
869 except:
870 try:
871 TapCtl.shutdown(pid)
872 except:
873 # Best effort to shutdown
874 pass
875 raise
877 except TapCtl.CommandFailure as ctl:
878 util.logException(ctl)
879 if ((path.startswith('/dev/xapi/cd/') or path.startswith('/dev/sr')) and 879 ↛ 883line 879 didn't jump to line 883, because the condition on line 879 was never false
880 ctl.has_status and ctl.get_error_code() == 123): # ENOMEDIUM (No medium found)
881 raise xs_errors.XenError('TapdiskDriveEmpty')
882 else:
883 raise TapdiskFailed(cls.Arg(_type, path), ctl)
885 @classmethod
886 def launch(cls, path, _type, rdonly):
887 blktap = Blktap.allocate()
888 try:
889 return cls.launch_on_tap(blktap, path, _type, {"rdonly": rdonly})
890 except:
891 blktap.free()
892 raise
894 def shutdown(self, force=False):
896 TapCtl.close(self.pid, self.minor, force)
898 TapCtl.detach(self.pid, self.minor)
900 self.get_blktap().free()
902 def pause(self):
904 if not self.is_running():
905 raise TapdiskInvalidState(self)
907 TapCtl.pause(self.pid, self.minor)
909 self._set_dirty()
911 def unpause(self, _type=None, path=None, mirror=None, cbtlog=None):
913 if not self.is_paused():
914 raise TapdiskInvalidState(self)
916 # FIXME: should the arguments be optional?
917 if _type is None:
918 _type = self.type
919 if path is None:
920 path = self.path
922 TapCtl.unpause(self.pid, self.minor, _type, path, mirror=mirror,
923 cbtlog=cbtlog)
925 self._set_dirty()
927 def stats(self):
928 return json.loads(TapCtl.stats(self.pid, self.minor))
929 #
930 # NB. dirty/refresh: reload attributes on next access
931 #
933 def _set_dirty(self):
934 self._dirty = True
936 def _refresh(self, __get):
937 t = self.from_minor(__get('minor'))
938 self.__init__(t.pid, t.minor, t.type, t.path, t.state)
940 @override
941 def __getattribute__(self, name) -> Any:
942 def __get(name):
943 # NB. avoid(rec(ursion)
944 return object.__getattribute__(self, name)
946 if __get('_dirty') and \ 946 ↛ 948line 946 didn't jump to line 948, because the condition on line 946 was never true
947 name in ['minor', 'type', 'path', 'state']:
948 self._refresh(__get)
949 self._dirty = False
951 return __get(name)
953 class PauseState:
954 RUNNING = 'R'
955 PAUSING = 'r'
956 PAUSED = 'P'
958 class Flags:
959 DEAD = 0x0001
960 CLOSED = 0x0002
961 QUIESCE_REQUESTED = 0x0004
962 QUIESCED = 0x0008
963 PAUSE_REQUESTED = 0x0010
964 PAUSED = 0x0020
965 SHUTDOWN_REQUESTED = 0x0040
966 LOCKING = 0x0080
967 RETRY_NEEDED = 0x0100
968 LOG_DROPPED = 0x0200
970 PAUSE_MASK = PAUSE_REQUESTED | PAUSED
972 def is_paused(self):
973 return not not (self.state & self.Flags.PAUSED)
975 def is_running(self):
976 return not (self.state & self.Flags.PAUSE_MASK)
978 def pause_state(self):
979 if self.state & self.Flags.PAUSED:
980 return self.PauseState.PAUSED
982 if self.state & self.Flags.PAUSE_REQUESTED:
983 return self.PauseState.PAUSING
985 return self.PauseState.RUNNING
987 @staticmethod
988 def _parse_minor(devpath):
989 regex = r'%s/(blktap|tapdev)(\d+)$' % Blktap.DEV_BASEDIR
990 pattern = re.compile(regex)
991 groups = pattern.search(devpath)
992 if not groups:
993 raise Exception("malformed tap device: '%s' (%s) " % (devpath, regex))
995 minor = groups.group(2)
996 return int(minor)
998 _major = None
1000 @classmethod
1001 def major(cls):
1002 if cls._major:
1003 return cls._major
1005 devices = open("/proc/devices")
1006 for line in devices:
1008 row = line.rstrip().split(' ')
1009 if len(row) != 2:
1010 continue
1012 major, name = row
1013 if name != 'tapdev':
1014 continue
1016 cls._major = int(major)
1017 break
1019 devices.close()
1020 return cls._major
1023class VDI(object):
1024 """SR.vdi driver decorator for blktap2"""
1026 CONF_KEY_ALLOW_CACHING = "vdi_allow_caching"
1027 CONF_KEY_MODE_ON_BOOT = "vdi_on_boot"
1028 CONF_KEY_CACHE_SR = "local_cache_sr"
1029 CONF_KEY_O_DIRECT = "o_direct"
1030 LOCK_CACHE_SETUP = "cachesetup"
1032 ATTACH_DETACH_RETRY_SECS = 120
1034 def __init__(self, uuid, target, driver_info):
1035 self.target = self.TargetDriver(target, driver_info)
1036 self._vdi_uuid = uuid
1037 self._session = target.session
1038 self.xenstore_data = scsiutil.update_XS_SCSIdata(uuid, scsiutil.gen_synthetic_page_data(uuid))
1039 self.__o_direct = None
1040 self.__o_direct_reason = None
1041 self.lock = Lock("vdi", uuid)
1042 self.tap = None
1044 def get_o_direct_capability(self, options):
1045 """Returns True/False based on licensing and caching_params"""
1046 if self.__o_direct is not None: 1046 ↛ 1047line 1046 didn't jump to line 1047, because the condition on line 1046 was never true
1047 return self.__o_direct, self.__o_direct_reason
1049 if util.read_caching_is_restricted(self._session): 1049 ↛ 1050line 1049 didn't jump to line 1050, because the condition on line 1049 was never true
1050 self.__o_direct = True
1051 self.__o_direct_reason = "LICENSE_RESTRICTION"
1052 elif not ((self.target.vdi.sr.handles("nfs") or self.target.vdi.sr.handles("ext") or self.target.vdi.sr.handles("smb"))): 1052 ↛ 1055line 1052 didn't jump to line 1055, because the condition on line 1052 was never false
1053 self.__o_direct = True
1054 self.__o_direct_reason = "SR_NOT_SUPPORTED"
1055 elif not (options.get("rdonly") or self.target.vdi.parent):
1056 util.SMlog(self.target.vdi)
1057 self.__o_direct = True
1058 self.__o_direct_reason = "NO_RO_IMAGE"
1059 elif options.get("rdonly") and not self.target.vdi.parent:
1060 self.__o_direct = True
1061 self.__o_direct_reason = "RO_WITH_NO_PARENT"
1062 elif options.get(self.CONF_KEY_O_DIRECT):
1063 self.__o_direct = True
1064 self.__o_direct_reason = "SR_OVERRIDE"
1066 if self.__o_direct is None: 1066 ↛ 1067line 1066 didn't jump to line 1067, because the condition on line 1066 was never true
1067 self.__o_direct = False
1068 self.__o_direct_reason = ""
1070 return self.__o_direct, self.__o_direct_reason
1072 @classmethod
1073 def from_cli(cls, uuid):
1074 import VDI as sm
1076 session = XenAPI.xapi_local()
1077 session.xenapi.login_with_password('root', '', '', 'SM')
1079 target = sm.VDI.from_uuid(session, uuid)
1080 driver_info = target.sr.srcmd.driver_info
1082 session.xenapi.session.logout()
1084 return cls(uuid, target, driver_info)
1086 @staticmethod
1087 def _tap_type(vdi_type):
1088 """Map a VDI type (e.g. 'raw') to a tapdisk driver type (e.g. 'aio')"""
1089 return {
1090 'raw': 'aio',
1091 'vhd': 'vhd',
1092 'iso': 'aio', # for ISO SR
1093 'aio': 'aio', # for LVHD
1094 'file': 'aio',
1095 'phy': 'aio'
1096 }[vdi_type]
1098 def get_tap_type(self):
1099 vdi_type = self.target.get_vdi_type()
1100 return VDI._tap_type(vdi_type)
1102 def get_phy_path(self):
1103 return self.target.get_vdi_path()
1105 class UnexpectedVDIType(Exception):
1107 def __init__(self, vdi_type, target):
1108 self.vdi_type = vdi_type
1109 self.target = target
1111 @override
1112 def __str__(self) -> str:
1113 return \
1114 "Target %s has unexpected VDI type '%s'" % \
1115 (type(self.target), self.vdi_type)
1117 VDI_PLUG_TYPE = {'phy': 'phy', # for NETAPP
1118 'raw': 'phy',
1119 'aio': 'tap', # for LVHD raw nodes
1120 'iso': 'tap', # for ISOSR
1121 'file': 'tap',
1122 'vhd': 'tap'}
1124 def tap_wanted(self):
1125 # 1. Let the target vdi_type decide
1127 vdi_type = self.target.get_vdi_type()
1129 try:
1130 plug_type = self.VDI_PLUG_TYPE[vdi_type]
1131 except KeyError:
1132 raise self.UnexpectedVDIType(vdi_type,
1133 self.target.vdi)
1135 if plug_type == 'tap': 1135 ↛ 1136line 1135 didn't jump to line 1136, because the condition on line 1135 was never true
1136 return True
1137 elif self.target.vdi.sr.handles('udev'): 1137 ↛ 1143line 1137 didn't jump to line 1143, because the condition on line 1137 was never false
1138 return True
1139 # 2. Otherwise, there may be more reasons
1140 #
1141 # .. TBD
1143 return False
1145 class TargetDriver:
1146 """Safe target driver access."""
1147 # NB. *Must* test caps for optional calls. Some targets
1148 # actually implement some slots, but do not enable them. Just
1149 # try/except would risk breaking compatibility.
1151 def __init__(self, vdi, driver_info):
1152 self.vdi = vdi
1153 self._caps = driver_info['capabilities']
1155 def has_cap(self, cap):
1156 """Determine if target has given capability"""
1157 return cap in self._caps
1159 def attach(self, sr_uuid, vdi_uuid):
1160 #assert self.has_cap("VDI_ATTACH")
1161 return self.vdi.attach(sr_uuid, vdi_uuid)
1163 def detach(self, sr_uuid, vdi_uuid):
1164 #assert self.has_cap("VDI_DETACH")
1165 self.vdi.detach(sr_uuid, vdi_uuid)
1167 def activate(self, sr_uuid, vdi_uuid):
1168 if self.has_cap("VDI_ACTIVATE"):
1169 return self.vdi.activate(sr_uuid, vdi_uuid)
1171 def deactivate(self, sr_uuid, vdi_uuid):
1172 if self.has_cap("VDI_DEACTIVATE"):
1173 self.vdi.deactivate(sr_uuid, vdi_uuid)
1174 #def resize(self, sr_uuid, vdi_uuid, size):
1175 # return self.vdi.resize(sr_uuid, vdi_uuid, size)
1177 def get_vdi_type(self):
1178 _type = self.vdi.vdi_type
1179 if not _type:
1180 _type = self.vdi.sr.sr_vditype
1181 if not _type:
1182 raise VDI.UnexpectedVDIType(_type, self.vdi)
1183 return _type
1185 def get_vdi_path(self):
1186 return self.vdi.path
1188 class Link(object):
1189 """Relink a node under a common name"""
1190 # NB. We have to provide the device node path during
1191 # VDI.attach, but currently do not allocate the tapdisk minor
1192 # before VDI.activate. Therefore those link steps where we
1193 # relink existing devices under deterministic path names.
1195 BASEDIR: ClassVar[str] = ""
1197 def _mklink(self, target) -> None:
1198 pass
1200 @abstractmethod
1201 def _equals(self, target) -> bool:
1202 pass
1204 def __init__(self, path):
1205 self._path = path
1207 @classmethod
1208 def from_name(cls, name):
1209 path = "%s/%s" % (cls.BASEDIR, name)
1210 return cls(path)
1212 @classmethod
1213 def from_uuid(cls, sr_uuid, vdi_uuid):
1214 name = "%s/%s" % (sr_uuid, vdi_uuid)
1215 return cls.from_name(name)
1217 def path(self):
1218 return self._path
1220 def stat(self):
1221 return os.stat(self.path())
1223 def mklink(self, target) -> None:
1225 path = self.path()
1226 util.SMlog("%s -> %s" % (self, target))
1228 mkdirs(os.path.dirname(path))
1229 try:
1230 self._mklink(target)
1231 except OSError as e:
1232 # We do unlink during teardown, but have to stay
1233 # idempotent. However, a *wrong* target should never
1234 # be seen.
1235 if e.errno != errno.EEXIST:
1236 raise
1237 assert self._equals(target), "'%s' not equal to '%s'" % (path, target)
1239 def unlink(self):
1240 try:
1241 os.unlink(self.path())
1242 except OSError as e:
1243 if e.errno != errno.ENOENT:
1244 raise
1246 @override
1247 def __str__(self) -> str:
1248 path = self.path()
1249 return "%s(%s)" % (self.__class__.__name__, path)
1251 class SymLink(Link):
1252 """Symlink some file to a common name"""
1254 def readlink(self):
1255 return os.readlink(self.path())
1257 def symlink(self):
1258 return self.path()
1260 @override
1261 def _mklink(self, target) -> None:
1262 os.symlink(target, self.path())
1264 @override
1265 def _equals(self, target) -> bool:
1266 return self.readlink() == target
1268 class DeviceNode(Link):
1269 """Relink a block device node to a common name"""
1271 @classmethod
1272 def _real_stat(cls, target):
1273 """stat() not on @target, but its realpath()"""
1274 _target = os.path.realpath(target)
1275 return os.stat(_target)
1277 @classmethod
1278 def is_block(cls, target):
1279 """Whether @target refers to a block device."""
1280 return S_ISBLK(cls._real_stat(target).st_mode)
1282 @override
1283 def _mklink(self, target) -> None:
1285 st = self._real_stat(target)
1286 if not S_ISBLK(st.st_mode):
1287 raise self.NotABlockDevice(target, st)
1289 # set group read for disk group as well as root
1290 os.mknod(self.path(), st.st_mode | stat.S_IRGRP, st.st_rdev)
1291 os.chown(self.path(), st.st_uid, grp.getgrnam("disk").gr_gid)
1293 @override
1294 def _equals(self, target) -> bool:
1295 target_rdev = self._real_stat(target).st_rdev
1296 return self.stat().st_rdev == target_rdev
1298 def rdev(self):
1299 st = self.stat()
1300 assert S_ISBLK(st.st_mode)
1301 return os.major(st.st_rdev), os.minor(st.st_rdev)
1303 class NotABlockDevice(Exception):
1305 def __init__(self, path, st):
1306 self.path = path
1307 self.st = st
1309 @override
1310 def __str__(self) -> str:
1311 return "%s is not a block device: %s" % (self.path, self.st)
1313 class Hybrid(Link):
1315 def __init__(self, path):
1316 VDI.Link.__init__(self, path)
1317 self._devnode = VDI.DeviceNode(path)
1318 self._symlink = VDI.SymLink(path)
1320 def rdev(self):
1321 st = self.stat()
1322 if S_ISBLK(st.st_mode):
1323 return self._devnode.rdev()
1324 raise self._devnode.NotABlockDevice(self.path(), st)
1326 @override
1327 def mklink(self, target) -> None:
1328 if self._devnode.is_block(target):
1329 self._obj = self._devnode
1330 else:
1331 self._obj = self._symlink
1332 self._obj.mklink(target)
1334 @override
1335 def _equals(self, target) -> bool:
1336 return self._obj._equals(target)
1338 class PhyLink(SymLink):
1339 BASEDIR = "/dev/sm/phy"
1340 # NB. Cannot use DeviceNodes, e.g. FileVDIs aren't bdevs.
1342 class NBDLink(SymLink):
1344 BASEDIR = "/run/blktap-control/nbd"
1346 class BackendLink(Hybrid):
1347 BASEDIR = "/dev/sm/backend"
1348 # NB. Could be SymLinks as well, but saving major,minor pairs in
1349 # Links enables neat state capturing when managing Tapdisks. Note
1350 # that we essentially have a tap-ctl list replacement here. For
1351 # now make it a 'Hybrid'. Likely to collapse into a DeviceNode as
1352 # soon as ISOs are tapdisks.
1354 @staticmethod
1355 def _tap_activate(phy_path, vdi_type, sr_uuid, options, pool_size=None):
1357 tapdisk = Tapdisk.find_by_path(phy_path)
1358 if not tapdisk: 1358 ↛ 1359line 1358 didn't jump to line 1359, because the condition on line 1358 was never true
1359 blktap = Blktap.allocate()
1360 blktap.set_pool_name(sr_uuid)
1361 if pool_size:
1362 blktap.set_pool_size(pool_size)
1364 try:
1365 tapdisk = \
1366 Tapdisk.launch_on_tap(blktap,
1367 phy_path,
1368 VDI._tap_type(vdi_type),
1369 options)
1370 except:
1371 blktap.free()
1372 raise
1373 util.SMlog("tap.activate: Launched %s" % tapdisk)
1375 else:
1376 util.SMlog("tap.activate: Found %s" % tapdisk)
1378 return tapdisk.get_devpath(), tapdisk
1380 @staticmethod
1381 def _tap_deactivate(minor):
1383 try:
1384 tapdisk = Tapdisk.from_minor(minor)
1385 except TapdiskNotRunning as e:
1386 util.SMlog("tap.deactivate: Warning, %s" % e)
1387 # NB. Should not be here unless the agent refcount
1388 # broke. Also, a clean shutdown should not have leaked
1389 # the recorded minor.
1390 else:
1391 tapdisk.shutdown()
1392 util.SMlog("tap.deactivate: Shut down %s" % tapdisk)
1394 @classmethod
1395 def tap_pause(cls, session, sr_uuid, vdi_uuid, failfast=False):
1396 """
1397 Pauses the tapdisk.
1399 session: a XAPI session
1400 sr_uuid: the UUID of the SR on which VDI lives
1401 vdi_uuid: the UUID of the VDI to pause
1402 failfast: controls whether the VDI lock should be acquired in a
1403 non-blocking manner
1404 """
1405 util.SMlog("Pause request for %s" % vdi_uuid)
1406 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1407 session.xenapi.VDI.add_to_sm_config(vdi_ref, 'paused', 'true')
1408 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1409 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 1409 ↛ 1410line 1409 didn't jump to line 1410, because the loop on line 1409 never started
1410 host_ref = key[len('host_'):]
1411 util.SMlog("Calling tap-pause on host %s" % host_ref)
1412 if not cls.call_pluginhandler(session, host_ref,
1413 sr_uuid, vdi_uuid, "pause", failfast=failfast):
1414 # Failed to pause node
1415 session.xenapi.VDI.remove_from_sm_config(vdi_ref, 'paused')
1416 return False
1417 return True
1419 @classmethod
1420 def tap_unpause(cls, session, sr_uuid, vdi_uuid, secondary=None,
1421 activate_parents=False):
1422 util.SMlog("Unpause request for %s secondary=%s" % (vdi_uuid, secondary))
1423 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1424 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1425 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 1425 ↛ 1426line 1425 didn't jump to line 1426, because the loop on line 1425 never started
1426 host_ref = key[len('host_'):]
1427 util.SMlog("Calling tap-unpause on host %s" % host_ref)
1428 if not cls.call_pluginhandler(session, host_ref,
1429 sr_uuid, vdi_uuid, "unpause", secondary, activate_parents):
1430 # Failed to unpause node
1431 return False
1432 session.xenapi.VDI.remove_from_sm_config(vdi_ref, 'paused')
1433 return True
1435 @classmethod
1436 def tap_refresh(cls, session, sr_uuid, vdi_uuid, activate_parents=False):
1437 util.SMlog("Refresh request for %s" % vdi_uuid)
1438 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1439 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1440 for key in [x for x in sm_config.keys() if x.startswith('host_')]:
1441 host_ref = key[len('host_'):]
1442 util.SMlog("Calling tap-refresh on host %s" % host_ref)
1443 if not cls.call_pluginhandler(session, host_ref,
1444 sr_uuid, vdi_uuid, "refresh", None,
1445 activate_parents=activate_parents):
1446 # Failed to refresh node
1447 return False
1448 return True
1450 @classmethod
1451 def tap_status(cls, session, vdi_uuid):
1452 """Return True if disk is attached, false if it isn't"""
1453 util.SMlog("Disk status request for %s" % vdi_uuid)
1454 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1455 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1456 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 1456 ↛ 1457line 1456 didn't jump to line 1457, because the loop on line 1456 never started
1457 return True
1458 return False
1460 @classmethod
1461 def call_pluginhandler(cls, session, host_ref, sr_uuid, vdi_uuid, action,
1462 secondary=None, activate_parents=False, failfast=False):
1463 """Optionally, activate the parent LV before unpausing"""
1464 try:
1465 args = {"sr_uuid": sr_uuid, "vdi_uuid": vdi_uuid,
1466 "failfast": str(failfast)}
1467 if secondary:
1468 args["secondary"] = secondary
1469 if activate_parents:
1470 args["activate_parents"] = "true"
1471 ret = session.xenapi.host.call_plugin(
1472 host_ref, PLUGIN_TAP_PAUSE, action,
1473 args)
1474 return ret == "True"
1475 except Exception as e:
1476 util.logException("BLKTAP2:call_pluginhandler %s" % e)
1477 return False
1479 def _add_tag(self, vdi_uuid, writable):
1480 util.SMlog("Adding tag to: %s" % vdi_uuid)
1481 attach_mode = "RO"
1482 if writable: 1482 ↛ 1484line 1482 didn't jump to line 1484, because the condition on line 1482 was never false
1483 attach_mode = "RW"
1484 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1485 host_ref = self._session.xenapi.host.get_by_uuid(util.get_this_host())
1486 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1487 attached_as = util.attached_as(sm_config)
1488 if NO_MULTIPLE_ATTACH and (attached_as == "RW" or \ 1488 ↛ 1490line 1488 didn't jump to line 1490, because the condition on line 1488 was never true
1489 (attached_as == "RO" and attach_mode == "RW")):
1490 util.SMlog("need to reset VDI %s" % vdi_uuid)
1491 if not resetvdis.reset_vdi(self._session, vdi_uuid, force=False,
1492 term_output=False, writable=writable):
1493 raise util.SMException("VDI %s not detached cleanly" % vdi_uuid)
1494 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1495 if 'relinking' in sm_config:
1496 util.SMlog("Relinking key found, back-off and retry" % sm_config)
1497 return False
1498 if 'paused' in sm_config:
1499 util.SMlog("Paused or host_ref key found [%s]" % sm_config)
1500 return False
1501 self._session.xenapi.VDI.add_to_sm_config(
1502 vdi_ref, 'activating', 'True')
1503 host_key = "host_%s" % host_ref
1504 assert host_key not in sm_config
1505 self._session.xenapi.VDI.add_to_sm_config(vdi_ref, host_key,
1506 attach_mode)
1507 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1508 if 'paused' in sm_config or 'relinking' in sm_config:
1509 util.SMlog("Found %s key, aborting" % (
1510 'paused' if 'paused' in sm_config else 'relinking'))
1511 self._session.xenapi.VDI.remove_from_sm_config(vdi_ref, host_key)
1512 self._session.xenapi.VDI.remove_from_sm_config(
1513 vdi_ref, 'activating')
1514 return False
1515 util.SMlog("Activate lock succeeded")
1516 return True
1518 def _check_tag(self, vdi_uuid):
1519 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1520 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1521 if 'paused' in sm_config:
1522 util.SMlog("Paused key found [%s]" % sm_config)
1523 return False
1524 return True
1526 def _remove_tag(self, vdi_uuid):
1527 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1528 host_ref = self._session.xenapi.host.get_by_uuid(util.get_this_host())
1529 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1530 host_key = "host_%s" % host_ref
1531 if host_key in sm_config:
1532 self._session.xenapi.VDI.remove_from_sm_config(vdi_ref, host_key)
1533 util.SMlog("Removed host key %s for %s" % (host_key, vdi_uuid))
1534 else:
1535 util.SMlog("_remove_tag: host key %s not found, ignore" % host_key)
1537 def _get_pool_config(self, pool_name):
1538 pool_info = dict()
1539 vdi_ref = self.target.vdi.sr.srcmd.params.get('vdi_ref')
1540 if not vdi_ref: 1540 ↛ 1543line 1540 didn't jump to line 1543, because the condition on line 1540 was never true
1541 # attach_from_config context: HA disks don't need to be in any
1542 # special pool
1543 return pool_info
1545 sr_ref = self.target.vdi.sr.srcmd.params.get('sr_ref')
1546 sr_config = self._session.xenapi.SR.get_other_config(sr_ref)
1547 vdi_config = self._session.xenapi.VDI.get_other_config(vdi_ref)
1548 pool_size_str = sr_config.get(POOL_SIZE_KEY)
1549 pool_name_override = vdi_config.get(POOL_NAME_KEY)
1550 if pool_name_override: 1550 ↛ 1555line 1550 didn't jump to line 1555, because the condition on line 1550 was never false
1551 pool_name = pool_name_override
1552 pool_size_override = vdi_config.get(POOL_SIZE_KEY)
1553 if pool_size_override: 1553 ↛ 1555line 1553 didn't jump to line 1555, because the condition on line 1553 was never false
1554 pool_size_str = pool_size_override
1555 pool_size = 0
1556 if pool_size_str: 1556 ↛ 1566line 1556 didn't jump to line 1566, because the condition on line 1556 was never false
1557 try:
1558 pool_size = int(pool_size_str)
1559 if pool_size < 1 or pool_size > MAX_FULL_RINGS: 1559 ↛ 1560line 1559 didn't jump to line 1560, because the condition on line 1559 was never true
1560 raise ValueError("outside of range")
1561 pool_size = NUM_PAGES_PER_RING * pool_size
1562 except ValueError:
1563 util.SMlog("Error: invalid mem-pool-size %s" % pool_size_str)
1564 pool_size = 0
1566 pool_info["mem-pool"] = pool_name
1567 if pool_size: 1567 ↛ 1570line 1567 didn't jump to line 1570, because the condition on line 1567 was never false
1568 pool_info["mem-pool-size"] = str(pool_size)
1570 return pool_info
1572 def linkNBD(self, sr_uuid, vdi_uuid):
1573 if self.tap:
1574 nbd_path = '/run/blktap-control/nbd%d.%d' % (int(self.tap.pid),
1575 int(self.tap.minor))
1576 VDI.NBDLink.from_uuid(sr_uuid, vdi_uuid).mklink(nbd_path)
1578 def attach(self, sr_uuid, vdi_uuid, writable, activate=False, caching_params={}):
1579 """Return/dev/sm/backend symlink path"""
1580 self.xenstore_data.update(self._get_pool_config(sr_uuid))
1581 if not self.target.has_cap("ATOMIC_PAUSE") or activate:
1582 util.SMlog("Attach & activate")
1583 self._attach(sr_uuid, vdi_uuid)
1584 dev_path = self._activate(sr_uuid, vdi_uuid,
1585 {"rdonly": not writable})
1586 self.BackendLink.from_uuid(sr_uuid, vdi_uuid).mklink(dev_path)
1587 self.linkNBD(sr_uuid, vdi_uuid)
1589 # Return backend/ link
1590 back_path = self.BackendLink.from_uuid(sr_uuid, vdi_uuid).path()
1591 if self.tap_wanted():
1592 # Only have NBD if we also have a tap
1593 nbd_path = "nbd:unix:{}:exportname={}".format(
1594 VDI.NBDLink.from_uuid(sr_uuid, vdi_uuid).path(),
1595 vdi_uuid)
1596 else:
1597 nbd_path = ""
1599 options = {"rdonly": not writable}
1600 options.update(caching_params)
1601 o_direct, o_direct_reason = self.get_o_direct_capability(options)
1602 struct = {'params': back_path,
1603 'params_nbd': nbd_path,
1604 'o_direct': o_direct,
1605 'o_direct_reason': o_direct_reason,
1606 'xenstore_data': self.xenstore_data}
1607 util.SMlog('result: %s' % struct)
1609 try:
1610 f = open("%s.attach_info" % back_path, 'a')
1611 f.write(xmlrpc.client.dumps((struct, ), "", True))
1612 f.close()
1613 except:
1614 pass
1616 return xmlrpc.client.dumps((struct, ), "", True)
1618 def activate(self, sr_uuid, vdi_uuid, writable, caching_params):
1619 util.SMlog("blktap2.activate")
1620 options = {"rdonly": not writable}
1621 options.update(caching_params)
1623 sr_ref = self.target.vdi.sr.srcmd.params.get('sr_ref')
1624 sr_other_config = self._session.xenapi.SR.get_other_config(sr_ref)
1625 for i in range(self.ATTACH_DETACH_RETRY_SECS): 1625 ↛ 1632line 1625 didn't jump to line 1632, because the loop on line 1625 didn't complete
1626 try:
1627 if self._activate_locked(sr_uuid, vdi_uuid, options):
1628 return
1629 except util.SRBusyException:
1630 util.SMlog("SR locked, retrying")
1631 time.sleep(1)
1632 raise util.SMException("VDI %s locked" % vdi_uuid)
1634 @locking("VDIUnavailable")
1635 def _activate_locked(self, sr_uuid, vdi_uuid, options):
1636 """Wraps target.activate and adds a tapdisk"""
1638 #util.SMlog("VDI.activate %s" % vdi_uuid)
1639 refresh = False
1640 if self.tap_wanted(): 1640 ↛ 1645line 1640 didn't jump to line 1645, because the condition on line 1640 was never false
1641 if not self._add_tag(vdi_uuid, not options["rdonly"]):
1642 return False
1643 refresh = True
1645 try:
1646 if refresh: 1646 ↛ 1657line 1646 didn't jump to line 1657, because the condition on line 1646 was never false
1647 # it is possible that while the VDI was paused some of its
1648 # attributes have changed (e.g. its size if it was inflated; or its
1649 # path if it was leaf-coalesced onto a raw LV), so refresh the
1650 # object completely
1651 params = self.target.vdi.sr.srcmd.params
1652 target = sm.VDI.from_uuid(self.target.vdi.session, vdi_uuid)
1653 target.sr.srcmd.params = params
1654 driver_info = target.sr.srcmd.driver_info
1655 self.target = self.TargetDriver(target, driver_info)
1657 util.fistpoint.activate_custom_fn( 1657 ↛ exitline 1657 didn't jump to the function exit
1658 "blktap_activate_inject_failure",
1659 lambda: util.inject_failure())
1661 # Attach the physical node
1662 if self.target.has_cap("ATOMIC_PAUSE"): 1662 ↛ 1665line 1662 didn't jump to line 1665, because the condition on line 1662 was never false
1663 self._attach(sr_uuid, vdi_uuid)
1665 vdi_type = self.target.get_vdi_type()
1667 # Take lvchange-p Lock before running
1668 # tap-ctl open
1669 # Needed to avoid race with lvchange -p which is
1670 # now taking the same lock
1671 # This is a fix for CA-155766
1672 if hasattr(self.target.vdi.sr, 'DRIVER_TYPE') and \ 1672 ↛ 1675line 1672 didn't jump to line 1675, because the condition on line 1672 was never true
1673 self.target.vdi.sr.DRIVER_TYPE == 'lvhd' and \
1674 vdi_type == vhdutil.VDI_TYPE_VHD:
1675 lock = Lock("lvchange-p", lvhdutil.NS_PREFIX_LVM + sr_uuid)
1676 lock.acquire()
1678 # When we attach a static VDI for HA, we cannot communicate with
1679 # xapi, because has not started yet. These VDIs are raw.
1680 if vdi_type != vhdutil.VDI_TYPE_RAW: 1680 ↛ 1691line 1680 didn't jump to line 1691, because the condition on line 1680 was never false
1681 session = self.target.vdi.session
1682 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1683 # pylint: disable=used-before-assignment
1684 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1685 if 'key_hash' in sm_config: 1685 ↛ 1686line 1685 didn't jump to line 1686, because the condition on line 1685 was never true
1686 key_hash = sm_config['key_hash']
1687 options['key_hash'] = key_hash
1688 options['vdi_uuid'] = vdi_uuid
1689 util.SMlog('Using key with hash {} for VDI {}'.format(key_hash, vdi_uuid))
1690 # Activate the physical node
1691 dev_path = self._activate(sr_uuid, vdi_uuid, options)
1693 if hasattr(self.target.vdi.sr, 'DRIVER_TYPE') and \ 1693 ↛ 1696line 1693 didn't jump to line 1696, because the condition on line 1693 was never true
1694 self.target.vdi.sr.DRIVER_TYPE == 'lvhd' and \
1695 self.target.get_vdi_type() == vhdutil.VDI_TYPE_VHD:
1696 lock.release()
1697 except:
1698 util.SMlog("Exception in activate/attach")
1699 if self.tap_wanted():
1700 util.fistpoint.activate_custom_fn(
1701 "blktap_activate_error_handling",
1702 lambda: time.sleep(30))
1703 while True:
1704 try:
1705 self._remove_tag(vdi_uuid)
1706 break
1707 except xmlrpc.client.ProtocolError as e:
1708 # If there's a connection error, keep trying forever.
1709 if e.errcode == http.HTTPStatus.INTERNAL_SERVER_ERROR.value:
1710 continue
1711 else:
1712 util.SMlog('failed to remove tag: %s' % e)
1713 break
1714 except Exception as e:
1715 util.SMlog('failed to remove tag: %s' % e)
1716 break
1717 raise
1718 finally:
1719 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1720 self._session.xenapi.VDI.remove_from_sm_config(
1721 vdi_ref, 'activating')
1722 util.SMlog("Removed activating flag from %s" % vdi_uuid) 1722 ↛ exitline 1722 didn't except from function '_activate_locked', because the raise on line 1717 wasn't executed
1724 # Link result to backend/
1725 self.BackendLink.from_uuid(sr_uuid, vdi_uuid).mklink(dev_path)
1726 self.linkNBD(sr_uuid, vdi_uuid)
1727 return True
1729 def _activate(self, sr_uuid, vdi_uuid, options):
1730 vdi_options = self.target.activate(sr_uuid, vdi_uuid)
1732 dev_path = self.setup_cache(sr_uuid, vdi_uuid, options)
1733 if not dev_path: 1733 ↛ 1747line 1733 didn't jump to line 1747, because the condition on line 1733 was never false
1734 phy_path = self.PhyLink.from_uuid(sr_uuid, vdi_uuid).readlink()
1735 # Maybe launch a tapdisk on the physical link
1736 if self.tap_wanted(): 1736 ↛ 1745line 1736 didn't jump to line 1745, because the condition on line 1736 was never false
1737 vdi_type = self.target.get_vdi_type()
1738 options["o_direct"] = self.get_o_direct_capability(options)[0]
1739 if vdi_options: 1739 ↛ 1741line 1739 didn't jump to line 1741, because the condition on line 1739 was never false
1740 options.update(vdi_options)
1741 dev_path, self.tap = self._tap_activate(phy_path, vdi_type,
1742 sr_uuid, options,
1743 self._get_pool_config(sr_uuid).get("mem-pool-size"))
1744 else:
1745 dev_path = phy_path # Just reuse phy
1747 return dev_path
1749 def _attach(self, sr_uuid, vdi_uuid):
1750 attach_info = xmlrpc.client.loads(self.target.attach(sr_uuid, vdi_uuid))[0][0]
1751 params = attach_info['params']
1752 xenstore_data = attach_info['xenstore_data']
1753 phy_path = util.to_plain_string(params)
1754 self.xenstore_data.update(xenstore_data)
1755 # Save it to phy/
1756 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).mklink(phy_path)
1758 def deactivate(self, sr_uuid, vdi_uuid, caching_params):
1759 util.SMlog("blktap2.deactivate")
1760 for i in range(self.ATTACH_DETACH_RETRY_SECS):
1761 try:
1762 if self._deactivate_locked(sr_uuid, vdi_uuid, caching_params):
1763 return
1764 except util.SRBusyException as e:
1765 util.SMlog("SR locked, retrying")
1766 time.sleep(1)
1767 raise util.SMException("VDI %s locked" % vdi_uuid)
1769 @locking("VDIUnavailable")
1770 def _deactivate_locked(self, sr_uuid, vdi_uuid, caching_params):
1771 """Wraps target.deactivate and removes a tapdisk"""
1773 #util.SMlog("VDI.deactivate %s" % vdi_uuid)
1774 if self.tap_wanted() and not self._check_tag(vdi_uuid):
1775 return False
1777 self._deactivate(sr_uuid, vdi_uuid, caching_params)
1778 if self.target.has_cap("ATOMIC_PAUSE"):
1779 self._detach(sr_uuid, vdi_uuid)
1780 if self.tap_wanted():
1781 self._remove_tag(vdi_uuid)
1783 return True
1785 def _resetPhylink(self, sr_uuid, vdi_uuid, path):
1786 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).mklink(path)
1788 def detach(self, sr_uuid, vdi_uuid, deactivate=False, caching_params={}):
1789 if not self.target.has_cap("ATOMIC_PAUSE") or deactivate:
1790 util.SMlog("Deactivate & detach")
1791 self._deactivate(sr_uuid, vdi_uuid, caching_params)
1792 self._detach(sr_uuid, vdi_uuid)
1793 else:
1794 pass # nothing to do
1796 def _deactivate(self, sr_uuid, vdi_uuid, caching_params):
1797 import VDI as sm
1799 # Shutdown tapdisk
1800 back_link = self.BackendLink.from_uuid(sr_uuid, vdi_uuid)
1802 if not util.pathexists(back_link.path()):
1803 util.SMlog("Backend path %s does not exist" % back_link.path())
1804 return
1806 try:
1807 attach_info_path = "%s.attach_info" % (back_link.path())
1808 os.unlink(attach_info_path)
1809 except:
1810 util.SMlog("unlink of attach_info failed")
1812 try:
1813 major, minor = back_link.rdev()
1814 except self.DeviceNode.NotABlockDevice:
1815 pass
1816 else:
1817 if major == Tapdisk.major():
1818 self._tap_deactivate(minor)
1819 self.remove_cache(sr_uuid, vdi_uuid, caching_params)
1821 # Remove the backend link
1822 back_link.unlink()
1823 VDI.NBDLink.from_uuid(sr_uuid, vdi_uuid).unlink()
1825 # Deactivate & detach the physical node
1826 if self.tap_wanted() and self.target.vdi.session is not None:
1827 # it is possible that while the VDI was paused some of its
1828 # attributes have changed (e.g. its size if it was inflated; or its
1829 # path if it was leaf-coalesced onto a raw LV), so refresh the
1830 # object completely
1831 target = sm.VDI.from_uuid(self.target.vdi.session, vdi_uuid)
1832 driver_info = target.sr.srcmd.driver_info
1833 self.target = self.TargetDriver(target, driver_info)
1835 self.target.deactivate(sr_uuid, vdi_uuid)
1837 def _detach(self, sr_uuid, vdi_uuid):
1838 self.target.detach(sr_uuid, vdi_uuid)
1840 # Remove phy/
1841 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).unlink()
1843 def _updateCacheRecord(self, session, vdi_uuid, on_boot, caching):
1844 # Remove existing VDI.sm_config fields
1845 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1846 for key in ["on_boot", "caching"]:
1847 session.xenapi.VDI.remove_from_sm_config(vdi_ref, key)
1848 if not on_boot is None:
1849 session.xenapi.VDI.add_to_sm_config(vdi_ref, 'on_boot', on_boot)
1850 if not caching is None:
1851 session.xenapi.VDI.add_to_sm_config(vdi_ref, 'caching', caching)
1853 def setup_cache(self, sr_uuid, vdi_uuid, params):
1854 if params.get(self.CONF_KEY_ALLOW_CACHING) != "true": 1854 ↛ 1857line 1854 didn't jump to line 1857, because the condition on line 1854 was never false
1855 return
1857 util.SMlog("Requested local caching")
1858 if not self.target.has_cap("SR_CACHING"):
1859 util.SMlog("Error: local caching not supported by this SR")
1860 return
1862 scratch_mode = False
1863 if params.get(self.CONF_KEY_MODE_ON_BOOT) == "reset":
1864 scratch_mode = True
1865 util.SMlog("Requested scratch mode")
1866 if not self.target.has_cap("VDI_RESET_ON_BOOT/2"):
1867 util.SMlog("Error: scratch mode not supported by this SR")
1868 return
1870 dev_path = None
1871 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR)
1872 if not local_sr_uuid:
1873 util.SMlog("ERROR: Local cache SR not specified, not enabling")
1874 return
1875 dev_path = self._setup_cache(self._session, sr_uuid, vdi_uuid,
1876 local_sr_uuid, scratch_mode, params)
1878 if dev_path:
1879 self._updateCacheRecord(self._session, self.target.vdi.uuid,
1880 params.get(self.CONF_KEY_MODE_ON_BOOT),
1881 params.get(self.CONF_KEY_ALLOW_CACHING))
1883 return dev_path
1885 def alert_no_cache(self, session, vdi_uuid, cache_sr_uuid, err):
1886 vm_uuid = None
1887 vm_label = ""
1888 try:
1889 cache_sr_ref = session.xenapi.SR.get_by_uuid(cache_sr_uuid)
1890 cache_sr_rec = session.xenapi.SR.get_record(cache_sr_ref)
1891 cache_sr_label = cache_sr_rec.get("name_label")
1893 host_ref = session.xenapi.host.get_by_uuid(util.get_this_host())
1894 host_rec = session.xenapi.host.get_record(host_ref)
1895 host_label = host_rec.get("name_label")
1897 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1898 vbds = session.xenapi.VBD.get_all_records_where( \
1899 "field \"VDI\" = \"%s\"" % vdi_ref)
1900 for vbd_rec in vbds.values():
1901 vm_ref = vbd_rec.get("VM")
1902 vm_rec = session.xenapi.VM.get_record(vm_ref)
1903 vm_uuid = vm_rec.get("uuid")
1904 vm_label = vm_rec.get("name_label")
1905 except:
1906 util.logException("alert_no_cache")
1908 alert_obj = "SR"
1909 alert_uuid = str(cache_sr_uuid)
1910 alert_str = "No space left in Local Cache SR %s" % cache_sr_uuid
1911 if vm_uuid:
1912 alert_obj = "VM"
1913 alert_uuid = vm_uuid
1914 reason = ""
1915 if err == errno.ENOSPC:
1916 reason = "because there is no space left"
1917 alert_str = "The VM \"%s\" is not using IntelliCache %s on the Local Cache SR (\"%s\") on host \"%s\"" % \
1918 (vm_label, reason, cache_sr_label, host_label)
1920 util.SMlog("Creating alert: (%s, %s, \"%s\")" % \
1921 (alert_obj, alert_uuid, alert_str))
1922 session.xenapi.message.create("No space left in local cache", "3",
1923 alert_obj, alert_uuid, alert_str)
1925 def _setup_cache(self, session, sr_uuid, vdi_uuid, local_sr_uuid,
1926 scratch_mode, options):
1927 import SR
1928 import EXTSR
1929 import NFSSR
1930 from lock import Lock
1931 from FileSR import FileVDI
1933 parent_uuid = vhdutil.getParent(self.target.vdi.path,
1934 FileVDI.extractUuid)
1935 if not parent_uuid:
1936 util.SMlog("ERROR: VDI %s has no parent, not enabling" % \
1937 self.target.vdi.uuid)
1938 return
1940 util.SMlog("Setting up cache")
1941 parent_uuid = parent_uuid.strip()
1942 shared_target = NFSSR.NFSFileVDI(self.target.vdi.sr, parent_uuid)
1944 if shared_target.parent:
1945 util.SMlog("ERROR: Parent VDI %s has parent, not enabling" %
1946 shared_target.uuid)
1947 return
1949 SR.registerSR(EXTSR.EXTSR)
1950 local_sr = SR.SR.from_uuid(session, local_sr_uuid)
1952 lock = Lock(self.LOCK_CACHE_SETUP, parent_uuid)
1953 lock.acquire()
1955 # read cache
1956 read_cache_path = "%s/%s.vhdcache" % (local_sr.path, shared_target.uuid)
1957 if util.pathexists(read_cache_path):
1958 util.SMlog("Read cache node (%s) already exists, not creating" % \
1959 read_cache_path)
1960 else:
1961 try:
1962 vhdutil.snapshot(read_cache_path, shared_target.path, False)
1963 except util.CommandException as e:
1964 util.SMlog("Error creating parent cache: %s" % e)
1965 self.alert_no_cache(session, vdi_uuid, local_sr_uuid, e.code)
1966 return None
1968 # local write node
1969 leaf_size = vhdutil.getSizeVirt(self.target.vdi.path)
1970 local_leaf_path = "%s/%s.vhdcache" % \
1971 (local_sr.path, self.target.vdi.uuid)
1972 if util.pathexists(local_leaf_path):
1973 util.SMlog("Local leaf node (%s) already exists, deleting" % \
1974 local_leaf_path)
1975 os.unlink(local_leaf_path)
1976 try:
1977 vhdutil.snapshot(local_leaf_path, read_cache_path, False,
1978 msize=leaf_size // 1024 // 1024, checkEmpty=False)
1979 except util.CommandException as e:
1980 util.SMlog("Error creating leaf cache: %s" % e)
1981 self.alert_no_cache(session, vdi_uuid, local_sr_uuid, e.code)
1982 return None
1984 local_leaf_size = vhdutil.getSizeVirt(local_leaf_path)
1985 if leaf_size > local_leaf_size:
1986 util.SMlog("Leaf size %d > local leaf cache size %d, resizing" %
1987 (leaf_size, local_leaf_size))
1988 vhdutil.setSizeVirtFast(local_leaf_path, leaf_size)
1990 vdi_type = self.target.get_vdi_type()
1992 prt_tapdisk = Tapdisk.find_by_path(read_cache_path)
1993 if not prt_tapdisk:
1994 parent_options = copy.deepcopy(options)
1995 parent_options["rdonly"] = False
1996 parent_options["lcache"] = True
1998 blktap = Blktap.allocate()
1999 try:
2000 blktap.set_pool_name("lcache-parent-pool-%s" % blktap.minor)
2001 # no need to change pool_size since each parent tapdisk is in
2002 # its own pool
2003 prt_tapdisk = \
2004 Tapdisk.launch_on_tap(blktap, read_cache_path,
2005 'vhd', parent_options)
2006 except:
2007 blktap.free()
2008 raise
2010 secondary = "%s:%s" % (self.target.get_vdi_type(),
2011 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).readlink())
2013 util.SMlog("Parent tapdisk: %s" % prt_tapdisk)
2014 leaf_tapdisk = Tapdisk.find_by_path(local_leaf_path)
2015 if not leaf_tapdisk:
2016 blktap = Blktap.allocate()
2017 child_options = copy.deepcopy(options)
2018 child_options["rdonly"] = False
2019 child_options["lcache"] = False
2020 child_options["existing_prt"] = prt_tapdisk.minor
2021 child_options["secondary"] = secondary
2022 child_options["standby"] = scratch_mode
2023 try:
2024 leaf_tapdisk = \
2025 Tapdisk.launch_on_tap(blktap, local_leaf_path,
2026 'vhd', child_options)
2027 except:
2028 blktap.free()
2029 raise
2031 lock.release()
2033 util.SMlog("Local read cache: %s, local leaf: %s" % \
2034 (read_cache_path, local_leaf_path))
2036 self.tap = leaf_tapdisk
2037 return leaf_tapdisk.get_devpath()
2039 def remove_cache(self, sr_uuid, vdi_uuid, params):
2040 if not self.target.has_cap("SR_CACHING"):
2041 return
2043 caching = params.get(self.CONF_KEY_ALLOW_CACHING) == "true"
2045 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR)
2046 if caching and not local_sr_uuid:
2047 util.SMlog("ERROR: Local cache SR not specified, ignore")
2048 return
2050 if caching:
2051 self._remove_cache(self._session, local_sr_uuid)
2053 if self._session is not None:
2054 self._updateCacheRecord(self._session, self.target.vdi.uuid, None, None)
2056 def _is_tapdisk_in_use(self, minor):
2057 retVal, links, sockets = util.findRunningProcessOrOpenFile("tapdisk")
2058 if not retVal:
2059 # err on the side of caution
2060 return True
2062 for link in links:
2063 if link.find("tapdev%d" % minor) != -1:
2064 return True
2066 socket_re = re.compile(r'^/.*/nbd\d+\.%d' % minor)
2067 for s in sockets:
2068 if socket_re.match(s):
2069 return True
2071 return False
2073 def _remove_cache(self, session, local_sr_uuid):
2074 import SR
2075 import EXTSR
2076 import NFSSR
2077 from lock import Lock
2078 from FileSR import FileVDI
2080 parent_uuid = vhdutil.getParent(self.target.vdi.path,
2081 FileVDI.extractUuid)
2082 if not parent_uuid:
2083 util.SMlog("ERROR: No parent for VDI %s, ignore" % \
2084 self.target.vdi.uuid)
2085 return
2087 util.SMlog("Tearing down the cache")
2089 parent_uuid = parent_uuid.strip()
2090 shared_target = NFSSR.NFSFileVDI(self.target.vdi.sr, parent_uuid)
2092 SR.registerSR(EXTSR.EXTSR)
2093 local_sr = SR.SR.from_uuid(session, local_sr_uuid)
2095 lock = Lock(self.LOCK_CACHE_SETUP, parent_uuid)
2096 lock.acquire()
2098 # local write node
2099 local_leaf_path = "%s/%s.vhdcache" % \
2100 (local_sr.path, self.target.vdi.uuid)
2101 if util.pathexists(local_leaf_path):
2102 util.SMlog("Deleting local leaf node %s" % local_leaf_path)
2103 os.unlink(local_leaf_path)
2105 read_cache_path = "%s/%s.vhdcache" % (local_sr.path, shared_target.uuid)
2106 prt_tapdisk = Tapdisk.find_by_path(read_cache_path)
2107 if not prt_tapdisk:
2108 util.SMlog("Parent tapdisk not found")
2109 elif not self._is_tapdisk_in_use(prt_tapdisk.minor):
2110 util.SMlog("Parent tapdisk not in use: shutting down %s" % \
2111 read_cache_path)
2112 try:
2113 prt_tapdisk.shutdown()
2114 except:
2115 util.logException("shutting down parent tapdisk")
2116 else:
2117 util.SMlog("Parent tapdisk still in use: %s" % read_cache_path)
2118 # the parent cache files are removed during the local SR's background
2119 # GC run
2121 lock.release()
2123PythonKeyError = KeyError
2126class UEventHandler(object):
2128 def __init__(self):
2129 self._action = None
2131 class KeyError(PythonKeyError):
2132 def __init__(self, args):
2133 super().__init__(args)
2134 self.key = args[0]
2136 @override
2137 def __str__(self) -> str:
2138 return \
2139 "Key '%s' missing in environment. " % self.key + \
2140 "Not called in udev context?"
2142 @classmethod
2143 def getenv(cls, key):
2144 try:
2145 return os.environ[key]
2146 except KeyError as e:
2147 raise cls.KeyError(e.args[0])
2149 def get_action(self):
2150 if not self._action:
2151 self._action = self.getenv('ACTION')
2152 return self._action
2154 class UnhandledEvent(Exception):
2156 def __init__(self, event, handler):
2157 self.event = event
2158 self.handler = handler
2160 @override
2161 def __str__(self) -> str:
2162 return "Uevent '%s' not handled by %s" % \
2163 (self.event, self.handler.__class__.__name__)
2165 ACTIONS: Dict[str, Callable] = {}
2167 def run(self):
2169 action = self.get_action()
2170 try:
2171 fn = self.ACTIONS[action]
2172 except KeyError:
2173 raise self.UnhandledEvent(action, self)
2175 return fn(self)
2177 @override
2178 def __str__(self) -> str:
2179 try:
2180 action = self.get_action()
2181 except:
2182 action = None
2183 return "%s[%s]" % (self.__class__.__name__, action)
2186class __BlktapControl(ClassDevice):
2187 SYSFS_CLASSTYPE = "misc"
2189 def __init__(self):
2190 ClassDevice.__init__(self)
2191 self._default_pool = None
2193 @override
2194 def sysfs_devname(self) -> str:
2195 return "blktap!control"
2197 class DefaultPool(Attribute):
2198 SYSFS_NODENAME = "default_pool"
2200 def get_default_pool_attr(self):
2201 if not self._default_pool:
2202 self._default_pool = self.DefaultPool.from_kobject(self)
2203 return self._default_pool
2205 def get_default_pool_name(self):
2206 return self.get_default_pool_attr().readline()
2208 def set_default_pool_name(self, name):
2209 self.get_default_pool_attr().writeline(name)
2211 def get_default_pool(self):
2212 return BlktapControl.get_pool(self.get_default_pool_name())
2214 def set_default_pool(self, pool):
2215 self.set_default_pool_name(pool.name)
2217 class NoSuchPool(Exception):
2218 def __init__(self, name):
2219 self.name = name
2221 @override
2222 def __str__(self) -> str:
2223 return "No such pool: {}".format(self.name)
2225 def get_pool(self, name):
2226 path = "%s/pools/%s" % (self.sysfs_path(), name)
2228 if not os.path.isdir(path):
2229 raise self.NoSuchPool(name)
2231 return PagePool(path)
2233BlktapControl = __BlktapControl()
2236class PagePool(KObject):
2238 def __init__(self, path):
2239 self.path = path
2240 self._size = None
2242 @override
2243 def sysfs_devname(self) -> str:
2244 return ''
2246 def sysfs_path(self):
2247 return self.path
2249 class Size(Attribute):
2250 SYSFS_NODENAME = "size"
2252 def get_size_attr(self):
2253 if not self._size:
2254 self._size = self.Size.from_kobject(self)
2255 return self._size
2257 def set_size(self, pages):
2258 pages = str(pages)
2259 self.get_size_attr().writeline(pages)
2261 def get_size(self):
2262 pages = self.get_size_attr().readline()
2263 return int(pages)
2266class BusDevice(KObject):
2268 SYSFS_BUSTYPE: ClassVar[str] = ""
2270 @classmethod
2271 def sysfs_bus_path(cls):
2272 return "/sys/bus/%s" % cls.SYSFS_BUSTYPE
2274 def sysfs_path(self):
2275 path = "%s/devices/%s" % (self.sysfs_bus_path(),
2276 self.sysfs_devname())
2278 return path
2281class XenbusDevice(BusDevice):
2282 """Xenbus device, in XS and sysfs"""
2284 XBT_NIL = ""
2286 XENBUS_DEVTYPE: ClassVar[str] = ""
2288 def __init__(self, domid, devid):
2289 self.domid = int(domid)
2290 self.devid = int(devid)
2291 self._xbt = XenbusDevice.XBT_NIL
2293 import xen.lowlevel.xs # pylint: disable=import-error
2294 self.xs = xen.lowlevel.xs.xs()
2296 def xs_path(self, key=None):
2297 path = "backend/%s/%d/%d" % (self.XENBUS_DEVTYPE,
2298 self.domid,
2299 self.devid)
2300 if key is not None:
2301 path = "%s/%s" % (path, key)
2303 return path
2305 def _log(self, prio, msg):
2306 syslog(prio, msg)
2308 def info(self, msg):
2309 self._log(_syslog.LOG_INFO, msg)
2311 def warn(self, msg):
2312 self._log(_syslog.LOG_WARNING, "WARNING: " + msg)
2314 def _xs_read_path(self, path):
2315 val = self.xs.read(self._xbt, path)
2316 #self.info("read %s = '%s'" % (path, val))
2317 return val
2319 def _xs_write_path(self, path, val):
2320 self.xs.write(self._xbt, path, val)
2321 self.info("wrote %s = '%s'" % (path, val))
2323 def _xs_rm_path(self, path):
2324 self.xs.rm(self._xbt, path)
2325 self.info("removed %s" % path)
2327 def read(self, key):
2328 return self._xs_read_path(self.xs_path(key))
2330 def has_xs_key(self, key):
2331 return self.read(key) is not None
2333 def write(self, key, val):
2334 self._xs_write_path(self.xs_path(key), val)
2336 def rm(self, key):
2337 self._xs_rm_path(self.xs_path(key))
2339 def exists(self):
2340 return self.has_xs_key(None)
2342 def begin(self):
2343 assert(self._xbt == XenbusDevice.XBT_NIL)
2344 self._xbt = self.xs.transaction_start()
2346 def commit(self):
2347 ok = self.xs.transaction_end(self._xbt, 0)
2348 self._xbt = XenbusDevice.XBT_NIL
2349 return ok
2351 def abort(self):
2352 ok = self.xs.transaction_end(self._xbt, 1)
2353 assert(ok == True)
2354 self._xbt = XenbusDevice.XBT_NIL
2356 def create_physical_device(self):
2357 """The standard protocol is: toolstack writes 'params', linux hotplug
2358 script translates this into physical-device=%x:%x"""
2359 if self.has_xs_key("physical-device"):
2360 return
2361 try:
2362 params = self.read("params")
2363 frontend = self.read("frontend")
2364 is_cdrom = self._xs_read_path("%s/device-type") == "cdrom"
2365 # We don't have PV drivers for CDROM devices, so we prevent blkback
2366 # from opening the physical-device
2367 if not(is_cdrom):
2368 major_minor = os.stat(params).st_rdev
2369 major, minor = divmod(major_minor, 256)
2370 self.write("physical-device", "%x:%x" % (major, minor))
2371 except:
2372 util.logException("BLKTAP2:create_physical_device")
2374 def signal_hotplug(self, online=True):
2375 xapi_path = "/xapi/%d/hotplug/%s/%d/hotplug" % (self.domid,
2376 self.XENBUS_DEVTYPE,
2377 self.devid)
2378 upstream_path = self.xs_path("hotplug-status")
2379 if online:
2380 self._xs_write_path(xapi_path, "online")
2381 self._xs_write_path(upstream_path, "connected")
2382 else:
2383 self._xs_rm_path(xapi_path)
2384 self._xs_rm_path(upstream_path)
2386 @override
2387 def sysfs_devname(self) -> str:
2388 return "%s-%d-%d" % (self.XENBUS_DEVTYPE,
2389 self.domid, self.devid)
2391 @override
2392 def __str__(self) -> str:
2393 return self.sysfs_devname()
2395 @classmethod
2396 def find(cls):
2397 pattern = "/sys/bus/%s/devices/%s*" % (cls.SYSFS_BUSTYPE,
2398 cls.XENBUS_DEVTYPE)
2399 for path in glob.glob(pattern):
2401 name = os.path.basename(path)
2402 (_type, domid, devid) = name.split('-')
2404 yield cls(domid, devid)
2407class XenBackendDevice(XenbusDevice):
2408 """Xenbus backend device"""
2409 SYSFS_BUSTYPE = "xen-backend"
2411 @classmethod
2412 def from_xs_path(cls, _path):
2413 (_backend, _type, domid, devid) = _path.split('/')
2415 assert _backend == 'backend'
2416 assert _type == cls.XENBUS_DEVTYPE
2418 domid = int(domid)
2419 devid = int(devid)
2421 return cls(domid, devid)
2424class Blkback(XenBackendDevice):
2425 """A blkback VBD"""
2427 XENBUS_DEVTYPE = "vbd"
2429 def __init__(self, domid, devid):
2430 XenBackendDevice.__init__(self, domid, devid)
2431 self._phy = None
2432 self._vdi_uuid = None
2433 self._q_state = None
2434 self._q_events = None
2436 class XenstoreValueError(Exception):
2437 KEY: ClassVar[str] = ""
2439 def __init__(self, vbd, _str):
2440 self.vbd = vbd
2441 self.str = _str
2443 @override
2444 def __str__(self) -> str:
2445 return "Backend %s " % self.vbd + \
2446 "has %s = %s" % (self.KEY, self.str)
2448 class PhysicalDeviceError(XenstoreValueError):
2449 KEY = "physical-device"
2451 class PhysicalDevice(object):
2453 def __init__(self, major, minor):
2454 self.major = int(major)
2455 self.minor = int(minor)
2457 @classmethod
2458 def from_xbdev(cls, xbdev):
2460 phy = xbdev.read("physical-device")
2462 try:
2463 major, minor = phy.split(':')
2464 major = int(major, 0x10)
2465 minor = int(minor, 0x10)
2466 except Exception as e:
2467 raise xbdev.PhysicalDeviceError(xbdev, phy)
2469 return cls(major, minor)
2471 def makedev(self):
2472 return os.makedev(self.major, self.minor)
2474 def is_tap(self):
2475 return self.major == Tapdisk.major()
2477 @override
2478 def __str__(self) -> str:
2479 return "%s:%s" % (self.major, self.minor)
2481 @override
2482 def __eq__(self, other) -> bool:
2483 return \
2484 self.major == other.major and \
2485 self.minor == other.minor
2487 def get_physical_device(self):
2488 if not self._phy:
2489 self._phy = self.PhysicalDevice.from_xbdev(self)
2490 return self._phy
2492 class QueueEvents(Attribute):
2493 """Blkback sysfs node to select queue-state event
2494 notifications emitted."""
2496 SYSFS_NODENAME = "queue_events"
2498 QUEUE_RUNNING = (1 << 0)
2499 QUEUE_PAUSE_DONE = (1 << 1)
2500 QUEUE_SHUTDOWN_DONE = (1 << 2)
2501 QUEUE_PAUSE_REQUEST = (1 << 3)
2502 QUEUE_SHUTDOWN_REQUEST = (1 << 4)
2504 def get_mask(self):
2505 return int(self.readline(), 0x10)
2507 def set_mask(self, mask):
2508 self.writeline("0x%x" % mask)
2510 def get_queue_events(self):
2511 if not self._q_events:
2512 self._q_events = self.QueueEvents.from_kobject(self)
2513 return self._q_events
2515 def get_vdi_uuid(self):
2516 if not self._vdi_uuid:
2517 self._vdi_uuid = self.read("sm-data/vdi-uuid")
2518 return self._vdi_uuid
2520 def pause_requested(self):
2521 return self.has_xs_key("pause")
2523 def shutdown_requested(self):
2524 return self.has_xs_key("shutdown-request")
2526 def shutdown_done(self):
2527 return self.has_xs_key("shutdown-done")
2529 def running(self):
2530 return self.has_xs_key('queue-0/kthread-pid')
2532 @classmethod
2533 def find_by_physical_device(cls, phy):
2534 for dev in cls.find():
2535 try:
2536 _phy = dev.get_physical_device()
2537 except cls.PhysicalDeviceError:
2538 continue
2540 if _phy == phy:
2541 yield dev
2543 @classmethod
2544 def find_by_tap_minor(cls, minor):
2545 phy = cls.PhysicalDevice(Tapdisk.major(), minor)
2546 return cls.find_by_physical_device(phy)
2548 @classmethod
2549 def find_by_tap(cls, tapdisk):
2550 return cls.find_by_tap_minor(tapdisk.minor)
2552 def has_tap(self):
2554 if not self.can_tap():
2555 return False
2557 phy = self.get_physical_device()
2558 if phy:
2559 return phy.is_tap()
2561 return False
2563 def is_bare_hvm(self):
2564 """File VDIs for bare HVM. These are directly accessible by Qemu."""
2565 try:
2566 self.get_physical_device()
2568 except self.PhysicalDeviceError as e:
2569 vdi_type = self.read("type")
2571 self.info("HVM VDI: type=%s" % vdi_type)
2573 if e.str is not None or vdi_type != 'file':
2574 raise
2576 return True
2578 return False
2580 def can_tap(self):
2581 return not self.is_bare_hvm()
2584class BlkbackEventHandler(UEventHandler):
2586 LOG_FACILITY = _syslog.LOG_DAEMON
2588 def __init__(self, ident=None, action=None):
2589 if not ident:
2590 ident = self.__class__.__name__
2592 self.ident = ident
2593 self._vbd = None
2594 self._tapdisk = None
2596 UEventHandler.__init__(self)
2598 @override
2599 def run(self) -> None:
2601 self.xs_path = self.getenv('XENBUS_PATH')
2602 openlog(str(self), 0, self.LOG_FACILITY)
2604 UEventHandler.run(self)
2606 @override
2607 def __str__(self) -> str:
2609 try:
2610 path = self.xs_path
2611 except:
2612 path = None
2614 try:
2615 action = self.get_action()
2616 except:
2617 action = None
2619 return "%s[%s](%s)" % (self.ident, action, path)
2621 def _log(self, prio, msg):
2622 syslog(prio, msg)
2623 util.SMlog("%s: " % self + msg)
2625 def info(self, msg):
2626 self._log(_syslog.LOG_INFO, msg)
2628 def warn(self, msg):
2629 self._log(_syslog.LOG_WARNING, "WARNING: " + msg)
2631 def error(self, msg):
2632 self._log(_syslog.LOG_ERR, "ERROR: " + msg)
2634 def get_vbd(self):
2635 if not self._vbd:
2636 self._vbd = Blkback.from_xs_path(self.xs_path)
2637 return self._vbd
2639 def get_tapdisk(self):
2640 if not self._tapdisk:
2641 minor = self.get_vbd().get_physical_device().minor
2642 self._tapdisk = Tapdisk.from_minor(minor)
2643 return self._tapdisk
2644 #
2645 # Events
2646 #
2648 def __add(self):
2649 vbd = self.get_vbd()
2650 # Manage blkback transitions
2651 # self._manage_vbd()
2653 vbd.create_physical_device()
2655 vbd.signal_hotplug()
2657 @retried(backoff=.5, limit=10)
2658 def add(self):
2659 try:
2660 self.__add()
2661 except Attribute.NoSuchAttribute as e:
2662 #
2663 # FIXME: KOBJ_ADD is racing backend.probe, which
2664 # registers device attributes. So poll a little.
2665 #
2666 self.warn("%s, still trying." % e)
2667 raise RetryLoop.TransientFailure(e)
2669 def __change(self):
2670 vbd = self.get_vbd()
2672 # 1. Pause or resume tapdisk (if there is one)
2674 if vbd.has_tap():
2675 pass
2676 #self._pause_update_tap()
2678 # 2. Signal Xapi.VBD.pause/resume completion
2680 self._signal_xapi()
2682 def change(self):
2683 vbd = self.get_vbd()
2685 # NB. Beware of spurious change events between shutdown
2686 # completion and device removal. Also, Xapi.VM.migrate will
2687 # hammer a couple extra shutdown-requests into the source VBD.
2689 while True:
2690 vbd.begin()
2692 if not vbd.exists() or \
2693 vbd.shutdown_done():
2694 break
2696 self.__change()
2698 if vbd.commit():
2699 return
2701 vbd.abort()
2702 self.info("spurious uevent, ignored.")
2704 def remove(self):
2705 vbd = self.get_vbd()
2707 vbd.signal_hotplug(False)
2709 ACTIONS = {'add': add,
2710 'change': change,
2711 'remove': remove}
2712 #
2713 # VDI.pause
2714 #
2716 def _tap_should_pause(self):
2717 """Enumerate all VBDs on our tapdisk. Returns true iff any was
2718 paused"""
2720 tapdisk = self.get_tapdisk()
2721 TapState = Tapdisk.PauseState
2723 PAUSED = 'P'
2724 RUNNING = 'R'
2725 PAUSED_SHUTDOWN = 'P,S'
2726 # NB. Shutdown/paused is special. We know it's not going
2727 # to restart again, so it's a RUNNING. Still better than
2728 # backtracking a removed device during Vbd.unplug completion.
2730 next = TapState.RUNNING
2731 vbds = {}
2733 for vbd in Blkback.find_by_tap(tapdisk):
2734 name = str(vbd)
2736 pausing = vbd.pause_requested()
2737 closing = vbd.shutdown_requested()
2738 running = vbd.running()
2740 if pausing:
2741 if closing and not running:
2742 vbds[name] = PAUSED_SHUTDOWN
2743 else:
2744 vbds[name] = PAUSED
2745 next = TapState.PAUSED
2747 else:
2748 vbds[name] = RUNNING
2750 self.info("tapdev%d (%s): %s -> %s"
2751 % (tapdisk.minor, tapdisk.pause_state(),
2752 vbds, next))
2754 return next == TapState.PAUSED
2756 def _pause_update_tap(self):
2757 vbd = self.get_vbd()
2759 if self._tap_should_pause():
2760 self._pause_tap()
2761 else:
2762 self._resume_tap()
2764 def _pause_tap(self):
2765 tapdisk = self.get_tapdisk()
2767 if not tapdisk.is_paused():
2768 self.info("pausing %s" % tapdisk)
2769 tapdisk.pause()
2771 def _resume_tap(self):
2772 tapdisk = self.get_tapdisk()
2774 # NB. Raw VDI snapshots. Refresh the physical path and
2775 # type while resuming.
2776 vbd = self.get_vbd()
2777 vdi_uuid = vbd.get_vdi_uuid()
2779 if tapdisk.is_paused():
2780 self.info("loading vdi uuid=%s" % vdi_uuid)
2781 vdi = VDI.from_cli(vdi_uuid)
2782 _type = vdi.get_tap_type()
2783 path = vdi.get_phy_path()
2784 self.info("resuming %s on %s:%s" % (tapdisk, _type, path))
2785 tapdisk.unpause(_type, path)
2786 #
2787 # VBD.pause/shutdown
2788 #
2790 def _manage_vbd(self):
2791 vbd = self.get_vbd()
2792 # NB. Hook into VBD state transitions.
2794 events = vbd.get_queue_events()
2796 mask = 0
2797 mask |= events.QUEUE_PAUSE_DONE # pause/unpause
2798 mask |= events.QUEUE_SHUTDOWN_DONE # shutdown
2799 # TODO: mask |= events.QUEUE_SHUTDOWN_REQUEST, for shutdown=force
2800 # TODO: mask |= events.QUEUE_RUNNING, for ionice updates etc
2802 events.set_mask(mask)
2803 self.info("wrote %s = %#02x" % (events.path, mask))
2805 def _signal_xapi(self):
2806 vbd = self.get_vbd()
2808 pausing = vbd.pause_requested()
2809 closing = vbd.shutdown_requested()
2810 running = vbd.running()
2812 handled = 0
2814 if pausing and not running:
2815 if 'pause-done' not in vbd:
2816 vbd.write('pause-done', '')
2817 handled += 1
2819 if not pausing:
2820 if 'pause-done' in vbd:
2821 vbd.rm('pause-done')
2822 handled += 1
2824 if closing and not running:
2825 if 'shutdown-done' not in vbd:
2826 vbd.write('shutdown-done', '')
2827 handled += 1
2829 if handled > 1:
2830 self.warn("handled %d events, " % handled +
2831 "pausing=%s closing=%s running=%s" % \
2832 (pausing, closing, running))
2834if __name__ == '__main__': 2834 ↛ 2836line 2834 didn't jump to line 2836, because the condition on line 2834 was never true
2836 import sys
2837 prog = os.path.basename(sys.argv[0])
2839 #
2840 # Simple CLI interface for manual operation
2841 #
2842 # tap.* level calls go down to local Tapdisk()s (by physical path)
2843 # vdi.* level calls run the plugin calls across host boundaries.
2844 #
2846 def usage(stream):
2847 print("usage: %s tap.{list|major}" % prog, file=stream)
2848 print(" %s tap.{launch|find|get|pause|" % prog + \
2849 "unpause|shutdown|stats} {[<tt>:]<path>} | [minor=]<int> | .. }", file=stream)
2850 print(" %s vbd.uevent" % prog, file=stream)
2852 try:
2853 cmd = sys.argv[1]
2854 except IndexError:
2855 usage(sys.stderr)
2856 sys.exit(1)
2858 try:
2859 _class, method = cmd.split('.')
2860 except:
2861 usage(sys.stderr)
2862 sys.exit(1)
2864 #
2865 # Local Tapdisks
2866 #
2868 if cmd == 'tap.major':
2870 print("%d" % Tapdisk.major())
2872 elif cmd == 'tap.launch':
2874 tapdisk = Tapdisk.launch_from_arg(sys.argv[2])
2875 print("Launched %s" % tapdisk, file=sys.stderr)
2877 elif _class == 'tap':
2879 attrs: Dict[str, Any] = {}
2880 for item in sys.argv[2:]:
2881 try:
2882 key, val = item.split('=')
2883 attrs[key] = val
2884 continue
2885 except ValueError:
2886 pass
2888 try:
2889 attrs['minor'] = int(item)
2890 continue
2891 except ValueError:
2892 pass
2894 try:
2895 arg = Tapdisk.Arg.parse(item)
2896 attrs['_type'] = arg.type
2897 attrs['path'] = arg.path
2898 continue
2899 except Tapdisk.Arg.InvalidArgument:
2900 pass
2902 attrs['path'] = item
2904 if cmd == 'tap.list':
2906 for tapdisk in Tapdisk.list( ** attrs):
2907 blktap = tapdisk.get_blktap()
2908 print(tapdisk, end=' ')
2909 print("%s: task=%s pool=%s" % \
2910 (blktap,
2911 blktap.get_task_pid(),
2912 blktap.get_pool_name()))
2914 elif cmd == 'tap.vbds':
2915 # Find all Blkback instances for a given tapdisk
2917 for tapdisk in Tapdisk.list( ** attrs):
2918 print("%s:" % tapdisk, end=' ')
2919 for vbd in Blkback.find_by_tap(tapdisk):
2920 print(vbd, end=' ')
2921 print()
2923 else:
2925 if not attrs:
2926 usage(sys.stderr)
2927 sys.exit(1)
2929 try:
2930 tapdisk = Tapdisk.get( ** attrs)
2931 except TypeError:
2932 usage(sys.stderr)
2933 sys.exit(1)
2935 if cmd == 'tap.shutdown':
2936 # Shutdown a running tapdisk, or raise
2937 tapdisk.shutdown()
2938 print("Shut down %s" % tapdisk, file=sys.stderr)
2940 elif cmd == 'tap.pause':
2941 # Pause an unpaused tapdisk, or raise
2942 tapdisk.pause()
2943 print("Paused %s" % tapdisk, file=sys.stderr)
2945 elif cmd == 'tap.unpause':
2946 # Unpause a paused tapdisk, or raise
2947 tapdisk.unpause()
2948 print("Unpaused %s" % tapdisk, file=sys.stderr)
2950 elif cmd == 'tap.stats':
2951 # Gather tapdisk status
2952 stats = tapdisk.stats()
2953 print("%s:" % tapdisk)
2954 print(json.dumps(stats, indent=True))
2956 else:
2957 usage(sys.stderr)
2958 sys.exit(1)
2960 elif cmd == 'vbd.uevent':
2962 hnd = BlkbackEventHandler(cmd)
2964 if not sys.stdin.isatty():
2965 try:
2966 hnd.run()
2967 except Exception as e:
2968 hnd.error("Unhandled Exception: %s" % e)
2970 import traceback
2971 _type, value, tb = sys.exc_info()
2972 trace = traceback.format_exception(_type, value, tb)
2973 for entry in trace:
2974 for line in entry.rstrip().split('\n'):
2975 util.SMlog(line)
2976 else:
2977 hnd.run()
2979 elif cmd == 'vbd.list':
2981 for vbd in Blkback.find():
2982 print(vbd, \
2983 "physical-device=%s" % vbd.get_physical_device(), \
2984 "pause=%s" % vbd.pause_requested())
2986 else:
2987 usage(sys.stderr)
2988 sys.exit(1)