Coverage for drivers/blktap2.py : 48%
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(), ...
47import resetvdis
48import vhdutil
49import lvhdutil
51import VDI as sm
53# For RRDD Plugin Registration
54from xmlrpc.client import ServerProxy, Transport
55from socket import socket, AF_UNIX, SOCK_STREAM
57try:
58 from linstorvolumemanager import log_drbd_openers
59 LINSTOR_AVAILABLE = True
60except ImportError:
61 LINSTOR_AVAILABLE = False
63PLUGIN_TAP_PAUSE = "tapdisk-pause"
65SOCKPATH = "/var/xapi/xcp-rrdd"
67NUM_PAGES_PER_RING = 32 * 11
68MAX_FULL_RINGS = 8
69POOL_NAME_KEY = "mem-pool"
70POOL_SIZE_KEY = "mem-pool-size-rings"
72ENABLE_MULTIPLE_ATTACH = "/etc/xensource/allow_multiple_vdi_attach"
73NO_MULTIPLE_ATTACH = not (os.path.exists(ENABLE_MULTIPLE_ATTACH))
76def locking(excType, override=True):
77 def locking2(op):
78 def wrapper(self, *args):
79 self.lock.acquire()
80 try:
81 try:
82 ret = op(self, * args)
83 except (util.CommandException, util.SMException, XenAPI.Failure) as e: 83 ↛ 93line 83 didn't jump to line 93
84 util.logException("BLKTAP2:%s" % op)
85 msg = str(e)
86 if isinstance(e, util.CommandException): 86 ↛ 87line 86 didn't jump to line 87, because the condition on line 86 was never true
87 msg = "Command %s failed (%s): %s" % \
88 (e.cmd, e.code, e.reason)
89 if override: 89 ↛ 92line 89 didn't jump to line 92, because the condition on line 89 was never false
90 raise xs_errors.XenError(excType, opterr=msg)
91 else:
92 raise
93 except:
94 util.logException("BLKTAP2:%s" % op)
95 raise
96 finally:
97 self.lock.release()
98 return ret
99 return wrapper
100 return locking2
103class RetryLoop(object):
105 def __init__(self, backoff, limit):
106 self.backoff = backoff
107 self.limit = limit
109 def __call__(self, f):
111 def loop(*__t, **__d):
112 attempt = 0
114 while True:
115 attempt += 1
117 try:
118 return f( * __t, ** __d)
120 except self.TransientFailure as e:
121 e = e.exception
123 if attempt >= self.limit: 123 ↛ 124line 123 didn't jump to line 124, because the condition on line 123 was never true
124 raise e
126 time.sleep(self.backoff)
128 return loop
130 class TransientFailure(Exception):
131 def __init__(self, exception):
132 self.exception = exception
135def retried(**args):
136 return RetryLoop( ** args)
139class TapCtl(object):
140 """Tapdisk IPC utility calls."""
142 PATH = "/usr/sbin/tap-ctl"
144 def __init__(self, cmd, p):
145 self.cmd = cmd
146 self._p = p
147 self.stdout = p.stdout
149 class CommandFailure(Exception):
150 """TapCtl cmd failure."""
152 def __init__(self, cmd, **info):
153 self.cmd = cmd
154 self.info = info
156 @override
157 def __str__(self) -> str:
158 items = self.info.items()
159 info = ", ".join("%s=%s" % item
160 for item in items)
161 return "%s failed: %s" % (self.cmd, info)
163 # Trying to get a non-existent attribute throws an AttributeError
164 # exception
165 def __getattr__(self, key):
166 if key in self.info: 166 ↛ 168line 166 didn't jump to line 168, because the condition on line 166 was never false
167 return self.info[key]
168 return object.__getattribute__(self, key)
170 @property
171 def has_status(self):
172 return 'status' in self.info
174 @property
175 def has_signal(self):
176 return 'signal' in self.info
178 # Retrieves the error code returned by the command. If the error code
179 # was not supplied at object-construction time, zero is returned.
180 def get_error_code(self):
181 key = 'status'
182 if key in self.info: 182 ↛ 185line 182 didn't jump to line 185, because the condition on line 182 was never false
183 return self.info[key]
184 else:
185 return 0
187 @classmethod
188 def __mkcmd_real(cls, args):
189 return [cls.PATH] + [str(x) for x in args]
191 __next_mkcmd = __mkcmd_real
193 @classmethod
194 def _mkcmd(cls, args):
196 __next_mkcmd = cls.__next_mkcmd
197 cls.__next_mkcmd = cls.__mkcmd_real
199 return __next_mkcmd(args)
201 @classmethod
202 def _call(cls, args, quiet=False, input=None, text_mode=True):
203 """
204 Spawn a tap-ctl process. Return a TapCtl invocation.
205 Raises a TapCtl.CommandFailure if subprocess creation failed.
206 """
207 cmd = cls._mkcmd(args)
209 if not quiet:
210 util.SMlog(cmd)
211 try:
212 p = subprocess.Popen(cmd,
213 stdin=subprocess.PIPE,
214 stdout=subprocess.PIPE,
215 stderr=subprocess.PIPE,
216 close_fds=True,
217 universal_newlines=text_mode)
218 if input:
219 p.stdin.write(input)
220 p.stdin.close()
221 except OSError as e:
222 raise cls.CommandFailure(cmd, errno=e.errno)
224 return cls(cmd, p)
226 def _errmsg(self):
227 output = map(str.rstrip, self._p.stderr)
228 return "; ".join(output)
230 def _wait(self, quiet=False):
231 """
232 Reap the child tap-ctl process of this invocation.
233 Raises a TapCtl.CommandFailure on non-zero exit status.
234 """
235 status = self._p.wait()
236 if not quiet:
237 util.SMlog(" = %d" % status)
239 if status == 0:
240 return
242 info = {'errmsg': self._errmsg(),
243 'pid': self._p.pid}
245 if status < 0:
246 info['signal'] = -status
247 else:
248 info['status'] = status
250 raise self.CommandFailure(self.cmd, ** info)
252 @classmethod
253 def _pread(cls, args, quiet=False, input=None, text_mode=True):
254 """
255 Spawn a tap-ctl invocation and read a single line.
256 """
257 tapctl = cls._call(args=args, quiet=quiet, input=input,
258 text_mode=text_mode)
260 output = tapctl.stdout.readline().rstrip()
262 tapctl._wait(quiet)
263 return output
265 @staticmethod
266 def _maybe(opt, parm):
267 if parm is not None:
268 return [opt, parm]
269 return []
271 @classmethod
272 def __list(cls, minor=None, pid=None, _type=None, path=None):
273 args = ["list"]
274 args += cls._maybe("-m", minor)
275 args += cls._maybe("-p", pid)
276 args += cls._maybe("-t", _type)
277 args += cls._maybe("-f", path)
279 tapctl = cls._call(args, True)
281 for stdout_line in tapctl.stdout:
282 # FIXME: tap-ctl writes error messages to stdout and
283 # confuses this parser
284 if stdout_line == "blktap kernel module not installed\n": 284 ↛ 287line 284 didn't jump to line 287, because the condition on line 284 was never true
285 # This isn't pretty but (a) neither is confusing stdout/stderr
286 # and at least causes the error to describe the fix
287 raise Exception("blktap kernel module not installed: try 'modprobe blktap'")
288 row = {}
290 for field in stdout_line.rstrip().split(' ', 3):
291 bits = field.split('=')
292 if len(bits) == 2: 292 ↛ 304line 292 didn't jump to line 304, because the condition on line 292 was never false
293 key, val = field.split('=')
295 if key in ('pid', 'minor'):
296 row[key] = int(val, 10)
298 elif key in ('state'):
299 row[key] = int(val, 0x10)
301 else:
302 row[key] = val
303 else:
304 util.SMlog("Ignoring unexpected tap-ctl output: %s" % repr(field))
305 yield row
307 tapctl._wait(True)
309 @classmethod
310 @retried(backoff=.5, limit=10)
311 def list(cls, **args):
313 # FIXME. We typically get an EPROTO when uevents interleave
314 # with SM ops and a tapdisk shuts down under our feet. Should
315 # be fixed in SM.
317 try:
318 return list(cls.__list( ** args))
320 except cls.CommandFailure as e:
321 transient = [errno.EPROTO, errno.ENOENT]
322 if e.has_status and e.status in transient:
323 raise RetryLoop.TransientFailure(e)
324 raise
326 @classmethod
327 def allocate(cls, devpath=None):
328 args = ["allocate"]
329 args += cls._maybe("-d", devpath)
330 return cls._pread(args)
332 @classmethod
333 def free(cls, minor):
334 args = ["free", "-m", minor]
335 cls._pread(args)
337 @classmethod
338 @retried(backoff=.5, limit=10)
339 def spawn(cls):
340 args = ["spawn"]
341 try:
342 pid = cls._pread(args)
343 return int(pid)
344 except cls.CommandFailure as ce:
345 # intermittent failures to spawn. CA-292268
346 if ce.status == 1:
347 raise RetryLoop.TransientFailure(ce)
348 raise
350 @classmethod
351 def attach(cls, pid, minor):
352 args = ["attach", "-p", pid, "-m", minor]
353 cls._pread(args)
355 @classmethod
356 def detach(cls, pid, minor):
357 args = ["detach", "-p", pid, "-m", minor]
358 cls._pread(args)
360 @classmethod
361 def _load_key(cls, key_hash, vdi_uuid):
362 import plugins
364 return plugins.load_key(key_hash, vdi_uuid)
366 @classmethod
367 def open(cls, pid, minor, _type, _file, options):
368 params = Tapdisk.Arg(_type, _file)
369 args = ["open", "-p", pid, "-m", minor, '-a', str(params)]
370 text_mode = True
371 input = None
372 if options.get("rdonly"):
373 args.append('-R')
374 if options.get("lcache"):
375 args.append("-r")
376 if options.get("existing_prt") is not None:
377 args.append("-e")
378 args.append(str(options["existing_prt"]))
379 if options.get("secondary"):
380 args.append("-2")
381 args.append(options["secondary"])
382 if options.get("standby"):
383 args.append("-s")
384 if options.get("timeout"):
385 args.append("-t")
386 args.append(str(options["timeout"]))
387 if not options.get("o_direct", True):
388 args.append("-D")
389 if options.get('cbtlog'):
390 args.extend(['-C', options['cbtlog']])
391 if options.get('key_hash'):
392 key_hash = options['key_hash']
393 vdi_uuid = options['vdi_uuid']
394 key = cls._load_key(key_hash, vdi_uuid)
396 if not key:
397 raise util.SMException("No key found with key hash {}".format(key_hash))
398 input = key
399 text_mode = False
400 args.append('-E')
402 cls._pread(args=args, input=input, text_mode=text_mode)
404 @classmethod
405 def close(cls, pid, minor, force=False):
406 args = ["close", "-p", pid, "-m", minor, "-t", "120"]
407 if force:
408 args += ["-f"]
409 cls._pread(args)
411 @classmethod
412 def pause(cls, pid, minor):
413 args = ["pause", "-p", pid, "-m", minor]
414 cls._pread(args)
416 @classmethod
417 def unpause(cls, pid, minor, _type=None, _file=None, mirror=None,
418 cbtlog=None):
419 args = ["unpause", "-p", pid, "-m", minor]
420 if mirror:
421 args.extend(["-2", mirror])
422 if _type and _file:
423 params = Tapdisk.Arg(_type, _file)
424 args += ["-a", str(params)]
425 if cbtlog:
426 args.extend(["-c", cbtlog])
427 cls._pread(args)
429 @classmethod
430 def shutdown(cls, pid):
431 # TODO: This should be a real tap-ctl command
432 os.kill(pid, signal.SIGTERM)
433 os.waitpid(pid, 0)
435 @classmethod
436 def stats(cls, pid, minor):
437 args = ["stats", "-p", pid, "-m", minor]
438 return cls._pread(args, quiet=True)
440 @classmethod
441 def major(cls):
442 args = ["major"]
443 major = cls._pread(args)
444 return int(major)
447class TapdiskExists(Exception):
448 """Tapdisk already running."""
450 def __init__(self, tapdisk):
451 self.tapdisk = tapdisk
453 @override
454 def __str__(self) -> str:
455 return "%s already running" % self.tapdisk
458class TapdiskNotRunning(Exception):
459 """No such Tapdisk."""
461 def __init__(self, **attrs):
462 self.attrs = attrs
464 @override
465 def __str__(self) -> str:
466 items = iter(self.attrs.items())
467 attrs = ", ".join("%s=%s" % attr
468 for attr in items)
469 return "No such Tapdisk(%s)" % attrs
472class TapdiskNotUnique(Exception):
473 """More than one tapdisk on one path."""
475 def __init__(self, tapdisks):
476 self.tapdisks = tapdisks
478 @override
479 def __str__(self) -> str:
480 tapdisks = map(str, self.tapdisks)
481 return "Found multiple tapdisks: %s" % tapdisks
484class TapdiskFailed(Exception):
485 """Tapdisk launch failure."""
487 def __init__(self, arg, err):
488 self.arg = arg
489 self.err = err
491 @override
492 def __str__(self) -> str:
493 return "Tapdisk(%s): %s" % (self.arg, self.err)
495 def get_error(self):
496 return self.err
499class TapdiskInvalidState(Exception):
500 """Tapdisk pause/unpause failure"""
502 def __init__(self, tapdisk):
503 self.tapdisk = tapdisk
505 @override
506 def __str__(self) -> str:
507 return str(self.tapdisk)
510def mkdirs(path, mode=0o777):
511 if not os.path.exists(path):
512 parent, subdir = os.path.split(path)
513 assert parent != path
514 try:
515 if parent:
516 mkdirs(parent, mode)
517 if subdir:
518 os.mkdir(path, mode)
519 except OSError as e:
520 if e.errno != errno.EEXIST:
521 raise
524class KObject(object):
526 SYSFS_CLASSTYPE: ClassVar[str] = ""
528 @abstractmethod
529 def sysfs_devname(self) -> str:
530 pass
533class Attribute(object):
535 SYSFS_NODENAME: ClassVar[str] = ""
537 def __init__(self, path):
538 self.path = path
540 @classmethod
541 def from_kobject(cls, kobj):
542 path = "%s/%s" % (kobj.sysfs_path(), cls.SYSFS_NODENAME)
543 return cls(path)
545 class NoSuchAttribute(Exception):
546 def __init__(self, name):
547 self.name = name
549 @override
550 def __str__(self) -> str:
551 return "No such attribute: %s" % self.name
553 def _open(self, mode='r'):
554 try:
555 return open(self.path, mode)
556 except IOError as e:
557 if e.errno == errno.ENOENT:
558 raise self.NoSuchAttribute(self)
559 raise
561 def readline(self):
562 f = self._open('r')
563 s = f.readline().rstrip()
564 f.close()
565 return s
567 def writeline(self, val):
568 f = self._open('w')
569 f.write(val)
570 f.close()
573class ClassDevice(KObject):
575 @classmethod
576 def sysfs_class_path(cls):
577 return "/sys/class/%s" % cls.SYSFS_CLASSTYPE
579 def sysfs_path(self):
580 return "%s/%s" % (self.sysfs_class_path(),
581 self.sysfs_devname())
584class Blktap(ClassDevice):
586 DEV_BASEDIR = '/dev/xen/blktap-2'
588 SYSFS_CLASSTYPE = "blktap2"
590 def __init__(self, minor):
591 self.minor = minor
592 self._pool = None
593 self._task = None
595 @classmethod
596 def allocate(cls):
597 # FIXME. Should rather go into init.
598 mkdirs(cls.DEV_BASEDIR)
600 devname = TapCtl.allocate()
601 minor = Tapdisk._parse_minor(devname)
602 return cls(minor)
604 def free(self):
605 TapCtl.free(self.minor)
607 @override
608 def __str__(self) -> str:
609 return "%s(minor=%d)" % (self.__class__.__name__, self.minor)
611 @override
612 def sysfs_devname(self) -> str:
613 return "blktap!blktap%d" % self.minor
615 class Pool(Attribute):
616 SYSFS_NODENAME = "pool"
618 def get_pool_attr(self):
619 if not self._pool:
620 self._pool = self.Pool.from_kobject(self)
621 return self._pool
623 def get_pool_name(self):
624 return self.get_pool_attr().readline()
626 def set_pool_name(self, name):
627 self.get_pool_attr().writeline(name)
629 def set_pool_size(self, pages):
630 self.get_pool().set_size(pages)
632 def get_pool(self):
633 return BlktapControl.get_pool(self.get_pool_name())
635 def set_pool(self, pool):
636 self.set_pool_name(pool.name)
638 class Task(Attribute):
639 SYSFS_NODENAME = "task"
641 def get_task_attr(self):
642 if not self._task:
643 self._task = self.Task.from_kobject(self)
644 return self._task
646 def get_task_pid(self):
647 pid = self.get_task_attr().readline()
648 try:
649 return int(pid)
650 except ValueError:
651 return None
653 def find_tapdisk(self):
654 pid = self.get_task_pid()
655 if pid is None:
656 return None
658 return Tapdisk.find(pid=pid, minor=self.minor)
660 def get_tapdisk(self):
661 tapdisk = self.find_tapdisk()
662 if not tapdisk:
663 raise TapdiskNotRunning(minor=self.minor)
664 return tapdisk
667class Tapdisk(object):
669 TYPES = ['aio', 'vhd']
671 def __init__(self, pid, minor, _type, path, state):
672 self.pid = pid
673 self.minor = minor
674 self.type = _type
675 self.path = path
676 self.state = state
677 self._dirty = False
678 self._blktap = None
680 @override
681 def __str__(self) -> str:
682 state = self.pause_state()
683 return "Tapdisk(%s, pid=%d, minor=%s, state=%s)" % \
684 (self.get_arg(), self.pid, self.minor, state)
686 @classmethod
687 def list(cls, **args):
689 for row in TapCtl.list( ** args):
691 args = {'pid': None,
692 'minor': None,
693 'state': None,
694 '_type': None,
695 'path': None}
697 for key, val in row.items():
698 if key in args:
699 args[key] = val
701 if 'args' in row: 701 ↛ 706line 701 didn't jump to line 706, because the condition on line 701 was never false
702 image = Tapdisk.Arg.parse(row['args'])
703 args['_type'] = image.type
704 args['path'] = image.path
706 if None in args.values(): 706 ↛ 707line 706 didn't jump to line 707, because the condition on line 706 was never true
707 continue
709 yield Tapdisk( ** args)
711 @classmethod
712 def find(cls, **args):
714 found = list(cls.list( ** args))
716 if len(found) > 1: 716 ↛ 717line 716 didn't jump to line 717, because the condition on line 716 was never true
717 raise TapdiskNotUnique(found)
719 if found: 719 ↛ 720line 719 didn't jump to line 720, because the condition on line 719 was never true
720 return found[0]
722 return None
724 @classmethod
725 def find_by_path(cls, path):
726 return cls.find(path=path)
728 @classmethod
729 def find_by_minor(cls, minor):
730 return cls.find(minor=minor)
732 @classmethod
733 def get(cls, **attrs):
735 tapdisk = cls.find( ** attrs)
737 if not tapdisk:
738 raise TapdiskNotRunning( ** attrs)
740 return tapdisk
742 @classmethod
743 def from_path(cls, path):
744 return cls.get(path=path)
746 @classmethod
747 def from_minor(cls, minor):
748 return cls.get(minor=minor)
750 @classmethod
751 def __from_blktap(cls, blktap):
752 tapdisk = cls.from_minor(minor=blktap.minor)
753 tapdisk._blktap = blktap
754 return tapdisk
756 def get_blktap(self):
757 if not self._blktap:
758 self._blktap = Blktap(self.minor)
759 return self._blktap
761 class Arg:
763 def __init__(self, _type, path):
764 self.type = _type
765 self.path = path
767 @override
768 def __str__(self) -> str:
769 return "%s:%s" % (self.type, self.path)
771 @classmethod
772 def parse(cls, arg):
774 try:
775 _type, path = arg.split(":", 1)
776 except ValueError:
777 raise cls.InvalidArgument(arg)
779 if _type not in Tapdisk.TYPES: 779 ↛ 780line 779 didn't jump to line 780, because the condition on line 779 was never true
780 raise cls.InvalidType(_type)
782 return cls(_type, path)
784 class InvalidType(Exception):
785 def __init__(self, _type):
786 self.type = _type
788 @override
789 def __str__(self) -> str:
790 return "Not a Tapdisk type: %s" % self.type
792 class InvalidArgument(Exception):
793 def __init__(self, arg):
794 self.arg = arg
796 @override
797 def __str__(self) -> str:
798 return "Not a Tapdisk image: %s" % self.arg
800 def get_arg(self):
801 return self.Arg(self.type, self.path)
803 def get_devpath(self):
804 return "%s/tapdev%d" % (Blktap.DEV_BASEDIR, self.minor)
806 @classmethod
807 def launch_from_arg(cls, arg):
808 arg = cls.Arg.parse(arg)
809 return cls.launch(arg.path, arg.type, False)
811 @staticmethod
812 def cgclassify(pid):
814 # We dont provide any <controllers>:<path>
815 # so cgclassify uses /etc/cgrules.conf which
816 # we have configured in the spec file.
817 cmd = ["cgclassify", str(pid)]
818 try:
819 util.pread2(cmd)
820 except util.CommandException as e:
821 util.logException(e)
823 @classmethod
824 def launch_on_tap(cls, blktap, path, _type, options):
826 tapdisk = cls.find_by_path(path)
827 if tapdisk: 827 ↛ 828line 827 didn't jump to line 828, because the condition on line 827 was never true
828 raise TapdiskExists(tapdisk)
830 minor = blktap.minor
831 try:
832 pid = TapCtl.spawn()
833 cls.cgclassify(pid)
834 try:
835 TapCtl.attach(pid, minor)
837 try:
838 retry_open = 0
839 while True:
840 try:
841 TapCtl.open(pid, minor, _type, path, options)
842 break
843 except TapCtl.CommandFailure as e:
844 err = (
845 'status' in e.info and e.info['status']
846 ) or None
847 if err in (errno.EIO, errno.EROFS, errno.EAGAIN): 847 ↛ 848line 847 didn't jump to line 848, because the condition on line 847 was never true
848 if retry_open < 5:
849 retry_open += 1
850 time.sleep(1)
851 continue
852 if LINSTOR_AVAILABLE and err == errno.EROFS:
853 log_drbd_openers(path)
854 raise
855 try:
856 tapdisk = cls.__from_blktap(blktap)
857 node = '/sys/dev/block/%d:%d' % (tapdisk.major(), tapdisk.minor)
858 util.set_scheduler_sysfs_node(node, ['none', 'noop'])
859 return tapdisk
860 except:
861 TapCtl.close(pid, minor)
862 raise
864 except:
865 TapCtl.detach(pid, minor)
866 raise
868 except:
869 try:
870 TapCtl.shutdown(pid)
871 except:
872 # Best effort to shutdown
873 pass
874 raise
876 except TapCtl.CommandFailure as ctl:
877 util.logException(ctl)
878 if ((path.startswith('/dev/xapi/cd/') or path.startswith('/dev/sr')) and 878 ↛ 882line 878 didn't jump to line 882, because the condition on line 878 was never false
879 ctl.has_status and ctl.get_error_code() == 123): # ENOMEDIUM (No medium found)
880 raise xs_errors.XenError('TapdiskDriveEmpty')
881 else:
882 raise TapdiskFailed(cls.Arg(_type, path), ctl)
884 @classmethod
885 def launch(cls, path, _type, rdonly):
886 blktap = Blktap.allocate()
887 try:
888 return cls.launch_on_tap(blktap, path, _type, {"rdonly": rdonly})
889 except:
890 blktap.free()
891 raise
893 def shutdown(self, force=False):
895 TapCtl.close(self.pid, self.minor, force)
897 TapCtl.detach(self.pid, self.minor)
899 self.get_blktap().free()
901 def pause(self):
903 if not self.is_running():
904 raise TapdiskInvalidState(self)
906 TapCtl.pause(self.pid, self.minor)
908 self._set_dirty()
910 def unpause(self, _type=None, path=None, mirror=None, cbtlog=None):
912 if not self.is_paused():
913 raise TapdiskInvalidState(self)
915 # FIXME: should the arguments be optional?
916 if _type is None:
917 _type = self.type
918 if path is None:
919 path = self.path
921 TapCtl.unpause(self.pid, self.minor, _type, path, mirror=mirror,
922 cbtlog=cbtlog)
924 self._set_dirty()
926 def stats(self):
927 return json.loads(TapCtl.stats(self.pid, self.minor))
928 #
929 # NB. dirty/refresh: reload attributes on next access
930 #
932 def _set_dirty(self):
933 self._dirty = True
935 def _refresh(self, __get):
936 t = self.from_minor(__get('minor'))
937 self.__init__(t.pid, t.minor, t.type, t.path, t.state)
939 @override
940 def __getattribute__(self, name) -> Any:
941 def __get(name):
942 # NB. avoid(rec(ursion)
943 return object.__getattribute__(self, name)
945 if __get('_dirty') and \ 945 ↛ 947line 945 didn't jump to line 947, because the condition on line 945 was never true
946 name in ['minor', 'type', 'path', 'state']:
947 self._refresh(__get)
948 self._dirty = False
950 return __get(name)
952 class PauseState:
953 RUNNING = 'R'
954 PAUSING = 'r'
955 PAUSED = 'P'
957 class Flags:
958 DEAD = 0x0001
959 CLOSED = 0x0002
960 QUIESCE_REQUESTED = 0x0004
961 QUIESCED = 0x0008
962 PAUSE_REQUESTED = 0x0010
963 PAUSED = 0x0020
964 SHUTDOWN_REQUESTED = 0x0040
965 LOCKING = 0x0080
966 RETRY_NEEDED = 0x0100
967 LOG_DROPPED = 0x0200
969 PAUSE_MASK = PAUSE_REQUESTED | PAUSED
971 def is_paused(self):
972 return not not (self.state & self.Flags.PAUSED)
974 def is_running(self):
975 return not (self.state & self.Flags.PAUSE_MASK)
977 def pause_state(self):
978 if self.state & self.Flags.PAUSED: 978 ↛ 979line 978 didn't jump to line 979, because the condition on line 978 was never true
979 return self.PauseState.PAUSED
981 if self.state & self.Flags.PAUSE_REQUESTED: 981 ↛ 982line 981 didn't jump to line 982, because the condition on line 981 was never true
982 return self.PauseState.PAUSING
984 return self.PauseState.RUNNING
986 @staticmethod
987 def _parse_minor(devpath):
988 regex = r'%s/(blktap|tapdev)(\d+)$' % Blktap.DEV_BASEDIR
989 pattern = re.compile(regex)
990 groups = pattern.search(devpath)
991 if not groups:
992 raise Exception("malformed tap device: '%s' (%s) " % (devpath, regex))
994 minor = groups.group(2)
995 return int(minor)
997 _major = None
999 @classmethod
1000 def major(cls):
1001 if cls._major:
1002 return cls._major
1004 devices = open("/proc/devices")
1005 for line in devices:
1007 row = line.rstrip().split(' ')
1008 if len(row) != 2:
1009 continue
1011 major, name = row
1012 if name != 'tapdev':
1013 continue
1015 cls._major = int(major)
1016 break
1018 devices.close()
1019 return cls._major
1022class VDI(object):
1023 """SR.vdi driver decorator for blktap2"""
1025 CONF_KEY_ALLOW_CACHING = "vdi_allow_caching"
1026 CONF_KEY_MODE_ON_BOOT = "vdi_on_boot"
1027 CONF_KEY_CACHE_SR = "local_cache_sr"
1028 CONF_KEY_O_DIRECT = "o_direct"
1029 LOCK_CACHE_SETUP = "cachesetup"
1031 ATTACH_DETACH_RETRY_SECS = 120
1033 def __init__(self, uuid, target, driver_info):
1034 self.target = self.TargetDriver(target, driver_info)
1035 self._vdi_uuid = uuid
1036 self._session = target.session
1037 self.xenstore_data = scsiutil.update_XS_SCSIdata(uuid, scsiutil.gen_synthetic_page_data(uuid))
1038 self.__o_direct = None
1039 self.__o_direct_reason = None
1040 self.lock = Lock("vdi", uuid)
1041 self.tap = None
1043 def get_o_direct_capability(self, options):
1044 """Returns True/False based on licensing and caching_params"""
1045 if self.__o_direct is not None: 1045 ↛ 1046line 1045 didn't jump to line 1046, because the condition on line 1045 was never true
1046 return self.__o_direct, self.__o_direct_reason
1048 if util.read_caching_is_restricted(self._session): 1048 ↛ 1049line 1048 didn't jump to line 1049, because the condition on line 1048 was never true
1049 self.__o_direct = True
1050 self.__o_direct_reason = "LICENSE_RESTRICTION"
1051 elif not ((self.target.vdi.sr.handles("nfs") or self.target.vdi.sr.handles("ext") or self.target.vdi.sr.handles("smb"))): 1051 ↛ 1054line 1051 didn't jump to line 1054, because the condition on line 1051 was never false
1052 self.__o_direct = True
1053 self.__o_direct_reason = "SR_NOT_SUPPORTED"
1054 elif options.get("rdonly") and not self.target.vdi.parent:
1055 self.__o_direct = True
1056 self.__o_direct_reason = "RO_WITH_NO_PARENT"
1057 elif options.get(self.CONF_KEY_O_DIRECT):
1058 self.__o_direct = True
1059 self.__o_direct_reason = "SR_OVERRIDE"
1061 if self.__o_direct is None: 1061 ↛ 1062line 1061 didn't jump to line 1062, because the condition on line 1061 was never true
1062 self.__o_direct = False
1063 self.__o_direct_reason = ""
1065 return self.__o_direct, self.__o_direct_reason
1067 @classmethod
1068 def from_cli(cls, uuid):
1069 session = XenAPI.xapi_local()
1070 session.xenapi.login_with_password('root', '', '', 'SM')
1072 target = sm.VDI.from_uuid(session, uuid)
1073 driver_info = target.sr.srcmd.driver_info
1075 session.xenapi.session.logout()
1077 return cls(uuid, target, driver_info)
1079 @staticmethod
1080 def _tap_type(vdi_type):
1081 """Map a VDI type (e.g. 'raw') to a tapdisk driver type (e.g. 'aio')"""
1082 return {
1083 'raw': 'aio',
1084 'vhd': 'vhd',
1085 'iso': 'aio', # for ISO SR
1086 'aio': 'aio', # for LVHD
1087 'file': 'aio',
1088 'phy': 'aio'
1089 }[vdi_type]
1091 def get_tap_type(self):
1092 vdi_type = self.target.get_vdi_type()
1093 return VDI._tap_type(vdi_type)
1095 def get_phy_path(self):
1096 return self.target.get_vdi_path()
1098 class UnexpectedVDIType(Exception):
1100 def __init__(self, vdi_type, target):
1101 self.vdi_type = vdi_type
1102 self.target = target
1104 @override
1105 def __str__(self) -> str:
1106 return \
1107 "Target %s has unexpected VDI type '%s'" % \
1108 (type(self.target), self.vdi_type)
1110 VDI_PLUG_TYPE = {'phy': 'phy', # for NETAPP
1111 'raw': 'phy',
1112 'aio': 'tap', # for LVHD raw nodes
1113 'iso': 'tap', # for ISOSR
1114 'file': 'tap',
1115 'vhd': 'tap'}
1117 def tap_wanted(self):
1118 # 1. Let the target vdi_type decide
1120 vdi_type = self.target.get_vdi_type()
1122 try:
1123 plug_type = self.VDI_PLUG_TYPE[vdi_type]
1124 except KeyError:
1125 raise self.UnexpectedVDIType(vdi_type,
1126 self.target.vdi)
1128 if plug_type == 'tap': 1128 ↛ 1129line 1128 didn't jump to line 1129, because the condition on line 1128 was never true
1129 return True
1130 elif self.target.vdi.sr.handles('udev'): 1130 ↛ 1136line 1130 didn't jump to line 1136, because the condition on line 1130 was never false
1131 return True
1132 # 2. Otherwise, there may be more reasons
1133 #
1134 # .. TBD
1136 return False
1138 class TargetDriver:
1139 """Safe target driver access."""
1140 # NB. *Must* test caps for optional calls. Some targets
1141 # actually implement some slots, but do not enable them. Just
1142 # try/except would risk breaking compatibility.
1144 def __init__(self, vdi, driver_info):
1145 self.vdi = vdi
1146 self._caps = driver_info['capabilities']
1148 def has_cap(self, cap):
1149 """Determine if target has given capability"""
1150 return cap in self._caps
1152 def attach(self, sr_uuid, vdi_uuid):
1153 #assert self.has_cap("VDI_ATTACH")
1154 return self.vdi.attach(sr_uuid, vdi_uuid)
1156 def detach(self, sr_uuid, vdi_uuid):
1157 #assert self.has_cap("VDI_DETACH")
1158 self.vdi.detach(sr_uuid, vdi_uuid)
1160 def activate(self, sr_uuid, vdi_uuid):
1161 if self.has_cap("VDI_ACTIVATE"):
1162 return self.vdi.activate(sr_uuid, vdi_uuid)
1164 def deactivate(self, sr_uuid, vdi_uuid):
1165 if self.has_cap("VDI_DEACTIVATE"):
1166 self.vdi.deactivate(sr_uuid, vdi_uuid)
1167 #def resize(self, sr_uuid, vdi_uuid, size):
1168 # return self.vdi.resize(sr_uuid, vdi_uuid, size)
1170 def get_vdi_type(self):
1171 _type = self.vdi.vdi_type
1172 if not _type:
1173 _type = self.vdi.sr.sr_vditype
1174 if not _type:
1175 raise VDI.UnexpectedVDIType(_type, self.vdi)
1176 return _type
1178 def get_vdi_path(self):
1179 return self.vdi.path
1181 class Link(object):
1182 """Relink a node under a common name"""
1183 # NB. We have to provide the device node path during
1184 # VDI.attach, but currently do not allocate the tapdisk minor
1185 # before VDI.activate. Therefore those link steps where we
1186 # relink existing devices under deterministic path names.
1188 BASEDIR: ClassVar[str] = ""
1190 def _mklink(self, target) -> None:
1191 pass
1193 @abstractmethod
1194 def _equals(self, target) -> bool:
1195 pass
1197 def __init__(self, path):
1198 self._path = path
1200 @classmethod
1201 def from_name(cls, name):
1202 path = "%s/%s" % (cls.BASEDIR, name)
1203 return cls(path)
1205 @classmethod
1206 def from_uuid(cls, sr_uuid, vdi_uuid):
1207 name = "%s/%s" % (sr_uuid, vdi_uuid)
1208 return cls.from_name(name)
1210 def path(self):
1211 return self._path
1213 def stat(self):
1214 return os.stat(self.path())
1216 def mklink(self, target) -> None:
1218 path = self.path()
1219 util.SMlog("%s -> %s" % (self, target))
1221 mkdirs(os.path.dirname(path))
1222 try:
1223 self._mklink(target)
1224 except OSError as e:
1225 # We do unlink during teardown, but have to stay
1226 # idempotent. However, a *wrong* target should never
1227 # be seen.
1228 if e.errno != errno.EEXIST:
1229 raise
1230 assert self._equals(target), "'%s' not equal to '%s'" % (path, target)
1232 def unlink(self):
1233 try:
1234 os.unlink(self.path())
1235 except OSError as e:
1236 if e.errno != errno.ENOENT:
1237 raise
1239 @override
1240 def __str__(self) -> str:
1241 path = self.path()
1242 return "%s(%s)" % (self.__class__.__name__, path)
1244 class SymLink(Link):
1245 """Symlink some file to a common name"""
1247 def readlink(self):
1248 return os.readlink(self.path())
1250 def symlink(self):
1251 return self.path()
1253 @override
1254 def _mklink(self, target) -> None:
1255 os.symlink(target, self.path())
1257 @override
1258 def _equals(self, target) -> bool:
1259 return self.readlink() == target
1261 class DeviceNode(Link):
1262 """Relink a block device node to a common name"""
1264 @classmethod
1265 def _real_stat(cls, target):
1266 """stat() not on @target, but its realpath()"""
1267 _target = os.path.realpath(target)
1268 return os.stat(_target)
1270 @classmethod
1271 def is_block(cls, target):
1272 """Whether @target refers to a block device."""
1273 return S_ISBLK(cls._real_stat(target).st_mode)
1275 @override
1276 def _mklink(self, target) -> None:
1278 st = self._real_stat(target)
1279 if not S_ISBLK(st.st_mode):
1280 raise self.NotABlockDevice(target, st)
1282 # set group read for disk group as well as root
1283 os.mknod(self.path(), st.st_mode | stat.S_IRGRP, st.st_rdev)
1284 os.chown(self.path(), st.st_uid, grp.getgrnam("disk").gr_gid)
1286 @override
1287 def _equals(self, target) -> bool:
1288 target_rdev = self._real_stat(target).st_rdev
1289 return self.stat().st_rdev == target_rdev
1291 def rdev(self):
1292 st = self.stat()
1293 assert S_ISBLK(st.st_mode)
1294 return os.major(st.st_rdev), os.minor(st.st_rdev)
1296 class NotABlockDevice(Exception):
1298 def __init__(self, path, st):
1299 self.path = path
1300 self.st = st
1302 @override
1303 def __str__(self) -> str:
1304 return "%s is not a block device: %s" % (self.path, self.st)
1306 class Hybrid(Link):
1308 def __init__(self, path):
1309 VDI.Link.__init__(self, path)
1310 self._devnode = VDI.DeviceNode(path)
1311 self._symlink = VDI.SymLink(path)
1313 def rdev(self):
1314 st = self.stat()
1315 if S_ISBLK(st.st_mode):
1316 return self._devnode.rdev()
1317 raise self._devnode.NotABlockDevice(self.path(), st)
1319 @override
1320 def mklink(self, target) -> None:
1321 if self._devnode.is_block(target):
1322 self._obj = self._devnode
1323 else:
1324 self._obj = self._symlink
1325 self._obj.mklink(target)
1327 @override
1328 def _equals(self, target) -> bool:
1329 return self._obj._equals(target)
1331 class PhyLink(SymLink):
1332 BASEDIR = "/dev/sm/phy"
1333 # NB. Cannot use DeviceNodes, e.g. FileVDIs aren't bdevs.
1335 class NBDLink(SymLink):
1337 BASEDIR = "/run/blktap-control/nbd"
1339 class BackendLink(Hybrid):
1340 BASEDIR = "/dev/sm/backend"
1341 # NB. Could be SymLinks as well, but saving major,minor pairs in
1342 # Links enables neat state capturing when managing Tapdisks. Note
1343 # that we essentially have a tap-ctl list replacement here. For
1344 # now make it a 'Hybrid'. Likely to collapse into a DeviceNode as
1345 # soon as ISOs are tapdisks.
1347 @staticmethod
1348 def _tap_activate(phy_path, vdi_type, sr_uuid, options, pool_size=None):
1350 tapdisk = Tapdisk.find_by_path(phy_path)
1351 if not tapdisk: 1351 ↛ 1352line 1351 didn't jump to line 1352, because the condition on line 1351 was never true
1352 blktap = Blktap.allocate()
1353 blktap.set_pool_name(sr_uuid)
1354 if pool_size:
1355 blktap.set_pool_size(pool_size)
1357 try:
1358 tapdisk = \
1359 Tapdisk.launch_on_tap(blktap,
1360 phy_path,
1361 VDI._tap_type(vdi_type),
1362 options)
1363 except:
1364 blktap.free()
1365 raise
1366 util.SMlog("tap.activate: Launched %s" % tapdisk)
1368 else:
1369 util.SMlog("tap.activate: Found %s" % tapdisk)
1371 return tapdisk.get_devpath(), tapdisk
1373 @staticmethod
1374 def _tap_deactivate(minor):
1376 try:
1377 tapdisk = Tapdisk.from_minor(minor)
1378 except TapdiskNotRunning as e:
1379 util.SMlog("tap.deactivate: Warning, %s" % e)
1380 # NB. Should not be here unless the agent refcount
1381 # broke. Also, a clean shutdown should not have leaked
1382 # the recorded minor.
1383 else:
1384 tapdisk.shutdown()
1385 util.SMlog("tap.deactivate: Shut down %s" % tapdisk)
1387 @classmethod
1388 def tap_pause(cls, session, sr_uuid, vdi_uuid, failfast=False):
1389 """
1390 Pauses the tapdisk.
1392 session: a XAPI session
1393 sr_uuid: the UUID of the SR on which VDI lives
1394 vdi_uuid: the UUID of the VDI to pause
1395 failfast: controls whether the VDI lock should be acquired in a
1396 non-blocking manner
1397 """
1398 util.SMlog("Pause request for %s" % vdi_uuid)
1399 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1400 session.xenapi.VDI.add_to_sm_config(vdi_ref, 'paused', 'true')
1401 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1402 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 1402 ↛ 1403line 1402 didn't jump to line 1403, because the loop on line 1402 never started
1403 host_ref = key[len('host_'):]
1404 util.SMlog("Calling tap-pause on host %s" % host_ref)
1405 if not cls.call_pluginhandler(session, host_ref,
1406 sr_uuid, vdi_uuid, "pause", failfast=failfast):
1407 # Failed to pause node
1408 session.xenapi.VDI.remove_from_sm_config(vdi_ref, 'paused')
1409 return False
1410 return True
1412 @classmethod
1413 def tap_unpause(cls, session, sr_uuid, vdi_uuid, secondary=None,
1414 activate_parents=False):
1415 util.SMlog("Unpause request for %s secondary=%s" % (vdi_uuid, secondary))
1416 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1417 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1418 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 1418 ↛ 1419line 1418 didn't jump to line 1419, because the loop on line 1418 never started
1419 host_ref = key[len('host_'):]
1420 util.SMlog("Calling tap-unpause on host %s" % host_ref)
1421 if not cls.call_pluginhandler(session, host_ref,
1422 sr_uuid, vdi_uuid, "unpause", secondary, activate_parents):
1423 # Failed to unpause node
1424 return False
1425 session.xenapi.VDI.remove_from_sm_config(vdi_ref, 'paused')
1426 return True
1428 @classmethod
1429 def tap_refresh(cls, session, sr_uuid, vdi_uuid, activate_parents=False):
1430 util.SMlog("Refresh request for %s" % vdi_uuid)
1431 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1432 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1433 for key in [x for x in sm_config.keys() if x.startswith('host_')]:
1434 host_ref = key[len('host_'):]
1435 util.SMlog("Calling tap-refresh on host %s" % host_ref)
1436 if not cls.call_pluginhandler(session, host_ref,
1437 sr_uuid, vdi_uuid, "refresh", None,
1438 activate_parents=activate_parents):
1439 # Failed to refresh node
1440 return False
1441 return True
1443 @classmethod
1444 def tap_status(cls, session, vdi_uuid):
1445 """Return True if disk is attached, false if it isn't"""
1446 util.SMlog("Disk status request for %s" % vdi_uuid)
1447 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1448 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1449 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 1449 ↛ 1450line 1449 didn't jump to line 1450, because the loop on line 1449 never started
1450 return True
1451 return False
1453 @classmethod
1454 def call_pluginhandler(cls, session, host_ref, sr_uuid, vdi_uuid, action,
1455 secondary=None, activate_parents=False, failfast=False):
1456 """Optionally, activate the parent LV before unpausing"""
1457 try:
1458 args = {"sr_uuid": sr_uuid, "vdi_uuid": vdi_uuid,
1459 "failfast": str(failfast)}
1460 if secondary:
1461 args["secondary"] = secondary
1462 if activate_parents:
1463 args["activate_parents"] = "true"
1464 ret = session.xenapi.host.call_plugin(
1465 host_ref, PLUGIN_TAP_PAUSE, action,
1466 args)
1467 return ret == "True"
1468 except Exception as e:
1469 util.logException("BLKTAP2:call_pluginhandler %s" % e)
1470 return False
1472 def _add_tag(self, vdi_uuid, writable):
1473 util.SMlog("Adding tag to: %s" % vdi_uuid)
1474 attach_mode = "RO"
1475 if writable:
1476 attach_mode = "RW"
1477 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1478 host_ref = self._session.xenapi.host.get_by_uuid(util.get_this_host())
1479 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1480 attached_as = util.attached_as(sm_config)
1481 if NO_MULTIPLE_ATTACH and (attached_as == "RW" or \ 1481 ↛ 1483line 1481 didn't jump to line 1483, because the condition on line 1481 was never true
1482 (attached_as == "RO" and attach_mode == "RW")):
1483 util.SMlog("need to reset VDI %s" % vdi_uuid)
1484 if not resetvdis.reset_vdi(self._session, vdi_uuid, force=False,
1485 term_output=False, writable=writable):
1486 raise util.SMException("VDI %s not detached cleanly" % vdi_uuid)
1487 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1488 if 'relinking' in sm_config:
1489 util.SMlog("Relinking key found, back-off and retry" % sm_config)
1490 return False
1491 if 'paused' in sm_config:
1492 util.SMlog("Paused or host_ref key found [%s]" % sm_config)
1493 return False
1494 try:
1495 self._session.xenapi.VDI.add_to_sm_config(
1496 vdi_ref, 'activating', 'True')
1497 except XenAPI.Failure as e:
1498 if e.details[0] == 'MAP_DUPLICATE_KEY' and not writable:
1499 # Someone else is activating - a retry might succeed
1500 return False
1501 raise
1502 host_key = "host_%s" % host_ref
1503 assert host_key not in sm_config
1504 self._session.xenapi.VDI.add_to_sm_config(vdi_ref, host_key,
1505 attach_mode)
1506 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1507 if 'paused' in sm_config or 'relinking' in sm_config:
1508 util.SMlog("Found %s key, aborting" % (
1509 'paused' if 'paused' in sm_config else 'relinking'))
1510 self._session.xenapi.VDI.remove_from_sm_config(vdi_ref, host_key)
1511 self._session.xenapi.VDI.remove_from_sm_config(
1512 vdi_ref, 'activating')
1513 return False
1514 util.SMlog("Activate lock succeeded")
1515 return True
1517 def _check_tag(self, vdi_uuid):
1518 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1519 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1520 if 'paused' in sm_config:
1521 util.SMlog("Paused key found [%s]" % sm_config)
1522 return False
1523 return True
1525 def _remove_tag(self, vdi_uuid):
1526 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1527 host_ref = self._session.xenapi.host.get_by_uuid(util.get_this_host())
1528 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1529 host_key = "host_%s" % host_ref
1530 if host_key in sm_config:
1531 self._session.xenapi.VDI.remove_from_sm_config(vdi_ref, host_key)
1532 util.SMlog("Removed host key %s for %s" % (host_key, vdi_uuid))
1533 else:
1534 util.SMlog("_remove_tag: host key %s not found, ignore" % host_key)
1536 def _get_pool_config(self, pool_name):
1537 pool_info = dict()
1538 vdi_ref = self.target.vdi.sr.srcmd.params.get('vdi_ref')
1539 if not vdi_ref: 1539 ↛ 1542line 1539 didn't jump to line 1542, because the condition on line 1539 was never true
1540 # attach_from_config context: HA disks don't need to be in any
1541 # special pool
1542 return pool_info
1544 sr_ref = self.target.vdi.sr.srcmd.params.get('sr_ref')
1545 sr_config = self._session.xenapi.SR.get_other_config(sr_ref)
1546 vdi_config = self._session.xenapi.VDI.get_other_config(vdi_ref)
1547 pool_size_str = sr_config.get(POOL_SIZE_KEY)
1548 pool_name_override = vdi_config.get(POOL_NAME_KEY)
1549 if pool_name_override: 1549 ↛ 1554line 1549 didn't jump to line 1554, because the condition on line 1549 was never false
1550 pool_name = pool_name_override
1551 pool_size_override = vdi_config.get(POOL_SIZE_KEY)
1552 if pool_size_override: 1552 ↛ 1554line 1552 didn't jump to line 1554, because the condition on line 1552 was never false
1553 pool_size_str = pool_size_override
1554 pool_size = 0
1555 if pool_size_str: 1555 ↛ 1565line 1555 didn't jump to line 1565, because the condition on line 1555 was never false
1556 try:
1557 pool_size = int(pool_size_str)
1558 if pool_size < 1 or pool_size > MAX_FULL_RINGS: 1558 ↛ 1559line 1558 didn't jump to line 1559, because the condition on line 1558 was never true
1559 raise ValueError("outside of range")
1560 pool_size = NUM_PAGES_PER_RING * pool_size
1561 except ValueError:
1562 util.SMlog("Error: invalid mem-pool-size %s" % pool_size_str)
1563 pool_size = 0
1565 pool_info["mem-pool"] = pool_name
1566 if pool_size: 1566 ↛ 1569line 1566 didn't jump to line 1569, because the condition on line 1566 was never false
1567 pool_info["mem-pool-size"] = str(pool_size)
1569 return pool_info
1571 def linkNBD(self, sr_uuid, vdi_uuid):
1572 if self.tap:
1573 nbd_path = '/run/blktap-control/nbd%d.%d' % (int(self.tap.pid),
1574 int(self.tap.minor))
1575 VDI.NBDLink.from_uuid(sr_uuid, vdi_uuid).mklink(nbd_path)
1577 def attach(self, sr_uuid, vdi_uuid, writable, activate=False, caching_params={}):
1578 """Return/dev/sm/backend symlink path"""
1579 self.xenstore_data.update(self._get_pool_config(sr_uuid))
1580 if not self.target.has_cap("ATOMIC_PAUSE") or activate:
1581 util.SMlog("Attach & activate")
1582 self._attach(sr_uuid, vdi_uuid)
1583 dev_path = self._activate(sr_uuid, vdi_uuid,
1584 {"rdonly": not writable})
1585 self.BackendLink.from_uuid(sr_uuid, vdi_uuid).mklink(dev_path)
1586 self.linkNBD(sr_uuid, vdi_uuid)
1588 # Return backend/ link
1589 back_path = self.BackendLink.from_uuid(sr_uuid, vdi_uuid).path()
1590 if self.tap_wanted():
1591 # Only have NBD if we also have a tap
1592 nbd_path = "nbd:unix:{}:exportname={}".format(
1593 VDI.NBDLink.from_uuid(sr_uuid, vdi_uuid).path(),
1594 vdi_uuid)
1595 else:
1596 nbd_path = ""
1598 options = {"rdonly": not writable}
1599 options.update(caching_params)
1600 o_direct, o_direct_reason = self.get_o_direct_capability(options)
1601 struct = {'params': back_path,
1602 'params_nbd': nbd_path,
1603 'o_direct': o_direct,
1604 'o_direct_reason': o_direct_reason,
1605 'xenstore_data': self.xenstore_data}
1606 util.SMlog('result: %s' % struct)
1608 try:
1609 f = open("%s.attach_info" % back_path, 'a')
1610 f.write(xmlrpc.client.dumps((struct, ), "", True))
1611 f.close()
1612 except:
1613 pass
1615 return xmlrpc.client.dumps((struct, ), "", True)
1617 def activate(self, sr_uuid, vdi_uuid, writable, caching_params):
1618 util.SMlog("blktap2.activate")
1619 options = {"rdonly": not writable}
1620 options.update(caching_params)
1622 sr_ref = self.target.vdi.sr.srcmd.params.get('sr_ref')
1623 sr_other_config = self._session.xenapi.SR.get_other_config(sr_ref)
1624 for i in range(self.ATTACH_DETACH_RETRY_SECS): 1624 ↛ 1631line 1624 didn't jump to line 1631, because the loop on line 1624 didn't complete
1625 try:
1626 if self._activate_locked(sr_uuid, vdi_uuid, options):
1627 return
1628 except util.SRBusyException:
1629 util.SMlog("SR locked, retrying")
1630 time.sleep(1)
1631 raise util.SMException("VDI %s locked" % vdi_uuid)
1633 @locking("VDIUnavailable")
1634 def _activate_locked(self, sr_uuid, vdi_uuid, options):
1635 """Wraps target.activate and adds a tapdisk"""
1637 #util.SMlog("VDI.activate %s" % vdi_uuid)
1638 refresh = False
1639 if self.tap_wanted(): 1639 ↛ 1644line 1639 didn't jump to line 1644, because the condition on line 1639 was never false
1640 if not self._add_tag(vdi_uuid, not options["rdonly"]):
1641 return False
1642 refresh = True
1644 try:
1645 if refresh: 1645 ↛ 1656line 1645 didn't jump to line 1656, because the condition on line 1645 was never false
1646 # it is possible that while the VDI was paused some of its
1647 # attributes have changed (e.g. its size if it was inflated; or its
1648 # path if it was leaf-coalesced onto a raw LV), so refresh the
1649 # object completely
1650 params = self.target.vdi.sr.srcmd.params
1651 target = sm.VDI.from_uuid(self.target.vdi.session, vdi_uuid)
1652 target.sr.srcmd.params = params
1653 driver_info = target.sr.srcmd.driver_info
1654 self.target = self.TargetDriver(target, driver_info)
1656 util.fistpoint.activate_custom_fn( 1656 ↛ exitline 1656 didn't jump to the function exit
1657 "blktap_activate_inject_failure",
1658 lambda: util.inject_failure())
1660 # Attach the physical node
1661 if self.target.has_cap("ATOMIC_PAUSE"): 1661 ↛ 1662line 1661 didn't jump to line 1662, because the condition on line 1661 was never true
1662 self._attach(sr_uuid, vdi_uuid)
1664 vdi_type = self.target.get_vdi_type()
1666 # Take lvchange-p Lock before running
1667 # tap-ctl open
1668 # Needed to avoid race with lvchange -p which is
1669 # now taking the same lock
1670 # This is a fix for CA-155766
1671 if hasattr(self.target.vdi.sr, 'DRIVER_TYPE') and \ 1671 ↛ 1674line 1671 didn't jump to line 1674, because the condition on line 1671 was never true
1672 self.target.vdi.sr.DRIVER_TYPE == 'lvhd' and \
1673 vdi_type == vhdutil.VDI_TYPE_VHD:
1674 lock = Lock("lvchange-p", lvhdutil.NS_PREFIX_LVM + sr_uuid)
1675 lock.acquire()
1677 # When we attach a static VDI for HA, we cannot communicate with
1678 # xapi, because has not started yet. These VDIs are raw.
1679 if vdi_type != vhdutil.VDI_TYPE_RAW: 1679 ↛ 1690line 1679 didn't jump to line 1690, because the condition on line 1679 was never false
1680 session = self.target.vdi.session
1681 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1682 # pylint: disable=used-before-assignment
1683 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1684 if 'key_hash' in sm_config: 1684 ↛ 1685line 1684 didn't jump to line 1685, because the condition on line 1684 was never true
1685 key_hash = sm_config['key_hash']
1686 options['key_hash'] = key_hash
1687 options['vdi_uuid'] = vdi_uuid
1688 util.SMlog('Using key with hash {} for VDI {}'.format(key_hash, vdi_uuid))
1689 # Activate the physical node
1690 dev_path = self._activate(sr_uuid, vdi_uuid, options)
1692 if hasattr(self.target.vdi.sr, 'DRIVER_TYPE') and \ 1692 ↛ 1695line 1692 didn't jump to line 1695, because the condition on line 1692 was never true
1693 self.target.vdi.sr.DRIVER_TYPE == 'lvhd' and \
1694 self.target.get_vdi_type() == vhdutil.VDI_TYPE_VHD:
1695 lock.release()
1696 except:
1697 util.SMlog("Exception in activate/attach")
1698 if self.tap_wanted():
1699 util.fistpoint.activate_custom_fn(
1700 "blktap_activate_error_handling",
1701 lambda: time.sleep(30))
1702 while True:
1703 try:
1704 self._remove_tag(vdi_uuid)
1705 break
1706 except xmlrpc.client.ProtocolError as e:
1707 # If there's a connection error, keep trying forever.
1708 if e.errcode == http.HTTPStatus.INTERNAL_SERVER_ERROR.value:
1709 continue
1710 else:
1711 util.SMlog('failed to remove tag: %s' % e)
1712 break
1713 except Exception as e:
1714 util.SMlog('failed to remove tag: %s' % e)
1715 break
1716 raise
1717 finally:
1718 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1719 self._session.xenapi.VDI.remove_from_sm_config(
1720 vdi_ref, 'activating')
1721 util.SMlog("Removed activating flag from %s" % vdi_uuid) 1721 ↛ exitline 1721 didn't except from function '_activate_locked', because the raise on line 1716 wasn't executed
1723 # Link result to backend/
1724 self.BackendLink.from_uuid(sr_uuid, vdi_uuid).mklink(dev_path)
1725 self.linkNBD(sr_uuid, vdi_uuid)
1726 return True
1728 def _activate(self, sr_uuid, vdi_uuid, options):
1729 vdi_options = self.target.activate(sr_uuid, vdi_uuid)
1731 dev_path = self.setup_cache(sr_uuid, vdi_uuid, options)
1732 if not dev_path: 1732 ↛ 1746line 1732 didn't jump to line 1746, because the condition on line 1732 was never false
1733 phy_path = self.PhyLink.from_uuid(sr_uuid, vdi_uuid).readlink()
1734 # Maybe launch a tapdisk on the physical link
1735 if self.tap_wanted(): 1735 ↛ 1744line 1735 didn't jump to line 1744, because the condition on line 1735 was never false
1736 vdi_type = self.target.get_vdi_type()
1737 options["o_direct"] = self.get_o_direct_capability(options)[0]
1738 if vdi_options: 1738 ↛ 1740line 1738 didn't jump to line 1740, because the condition on line 1738 was never false
1739 options.update(vdi_options)
1740 dev_path, self.tap = self._tap_activate(phy_path, vdi_type,
1741 sr_uuid, options,
1742 self._get_pool_config(sr_uuid).get("mem-pool-size"))
1743 else:
1744 dev_path = phy_path # Just reuse phy
1746 return dev_path
1748 def _attach(self, sr_uuid, vdi_uuid):
1749 attach_info = xmlrpc.client.loads(self.target.attach(sr_uuid, vdi_uuid))[0][0]
1750 params = attach_info['params']
1751 xenstore_data = attach_info['xenstore_data']
1752 phy_path = util.to_plain_string(params)
1753 self.xenstore_data.update(xenstore_data)
1754 # Save it to phy/
1755 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).mklink(phy_path)
1757 def deactivate(self, sr_uuid, vdi_uuid, caching_params):
1758 util.SMlog("blktap2.deactivate")
1759 for i in range(self.ATTACH_DETACH_RETRY_SECS):
1760 try:
1761 if self._deactivate_locked(sr_uuid, vdi_uuid, caching_params):
1762 return
1763 except util.SRBusyException as e:
1764 util.SMlog("SR locked, retrying")
1765 time.sleep(1)
1766 raise util.SMException("VDI %s locked" % vdi_uuid)
1768 @locking("VDIUnavailable")
1769 def _deactivate_locked(self, sr_uuid, vdi_uuid, caching_params):
1770 """Wraps target.deactivate and removes a tapdisk"""
1772 #util.SMlog("VDI.deactivate %s" % vdi_uuid)
1773 if self.tap_wanted() and not self._check_tag(vdi_uuid):
1774 return False
1776 self._deactivate(sr_uuid, vdi_uuid, caching_params)
1777 if self.target.has_cap("ATOMIC_PAUSE"):
1778 self._detach(sr_uuid, vdi_uuid)
1779 if self.tap_wanted():
1780 self._remove_tag(vdi_uuid)
1782 return True
1784 def _resetPhylink(self, sr_uuid, vdi_uuid, path):
1785 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).mklink(path)
1787 def detach(self, sr_uuid, vdi_uuid, deactivate=False, caching_params={}):
1788 if not self.target.has_cap("ATOMIC_PAUSE") or deactivate:
1789 util.SMlog("Deactivate & detach")
1790 self._deactivate(sr_uuid, vdi_uuid, caching_params)
1791 self._detach(sr_uuid, vdi_uuid)
1792 else:
1793 pass # nothing to do
1795 def _deactivate(self, sr_uuid, vdi_uuid, caching_params):
1796 # Shutdown tapdisk
1797 back_link = self.BackendLink.from_uuid(sr_uuid, vdi_uuid)
1799 if not util.pathexists(back_link.path()):
1800 util.SMlog("Backend path %s does not exist" % back_link.path())
1801 return
1803 try:
1804 attach_info_path = "%s.attach_info" % (back_link.path())
1805 os.unlink(attach_info_path)
1806 except:
1807 util.SMlog("unlink of attach_info failed")
1809 try:
1810 major, minor = back_link.rdev()
1811 except self.DeviceNode.NotABlockDevice:
1812 pass
1813 else:
1814 if major == Tapdisk.major():
1815 self._tap_deactivate(minor)
1816 self.remove_cache(caching_params)
1818 # Remove the backend link
1819 back_link.unlink()
1820 VDI.NBDLink.from_uuid(sr_uuid, vdi_uuid).unlink()
1822 # Deactivate & detach the physical node
1823 if self.tap_wanted() and self.target.vdi.session is not None:
1824 # it is possible that while the VDI was paused some of its
1825 # attributes have changed (e.g. its size if it was inflated; or its
1826 # path if it was leaf-coalesced onto a raw LV), so refresh the
1827 # object completely
1828 target = sm.VDI.from_uuid(self.target.vdi.session, vdi_uuid)
1829 driver_info = target.sr.srcmd.driver_info
1830 self.target = self.TargetDriver(target, driver_info)
1832 self.target.deactivate(sr_uuid, vdi_uuid)
1834 def _detach(self, sr_uuid, vdi_uuid):
1835 self.target.detach(sr_uuid, vdi_uuid)
1837 # Remove phy/
1838 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).unlink()
1840 def _updateCacheRecord(self, session, vdi_uuid, on_boot, caching):
1841 # Remove existing VDI.sm_config fields
1842 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1843 for key in ["on_boot", "caching"]:
1844 session.xenapi.VDI.remove_from_sm_config(vdi_ref, key)
1845 if not on_boot is None: 1845 ↛ 1846line 1845 didn't jump to line 1846, because the condition on line 1845 was never true
1846 session.xenapi.VDI.add_to_sm_config(vdi_ref, 'on_boot', on_boot)
1847 if not caching is None:
1848 session.xenapi.VDI.add_to_sm_config(vdi_ref, 'caching', caching)
1850 def setup_cache(self, sr_uuid, vdi_uuid, params):
1851 if params.get(self.CONF_KEY_ALLOW_CACHING) != "true":
1852 return
1854 util.SMlog("Requested local caching")
1855 if not self.target.has_cap("SR_CACHING"):
1856 util.SMlog("Error: local caching not supported by this SR")
1857 return
1859 scratch_mode = False
1860 if params.get(self.CONF_KEY_MODE_ON_BOOT) == "reset":
1861 scratch_mode = True
1862 util.SMlog("Requested scratch mode")
1863 if not self.target.has_cap("VDI_RESET_ON_BOOT/2"): 1863 ↛ 1867line 1863 didn't jump to line 1867, because the condition on line 1863 was never false
1864 util.SMlog("Error: scratch mode not supported by this SR")
1865 return
1867 dev_path = None
1868 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR)
1869 if not local_sr_uuid:
1870 util.SMlog("ERROR: Local cache SR not specified, not enabling")
1871 return
1872 dev_path = self._setup_cache(self._session, sr_uuid, vdi_uuid,
1873 local_sr_uuid, scratch_mode, params)
1875 if dev_path:
1876 self._updateCacheRecord(self._session, self.target.vdi.uuid,
1877 params.get(self.CONF_KEY_MODE_ON_BOOT),
1878 params.get(self.CONF_KEY_ALLOW_CACHING))
1880 return dev_path
1882 def alert_no_cache(self, session, vdi_uuid, cache_sr_uuid, err):
1883 vm_uuid = None
1884 vm_label = ""
1885 try:
1886 cache_sr_ref = session.xenapi.SR.get_by_uuid(cache_sr_uuid)
1887 cache_sr_rec = session.xenapi.SR.get_record(cache_sr_ref)
1888 cache_sr_label = cache_sr_rec.get("name_label")
1890 host_ref = session.xenapi.host.get_by_uuid(util.get_this_host())
1891 host_rec = session.xenapi.host.get_record(host_ref)
1892 host_label = host_rec.get("name_label")
1894 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1895 vbds = session.xenapi.VBD.get_all_records_where( \
1896 "field \"VDI\" = \"%s\"" % vdi_ref)
1897 for vbd_rec in vbds.values():
1898 vm_ref = vbd_rec.get("VM")
1899 vm_rec = session.xenapi.VM.get_record(vm_ref)
1900 vm_uuid = vm_rec.get("uuid")
1901 vm_label = vm_rec.get("name_label")
1902 except:
1903 util.logException("alert_no_cache")
1905 alert_obj = "SR"
1906 alert_uuid = str(cache_sr_uuid)
1907 alert_str = "No space left in Local Cache SR %s" % cache_sr_uuid
1908 if vm_uuid:
1909 alert_obj = "VM"
1910 alert_uuid = vm_uuid
1911 reason = ""
1912 if err == errno.ENOSPC:
1913 reason = "because there is no space left"
1914 alert_str = "The VM \"%s\" is not using IntelliCache %s on the Local Cache SR (\"%s\") on host \"%s\"" % \
1915 (vm_label, reason, cache_sr_label, host_label)
1917 util.SMlog("Creating alert: (%s, %s, \"%s\")" % \
1918 (alert_obj, alert_uuid, alert_str))
1919 session.xenapi.message.create("No space left in local cache", "3",
1920 alert_obj, alert_uuid, alert_str)
1922 def _setup_cache(self, session, sr_uuid, vdi_uuid, local_sr_uuid,
1923 scratch_mode, options):
1924 import SR
1925 import EXTSR
1927 if self._no_parent(self.target.vdi): 1927 ↛ 1928line 1927 didn't jump to line 1928, because the condition on line 1927 was never true
1928 util.SMlog("ERROR: VDI %s has no parent, not enabling" %
1929 self.target.vdi.uuid)
1930 return
1932 util.SMlog("Setting up cache")
1933 shared_target = self.target.vdi.sr.vdi(self.target.vdi.parent)
1935 if shared_target.parent:
1936 util.SMlog("ERROR: Parent VDI %s has parent, not enabling" %
1937 shared_target.uuid)
1938 return
1940 SR.registerSR(EXTSR.EXTSR)
1941 local_sr = SR.SR.from_uuid(session, local_sr_uuid)
1943 lock = Lock(self.LOCK_CACHE_SETUP, shared_target.uuid)
1944 lock.acquire()
1946 # read cache
1947 read_cache_path = "%s/%s.vhdcache" % (local_sr.path, shared_target.uuid)
1948 if util.pathexists(read_cache_path): 1948 ↛ 1952line 1948 didn't jump to line 1952, because the condition on line 1948 was never false
1949 util.SMlog("Read cache node (%s) already exists, not creating" %
1950 read_cache_path)
1951 else:
1952 try:
1953 vhdutil.snapshot(read_cache_path, shared_target.path, False)
1954 except util.CommandException as e:
1955 util.SMlog("Error creating parent cache: %s" % e)
1956 self.alert_no_cache(session, vdi_uuid, local_sr_uuid, e.code)
1957 return None
1959 # local write node
1960 leaf_size = vhdutil.getSizeVirt(self.target.vdi.path)
1961 local_leaf_path = "%s/%s.vhdcache" % \
1962 (local_sr.path, self.target.vdi.uuid)
1963 if util.pathexists(local_leaf_path): 1963 ↛ 1967line 1963 didn't jump to line 1967, because the condition on line 1963 was never false
1964 util.SMlog("Local leaf node (%s) already exists, deleting" %
1965 local_leaf_path)
1966 os.unlink(local_leaf_path)
1967 try:
1968 vhdutil.snapshot(local_leaf_path, read_cache_path, False,
1969 msize=leaf_size // 1024 // 1024, checkEmpty=False)
1970 except util.CommandException as e:
1971 util.SMlog("Error creating leaf cache: %s" % e)
1972 self.alert_no_cache(session, vdi_uuid, local_sr_uuid, e.code)
1973 return None
1975 local_leaf_size = vhdutil.getSizeVirt(local_leaf_path)
1976 if leaf_size > local_leaf_size: 1976 ↛ 1977line 1976 didn't jump to line 1977, because the condition on line 1976 was never true
1977 util.SMlog("Leaf size %d > local leaf cache size %d, resizing" %
1978 (leaf_size, local_leaf_size))
1979 vhdutil.setSizeVirtFast(local_leaf_path, leaf_size)
1981 prt_tapdisk = Tapdisk.find_by_path(read_cache_path)
1982 if not prt_tapdisk:
1983 parent_options = copy.deepcopy(options)
1984 parent_options["rdonly"] = False
1985 parent_options["lcache"] = True
1987 blktap = Blktap.allocate()
1988 try:
1989 blktap.set_pool_name("lcache-parent-pool-%s" % blktap.minor)
1990 # no need to change pool_size since each parent tapdisk is in
1991 # its own pool
1992 prt_tapdisk = \
1993 Tapdisk.launch_on_tap(blktap, read_cache_path,
1994 'vhd', parent_options)
1995 except:
1996 blktap.free()
1997 raise
1999 secondary = "%s:%s" % (self.target.get_vdi_type(),
2000 (self.PhyLink.from_uuid(sr_uuid, vdi_uuid).readlink()))
2002 util.SMlog("Parent tapdisk: %s" % prt_tapdisk)
2003 leaf_tapdisk = Tapdisk.find_by_path(local_leaf_path)
2004 if not leaf_tapdisk: 2004 ↛ 2022line 2004 didn't jump to line 2022, because the condition on line 2004 was never false
2005 blktap = Blktap.allocate()
2006 child_options = copy.deepcopy(options)
2007 child_options["rdonly"] = False
2008 child_options["lcache"] = (not scratch_mode)
2009 child_options["existing_prt"] = prt_tapdisk.minor
2010 child_options["secondary"] = secondary
2011 child_options["standby"] = scratch_mode
2012 # Disable memory read caching
2013 child_options.pop("o_direct", None)
2014 try:
2015 leaf_tapdisk = \
2016 Tapdisk.launch_on_tap(blktap, local_leaf_path,
2017 'vhd', child_options)
2018 except:
2019 blktap.free()
2020 raise
2022 lock.release()
2024 util.SMlog("Local read cache: %s, local leaf: %s" %
2025 (read_cache_path, local_leaf_path))
2027 self.tap = leaf_tapdisk
2028 return leaf_tapdisk.get_devpath()
2030 def remove_cache(self, params):
2031 if not self.target.has_cap("SR_CACHING"):
2032 return
2034 caching = params.get(self.CONF_KEY_ALLOW_CACHING) == "true"
2036 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR)
2037 if caching and not local_sr_uuid:
2038 util.SMlog("ERROR: Local cache SR not specified, ignore")
2039 return
2041 if caching: 2041 ↛ 2044line 2041 didn't jump to line 2044, because the condition on line 2041 was never false
2042 self._remove_cache(self._session, local_sr_uuid)
2044 if self._session is not None: 2044 ↛ exitline 2044 didn't return from function 'remove_cache', because the condition on line 2044 was never false
2045 self._updateCacheRecord(self._session, self.target.vdi.uuid, None, None)
2047 def _is_tapdisk_in_use(self, minor):
2048 retVal, links, sockets = util.findRunningProcessOrOpenFile("tapdisk")
2049 if not retVal:
2050 # err on the side of caution
2051 return True
2053 for link in links:
2054 if link.find("tapdev%d" % minor) != -1:
2055 return True
2057 socket_re = re.compile(r'^/.*/nbd\d+\.%d' % minor)
2058 for s in sockets:
2059 if socket_re.match(s):
2060 return True
2062 return False
2064 def _remove_cache(self, session, local_sr_uuid):
2065 import SR
2066 import EXTSR
2068 if self._no_parent(self.target.vdi):
2069 util.SMlog("ERROR: No parent for VDI %s, ignore" %
2070 self.target.vdi.uuid)
2071 return
2073 util.SMlog("Tearing down the cache")
2075 shared_target = self.target.vdi.sr.vdi(self.target.vdi.parent)
2077 SR.registerSR(EXTSR.EXTSR)
2078 local_sr = SR.SR.from_uuid(session, local_sr_uuid)
2080 lock = Lock(self.LOCK_CACHE_SETUP, shared_target.uuid)
2081 lock.acquire()
2083 # local write node
2084 local_leaf_path = "%s/%s.vhdcache" % \
2085 (local_sr.path, self.target.vdi.uuid)
2086 if util.pathexists(local_leaf_path): 2086 ↛ 2090line 2086 didn't jump to line 2090, because the condition on line 2086 was never false
2087 util.SMlog("Deleting local leaf node %s" % local_leaf_path)
2088 os.unlink(local_leaf_path)
2090 read_cache_path = "%s/%s.vhdcache" % (local_sr.path, shared_target.uuid)
2091 prt_tapdisk = Tapdisk.find_by_path(read_cache_path)
2092 if not prt_tapdisk: 2092 ↛ 2093line 2092 didn't jump to line 2093, because the condition on line 2092 was never true
2093 util.SMlog("Parent tapdisk not found")
2094 elif not self._is_tapdisk_in_use(prt_tapdisk.minor): 2094 ↛ 2102line 2094 didn't jump to line 2102, because the condition on line 2094 was never false
2095 util.SMlog("Parent tapdisk not in use: shutting down %s" %
2096 read_cache_path)
2097 try:
2098 prt_tapdisk.shutdown()
2099 except:
2100 util.logException("shutting down parent tapdisk")
2101 else:
2102 util.SMlog("Parent tapdisk still in use: %s" % read_cache_path)
2103 # the parent cache files are removed during the local SR's background
2104 # GC run
2106 lock.release()
2108 @staticmethod
2109 def _no_parent(vdi):
2110 return vdi.parent is None or vdi.parent == ''
2113PythonKeyError = KeyError
2116class UEventHandler(object):
2118 def __init__(self):
2119 self._action = None
2121 class KeyError(PythonKeyError):
2122 def __init__(self, args):
2123 super().__init__(args)
2124 self.key = args[0]
2126 @override
2127 def __str__(self) -> str:
2128 return \
2129 "Key '%s' missing in environment. " % self.key + \
2130 "Not called in udev context?"
2132 @classmethod
2133 def getenv(cls, key):
2134 try:
2135 return os.environ[key]
2136 except KeyError as e:
2137 raise cls.KeyError(e.args[0])
2139 def get_action(self):
2140 if not self._action:
2141 self._action = self.getenv('ACTION')
2142 return self._action
2144 class UnhandledEvent(Exception):
2146 def __init__(self, event, handler):
2147 self.event = event
2148 self.handler = handler
2150 @override
2151 def __str__(self) -> str:
2152 return "Uevent '%s' not handled by %s" % \
2153 (self.event, self.handler.__class__.__name__)
2155 ACTIONS: Dict[str, Callable] = {}
2157 def run(self):
2159 action = self.get_action()
2160 try:
2161 fn = self.ACTIONS[action]
2162 except KeyError:
2163 raise self.UnhandledEvent(action, self)
2165 return fn(self)
2167 @override
2168 def __str__(self) -> str:
2169 try:
2170 action = self.get_action()
2171 except:
2172 action = None
2173 return "%s[%s]" % (self.__class__.__name__, action)
2176class __BlktapControl(ClassDevice):
2177 SYSFS_CLASSTYPE = "misc"
2179 def __init__(self):
2180 ClassDevice.__init__(self)
2181 self._default_pool = None
2183 @override
2184 def sysfs_devname(self) -> str:
2185 return "blktap!control"
2187 class DefaultPool(Attribute):
2188 SYSFS_NODENAME = "default_pool"
2190 def get_default_pool_attr(self):
2191 if not self._default_pool:
2192 self._default_pool = self.DefaultPool.from_kobject(self)
2193 return self._default_pool
2195 def get_default_pool_name(self):
2196 return self.get_default_pool_attr().readline()
2198 def set_default_pool_name(self, name):
2199 self.get_default_pool_attr().writeline(name)
2201 def get_default_pool(self):
2202 return BlktapControl.get_pool(self.get_default_pool_name())
2204 def set_default_pool(self, pool):
2205 self.set_default_pool_name(pool.name)
2207 class NoSuchPool(Exception):
2208 def __init__(self, name):
2209 self.name = name
2211 @override
2212 def __str__(self) -> str:
2213 return "No such pool: {}".format(self.name)
2215 def get_pool(self, name):
2216 path = "%s/pools/%s" % (self.sysfs_path(), name)
2218 if not os.path.isdir(path):
2219 raise self.NoSuchPool(name)
2221 return PagePool(path)
2223BlktapControl = __BlktapControl()
2226class PagePool(KObject):
2228 def __init__(self, path):
2229 self.path = path
2230 self._size = None
2232 @override
2233 def sysfs_devname(self) -> str:
2234 return ''
2236 def sysfs_path(self):
2237 return self.path
2239 class Size(Attribute):
2240 SYSFS_NODENAME = "size"
2242 def get_size_attr(self):
2243 if not self._size:
2244 self._size = self.Size.from_kobject(self)
2245 return self._size
2247 def set_size(self, pages):
2248 pages = str(pages)
2249 self.get_size_attr().writeline(pages)
2251 def get_size(self):
2252 pages = self.get_size_attr().readline()
2253 return int(pages)
2256class BusDevice(KObject):
2258 SYSFS_BUSTYPE: ClassVar[str] = ""
2260 @classmethod
2261 def sysfs_bus_path(cls):
2262 return "/sys/bus/%s" % cls.SYSFS_BUSTYPE
2264 def sysfs_path(self):
2265 path = "%s/devices/%s" % (self.sysfs_bus_path(),
2266 self.sysfs_devname())
2268 return path
2271class XenbusDevice(BusDevice):
2272 """Xenbus device, in XS and sysfs"""
2274 XBT_NIL = ""
2276 XENBUS_DEVTYPE: ClassVar[str] = ""
2278 def __init__(self, domid, devid):
2279 self.domid = int(domid)
2280 self.devid = int(devid)
2281 self._xbt = XenbusDevice.XBT_NIL
2283 import xen.lowlevel.xs # pylint: disable=import-error
2284 self.xs = xen.lowlevel.xs.xs()
2286 def xs_path(self, key=None):
2287 path = "backend/%s/%d/%d" % (self.XENBUS_DEVTYPE,
2288 self.domid,
2289 self.devid)
2290 if key is not None:
2291 path = "%s/%s" % (path, key)
2293 return path
2295 def _log(self, prio, msg):
2296 syslog(prio, msg)
2298 def info(self, msg):
2299 self._log(_syslog.LOG_INFO, msg)
2301 def warn(self, msg):
2302 self._log(_syslog.LOG_WARNING, "WARNING: " + msg)
2304 def _xs_read_path(self, path):
2305 val = self.xs.read(self._xbt, path)
2306 #self.info("read %s = '%s'" % (path, val))
2307 return val
2309 def _xs_write_path(self, path, val):
2310 self.xs.write(self._xbt, path, val)
2311 self.info("wrote %s = '%s'" % (path, val))
2313 def _xs_rm_path(self, path):
2314 self.xs.rm(self._xbt, path)
2315 self.info("removed %s" % path)
2317 def read(self, key):
2318 return self._xs_read_path(self.xs_path(key))
2320 def has_xs_key(self, key):
2321 return self.read(key) is not None
2323 def write(self, key, val):
2324 self._xs_write_path(self.xs_path(key), val)
2326 def rm(self, key):
2327 self._xs_rm_path(self.xs_path(key))
2329 def exists(self):
2330 return self.has_xs_key(None)
2332 def begin(self):
2333 assert(self._xbt == XenbusDevice.XBT_NIL)
2334 self._xbt = self.xs.transaction_start()
2336 def commit(self):
2337 ok = self.xs.transaction_end(self._xbt, 0)
2338 self._xbt = XenbusDevice.XBT_NIL
2339 return ok
2341 def abort(self):
2342 ok = self.xs.transaction_end(self._xbt, 1)
2343 assert(ok == True)
2344 self._xbt = XenbusDevice.XBT_NIL
2346 def create_physical_device(self):
2347 """The standard protocol is: toolstack writes 'params', linux hotplug
2348 script translates this into physical-device=%x:%x"""
2349 if self.has_xs_key("physical-device"):
2350 return
2351 try:
2352 params = self.read("params")
2353 frontend = self.read("frontend")
2354 is_cdrom = self._xs_read_path("%s/device-type") == "cdrom"
2355 # We don't have PV drivers for CDROM devices, so we prevent blkback
2356 # from opening the physical-device
2357 if not(is_cdrom):
2358 major_minor = os.stat(params).st_rdev
2359 major, minor = divmod(major_minor, 256)
2360 self.write("physical-device", "%x:%x" % (major, minor))
2361 except:
2362 util.logException("BLKTAP2:create_physical_device")
2364 def signal_hotplug(self, online=True):
2365 xapi_path = "/xapi/%d/hotplug/%s/%d/hotplug" % (self.domid,
2366 self.XENBUS_DEVTYPE,
2367 self.devid)
2368 upstream_path = self.xs_path("hotplug-status")
2369 if online:
2370 self._xs_write_path(xapi_path, "online")
2371 self._xs_write_path(upstream_path, "connected")
2372 else:
2373 self._xs_rm_path(xapi_path)
2374 self._xs_rm_path(upstream_path)
2376 @override
2377 def sysfs_devname(self) -> str:
2378 return "%s-%d-%d" % (self.XENBUS_DEVTYPE,
2379 self.domid, self.devid)
2381 @override
2382 def __str__(self) -> str:
2383 return self.sysfs_devname()
2385 @classmethod
2386 def find(cls):
2387 pattern = "/sys/bus/%s/devices/%s*" % (cls.SYSFS_BUSTYPE,
2388 cls.XENBUS_DEVTYPE)
2389 for path in glob.glob(pattern):
2391 name = os.path.basename(path)
2392 (_type, domid, devid) = name.split('-')
2394 yield cls(domid, devid)
2397class XenBackendDevice(XenbusDevice):
2398 """Xenbus backend device"""
2399 SYSFS_BUSTYPE = "xen-backend"
2401 @classmethod
2402 def from_xs_path(cls, _path):
2403 (_backend, _type, domid, devid) = _path.split('/')
2405 assert _backend == 'backend'
2406 assert _type == cls.XENBUS_DEVTYPE
2408 domid = int(domid)
2409 devid = int(devid)
2411 return cls(domid, devid)
2414class Blkback(XenBackendDevice):
2415 """A blkback VBD"""
2417 XENBUS_DEVTYPE = "vbd"
2419 def __init__(self, domid, devid):
2420 XenBackendDevice.__init__(self, domid, devid)
2421 self._phy = None
2422 self._vdi_uuid = None
2423 self._q_state = None
2424 self._q_events = None
2426 class XenstoreValueError(Exception):
2427 KEY: ClassVar[str] = ""
2429 def __init__(self, vbd, _str):
2430 self.vbd = vbd
2431 self.str = _str
2433 @override
2434 def __str__(self) -> str:
2435 return "Backend %s " % self.vbd + \
2436 "has %s = %s" % (self.KEY, self.str)
2438 class PhysicalDeviceError(XenstoreValueError):
2439 KEY = "physical-device"
2441 class PhysicalDevice(object):
2443 def __init__(self, major, minor):
2444 self.major = int(major)
2445 self.minor = int(minor)
2447 @classmethod
2448 def from_xbdev(cls, xbdev):
2450 phy = xbdev.read("physical-device")
2452 try:
2453 major, minor = phy.split(':')
2454 major = int(major, 0x10)
2455 minor = int(minor, 0x10)
2456 except Exception as e:
2457 raise xbdev.PhysicalDeviceError(xbdev, phy)
2459 return cls(major, minor)
2461 def makedev(self):
2462 return os.makedev(self.major, self.minor)
2464 def is_tap(self):
2465 return self.major == Tapdisk.major()
2467 @override
2468 def __str__(self) -> str:
2469 return "%s:%s" % (self.major, self.minor)
2471 @override
2472 def __eq__(self, other) -> bool:
2473 return \
2474 self.major == other.major and \
2475 self.minor == other.minor
2477 def get_physical_device(self):
2478 if not self._phy:
2479 self._phy = self.PhysicalDevice.from_xbdev(self)
2480 return self._phy
2482 class QueueEvents(Attribute):
2483 """Blkback sysfs node to select queue-state event
2484 notifications emitted."""
2486 SYSFS_NODENAME = "queue_events"
2488 QUEUE_RUNNING = (1 << 0)
2489 QUEUE_PAUSE_DONE = (1 << 1)
2490 QUEUE_SHUTDOWN_DONE = (1 << 2)
2491 QUEUE_PAUSE_REQUEST = (1 << 3)
2492 QUEUE_SHUTDOWN_REQUEST = (1 << 4)
2494 def get_mask(self):
2495 return int(self.readline(), 0x10)
2497 def set_mask(self, mask):
2498 self.writeline("0x%x" % mask)
2500 def get_queue_events(self):
2501 if not self._q_events:
2502 self._q_events = self.QueueEvents.from_kobject(self)
2503 return self._q_events
2505 def get_vdi_uuid(self):
2506 if not self._vdi_uuid:
2507 self._vdi_uuid = self.read("sm-data/vdi-uuid")
2508 return self._vdi_uuid
2510 def pause_requested(self):
2511 return self.has_xs_key("pause")
2513 def shutdown_requested(self):
2514 return self.has_xs_key("shutdown-request")
2516 def shutdown_done(self):
2517 return self.has_xs_key("shutdown-done")
2519 def running(self):
2520 return self.has_xs_key('queue-0/kthread-pid')
2522 @classmethod
2523 def find_by_physical_device(cls, phy):
2524 for dev in cls.find():
2525 try:
2526 _phy = dev.get_physical_device()
2527 except cls.PhysicalDeviceError:
2528 continue
2530 if _phy == phy:
2531 yield dev
2533 @classmethod
2534 def find_by_tap_minor(cls, minor):
2535 phy = cls.PhysicalDevice(Tapdisk.major(), minor)
2536 return cls.find_by_physical_device(phy)
2538 @classmethod
2539 def find_by_tap(cls, tapdisk):
2540 return cls.find_by_tap_minor(tapdisk.minor)
2542 def has_tap(self):
2544 if not self.can_tap():
2545 return False
2547 phy = self.get_physical_device()
2548 if phy:
2549 return phy.is_tap()
2551 return False
2553 def is_bare_hvm(self):
2554 """File VDIs for bare HVM. These are directly accessible by Qemu."""
2555 try:
2556 self.get_physical_device()
2558 except self.PhysicalDeviceError as e:
2559 vdi_type = self.read("type")
2561 self.info("HVM VDI: type=%s" % vdi_type)
2563 if e.str is not None or vdi_type != 'file':
2564 raise
2566 return True
2568 return False
2570 def can_tap(self):
2571 return not self.is_bare_hvm()
2574class BlkbackEventHandler(UEventHandler):
2576 LOG_FACILITY = _syslog.LOG_DAEMON
2578 def __init__(self, ident=None, action=None):
2579 if not ident:
2580 ident = self.__class__.__name__
2582 self.ident = ident
2583 self._vbd = None
2584 self._tapdisk = None
2586 UEventHandler.__init__(self)
2588 @override
2589 def run(self) -> None:
2591 self.xs_path = self.getenv('XENBUS_PATH')
2592 openlog(str(self), 0, self.LOG_FACILITY)
2594 UEventHandler.run(self)
2596 @override
2597 def __str__(self) -> str:
2599 try:
2600 path = self.xs_path
2601 except:
2602 path = None
2604 try:
2605 action = self.get_action()
2606 except:
2607 action = None
2609 return "%s[%s](%s)" % (self.ident, action, path)
2611 def _log(self, prio, msg):
2612 syslog(prio, msg)
2613 util.SMlog("%s: " % self + msg)
2615 def info(self, msg):
2616 self._log(_syslog.LOG_INFO, msg)
2618 def warn(self, msg):
2619 self._log(_syslog.LOG_WARNING, "WARNING: " + msg)
2621 def error(self, msg):
2622 self._log(_syslog.LOG_ERR, "ERROR: " + msg)
2624 def get_vbd(self):
2625 if not self._vbd:
2626 self._vbd = Blkback.from_xs_path(self.xs_path)
2627 return self._vbd
2629 def get_tapdisk(self):
2630 if not self._tapdisk:
2631 minor = self.get_vbd().get_physical_device().minor
2632 self._tapdisk = Tapdisk.from_minor(minor)
2633 return self._tapdisk
2634 #
2635 # Events
2636 #
2638 def __add(self):
2639 vbd = self.get_vbd()
2640 # Manage blkback transitions
2641 # self._manage_vbd()
2643 vbd.create_physical_device()
2645 vbd.signal_hotplug()
2647 @retried(backoff=.5, limit=10)
2648 def add(self):
2649 try:
2650 self.__add()
2651 except Attribute.NoSuchAttribute as e:
2652 #
2653 # FIXME: KOBJ_ADD is racing backend.probe, which
2654 # registers device attributes. So poll a little.
2655 #
2656 self.warn("%s, still trying." % e)
2657 raise RetryLoop.TransientFailure(e)
2659 def __change(self):
2660 vbd = self.get_vbd()
2662 # 1. Pause or resume tapdisk (if there is one)
2664 if vbd.has_tap():
2665 pass
2666 #self._pause_update_tap()
2668 # 2. Signal Xapi.VBD.pause/resume completion
2670 self._signal_xapi()
2672 def change(self):
2673 vbd = self.get_vbd()
2675 # NB. Beware of spurious change events between shutdown
2676 # completion and device removal. Also, Xapi.VM.migrate will
2677 # hammer a couple extra shutdown-requests into the source VBD.
2679 while True:
2680 vbd.begin()
2682 if not vbd.exists() or \
2683 vbd.shutdown_done():
2684 break
2686 self.__change()
2688 if vbd.commit():
2689 return
2691 vbd.abort()
2692 self.info("spurious uevent, ignored.")
2694 def remove(self):
2695 vbd = self.get_vbd()
2697 vbd.signal_hotplug(False)
2699 ACTIONS = {'add': add,
2700 'change': change,
2701 'remove': remove}
2702 #
2703 # VDI.pause
2704 #
2706 def _tap_should_pause(self):
2707 """Enumerate all VBDs on our tapdisk. Returns true iff any was
2708 paused"""
2710 tapdisk = self.get_tapdisk()
2711 TapState = Tapdisk.PauseState
2713 PAUSED = 'P'
2714 RUNNING = 'R'
2715 PAUSED_SHUTDOWN = 'P,S'
2716 # NB. Shutdown/paused is special. We know it's not going
2717 # to restart again, so it's a RUNNING. Still better than
2718 # backtracking a removed device during Vbd.unplug completion.
2720 next = TapState.RUNNING
2721 vbds = {}
2723 for vbd in Blkback.find_by_tap(tapdisk):
2724 name = str(vbd)
2726 pausing = vbd.pause_requested()
2727 closing = vbd.shutdown_requested()
2728 running = vbd.running()
2730 if pausing:
2731 if closing and not running:
2732 vbds[name] = PAUSED_SHUTDOWN
2733 else:
2734 vbds[name] = PAUSED
2735 next = TapState.PAUSED
2737 else:
2738 vbds[name] = RUNNING
2740 self.info("tapdev%d (%s): %s -> %s"
2741 % (tapdisk.minor, tapdisk.pause_state(),
2742 vbds, next))
2744 return next == TapState.PAUSED
2746 def _pause_update_tap(self):
2747 vbd = self.get_vbd()
2749 if self._tap_should_pause():
2750 self._pause_tap()
2751 else:
2752 self._resume_tap()
2754 def _pause_tap(self):
2755 tapdisk = self.get_tapdisk()
2757 if not tapdisk.is_paused():
2758 self.info("pausing %s" % tapdisk)
2759 tapdisk.pause()
2761 def _resume_tap(self):
2762 tapdisk = self.get_tapdisk()
2764 # NB. Raw VDI snapshots. Refresh the physical path and
2765 # type while resuming.
2766 vbd = self.get_vbd()
2767 vdi_uuid = vbd.get_vdi_uuid()
2769 if tapdisk.is_paused():
2770 self.info("loading vdi uuid=%s" % vdi_uuid)
2771 vdi = VDI.from_cli(vdi_uuid)
2772 _type = vdi.get_tap_type()
2773 path = vdi.get_phy_path()
2774 self.info("resuming %s on %s:%s" % (tapdisk, _type, path))
2775 tapdisk.unpause(_type, path)
2776 #
2777 # VBD.pause/shutdown
2778 #
2780 def _manage_vbd(self):
2781 vbd = self.get_vbd()
2782 # NB. Hook into VBD state transitions.
2784 events = vbd.get_queue_events()
2786 mask = 0
2787 mask |= events.QUEUE_PAUSE_DONE # pause/unpause
2788 mask |= events.QUEUE_SHUTDOWN_DONE # shutdown
2789 # TODO: mask |= events.QUEUE_SHUTDOWN_REQUEST, for shutdown=force
2790 # TODO: mask |= events.QUEUE_RUNNING, for ionice updates etc
2792 events.set_mask(mask)
2793 self.info("wrote %s = %#02x" % (events.path, mask))
2795 def _signal_xapi(self):
2796 vbd = self.get_vbd()
2798 pausing = vbd.pause_requested()
2799 closing = vbd.shutdown_requested()
2800 running = vbd.running()
2802 handled = 0
2804 if pausing and not running:
2805 if 'pause-done' not in vbd:
2806 vbd.write('pause-done', '')
2807 handled += 1
2809 if not pausing:
2810 if 'pause-done' in vbd:
2811 vbd.rm('pause-done')
2812 handled += 1
2814 if closing and not running:
2815 if 'shutdown-done' not in vbd:
2816 vbd.write('shutdown-done', '')
2817 handled += 1
2819 if handled > 1:
2820 self.warn("handled %d events, " % handled +
2821 "pausing=%s closing=%s running=%s" % \
2822 (pausing, closing, running))
2824if __name__ == '__main__': 2824 ↛ 2826line 2824 didn't jump to line 2826, because the condition on line 2824 was never true
2826 import sys
2827 prog = os.path.basename(sys.argv[0])
2829 #
2830 # Simple CLI interface for manual operation
2831 #
2832 # tap.* level calls go down to local Tapdisk()s (by physical path)
2833 # vdi.* level calls run the plugin calls across host boundaries.
2834 #
2836 def usage(stream):
2837 print("usage: %s tap.{list|major}" % prog, file=stream)
2838 print(" %s tap.{launch|find|get|pause|" % prog + \
2839 "unpause|shutdown|stats} {[<tt>:]<path>} | [minor=]<int> | .. }", file=stream)
2840 print(" %s vbd.uevent" % prog, file=stream)
2842 try:
2843 cmd = sys.argv[1]
2844 except IndexError:
2845 usage(sys.stderr)
2846 sys.exit(1)
2848 try:
2849 _class, method = cmd.split('.')
2850 except:
2851 usage(sys.stderr)
2852 sys.exit(1)
2854 #
2855 # Local Tapdisks
2856 #
2858 if cmd == 'tap.major':
2860 print("%d" % Tapdisk.major())
2862 elif cmd == 'tap.launch':
2864 tapdisk = Tapdisk.launch_from_arg(sys.argv[2])
2865 print("Launched %s" % tapdisk, file=sys.stderr)
2867 elif _class == 'tap':
2869 attrs: Dict[str, Any] = {}
2870 for item in sys.argv[2:]:
2871 try:
2872 key, val = item.split('=')
2873 attrs[key] = val
2874 continue
2875 except ValueError:
2876 pass
2878 try:
2879 attrs['minor'] = int(item)
2880 continue
2881 except ValueError:
2882 pass
2884 try:
2885 arg = Tapdisk.Arg.parse(item)
2886 attrs['_type'] = arg.type
2887 attrs['path'] = arg.path
2888 continue
2889 except Tapdisk.Arg.InvalidArgument:
2890 pass
2892 attrs['path'] = item
2894 if cmd == 'tap.list':
2896 for tapdisk in Tapdisk.list( ** attrs):
2897 blktap = tapdisk.get_blktap()
2898 print(tapdisk, end=' ')
2899 print("%s: task=%s pool=%s" % \
2900 (blktap,
2901 blktap.get_task_pid(),
2902 blktap.get_pool_name()))
2904 elif cmd == 'tap.vbds':
2905 # Find all Blkback instances for a given tapdisk
2907 for tapdisk in Tapdisk.list( ** attrs):
2908 print("%s:" % tapdisk, end=' ')
2909 for vbd in Blkback.find_by_tap(tapdisk):
2910 print(vbd, end=' ')
2911 print()
2913 else:
2915 if not attrs:
2916 usage(sys.stderr)
2917 sys.exit(1)
2919 try:
2920 tapdisk = Tapdisk.get( ** attrs)
2921 except TypeError:
2922 usage(sys.stderr)
2923 sys.exit(1)
2925 if cmd == 'tap.shutdown':
2926 # Shutdown a running tapdisk, or raise
2927 tapdisk.shutdown()
2928 print("Shut down %s" % tapdisk, file=sys.stderr)
2930 elif cmd == 'tap.pause':
2931 # Pause an unpaused tapdisk, or raise
2932 tapdisk.pause()
2933 print("Paused %s" % tapdisk, file=sys.stderr)
2935 elif cmd == 'tap.unpause':
2936 # Unpause a paused tapdisk, or raise
2937 tapdisk.unpause()
2938 print("Unpaused %s" % tapdisk, file=sys.stderr)
2940 elif cmd == 'tap.stats':
2941 # Gather tapdisk status
2942 stats = tapdisk.stats()
2943 print("%s:" % tapdisk)
2944 print(json.dumps(stats, indent=True))
2946 else:
2947 usage(sys.stderr)
2948 sys.exit(1)
2950 elif cmd == 'vbd.uevent':
2952 hnd = BlkbackEventHandler(cmd)
2954 if not sys.stdin.isatty():
2955 try:
2956 hnd.run()
2957 except Exception as e:
2958 hnd.error("Unhandled Exception: %s" % e)
2960 import traceback
2961 _type, value, tb = sys.exc_info()
2962 trace = traceback.format_exception(_type, value, tb)
2963 for entry in trace:
2964 for line in entry.rstrip().split('\n'):
2965 util.SMlog(line)
2966 else:
2967 hnd.run()
2969 elif cmd == 'vbd.list':
2971 for vbd in Blkback.find():
2972 print(vbd, \
2973 "physical-device=%s" % vbd.get_physical_device(), \
2974 "pause=%s" % vbd.pause_requested())
2976 else:
2977 usage(sys.stderr)
2978 sys.exit(1)