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, Dict, override
23from abc import ABC, ABCMeta, 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(ABC):
526 @property
527 @abstractmethod
528 def SYSFS_CLASSTYPE(self) -> str:
529 pass
531 @abstractmethod
532 def sysfs_devname(self) -> str:
533 pass
536class Attribute(ABC):
537 @property
538 @abstractmethod
539 def SYSFS_NODENAME(self) -> str:
540 pass
542 def __init__(self, path):
543 self.path = path
545 @classmethod
546 def from_kobject(cls, kobj):
547 path = "%s/%s" % (kobj.sysfs_path(), cls.SYSFS_NODENAME)
548 return cls(path)
550 class NoSuchAttribute(Exception):
551 def __init__(self, name):
552 self.name = name
554 @override
555 def __str__(self) -> str:
556 return "No such attribute: %s" % self.name
558 def _open(self, mode='r'):
559 try:
560 return open(self.path, mode)
561 except IOError as e:
562 if e.errno == errno.ENOENT:
563 raise self.NoSuchAttribute(self)
564 raise
566 def readline(self):
567 f = self._open('r')
568 s = f.readline().rstrip()
569 f.close()
570 return s
572 def writeline(self, val):
573 f = self._open('w')
574 f.write(val)
575 f.close()
578class ClassDevice(KObject):
580 @classmethod
581 def sysfs_class_path(cls):
582 return "/sys/class/%s" % cls.SYSFS_CLASSTYPE
584 def sysfs_path(self):
585 return "%s/%s" % (self.sysfs_class_path(),
586 self.sysfs_devname())
589class Blktap(ClassDevice):
591 DEV_BASEDIR = '/dev/xen/blktap-2'
593 @property
594 @override
595 def SYSFS_CLASSTYPE(self) -> str:
596 return 'blktap2'
598 def __init__(self, minor):
599 self.minor = minor
600 self._pool = None
601 self._task = None
603 @classmethod
604 def allocate(cls):
605 # FIXME. Should rather go into init.
606 mkdirs(cls.DEV_BASEDIR)
608 devname = TapCtl.allocate()
609 minor = Tapdisk._parse_minor(devname)
610 return cls(minor)
612 def free(self):
613 TapCtl.free(self.minor)
615 @override
616 def __str__(self) -> str:
617 return "%s(minor=%d)" % (self.__class__.__name__, self.minor)
619 @override
620 def sysfs_devname(self) -> str:
621 return "blktap!blktap%d" % self.minor
623 class Pool(Attribute):
624 @property
625 @override
626 def SYSFS_NODENAME(self) -> str:
627 return 'pool'
629 def get_pool_attr(self):
630 if not self._pool:
631 self._pool = self.Pool.from_kobject(self)
632 return self._pool
634 def get_pool_name(self):
635 return self.get_pool_attr().readline()
637 def set_pool_name(self, name):
638 self.get_pool_attr().writeline(name)
640 def set_pool_size(self, pages):
641 self.get_pool().set_size(pages)
643 def get_pool(self):
644 return BlktapControl.get_pool(self.get_pool_name())
646 def set_pool(self, pool):
647 self.set_pool_name(pool.name)
649 class Task(Attribute):
650 @property
651 @override
652 def SYSFS_NODENAME(self) -> str:
653 return 'task'
655 def get_task_attr(self):
656 if not self._task:
657 self._task = self.Task.from_kobject(self)
658 return self._task
660 def get_task_pid(self):
661 pid = self.get_task_attr().readline()
662 try:
663 return int(pid)
664 except ValueError:
665 return None
667 def find_tapdisk(self):
668 pid = self.get_task_pid()
669 if pid is None:
670 return None
672 return Tapdisk.find(pid=pid, minor=self.minor)
674 def get_tapdisk(self):
675 tapdisk = self.find_tapdisk()
676 if not tapdisk:
677 raise TapdiskNotRunning(minor=self.minor)
678 return tapdisk
681class Tapdisk(object):
683 TYPES = ['aio', 'vhd']
685 def __init__(self, pid, minor, _type, path, state):
686 self.pid = pid
687 self.minor = minor
688 self.type = _type
689 self.path = path
690 self.state = state
691 self._dirty = False
692 self._blktap = None
694 @override
695 def __str__(self) -> str:
696 state = self.pause_state()
697 return "Tapdisk(%s, pid=%d, minor=%s, state=%s)" % \
698 (self.get_arg(), self.pid, self.minor, state)
700 @classmethod
701 def list(cls, **args):
703 for row in TapCtl.list( ** args):
705 args = {'pid': None,
706 'minor': None,
707 'state': None,
708 '_type': None,
709 'path': None}
711 for key, val in row.items():
712 if key in args:
713 args[key] = val
715 if 'args' in row: 715 ↛ 720line 715 didn't jump to line 720, because the condition on line 715 was never false
716 image = Tapdisk.Arg.parse(row['args'])
717 args['_type'] = image.type
718 args['path'] = image.path
720 if None in args.values(): 720 ↛ 721line 720 didn't jump to line 721, because the condition on line 720 was never true
721 continue
723 yield Tapdisk( ** args)
725 @classmethod
726 def find(cls, **args):
728 found = list(cls.list( ** args))
730 if len(found) > 1: 730 ↛ 731line 730 didn't jump to line 731, because the condition on line 730 was never true
731 raise TapdiskNotUnique(found)
733 if found: 733 ↛ 734line 733 didn't jump to line 734, because the condition on line 733 was never true
734 return found[0]
736 return None
738 @classmethod
739 def find_by_path(cls, path):
740 return cls.find(path=path)
742 @classmethod
743 def find_by_minor(cls, minor):
744 return cls.find(minor=minor)
746 @classmethod
747 def get(cls, **attrs):
749 tapdisk = cls.find( ** attrs)
751 if not tapdisk:
752 raise TapdiskNotRunning( ** attrs)
754 return tapdisk
756 @classmethod
757 def from_path(cls, path):
758 return cls.get(path=path)
760 @classmethod
761 def from_minor(cls, minor):
762 return cls.get(minor=minor)
764 @classmethod
765 def __from_blktap(cls, blktap):
766 tapdisk = cls.from_minor(minor=blktap.minor)
767 tapdisk._blktap = blktap
768 return tapdisk
770 def get_blktap(self):
771 if not self._blktap:
772 self._blktap = Blktap(self.minor)
773 return self._blktap
775 class Arg:
777 def __init__(self, _type, path):
778 self.type = _type
779 self.path = path
781 @override
782 def __str__(self) -> str:
783 return "%s:%s" % (self.type, self.path)
785 @classmethod
786 def parse(cls, arg):
788 try:
789 _type, path = arg.split(":", 1)
790 except ValueError:
791 raise cls.InvalidArgument(arg)
793 if _type not in Tapdisk.TYPES: 793 ↛ 794line 793 didn't jump to line 794, because the condition on line 793 was never true
794 raise cls.InvalidType(_type)
796 return cls(_type, path)
798 class InvalidType(Exception):
799 def __init__(self, _type):
800 self.type = _type
802 @override
803 def __str__(self) -> str:
804 return "Not a Tapdisk type: %s" % self.type
806 class InvalidArgument(Exception):
807 def __init__(self, arg):
808 self.arg = arg
810 @override
811 def __str__(self) -> str:
812 return "Not a Tapdisk image: %s" % self.arg
814 def get_arg(self):
815 return self.Arg(self.type, self.path)
817 def get_devpath(self):
818 return "%s/tapdev%d" % (Blktap.DEV_BASEDIR, self.minor)
820 @classmethod
821 def launch_from_arg(cls, arg):
822 arg = cls.Arg.parse(arg)
823 return cls.launch(arg.path, arg.type, False)
825 @staticmethod
826 def cgclassify(pid):
828 # We dont provide any <controllers>:<path>
829 # so cgclassify uses /etc/cgrules.conf which
830 # we have configured in the spec file.
831 cmd = ["cgclassify", str(pid)]
832 try:
833 util.pread2(cmd)
834 except util.CommandException as e:
835 util.logException(e)
837 @classmethod
838 def launch_on_tap(cls, blktap, path, _type, options):
840 tapdisk = cls.find_by_path(path)
841 if tapdisk: 841 ↛ 842line 841 didn't jump to line 842, because the condition on line 841 was never true
842 raise TapdiskExists(tapdisk)
844 minor = blktap.minor
845 try:
846 pid = TapCtl.spawn()
847 cls.cgclassify(pid)
848 try:
849 TapCtl.attach(pid, minor)
851 try:
852 retry_open = 0
853 while True:
854 try:
855 TapCtl.open(pid, minor, _type, path, options)
856 break
857 except TapCtl.CommandFailure as e:
858 err = (
859 'status' in e.info and e.info['status']
860 ) or None
861 if err in (errno.EIO, errno.EROFS, errno.EAGAIN): 861 ↛ 862line 861 didn't jump to line 862, because the condition on line 861 was never true
862 if retry_open < 5:
863 retry_open += 1
864 time.sleep(1)
865 continue
866 if LINSTOR_AVAILABLE and err == errno.EROFS:
867 log_drbd_openers(path)
868 raise
869 try:
870 tapdisk = cls.__from_blktap(blktap)
871 node = '/sys/dev/block/%d:%d' % (tapdisk.major(), tapdisk.minor)
872 util.set_scheduler_sysfs_node(node, ['none', 'noop'])
873 return tapdisk
874 except:
875 TapCtl.close(pid, minor)
876 raise
878 except:
879 TapCtl.detach(pid, minor)
880 raise
882 except:
883 try:
884 TapCtl.shutdown(pid)
885 except:
886 # Best effort to shutdown
887 pass
888 raise
890 except TapCtl.CommandFailure as ctl:
891 util.logException(ctl)
892 if ((path.startswith('/dev/xapi/cd/') or path.startswith('/dev/sr')) and 892 ↛ 896line 892 didn't jump to line 896, because the condition on line 892 was never false
893 ctl.has_status and ctl.get_error_code() == 123): # ENOMEDIUM (No medium found)
894 raise xs_errors.XenError('TapdiskDriveEmpty')
895 else:
896 raise TapdiskFailed(cls.Arg(_type, path), ctl)
898 @classmethod
899 def launch(cls, path, _type, rdonly):
900 blktap = Blktap.allocate()
901 try:
902 return cls.launch_on_tap(blktap, path, _type, {"rdonly": rdonly})
903 except:
904 blktap.free()
905 raise
907 def shutdown(self, force=False):
909 TapCtl.close(self.pid, self.minor, force)
911 TapCtl.detach(self.pid, self.minor)
913 self.get_blktap().free()
915 def pause(self):
917 if not self.is_running():
918 raise TapdiskInvalidState(self)
920 TapCtl.pause(self.pid, self.minor)
922 self._set_dirty()
924 def unpause(self, _type=None, path=None, mirror=None, cbtlog=None):
926 if not self.is_paused():
927 raise TapdiskInvalidState(self)
929 # FIXME: should the arguments be optional?
930 if _type is None:
931 _type = self.type
932 if path is None:
933 path = self.path
935 TapCtl.unpause(self.pid, self.minor, _type, path, mirror=mirror,
936 cbtlog=cbtlog)
938 self._set_dirty()
940 def stats(self):
941 return json.loads(TapCtl.stats(self.pid, self.minor))
942 #
943 # NB. dirty/refresh: reload attributes on next access
944 #
946 def _set_dirty(self):
947 self._dirty = True
949 def _refresh(self, __get):
950 t = self.from_minor(__get('minor'))
951 self.__init__(t.pid, t.minor, t.type, t.path, t.state)
953 @override
954 def __getattribute__(self, name) -> Any:
955 def __get(name):
956 # NB. avoid(rec(ursion)
957 return object.__getattribute__(self, name)
959 if __get('_dirty') and \ 959 ↛ 961line 959 didn't jump to line 961, because the condition on line 959 was never true
960 name in ['minor', 'type', 'path', 'state']:
961 self._refresh(__get)
962 self._dirty = False
964 return __get(name)
966 class PauseState:
967 RUNNING = 'R'
968 PAUSING = 'r'
969 PAUSED = 'P'
971 class Flags:
972 DEAD = 0x0001
973 CLOSED = 0x0002
974 QUIESCE_REQUESTED = 0x0004
975 QUIESCED = 0x0008
976 PAUSE_REQUESTED = 0x0010
977 PAUSED = 0x0020
978 SHUTDOWN_REQUESTED = 0x0040
979 LOCKING = 0x0080
980 RETRY_NEEDED = 0x0100
981 LOG_DROPPED = 0x0200
983 PAUSE_MASK = PAUSE_REQUESTED | PAUSED
985 def is_paused(self):
986 return not not (self.state & self.Flags.PAUSED)
988 def is_running(self):
989 return not (self.state & self.Flags.PAUSE_MASK)
991 def pause_state(self):
992 if self.state & self.Flags.PAUSED:
993 return self.PauseState.PAUSED
995 if self.state & self.Flags.PAUSE_REQUESTED:
996 return self.PauseState.PAUSING
998 return self.PauseState.RUNNING
1000 @staticmethod
1001 def _parse_minor(devpath):
1002 regex = r'%s/(blktap|tapdev)(\d+)$' % Blktap.DEV_BASEDIR
1003 pattern = re.compile(regex)
1004 groups = pattern.search(devpath)
1005 if not groups:
1006 raise Exception("malformed tap device: '%s' (%s) " % (devpath, regex))
1008 minor = groups.group(2)
1009 return int(minor)
1011 _major = None
1013 @classmethod
1014 def major(cls):
1015 if cls._major:
1016 return cls._major
1018 devices = open("/proc/devices")
1019 for line in devices:
1021 row = line.rstrip().split(' ')
1022 if len(row) != 2:
1023 continue
1025 major, name = row
1026 if name != 'tapdev':
1027 continue
1029 cls._major = int(major)
1030 break
1032 devices.close()
1033 return cls._major
1036class VDI(object):
1037 """SR.vdi driver decorator for blktap2"""
1039 CONF_KEY_ALLOW_CACHING = "vdi_allow_caching"
1040 CONF_KEY_MODE_ON_BOOT = "vdi_on_boot"
1041 CONF_KEY_CACHE_SR = "local_cache_sr"
1042 CONF_KEY_O_DIRECT = "o_direct"
1043 LOCK_CACHE_SETUP = "cachesetup"
1045 ATTACH_DETACH_RETRY_SECS = 120
1047 def __init__(self, uuid, target, driver_info):
1048 self.target = self.TargetDriver(target, driver_info)
1049 self._vdi_uuid = uuid
1050 self._session = target.session
1051 self.xenstore_data = scsiutil.update_XS_SCSIdata(uuid, scsiutil.gen_synthetic_page_data(uuid))
1052 self.__o_direct = None
1053 self.__o_direct_reason = None
1054 self.lock = Lock("vdi", uuid)
1055 self.tap = None
1057 def get_o_direct_capability(self, options):
1058 """Returns True/False based on licensing and caching_params"""
1059 if self.__o_direct is not None: 1059 ↛ 1060line 1059 didn't jump to line 1060, because the condition on line 1059 was never true
1060 return self.__o_direct, self.__o_direct_reason
1062 if util.read_caching_is_restricted(self._session): 1062 ↛ 1063line 1062 didn't jump to line 1063, because the condition on line 1062 was never true
1063 self.__o_direct = True
1064 self.__o_direct_reason = "LICENSE_RESTRICTION"
1065 elif not ((self.target.vdi.sr.handles("nfs") or self.target.vdi.sr.handles("ext") or self.target.vdi.sr.handles("smb"))): 1065 ↛ 1068line 1065 didn't jump to line 1068, because the condition on line 1065 was never false
1066 self.__o_direct = True
1067 self.__o_direct_reason = "SR_NOT_SUPPORTED"
1068 elif not (options.get("rdonly") or self.target.vdi.parent):
1069 util.SMlog(self.target.vdi)
1070 self.__o_direct = True
1071 self.__o_direct_reason = "NO_RO_IMAGE"
1072 elif options.get("rdonly") and not self.target.vdi.parent:
1073 self.__o_direct = True
1074 self.__o_direct_reason = "RO_WITH_NO_PARENT"
1075 elif options.get(self.CONF_KEY_O_DIRECT):
1076 self.__o_direct = True
1077 self.__o_direct_reason = "SR_OVERRIDE"
1079 if self.__o_direct is None: 1079 ↛ 1080line 1079 didn't jump to line 1080, because the condition on line 1079 was never true
1080 self.__o_direct = False
1081 self.__o_direct_reason = ""
1083 return self.__o_direct, self.__o_direct_reason
1085 @classmethod
1086 def from_cli(cls, uuid):
1087 import VDI as sm
1089 session = XenAPI.xapi_local()
1090 session.xenapi.login_with_password('root', '', '', 'SM')
1092 target = sm.VDI.from_uuid(session, uuid)
1093 driver_info = target.sr.srcmd.driver_info
1095 session.xenapi.session.logout()
1097 return cls(uuid, target, driver_info)
1099 @staticmethod
1100 def _tap_type(vdi_type):
1101 """Map a VDI type (e.g. 'raw') to a tapdisk driver type (e.g. 'aio')"""
1102 return {
1103 'raw': 'aio',
1104 'vhd': 'vhd',
1105 'iso': 'aio', # for ISO SR
1106 'aio': 'aio', # for LVHD
1107 'file': 'aio',
1108 'phy': 'aio'
1109 }[vdi_type]
1111 def get_tap_type(self):
1112 vdi_type = self.target.get_vdi_type()
1113 return VDI._tap_type(vdi_type)
1115 def get_phy_path(self):
1116 return self.target.get_vdi_path()
1118 class UnexpectedVDIType(Exception):
1120 def __init__(self, vdi_type, target):
1121 self.vdi_type = vdi_type
1122 self.target = target
1124 @override
1125 def __str__(self) -> str:
1126 return \
1127 "Target %s has unexpected VDI type '%s'" % \
1128 (type(self.target), self.vdi_type)
1130 VDI_PLUG_TYPE = {'phy': 'phy', # for NETAPP
1131 'raw': 'phy',
1132 'aio': 'tap', # for LVHD raw nodes
1133 'iso': 'tap', # for ISOSR
1134 'file': 'tap',
1135 'vhd': 'tap'}
1137 def tap_wanted(self):
1138 # 1. Let the target vdi_type decide
1140 vdi_type = self.target.get_vdi_type()
1142 try:
1143 plug_type = self.VDI_PLUG_TYPE[vdi_type]
1144 except KeyError:
1145 raise self.UnexpectedVDIType(vdi_type,
1146 self.target.vdi)
1148 if plug_type == 'tap': 1148 ↛ 1149line 1148 didn't jump to line 1149, because the condition on line 1148 was never true
1149 return True
1150 elif self.target.vdi.sr.handles('udev'): 1150 ↛ 1156line 1150 didn't jump to line 1156, because the condition on line 1150 was never false
1151 return True
1152 # 2. Otherwise, there may be more reasons
1153 #
1154 # .. TBD
1156 return False
1158 class TargetDriver:
1159 """Safe target driver access."""
1160 # NB. *Must* test caps for optional calls. Some targets
1161 # actually implement some slots, but do not enable them. Just
1162 # try/except would risk breaking compatibility.
1164 def __init__(self, vdi, driver_info):
1165 self.vdi = vdi
1166 self._caps = driver_info['capabilities']
1168 def has_cap(self, cap):
1169 """Determine if target has given capability"""
1170 return cap in self._caps
1172 def attach(self, sr_uuid, vdi_uuid):
1173 #assert self.has_cap("VDI_ATTACH")
1174 return self.vdi.attach(sr_uuid, vdi_uuid)
1176 def detach(self, sr_uuid, vdi_uuid):
1177 #assert self.has_cap("VDI_DETACH")
1178 self.vdi.detach(sr_uuid, vdi_uuid)
1180 def activate(self, sr_uuid, vdi_uuid):
1181 if self.has_cap("VDI_ACTIVATE"):
1182 return self.vdi.activate(sr_uuid, vdi_uuid)
1184 def deactivate(self, sr_uuid, vdi_uuid):
1185 if self.has_cap("VDI_DEACTIVATE"):
1186 self.vdi.deactivate(sr_uuid, vdi_uuid)
1187 #def resize(self, sr_uuid, vdi_uuid, size):
1188 # return self.vdi.resize(sr_uuid, vdi_uuid, size)
1190 def get_vdi_type(self):
1191 _type = self.vdi.vdi_type
1192 if not _type:
1193 _type = self.vdi.sr.sr_vditype
1194 if not _type:
1195 raise VDI.UnexpectedVDIType(_type, self.vdi)
1196 return _type
1198 def get_vdi_path(self):
1199 return self.vdi.path
1201 class Link(ABC):
1202 """Relink a node under a common name"""
1203 # NB. We have to provide the device node path during
1204 # VDI.attach, but currently do not allocate the tapdisk minor
1205 # before VDI.activate. Therefore those link steps where we
1206 # relink existing devices under deterministic path names.
1208 @property
1209 @abstractmethod
1210 def BASEDIR(self) -> str:
1211 pass
1213 @abstractmethod
1214 def _mklink(self, target) -> None:
1215 pass
1217 @abstractmethod
1218 def _equals(self, target) -> bool:
1219 pass
1221 def __init__(self, path):
1222 self._path = path
1224 @classmethod
1225 def from_name(cls, name):
1226 path = "%s/%s" % (cls.BASEDIR, name)
1227 return cls(path)
1229 @classmethod
1230 def from_uuid(cls, sr_uuid, vdi_uuid):
1231 name = "%s/%s" % (sr_uuid, vdi_uuid)
1232 return cls.from_name(name)
1234 def path(self):
1235 return self._path
1237 def stat(self):
1238 return os.stat(self.path())
1240 def mklink(self, target) -> None:
1242 path = self.path()
1243 util.SMlog("%s -> %s" % (self, target))
1245 mkdirs(os.path.dirname(path))
1246 try:
1247 self._mklink(target)
1248 except OSError as e:
1249 # We do unlink during teardown, but have to stay
1250 # idempotent. However, a *wrong* target should never
1251 # be seen.
1252 if e.errno != errno.EEXIST:
1253 raise
1254 assert self._equals(target), "'%s' not equal to '%s'" % (path, target)
1256 def unlink(self):
1257 try:
1258 os.unlink(self.path())
1259 except OSError as e:
1260 if e.errno != errno.ENOENT:
1261 raise
1263 @override
1264 def __str__(self) -> str:
1265 path = self.path()
1266 return "%s(%s)" % (self.__class__.__name__, path)
1268 class SymLink(Link):
1269 """Symlink some file to a common name"""
1271 @property
1272 @override
1273 def BASEDIR(self) -> str:
1274 return ''
1276 def readlink(self):
1277 return os.readlink(self.path())
1279 def symlink(self):
1280 return self.path()
1282 @override
1283 def _mklink(self, target) -> None:
1284 os.symlink(target, self.path())
1286 @override
1287 def _equals(self, target) -> bool:
1288 return self.readlink() == target
1290 class DeviceNode(Link):
1291 """Relink a block device node to a common name"""
1293 @property
1294 @override
1295 def BASEDIR(self) -> str:
1296 return ''
1298 @classmethod
1299 def _real_stat(cls, target):
1300 """stat() not on @target, but its realpath()"""
1301 _target = os.path.realpath(target)
1302 return os.stat(_target)
1304 @classmethod
1305 def is_block(cls, target):
1306 """Whether @target refers to a block device."""
1307 return S_ISBLK(cls._real_stat(target).st_mode)
1309 @override
1310 def _mklink(self, target) -> None:
1312 st = self._real_stat(target)
1313 if not S_ISBLK(st.st_mode):
1314 raise self.NotABlockDevice(target, st)
1316 # set group read for disk group as well as root
1317 os.mknod(self.path(), st.st_mode | stat.S_IRGRP, st.st_rdev)
1318 os.chown(self.path(), st.st_uid, grp.getgrnam("disk").gr_gid)
1320 @override
1321 def _equals(self, target) -> bool:
1322 target_rdev = self._real_stat(target).st_rdev
1323 return self.stat().st_rdev == target_rdev
1325 def rdev(self):
1326 st = self.stat()
1327 assert S_ISBLK(st.st_mode)
1328 return os.major(st.st_rdev), os.minor(st.st_rdev)
1330 class NotABlockDevice(Exception):
1332 def __init__(self, path, st):
1333 self.path = path
1334 self.st = st
1336 @override
1337 def __str__(self) -> str:
1338 return "%s is not a block device: %s" % (self.path, self.st)
1340 class Hybrid(Link):
1342 def __init__(self, path):
1343 VDI.Link.__init__(self, path)
1344 self._devnode = VDI.DeviceNode(path)
1345 self._symlink = VDI.SymLink(path)
1347 def rdev(self):
1348 st = self.stat()
1349 if S_ISBLK(st.st_mode):
1350 return self._devnode.rdev()
1351 raise self._devnode.NotABlockDevice(self.path(), st)
1353 @override
1354 def mklink(self, target) -> None:
1355 if self._devnode.is_block(target):
1356 self._obj = self._devnode
1357 else:
1358 self._obj = self._symlink
1359 self._obj.mklink(target)
1361 @override
1362 def _equals(self, target) -> bool:
1363 return self._obj._equals(target)
1365 class PhyLink(SymLink):
1366 @property
1367 @override
1368 def BASEDIR(self) -> str:
1369 return '/dev/sm/phy'
1370 # NB. Cannot use DeviceNodes, e.g. FileVDIs aren't bdevs.
1372 class NBDLink(SymLink):
1373 @property
1374 @override
1375 def BASEDIR(self) -> str:
1376 return '/run/blktap-control/nbd'
1378 class BackendLink(Hybrid):
1379 @property
1380 @override
1381 def BASEDIR(self) -> str:
1382 return '/dev/sm/backend'
1384 # NB. Could be SymLinks as well, but saving major,minor pairs in
1385 # Links enables neat state capturing when managing Tapdisks. Note
1386 # that we essentially have a tap-ctl list replacement here. For
1387 # now make it a 'Hybrid'. Likely to collapse into a DeviceNode as
1388 # soon as ISOs are tapdisks.
1390 @staticmethod
1391 def _tap_activate(phy_path, vdi_type, sr_uuid, options, pool_size=None):
1393 tapdisk = Tapdisk.find_by_path(phy_path)
1394 if not tapdisk: 1394 ↛ 1395line 1394 didn't jump to line 1395, because the condition on line 1394 was never true
1395 blktap = Blktap.allocate()
1396 blktap.set_pool_name(sr_uuid)
1397 if pool_size:
1398 blktap.set_pool_size(pool_size)
1400 try:
1401 tapdisk = \
1402 Tapdisk.launch_on_tap(blktap,
1403 phy_path,
1404 VDI._tap_type(vdi_type),
1405 options)
1406 except:
1407 blktap.free()
1408 raise
1409 util.SMlog("tap.activate: Launched %s" % tapdisk)
1411 else:
1412 util.SMlog("tap.activate: Found %s" % tapdisk)
1414 return tapdisk.get_devpath(), tapdisk
1416 @staticmethod
1417 def _tap_deactivate(minor):
1419 try:
1420 tapdisk = Tapdisk.from_minor(minor)
1421 except TapdiskNotRunning as e:
1422 util.SMlog("tap.deactivate: Warning, %s" % e)
1423 # NB. Should not be here unless the agent refcount
1424 # broke. Also, a clean shutdown should not have leaked
1425 # the recorded minor.
1426 else:
1427 tapdisk.shutdown()
1428 util.SMlog("tap.deactivate: Shut down %s" % tapdisk)
1430 @classmethod
1431 def tap_pause(cls, session, sr_uuid, vdi_uuid, failfast=False):
1432 """
1433 Pauses the tapdisk.
1435 session: a XAPI session
1436 sr_uuid: the UUID of the SR on which VDI lives
1437 vdi_uuid: the UUID of the VDI to pause
1438 failfast: controls whether the VDI lock should be acquired in a
1439 non-blocking manner
1440 """
1441 util.SMlog("Pause request for %s" % vdi_uuid)
1442 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1443 session.xenapi.VDI.add_to_sm_config(vdi_ref, 'paused', 'true')
1444 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1445 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 1445 ↛ 1446line 1445 didn't jump to line 1446, because the loop on line 1445 never started
1446 host_ref = key[len('host_'):]
1447 util.SMlog("Calling tap-pause on host %s" % host_ref)
1448 if not cls.call_pluginhandler(session, host_ref,
1449 sr_uuid, vdi_uuid, "pause", failfast=failfast):
1450 # Failed to pause node
1451 session.xenapi.VDI.remove_from_sm_config(vdi_ref, 'paused')
1452 return False
1453 return True
1455 @classmethod
1456 def tap_unpause(cls, session, sr_uuid, vdi_uuid, secondary=None,
1457 activate_parents=False):
1458 util.SMlog("Unpause request for %s secondary=%s" % (vdi_uuid, secondary))
1459 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1460 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1461 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 1461 ↛ 1462line 1461 didn't jump to line 1462, because the loop on line 1461 never started
1462 host_ref = key[len('host_'):]
1463 util.SMlog("Calling tap-unpause on host %s" % host_ref)
1464 if not cls.call_pluginhandler(session, host_ref,
1465 sr_uuid, vdi_uuid, "unpause", secondary, activate_parents):
1466 # Failed to unpause node
1467 return False
1468 session.xenapi.VDI.remove_from_sm_config(vdi_ref, 'paused')
1469 return True
1471 @classmethod
1472 def tap_refresh(cls, session, sr_uuid, vdi_uuid, activate_parents=False):
1473 util.SMlog("Refresh request for %s" % vdi_uuid)
1474 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1475 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1476 for key in [x for x in sm_config.keys() if x.startswith('host_')]:
1477 host_ref = key[len('host_'):]
1478 util.SMlog("Calling tap-refresh on host %s" % host_ref)
1479 if not cls.call_pluginhandler(session, host_ref,
1480 sr_uuid, vdi_uuid, "refresh", None,
1481 activate_parents=activate_parents):
1482 # Failed to refresh node
1483 return False
1484 return True
1486 @classmethod
1487 def tap_status(cls, session, vdi_uuid):
1488 """Return True if disk is attached, false if it isn't"""
1489 util.SMlog("Disk status request for %s" % vdi_uuid)
1490 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1491 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1492 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 1492 ↛ 1493line 1492 didn't jump to line 1493, because the loop on line 1492 never started
1493 return True
1494 return False
1496 @classmethod
1497 def call_pluginhandler(cls, session, host_ref, sr_uuid, vdi_uuid, action,
1498 secondary=None, activate_parents=False, failfast=False):
1499 """Optionally, activate the parent LV before unpausing"""
1500 try:
1501 args = {"sr_uuid": sr_uuid, "vdi_uuid": vdi_uuid,
1502 "failfast": str(failfast)}
1503 if secondary:
1504 args["secondary"] = secondary
1505 if activate_parents:
1506 args["activate_parents"] = "true"
1507 ret = session.xenapi.host.call_plugin(
1508 host_ref, PLUGIN_TAP_PAUSE, action,
1509 args)
1510 return ret == "True"
1511 except Exception as e:
1512 util.logException("BLKTAP2:call_pluginhandler %s" % e)
1513 return False
1515 def _add_tag(self, vdi_uuid, writable):
1516 util.SMlog("Adding tag to: %s" % vdi_uuid)
1517 attach_mode = "RO"
1518 if writable: 1518 ↛ 1520line 1518 didn't jump to line 1520, because the condition on line 1518 was never false
1519 attach_mode = "RW"
1520 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1521 host_ref = self._session.xenapi.host.get_by_uuid(util.get_this_host())
1522 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1523 attached_as = util.attached_as(sm_config)
1524 if NO_MULTIPLE_ATTACH and (attached_as == "RW" or \ 1524 ↛ 1526line 1524 didn't jump to line 1526, because the condition on line 1524 was never true
1525 (attached_as == "RO" and attach_mode == "RW")):
1526 util.SMlog("need to reset VDI %s" % vdi_uuid)
1527 if not resetvdis.reset_vdi(self._session, vdi_uuid, force=False,
1528 term_output=False, writable=writable):
1529 raise util.SMException("VDI %s not detached cleanly" % vdi_uuid)
1530 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1531 if 'relinking' in sm_config:
1532 util.SMlog("Relinking key found, back-off and retry" % sm_config)
1533 return False
1534 if 'paused' in sm_config:
1535 util.SMlog("Paused or host_ref key found [%s]" % sm_config)
1536 return False
1537 self._session.xenapi.VDI.add_to_sm_config(
1538 vdi_ref, 'activating', 'True')
1539 host_key = "host_%s" % host_ref
1540 assert host_key not in sm_config
1541 self._session.xenapi.VDI.add_to_sm_config(vdi_ref, host_key,
1542 attach_mode)
1543 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1544 if 'paused' in sm_config or 'relinking' in sm_config:
1545 util.SMlog("Found %s key, aborting" % (
1546 'paused' if 'paused' in sm_config else 'relinking'))
1547 self._session.xenapi.VDI.remove_from_sm_config(vdi_ref, host_key)
1548 self._session.xenapi.VDI.remove_from_sm_config(
1549 vdi_ref, 'activating')
1550 return False
1551 util.SMlog("Activate lock succeeded")
1552 return True
1554 def _check_tag(self, vdi_uuid):
1555 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1556 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1557 if 'paused' in sm_config:
1558 util.SMlog("Paused key found [%s]" % sm_config)
1559 return False
1560 return True
1562 def _remove_tag(self, vdi_uuid):
1563 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1564 host_ref = self._session.xenapi.host.get_by_uuid(util.get_this_host())
1565 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1566 host_key = "host_%s" % host_ref
1567 if host_key in sm_config:
1568 self._session.xenapi.VDI.remove_from_sm_config(vdi_ref, host_key)
1569 util.SMlog("Removed host key %s for %s" % (host_key, vdi_uuid))
1570 else:
1571 util.SMlog("_remove_tag: host key %s not found, ignore" % host_key)
1573 def _get_pool_config(self, pool_name):
1574 pool_info = dict()
1575 vdi_ref = self.target.vdi.sr.srcmd.params.get('vdi_ref')
1576 if not vdi_ref: 1576 ↛ 1579line 1576 didn't jump to line 1579, because the condition on line 1576 was never true
1577 # attach_from_config context: HA disks don't need to be in any
1578 # special pool
1579 return pool_info
1581 sr_ref = self.target.vdi.sr.srcmd.params.get('sr_ref')
1582 sr_config = self._session.xenapi.SR.get_other_config(sr_ref)
1583 vdi_config = self._session.xenapi.VDI.get_other_config(vdi_ref)
1584 pool_size_str = sr_config.get(POOL_SIZE_KEY)
1585 pool_name_override = vdi_config.get(POOL_NAME_KEY)
1586 if pool_name_override: 1586 ↛ 1591line 1586 didn't jump to line 1591, because the condition on line 1586 was never false
1587 pool_name = pool_name_override
1588 pool_size_override = vdi_config.get(POOL_SIZE_KEY)
1589 if pool_size_override: 1589 ↛ 1591line 1589 didn't jump to line 1591, because the condition on line 1589 was never false
1590 pool_size_str = pool_size_override
1591 pool_size = 0
1592 if pool_size_str: 1592 ↛ 1602line 1592 didn't jump to line 1602, because the condition on line 1592 was never false
1593 try:
1594 pool_size = int(pool_size_str)
1595 if pool_size < 1 or pool_size > MAX_FULL_RINGS: 1595 ↛ 1596line 1595 didn't jump to line 1596, because the condition on line 1595 was never true
1596 raise ValueError("outside of range")
1597 pool_size = NUM_PAGES_PER_RING * pool_size
1598 except ValueError:
1599 util.SMlog("Error: invalid mem-pool-size %s" % pool_size_str)
1600 pool_size = 0
1602 pool_info["mem-pool"] = pool_name
1603 if pool_size: 1603 ↛ 1606line 1603 didn't jump to line 1606, because the condition on line 1603 was never false
1604 pool_info["mem-pool-size"] = str(pool_size)
1606 return pool_info
1608 def linkNBD(self, sr_uuid, vdi_uuid):
1609 if self.tap:
1610 nbd_path = '/run/blktap-control/nbd%d.%d' % (int(self.tap.pid),
1611 int(self.tap.minor))
1612 VDI.NBDLink.from_uuid(sr_uuid, vdi_uuid).mklink(nbd_path)
1614 def attach(self, sr_uuid, vdi_uuid, writable, activate=False, caching_params={}):
1615 """Return/dev/sm/backend symlink path"""
1616 self.xenstore_data.update(self._get_pool_config(sr_uuid))
1617 if not self.target.has_cap("ATOMIC_PAUSE") or activate:
1618 util.SMlog("Attach & activate")
1619 self._attach(sr_uuid, vdi_uuid)
1620 dev_path = self._activate(sr_uuid, vdi_uuid,
1621 {"rdonly": not writable})
1622 self.BackendLink.from_uuid(sr_uuid, vdi_uuid).mklink(dev_path)
1623 self.linkNBD(sr_uuid, vdi_uuid)
1625 # Return backend/ link
1626 back_path = self.BackendLink.from_uuid(sr_uuid, vdi_uuid).path()
1627 if self.tap_wanted():
1628 # Only have NBD if we also have a tap
1629 nbd_path = "nbd:unix:{}:exportname={}".format(
1630 VDI.NBDLink.from_uuid(sr_uuid, vdi_uuid).path(),
1631 vdi_uuid)
1632 else:
1633 nbd_path = ""
1635 options = {"rdonly": not writable}
1636 options.update(caching_params)
1637 o_direct, o_direct_reason = self.get_o_direct_capability(options)
1638 struct = {'params': back_path,
1639 'params_nbd': nbd_path,
1640 'o_direct': o_direct,
1641 'o_direct_reason': o_direct_reason,
1642 'xenstore_data': self.xenstore_data}
1643 util.SMlog('result: %s' % struct)
1645 try:
1646 f = open("%s.attach_info" % back_path, 'a')
1647 f.write(xmlrpc.client.dumps((struct, ), "", True))
1648 f.close()
1649 except:
1650 pass
1652 return xmlrpc.client.dumps((struct, ), "", True)
1654 def activate(self, sr_uuid, vdi_uuid, writable, caching_params):
1655 util.SMlog("blktap2.activate")
1656 options = {"rdonly": not writable}
1657 options.update(caching_params)
1659 sr_ref = self.target.vdi.sr.srcmd.params.get('sr_ref')
1660 sr_other_config = self._session.xenapi.SR.get_other_config(sr_ref)
1661 for i in range(self.ATTACH_DETACH_RETRY_SECS): 1661 ↛ 1668line 1661 didn't jump to line 1668, because the loop on line 1661 didn't complete
1662 try:
1663 if self._activate_locked(sr_uuid, vdi_uuid, options):
1664 return
1665 except util.SRBusyException:
1666 util.SMlog("SR locked, retrying")
1667 time.sleep(1)
1668 raise util.SMException("VDI %s locked" % vdi_uuid)
1670 @locking("VDIUnavailable")
1671 def _activate_locked(self, sr_uuid, vdi_uuid, options):
1672 """Wraps target.activate and adds a tapdisk"""
1674 #util.SMlog("VDI.activate %s" % vdi_uuid)
1675 refresh = False
1676 if self.tap_wanted(): 1676 ↛ 1681line 1676 didn't jump to line 1681, because the condition on line 1676 was never false
1677 if not self._add_tag(vdi_uuid, not options["rdonly"]):
1678 return False
1679 refresh = True
1681 try:
1682 if refresh: 1682 ↛ 1693line 1682 didn't jump to line 1693, because the condition on line 1682 was never false
1683 # it is possible that while the VDI was paused some of its
1684 # attributes have changed (e.g. its size if it was inflated; or its
1685 # path if it was leaf-coalesced onto a raw LV), so refresh the
1686 # object completely
1687 params = self.target.vdi.sr.srcmd.params
1688 target = sm.VDI.from_uuid(self.target.vdi.session, vdi_uuid)
1689 target.sr.srcmd.params = params
1690 driver_info = target.sr.srcmd.driver_info
1691 self.target = self.TargetDriver(target, driver_info)
1693 util.fistpoint.activate_custom_fn( 1693 ↛ exitline 1693 didn't jump to the function exit
1694 "blktap_activate_inject_failure",
1695 lambda: util.inject_failure())
1697 # Attach the physical node
1698 if self.target.has_cap("ATOMIC_PAUSE"): 1698 ↛ 1701line 1698 didn't jump to line 1701, because the condition on line 1698 was never false
1699 self._attach(sr_uuid, vdi_uuid)
1701 vdi_type = self.target.get_vdi_type()
1703 # Take lvchange-p Lock before running
1704 # tap-ctl open
1705 # Needed to avoid race with lvchange -p which is
1706 # now taking the same lock
1707 # This is a fix for CA-155766
1708 if hasattr(self.target.vdi.sr, 'DRIVER_TYPE') and \ 1708 ↛ 1711line 1708 didn't jump to line 1711, because the condition on line 1708 was never true
1709 self.target.vdi.sr.DRIVER_TYPE == 'lvhd' and \
1710 vdi_type == vhdutil.VDI_TYPE_VHD:
1711 lock = Lock("lvchange-p", lvhdutil.NS_PREFIX_LVM + sr_uuid)
1712 lock.acquire()
1714 # When we attach a static VDI for HA, we cannot communicate with
1715 # xapi, because has not started yet. These VDIs are raw.
1716 if vdi_type != vhdutil.VDI_TYPE_RAW: 1716 ↛ 1727line 1716 didn't jump to line 1727, because the condition on line 1716 was never false
1717 session = self.target.vdi.session
1718 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1719 # pylint: disable=used-before-assignment
1720 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1721 if 'key_hash' in sm_config: 1721 ↛ 1722line 1721 didn't jump to line 1722, because the condition on line 1721 was never true
1722 key_hash = sm_config['key_hash']
1723 options['key_hash'] = key_hash
1724 options['vdi_uuid'] = vdi_uuid
1725 util.SMlog('Using key with hash {} for VDI {}'.format(key_hash, vdi_uuid))
1726 # Activate the physical node
1727 dev_path = self._activate(sr_uuid, vdi_uuid, options)
1729 if hasattr(self.target.vdi.sr, 'DRIVER_TYPE') and \ 1729 ↛ 1732line 1729 didn't jump to line 1732, because the condition on line 1729 was never true
1730 self.target.vdi.sr.DRIVER_TYPE == 'lvhd' and \
1731 self.target.get_vdi_type() == vhdutil.VDI_TYPE_VHD:
1732 lock.release()
1733 except:
1734 util.SMlog("Exception in activate/attach")
1735 if self.tap_wanted():
1736 util.fistpoint.activate_custom_fn(
1737 "blktap_activate_error_handling",
1738 lambda: time.sleep(30))
1739 while True:
1740 try:
1741 self._remove_tag(vdi_uuid)
1742 break
1743 except xmlrpc.client.ProtocolError as e:
1744 # If there's a connection error, keep trying forever.
1745 if e.errcode == http.HTTPStatus.INTERNAL_SERVER_ERROR.value:
1746 continue
1747 else:
1748 util.SMlog('failed to remove tag: %s' % e)
1749 break
1750 except Exception as e:
1751 util.SMlog('failed to remove tag: %s' % e)
1752 break
1753 raise
1754 finally:
1755 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1756 self._session.xenapi.VDI.remove_from_sm_config(
1757 vdi_ref, 'activating')
1758 util.SMlog("Removed activating flag from %s" % vdi_uuid) 1758 ↛ exitline 1758 didn't except from function '_activate_locked', because the raise on line 1753 wasn't executed
1760 # Link result to backend/
1761 self.BackendLink.from_uuid(sr_uuid, vdi_uuid).mklink(dev_path)
1762 self.linkNBD(sr_uuid, vdi_uuid)
1763 return True
1765 def _activate(self, sr_uuid, vdi_uuid, options):
1766 vdi_options = self.target.activate(sr_uuid, vdi_uuid)
1768 dev_path = self.setup_cache(sr_uuid, vdi_uuid, options)
1769 if not dev_path: 1769 ↛ 1783line 1769 didn't jump to line 1783, because the condition on line 1769 was never false
1770 phy_path = self.PhyLink.from_uuid(sr_uuid, vdi_uuid).readlink()
1771 # Maybe launch a tapdisk on the physical link
1772 if self.tap_wanted(): 1772 ↛ 1781line 1772 didn't jump to line 1781, because the condition on line 1772 was never false
1773 vdi_type = self.target.get_vdi_type()
1774 options["o_direct"] = self.get_o_direct_capability(options)[0]
1775 if vdi_options: 1775 ↛ 1777line 1775 didn't jump to line 1777, because the condition on line 1775 was never false
1776 options.update(vdi_options)
1777 dev_path, self.tap = self._tap_activate(phy_path, vdi_type,
1778 sr_uuid, options,
1779 self._get_pool_config(sr_uuid).get("mem-pool-size"))
1780 else:
1781 dev_path = phy_path # Just reuse phy
1783 return dev_path
1785 def _attach(self, sr_uuid, vdi_uuid):
1786 attach_info = xmlrpc.client.loads(self.target.attach(sr_uuid, vdi_uuid))[0][0]
1787 params = attach_info['params']
1788 xenstore_data = attach_info['xenstore_data']
1789 phy_path = util.to_plain_string(params)
1790 self.xenstore_data.update(xenstore_data)
1791 # Save it to phy/
1792 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).mklink(phy_path)
1794 def deactivate(self, sr_uuid, vdi_uuid, caching_params):
1795 util.SMlog("blktap2.deactivate")
1796 for i in range(self.ATTACH_DETACH_RETRY_SECS):
1797 try:
1798 if self._deactivate_locked(sr_uuid, vdi_uuid, caching_params):
1799 return
1800 except util.SRBusyException as e:
1801 util.SMlog("SR locked, retrying")
1802 time.sleep(1)
1803 raise util.SMException("VDI %s locked" % vdi_uuid)
1805 @locking("VDIUnavailable")
1806 def _deactivate_locked(self, sr_uuid, vdi_uuid, caching_params):
1807 """Wraps target.deactivate and removes a tapdisk"""
1809 #util.SMlog("VDI.deactivate %s" % vdi_uuid)
1810 if self.tap_wanted() and not self._check_tag(vdi_uuid):
1811 return False
1813 self._deactivate(sr_uuid, vdi_uuid, caching_params)
1814 if self.target.has_cap("ATOMIC_PAUSE"):
1815 self._detach(sr_uuid, vdi_uuid)
1816 if self.tap_wanted():
1817 self._remove_tag(vdi_uuid)
1819 return True
1821 def _resetPhylink(self, sr_uuid, vdi_uuid, path):
1822 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).mklink(path)
1824 def detach(self, sr_uuid, vdi_uuid, deactivate=False, caching_params={}):
1825 if not self.target.has_cap("ATOMIC_PAUSE") or deactivate:
1826 util.SMlog("Deactivate & detach")
1827 self._deactivate(sr_uuid, vdi_uuid, caching_params)
1828 self._detach(sr_uuid, vdi_uuid)
1829 else:
1830 pass # nothing to do
1832 def _deactivate(self, sr_uuid, vdi_uuid, caching_params):
1833 import VDI as sm
1835 # Shutdown tapdisk
1836 back_link = self.BackendLink.from_uuid(sr_uuid, vdi_uuid)
1838 if not util.pathexists(back_link.path()):
1839 util.SMlog("Backend path %s does not exist" % back_link.path())
1840 return
1842 try:
1843 attach_info_path = "%s.attach_info" % (back_link.path())
1844 os.unlink(attach_info_path)
1845 except:
1846 util.SMlog("unlink of attach_info failed")
1848 try:
1849 major, minor = back_link.rdev()
1850 except self.DeviceNode.NotABlockDevice:
1851 pass
1852 else:
1853 if major == Tapdisk.major():
1854 self._tap_deactivate(minor)
1855 self.remove_cache(sr_uuid, vdi_uuid, caching_params)
1857 # Remove the backend link
1858 back_link.unlink()
1859 VDI.NBDLink.from_uuid(sr_uuid, vdi_uuid).unlink()
1861 # Deactivate & detach the physical node
1862 if self.tap_wanted() and self.target.vdi.session is not None:
1863 # it is possible that while the VDI was paused some of its
1864 # attributes have changed (e.g. its size if it was inflated; or its
1865 # path if it was leaf-coalesced onto a raw LV), so refresh the
1866 # object completely
1867 target = sm.VDI.from_uuid(self.target.vdi.session, vdi_uuid)
1868 driver_info = target.sr.srcmd.driver_info
1869 self.target = self.TargetDriver(target, driver_info)
1871 self.target.deactivate(sr_uuid, vdi_uuid)
1873 def _detach(self, sr_uuid, vdi_uuid):
1874 self.target.detach(sr_uuid, vdi_uuid)
1876 # Remove phy/
1877 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).unlink()
1879 def _updateCacheRecord(self, session, vdi_uuid, on_boot, caching):
1880 # Remove existing VDI.sm_config fields
1881 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1882 for key in ["on_boot", "caching"]:
1883 session.xenapi.VDI.remove_from_sm_config(vdi_ref, key)
1884 if not on_boot is None:
1885 session.xenapi.VDI.add_to_sm_config(vdi_ref, 'on_boot', on_boot)
1886 if not caching is None:
1887 session.xenapi.VDI.add_to_sm_config(vdi_ref, 'caching', caching)
1889 def setup_cache(self, sr_uuid, vdi_uuid, params):
1890 if params.get(self.CONF_KEY_ALLOW_CACHING) != "true": 1890 ↛ 1893line 1890 didn't jump to line 1893, because the condition on line 1890 was never false
1891 return
1893 util.SMlog("Requested local caching")
1894 if not self.target.has_cap("SR_CACHING"):
1895 util.SMlog("Error: local caching not supported by this SR")
1896 return
1898 scratch_mode = False
1899 if params.get(self.CONF_KEY_MODE_ON_BOOT) == "reset":
1900 scratch_mode = True
1901 util.SMlog("Requested scratch mode")
1902 if not self.target.has_cap("VDI_RESET_ON_BOOT/2"):
1903 util.SMlog("Error: scratch mode not supported by this SR")
1904 return
1906 dev_path = None
1907 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR)
1908 if not local_sr_uuid:
1909 util.SMlog("ERROR: Local cache SR not specified, not enabling")
1910 return
1911 dev_path = self._setup_cache(self._session, sr_uuid, vdi_uuid,
1912 local_sr_uuid, scratch_mode, params)
1914 if dev_path:
1915 self._updateCacheRecord(self._session, self.target.vdi.uuid,
1916 params.get(self.CONF_KEY_MODE_ON_BOOT),
1917 params.get(self.CONF_KEY_ALLOW_CACHING))
1919 return dev_path
1921 def alert_no_cache(self, session, vdi_uuid, cache_sr_uuid, err):
1922 vm_uuid = None
1923 vm_label = ""
1924 try:
1925 cache_sr_ref = session.xenapi.SR.get_by_uuid(cache_sr_uuid)
1926 cache_sr_rec = session.xenapi.SR.get_record(cache_sr_ref)
1927 cache_sr_label = cache_sr_rec.get("name_label")
1929 host_ref = session.xenapi.host.get_by_uuid(util.get_this_host())
1930 host_rec = session.xenapi.host.get_record(host_ref)
1931 host_label = host_rec.get("name_label")
1933 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1934 vbds = session.xenapi.VBD.get_all_records_where( \
1935 "field \"VDI\" = \"%s\"" % vdi_ref)
1936 for vbd_rec in vbds.values():
1937 vm_ref = vbd_rec.get("VM")
1938 vm_rec = session.xenapi.VM.get_record(vm_ref)
1939 vm_uuid = vm_rec.get("uuid")
1940 vm_label = vm_rec.get("name_label")
1941 except:
1942 util.logException("alert_no_cache")
1944 alert_obj = "SR"
1945 alert_uuid = str(cache_sr_uuid)
1946 alert_str = "No space left in Local Cache SR %s" % cache_sr_uuid
1947 if vm_uuid:
1948 alert_obj = "VM"
1949 alert_uuid = vm_uuid
1950 reason = ""
1951 if err == errno.ENOSPC:
1952 reason = "because there is no space left"
1953 alert_str = "The VM \"%s\" is not using IntelliCache %s on the Local Cache SR (\"%s\") on host \"%s\"" % \
1954 (vm_label, reason, cache_sr_label, host_label)
1956 util.SMlog("Creating alert: (%s, %s, \"%s\")" % \
1957 (alert_obj, alert_uuid, alert_str))
1958 session.xenapi.message.create("No space left in local cache", "3",
1959 alert_obj, alert_uuid, alert_str)
1961 def _setup_cache(self, session, sr_uuid, vdi_uuid, local_sr_uuid,
1962 scratch_mode, options):
1963 import SR
1964 import EXTSR
1965 import NFSSR
1966 from lock import Lock
1967 from FileSR import FileVDI
1969 parent_uuid = vhdutil.getParent(self.target.vdi.path,
1970 FileVDI.extractUuid)
1971 if not parent_uuid:
1972 util.SMlog("ERROR: VDI %s has no parent, not enabling" % \
1973 self.target.vdi.uuid)
1974 return
1976 util.SMlog("Setting up cache")
1977 parent_uuid = parent_uuid.strip()
1978 shared_target = NFSSR.NFSFileVDI(self.target.vdi.sr, parent_uuid)
1980 if shared_target.parent:
1981 util.SMlog("ERROR: Parent VDI %s has parent, not enabling" %
1982 shared_target.uuid)
1983 return
1985 SR.registerSR(EXTSR.EXTSR)
1986 local_sr = SR.SR.from_uuid(session, local_sr_uuid)
1988 lock = Lock(self.LOCK_CACHE_SETUP, parent_uuid)
1989 lock.acquire()
1991 # read cache
1992 read_cache_path = "%s/%s.vhdcache" % (local_sr.path, shared_target.uuid)
1993 if util.pathexists(read_cache_path):
1994 util.SMlog("Read cache node (%s) already exists, not creating" % \
1995 read_cache_path)
1996 else:
1997 try:
1998 vhdutil.snapshot(read_cache_path, shared_target.path, False)
1999 except util.CommandException as e:
2000 util.SMlog("Error creating parent cache: %s" % e)
2001 self.alert_no_cache(session, vdi_uuid, local_sr_uuid, e.code)
2002 return None
2004 # local write node
2005 leaf_size = vhdutil.getSizeVirt(self.target.vdi.path)
2006 local_leaf_path = "%s/%s.vhdcache" % \
2007 (local_sr.path, self.target.vdi.uuid)
2008 if util.pathexists(local_leaf_path):
2009 util.SMlog("Local leaf node (%s) already exists, deleting" % \
2010 local_leaf_path)
2011 os.unlink(local_leaf_path)
2012 try:
2013 vhdutil.snapshot(local_leaf_path, read_cache_path, False,
2014 msize=leaf_size // 1024 // 1024, checkEmpty=False)
2015 except util.CommandException as e:
2016 util.SMlog("Error creating leaf cache: %s" % e)
2017 self.alert_no_cache(session, vdi_uuid, local_sr_uuid, e.code)
2018 return None
2020 local_leaf_size = vhdutil.getSizeVirt(local_leaf_path)
2021 if leaf_size > local_leaf_size:
2022 util.SMlog("Leaf size %d > local leaf cache size %d, resizing" %
2023 (leaf_size, local_leaf_size))
2024 vhdutil.setSizeVirtFast(local_leaf_path, leaf_size)
2026 vdi_type = self.target.get_vdi_type()
2028 prt_tapdisk = Tapdisk.find_by_path(read_cache_path)
2029 if not prt_tapdisk:
2030 parent_options = copy.deepcopy(options)
2031 parent_options["rdonly"] = False
2032 parent_options["lcache"] = True
2034 blktap = Blktap.allocate()
2035 try:
2036 blktap.set_pool_name("lcache-parent-pool-%s" % blktap.minor)
2037 # no need to change pool_size since each parent tapdisk is in
2038 # its own pool
2039 prt_tapdisk = \
2040 Tapdisk.launch_on_tap(blktap, read_cache_path,
2041 'vhd', parent_options)
2042 except:
2043 blktap.free()
2044 raise
2046 secondary = "%s:%s" % (self.target.get_vdi_type(),
2047 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).readlink())
2049 util.SMlog("Parent tapdisk: %s" % prt_tapdisk)
2050 leaf_tapdisk = Tapdisk.find_by_path(local_leaf_path)
2051 if not leaf_tapdisk:
2052 blktap = Blktap.allocate()
2053 child_options = copy.deepcopy(options)
2054 child_options["rdonly"] = False
2055 child_options["lcache"] = False
2056 child_options["existing_prt"] = prt_tapdisk.minor
2057 child_options["secondary"] = secondary
2058 child_options["standby"] = scratch_mode
2059 try:
2060 leaf_tapdisk = \
2061 Tapdisk.launch_on_tap(blktap, local_leaf_path,
2062 'vhd', child_options)
2063 except:
2064 blktap.free()
2065 raise
2067 lock.release()
2069 util.SMlog("Local read cache: %s, local leaf: %s" % \
2070 (read_cache_path, local_leaf_path))
2072 self.tap = leaf_tapdisk
2073 return leaf_tapdisk.get_devpath()
2075 def remove_cache(self, sr_uuid, vdi_uuid, params):
2076 if not self.target.has_cap("SR_CACHING"):
2077 return
2079 caching = params.get(self.CONF_KEY_ALLOW_CACHING) == "true"
2081 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR)
2082 if caching and not local_sr_uuid:
2083 util.SMlog("ERROR: Local cache SR not specified, ignore")
2084 return
2086 if caching:
2087 self._remove_cache(self._session, local_sr_uuid)
2089 if self._session is not None:
2090 self._updateCacheRecord(self._session, self.target.vdi.uuid, None, None)
2092 def _is_tapdisk_in_use(self, minor):
2093 retVal, links, sockets = util.findRunningProcessOrOpenFile("tapdisk")
2094 if not retVal:
2095 # err on the side of caution
2096 return True
2098 for link in links:
2099 if link.find("tapdev%d" % minor) != -1:
2100 return True
2102 socket_re = re.compile(r'^/.*/nbd\d+\.%d' % minor)
2103 for s in sockets:
2104 if socket_re.match(s):
2105 return True
2107 return False
2109 def _remove_cache(self, session, local_sr_uuid):
2110 import SR
2111 import EXTSR
2112 import NFSSR
2113 from lock import Lock
2114 from FileSR import FileVDI
2116 parent_uuid = vhdutil.getParent(self.target.vdi.path,
2117 FileVDI.extractUuid)
2118 if not parent_uuid:
2119 util.SMlog("ERROR: No parent for VDI %s, ignore" % \
2120 self.target.vdi.uuid)
2121 return
2123 util.SMlog("Tearing down the cache")
2125 parent_uuid = parent_uuid.strip()
2126 shared_target = NFSSR.NFSFileVDI(self.target.vdi.sr, parent_uuid)
2128 SR.registerSR(EXTSR.EXTSR)
2129 local_sr = SR.SR.from_uuid(session, local_sr_uuid)
2131 lock = Lock(self.LOCK_CACHE_SETUP, parent_uuid)
2132 lock.acquire()
2134 # local write node
2135 local_leaf_path = "%s/%s.vhdcache" % \
2136 (local_sr.path, self.target.vdi.uuid)
2137 if util.pathexists(local_leaf_path):
2138 util.SMlog("Deleting local leaf node %s" % local_leaf_path)
2139 os.unlink(local_leaf_path)
2141 read_cache_path = "%s/%s.vhdcache" % (local_sr.path, shared_target.uuid)
2142 prt_tapdisk = Tapdisk.find_by_path(read_cache_path)
2143 if not prt_tapdisk:
2144 util.SMlog("Parent tapdisk not found")
2145 elif not self._is_tapdisk_in_use(prt_tapdisk.minor):
2146 util.SMlog("Parent tapdisk not in use: shutting down %s" % \
2147 read_cache_path)
2148 try:
2149 prt_tapdisk.shutdown()
2150 except:
2151 util.logException("shutting down parent tapdisk")
2152 else:
2153 util.SMlog("Parent tapdisk still in use: %s" % read_cache_path)
2154 # the parent cache files are removed during the local SR's background
2155 # GC run
2157 lock.release()
2159PythonKeyError = KeyError
2162class UEventHandler(object):
2164 def __init__(self):
2165 self._action = None
2167 class KeyError(PythonKeyError):
2168 def __init__(self, args):
2169 super().__init__(args)
2170 self.key = args[0]
2172 @override
2173 def __str__(self) -> str:
2174 return \
2175 "Key '%s' missing in environment. " % self.key + \
2176 "Not called in udev context?"
2178 @classmethod
2179 def getenv(cls, key):
2180 try:
2181 return os.environ[key]
2182 except KeyError as e:
2183 raise cls.KeyError(e.args[0])
2185 def get_action(self):
2186 if not self._action:
2187 self._action = self.getenv('ACTION')
2188 return self._action
2190 class UnhandledEvent(Exception):
2192 def __init__(self, event, handler):
2193 self.event = event
2194 self.handler = handler
2196 @override
2197 def __str__(self) -> str:
2198 return "Uevent '%s' not handled by %s" % \
2199 (self.event, self.handler.__class__.__name__)
2201 ACTIONS: Dict[str, Callable] = {}
2203 def run(self):
2205 action = self.get_action()
2206 try:
2207 fn = self.ACTIONS[action]
2208 except KeyError:
2209 raise self.UnhandledEvent(action, self)
2211 return fn(self)
2213 @override
2214 def __str__(self) -> str:
2215 try:
2216 action = self.get_action()
2217 except:
2218 action = None
2219 return "%s[%s]" % (self.__class__.__name__, action)
2222class __BlktapControl(ClassDevice):
2223 @property
2224 @override
2225 def SYSFS_CLASSTYPE(self) -> str:
2226 return 'misc'
2228 def __init__(self):
2229 ClassDevice.__init__(self)
2230 self._default_pool = None
2232 @override
2233 def sysfs_devname(self) -> str:
2234 return "blktap!control"
2236 class DefaultPool(Attribute):
2237 @property
2238 @override
2239 def SYSFS_NODENAME(self) -> str:
2240 return 'default_pool'
2242 def get_default_pool_attr(self):
2243 if not self._default_pool:
2244 self._default_pool = self.DefaultPool.from_kobject(self)
2245 return self._default_pool
2247 def get_default_pool_name(self):
2248 return self.get_default_pool_attr().readline()
2250 def set_default_pool_name(self, name):
2251 self.get_default_pool_attr().writeline(name)
2253 def get_default_pool(self):
2254 return BlktapControl.get_pool(self.get_default_pool_name())
2256 def set_default_pool(self, pool):
2257 self.set_default_pool_name(pool.name)
2259 class NoSuchPool(Exception):
2260 def __init__(self, name):
2261 self.name = name
2263 @override
2264 def __str__(self) -> str:
2265 return "No such pool: {}".format(self.name)
2267 def get_pool(self, name):
2268 path = "%s/pools/%s" % (self.sysfs_path(), name)
2270 if not os.path.isdir(path):
2271 raise self.NoSuchPool(name)
2273 return PagePool(path)
2275BlktapControl = __BlktapControl()
2278class PagePool(KObject):
2279 @property
2280 @override
2281 def SYSFS_CLASSTYPE(self) -> str:
2282 return ''
2284 def __init__(self, path):
2285 self.path = path
2286 self._size = None
2288 @override
2289 def sysfs_devname(self) -> str:
2290 return ''
2292 def sysfs_path(self):
2293 return self.path
2295 class Size(Attribute):
2296 @property
2297 @override
2298 def SYSFS_NODENAME(self) -> str:
2299 return 'size'
2301 def get_size_attr(self):
2302 if not self._size:
2303 self._size = self.Size.from_kobject(self)
2304 return self._size
2306 def set_size(self, pages):
2307 pages = str(pages)
2308 self.get_size_attr().writeline(pages)
2310 def get_size(self):
2311 pages = self.get_size_attr().readline()
2312 return int(pages)
2315class BusDevice(KObject):
2316 @property
2317 @abstractmethod
2318 def SYSFS_BUSTYPE(self) -> str:
2319 pass
2321 @classmethod
2322 def sysfs_bus_path(cls):
2323 return "/sys/bus/%s" % cls.SYSFS_BUSTYPE
2325 def sysfs_path(self):
2326 path = "%s/devices/%s" % (self.sysfs_bus_path(),
2327 self.sysfs_devname())
2329 return path
2332class XenbusDevice(BusDevice):
2333 """Xenbus device, in XS and sysfs"""
2335 XBT_NIL = ""
2337 @property
2338 @abstractmethod
2339 def XENBUS_DEVTYPE(self) -> str:
2340 pass
2342 def __init__(self, domid, devid):
2343 self.domid = int(domid)
2344 self.devid = int(devid)
2345 self._xbt = XenbusDevice.XBT_NIL
2347 import xen.lowlevel.xs # pylint: disable=import-error
2348 self.xs = xen.lowlevel.xs.xs()
2350 def xs_path(self, key=None):
2351 path = "backend/%s/%d/%d" % (self.XENBUS_DEVTYPE,
2352 self.domid,
2353 self.devid)
2354 if key is not None:
2355 path = "%s/%s" % (path, key)
2357 return path
2359 def _log(self, prio, msg):
2360 syslog(prio, msg)
2362 def info(self, msg):
2363 self._log(_syslog.LOG_INFO, msg)
2365 def warn(self, msg):
2366 self._log(_syslog.LOG_WARNING, "WARNING: " + msg)
2368 def _xs_read_path(self, path):
2369 val = self.xs.read(self._xbt, path)
2370 #self.info("read %s = '%s'" % (path, val))
2371 return val
2373 def _xs_write_path(self, path, val):
2374 self.xs.write(self._xbt, path, val)
2375 self.info("wrote %s = '%s'" % (path, val))
2377 def _xs_rm_path(self, path):
2378 self.xs.rm(self._xbt, path)
2379 self.info("removed %s" % path)
2381 def read(self, key):
2382 return self._xs_read_path(self.xs_path(key))
2384 def has_xs_key(self, key):
2385 return self.read(key) is not None
2387 def write(self, key, val):
2388 self._xs_write_path(self.xs_path(key), val)
2390 def rm(self, key):
2391 self._xs_rm_path(self.xs_path(key))
2393 def exists(self):
2394 return self.has_xs_key(None)
2396 def begin(self):
2397 assert(self._xbt == XenbusDevice.XBT_NIL)
2398 self._xbt = self.xs.transaction_start()
2400 def commit(self):
2401 ok = self.xs.transaction_end(self._xbt, 0)
2402 self._xbt = XenbusDevice.XBT_NIL
2403 return ok
2405 def abort(self):
2406 ok = self.xs.transaction_end(self._xbt, 1)
2407 assert(ok == True)
2408 self._xbt = XenbusDevice.XBT_NIL
2410 def create_physical_device(self):
2411 """The standard protocol is: toolstack writes 'params', linux hotplug
2412 script translates this into physical-device=%x:%x"""
2413 if self.has_xs_key("physical-device"):
2414 return
2415 try:
2416 params = self.read("params")
2417 frontend = self.read("frontend")
2418 is_cdrom = self._xs_read_path("%s/device-type") == "cdrom"
2419 # We don't have PV drivers for CDROM devices, so we prevent blkback
2420 # from opening the physical-device
2421 if not(is_cdrom):
2422 major_minor = os.stat(params).st_rdev
2423 major, minor = divmod(major_minor, 256)
2424 self.write("physical-device", "%x:%x" % (major, minor))
2425 except:
2426 util.logException("BLKTAP2:create_physical_device")
2428 def signal_hotplug(self, online=True):
2429 xapi_path = "/xapi/%d/hotplug/%s/%d/hotplug" % (self.domid,
2430 self.XENBUS_DEVTYPE,
2431 self.devid)
2432 upstream_path = self.xs_path("hotplug-status")
2433 if online:
2434 self._xs_write_path(xapi_path, "online")
2435 self._xs_write_path(upstream_path, "connected")
2436 else:
2437 self._xs_rm_path(xapi_path)
2438 self._xs_rm_path(upstream_path)
2440 @override
2441 def sysfs_devname(self) -> str:
2442 return "%s-%d-%d" % (self.XENBUS_DEVTYPE,
2443 self.domid, self.devid)
2445 @override
2446 def __str__(self) -> str:
2447 return self.sysfs_devname()
2449 @classmethod
2450 def find(cls):
2451 pattern = "/sys/bus/%s/devices/%s*" % (cls.SYSFS_BUSTYPE,
2452 cls.XENBUS_DEVTYPE)
2453 for path in glob.glob(pattern):
2455 name = os.path.basename(path)
2456 (_type, domid, devid) = name.split('-')
2458 yield cls(domid, devid)
2461class XenBackendDevice(XenbusDevice):
2462 """Xenbus backend device"""
2464 @property
2465 @override
2466 def SYSFS_BUSTYPE(self) -> str:
2467 return 'xen-backend'
2469 @classmethod
2470 def from_xs_path(cls, _path):
2471 (_backend, _type, domid, devid) = _path.split('/')
2473 assert _backend == 'backend'
2474 assert _type == cls.XENBUS_DEVTYPE
2476 domid = int(domid)
2477 devid = int(devid)
2479 return cls(domid, devid)
2482class Blkback(XenBackendDevice):
2483 """A blkback VBD"""
2485 @property
2486 @override
2487 def XENBUS_DEVTYPE(self) -> str:
2488 return 'vbd'
2490 def __init__(self, domid, devid):
2491 XenBackendDevice.__init__(self, domid, devid)
2492 self._phy = None
2493 self._vdi_uuid = None
2494 self._q_state = None
2495 self._q_events = None
2497 class XenstoreValueError(Exception, metaclass=ABCMeta):
2498 @property
2499 @abstractmethod
2500 def KEY(self) -> str:
2501 pass
2503 def __init__(self, vbd, _str):
2504 self.vbd = vbd
2505 self.str = _str
2507 @override
2508 def __str__(self) -> str:
2509 return "Backend %s " % self.vbd + \
2510 "has %s = %s" % (self.KEY, self.str)
2512 class PhysicalDeviceError(XenstoreValueError):
2513 @property
2514 @override
2515 def KEY(self) -> str:
2516 return 'physical-device'
2518 class PhysicalDevice(object):
2520 def __init__(self, major, minor):
2521 self.major = int(major)
2522 self.minor = int(minor)
2524 @classmethod
2525 def from_xbdev(cls, xbdev):
2527 phy = xbdev.read("physical-device")
2529 try:
2530 major, minor = phy.split(':')
2531 major = int(major, 0x10)
2532 minor = int(minor, 0x10)
2533 except Exception as e:
2534 raise xbdev.PhysicalDeviceError(xbdev, phy)
2536 return cls(major, minor)
2538 def makedev(self):
2539 return os.makedev(self.major, self.minor)
2541 def is_tap(self):
2542 return self.major == Tapdisk.major()
2544 @override
2545 def __str__(self) -> str:
2546 return "%s:%s" % (self.major, self.minor)
2548 @override
2549 def __eq__(self, other) -> bool:
2550 return \
2551 self.major == other.major and \
2552 self.minor == other.minor
2554 def get_physical_device(self):
2555 if not self._phy:
2556 self._phy = self.PhysicalDevice.from_xbdev(self)
2557 return self._phy
2559 class QueueEvents(Attribute):
2560 """Blkback sysfs node to select queue-state event
2561 notifications emitted."""
2563 @property
2564 @override
2565 def SYSFS_NODENAME(self) -> str:
2566 return 'queue_events'
2568 QUEUE_RUNNING = (1 << 0)
2569 QUEUE_PAUSE_DONE = (1 << 1)
2570 QUEUE_SHUTDOWN_DONE = (1 << 2)
2571 QUEUE_PAUSE_REQUEST = (1 << 3)
2572 QUEUE_SHUTDOWN_REQUEST = (1 << 4)
2574 def get_mask(self):
2575 return int(self.readline(), 0x10)
2577 def set_mask(self, mask):
2578 self.writeline("0x%x" % mask)
2580 def get_queue_events(self):
2581 if not self._q_events:
2582 self._q_events = self.QueueEvents.from_kobject(self)
2583 return self._q_events
2585 def get_vdi_uuid(self):
2586 if not self._vdi_uuid:
2587 self._vdi_uuid = self.read("sm-data/vdi-uuid")
2588 return self._vdi_uuid
2590 def pause_requested(self):
2591 return self.has_xs_key("pause")
2593 def shutdown_requested(self):
2594 return self.has_xs_key("shutdown-request")
2596 def shutdown_done(self):
2597 return self.has_xs_key("shutdown-done")
2599 def running(self):
2600 return self.has_xs_key('queue-0/kthread-pid')
2602 @classmethod
2603 def find_by_physical_device(cls, phy):
2604 for dev in cls.find():
2605 try:
2606 _phy = dev.get_physical_device()
2607 except cls.PhysicalDeviceError:
2608 continue
2610 if _phy == phy:
2611 yield dev
2613 @classmethod
2614 def find_by_tap_minor(cls, minor):
2615 phy = cls.PhysicalDevice(Tapdisk.major(), minor)
2616 return cls.find_by_physical_device(phy)
2618 @classmethod
2619 def find_by_tap(cls, tapdisk):
2620 return cls.find_by_tap_minor(tapdisk.minor)
2622 def has_tap(self):
2624 if not self.can_tap():
2625 return False
2627 phy = self.get_physical_device()
2628 if phy:
2629 return phy.is_tap()
2631 return False
2633 def is_bare_hvm(self):
2634 """File VDIs for bare HVM. These are directly accessible by Qemu."""
2635 try:
2636 self.get_physical_device()
2638 except self.PhysicalDeviceError as e:
2639 vdi_type = self.read("type")
2641 self.info("HVM VDI: type=%s" % vdi_type)
2643 if e.str is not None or vdi_type != 'file':
2644 raise
2646 return True
2648 return False
2650 def can_tap(self):
2651 return not self.is_bare_hvm()
2654class BlkbackEventHandler(UEventHandler):
2656 LOG_FACILITY = _syslog.LOG_DAEMON
2658 def __init__(self, ident=None, action=None):
2659 if not ident:
2660 ident = self.__class__.__name__
2662 self.ident = ident
2663 self._vbd = None
2664 self._tapdisk = None
2666 UEventHandler.__init__(self)
2668 @override
2669 def run(self) -> None:
2671 self.xs_path = self.getenv('XENBUS_PATH')
2672 openlog(str(self), 0, self.LOG_FACILITY)
2674 UEventHandler.run(self)
2676 @override
2677 def __str__(self) -> str:
2679 try:
2680 path = self.xs_path
2681 except:
2682 path = None
2684 try:
2685 action = self.get_action()
2686 except:
2687 action = None
2689 return "%s[%s](%s)" % (self.ident, action, path)
2691 def _log(self, prio, msg):
2692 syslog(prio, msg)
2693 util.SMlog("%s: " % self + msg)
2695 def info(self, msg):
2696 self._log(_syslog.LOG_INFO, msg)
2698 def warn(self, msg):
2699 self._log(_syslog.LOG_WARNING, "WARNING: " + msg)
2701 def error(self, msg):
2702 self._log(_syslog.LOG_ERR, "ERROR: " + msg)
2704 def get_vbd(self):
2705 if not self._vbd:
2706 self._vbd = Blkback.from_xs_path(self.xs_path)
2707 return self._vbd
2709 def get_tapdisk(self):
2710 if not self._tapdisk:
2711 minor = self.get_vbd().get_physical_device().minor
2712 self._tapdisk = Tapdisk.from_minor(minor)
2713 return self._tapdisk
2714 #
2715 # Events
2716 #
2718 def __add(self):
2719 vbd = self.get_vbd()
2720 # Manage blkback transitions
2721 # self._manage_vbd()
2723 vbd.create_physical_device()
2725 vbd.signal_hotplug()
2727 @retried(backoff=.5, limit=10)
2728 def add(self):
2729 try:
2730 self.__add()
2731 except Attribute.NoSuchAttribute as e:
2732 #
2733 # FIXME: KOBJ_ADD is racing backend.probe, which
2734 # registers device attributes. So poll a little.
2735 #
2736 self.warn("%s, still trying." % e)
2737 raise RetryLoop.TransientFailure(e)
2739 def __change(self):
2740 vbd = self.get_vbd()
2742 # 1. Pause or resume tapdisk (if there is one)
2744 if vbd.has_tap():
2745 pass
2746 #self._pause_update_tap()
2748 # 2. Signal Xapi.VBD.pause/resume completion
2750 self._signal_xapi()
2752 def change(self):
2753 vbd = self.get_vbd()
2755 # NB. Beware of spurious change events between shutdown
2756 # completion and device removal. Also, Xapi.VM.migrate will
2757 # hammer a couple extra shutdown-requests into the source VBD.
2759 while True:
2760 vbd.begin()
2762 if not vbd.exists() or \
2763 vbd.shutdown_done():
2764 break
2766 self.__change()
2768 if vbd.commit():
2769 return
2771 vbd.abort()
2772 self.info("spurious uevent, ignored.")
2774 def remove(self):
2775 vbd = self.get_vbd()
2777 vbd.signal_hotplug(False)
2779 ACTIONS = {'add': add,
2780 'change': change,
2781 'remove': remove}
2782 #
2783 # VDI.pause
2784 #
2786 def _tap_should_pause(self):
2787 """Enumerate all VBDs on our tapdisk. Returns true iff any was
2788 paused"""
2790 tapdisk = self.get_tapdisk()
2791 TapState = Tapdisk.PauseState
2793 PAUSED = 'P'
2794 RUNNING = 'R'
2795 PAUSED_SHUTDOWN = 'P,S'
2796 # NB. Shutdown/paused is special. We know it's not going
2797 # to restart again, so it's a RUNNING. Still better than
2798 # backtracking a removed device during Vbd.unplug completion.
2800 next = TapState.RUNNING
2801 vbds = {}
2803 for vbd in Blkback.find_by_tap(tapdisk):
2804 name = str(vbd)
2806 pausing = vbd.pause_requested()
2807 closing = vbd.shutdown_requested()
2808 running = vbd.running()
2810 if pausing:
2811 if closing and not running:
2812 vbds[name] = PAUSED_SHUTDOWN
2813 else:
2814 vbds[name] = PAUSED
2815 next = TapState.PAUSED
2817 else:
2818 vbds[name] = RUNNING
2820 self.info("tapdev%d (%s): %s -> %s"
2821 % (tapdisk.minor, tapdisk.pause_state(),
2822 vbds, next))
2824 return next == TapState.PAUSED
2826 def _pause_update_tap(self):
2827 vbd = self.get_vbd()
2829 if self._tap_should_pause():
2830 self._pause_tap()
2831 else:
2832 self._resume_tap()
2834 def _pause_tap(self):
2835 tapdisk = self.get_tapdisk()
2837 if not tapdisk.is_paused():
2838 self.info("pausing %s" % tapdisk)
2839 tapdisk.pause()
2841 def _resume_tap(self):
2842 tapdisk = self.get_tapdisk()
2844 # NB. Raw VDI snapshots. Refresh the physical path and
2845 # type while resuming.
2846 vbd = self.get_vbd()
2847 vdi_uuid = vbd.get_vdi_uuid()
2849 if tapdisk.is_paused():
2850 self.info("loading vdi uuid=%s" % vdi_uuid)
2851 vdi = VDI.from_cli(vdi_uuid)
2852 _type = vdi.get_tap_type()
2853 path = vdi.get_phy_path()
2854 self.info("resuming %s on %s:%s" % (tapdisk, _type, path))
2855 tapdisk.unpause(_type, path)
2856 #
2857 # VBD.pause/shutdown
2858 #
2860 def _manage_vbd(self):
2861 vbd = self.get_vbd()
2862 # NB. Hook into VBD state transitions.
2864 events = vbd.get_queue_events()
2866 mask = 0
2867 mask |= events.QUEUE_PAUSE_DONE # pause/unpause
2868 mask |= events.QUEUE_SHUTDOWN_DONE # shutdown
2869 # TODO: mask |= events.QUEUE_SHUTDOWN_REQUEST, for shutdown=force
2870 # TODO: mask |= events.QUEUE_RUNNING, for ionice updates etc
2872 events.set_mask(mask)
2873 self.info("wrote %s = %#02x" % (events.path, mask))
2875 def _signal_xapi(self):
2876 vbd = self.get_vbd()
2878 pausing = vbd.pause_requested()
2879 closing = vbd.shutdown_requested()
2880 running = vbd.running()
2882 handled = 0
2884 if pausing and not running:
2885 if 'pause-done' not in vbd:
2886 vbd.write('pause-done', '')
2887 handled += 1
2889 if not pausing:
2890 if 'pause-done' in vbd:
2891 vbd.rm('pause-done')
2892 handled += 1
2894 if closing and not running:
2895 if 'shutdown-done' not in vbd:
2896 vbd.write('shutdown-done', '')
2897 handled += 1
2899 if handled > 1:
2900 self.warn("handled %d events, " % handled +
2901 "pausing=%s closing=%s running=%s" % \
2902 (pausing, closing, running))
2904if __name__ == '__main__': 2904 ↛ 2906line 2904 didn't jump to line 2906, because the condition on line 2904 was never true
2906 import sys
2907 prog = os.path.basename(sys.argv[0])
2909 #
2910 # Simple CLI interface for manual operation
2911 #
2912 # tap.* level calls go down to local Tapdisk()s (by physical path)
2913 # vdi.* level calls run the plugin calls across host boundaries.
2914 #
2916 def usage(stream):
2917 print("usage: %s tap.{list|major}" % prog, file=stream)
2918 print(" %s tap.{launch|find|get|pause|" % prog + \
2919 "unpause|shutdown|stats} {[<tt>:]<path>} | [minor=]<int> | .. }", file=stream)
2920 print(" %s vbd.uevent" % prog, file=stream)
2922 try:
2923 cmd = sys.argv[1]
2924 except IndexError:
2925 usage(sys.stderr)
2926 sys.exit(1)
2928 try:
2929 _class, method = cmd.split('.')
2930 except:
2931 usage(sys.stderr)
2932 sys.exit(1)
2934 #
2935 # Local Tapdisks
2936 #
2938 if cmd == 'tap.major':
2940 print("%d" % Tapdisk.major())
2942 elif cmd == 'tap.launch':
2944 tapdisk = Tapdisk.launch_from_arg(sys.argv[2])
2945 print("Launched %s" % tapdisk, file=sys.stderr)
2947 elif _class == 'tap':
2949 attrs: Dict[str, Any] = {}
2950 for item in sys.argv[2:]:
2951 try:
2952 key, val = item.split('=')
2953 attrs[key] = val
2954 continue
2955 except ValueError:
2956 pass
2958 try:
2959 attrs['minor'] = int(item)
2960 continue
2961 except ValueError:
2962 pass
2964 try:
2965 arg = Tapdisk.Arg.parse(item)
2966 attrs['_type'] = arg.type
2967 attrs['path'] = arg.path
2968 continue
2969 except Tapdisk.Arg.InvalidArgument:
2970 pass
2972 attrs['path'] = item
2974 if cmd == 'tap.list':
2976 for tapdisk in Tapdisk.list( ** attrs):
2977 blktap = tapdisk.get_blktap()
2978 print(tapdisk, end=' ')
2979 print("%s: task=%s pool=%s" % \
2980 (blktap,
2981 blktap.get_task_pid(),
2982 blktap.get_pool_name()))
2984 elif cmd == 'tap.vbds':
2985 # Find all Blkback instances for a given tapdisk
2987 for tapdisk in Tapdisk.list( ** attrs):
2988 print("%s:" % tapdisk, end=' ')
2989 for vbd in Blkback.find_by_tap(tapdisk):
2990 print(vbd, end=' ')
2991 print()
2993 else:
2995 if not attrs:
2996 usage(sys.stderr)
2997 sys.exit(1)
2999 try:
3000 tapdisk = Tapdisk.get( ** attrs)
3001 except TypeError:
3002 usage(sys.stderr)
3003 sys.exit(1)
3005 if cmd == 'tap.shutdown':
3006 # Shutdown a running tapdisk, or raise
3007 tapdisk.shutdown()
3008 print("Shut down %s" % tapdisk, file=sys.stderr)
3010 elif cmd == 'tap.pause':
3011 # Pause an unpaused tapdisk, or raise
3012 tapdisk.pause()
3013 print("Paused %s" % tapdisk, file=sys.stderr)
3015 elif cmd == 'tap.unpause':
3016 # Unpause a paused tapdisk, or raise
3017 tapdisk.unpause()
3018 print("Unpaused %s" % tapdisk, file=sys.stderr)
3020 elif cmd == 'tap.stats':
3021 # Gather tapdisk status
3022 stats = tapdisk.stats()
3023 print("%s:" % tapdisk)
3024 print(json.dumps(stats, indent=True))
3026 else:
3027 usage(sys.stderr)
3028 sys.exit(1)
3030 elif cmd == 'vbd.uevent':
3032 hnd = BlkbackEventHandler(cmd)
3034 if not sys.stdin.isatty():
3035 try:
3036 hnd.run()
3037 except Exception as e:
3038 hnd.error("Unhandled Exception: %s" % e)
3040 import traceback
3041 _type, value, tb = sys.exc_info()
3042 trace = traceback.format_exception(_type, value, tb)
3043 for entry in trace:
3044 for line in entry.rstrip().split('\n'):
3045 util.SMlog(line)
3046 else:
3047 hnd.run()
3049 elif cmd == 'vbd.list':
3051 for vbd in Blkback.find():
3052 print(vbd, \
3053 "physical-device=%s" % vbd.get_physical_device(), \
3054 "pause=%s" % vbd.pause_requested())
3056 else:
3057 usage(sys.stderr)
3058 sys.exit(1)