Coverage for drivers/blktap2.py : 42%

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: 84 ↛ 94line 84 didn't jump to line 94
85 util.logException("BLKTAP2:%s" % op)
86 msg = str(e)
87 if isinstance(e, util.CommandException): 87 ↛ 88line 87 didn't jump to line 88, because the condition on line 87 was never true
88 msg = "Command %s failed (%s): %s" % \
89 (e.cmd, e.code, e.reason)
90 if override: 90 ↛ 93line 90 didn't jump to line 93, because the condition on line 90 was never false
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()
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 options.get("rdonly") and not self.target.vdi.parent:
1056 self.__o_direct = True
1057 self.__o_direct_reason = "RO_WITH_NO_PARENT"
1058 elif options.get(self.CONF_KEY_O_DIRECT):
1059 self.__o_direct = True
1060 self.__o_direct_reason = "SR_OVERRIDE"
1062 if self.__o_direct is None: 1062 ↛ 1063line 1062 didn't jump to line 1063, because the condition on line 1062 was never true
1063 self.__o_direct = False
1064 self.__o_direct_reason = ""
1066 return self.__o_direct, self.__o_direct_reason
1068 @classmethod
1069 def from_cli(cls, uuid):
1070 import VDI as sm
1072 session = XenAPI.xapi_local()
1073 session.xenapi.login_with_password('root', '', '', 'SM')
1075 target = sm.VDI.from_uuid(session, uuid)
1076 driver_info = target.sr.srcmd.driver_info
1078 session.xenapi.session.logout()
1080 return cls(uuid, target, driver_info)
1082 @staticmethod
1083 def _tap_type(vdi_type):
1084 """Map a VDI type (e.g. 'raw') to a tapdisk driver type (e.g. 'aio')"""
1085 return {
1086 'raw': 'aio',
1087 'vhd': 'vhd',
1088 'iso': 'aio', # for ISO SR
1089 'aio': 'aio', # for LVHD
1090 'file': 'aio',
1091 'phy': 'aio'
1092 }[vdi_type]
1094 def get_tap_type(self):
1095 vdi_type = self.target.get_vdi_type()
1096 return VDI._tap_type(vdi_type)
1098 def get_phy_path(self):
1099 return self.target.get_vdi_path()
1101 class UnexpectedVDIType(Exception):
1103 def __init__(self, vdi_type, target):
1104 self.vdi_type = vdi_type
1105 self.target = target
1107 @override
1108 def __str__(self) -> str:
1109 return \
1110 "Target %s has unexpected VDI type '%s'" % \
1111 (type(self.target), self.vdi_type)
1113 VDI_PLUG_TYPE = {'phy': 'phy', # for NETAPP
1114 'raw': 'phy',
1115 'aio': 'tap', # for LVHD raw nodes
1116 'iso': 'tap', # for ISOSR
1117 'file': 'tap',
1118 'vhd': 'tap'}
1120 def tap_wanted(self):
1121 # 1. Let the target vdi_type decide
1123 vdi_type = self.target.get_vdi_type()
1125 try:
1126 plug_type = self.VDI_PLUG_TYPE[vdi_type]
1127 except KeyError:
1128 raise self.UnexpectedVDIType(vdi_type,
1129 self.target.vdi)
1131 if plug_type == 'tap': 1131 ↛ 1132line 1131 didn't jump to line 1132, because the condition on line 1131 was never true
1132 return True
1133 elif self.target.vdi.sr.handles('udev'): 1133 ↛ 1139line 1133 didn't jump to line 1139, because the condition on line 1133 was never false
1134 return True
1135 # 2. Otherwise, there may be more reasons
1136 #
1137 # .. TBD
1139 return False
1141 class TargetDriver:
1142 """Safe target driver access."""
1143 # NB. *Must* test caps for optional calls. Some targets
1144 # actually implement some slots, but do not enable them. Just
1145 # try/except would risk breaking compatibility.
1147 def __init__(self, vdi, driver_info):
1148 self.vdi = vdi
1149 self._caps = driver_info['capabilities']
1151 def has_cap(self, cap):
1152 """Determine if target has given capability"""
1153 return cap in self._caps
1155 def attach(self, sr_uuid, vdi_uuid):
1156 #assert self.has_cap("VDI_ATTACH")
1157 return self.vdi.attach(sr_uuid, vdi_uuid)
1159 def detach(self, sr_uuid, vdi_uuid):
1160 #assert self.has_cap("VDI_DETACH")
1161 self.vdi.detach(sr_uuid, vdi_uuid)
1163 def activate(self, sr_uuid, vdi_uuid):
1164 if self.has_cap("VDI_ACTIVATE"):
1165 return self.vdi.activate(sr_uuid, vdi_uuid)
1167 def deactivate(self, sr_uuid, vdi_uuid):
1168 if self.has_cap("VDI_DEACTIVATE"):
1169 self.vdi.deactivate(sr_uuid, vdi_uuid)
1170 #def resize(self, sr_uuid, vdi_uuid, size):
1171 # return self.vdi.resize(sr_uuid, vdi_uuid, size)
1173 def get_vdi_type(self):
1174 _type = self.vdi.vdi_type
1175 if not _type:
1176 _type = self.vdi.sr.sr_vditype
1177 if not _type:
1178 raise VDI.UnexpectedVDIType(_type, self.vdi)
1179 return _type
1181 def get_vdi_path(self):
1182 return self.vdi.path
1184 class Link(object):
1185 """Relink a node under a common name"""
1186 # NB. We have to provide the device node path during
1187 # VDI.attach, but currently do not allocate the tapdisk minor
1188 # before VDI.activate. Therefore those link steps where we
1189 # relink existing devices under deterministic path names.
1191 BASEDIR: ClassVar[str] = ""
1193 def _mklink(self, target) -> None:
1194 pass
1196 @abstractmethod
1197 def _equals(self, target) -> bool:
1198 pass
1200 def __init__(self, path):
1201 self._path = path
1203 @classmethod
1204 def from_name(cls, name):
1205 path = "%s/%s" % (cls.BASEDIR, name)
1206 return cls(path)
1208 @classmethod
1209 def from_uuid(cls, sr_uuid, vdi_uuid):
1210 name = "%s/%s" % (sr_uuid, vdi_uuid)
1211 return cls.from_name(name)
1213 def path(self):
1214 return self._path
1216 def stat(self):
1217 return os.stat(self.path())
1219 def mklink(self, target) -> None:
1221 path = self.path()
1222 util.SMlog("%s -> %s" % (self, target))
1224 mkdirs(os.path.dirname(path))
1225 try:
1226 self._mklink(target)
1227 except OSError as e:
1228 # We do unlink during teardown, but have to stay
1229 # idempotent. However, a *wrong* target should never
1230 # be seen.
1231 if e.errno != errno.EEXIST:
1232 raise
1233 assert self._equals(target), "'%s' not equal to '%s'" % (path, target)
1235 def unlink(self):
1236 try:
1237 os.unlink(self.path())
1238 except OSError as e:
1239 if e.errno != errno.ENOENT:
1240 raise
1242 @override
1243 def __str__(self) -> str:
1244 path = self.path()
1245 return "%s(%s)" % (self.__class__.__name__, path)
1247 class SymLink(Link):
1248 """Symlink some file to a common name"""
1250 def readlink(self):
1251 return os.readlink(self.path())
1253 def symlink(self):
1254 return self.path()
1256 @override
1257 def _mklink(self, target) -> None:
1258 os.symlink(target, self.path())
1260 @override
1261 def _equals(self, target) -> bool:
1262 return self.readlink() == target
1264 class DeviceNode(Link):
1265 """Relink a block device node to a common name"""
1267 @classmethod
1268 def _real_stat(cls, target):
1269 """stat() not on @target, but its realpath()"""
1270 _target = os.path.realpath(target)
1271 return os.stat(_target)
1273 @classmethod
1274 def is_block(cls, target):
1275 """Whether @target refers to a block device."""
1276 return S_ISBLK(cls._real_stat(target).st_mode)
1278 @override
1279 def _mklink(self, target) -> None:
1281 st = self._real_stat(target)
1282 if not S_ISBLK(st.st_mode):
1283 raise self.NotABlockDevice(target, st)
1285 # set group read for disk group as well as root
1286 os.mknod(self.path(), st.st_mode | stat.S_IRGRP, st.st_rdev)
1287 os.chown(self.path(), st.st_uid, grp.getgrnam("disk").gr_gid)
1289 @override
1290 def _equals(self, target) -> bool:
1291 target_rdev = self._real_stat(target).st_rdev
1292 return self.stat().st_rdev == target_rdev
1294 def rdev(self):
1295 st = self.stat()
1296 assert S_ISBLK(st.st_mode)
1297 return os.major(st.st_rdev), os.minor(st.st_rdev)
1299 class NotABlockDevice(Exception):
1301 def __init__(self, path, st):
1302 self.path = path
1303 self.st = st
1305 @override
1306 def __str__(self) -> str:
1307 return "%s is not a block device: %s" % (self.path, self.st)
1309 class Hybrid(Link):
1311 def __init__(self, path):
1312 VDI.Link.__init__(self, path)
1313 self._devnode = VDI.DeviceNode(path)
1314 self._symlink = VDI.SymLink(path)
1316 def rdev(self):
1317 st = self.stat()
1318 if S_ISBLK(st.st_mode):
1319 return self._devnode.rdev()
1320 raise self._devnode.NotABlockDevice(self.path(), st)
1322 @override
1323 def mklink(self, target) -> None:
1324 if self._devnode.is_block(target):
1325 self._obj = self._devnode
1326 else:
1327 self._obj = self._symlink
1328 self._obj.mklink(target)
1330 @override
1331 def _equals(self, target) -> bool:
1332 return self._obj._equals(target)
1334 class PhyLink(SymLink):
1335 BASEDIR = "/dev/sm/phy"
1336 # NB. Cannot use DeviceNodes, e.g. FileVDIs aren't bdevs.
1338 class NBDLink(SymLink):
1340 BASEDIR = "/run/blktap-control/nbd"
1342 class BackendLink(Hybrid):
1343 BASEDIR = "/dev/sm/backend"
1344 # NB. Could be SymLinks as well, but saving major,minor pairs in
1345 # Links enables neat state capturing when managing Tapdisks. Note
1346 # that we essentially have a tap-ctl list replacement here. For
1347 # now make it a 'Hybrid'. Likely to collapse into a DeviceNode as
1348 # soon as ISOs are tapdisks.
1350 @staticmethod
1351 def _tap_activate(phy_path, vdi_type, sr_uuid, options, pool_size=None):
1353 tapdisk = Tapdisk.find_by_path(phy_path)
1354 if not tapdisk: 1354 ↛ 1355line 1354 didn't jump to line 1355, because the condition on line 1354 was never true
1355 blktap = Blktap.allocate()
1356 blktap.set_pool_name(sr_uuid)
1357 if pool_size:
1358 blktap.set_pool_size(pool_size)
1360 try:
1361 tapdisk = \
1362 Tapdisk.launch_on_tap(blktap,
1363 phy_path,
1364 VDI._tap_type(vdi_type),
1365 options)
1366 except:
1367 blktap.free()
1368 raise
1369 util.SMlog("tap.activate: Launched %s" % tapdisk)
1371 else:
1372 util.SMlog("tap.activate: Found %s" % tapdisk)
1374 return tapdisk.get_devpath(), tapdisk
1376 @staticmethod
1377 def _tap_deactivate(minor):
1379 try:
1380 tapdisk = Tapdisk.from_minor(minor)
1381 except TapdiskNotRunning as e:
1382 util.SMlog("tap.deactivate: Warning, %s" % e)
1383 # NB. Should not be here unless the agent refcount
1384 # broke. Also, a clean shutdown should not have leaked
1385 # the recorded minor.
1386 else:
1387 tapdisk.shutdown()
1388 util.SMlog("tap.deactivate: Shut down %s" % tapdisk)
1390 @classmethod
1391 def tap_pause(cls, session, sr_uuid, vdi_uuid, failfast=False):
1392 """
1393 Pauses the tapdisk.
1395 session: a XAPI session
1396 sr_uuid: the UUID of the SR on which VDI lives
1397 vdi_uuid: the UUID of the VDI to pause
1398 failfast: controls whether the VDI lock should be acquired in a
1399 non-blocking manner
1400 """
1401 util.SMlog("Pause request for %s" % vdi_uuid)
1402 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1403 session.xenapi.VDI.add_to_sm_config(vdi_ref, 'paused', 'true')
1404 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1405 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 1405 ↛ 1406line 1405 didn't jump to line 1406, because the loop on line 1405 never started
1406 host_ref = key[len('host_'):]
1407 util.SMlog("Calling tap-pause on host %s" % host_ref)
1408 if not cls.call_pluginhandler(session, host_ref,
1409 sr_uuid, vdi_uuid, "pause", failfast=failfast):
1410 # Failed to pause node
1411 session.xenapi.VDI.remove_from_sm_config(vdi_ref, 'paused')
1412 return False
1413 return True
1415 @classmethod
1416 def tap_unpause(cls, session, sr_uuid, vdi_uuid, secondary=None,
1417 activate_parents=False):
1418 util.SMlog("Unpause request for %s secondary=%s" % (vdi_uuid, secondary))
1419 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1420 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1421 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 1421 ↛ 1422line 1421 didn't jump to line 1422, because the loop on line 1421 never started
1422 host_ref = key[len('host_'):]
1423 util.SMlog("Calling tap-unpause on host %s" % host_ref)
1424 if not cls.call_pluginhandler(session, host_ref,
1425 sr_uuid, vdi_uuid, "unpause", secondary, activate_parents):
1426 # Failed to unpause node
1427 return False
1428 session.xenapi.VDI.remove_from_sm_config(vdi_ref, 'paused')
1429 return True
1431 @classmethod
1432 def tap_refresh(cls, session, sr_uuid, vdi_uuid, activate_parents=False):
1433 util.SMlog("Refresh request for %s" % vdi_uuid)
1434 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1435 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1436 for key in [x for x in sm_config.keys() if x.startswith('host_')]:
1437 host_ref = key[len('host_'):]
1438 util.SMlog("Calling tap-refresh on host %s" % host_ref)
1439 if not cls.call_pluginhandler(session, host_ref,
1440 sr_uuid, vdi_uuid, "refresh", None,
1441 activate_parents=activate_parents):
1442 # Failed to refresh node
1443 return False
1444 return True
1446 @classmethod
1447 def tap_status(cls, session, vdi_uuid):
1448 """Return True if disk is attached, false if it isn't"""
1449 util.SMlog("Disk status request for %s" % vdi_uuid)
1450 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1451 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1452 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 1452 ↛ 1453line 1452 didn't jump to line 1453, because the loop on line 1452 never started
1453 return True
1454 return False
1456 @classmethod
1457 def call_pluginhandler(cls, session, host_ref, sr_uuid, vdi_uuid, action,
1458 secondary=None, activate_parents=False, failfast=False):
1459 """Optionally, activate the parent LV before unpausing"""
1460 try:
1461 args = {"sr_uuid": sr_uuid, "vdi_uuid": vdi_uuid,
1462 "failfast": str(failfast)}
1463 if secondary:
1464 args["secondary"] = secondary
1465 if activate_parents:
1466 args["activate_parents"] = "true"
1467 ret = session.xenapi.host.call_plugin(
1468 host_ref, PLUGIN_TAP_PAUSE, action,
1469 args)
1470 return ret == "True"
1471 except Exception as e:
1472 util.logException("BLKTAP2:call_pluginhandler %s" % e)
1473 return False
1475 def _add_tag(self, vdi_uuid, writable):
1476 util.SMlog("Adding tag to: %s" % vdi_uuid)
1477 attach_mode = "RO"
1478 if writable:
1479 attach_mode = "RW"
1480 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1481 host_ref = self._session.xenapi.host.get_by_uuid(util.get_this_host())
1482 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1483 attached_as = util.attached_as(sm_config)
1484 if NO_MULTIPLE_ATTACH and (attached_as == "RW" or \ 1484 ↛ 1486line 1484 didn't jump to line 1486, because the condition on line 1484 was never true
1485 (attached_as == "RO" and attach_mode == "RW")):
1486 util.SMlog("need to reset VDI %s" % vdi_uuid)
1487 if not resetvdis.reset_vdi(self._session, vdi_uuid, force=False,
1488 term_output=False, writable=writable):
1489 raise util.SMException("VDI %s not detached cleanly" % vdi_uuid)
1490 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1491 if 'relinking' in sm_config:
1492 util.SMlog("Relinking key found, back-off and retry" % sm_config)
1493 return False
1494 if 'paused' in sm_config:
1495 util.SMlog("Paused or host_ref key found [%s]" % sm_config)
1496 return False
1497 try:
1498 self._session.xenapi.VDI.add_to_sm_config(
1499 vdi_ref, 'activating', 'True')
1500 except XenAPI.Failure as e:
1501 if e.details[0] == 'MAP_DUPLICATE_KEY' and not writable:
1502 # Someone else is activating - a retry might succeed
1503 return False
1504 raise
1505 host_key = "host_%s" % host_ref
1506 assert host_key not in sm_config
1507 self._session.xenapi.VDI.add_to_sm_config(vdi_ref, host_key,
1508 attach_mode)
1509 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1510 if 'paused' in sm_config or 'relinking' in sm_config:
1511 util.SMlog("Found %s key, aborting" % (
1512 'paused' if 'paused' in sm_config else 'relinking'))
1513 self._session.xenapi.VDI.remove_from_sm_config(vdi_ref, host_key)
1514 self._session.xenapi.VDI.remove_from_sm_config(
1515 vdi_ref, 'activating')
1516 return False
1517 util.SMlog("Activate lock succeeded")
1518 return True
1520 def _check_tag(self, vdi_uuid):
1521 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1522 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1523 if 'paused' in sm_config:
1524 util.SMlog("Paused key found [%s]" % sm_config)
1525 return False
1526 return True
1528 def _remove_tag(self, vdi_uuid):
1529 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1530 host_ref = self._session.xenapi.host.get_by_uuid(util.get_this_host())
1531 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1532 host_key = "host_%s" % host_ref
1533 if host_key in sm_config:
1534 self._session.xenapi.VDI.remove_from_sm_config(vdi_ref, host_key)
1535 util.SMlog("Removed host key %s for %s" % (host_key, vdi_uuid))
1536 else:
1537 util.SMlog("_remove_tag: host key %s not found, ignore" % host_key)
1539 def _get_pool_config(self, pool_name):
1540 pool_info = dict()
1541 vdi_ref = self.target.vdi.sr.srcmd.params.get('vdi_ref')
1542 if not vdi_ref: 1542 ↛ 1545line 1542 didn't jump to line 1545, because the condition on line 1542 was never true
1543 # attach_from_config context: HA disks don't need to be in any
1544 # special pool
1545 return pool_info
1547 sr_ref = self.target.vdi.sr.srcmd.params.get('sr_ref')
1548 sr_config = self._session.xenapi.SR.get_other_config(sr_ref)
1549 vdi_config = self._session.xenapi.VDI.get_other_config(vdi_ref)
1550 pool_size_str = sr_config.get(POOL_SIZE_KEY)
1551 pool_name_override = vdi_config.get(POOL_NAME_KEY)
1552 if pool_name_override: 1552 ↛ 1557line 1552 didn't jump to line 1557, because the condition on line 1552 was never false
1553 pool_name = pool_name_override
1554 pool_size_override = vdi_config.get(POOL_SIZE_KEY)
1555 if pool_size_override: 1555 ↛ 1557line 1555 didn't jump to line 1557, because the condition on line 1555 was never false
1556 pool_size_str = pool_size_override
1557 pool_size = 0
1558 if pool_size_str: 1558 ↛ 1568line 1558 didn't jump to line 1568, because the condition on line 1558 was never false
1559 try:
1560 pool_size = int(pool_size_str)
1561 if pool_size < 1 or pool_size > MAX_FULL_RINGS: 1561 ↛ 1562line 1561 didn't jump to line 1562, because the condition on line 1561 was never true
1562 raise ValueError("outside of range")
1563 pool_size = NUM_PAGES_PER_RING * pool_size
1564 except ValueError:
1565 util.SMlog("Error: invalid mem-pool-size %s" % pool_size_str)
1566 pool_size = 0
1568 pool_info["mem-pool"] = pool_name
1569 if pool_size: 1569 ↛ 1572line 1569 didn't jump to line 1572, because the condition on line 1569 was never false
1570 pool_info["mem-pool-size"] = str(pool_size)
1572 return pool_info
1574 def linkNBD(self, sr_uuid, vdi_uuid):
1575 if self.tap:
1576 nbd_path = '/run/blktap-control/nbd%d.%d' % (int(self.tap.pid),
1577 int(self.tap.minor))
1578 VDI.NBDLink.from_uuid(sr_uuid, vdi_uuid).mklink(nbd_path)
1580 def attach(self, sr_uuid, vdi_uuid, writable, activate=False, caching_params={}):
1581 """Return/dev/sm/backend symlink path"""
1582 self.xenstore_data.update(self._get_pool_config(sr_uuid))
1583 if not self.target.has_cap("ATOMIC_PAUSE") or activate:
1584 util.SMlog("Attach & activate")
1585 self._attach(sr_uuid, vdi_uuid)
1586 dev_path = self._activate(sr_uuid, vdi_uuid,
1587 {"rdonly": not writable})
1588 self.BackendLink.from_uuid(sr_uuid, vdi_uuid).mklink(dev_path)
1589 self.linkNBD(sr_uuid, vdi_uuid)
1591 # Return backend/ link
1592 back_path = self.BackendLink.from_uuid(sr_uuid, vdi_uuid).path()
1593 if self.tap_wanted():
1594 # Only have NBD if we also have a tap
1595 nbd_path = "nbd:unix:{}:exportname={}".format(
1596 VDI.NBDLink.from_uuid(sr_uuid, vdi_uuid).path(),
1597 vdi_uuid)
1598 else:
1599 nbd_path = ""
1601 options = {"rdonly": not writable}
1602 options.update(caching_params)
1603 o_direct, o_direct_reason = self.get_o_direct_capability(options)
1604 struct = {'params': back_path,
1605 'params_nbd': nbd_path,
1606 'o_direct': o_direct,
1607 'o_direct_reason': o_direct_reason,
1608 'xenstore_data': self.xenstore_data}
1609 util.SMlog('result: %s' % struct)
1611 try:
1612 f = open("%s.attach_info" % back_path, 'a')
1613 f.write(xmlrpc.client.dumps((struct, ), "", True))
1614 f.close()
1615 except:
1616 pass
1618 return xmlrpc.client.dumps((struct, ), "", True)
1620 def activate(self, sr_uuid, vdi_uuid, writable, caching_params):
1621 util.SMlog("blktap2.activate")
1622 options = {"rdonly": not writable}
1623 options.update(caching_params)
1625 sr_ref = self.target.vdi.sr.srcmd.params.get('sr_ref')
1626 sr_other_config = self._session.xenapi.SR.get_other_config(sr_ref)
1627 for i in range(self.ATTACH_DETACH_RETRY_SECS): 1627 ↛ 1634line 1627 didn't jump to line 1634, because the loop on line 1627 didn't complete
1628 try:
1629 if self._activate_locked(sr_uuid, vdi_uuid, options):
1630 return
1631 except util.SRBusyException:
1632 util.SMlog("SR locked, retrying")
1633 time.sleep(1)
1634 raise util.SMException("VDI %s locked" % vdi_uuid)
1636 @locking("VDIUnavailable")
1637 def _activate_locked(self, sr_uuid, vdi_uuid, options):
1638 """Wraps target.activate and adds a tapdisk"""
1640 #util.SMlog("VDI.activate %s" % vdi_uuid)
1641 refresh = False
1642 if self.tap_wanted(): 1642 ↛ 1647line 1642 didn't jump to line 1647, because the condition on line 1642 was never false
1643 if not self._add_tag(vdi_uuid, not options["rdonly"]):
1644 return False
1645 refresh = True
1647 try:
1648 if refresh: 1648 ↛ 1659line 1648 didn't jump to line 1659, because the condition on line 1648 was never false
1649 # it is possible that while the VDI was paused some of its
1650 # attributes have changed (e.g. its size if it was inflated; or its
1651 # path if it was leaf-coalesced onto a raw LV), so refresh the
1652 # object completely
1653 params = self.target.vdi.sr.srcmd.params
1654 target = sm.VDI.from_uuid(self.target.vdi.session, vdi_uuid)
1655 target.sr.srcmd.params = params
1656 driver_info = target.sr.srcmd.driver_info
1657 self.target = self.TargetDriver(target, driver_info)
1659 util.fistpoint.activate_custom_fn( 1659 ↛ exitline 1659 didn't jump to the function exit
1660 "blktap_activate_inject_failure",
1661 lambda: util.inject_failure())
1663 # Attach the physical node
1664 if self.target.has_cap("ATOMIC_PAUSE"): 1664 ↛ 1667line 1664 didn't jump to line 1667, because the condition on line 1664 was never false
1665 self._attach(sr_uuid, vdi_uuid)
1667 vdi_type = self.target.get_vdi_type()
1669 # Take lvchange-p Lock before running
1670 # tap-ctl open
1671 # Needed to avoid race with lvchange -p which is
1672 # now taking the same lock
1673 # This is a fix for CA-155766
1674 if hasattr(self.target.vdi.sr, 'DRIVER_TYPE') and \ 1674 ↛ 1677line 1674 didn't jump to line 1677, because the condition on line 1674 was never true
1675 self.target.vdi.sr.DRIVER_TYPE == 'lvhd' and \
1676 vdi_type == vhdutil.VDI_TYPE_VHD:
1677 lock = Lock("lvchange-p", lvhdutil.NS_PREFIX_LVM + sr_uuid)
1678 lock.acquire()
1680 # When we attach a static VDI for HA, we cannot communicate with
1681 # xapi, because has not started yet. These VDIs are raw.
1682 if vdi_type != vhdutil.VDI_TYPE_RAW: 1682 ↛ 1693line 1682 didn't jump to line 1693, because the condition on line 1682 was never false
1683 session = self.target.vdi.session
1684 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1685 # pylint: disable=used-before-assignment
1686 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1687 if 'key_hash' in sm_config: 1687 ↛ 1688line 1687 didn't jump to line 1688, because the condition on line 1687 was never true
1688 key_hash = sm_config['key_hash']
1689 options['key_hash'] = key_hash
1690 options['vdi_uuid'] = vdi_uuid
1691 util.SMlog('Using key with hash {} for VDI {}'.format(key_hash, vdi_uuid))
1692 # Activate the physical node
1693 dev_path = self._activate(sr_uuid, vdi_uuid, options)
1695 if hasattr(self.target.vdi.sr, 'DRIVER_TYPE') and \ 1695 ↛ 1698line 1695 didn't jump to line 1698, because the condition on line 1695 was never true
1696 self.target.vdi.sr.DRIVER_TYPE == 'lvhd' and \
1697 self.target.get_vdi_type() == vhdutil.VDI_TYPE_VHD:
1698 lock.release()
1699 except:
1700 util.SMlog("Exception in activate/attach")
1701 if self.tap_wanted():
1702 util.fistpoint.activate_custom_fn(
1703 "blktap_activate_error_handling",
1704 lambda: time.sleep(30))
1705 while True:
1706 try:
1707 self._remove_tag(vdi_uuid)
1708 break
1709 except xmlrpc.client.ProtocolError as e:
1710 # If there's a connection error, keep trying forever.
1711 if e.errcode == http.HTTPStatus.INTERNAL_SERVER_ERROR.value:
1712 continue
1713 else:
1714 util.SMlog('failed to remove tag: %s' % e)
1715 break
1716 except Exception as e:
1717 util.SMlog('failed to remove tag: %s' % e)
1718 break
1719 raise
1720 finally:
1721 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1722 self._session.xenapi.VDI.remove_from_sm_config(
1723 vdi_ref, 'activating')
1724 util.SMlog("Removed activating flag from %s" % vdi_uuid) 1724 ↛ exitline 1724 didn't except from function '_activate_locked', because the raise on line 1719 wasn't executed
1726 # Link result to backend/
1727 self.BackendLink.from_uuid(sr_uuid, vdi_uuid).mklink(dev_path)
1728 self.linkNBD(sr_uuid, vdi_uuid)
1729 return True
1731 def _activate(self, sr_uuid, vdi_uuid, options):
1732 vdi_options = self.target.activate(sr_uuid, vdi_uuid)
1734 dev_path = self.setup_cache(sr_uuid, vdi_uuid, options)
1735 if not dev_path: 1735 ↛ 1749line 1735 didn't jump to line 1749, because the condition on line 1735 was never false
1736 phy_path = self.PhyLink.from_uuid(sr_uuid, vdi_uuid).readlink()
1737 # Maybe launch a tapdisk on the physical link
1738 if self.tap_wanted(): 1738 ↛ 1747line 1738 didn't jump to line 1747, because the condition on line 1738 was never false
1739 vdi_type = self.target.get_vdi_type()
1740 options["o_direct"] = self.get_o_direct_capability(options)[0]
1741 if vdi_options: 1741 ↛ 1743line 1741 didn't jump to line 1743, because the condition on line 1741 was never false
1742 options.update(vdi_options)
1743 dev_path, self.tap = self._tap_activate(phy_path, vdi_type,
1744 sr_uuid, options,
1745 self._get_pool_config(sr_uuid).get("mem-pool-size"))
1746 else:
1747 dev_path = phy_path # Just reuse phy
1749 return dev_path
1751 def _attach(self, sr_uuid, vdi_uuid):
1752 attach_info = xmlrpc.client.loads(self.target.attach(sr_uuid, vdi_uuid))[0][0]
1753 params = attach_info['params']
1754 xenstore_data = attach_info['xenstore_data']
1755 phy_path = util.to_plain_string(params)
1756 self.xenstore_data.update(xenstore_data)
1757 # Save it to phy/
1758 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).mklink(phy_path)
1760 def deactivate(self, sr_uuid, vdi_uuid, caching_params):
1761 util.SMlog("blktap2.deactivate")
1762 for i in range(self.ATTACH_DETACH_RETRY_SECS):
1763 try:
1764 if self._deactivate_locked(sr_uuid, vdi_uuid, caching_params):
1765 return
1766 except util.SRBusyException as e:
1767 util.SMlog("SR locked, retrying")
1768 time.sleep(1)
1769 raise util.SMException("VDI %s locked" % vdi_uuid)
1771 @locking("VDIUnavailable")
1772 def _deactivate_locked(self, sr_uuid, vdi_uuid, caching_params):
1773 """Wraps target.deactivate and removes a tapdisk"""
1775 #util.SMlog("VDI.deactivate %s" % vdi_uuid)
1776 if self.tap_wanted() and not self._check_tag(vdi_uuid):
1777 return False
1779 self._deactivate(sr_uuid, vdi_uuid, caching_params)
1780 if self.target.has_cap("ATOMIC_PAUSE"):
1781 self._detach(sr_uuid, vdi_uuid)
1782 if self.tap_wanted():
1783 self._remove_tag(vdi_uuid)
1785 return True
1787 def _resetPhylink(self, sr_uuid, vdi_uuid, path):
1788 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).mklink(path)
1790 def detach(self, sr_uuid, vdi_uuid, deactivate=False, caching_params={}):
1791 if not self.target.has_cap("ATOMIC_PAUSE") or deactivate:
1792 util.SMlog("Deactivate & detach")
1793 self._deactivate(sr_uuid, vdi_uuid, caching_params)
1794 self._detach(sr_uuid, vdi_uuid)
1795 else:
1796 pass # nothing to do
1798 def _deactivate(self, sr_uuid, vdi_uuid, caching_params):
1799 import VDI as sm
1801 # Shutdown tapdisk
1802 back_link = self.BackendLink.from_uuid(sr_uuid, vdi_uuid)
1804 if not util.pathexists(back_link.path()):
1805 util.SMlog("Backend path %s does not exist" % back_link.path())
1806 return
1808 try:
1809 attach_info_path = "%s.attach_info" % (back_link.path())
1810 os.unlink(attach_info_path)
1811 except:
1812 util.SMlog("unlink of attach_info failed")
1814 try:
1815 major, minor = back_link.rdev()
1816 except self.DeviceNode.NotABlockDevice:
1817 pass
1818 else:
1819 if major == Tapdisk.major():
1820 self._tap_deactivate(minor)
1821 self.remove_cache(sr_uuid, vdi_uuid, caching_params)
1823 # Remove the backend link
1824 back_link.unlink()
1825 VDI.NBDLink.from_uuid(sr_uuid, vdi_uuid).unlink()
1827 # Deactivate & detach the physical node
1828 if self.tap_wanted() and self.target.vdi.session is not None:
1829 # it is possible that while the VDI was paused some of its
1830 # attributes have changed (e.g. its size if it was inflated; or its
1831 # path if it was leaf-coalesced onto a raw LV), so refresh the
1832 # object completely
1833 target = sm.VDI.from_uuid(self.target.vdi.session, vdi_uuid)
1834 driver_info = target.sr.srcmd.driver_info
1835 self.target = self.TargetDriver(target, driver_info)
1837 self.target.deactivate(sr_uuid, vdi_uuid)
1839 def _detach(self, sr_uuid, vdi_uuid):
1840 self.target.detach(sr_uuid, vdi_uuid)
1842 # Remove phy/
1843 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).unlink()
1845 def _updateCacheRecord(self, session, vdi_uuid, on_boot, caching):
1846 # Remove existing VDI.sm_config fields
1847 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1848 for key in ["on_boot", "caching"]:
1849 session.xenapi.VDI.remove_from_sm_config(vdi_ref, key)
1850 if not on_boot is None:
1851 session.xenapi.VDI.add_to_sm_config(vdi_ref, 'on_boot', on_boot)
1852 if not caching is None:
1853 session.xenapi.VDI.add_to_sm_config(vdi_ref, 'caching', caching)
1855 def setup_cache(self, sr_uuid, vdi_uuid, params):
1856 if params.get(self.CONF_KEY_ALLOW_CACHING) != "true": 1856 ↛ 1859line 1856 didn't jump to line 1859, because the condition on line 1856 was never false
1857 return
1859 util.SMlog("Requested local caching")
1860 if not self.target.has_cap("SR_CACHING"):
1861 util.SMlog("Error: local caching not supported by this SR")
1862 return
1864 scratch_mode = False
1865 if params.get(self.CONF_KEY_MODE_ON_BOOT) == "reset":
1866 scratch_mode = True
1867 util.SMlog("Requested scratch mode")
1868 if not self.target.has_cap("VDI_RESET_ON_BOOT/2"):
1869 util.SMlog("Error: scratch mode not supported by this SR")
1870 return
1872 dev_path = None
1873 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR)
1874 if not local_sr_uuid:
1875 util.SMlog("ERROR: Local cache SR not specified, not enabling")
1876 return
1877 dev_path = self._setup_cache(self._session, sr_uuid, vdi_uuid,
1878 local_sr_uuid, scratch_mode, params)
1880 if dev_path:
1881 self._updateCacheRecord(self._session, self.target.vdi.uuid,
1882 params.get(self.CONF_KEY_MODE_ON_BOOT),
1883 params.get(self.CONF_KEY_ALLOW_CACHING))
1885 return dev_path
1887 def alert_no_cache(self, session, vdi_uuid, cache_sr_uuid, err):
1888 vm_uuid = None
1889 vm_label = ""
1890 try:
1891 cache_sr_ref = session.xenapi.SR.get_by_uuid(cache_sr_uuid)
1892 cache_sr_rec = session.xenapi.SR.get_record(cache_sr_ref)
1893 cache_sr_label = cache_sr_rec.get("name_label")
1895 host_ref = session.xenapi.host.get_by_uuid(util.get_this_host())
1896 host_rec = session.xenapi.host.get_record(host_ref)
1897 host_label = host_rec.get("name_label")
1899 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1900 vbds = session.xenapi.VBD.get_all_records_where( \
1901 "field \"VDI\" = \"%s\"" % vdi_ref)
1902 for vbd_rec in vbds.values():
1903 vm_ref = vbd_rec.get("VM")
1904 vm_rec = session.xenapi.VM.get_record(vm_ref)
1905 vm_uuid = vm_rec.get("uuid")
1906 vm_label = vm_rec.get("name_label")
1907 except:
1908 util.logException("alert_no_cache")
1910 alert_obj = "SR"
1911 alert_uuid = str(cache_sr_uuid)
1912 alert_str = "No space left in Local Cache SR %s" % cache_sr_uuid
1913 if vm_uuid:
1914 alert_obj = "VM"
1915 alert_uuid = vm_uuid
1916 reason = ""
1917 if err == errno.ENOSPC:
1918 reason = "because there is no space left"
1919 alert_str = "The VM \"%s\" is not using IntelliCache %s on the Local Cache SR (\"%s\") on host \"%s\"" % \
1920 (vm_label, reason, cache_sr_label, host_label)
1922 util.SMlog("Creating alert: (%s, %s, \"%s\")" % \
1923 (alert_obj, alert_uuid, alert_str))
1924 session.xenapi.message.create("No space left in local cache", "3",
1925 alert_obj, alert_uuid, alert_str)
1927 def _setup_cache(self, session, sr_uuid, vdi_uuid, local_sr_uuid,
1928 scratch_mode, options):
1929 import SR
1930 import EXTSR
1931 import NFSSR
1932 from lock import Lock
1933 from FileSR import FileVDI
1935 parent_uuid = vhdutil.getParent(self.target.vdi.path,
1936 FileVDI.extractUuid)
1937 if not parent_uuid:
1938 util.SMlog("ERROR: VDI %s has no parent, not enabling" % \
1939 self.target.vdi.uuid)
1940 return
1942 util.SMlog("Setting up cache")
1943 parent_uuid = parent_uuid.strip()
1944 shared_target = NFSSR.NFSFileVDI(self.target.vdi.sr, parent_uuid)
1946 if shared_target.parent:
1947 util.SMlog("ERROR: Parent VDI %s has parent, not enabling" %
1948 shared_target.uuid)
1949 return
1951 SR.registerSR(EXTSR.EXTSR)
1952 local_sr = SR.SR.from_uuid(session, local_sr_uuid)
1954 lock = Lock(self.LOCK_CACHE_SETUP, parent_uuid)
1955 lock.acquire()
1957 # read cache
1958 read_cache_path = "%s/%s.vhdcache" % (local_sr.path, shared_target.uuid)
1959 if util.pathexists(read_cache_path):
1960 util.SMlog("Read cache node (%s) already exists, not creating" % \
1961 read_cache_path)
1962 else:
1963 try:
1964 vhdutil.snapshot(read_cache_path, shared_target.path, False)
1965 except util.CommandException as e:
1966 util.SMlog("Error creating parent cache: %s" % e)
1967 self.alert_no_cache(session, vdi_uuid, local_sr_uuid, e.code)
1968 return None
1970 # local write node
1971 leaf_size = vhdutil.getSizeVirt(self.target.vdi.path)
1972 local_leaf_path = "%s/%s.vhdcache" % \
1973 (local_sr.path, self.target.vdi.uuid)
1974 if util.pathexists(local_leaf_path):
1975 util.SMlog("Local leaf node (%s) already exists, deleting" % \
1976 local_leaf_path)
1977 os.unlink(local_leaf_path)
1978 try:
1979 vhdutil.snapshot(local_leaf_path, read_cache_path, False,
1980 msize=leaf_size // 1024 // 1024, checkEmpty=False)
1981 except util.CommandException as e:
1982 util.SMlog("Error creating leaf cache: %s" % e)
1983 self.alert_no_cache(session, vdi_uuid, local_sr_uuid, e.code)
1984 return None
1986 local_leaf_size = vhdutil.getSizeVirt(local_leaf_path)
1987 if leaf_size > local_leaf_size:
1988 util.SMlog("Leaf size %d > local leaf cache size %d, resizing" %
1989 (leaf_size, local_leaf_size))
1990 vhdutil.setSizeVirtFast(local_leaf_path, leaf_size)
1992 vdi_type = self.target.get_vdi_type()
1994 prt_tapdisk = Tapdisk.find_by_path(read_cache_path)
1995 if not prt_tapdisk:
1996 parent_options = copy.deepcopy(options)
1997 parent_options["rdonly"] = False
1998 parent_options["lcache"] = True
2000 blktap = Blktap.allocate()
2001 try:
2002 blktap.set_pool_name("lcache-parent-pool-%s" % blktap.minor)
2003 # no need to change pool_size since each parent tapdisk is in
2004 # its own pool
2005 prt_tapdisk = \
2006 Tapdisk.launch_on_tap(blktap, read_cache_path,
2007 'vhd', parent_options)
2008 except:
2009 blktap.free()
2010 raise
2012 secondary = "%s:%s" % (self.target.get_vdi_type(),
2013 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).readlink())
2015 util.SMlog("Parent tapdisk: %s" % prt_tapdisk)
2016 leaf_tapdisk = Tapdisk.find_by_path(local_leaf_path)
2017 if not leaf_tapdisk:
2018 blktap = Blktap.allocate()
2019 child_options = copy.deepcopy(options)
2020 child_options["rdonly"] = False
2021 child_options["lcache"] = (not scratch_mode)
2022 child_options["existing_prt"] = prt_tapdisk.minor
2023 child_options["secondary"] = secondary
2024 child_options["standby"] = scratch_mode
2025 try:
2026 leaf_tapdisk = \
2027 Tapdisk.launch_on_tap(blktap, local_leaf_path,
2028 'vhd', child_options)
2029 except:
2030 blktap.free()
2031 raise
2033 lock.release()
2035 util.SMlog("Local read cache: %s, local leaf: %s" % \
2036 (read_cache_path, local_leaf_path))
2038 self.tap = leaf_tapdisk
2039 return leaf_tapdisk.get_devpath()
2041 def remove_cache(self, sr_uuid, vdi_uuid, params):
2042 if not self.target.has_cap("SR_CACHING"):
2043 return
2045 caching = params.get(self.CONF_KEY_ALLOW_CACHING) == "true"
2047 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR)
2048 if caching and not local_sr_uuid:
2049 util.SMlog("ERROR: Local cache SR not specified, ignore")
2050 return
2052 if caching:
2053 self._remove_cache(self._session, local_sr_uuid)
2055 if self._session is not None:
2056 self._updateCacheRecord(self._session, self.target.vdi.uuid, None, None)
2058 def _is_tapdisk_in_use(self, minor):
2059 retVal, links, sockets = util.findRunningProcessOrOpenFile("tapdisk")
2060 if not retVal:
2061 # err on the side of caution
2062 return True
2064 for link in links:
2065 if link.find("tapdev%d" % minor) != -1:
2066 return True
2068 socket_re = re.compile(r'^/.*/nbd\d+\.%d' % minor)
2069 for s in sockets:
2070 if socket_re.match(s):
2071 return True
2073 return False
2075 def _remove_cache(self, session, local_sr_uuid):
2076 import SR
2077 import EXTSR
2078 import NFSSR
2079 from lock import Lock
2080 from FileSR import FileVDI
2082 parent_uuid = vhdutil.getParent(self.target.vdi.path,
2083 FileVDI.extractUuid)
2084 if not parent_uuid:
2085 util.SMlog("ERROR: No parent for VDI %s, ignore" % \
2086 self.target.vdi.uuid)
2087 return
2089 util.SMlog("Tearing down the cache")
2091 parent_uuid = parent_uuid.strip()
2092 shared_target = NFSSR.NFSFileVDI(self.target.vdi.sr, parent_uuid)
2094 SR.registerSR(EXTSR.EXTSR)
2095 local_sr = SR.SR.from_uuid(session, local_sr_uuid)
2097 lock = Lock(self.LOCK_CACHE_SETUP, parent_uuid)
2098 lock.acquire()
2100 # local write node
2101 local_leaf_path = "%s/%s.vhdcache" % \
2102 (local_sr.path, self.target.vdi.uuid)
2103 if util.pathexists(local_leaf_path):
2104 util.SMlog("Deleting local leaf node %s" % local_leaf_path)
2105 os.unlink(local_leaf_path)
2107 read_cache_path = "%s/%s.vhdcache" % (local_sr.path, shared_target.uuid)
2108 prt_tapdisk = Tapdisk.find_by_path(read_cache_path)
2109 if not prt_tapdisk:
2110 util.SMlog("Parent tapdisk not found")
2111 elif not self._is_tapdisk_in_use(prt_tapdisk.minor):
2112 util.SMlog("Parent tapdisk not in use: shutting down %s" % \
2113 read_cache_path)
2114 try:
2115 prt_tapdisk.shutdown()
2116 except:
2117 util.logException("shutting down parent tapdisk")
2118 else:
2119 util.SMlog("Parent tapdisk still in use: %s" % read_cache_path)
2120 # the parent cache files are removed during the local SR's background
2121 # GC run
2123 lock.release()
2125PythonKeyError = KeyError
2128class UEventHandler(object):
2130 def __init__(self):
2131 self._action = None
2133 class KeyError(PythonKeyError):
2134 def __init__(self, args):
2135 super().__init__(args)
2136 self.key = args[0]
2138 @override
2139 def __str__(self) -> str:
2140 return \
2141 "Key '%s' missing in environment. " % self.key + \
2142 "Not called in udev context?"
2144 @classmethod
2145 def getenv(cls, key):
2146 try:
2147 return os.environ[key]
2148 except KeyError as e:
2149 raise cls.KeyError(e.args[0])
2151 def get_action(self):
2152 if not self._action:
2153 self._action = self.getenv('ACTION')
2154 return self._action
2156 class UnhandledEvent(Exception):
2158 def __init__(self, event, handler):
2159 self.event = event
2160 self.handler = handler
2162 @override
2163 def __str__(self) -> str:
2164 return "Uevent '%s' not handled by %s" % \
2165 (self.event, self.handler.__class__.__name__)
2167 ACTIONS: Dict[str, Callable] = {}
2169 def run(self):
2171 action = self.get_action()
2172 try:
2173 fn = self.ACTIONS[action]
2174 except KeyError:
2175 raise self.UnhandledEvent(action, self)
2177 return fn(self)
2179 @override
2180 def __str__(self) -> str:
2181 try:
2182 action = self.get_action()
2183 except:
2184 action = None
2185 return "%s[%s]" % (self.__class__.__name__, action)
2188class __BlktapControl(ClassDevice):
2189 SYSFS_CLASSTYPE = "misc"
2191 def __init__(self):
2192 ClassDevice.__init__(self)
2193 self._default_pool = None
2195 @override
2196 def sysfs_devname(self) -> str:
2197 return "blktap!control"
2199 class DefaultPool(Attribute):
2200 SYSFS_NODENAME = "default_pool"
2202 def get_default_pool_attr(self):
2203 if not self._default_pool:
2204 self._default_pool = self.DefaultPool.from_kobject(self)
2205 return self._default_pool
2207 def get_default_pool_name(self):
2208 return self.get_default_pool_attr().readline()
2210 def set_default_pool_name(self, name):
2211 self.get_default_pool_attr().writeline(name)
2213 def get_default_pool(self):
2214 return BlktapControl.get_pool(self.get_default_pool_name())
2216 def set_default_pool(self, pool):
2217 self.set_default_pool_name(pool.name)
2219 class NoSuchPool(Exception):
2220 def __init__(self, name):
2221 self.name = name
2223 @override
2224 def __str__(self) -> str:
2225 return "No such pool: {}".format(self.name)
2227 def get_pool(self, name):
2228 path = "%s/pools/%s" % (self.sysfs_path(), name)
2230 if not os.path.isdir(path):
2231 raise self.NoSuchPool(name)
2233 return PagePool(path)
2235BlktapControl = __BlktapControl()
2238class PagePool(KObject):
2240 def __init__(self, path):
2241 self.path = path
2242 self._size = None
2244 @override
2245 def sysfs_devname(self) -> str:
2246 return ''
2248 def sysfs_path(self):
2249 return self.path
2251 class Size(Attribute):
2252 SYSFS_NODENAME = "size"
2254 def get_size_attr(self):
2255 if not self._size:
2256 self._size = self.Size.from_kobject(self)
2257 return self._size
2259 def set_size(self, pages):
2260 pages = str(pages)
2261 self.get_size_attr().writeline(pages)
2263 def get_size(self):
2264 pages = self.get_size_attr().readline()
2265 return int(pages)
2268class BusDevice(KObject):
2270 SYSFS_BUSTYPE: ClassVar[str] = ""
2272 @classmethod
2273 def sysfs_bus_path(cls):
2274 return "/sys/bus/%s" % cls.SYSFS_BUSTYPE
2276 def sysfs_path(self):
2277 path = "%s/devices/%s" % (self.sysfs_bus_path(),
2278 self.sysfs_devname())
2280 return path
2283class XenbusDevice(BusDevice):
2284 """Xenbus device, in XS and sysfs"""
2286 XBT_NIL = ""
2288 XENBUS_DEVTYPE: ClassVar[str] = ""
2290 def __init__(self, domid, devid):
2291 self.domid = int(domid)
2292 self.devid = int(devid)
2293 self._xbt = XenbusDevice.XBT_NIL
2295 import xen.lowlevel.xs # pylint: disable=import-error
2296 self.xs = xen.lowlevel.xs.xs()
2298 def xs_path(self, key=None):
2299 path = "backend/%s/%d/%d" % (self.XENBUS_DEVTYPE,
2300 self.domid,
2301 self.devid)
2302 if key is not None:
2303 path = "%s/%s" % (path, key)
2305 return path
2307 def _log(self, prio, msg):
2308 syslog(prio, msg)
2310 def info(self, msg):
2311 self._log(_syslog.LOG_INFO, msg)
2313 def warn(self, msg):
2314 self._log(_syslog.LOG_WARNING, "WARNING: " + msg)
2316 def _xs_read_path(self, path):
2317 val = self.xs.read(self._xbt, path)
2318 #self.info("read %s = '%s'" % (path, val))
2319 return val
2321 def _xs_write_path(self, path, val):
2322 self.xs.write(self._xbt, path, val)
2323 self.info("wrote %s = '%s'" % (path, val))
2325 def _xs_rm_path(self, path):
2326 self.xs.rm(self._xbt, path)
2327 self.info("removed %s" % path)
2329 def read(self, key):
2330 return self._xs_read_path(self.xs_path(key))
2332 def has_xs_key(self, key):
2333 return self.read(key) is not None
2335 def write(self, key, val):
2336 self._xs_write_path(self.xs_path(key), val)
2338 def rm(self, key):
2339 self._xs_rm_path(self.xs_path(key))
2341 def exists(self):
2342 return self.has_xs_key(None)
2344 def begin(self):
2345 assert(self._xbt == XenbusDevice.XBT_NIL)
2346 self._xbt = self.xs.transaction_start()
2348 def commit(self):
2349 ok = self.xs.transaction_end(self._xbt, 0)
2350 self._xbt = XenbusDevice.XBT_NIL
2351 return ok
2353 def abort(self):
2354 ok = self.xs.transaction_end(self._xbt, 1)
2355 assert(ok == True)
2356 self._xbt = XenbusDevice.XBT_NIL
2358 def create_physical_device(self):
2359 """The standard protocol is: toolstack writes 'params', linux hotplug
2360 script translates this into physical-device=%x:%x"""
2361 if self.has_xs_key("physical-device"):
2362 return
2363 try:
2364 params = self.read("params")
2365 frontend = self.read("frontend")
2366 is_cdrom = self._xs_read_path("%s/device-type") == "cdrom"
2367 # We don't have PV drivers for CDROM devices, so we prevent blkback
2368 # from opening the physical-device
2369 if not(is_cdrom):
2370 major_minor = os.stat(params).st_rdev
2371 major, minor = divmod(major_minor, 256)
2372 self.write("physical-device", "%x:%x" % (major, minor))
2373 except:
2374 util.logException("BLKTAP2:create_physical_device")
2376 def signal_hotplug(self, online=True):
2377 xapi_path = "/xapi/%d/hotplug/%s/%d/hotplug" % (self.domid,
2378 self.XENBUS_DEVTYPE,
2379 self.devid)
2380 upstream_path = self.xs_path("hotplug-status")
2381 if online:
2382 self._xs_write_path(xapi_path, "online")
2383 self._xs_write_path(upstream_path, "connected")
2384 else:
2385 self._xs_rm_path(xapi_path)
2386 self._xs_rm_path(upstream_path)
2388 @override
2389 def sysfs_devname(self) -> str:
2390 return "%s-%d-%d" % (self.XENBUS_DEVTYPE,
2391 self.domid, self.devid)
2393 @override
2394 def __str__(self) -> str:
2395 return self.sysfs_devname()
2397 @classmethod
2398 def find(cls):
2399 pattern = "/sys/bus/%s/devices/%s*" % (cls.SYSFS_BUSTYPE,
2400 cls.XENBUS_DEVTYPE)
2401 for path in glob.glob(pattern):
2403 name = os.path.basename(path)
2404 (_type, domid, devid) = name.split('-')
2406 yield cls(domid, devid)
2409class XenBackendDevice(XenbusDevice):
2410 """Xenbus backend device"""
2411 SYSFS_BUSTYPE = "xen-backend"
2413 @classmethod
2414 def from_xs_path(cls, _path):
2415 (_backend, _type, domid, devid) = _path.split('/')
2417 assert _backend == 'backend'
2418 assert _type == cls.XENBUS_DEVTYPE
2420 domid = int(domid)
2421 devid = int(devid)
2423 return cls(domid, devid)
2426class Blkback(XenBackendDevice):
2427 """A blkback VBD"""
2429 XENBUS_DEVTYPE = "vbd"
2431 def __init__(self, domid, devid):
2432 XenBackendDevice.__init__(self, domid, devid)
2433 self._phy = None
2434 self._vdi_uuid = None
2435 self._q_state = None
2436 self._q_events = None
2438 class XenstoreValueError(Exception):
2439 KEY: ClassVar[str] = ""
2441 def __init__(self, vbd, _str):
2442 self.vbd = vbd
2443 self.str = _str
2445 @override
2446 def __str__(self) -> str:
2447 return "Backend %s " % self.vbd + \
2448 "has %s = %s" % (self.KEY, self.str)
2450 class PhysicalDeviceError(XenstoreValueError):
2451 KEY = "physical-device"
2453 class PhysicalDevice(object):
2455 def __init__(self, major, minor):
2456 self.major = int(major)
2457 self.minor = int(minor)
2459 @classmethod
2460 def from_xbdev(cls, xbdev):
2462 phy = xbdev.read("physical-device")
2464 try:
2465 major, minor = phy.split(':')
2466 major = int(major, 0x10)
2467 minor = int(minor, 0x10)
2468 except Exception as e:
2469 raise xbdev.PhysicalDeviceError(xbdev, phy)
2471 return cls(major, minor)
2473 def makedev(self):
2474 return os.makedev(self.major, self.minor)
2476 def is_tap(self):
2477 return self.major == Tapdisk.major()
2479 @override
2480 def __str__(self) -> str:
2481 return "%s:%s" % (self.major, self.minor)
2483 @override
2484 def __eq__(self, other) -> bool:
2485 return \
2486 self.major == other.major and \
2487 self.minor == other.minor
2489 def get_physical_device(self):
2490 if not self._phy:
2491 self._phy = self.PhysicalDevice.from_xbdev(self)
2492 return self._phy
2494 class QueueEvents(Attribute):
2495 """Blkback sysfs node to select queue-state event
2496 notifications emitted."""
2498 SYSFS_NODENAME = "queue_events"
2500 QUEUE_RUNNING = (1 << 0)
2501 QUEUE_PAUSE_DONE = (1 << 1)
2502 QUEUE_SHUTDOWN_DONE = (1 << 2)
2503 QUEUE_PAUSE_REQUEST = (1 << 3)
2504 QUEUE_SHUTDOWN_REQUEST = (1 << 4)
2506 def get_mask(self):
2507 return int(self.readline(), 0x10)
2509 def set_mask(self, mask):
2510 self.writeline("0x%x" % mask)
2512 def get_queue_events(self):
2513 if not self._q_events:
2514 self._q_events = self.QueueEvents.from_kobject(self)
2515 return self._q_events
2517 def get_vdi_uuid(self):
2518 if not self._vdi_uuid:
2519 self._vdi_uuid = self.read("sm-data/vdi-uuid")
2520 return self._vdi_uuid
2522 def pause_requested(self):
2523 return self.has_xs_key("pause")
2525 def shutdown_requested(self):
2526 return self.has_xs_key("shutdown-request")
2528 def shutdown_done(self):
2529 return self.has_xs_key("shutdown-done")
2531 def running(self):
2532 return self.has_xs_key('queue-0/kthread-pid')
2534 @classmethod
2535 def find_by_physical_device(cls, phy):
2536 for dev in cls.find():
2537 try:
2538 _phy = dev.get_physical_device()
2539 except cls.PhysicalDeviceError:
2540 continue
2542 if _phy == phy:
2543 yield dev
2545 @classmethod
2546 def find_by_tap_minor(cls, minor):
2547 phy = cls.PhysicalDevice(Tapdisk.major(), minor)
2548 return cls.find_by_physical_device(phy)
2550 @classmethod
2551 def find_by_tap(cls, tapdisk):
2552 return cls.find_by_tap_minor(tapdisk.minor)
2554 def has_tap(self):
2556 if not self.can_tap():
2557 return False
2559 phy = self.get_physical_device()
2560 if phy:
2561 return phy.is_tap()
2563 return False
2565 def is_bare_hvm(self):
2566 """File VDIs for bare HVM. These are directly accessible by Qemu."""
2567 try:
2568 self.get_physical_device()
2570 except self.PhysicalDeviceError as e:
2571 vdi_type = self.read("type")
2573 self.info("HVM VDI: type=%s" % vdi_type)
2575 if e.str is not None or vdi_type != 'file':
2576 raise
2578 return True
2580 return False
2582 def can_tap(self):
2583 return not self.is_bare_hvm()
2586class BlkbackEventHandler(UEventHandler):
2588 LOG_FACILITY = _syslog.LOG_DAEMON
2590 def __init__(self, ident=None, action=None):
2591 if not ident:
2592 ident = self.__class__.__name__
2594 self.ident = ident
2595 self._vbd = None
2596 self._tapdisk = None
2598 UEventHandler.__init__(self)
2600 @override
2601 def run(self) -> None:
2603 self.xs_path = self.getenv('XENBUS_PATH')
2604 openlog(str(self), 0, self.LOG_FACILITY)
2606 UEventHandler.run(self)
2608 @override
2609 def __str__(self) -> str:
2611 try:
2612 path = self.xs_path
2613 except:
2614 path = None
2616 try:
2617 action = self.get_action()
2618 except:
2619 action = None
2621 return "%s[%s](%s)" % (self.ident, action, path)
2623 def _log(self, prio, msg):
2624 syslog(prio, msg)
2625 util.SMlog("%s: " % self + msg)
2627 def info(self, msg):
2628 self._log(_syslog.LOG_INFO, msg)
2630 def warn(self, msg):
2631 self._log(_syslog.LOG_WARNING, "WARNING: " + msg)
2633 def error(self, msg):
2634 self._log(_syslog.LOG_ERR, "ERROR: " + msg)
2636 def get_vbd(self):
2637 if not self._vbd:
2638 self._vbd = Blkback.from_xs_path(self.xs_path)
2639 return self._vbd
2641 def get_tapdisk(self):
2642 if not self._tapdisk:
2643 minor = self.get_vbd().get_physical_device().minor
2644 self._tapdisk = Tapdisk.from_minor(minor)
2645 return self._tapdisk
2646 #
2647 # Events
2648 #
2650 def __add(self):
2651 vbd = self.get_vbd()
2652 # Manage blkback transitions
2653 # self._manage_vbd()
2655 vbd.create_physical_device()
2657 vbd.signal_hotplug()
2659 @retried(backoff=.5, limit=10)
2660 def add(self):
2661 try:
2662 self.__add()
2663 except Attribute.NoSuchAttribute as e:
2664 #
2665 # FIXME: KOBJ_ADD is racing backend.probe, which
2666 # registers device attributes. So poll a little.
2667 #
2668 self.warn("%s, still trying." % e)
2669 raise RetryLoop.TransientFailure(e)
2671 def __change(self):
2672 vbd = self.get_vbd()
2674 # 1. Pause or resume tapdisk (if there is one)
2676 if vbd.has_tap():
2677 pass
2678 #self._pause_update_tap()
2680 # 2. Signal Xapi.VBD.pause/resume completion
2682 self._signal_xapi()
2684 def change(self):
2685 vbd = self.get_vbd()
2687 # NB. Beware of spurious change events between shutdown
2688 # completion and device removal. Also, Xapi.VM.migrate will
2689 # hammer a couple extra shutdown-requests into the source VBD.
2691 while True:
2692 vbd.begin()
2694 if not vbd.exists() or \
2695 vbd.shutdown_done():
2696 break
2698 self.__change()
2700 if vbd.commit():
2701 return
2703 vbd.abort()
2704 self.info("spurious uevent, ignored.")
2706 def remove(self):
2707 vbd = self.get_vbd()
2709 vbd.signal_hotplug(False)
2711 ACTIONS = {'add': add,
2712 'change': change,
2713 'remove': remove}
2714 #
2715 # VDI.pause
2716 #
2718 def _tap_should_pause(self):
2719 """Enumerate all VBDs on our tapdisk. Returns true iff any was
2720 paused"""
2722 tapdisk = self.get_tapdisk()
2723 TapState = Tapdisk.PauseState
2725 PAUSED = 'P'
2726 RUNNING = 'R'
2727 PAUSED_SHUTDOWN = 'P,S'
2728 # NB. Shutdown/paused is special. We know it's not going
2729 # to restart again, so it's a RUNNING. Still better than
2730 # backtracking a removed device during Vbd.unplug completion.
2732 next = TapState.RUNNING
2733 vbds = {}
2735 for vbd in Blkback.find_by_tap(tapdisk):
2736 name = str(vbd)
2738 pausing = vbd.pause_requested()
2739 closing = vbd.shutdown_requested()
2740 running = vbd.running()
2742 if pausing:
2743 if closing and not running:
2744 vbds[name] = PAUSED_SHUTDOWN
2745 else:
2746 vbds[name] = PAUSED
2747 next = TapState.PAUSED
2749 else:
2750 vbds[name] = RUNNING
2752 self.info("tapdev%d (%s): %s -> %s"
2753 % (tapdisk.minor, tapdisk.pause_state(),
2754 vbds, next))
2756 return next == TapState.PAUSED
2758 def _pause_update_tap(self):
2759 vbd = self.get_vbd()
2761 if self._tap_should_pause():
2762 self._pause_tap()
2763 else:
2764 self._resume_tap()
2766 def _pause_tap(self):
2767 tapdisk = self.get_tapdisk()
2769 if not tapdisk.is_paused():
2770 self.info("pausing %s" % tapdisk)
2771 tapdisk.pause()
2773 def _resume_tap(self):
2774 tapdisk = self.get_tapdisk()
2776 # NB. Raw VDI snapshots. Refresh the physical path and
2777 # type while resuming.
2778 vbd = self.get_vbd()
2779 vdi_uuid = vbd.get_vdi_uuid()
2781 if tapdisk.is_paused():
2782 self.info("loading vdi uuid=%s" % vdi_uuid)
2783 vdi = VDI.from_cli(vdi_uuid)
2784 _type = vdi.get_tap_type()
2785 path = vdi.get_phy_path()
2786 self.info("resuming %s on %s:%s" % (tapdisk, _type, path))
2787 tapdisk.unpause(_type, path)
2788 #
2789 # VBD.pause/shutdown
2790 #
2792 def _manage_vbd(self):
2793 vbd = self.get_vbd()
2794 # NB. Hook into VBD state transitions.
2796 events = vbd.get_queue_events()
2798 mask = 0
2799 mask |= events.QUEUE_PAUSE_DONE # pause/unpause
2800 mask |= events.QUEUE_SHUTDOWN_DONE # shutdown
2801 # TODO: mask |= events.QUEUE_SHUTDOWN_REQUEST, for shutdown=force
2802 # TODO: mask |= events.QUEUE_RUNNING, for ionice updates etc
2804 events.set_mask(mask)
2805 self.info("wrote %s = %#02x" % (events.path, mask))
2807 def _signal_xapi(self):
2808 vbd = self.get_vbd()
2810 pausing = vbd.pause_requested()
2811 closing = vbd.shutdown_requested()
2812 running = vbd.running()
2814 handled = 0
2816 if pausing and not running:
2817 if 'pause-done' not in vbd:
2818 vbd.write('pause-done', '')
2819 handled += 1
2821 if not pausing:
2822 if 'pause-done' in vbd:
2823 vbd.rm('pause-done')
2824 handled += 1
2826 if closing and not running:
2827 if 'shutdown-done' not in vbd:
2828 vbd.write('shutdown-done', '')
2829 handled += 1
2831 if handled > 1:
2832 self.warn("handled %d events, " % handled +
2833 "pausing=%s closing=%s running=%s" % \
2834 (pausing, closing, running))
2836if __name__ == '__main__': 2836 ↛ 2838line 2836 didn't jump to line 2838, because the condition on line 2836 was never true
2838 import sys
2839 prog = os.path.basename(sys.argv[0])
2841 #
2842 # Simple CLI interface for manual operation
2843 #
2844 # tap.* level calls go down to local Tapdisk()s (by physical path)
2845 # vdi.* level calls run the plugin calls across host boundaries.
2846 #
2848 def usage(stream):
2849 print("usage: %s tap.{list|major}" % prog, file=stream)
2850 print(" %s tap.{launch|find|get|pause|" % prog + \
2851 "unpause|shutdown|stats} {[<tt>:]<path>} | [minor=]<int> | .. }", file=stream)
2852 print(" %s vbd.uevent" % prog, file=stream)
2854 try:
2855 cmd = sys.argv[1]
2856 except IndexError:
2857 usage(sys.stderr)
2858 sys.exit(1)
2860 try:
2861 _class, method = cmd.split('.')
2862 except:
2863 usage(sys.stderr)
2864 sys.exit(1)
2866 #
2867 # Local Tapdisks
2868 #
2870 if cmd == 'tap.major':
2872 print("%d" % Tapdisk.major())
2874 elif cmd == 'tap.launch':
2876 tapdisk = Tapdisk.launch_from_arg(sys.argv[2])
2877 print("Launched %s" % tapdisk, file=sys.stderr)
2879 elif _class == 'tap':
2881 attrs: Dict[str, Any] = {}
2882 for item in sys.argv[2:]:
2883 try:
2884 key, val = item.split('=')
2885 attrs[key] = val
2886 continue
2887 except ValueError:
2888 pass
2890 try:
2891 attrs['minor'] = int(item)
2892 continue
2893 except ValueError:
2894 pass
2896 try:
2897 arg = Tapdisk.Arg.parse(item)
2898 attrs['_type'] = arg.type
2899 attrs['path'] = arg.path
2900 continue
2901 except Tapdisk.Arg.InvalidArgument:
2902 pass
2904 attrs['path'] = item
2906 if cmd == 'tap.list':
2908 for tapdisk in Tapdisk.list( ** attrs):
2909 blktap = tapdisk.get_blktap()
2910 print(tapdisk, end=' ')
2911 print("%s: task=%s pool=%s" % \
2912 (blktap,
2913 blktap.get_task_pid(),
2914 blktap.get_pool_name()))
2916 elif cmd == 'tap.vbds':
2917 # Find all Blkback instances for a given tapdisk
2919 for tapdisk in Tapdisk.list( ** attrs):
2920 print("%s:" % tapdisk, end=' ')
2921 for vbd in Blkback.find_by_tap(tapdisk):
2922 print(vbd, end=' ')
2923 print()
2925 else:
2927 if not attrs:
2928 usage(sys.stderr)
2929 sys.exit(1)
2931 try:
2932 tapdisk = Tapdisk.get( ** attrs)
2933 except TypeError:
2934 usage(sys.stderr)
2935 sys.exit(1)
2937 if cmd == 'tap.shutdown':
2938 # Shutdown a running tapdisk, or raise
2939 tapdisk.shutdown()
2940 print("Shut down %s" % tapdisk, file=sys.stderr)
2942 elif cmd == 'tap.pause':
2943 # Pause an unpaused tapdisk, or raise
2944 tapdisk.pause()
2945 print("Paused %s" % tapdisk, file=sys.stderr)
2947 elif cmd == 'tap.unpause':
2948 # Unpause a paused tapdisk, or raise
2949 tapdisk.unpause()
2950 print("Unpaused %s" % tapdisk, file=sys.stderr)
2952 elif cmd == 'tap.stats':
2953 # Gather tapdisk status
2954 stats = tapdisk.stats()
2955 print("%s:" % tapdisk)
2956 print(json.dumps(stats, indent=True))
2958 else:
2959 usage(sys.stderr)
2960 sys.exit(1)
2962 elif cmd == 'vbd.uevent':
2964 hnd = BlkbackEventHandler(cmd)
2966 if not sys.stdin.isatty():
2967 try:
2968 hnd.run()
2969 except Exception as e:
2970 hnd.error("Unhandled Exception: %s" % e)
2972 import traceback
2973 _type, value, tb = sys.exc_info()
2974 trace = traceback.format_exception(_type, value, tb)
2975 for entry in trace:
2976 for line in entry.rstrip().split('\n'):
2977 util.SMlog(line)
2978 else:
2979 hnd.run()
2981 elif cmd == 'vbd.list':
2983 for vbd in Blkback.find():
2984 print(vbd, \
2985 "physical-device=%s" % vbd.get_physical_device(), \
2986 "pause=%s" % vbd.pause_requested())
2988 else:
2989 usage(sys.stderr)
2990 sys.exit(1)