Coverage for drivers/blktap2.py : 47%
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, List, Union
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 constants import NS_PREFIX_LVM
45from syslog import openlog, syslog
46from stat import * # S_ISBLK(), ...
47from vditype import VdiType
49import resetvdis
51import VDI as sm
53from cowutil import getCowUtil
55# For RRDD Plugin Registration
56from xmlrpc.client import ServerProxy, Transport
57from socket import socket, AF_UNIX, SOCK_STREAM
60try:
61 from linstorvolumemanager import log_drbd_openers
62 LINSTOR_AVAILABLE = True
63except ImportError:
64 LINSTOR_AVAILABLE = False
66PLUGIN_TAP_PAUSE = "tapdisk-pause"
67PLUGIN_ON_SLAVE = "on-slave"
69SOCKPATH = "/var/xapi/xcp-rrdd"
71NUM_PAGES_PER_RING = 32 * 11
72MAX_FULL_RINGS = 8
73POOL_NAME_KEY = "mem-pool"
74POOL_SIZE_KEY = "mem-pool-size-rings"
76ENABLE_MULTIPLE_ATTACH = "/etc/xensource/allow_multiple_vdi_attach"
77NO_MULTIPLE_ATTACH = not (os.path.exists(ENABLE_MULTIPLE_ATTACH))
80def locking(excType, override=True):
81 def locking2(op):
82 def wrapper(self, *args):
83 self.lock.acquire()
84 try:
85 try:
86 ret = op(self, * args)
87 except (util.CommandException, util.SMException, XenAPI.Failure) as e: 87 ↛ 97line 87 didn't jump to line 97
88 util.logException("BLKTAP2:%s" % op)
89 msg = str(e)
90 if isinstance(e, util.CommandException): 90 ↛ 91line 90 didn't jump to line 91, because the condition on line 90 was never true
91 msg = "Command %s failed (%s): %s" % \
92 (e.cmd, e.code, e.reason)
93 if override: 93 ↛ 96line 93 didn't jump to line 96, because the condition on line 93 was never false
94 raise xs_errors.XenError(excType, opterr=msg)
95 else:
96 raise
97 except:
98 util.logException("BLKTAP2:%s" % op)
99 raise
100 finally:
101 self.lock.release()
102 return ret
103 return wrapper
104 return locking2
107class RetryLoop(object):
109 def __init__(self, backoff, limit):
110 self.backoff = backoff
111 self.limit = limit
113 def __call__(self, f):
115 def loop(*__t, **__d):
116 attempt = 0
118 while True:
119 attempt += 1
121 try:
122 return f( * __t, ** __d)
124 except self.TransientFailure as e:
125 e = e.exception
127 if attempt >= self.limit: 127 ↛ 128line 127 didn't jump to line 128, because the condition on line 127 was never true
128 raise e
130 time.sleep(self.backoff)
132 return loop
134 class TransientFailure(Exception):
135 def __init__(self, exception):
136 self.exception = exception
139def retried(**args):
140 return RetryLoop( ** args)
143class TapCtl(object):
144 """Tapdisk IPC utility calls."""
146 PATH = "/usr/sbin/tap-ctl"
148 def __init__(self, cmd, p):
149 self.cmd = cmd
150 self._p = p
151 self.stdout = p.stdout
153 class CommandFailure(Exception):
154 """TapCtl cmd failure."""
156 def __init__(self, cmd, **info):
157 self.cmd = cmd
158 self.info = info
160 @override
161 def __str__(self) -> str:
162 items = self.info.items()
163 info = ", ".join("%s=%s" % item
164 for item in items)
165 return "%s failed: %s" % (self.cmd, info)
167 # Trying to get a non-existent attribute throws an AttributeError
168 # exception
169 def __getattr__(self, key):
170 if key in self.info: 170 ↛ 172line 170 didn't jump to line 172, because the condition on line 170 was never false
171 return self.info[key]
172 return object.__getattribute__(self, key)
174 @property
175 def has_status(self):
176 return 'status' in self.info
178 @property
179 def has_signal(self):
180 return 'signal' in self.info
182 # Retrieves the error code returned by the command. If the error code
183 # was not supplied at object-construction time, zero is returned.
184 def get_error_code(self):
185 key = 'status'
186 if key in self.info: 186 ↛ 189line 186 didn't jump to line 189, because the condition on line 186 was never false
187 return self.info[key]
188 else:
189 return 0
191 @classmethod
192 def __mkcmd_real(cls, args):
193 return [cls.PATH] + [str(x) for x in args]
195 __next_mkcmd = __mkcmd_real
197 @classmethod
198 def _mkcmd(cls, args):
200 __next_mkcmd = cls.__next_mkcmd
201 cls.__next_mkcmd = cls.__mkcmd_real
203 return __next_mkcmd(args)
205 @classmethod
206 def _call(cls, args, quiet=False, input=None, text_mode=True):
207 """
208 Spawn a tap-ctl process. Return a TapCtl invocation.
209 Raises a TapCtl.CommandFailure if subprocess creation failed.
210 """
211 cmd = cls._mkcmd(args)
213 if not quiet:
214 util.SMlog(cmd)
215 try:
216 p = subprocess.Popen(cmd,
217 stdin=subprocess.PIPE,
218 stdout=subprocess.PIPE,
219 stderr=subprocess.PIPE,
220 close_fds=True,
221 universal_newlines=text_mode)
222 if input:
223 p.stdin.write(input)
224 p.stdin.close()
225 except OSError as e:
226 raise cls.CommandFailure(cmd, errno=e.errno)
228 return cls(cmd, p)
230 def _errmsg(self):
231 output = map(str.rstrip, self._p.stderr)
232 return "; ".join(output)
234 def _wait(self, quiet=False):
235 """
236 Reap the child tap-ctl process of this invocation.
237 Raises a TapCtl.CommandFailure on non-zero exit status.
238 """
239 status = self._p.wait()
240 if not quiet:
241 util.SMlog(" = %d" % status)
243 if status == 0:
244 return
246 info = {'errmsg': self._errmsg(),
247 'pid': self._p.pid}
249 if status < 0:
250 info['signal'] = -status
251 else:
252 info['status'] = status
254 raise self.CommandFailure(self.cmd, ** info)
256 @classmethod
257 def _pread(cls, args, quiet=False, input=None, text_mode=True):
258 """
259 Spawn a tap-ctl invocation and read a single line.
260 """
261 tapctl = cls._call(args=args, quiet=quiet, input=input,
262 text_mode=text_mode)
264 output = tapctl.stdout.readline().rstrip()
266 tapctl._wait(quiet)
267 return output
269 @staticmethod
270 def _maybe(opt, parm):
271 if parm is not None:
272 return [opt, parm]
273 return []
275 @classmethod
276 def __list(cls, minor=None, pid=None, _type=None, path=None):
277 args = ["list"]
278 args += cls._maybe("-m", minor)
279 args += cls._maybe("-p", pid)
280 args += cls._maybe("-t", _type)
281 args += cls._maybe("-f", path)
283 tapctl = cls._call(args, True)
285 for stdout_line in tapctl.stdout:
286 # FIXME: tap-ctl writes error messages to stdout and
287 # confuses this parser
288 if stdout_line == "blktap kernel module not installed\n": 288 ↛ 291line 288 didn't jump to line 291, because the condition on line 288 was never true
289 # This isn't pretty but (a) neither is confusing stdout/stderr
290 # and at least causes the error to describe the fix
291 raise Exception("blktap kernel module not installed: try 'modprobe blktap'")
292 row = {}
294 for field in stdout_line.rstrip().split(' ', 3):
295 bits = field.split('=')
296 if len(bits) == 2: 296 ↛ 308line 296 didn't jump to line 308, because the condition on line 296 was never false
297 key, val = field.split('=')
299 if key in ('pid', 'minor'):
300 row[key] = int(val, 10)
302 elif key in ('state'):
303 row[key] = int(val, 0x10)
305 else:
306 row[key] = val
307 else:
308 util.SMlog("Ignoring unexpected tap-ctl output: %s" % repr(field))
309 yield row
311 tapctl._wait(True)
313 @classmethod
314 @retried(backoff=.5, limit=10)
315 def list(cls, **args):
317 # FIXME. We typically get an EPROTO when uevents interleave
318 # with SM ops and a tapdisk shuts down under our feet. Should
319 # be fixed in SM.
321 try:
322 return list(cls.__list( ** args))
324 except cls.CommandFailure as e:
325 transient = [errno.EPROTO, errno.ENOENT]
326 if e.has_status and e.status in transient:
327 raise RetryLoop.TransientFailure(e)
328 raise
330 @classmethod
331 def allocate(cls, devpath=None):
332 args = ["allocate"]
333 args += cls._maybe("-d", devpath)
334 return cls._pread(args)
336 @classmethod
337 def free(cls, minor):
338 args = ["free", "-m", minor]
339 cls._pread(args)
341 @classmethod
342 @retried(backoff=.5, limit=10)
343 def spawn(cls):
344 args = ["spawn"]
345 try:
346 pid = cls._pread(args)
347 return int(pid)
348 except cls.CommandFailure as ce:
349 # intermittent failures to spawn. CA-292268
350 if ce.status == 1:
351 raise RetryLoop.TransientFailure(ce)
352 raise
354 @classmethod
355 def attach(cls, pid, minor):
356 args = ["attach", "-p", pid, "-m", minor]
357 cls._pread(args)
359 @classmethod
360 def detach(cls, pid, minor):
361 args = ["detach", "-p", pid, "-m", minor]
362 cls._pread(args)
364 @classmethod
365 def _load_key(cls, key_hash, vdi_uuid):
366 import plugins
368 return plugins.load_key(key_hash, vdi_uuid)
370 @classmethod
371 def open(cls, pid, minor, _type, _file, options):
372 params = Tapdisk.Arg(_type, _file)
373 args = ["open", "-p", pid, "-m", minor, '-a', str(params)]
374 text_mode = True
375 input = None
376 if options.get("rdonly"):
377 args.append('-R')
378 if options.get("lcache"):
379 args.append("-r")
380 if options.get("existing_prt") is not None:
381 args.append("-e")
382 args.append(str(options["existing_prt"]))
383 if options.get("secondary"):
384 args.append("-2")
385 args.append(options["secondary"])
386 if options.get("standby"):
387 args.append("-s")
388 if options.get("timeout"):
389 args.append("-t")
390 args.append(str(options["timeout"]))
391 if not options.get("o_direct", True):
392 args.append("-D")
393 if options.get('cbtlog'):
394 args.extend(['-C', options['cbtlog']])
395 if options.get('key_hash'):
396 key_hash = options['key_hash']
397 vdi_uuid = options['vdi_uuid']
398 key = cls._load_key(key_hash, vdi_uuid)
400 if not key:
401 raise util.SMException("No key found with key hash {}".format(key_hash))
402 input = key
403 text_mode = False
404 args.append('-E')
406 cls._pread(args=args, input=input, text_mode=text_mode)
408 @classmethod
409 def close(cls, pid, minor, force=False):
410 args = ["close", "-p", pid, "-m", minor, "-t", "120"]
411 if force:
412 args += ["-f"]
413 cls._pread(args)
415 @classmethod
416 def pause(cls, pid, minor):
417 args = ["pause", "-p", pid, "-m", minor]
418 cls._pread(args)
420 @classmethod
421 def unpause(cls, pid, minor, _type=None, _file=None, mirror=None,
422 cbtlog=None):
423 args = ["unpause", "-p", pid, "-m", minor]
424 if mirror:
425 args.extend(["-2", mirror])
426 if _type and _file:
427 params = Tapdisk.Arg(_type, _file)
428 args += ["-a", str(params)]
429 if cbtlog:
430 args.extend(["-c", cbtlog])
431 cls._pread(args)
433 @classmethod
434 def shutdown(cls, pid):
435 # TODO: This should be a real tap-ctl command
436 os.kill(pid, signal.SIGTERM)
437 os.waitpid(pid, 0)
439 @classmethod
440 def stats(cls, pid, minor):
441 args = ["stats", "-p", pid, "-m", minor]
442 return cls._pread(args, quiet=True)
444 @classmethod
445 def major(cls):
446 args = ["major"]
447 major = cls._pread(args)
448 return int(major)
450 @classmethod
451 def commit(cls, pid, minor, vdi_type, path):
452 args = ["commit", "-p", pid, "-m", minor, "-a", path]
453 cls._pread(args)
455 @classmethod
456 def query(cls, pid, minor, quiet=False):
457 args = ["query", "-p", pid, "-m", minor]
458 output = cls._pread(args, quiet=quiet)
459 m = re.match(r"Commit status '(.+)' \((\d+)\/(\d+)\)", output)
460 status = m.group(1)
461 coalesced = int(m.group(2))
462 total_coalesce = int(m.group(3))
463 return (status, coalesced, total_coalesce)
465 @classmethod
466 def cancel_commit(cls, pid, minor, wait=True):
467 args = ["cancel", "-p", pid, "-m", minor]
468 if wait:
469 args.append("-w")
470 cls._pread(args)
472class TapdiskExists(Exception):
473 """Tapdisk already running."""
475 def __init__(self, tapdisk):
476 self.tapdisk = tapdisk
478 @override
479 def __str__(self) -> str:
480 return "%s already running" % self.tapdisk
483class TapdiskNotRunning(Exception):
484 """No such Tapdisk."""
486 def __init__(self, **attrs):
487 self.attrs = attrs
489 @override
490 def __str__(self) -> str:
491 items = iter(self.attrs.items())
492 attrs = ", ".join("%s=%s" % attr
493 for attr in items)
494 return "No such Tapdisk(%s)" % attrs
497class TapdiskNotUnique(Exception):
498 """More than one tapdisk on one path."""
500 def __init__(self, tapdisks):
501 self.tapdisks = tapdisks
503 @override
504 def __str__(self) -> str:
505 tapdisks = map(str, self.tapdisks)
506 return "Found multiple tapdisks: %s" % tapdisks
509class TapdiskFailed(Exception):
510 """Tapdisk launch failure."""
512 def __init__(self, arg, err):
513 self.arg = arg
514 self.err = err
516 @override
517 def __str__(self) -> str:
518 return "Tapdisk(%s): %s" % (self.arg, self.err)
520 def get_error(self):
521 return self.err
524class TapdiskInvalidState(Exception):
525 """Tapdisk pause/unpause failure"""
527 def __init__(self, tapdisk):
528 self.tapdisk = tapdisk
530 @override
531 def __str__(self) -> str:
532 return str(self.tapdisk)
535def mkdirs(path, mode=0o777):
536 if not os.path.exists(path):
537 parent, subdir = os.path.split(path)
538 assert parent != path
539 try:
540 if parent:
541 mkdirs(parent, mode)
542 if subdir:
543 os.mkdir(path, mode)
544 except OSError as e:
545 if e.errno != errno.EEXIST:
546 raise
549class KObject(object):
551 SYSFS_CLASSTYPE: ClassVar[str] = ""
553 @abstractmethod
554 def sysfs_devname(self) -> str:
555 pass
558class Attribute(object):
560 SYSFS_NODENAME: ClassVar[str] = ""
562 def __init__(self, path):
563 self.path = path
565 @classmethod
566 def from_kobject(cls, kobj):
567 path = "%s/%s" % (kobj.sysfs_path(), cls.SYSFS_NODENAME)
568 return cls(path)
570 class NoSuchAttribute(Exception):
571 def __init__(self, name):
572 self.name = name
574 @override
575 def __str__(self) -> str:
576 return "No such attribute: %s" % self.name
578 def _open(self, mode='r'):
579 try:
580 return open(self.path, mode)
581 except IOError as e:
582 if e.errno == errno.ENOENT:
583 raise self.NoSuchAttribute(self)
584 raise
586 def readline(self):
587 f = self._open('r')
588 s = f.readline().rstrip()
589 f.close()
590 return s
592 def writeline(self, val):
593 f = self._open('w')
594 f.write(val)
595 f.close()
598class ClassDevice(KObject):
600 @classmethod
601 def sysfs_class_path(cls):
602 return "/sys/class/%s" % cls.SYSFS_CLASSTYPE
604 def sysfs_path(self):
605 return "%s/%s" % (self.sysfs_class_path(),
606 self.sysfs_devname())
609class Blktap(ClassDevice):
611 DEV_BASEDIR = '/dev/xen/blktap-2'
613 SYSFS_CLASSTYPE = "blktap2"
615 def __init__(self, minor):
616 self.minor = minor
617 self._pool = None
618 self._task = None
620 @classmethod
621 def allocate(cls):
622 # FIXME. Should rather go into init.
623 mkdirs(cls.DEV_BASEDIR)
625 devname = TapCtl.allocate()
626 minor = Tapdisk._parse_minor(devname)
627 return cls(minor)
629 def free(self):
630 TapCtl.free(self.minor)
632 @override
633 def __str__(self) -> str:
634 return "%s(minor=%d)" % (self.__class__.__name__, self.minor)
636 @override
637 def sysfs_devname(self) -> str:
638 return "blktap!blktap%d" % self.minor
640 class Pool(Attribute):
641 SYSFS_NODENAME = "pool"
643 def get_pool_attr(self):
644 if not self._pool:
645 self._pool = self.Pool.from_kobject(self)
646 return self._pool
648 def get_pool_name(self):
649 return self.get_pool_attr().readline()
651 def set_pool_name(self, name):
652 self.get_pool_attr().writeline(name)
654 def set_pool_size(self, pages):
655 self.get_pool().set_size(pages)
657 def get_pool(self):
658 return BlktapControl.get_pool(self.get_pool_name())
660 def set_pool(self, pool):
661 self.set_pool_name(pool.name)
663 class Task(Attribute):
664 SYSFS_NODENAME = "task"
666 def get_task_attr(self):
667 if not self._task:
668 self._task = self.Task.from_kobject(self)
669 return self._task
671 def get_task_pid(self):
672 pid = self.get_task_attr().readline()
673 try:
674 return int(pid)
675 except ValueError:
676 return None
678 def find_tapdisk(self):
679 pid = self.get_task_pid()
680 if pid is None:
681 return None
683 return Tapdisk.find(pid=pid, minor=self.minor)
685 def get_tapdisk(self):
686 tapdisk = self.find_tapdisk()
687 if not tapdisk:
688 raise TapdiskNotRunning(minor=self.minor)
689 return tapdisk
692class Tapdisk(object):
694 TYPES = ['aio', 'vhd', 'qcow2']
696 def __init__(self, pid, minor, _type, path, state):
697 self.pid = pid
698 self.minor = minor
699 self.type = _type
700 self.path = path
701 self.state = state
702 self._dirty = False
703 self._blktap = None
705 @override
706 def __str__(self) -> str:
707 state = self.pause_state()
708 return "Tapdisk(%s, pid=%d, minor=%s, state=%s)" % \
709 (self.get_arg(), self.pid, self.minor, state)
711 @classmethod
712 def list(cls, **args):
714 for row in TapCtl.list( ** args):
716 args = {'pid': None,
717 'minor': None,
718 'state': None,
719 '_type': None,
720 'path': None}
722 for key, val in row.items():
723 if key in args:
724 args[key] = val
726 if 'args' in row: 726 ↛ 731line 726 didn't jump to line 731, because the condition on line 726 was never false
727 image = Tapdisk.Arg.parse(row['args'])
728 args['_type'] = image.type
729 args['path'] = image.path
731 if None in args.values(): 731 ↛ 732line 731 didn't jump to line 732, because the condition on line 731 was never true
732 continue
734 yield Tapdisk( ** args)
736 @classmethod
737 def find(cls, **args):
739 found = list(cls.list( ** args))
741 if len(found) > 1: 741 ↛ 742line 741 didn't jump to line 742, because the condition on line 741 was never true
742 raise TapdiskNotUnique(found)
744 if found: 744 ↛ 745line 744 didn't jump to line 745, because the condition on line 744 was never true
745 return found[0]
747 return None
749 @classmethod
750 def find_by_path(cls, path):
751 return cls.find(path=path)
753 @classmethod
754 def find_by_minor(cls, minor):
755 return cls.find(minor=minor)
757 @classmethod
758 def get(cls, **attrs):
760 tapdisk = cls.find( ** attrs)
762 if not tapdisk:
763 raise TapdiskNotRunning( ** attrs)
765 return tapdisk
767 @classmethod
768 def from_path(cls, path):
769 return cls.get(path=path)
771 @classmethod
772 def from_minor(cls, minor):
773 return cls.get(minor=minor)
775 @classmethod
776 def __from_blktap(cls, blktap):
777 tapdisk = cls.from_minor(minor=blktap.minor)
778 tapdisk._blktap = blktap
779 return tapdisk
781 def get_blktap(self):
782 if not self._blktap:
783 self._blktap = Blktap(self.minor)
784 return self._blktap
786 class Arg:
788 def __init__(self, _type, path):
789 self.type = _type
790 self.path = path
792 @override
793 def __str__(self) -> str:
794 return "%s:%s" % (self.type, self.path)
796 @classmethod
797 def parse(cls, arg):
799 try:
800 _type, path = arg.split(":", 1)
801 except ValueError:
802 raise cls.InvalidArgument(arg)
804 if _type not in Tapdisk.TYPES: 804 ↛ 805line 804 didn't jump to line 805, because the condition on line 804 was never true
805 raise cls.InvalidType(_type)
807 return cls(_type, path)
809 class InvalidType(Exception):
810 def __init__(self, _type):
811 self.type = _type
813 @override
814 def __str__(self) -> str:
815 return "Not a Tapdisk type: %s" % self.type
817 class InvalidArgument(Exception):
818 def __init__(self, arg):
819 self.arg = arg
821 @override
822 def __str__(self) -> str:
823 return "Not a Tapdisk image: %s" % self.arg
825 def get_arg(self):
826 return self.Arg(self.type, self.path)
828 def get_devpath(self):
829 return "%s/tapdev%d" % (Blktap.DEV_BASEDIR, self.minor)
831 @classmethod
832 def launch_from_arg(cls, arg):
833 arg = cls.Arg.parse(arg)
834 return cls.launch(arg.path, arg.type, False)
836 @staticmethod
837 def cgclassify(pid):
839 # We dont provide any <controllers>:<path>
840 # so cgclassify uses /etc/cgrules.conf which
841 # we have configured in the spec file.
842 cmd = ["cgclassify", str(pid)]
843 try:
844 util.pread2(cmd)
845 except util.CommandException as e:
846 util.logException(e)
848 @classmethod
849 def launch_on_tap(cls, blktap, path, _type, options):
851 tapdisk = cls.find_by_path(path)
852 if tapdisk: 852 ↛ 853line 852 didn't jump to line 853, because the condition on line 852 was never true
853 raise TapdiskExists(tapdisk)
855 minor = blktap.minor
856 try:
857 pid = TapCtl.spawn()
858 cls.cgclassify(pid)
859 try:
860 TapCtl.attach(pid, minor)
862 try:
863 retry_open = 0
864 while True:
865 try:
866 TapCtl.open(pid, minor, _type, path, options)
867 break
868 except TapCtl.CommandFailure as e:
869 err = (
870 'status' in e.info and e.info['status']
871 ) or None
872 if err in (errno.EIO, errno.EROFS, errno.EAGAIN): 872 ↛ 873line 872 didn't jump to line 873, because the condition on line 872 was never true
873 if retry_open < 5:
874 retry_open += 1
875 time.sleep(1)
876 continue
877 if LINSTOR_AVAILABLE and err == errno.EROFS:
878 log_drbd_openers(path)
879 raise
880 try:
881 tapdisk = cls.__from_blktap(blktap)
882 node = '/sys/dev/block/%d:%d' % (tapdisk.major(), tapdisk.minor)
883 util.set_scheduler_sysfs_node(node, ['none', 'noop'])
884 return tapdisk
885 except:
886 TapCtl.close(pid, minor)
887 raise
889 except:
890 TapCtl.detach(pid, minor)
891 raise
893 except:
894 try:
895 TapCtl.shutdown(pid)
896 except:
897 # Best effort to shutdown
898 pass
899 raise
901 except TapCtl.CommandFailure as ctl:
902 util.logException(ctl)
903 if ((path.startswith('/dev/xapi/cd/') or path.startswith('/dev/sr')) and 903 ↛ 907line 903 didn't jump to line 907, because the condition on line 903 was never false
904 ctl.has_status and ctl.get_error_code() == 123): # ENOMEDIUM (No medium found)
905 raise xs_errors.XenError('TapdiskDriveEmpty')
906 else:
907 raise TapdiskFailed(cls.Arg(_type, path), ctl)
909 @classmethod
910 def launch(cls, path, _type, rdonly):
911 blktap = Blktap.allocate()
912 try:
913 return cls.launch_on_tap(blktap, path, _type, {"rdonly": rdonly})
914 except:
915 blktap.free()
916 raise
918 def shutdown(self, force=False):
920 TapCtl.close(self.pid, self.minor, force)
922 TapCtl.detach(self.pid, self.minor)
924 self.get_blktap().free()
926 def pause(self):
928 if not self.is_running():
929 raise TapdiskInvalidState(self)
931 TapCtl.pause(self.pid, self.minor)
933 self._set_dirty()
935 def unpause(self, _type=None, path=None, mirror=None, cbtlog=None):
937 if not self.is_paused():
938 raise TapdiskInvalidState(self)
940 # FIXME: should the arguments be optional?
941 if _type is None:
942 _type = self.type
943 if path is None:
944 path = self.path
946 TapCtl.unpause(self.pid, self.minor, _type, path, mirror=mirror,
947 cbtlog=cbtlog)
949 self._set_dirty()
951 def stats(self):
952 return json.loads(TapCtl.stats(self.pid, self.minor))
953 #
954 # NB. dirty/refresh: reload attributes on next access
955 #
957 def _set_dirty(self):
958 self._dirty = True
960 def _refresh(self, __get):
961 t = self.from_minor(__get('minor'))
962 self.__init__(t.pid, t.minor, t.type, t.path, t.state)
964 @override
965 def __getattribute__(self, name) -> Any:
966 def __get(name):
967 # NB. avoid(rec(ursion)
968 return object.__getattribute__(self, name)
970 if __get('_dirty') and \ 970 ↛ 972line 970 didn't jump to line 972, because the condition on line 970 was never true
971 name in ['minor', 'type', 'path', 'state']:
972 self._refresh(__get)
973 self._dirty = False
975 return __get(name)
977 class PauseState:
978 RUNNING = 'R'
979 PAUSING = 'r'
980 PAUSED = 'P'
982 class Flags:
983 DEAD = 0x0001
984 CLOSED = 0x0002
985 QUIESCE_REQUESTED = 0x0004
986 QUIESCED = 0x0008
987 PAUSE_REQUESTED = 0x0010
988 PAUSED = 0x0020
989 SHUTDOWN_REQUESTED = 0x0040
990 LOCKING = 0x0080
991 RETRY_NEEDED = 0x0100
992 LOG_DROPPED = 0x0200
994 PAUSE_MASK = PAUSE_REQUESTED | PAUSED
996 def is_paused(self):
997 return not not (self.state & self.Flags.PAUSED)
999 def is_running(self):
1000 return not (self.state & self.Flags.PAUSE_MASK)
1002 def pause_state(self):
1003 if self.state & self.Flags.PAUSED: 1003 ↛ 1004line 1003 didn't jump to line 1004, because the condition on line 1003 was never true
1004 return self.PauseState.PAUSED
1006 if self.state & self.Flags.PAUSE_REQUESTED: 1006 ↛ 1007line 1006 didn't jump to line 1007, because the condition on line 1006 was never true
1007 return self.PauseState.PAUSING
1009 return self.PauseState.RUNNING
1011 @staticmethod
1012 def _parse_minor(devpath):
1013 regex = r'%s/(blktap|tapdev)(\d+)$' % Blktap.DEV_BASEDIR
1014 pattern = re.compile(regex)
1015 groups = pattern.search(devpath)
1016 if not groups:
1017 raise Exception("malformed tap device: '%s' (%s) " % (devpath, regex))
1019 minor = groups.group(2)
1020 return int(minor)
1022 _major = None
1024 @classmethod
1025 def major(cls):
1026 if cls._major:
1027 return cls._major
1029 devices = open("/proc/devices")
1030 for line in devices:
1032 row = line.rstrip().split(' ')
1033 if len(row) != 2:
1034 continue
1036 major, name = row
1037 if name != 'tapdev':
1038 continue
1040 cls._major = int(major)
1041 break
1043 devices.close()
1044 return cls._major
1047class VDI(object):
1048 """SR.vdi driver decorator for blktap2"""
1050 CONF_KEY_ALLOW_CACHING = "vdi_allow_caching"
1051 CONF_KEY_MODE_ON_BOOT = "vdi_on_boot"
1052 CONF_KEY_CACHE_SR = "local_cache_sr"
1053 CONF_KEY_O_DIRECT = "o_direct"
1054 LOCK_CACHE_SETUP = "cachesetup"
1056 ATTACH_DETACH_RETRY_SECS = 120
1058 def __init__(self, uuid, target, driver_info):
1059 self.target = self.TargetDriver(target, driver_info)
1060 self._vdi_uuid = uuid
1061 self._session = target.session
1062 self.xenstore_data = scsiutil.update_XS_SCSIdata(uuid, scsiutil.gen_synthetic_page_data(uuid))
1063 self.__o_direct = None
1064 self.__o_direct_reason = None
1065 self.lock = Lock("vdi", uuid)
1066 self.tap = None
1068 def get_o_direct_capability(self, options):
1069 """Returns True/False based on licensing and caching_params"""
1070 if self.__o_direct is not None: 1070 ↛ 1071line 1070 didn't jump to line 1071, because the condition on line 1070 was never true
1071 return self.__o_direct, self.__o_direct_reason
1073 if util.read_caching_is_restricted(self._session): 1073 ↛ 1074line 1073 didn't jump to line 1074, because the condition on line 1073 was never true
1074 self.__o_direct = True
1075 self.__o_direct_reason = "LICENSE_RESTRICTION"
1076 elif not ((self.target.vdi.sr.handles("nfs") or self.target.vdi.sr.handles("ext") or self.target.vdi.sr.handles("smb"))): 1076 ↛ 1079line 1076 didn't jump to line 1079, because the condition on line 1076 was never false
1077 self.__o_direct = True
1078 self.__o_direct_reason = "SR_NOT_SUPPORTED"
1079 elif options.get("rdonly") and not self.target.vdi.parent:
1080 self.__o_direct = True
1081 self.__o_direct_reason = "RO_WITH_NO_PARENT"
1082 elif options.get(self.CONF_KEY_O_DIRECT):
1083 self.__o_direct = True
1084 self.__o_direct_reason = "SR_OVERRIDE"
1086 if self.__o_direct is None: 1086 ↛ 1087line 1086 didn't jump to line 1087, because the condition on line 1086 was never true
1087 self.__o_direct = False
1088 self.__o_direct_reason = ""
1090 return self.__o_direct, self.__o_direct_reason
1092 @classmethod
1093 def from_cli(cls, uuid):
1094 session = XenAPI.xapi_local()
1095 session.xenapi.login_with_password('root', '', '', 'SM')
1097 target = sm.VDI.from_uuid(session, uuid)
1098 driver_info = target.sr.srcmd.driver_info
1100 session.xenapi.session.logout()
1102 return cls(uuid, target, driver_info)
1104 @staticmethod
1105 def _tap_type(vdi_type):
1106 """Map a VDI type (e.g. 'raw') to a tapdisk driver type (e.g. 'aio')"""
1107 return {
1108 'raw': 'aio',
1109 'vhd': 'vhd',
1110 'qcow2': 'qcow2',
1111 'iso': 'aio', # for ISO SR
1112 'aio': 'aio', # for LVHD
1113 'file': 'aio',
1114 'phy': 'aio'
1115 }[vdi_type]
1117 def get_tap_type(self):
1118 vdi_type = self.target.get_vdi_type()
1119 return VDI._tap_type(vdi_type)
1121 def get_phy_path(self):
1122 return self.target.get_vdi_path()
1124 class UnexpectedVDIType(Exception):
1126 def __init__(self, vdi_type, target):
1127 self.vdi_type = vdi_type
1128 self.target = target
1130 @override
1131 def __str__(self) -> str:
1132 return \
1133 "Target %s has unexpected VDI type '%s'" % \
1134 (type(self.target), self.vdi_type)
1136 VDI_PLUG_TYPE = {'phy': 'phy', # for NETAPP
1137 'raw': 'phy',
1138 'aio': 'tap', # for LVM raw nodes
1139 'iso': 'tap', # for ISOSR
1140 'file': 'tap',
1141 'vhd': 'tap',
1142 'qcow2': 'tap'}
1144 def tap_wanted(self):
1145 # 1. Let the target vdi_type decide
1147 vdi_type = self.target.get_vdi_type()
1149 try:
1150 plug_type = self.VDI_PLUG_TYPE[vdi_type]
1151 except KeyError:
1152 raise self.UnexpectedVDIType(vdi_type,
1153 self.target.vdi)
1155 if plug_type == 'tap': 1155 ↛ 1157line 1155 didn't jump to line 1157, because the condition on line 1155 was never false
1156 return True
1157 elif self.target.vdi.sr.handles('udev'):
1158 return True
1159 # 2. Otherwise, there may be more reasons
1160 #
1161 # .. TBD
1163 return False
1165 class TargetDriver:
1166 """Safe target driver access."""
1167 # NB. *Must* test caps for optional calls. Some targets
1168 # actually implement some slots, but do not enable them. Just
1169 # try/except would risk breaking compatibility.
1171 def __init__(self, vdi, driver_info):
1172 self.vdi = vdi
1173 self._caps = driver_info['capabilities']
1175 def has_cap(self, cap):
1176 """Determine if target has given capability"""
1177 return cap in self._caps
1179 def attach(self, sr_uuid, vdi_uuid):
1180 #assert self.has_cap("VDI_ATTACH")
1181 return self.vdi.attach(sr_uuid, vdi_uuid)
1183 def detach(self, sr_uuid, vdi_uuid):
1184 #assert self.has_cap("VDI_DETACH")
1185 self.vdi.detach(sr_uuid, vdi_uuid)
1187 def activate(self, sr_uuid, vdi_uuid):
1188 if self.has_cap("VDI_ACTIVATE"):
1189 return self.vdi.activate(sr_uuid, vdi_uuid)
1191 def deactivate(self, sr_uuid, vdi_uuid):
1192 if self.has_cap("VDI_DEACTIVATE"):
1193 self.vdi.deactivate(sr_uuid, vdi_uuid)
1194 #def resize(self, sr_uuid, vdi_uuid, size):
1195 # return self.vdi.resize(sr_uuid, vdi_uuid, size)
1197 def get_vdi_type(self):
1198 _type = self.vdi.vdi_type
1199 if not _type:
1200 raise VDI.UnexpectedVDIType(_type, self.vdi)
1201 return _type
1203 def get_vdi_path(self):
1204 return self.vdi.path
1206 class Link(object):
1207 """Relink a node under a common name"""
1208 # NB. We have to provide the device node path during
1209 # VDI.attach, but currently do not allocate the tapdisk minor
1210 # before VDI.activate. Therefore those link steps where we
1211 # relink existing devices under deterministic path names.
1213 BASEDIR: ClassVar[str] = ""
1215 def _mklink(self, target) -> None:
1216 pass
1218 @abstractmethod
1219 def _equals(self, target) -> bool:
1220 pass
1222 def __init__(self, path):
1223 self._path = path
1225 @classmethod
1226 def from_name(cls, name):
1227 path = "%s/%s" % (cls.BASEDIR, name)
1228 return cls(path)
1230 @classmethod
1231 def from_uuid(cls, sr_uuid, vdi_uuid):
1232 name = "%s/%s" % (sr_uuid, vdi_uuid)
1233 return cls.from_name(name)
1235 def path(self):
1236 return self._path
1238 def stat(self):
1239 return os.stat(self.path())
1241 def mklink(self, target) -> None:
1243 path = self.path()
1244 util.SMlog("%s -> %s" % (self, target))
1246 mkdirs(os.path.dirname(path))
1247 try:
1248 self._mklink(target)
1249 except OSError as e:
1250 # We do unlink during teardown, but have to stay
1251 # idempotent. However, a *wrong* target should never
1252 # be seen.
1253 if e.errno != errno.EEXIST:
1254 raise
1255 assert self._equals(target), "'%s' not equal to '%s'" % (path, target)
1257 def unlink(self):
1258 try:
1259 os.unlink(self.path())
1260 except OSError as e:
1261 if e.errno != errno.ENOENT:
1262 raise
1264 @override
1265 def __str__(self) -> str:
1266 path = self.path()
1267 return "%s(%s)" % (self.__class__.__name__, path)
1269 class SymLink(Link):
1270 """Symlink some file to a common name"""
1272 def readlink(self):
1273 return os.readlink(self.path())
1275 def symlink(self):
1276 return self.path()
1278 @override
1279 def _mklink(self, target) -> None:
1280 os.symlink(target, self.path())
1282 @override
1283 def _equals(self, target) -> bool:
1284 return self.readlink() == target
1286 class DeviceNode(Link):
1287 """Relink a block device node to a common name"""
1289 @classmethod
1290 def _real_stat(cls, target):
1291 """stat() not on @target, but its realpath()"""
1292 _target = os.path.realpath(target)
1293 return os.stat(_target)
1295 @classmethod
1296 def is_block(cls, target):
1297 """Whether @target refers to a block device."""
1298 return S_ISBLK(cls._real_stat(target).st_mode)
1300 @override
1301 def _mklink(self, target) -> None:
1303 st = self._real_stat(target)
1304 if not S_ISBLK(st.st_mode):
1305 raise self.NotABlockDevice(target, st)
1307 # set group read for disk group as well as root
1308 os.mknod(self.path(), st.st_mode | stat.S_IRGRP, st.st_rdev)
1309 os.chown(self.path(), st.st_uid, grp.getgrnam("disk").gr_gid)
1311 @override
1312 def _equals(self, target) -> bool:
1313 target_rdev = self._real_stat(target).st_rdev
1314 return self.stat().st_rdev == target_rdev
1316 def rdev(self):
1317 st = self.stat()
1318 assert S_ISBLK(st.st_mode)
1319 return os.major(st.st_rdev), os.minor(st.st_rdev)
1321 class NotABlockDevice(Exception):
1323 def __init__(self, path, st):
1324 self.path = path
1325 self.st = st
1327 @override
1328 def __str__(self) -> str:
1329 return "%s is not a block device: %s" % (self.path, self.st)
1331 class Hybrid(Link):
1333 def __init__(self, path):
1334 VDI.Link.__init__(self, path)
1335 self._devnode = VDI.DeviceNode(path)
1336 self._symlink = VDI.SymLink(path)
1338 def rdev(self):
1339 st = self.stat()
1340 if S_ISBLK(st.st_mode):
1341 return self._devnode.rdev()
1342 raise self._devnode.NotABlockDevice(self.path(), st)
1344 @override
1345 def mklink(self, target) -> None:
1346 if self._devnode.is_block(target):
1347 self._obj = self._devnode
1348 else:
1349 self._obj = self._symlink
1350 self._obj.mklink(target)
1352 @override
1353 def _equals(self, target) -> bool:
1354 return self._obj._equals(target)
1356 class PhyLink(SymLink):
1357 BASEDIR = "/dev/sm/phy"
1358 # NB. Cannot use DeviceNodes, e.g. FileVDIs aren't bdevs.
1360 class NBDLink(SymLink):
1362 BASEDIR = "/run/blktap-control/nbd"
1364 class BackendLink(Hybrid):
1365 BASEDIR = "/dev/sm/backend"
1366 # NB. Could be SymLinks as well, but saving major,minor pairs in
1367 # Links enables neat state capturing when managing Tapdisks. Note
1368 # that we essentially have a tap-ctl list replacement here. For
1369 # now make it a 'Hybrid'. Likely to collapse into a DeviceNode as
1370 # soon as ISOs are tapdisks.
1372 @staticmethod
1373 def _tap_activate(phy_path, vdi_type, sr_uuid, options, pool_size=None):
1375 tapdisk = Tapdisk.find_by_path(phy_path)
1376 if not tapdisk: 1376 ↛ 1377line 1376 didn't jump to line 1377, because the condition on line 1376 was never true
1377 blktap = Blktap.allocate()
1378 blktap.set_pool_name(sr_uuid)
1379 if pool_size:
1380 blktap.set_pool_size(pool_size)
1382 try:
1383 tapdisk = \
1384 Tapdisk.launch_on_tap(blktap,
1385 phy_path,
1386 VDI._tap_type(vdi_type),
1387 options)
1388 except:
1389 blktap.free()
1390 raise
1391 util.SMlog("tap.activate: Launched %s" % tapdisk)
1393 else:
1394 util.SMlog("tap.activate: Found %s" % tapdisk)
1396 return tapdisk.get_devpath(), tapdisk
1398 @staticmethod
1399 def _tap_deactivate(minor):
1401 try:
1402 tapdisk = Tapdisk.from_minor(minor)
1403 except TapdiskNotRunning as e:
1404 util.SMlog("tap.deactivate: Warning, %s" % e)
1405 # NB. Should not be here unless the agent refcount
1406 # broke. Also, a clean shutdown should not have leaked
1407 # the recorded minor.
1408 else:
1409 tapdisk.shutdown()
1410 util.SMlog("tap.deactivate: Shut down %s" % tapdisk)
1412 @classmethod
1413 def tap_pause(cls, session, sr_uuid, vdi_uuid, failfast=False):
1414 """
1415 Pauses the tapdisk.
1417 session: a XAPI session
1418 sr_uuid: the UUID of the SR on which VDI lives
1419 vdi_uuid: the UUID of the VDI to pause
1420 failfast: controls whether the VDI lock should be acquired in a
1421 non-blocking manner
1422 """
1423 util.SMlog("Pause request for %s" % vdi_uuid)
1424 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1425 session.xenapi.VDI.add_to_sm_config(vdi_ref, 'paused', 'true')
1426 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1427 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 1427 ↛ 1428line 1427 didn't jump to line 1428, because the loop on line 1427 never started
1428 host_ref = key[len('host_'):]
1429 util.SMlog("Calling tap-pause on host %s" % host_ref)
1430 if not cls.call_pluginhandler(session, host_ref,
1431 sr_uuid, vdi_uuid, "pause", failfast=failfast):
1432 # Failed to pause node
1433 session.xenapi.VDI.remove_from_sm_config(vdi_ref, 'paused')
1434 return False
1435 return True
1437 @classmethod
1438 def tap_unpause(cls, session, sr_uuid, vdi_uuid, secondary=None,
1439 activate_parents=False):
1440 util.SMlog("Unpause request for %s secondary=%s" % (vdi_uuid, secondary))
1441 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1442 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1443 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 1443 ↛ 1444line 1443 didn't jump to line 1444, because the loop on line 1443 never started
1444 host_ref = key[len('host_'):]
1445 util.SMlog("Calling tap-unpause on host %s" % host_ref)
1446 if not cls.call_pluginhandler(session, host_ref,
1447 sr_uuid, vdi_uuid, "unpause", secondary, activate_parents):
1448 # Failed to unpause node
1449 return False
1450 session.xenapi.VDI.remove_from_sm_config(vdi_ref, 'paused')
1451 return True
1453 @classmethod
1454 def tap_refresh(cls, session, sr_uuid, vdi_uuid, activate_parents=False):
1455 util.SMlog("Refresh request for %s" % vdi_uuid)
1456 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1457 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1458 for key in [x for x in sm_config.keys() if x.startswith('host_')]:
1459 host_ref = key[len('host_'):]
1460 util.SMlog("Calling tap-refresh on host %s" % host_ref)
1461 if not cls.call_pluginhandler(session, host_ref,
1462 sr_uuid, vdi_uuid, "refresh", None,
1463 activate_parents=activate_parents):
1464 # Failed to refresh node
1465 return False
1466 return True
1468 @classmethod
1469 def tap_status(cls, session, vdi_uuid):
1470 """Return True if disk is attached, false if it isn't"""
1471 util.SMlog("Disk status request for %s" % vdi_uuid)
1472 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1473 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1474 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 1474 ↛ 1475line 1474 didn't jump to line 1475, because the loop on line 1474 never started
1475 return True
1476 return False
1478 @classmethod
1479 def call_pluginhandler(cls, session, host_ref, sr_uuid, vdi_uuid, action,
1480 secondary=None, activate_parents=False, failfast=False):
1481 """Optionally, activate the parent LV before unpausing"""
1482 try:
1483 args = {"sr_uuid": sr_uuid, "vdi_uuid": vdi_uuid,
1484 "failfast": str(failfast)}
1485 if secondary:
1486 args["secondary"] = secondary
1487 if activate_parents:
1488 args["activate_parents"] = "true"
1489 ret = session.xenapi.host.call_plugin(
1490 host_ref, PLUGIN_TAP_PAUSE, action,
1491 args)
1492 return ret == "True"
1493 except Exception as e:
1494 util.logException("BLKTAP2:call_pluginhandler %s" % e)
1495 return False
1497 def _add_tag(self, vdi_uuid, writable):
1498 util.SMlog("Adding tag to: %s" % vdi_uuid)
1499 attach_mode = "RO"
1500 if writable:
1501 attach_mode = "RW"
1502 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1503 host_ref = self._session.xenapi.host.get_by_uuid(util.get_this_host())
1504 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1505 attached_as = util.attached_as(sm_config)
1506 if NO_MULTIPLE_ATTACH and (attached_as == "RW" or \ 1506 ↛ 1508line 1506 didn't jump to line 1508, because the condition on line 1506 was never true
1507 (attached_as == "RO" and attach_mode == "RW")):
1508 util.SMlog("need to reset VDI %s" % vdi_uuid)
1509 if not resetvdis.reset_vdi(self._session, vdi_uuid, force=False,
1510 term_output=False, writable=writable):
1511 raise util.SMException("VDI %s not detached cleanly" % vdi_uuid)
1512 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1513 if 'relinking' in sm_config:
1514 util.SMlog("Relinking key found, back-off and retry" % sm_config)
1515 return False
1516 if 'paused' in sm_config:
1517 util.SMlog("Paused or host_ref key found [%s]" % sm_config)
1518 return False
1519 try:
1520 self._session.xenapi.VDI.add_to_sm_config(
1521 vdi_ref, 'activating', 'True')
1522 except XenAPI.Failure as e:
1523 if e.details[0] == 'MAP_DUPLICATE_KEY' and not writable:
1524 # Someone else is activating - a retry might succeed
1525 return False
1526 raise
1527 host_key = "host_%s" % host_ref
1528 assert host_key not in sm_config
1529 self._session.xenapi.VDI.add_to_sm_config(vdi_ref, host_key,
1530 attach_mode)
1531 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1532 if 'paused' in sm_config or 'relinking' in sm_config:
1533 util.SMlog("Found %s key, aborting" % (
1534 'paused' if 'paused' in sm_config else 'relinking'))
1535 self._session.xenapi.VDI.remove_from_sm_config(vdi_ref, host_key)
1536 self._session.xenapi.VDI.remove_from_sm_config(
1537 vdi_ref, 'activating')
1538 return False
1539 util.SMlog("Activate lock succeeded")
1540 return True
1542 def _check_tag(self, vdi_uuid):
1543 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1544 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1545 if 'paused' in sm_config:
1546 util.SMlog("Paused key found [%s]" % sm_config)
1547 return False
1548 return True
1550 def _remove_tag(self, vdi_uuid):
1551 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1552 host_ref = self._session.xenapi.host.get_by_uuid(util.get_this_host())
1553 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1554 host_key = "host_%s" % host_ref
1555 if host_key in sm_config:
1556 self._session.xenapi.VDI.remove_from_sm_config(vdi_ref, host_key)
1557 util.SMlog("Removed host key %s for %s" % (host_key, vdi_uuid))
1558 else:
1559 util.SMlog("_remove_tag: host key %s not found, ignore" % host_key)
1561 def _get_pool_config(self, pool_name):
1562 pool_info = dict()
1563 vdi_ref = self.target.vdi.sr.srcmd.params.get('vdi_ref')
1564 if not vdi_ref: 1564 ↛ 1567line 1564 didn't jump to line 1567, because the condition on line 1564 was never true
1565 # attach_from_config context: HA disks don't need to be in any
1566 # special pool
1567 return pool_info
1569 sr_ref = self.target.vdi.sr.srcmd.params.get('sr_ref')
1570 sr_config = self._session.xenapi.SR.get_other_config(sr_ref)
1571 vdi_config = self._session.xenapi.VDI.get_other_config(vdi_ref)
1572 pool_size_str = sr_config.get(POOL_SIZE_KEY)
1573 pool_name_override = vdi_config.get(POOL_NAME_KEY)
1574 if pool_name_override: 1574 ↛ 1579line 1574 didn't jump to line 1579, because the condition on line 1574 was never false
1575 pool_name = pool_name_override
1576 pool_size_override = vdi_config.get(POOL_SIZE_KEY)
1577 if pool_size_override: 1577 ↛ 1579line 1577 didn't jump to line 1579, because the condition on line 1577 was never false
1578 pool_size_str = pool_size_override
1579 pool_size = 0
1580 if pool_size_str: 1580 ↛ 1590line 1580 didn't jump to line 1590, because the condition on line 1580 was never false
1581 try:
1582 pool_size = int(pool_size_str)
1583 if pool_size < 1 or pool_size > MAX_FULL_RINGS: 1583 ↛ 1584line 1583 didn't jump to line 1584, because the condition on line 1583 was never true
1584 raise ValueError("outside of range")
1585 pool_size = NUM_PAGES_PER_RING * pool_size
1586 except ValueError:
1587 util.SMlog("Error: invalid mem-pool-size %s" % pool_size_str)
1588 pool_size = 0
1590 pool_info["mem-pool"] = pool_name
1591 if pool_size: 1591 ↛ 1594line 1591 didn't jump to line 1594, because the condition on line 1591 was never false
1592 pool_info["mem-pool-size"] = str(pool_size)
1594 return pool_info
1596 def linkNBD(self, sr_uuid, vdi_uuid):
1597 if self.tap:
1598 nbd_path = '/run/blktap-control/nbd%d.%d' % (int(self.tap.pid),
1599 int(self.tap.minor))
1600 VDI.NBDLink.from_uuid(sr_uuid, vdi_uuid).mklink(nbd_path)
1602 def attach(self, sr_uuid, vdi_uuid, writable, activate=False, caching_params={}):
1603 """Return/dev/sm/backend symlink path"""
1604 self.xenstore_data.update(self._get_pool_config(sr_uuid))
1605 if not self.target.has_cap("ATOMIC_PAUSE") or activate:
1606 util.SMlog("Attach & activate")
1607 self._attach(sr_uuid, vdi_uuid)
1608 dev_path = self._activate(sr_uuid, vdi_uuid,
1609 {"rdonly": not writable})
1610 self.BackendLink.from_uuid(sr_uuid, vdi_uuid).mklink(dev_path)
1611 self.linkNBD(sr_uuid, vdi_uuid)
1613 # Return backend/ link
1614 back_path = self.BackendLink.from_uuid(sr_uuid, vdi_uuid).path()
1615 if self.tap_wanted():
1616 # Only have NBD if we also have a tap
1617 nbd_path = "nbd:unix:{}:exportname={}".format(
1618 VDI.NBDLink.from_uuid(sr_uuid, vdi_uuid).path(),
1619 vdi_uuid)
1620 else:
1621 nbd_path = ""
1623 options = {"rdonly": not writable}
1624 options.update(caching_params)
1625 o_direct, o_direct_reason = self.get_o_direct_capability(options)
1626 struct = {'params': back_path,
1627 'params_nbd': nbd_path,
1628 'o_direct': o_direct,
1629 'o_direct_reason': o_direct_reason,
1630 'xenstore_data': self.xenstore_data}
1631 util.SMlog('result: %s' % struct)
1633 try:
1634 f = open("%s.attach_info" % back_path, 'a')
1635 f.write(xmlrpc.client.dumps((struct, ), "", True))
1636 f.close()
1637 except:
1638 pass
1640 return xmlrpc.client.dumps((struct, ), "", True)
1642 def activate(self, sr_uuid, vdi_uuid, writable, caching_params):
1643 util.SMlog("blktap2.activate")
1644 options = {"rdonly": not writable}
1645 options.update(caching_params)
1647 sr_ref = self.target.vdi.sr.srcmd.params.get('sr_ref')
1648 sr_other_config = self._session.xenapi.SR.get_other_config(sr_ref)
1649 for i in range(self.ATTACH_DETACH_RETRY_SECS): 1649 ↛ 1656line 1649 didn't jump to line 1656, because the loop on line 1649 didn't complete
1650 try:
1651 if self._activate_locked(sr_uuid, vdi_uuid, options):
1652 return
1653 except util.SRBusyException:
1654 util.SMlog("SR locked, retrying")
1655 time.sleep(1)
1656 raise util.SMException("VDI %s locked" % vdi_uuid)
1658 def _get_sr_master_host_ref(self) -> str:
1659 """
1660 Give the host ref of the one responsible for Garbage Collection for a SR.
1661 Meaning this host for a local SR, the master for a shared SR.
1662 """
1663 sr = self.target.vdi.sr
1664 if sr.is_shared():
1665 host_ref = util.get_master_ref(self._session)
1666 else:
1667 host_ref = sr.host_ref
1668 return host_ref
1670 def _get_vdi_chain(self, cowutil, extractUuid) -> List[str]:
1671 vdi_chain = []
1672 path = self.target.get_vdi_path()
1674 #TODO: Need to add handling of error for getParentNoCheck, e.g. corrupted VDI where we can't read parent
1675 vdi_chain.append(extractUuid(path))
1676 parent = cowutil.getParentNoCheck(path)
1677 while parent:
1678 vdi_chain.append(extractUuid(parent))
1679 parent = cowutil.getParentNoCheck(parent)
1680 vdi_chain.reverse()
1681 return vdi_chain
1683 def _check_journal_coalesce_chain(self, sr_uuid: str, vdi_uuid: str) -> bool:
1684 vdi_type = self.target.get_vdi_type()
1685 cowutil = getCowUtil(vdi_type)
1687 if not cowutil.isCoalesceableOnRemote(): #We only need to stop the coalesce in case of QCOW2
1688 return True
1690 path = self.target.get_vdi_path()
1692 import fjournaler
1693 import journaler
1694 from lvmcowutil import LvmCowUtil
1695 from FileSR import FileVDI
1696 import lvmcache
1698 journal: Union[journaler.Journaler, fjournaler.Journaler]
1699 # Different extractUUID & journaler function for LVMSR and FileSR
1700 if path.startswith("/dev/"): #TODO: How to identify SR type easily, we could ask XAPI since we have the sruuid (and even ref)
1701 vgName = "VG_XenStorage-{}".format(sr_uuid)
1702 lvmCache = lvmcache.LVMCache(vgName)
1703 journal = journaler.Journaler(lvmCache)
1705 extractUuid = LvmCowUtil.extractUuid
1706 else:
1707 journal = fjournaler.Journaler(os.getcwd())
1708 extractUuid = FileVDI.extractUuid
1710 # Get the VDI chain
1711 vdi_chain = self._get_vdi_chain(cowutil, extractUuid)
1713 if len(vdi_chain) == 1:
1714 # We only have a leaf, do nothing
1715 util.SMlog("VDI {} is only a leaf, continuing...".format(vdi_uuid))
1716 return True
1718 # Log the chain of active VDI
1719 level = 0
1720 util.SMlog("VDI chain:")
1721 for vdi in vdi_chain:
1722 prefix = " " * level
1723 level += 1
1724 util.SMlog("{}{}".format(prefix, vdi))
1726 vdi_to_cancel = []
1727 for entry in journal.getAll("coalesce").keys():
1728 if entry in vdi_chain:
1729 vdi_to_cancel.append(entry)
1730 util.SMlog("Coalescing VDI {} in chain".format(entry))
1732 # Get the host_ref from the host doing the GC work
1733 host_ref = self._get_sr_master_host_ref()
1734 for vdi in vdi_to_cancel:
1735 args = {"sr_uuid": sr_uuid, "vdi_uuid": vdi}
1736 util.SMlog("Calling cancel_coalesce_master with args: {}".format(args))
1737 self._session.xenapi.host.call_plugin(\
1738 host_ref, PLUGIN_ON_SLAVE, "cancel_coalesce_master", args)
1740 return True
1742 @locking("VDIUnavailable")
1743 def _activate_locked(self, sr_uuid, vdi_uuid, options):
1744 """Wraps target.activate and adds a tapdisk"""
1746 #util.SMlog("VDI.activate %s" % vdi_uuid)
1747 refresh = False
1748 if self.tap_wanted(): 1748 ↛ 1753line 1748 didn't jump to line 1753, because the condition on line 1748 was never false
1749 if not self._add_tag(vdi_uuid, not options["rdonly"]):
1750 return False
1751 refresh = True
1753 try:
1754 if refresh: 1754 ↛ 1765line 1754 didn't jump to line 1765, because the condition on line 1754 was never false
1755 # it is possible that while the VDI was paused some of its
1756 # attributes have changed (e.g. its size if it was inflated; or its
1757 # path if it was leaf-coalesced onto a raw LV), so refresh the
1758 # object completely
1759 params = self.target.vdi.sr.srcmd.params
1760 target = sm.VDI.from_uuid(self.target.vdi.session, vdi_uuid)
1761 target.sr.srcmd.params = params
1762 driver_info = target.sr.srcmd.driver_info
1763 self.target = self.TargetDriver(target, driver_info)
1765 util.fistpoint.activate_custom_fn( 1765 ↛ exitline 1765 didn't jump to the function exit
1766 "blktap_activate_inject_failure",
1767 lambda: util.inject_failure())
1769 # Attach the physical node
1770 if self.target.has_cap("ATOMIC_PAUSE"): 1770 ↛ 1771line 1770 didn't jump to line 1771, because the condition on line 1770 was never true
1771 self._attach(sr_uuid, vdi_uuid)
1773 vdi_type = self.target.get_vdi_type()
1775 if not self._check_journal_coalesce_chain(sr_uuid, vdi_uuid): 1775 ↛ 1776line 1775 didn't jump to line 1776, because the condition on line 1775 was never true
1776 return False
1778 # Take lvchange-p Lock before running
1779 # tap-ctl open
1780 # Needed to avoid race with lvchange -p which is
1781 # now taking the same lock
1782 # This is a fix for CA-155766
1783 if hasattr(self.target.vdi.sr, 'DRIVER_TYPE') and \ 1783 ↛ 1786line 1783 didn't jump to line 1786, because the condition on line 1783 was never true
1784 self.target.vdi.sr.DRIVER_TYPE == 'lvhd' and \
1785 VdiType.isCowImage(vdi_type):
1786 lock = Lock("lvchange-p", NS_PREFIX_LVM + sr_uuid)
1787 lock.acquire()
1789 # When we attach a static VDI for HA, we cannot communicate with
1790 # xapi, because has not started yet. These VDIs are raw.
1791 if VdiType.isCowImage(vdi_type): 1791 ↛ 1792line 1791 didn't jump to line 1792, because the condition on line 1791 was never true
1792 session = self.target.vdi.session
1793 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1794 # pylint: disable=used-before-assignment
1795 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1796 if 'key_hash' in sm_config:
1797 key_hash = sm_config['key_hash']
1798 options['key_hash'] = key_hash
1799 options['vdi_uuid'] = vdi_uuid
1800 util.SMlog('Using key with hash {} for VDI {}'.format(key_hash, vdi_uuid))
1801 # Activate the physical node
1802 dev_path = self._activate(sr_uuid, vdi_uuid, options)
1804 if hasattr(self.target.vdi.sr, 'DRIVER_TYPE') and \ 1804 ↛ 1807line 1804 didn't jump to line 1807, because the condition on line 1804 was never true
1805 self.target.vdi.sr.DRIVER_TYPE == 'lvhd' and \
1806 VdiType.isCowImage(self.target.get_vdi_type()):
1807 lock.release()
1808 except:
1809 util.SMlog("Exception in activate/attach")
1810 if self.tap_wanted():
1811 util.fistpoint.activate_custom_fn(
1812 "blktap_activate_error_handling",
1813 lambda: time.sleep(30))
1814 while True:
1815 try:
1816 self._remove_tag(vdi_uuid)
1817 break
1818 except xmlrpc.client.ProtocolError as e:
1819 # If there's a connection error, keep trying forever.
1820 if e.errcode == http.HTTPStatus.INTERNAL_SERVER_ERROR.value:
1821 continue
1822 else:
1823 util.SMlog('failed to remove tag: %s' % e)
1824 break
1825 except Exception as e:
1826 util.SMlog('failed to remove tag: %s' % e)
1827 break
1828 raise
1829 finally:
1830 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1831 self._session.xenapi.VDI.remove_from_sm_config(
1832 vdi_ref, 'activating')
1833 util.SMlog("Removed activating flag from %s" % vdi_uuid) 1833 ↛ exitline 1833 didn't except from function '_activate_locked', because the raise on line 1828 wasn't executed or line 1833 didn't return from function '_activate_locked', because the return on line 1776 wasn't executed
1835 # Link result to backend/
1836 self.BackendLink.from_uuid(sr_uuid, vdi_uuid).mklink(dev_path)
1837 self.linkNBD(sr_uuid, vdi_uuid)
1838 return True
1840 def _activate(self, sr_uuid, vdi_uuid, options):
1841 vdi_options = self.target.activate(sr_uuid, vdi_uuid)
1843 dev_path = self.setup_cache(sr_uuid, vdi_uuid, options)
1844 if not dev_path: 1844 ↛ 1858line 1844 didn't jump to line 1858, because the condition on line 1844 was never false
1845 phy_path = self.PhyLink.from_uuid(sr_uuid, vdi_uuid).readlink()
1846 # Maybe launch a tapdisk on the physical link
1847 if self.tap_wanted(): 1847 ↛ 1856line 1847 didn't jump to line 1856, because the condition on line 1847 was never false
1848 vdi_type = self.target.get_vdi_type()
1849 options["o_direct"] = self.get_o_direct_capability(options)[0]
1850 if vdi_options: 1850 ↛ 1852line 1850 didn't jump to line 1852, because the condition on line 1850 was never false
1851 options.update(vdi_options)
1852 dev_path, self.tap = self._tap_activate(phy_path, vdi_type,
1853 sr_uuid, options,
1854 self._get_pool_config(sr_uuid).get("mem-pool-size"))
1855 else:
1856 dev_path = phy_path # Just reuse phy
1858 return dev_path
1860 def _attach(self, sr_uuid, vdi_uuid):
1861 attach_info = xmlrpc.client.loads(self.target.attach(sr_uuid, vdi_uuid))[0][0]
1862 params = attach_info['params']
1863 xenstore_data = attach_info['xenstore_data']
1864 phy_path = util.to_plain_string(params)
1865 self.xenstore_data.update(xenstore_data)
1866 # Save it to phy/
1867 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).mklink(phy_path)
1869 def deactivate(self, sr_uuid, vdi_uuid, caching_params):
1870 util.SMlog("blktap2.deactivate")
1871 for i in range(self.ATTACH_DETACH_RETRY_SECS):
1872 try:
1873 if self._deactivate_locked(sr_uuid, vdi_uuid, caching_params):
1874 return
1875 except util.SRBusyException as e:
1876 util.SMlog("SR locked, retrying")
1877 time.sleep(1)
1878 raise util.SMException("VDI %s locked" % vdi_uuid)
1880 @locking("VDIUnavailable")
1881 def _deactivate_locked(self, sr_uuid, vdi_uuid, caching_params):
1882 """Wraps target.deactivate and removes a tapdisk"""
1884 #util.SMlog("VDI.deactivate %s" % vdi_uuid)
1885 if self.tap_wanted() and not self._check_tag(vdi_uuid):
1886 return False
1888 self._deactivate(sr_uuid, vdi_uuid, caching_params)
1889 if self.target.has_cap("ATOMIC_PAUSE"):
1890 self._detach(sr_uuid, vdi_uuid)
1891 if self.tap_wanted():
1892 self._remove_tag(vdi_uuid)
1894 return True
1896 def _resetPhylink(self, sr_uuid, vdi_uuid, path):
1897 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).mklink(path)
1899 def detach(self, sr_uuid, vdi_uuid, deactivate=False, caching_params={}):
1900 if not self.target.has_cap("ATOMIC_PAUSE") or deactivate:
1901 util.SMlog("Deactivate & detach")
1902 self._deactivate(sr_uuid, vdi_uuid, caching_params)
1903 self._detach(sr_uuid, vdi_uuid)
1904 else:
1905 pass # nothing to do
1907 def _deactivate(self, sr_uuid, vdi_uuid, caching_params):
1908 # Shutdown tapdisk
1909 back_link = self.BackendLink.from_uuid(sr_uuid, vdi_uuid)
1911 if not util.pathexists(back_link.path()):
1912 util.SMlog("Backend path %s does not exist" % back_link.path())
1913 return
1915 try:
1916 attach_info_path = "%s.attach_info" % (back_link.path())
1917 os.unlink(attach_info_path)
1918 except:
1919 util.SMlog("unlink of attach_info failed")
1921 try:
1922 major, minor = back_link.rdev()
1923 except self.DeviceNode.NotABlockDevice:
1924 pass
1925 else:
1926 if major == Tapdisk.major():
1927 self._tap_deactivate(minor)
1928 self.remove_cache(caching_params)
1930 # Remove the backend link
1931 back_link.unlink()
1932 VDI.NBDLink.from_uuid(sr_uuid, vdi_uuid).unlink()
1934 # Deactivate & detach the physical node
1935 if self.tap_wanted() and self.target.vdi.session is not None:
1936 # it is possible that while the VDI was paused some of its
1937 # attributes have changed (e.g. its size if it was inflated; or its
1938 # path if it was leaf-coalesced onto a raw LV), so refresh the
1939 # object completely
1940 target = sm.VDI.from_uuid(self.target.vdi.session, vdi_uuid)
1941 driver_info = target.sr.srcmd.driver_info
1942 self.target = self.TargetDriver(target, driver_info)
1944 self.target.deactivate(sr_uuid, vdi_uuid)
1946 def _detach(self, sr_uuid, vdi_uuid):
1947 self.target.detach(sr_uuid, vdi_uuid)
1949 # Remove phy/
1950 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).unlink()
1952 def _updateCacheRecord(self, session, vdi_uuid, on_boot, caching):
1953 # Remove existing VDI.sm_config fields
1954 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1955 for key in ["on_boot", "caching"]:
1956 session.xenapi.VDI.remove_from_sm_config(vdi_ref, key)
1957 if not on_boot is None: 1957 ↛ 1958line 1957 didn't jump to line 1958, because the condition on line 1957 was never true
1958 session.xenapi.VDI.add_to_sm_config(vdi_ref, 'on_boot', on_boot)
1959 if not caching is None:
1960 session.xenapi.VDI.add_to_sm_config(vdi_ref, 'caching', caching)
1962 def setup_cache(self, sr_uuid, vdi_uuid, params):
1963 if params.get(self.CONF_KEY_ALLOW_CACHING) != "true":
1964 return
1966 util.SMlog("Requested local caching")
1967 if not self.target.has_cap("SR_CACHING"):
1968 util.SMlog("Error: local caching not supported by this SR")
1969 return
1971 scratch_mode = False
1972 if params.get(self.CONF_KEY_MODE_ON_BOOT) == "reset":
1973 scratch_mode = True
1974 util.SMlog("Requested scratch mode")
1975 if not self.target.has_cap("VDI_RESET_ON_BOOT/2"): 1975 ↛ 1979line 1975 didn't jump to line 1979, because the condition on line 1975 was never false
1976 util.SMlog("Error: scratch mode not supported by this SR")
1977 return
1979 dev_path = None
1980 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR)
1981 if not local_sr_uuid:
1982 util.SMlog("ERROR: Local cache SR not specified, not enabling")
1983 return
1984 dev_path = self._setup_cache(self._session, sr_uuid, vdi_uuid,
1985 local_sr_uuid, scratch_mode, params)
1987 if dev_path:
1988 self._updateCacheRecord(self._session, self.target.vdi.uuid,
1989 params.get(self.CONF_KEY_MODE_ON_BOOT),
1990 params.get(self.CONF_KEY_ALLOW_CACHING))
1992 return dev_path
1994 def alert_no_cache(self, session, vdi_uuid, cache_sr_uuid, err):
1995 vm_uuid = None
1996 vm_label = ""
1997 try:
1998 cache_sr_ref = session.xenapi.SR.get_by_uuid(cache_sr_uuid)
1999 cache_sr_rec = session.xenapi.SR.get_record(cache_sr_ref)
2000 cache_sr_label = cache_sr_rec.get("name_label")
2002 host_ref = session.xenapi.host.get_by_uuid(util.get_this_host())
2003 host_rec = session.xenapi.host.get_record(host_ref)
2004 host_label = host_rec.get("name_label")
2006 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
2007 vbds = session.xenapi.VBD.get_all_records_where( \
2008 "field \"VDI\" = \"%s\"" % vdi_ref)
2009 for vbd_rec in vbds.values():
2010 vm_ref = vbd_rec.get("VM")
2011 vm_rec = session.xenapi.VM.get_record(vm_ref)
2012 vm_uuid = vm_rec.get("uuid")
2013 vm_label = vm_rec.get("name_label")
2014 except:
2015 util.logException("alert_no_cache")
2017 alert_obj = "SR"
2018 alert_uuid = str(cache_sr_uuid)
2019 alert_str = "No space left in Local Cache SR %s" % cache_sr_uuid
2020 if vm_uuid:
2021 alert_obj = "VM"
2022 alert_uuid = vm_uuid
2023 reason = ""
2024 if err == errno.ENOSPC:
2025 reason = "because there is no space left"
2026 alert_str = "The VM \"%s\" is not using IntelliCache %s on the Local Cache SR (\"%s\") on host \"%s\"" % \
2027 (vm_label, reason, cache_sr_label, host_label)
2029 util.SMlog("Creating alert: (%s, %s, \"%s\")" % \
2030 (alert_obj, alert_uuid, alert_str))
2031 session.xenapi.message.create("No space left in local cache", "3",
2032 alert_obj, alert_uuid, alert_str)
2034 def _setup_cache(self, session, sr_uuid, vdi_uuid, local_sr_uuid,
2035 scratch_mode, options):
2036 import SR
2037 import EXTSR
2039 if self._no_parent(self.target.vdi): 2039 ↛ 2040line 2039 didn't jump to line 2040, because the condition on line 2039 was never true
2040 util.SMlog("ERROR: VDI %s has no parent, not enabling" %
2041 self.target.vdi.uuid)
2042 return
2044 util.SMlog("Setting up cache")
2045 shared_target = self.target.vdi.sr.vdi(self.target.vdi.parent)
2047 if shared_target.parent:
2048 util.SMlog("ERROR: Parent VDI %s has parent, not enabling" %
2049 shared_target.uuid)
2050 return
2052 SR.registerSR(EXTSR.EXTSR)
2053 local_sr = SR.SR.from_uuid(session, local_sr_uuid)
2055 vdi_type = self.target.get_vdi_type()
2056 tap_type = VDI._tap_type(vdi_type)
2057 cowutil = getCowUtil(vdi_type)
2059 lock = Lock(self.LOCK_CACHE_SETUP, shared_target.uuid)
2060 lock.acquire()
2062 # read cache
2063 read_cache_path = "%s/%s.vhdcache" % (local_sr.path, shared_target.uuid)
2064 if util.pathexists(read_cache_path): 2064 ↛ 2068line 2064 didn't jump to line 2068, because the condition on line 2064 was never false
2065 util.SMlog("Read cache node (%s) already exists, not creating" %
2066 read_cache_path)
2067 else:
2068 try:
2069 cowutil.snapshot(read_cache_path, shared_target.path, False)
2070 except util.CommandException as e:
2071 util.SMlog("Error creating parent cache: %s" % e)
2072 self.alert_no_cache(session, vdi_uuid, local_sr_uuid, e.code)
2073 return None
2075 # local write node
2076 leaf_size = cowutil.getSizeVirt(self.target.vdi.path)
2077 local_leaf_path = "%s/%s.vhdcache" % \
2078 (local_sr.path, self.target.vdi.uuid)
2079 if util.pathexists(local_leaf_path): 2079 ↛ 2083line 2079 didn't jump to line 2083, because the condition on line 2079 was never false
2080 util.SMlog("Local leaf node (%s) already exists, deleting" %
2081 local_leaf_path)
2082 os.unlink(local_leaf_path)
2083 try:
2084 cowutil.snapshot(local_leaf_path, read_cache_path, False,
2085 msize=leaf_size, checkEmpty=False)
2086 except util.CommandException as e:
2087 util.SMlog("Error creating leaf cache: %s" % e)
2088 self.alert_no_cache(session, vdi_uuid, local_sr_uuid, e.code)
2089 return None
2091 local_leaf_size = cowutil.getSizeVirt(local_leaf_path)
2092 if leaf_size > local_leaf_size: 2092 ↛ 2093line 2092 didn't jump to line 2093, because the condition on line 2092 was never true
2093 util.SMlog("Leaf size %d > local leaf cache size %d, resizing" %
2094 (leaf_size, local_leaf_size))
2095 cowutil.setSizeVirtFast(local_leaf_path, leaf_size)
2097 prt_tapdisk = Tapdisk.find_by_path(read_cache_path)
2098 if not prt_tapdisk:
2099 parent_options = copy.deepcopy(options)
2100 parent_options["rdonly"] = False
2101 parent_options["lcache"] = True
2103 blktap = Blktap.allocate()
2104 try:
2105 blktap.set_pool_name("lcache-parent-pool-%s" % blktap.minor)
2106 # no need to change pool_size since each parent tapdisk is in
2107 # its own pool
2108 prt_tapdisk = Tapdisk.launch_on_tap(blktap, read_cache_path, tap_type, parent_options)
2109 except:
2110 blktap.free()
2111 raise
2113 secondary = "%s:%s" % (vdi_type, self.PhyLink.from_uuid(sr_uuid, vdi_uuid).readlink())
2115 util.SMlog("Parent tapdisk: %s" % prt_tapdisk)
2116 leaf_tapdisk = Tapdisk.find_by_path(local_leaf_path)
2117 if not leaf_tapdisk: 2117 ↛ 2133line 2117 didn't jump to line 2133, because the condition on line 2117 was never false
2118 blktap = Blktap.allocate()
2119 child_options = copy.deepcopy(options)
2120 child_options["rdonly"] = False
2121 child_options["lcache"] = (not scratch_mode)
2122 child_options["existing_prt"] = prt_tapdisk.minor
2123 child_options["secondary"] = secondary
2124 child_options["standby"] = scratch_mode
2125 # Disable memory read caching
2126 child_options.pop("o_direct", None)
2127 try:
2128 leaf_tapdisk = Tapdisk.launch_on_tap(blktap, local_leaf_path, tap_type, child_options)
2129 except:
2130 blktap.free()
2131 raise
2133 lock.release()
2135 util.SMlog("Local read cache: %s, local leaf: %s" %
2136 (read_cache_path, local_leaf_path))
2138 self.tap = leaf_tapdisk
2139 return leaf_tapdisk.get_devpath()
2141 def remove_cache(self, params):
2142 if not self.target.has_cap("SR_CACHING"):
2143 return
2145 caching = params.get(self.CONF_KEY_ALLOW_CACHING) == "true"
2147 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR)
2148 if caching and not local_sr_uuid:
2149 util.SMlog("ERROR: Local cache SR not specified, ignore")
2150 return
2152 if caching: 2152 ↛ 2155line 2152 didn't jump to line 2155, because the condition on line 2152 was never false
2153 self._remove_cache(self._session, local_sr_uuid)
2155 if self._session is not None: 2155 ↛ exitline 2155 didn't return from function 'remove_cache', because the condition on line 2155 was never false
2156 self._updateCacheRecord(self._session, self.target.vdi.uuid, None, None)
2158 def _is_tapdisk_in_use(self, minor):
2159 retVal, links, sockets = util.findRunningProcessOrOpenFile("tapdisk")
2160 if not retVal:
2161 # err on the side of caution
2162 return True
2164 for link in links:
2165 if link.find("tapdev%d" % minor) != -1:
2166 return True
2168 socket_re = re.compile(r'^/.*/nbd\d+\.%d' % minor)
2169 for s in sockets:
2170 if socket_re.match(s):
2171 return True
2173 return False
2175 def _remove_cache(self, session, local_sr_uuid):
2176 import SR
2177 import EXTSR
2179 if self._no_parent(self.target.vdi):
2180 util.SMlog("ERROR: No parent for VDI %s, ignore" %
2181 self.target.vdi.uuid)
2182 return
2184 util.SMlog("Tearing down the cache")
2186 shared_target = self.target.vdi.sr.vdi(self.target.vdi.parent)
2188 SR.registerSR(EXTSR.EXTSR)
2189 local_sr = SR.SR.from_uuid(session, local_sr_uuid)
2191 lock = Lock(self.LOCK_CACHE_SETUP, shared_target.uuid)
2192 lock.acquire()
2194 # local write node
2195 local_leaf_path = "%s/%s.vhdcache" % \
2196 (local_sr.path, self.target.vdi.uuid)
2197 if util.pathexists(local_leaf_path): 2197 ↛ 2201line 2197 didn't jump to line 2201, because the condition on line 2197 was never false
2198 util.SMlog("Deleting local leaf node %s" % local_leaf_path)
2199 os.unlink(local_leaf_path)
2201 read_cache_path = "%s/%s.vhdcache" % (local_sr.path, shared_target.uuid)
2202 prt_tapdisk = Tapdisk.find_by_path(read_cache_path)
2203 if not prt_tapdisk: 2203 ↛ 2204line 2203 didn't jump to line 2204, because the condition on line 2203 was never true
2204 util.SMlog("Parent tapdisk not found")
2205 elif not self._is_tapdisk_in_use(prt_tapdisk.minor): 2205 ↛ 2213line 2205 didn't jump to line 2213, because the condition on line 2205 was never false
2206 util.SMlog("Parent tapdisk not in use: shutting down %s" %
2207 read_cache_path)
2208 try:
2209 prt_tapdisk.shutdown()
2210 except:
2211 util.logException("shutting down parent tapdisk")
2212 else:
2213 util.SMlog("Parent tapdisk still in use: %s" % read_cache_path)
2214 # the parent cache files are removed during the local SR's background
2215 # GC run
2217 lock.release()
2219 @staticmethod
2220 def _no_parent(vdi):
2221 return vdi.parent is None or vdi.parent == ''
2224PythonKeyError = KeyError
2227class UEventHandler(object):
2229 def __init__(self):
2230 self._action = None
2232 class KeyError(PythonKeyError):
2233 def __init__(self, args):
2234 super().__init__(args)
2235 self.key = args[0]
2237 @override
2238 def __str__(self) -> str:
2239 return \
2240 "Key '%s' missing in environment. " % self.key + \
2241 "Not called in udev context?"
2243 @classmethod
2244 def getenv(cls, key):
2245 try:
2246 return os.environ[key]
2247 except KeyError as e:
2248 raise cls.KeyError(e.args[0])
2250 def get_action(self):
2251 if not self._action:
2252 self._action = self.getenv('ACTION')
2253 return self._action
2255 class UnhandledEvent(Exception):
2257 def __init__(self, event, handler):
2258 self.event = event
2259 self.handler = handler
2261 @override
2262 def __str__(self) -> str:
2263 return "Uevent '%s' not handled by %s" % \
2264 (self.event, self.handler.__class__.__name__)
2266 ACTIONS: Dict[str, Callable] = {}
2268 def run(self):
2270 action = self.get_action()
2271 try:
2272 fn = self.ACTIONS[action]
2273 except KeyError:
2274 raise self.UnhandledEvent(action, self)
2276 return fn(self)
2278 @override
2279 def __str__(self) -> str:
2280 try:
2281 action = self.get_action()
2282 except:
2283 action = None
2284 return "%s[%s]" % (self.__class__.__name__, action)
2287class __BlktapControl(ClassDevice):
2288 SYSFS_CLASSTYPE = "misc"
2290 def __init__(self):
2291 ClassDevice.__init__(self)
2292 self._default_pool = None
2294 @override
2295 def sysfs_devname(self) -> str:
2296 return "blktap!control"
2298 class DefaultPool(Attribute):
2299 SYSFS_NODENAME = "default_pool"
2301 def get_default_pool_attr(self):
2302 if not self._default_pool:
2303 self._default_pool = self.DefaultPool.from_kobject(self)
2304 return self._default_pool
2306 def get_default_pool_name(self):
2307 return self.get_default_pool_attr().readline()
2309 def set_default_pool_name(self, name):
2310 self.get_default_pool_attr().writeline(name)
2312 def get_default_pool(self):
2313 return BlktapControl.get_pool(self.get_default_pool_name())
2315 def set_default_pool(self, pool):
2316 self.set_default_pool_name(pool.name)
2318 class NoSuchPool(Exception):
2319 def __init__(self, name):
2320 self.name = name
2322 @override
2323 def __str__(self) -> str:
2324 return "No such pool: {}".format(self.name)
2326 def get_pool(self, name):
2327 path = "%s/pools/%s" % (self.sysfs_path(), name)
2329 if not os.path.isdir(path):
2330 raise self.NoSuchPool(name)
2332 return PagePool(path)
2334BlktapControl = __BlktapControl()
2337class PagePool(KObject):
2339 def __init__(self, path):
2340 self.path = path
2341 self._size = None
2343 @override
2344 def sysfs_devname(self) -> str:
2345 return ''
2347 def sysfs_path(self):
2348 return self.path
2350 class Size(Attribute):
2351 SYSFS_NODENAME = "size"
2353 def get_size_attr(self):
2354 if not self._size:
2355 self._size = self.Size.from_kobject(self)
2356 return self._size
2358 def set_size(self, pages):
2359 pages = str(pages)
2360 self.get_size_attr().writeline(pages)
2362 def get_size(self):
2363 pages = self.get_size_attr().readline()
2364 return int(pages)
2367class BusDevice(KObject):
2369 SYSFS_BUSTYPE: ClassVar[str] = ""
2371 @classmethod
2372 def sysfs_bus_path(cls):
2373 return "/sys/bus/%s" % cls.SYSFS_BUSTYPE
2375 def sysfs_path(self):
2376 path = "%s/devices/%s" % (self.sysfs_bus_path(),
2377 self.sysfs_devname())
2379 return path
2382class XenbusDevice(BusDevice):
2383 """Xenbus device, in XS and sysfs"""
2385 XBT_NIL = ""
2387 XENBUS_DEVTYPE: ClassVar[str] = ""
2389 def __init__(self, domid, devid):
2390 self.domid = int(domid)
2391 self.devid = int(devid)
2392 self._xbt = XenbusDevice.XBT_NIL
2394 import xen.lowlevel.xs # pylint: disable=import-error
2395 self.xs = xen.lowlevel.xs.xs()
2397 def xs_path(self, key=None):
2398 path = "backend/%s/%d/%d" % (self.XENBUS_DEVTYPE,
2399 self.domid,
2400 self.devid)
2401 if key is not None:
2402 path = "%s/%s" % (path, key)
2404 return path
2406 def _log(self, prio, msg):
2407 syslog(prio, msg)
2409 def info(self, msg):
2410 self._log(_syslog.LOG_INFO, msg)
2412 def warn(self, msg):
2413 self._log(_syslog.LOG_WARNING, "WARNING: " + msg)
2415 def _xs_read_path(self, path):
2416 val = self.xs.read(self._xbt, path)
2417 #self.info("read %s = '%s'" % (path, val))
2418 return val
2420 def _xs_write_path(self, path, val):
2421 self.xs.write(self._xbt, path, val)
2422 self.info("wrote %s = '%s'" % (path, val))
2424 def _xs_rm_path(self, path):
2425 self.xs.rm(self._xbt, path)
2426 self.info("removed %s" % path)
2428 def read(self, key):
2429 return self._xs_read_path(self.xs_path(key))
2431 def has_xs_key(self, key):
2432 return self.read(key) is not None
2434 def write(self, key, val):
2435 self._xs_write_path(self.xs_path(key), val)
2437 def rm(self, key):
2438 self._xs_rm_path(self.xs_path(key))
2440 def exists(self):
2441 return self.has_xs_key(None)
2443 def begin(self):
2444 assert(self._xbt == XenbusDevice.XBT_NIL)
2445 self._xbt = self.xs.transaction_start()
2447 def commit(self):
2448 ok = self.xs.transaction_end(self._xbt, 0)
2449 self._xbt = XenbusDevice.XBT_NIL
2450 return ok
2452 def abort(self):
2453 ok = self.xs.transaction_end(self._xbt, 1)
2454 assert(ok == True)
2455 self._xbt = XenbusDevice.XBT_NIL
2457 def create_physical_device(self):
2458 """The standard protocol is: toolstack writes 'params', linux hotplug
2459 script translates this into physical-device=%x:%x"""
2460 if self.has_xs_key("physical-device"):
2461 return
2462 try:
2463 params = self.read("params")
2464 frontend = self.read("frontend")
2465 is_cdrom = self._xs_read_path("%s/device-type") == "cdrom"
2466 # We don't have PV drivers for CDROM devices, so we prevent blkback
2467 # from opening the physical-device
2468 if not(is_cdrom):
2469 major_minor = os.stat(params).st_rdev
2470 major, minor = divmod(major_minor, 256)
2471 self.write("physical-device", "%x:%x" % (major, minor))
2472 except:
2473 util.logException("BLKTAP2:create_physical_device")
2475 def signal_hotplug(self, online=True):
2476 xapi_path = "/xapi/%d/hotplug/%s/%d/hotplug" % (self.domid,
2477 self.XENBUS_DEVTYPE,
2478 self.devid)
2479 upstream_path = self.xs_path("hotplug-status")
2480 if online:
2481 self._xs_write_path(xapi_path, "online")
2482 self._xs_write_path(upstream_path, "connected")
2483 else:
2484 self._xs_rm_path(xapi_path)
2485 self._xs_rm_path(upstream_path)
2487 @override
2488 def sysfs_devname(self) -> str:
2489 return "%s-%d-%d" % (self.XENBUS_DEVTYPE,
2490 self.domid, self.devid)
2492 @override
2493 def __str__(self) -> str:
2494 return self.sysfs_devname()
2496 @classmethod
2497 def find(cls):
2498 pattern = "/sys/bus/%s/devices/%s*" % (cls.SYSFS_BUSTYPE,
2499 cls.XENBUS_DEVTYPE)
2500 for path in glob.glob(pattern):
2502 name = os.path.basename(path)
2503 (_type, domid, devid) = name.split('-')
2505 yield cls(domid, devid)
2508class XenBackendDevice(XenbusDevice):
2509 """Xenbus backend device"""
2510 SYSFS_BUSTYPE = "xen-backend"
2512 @classmethod
2513 def from_xs_path(cls, _path):
2514 (_backend, _type, domid, devid) = _path.split('/')
2516 assert _backend == 'backend'
2517 assert _type == cls.XENBUS_DEVTYPE
2519 domid = int(domid)
2520 devid = int(devid)
2522 return cls(domid, devid)
2525class Blkback(XenBackendDevice):
2526 """A blkback VBD"""
2528 XENBUS_DEVTYPE = "vbd"
2530 def __init__(self, domid, devid):
2531 XenBackendDevice.__init__(self, domid, devid)
2532 self._phy = None
2533 self._vdi_uuid = None
2534 self._q_state = None
2535 self._q_events = None
2537 class XenstoreValueError(Exception):
2538 KEY: ClassVar[str] = ""
2540 def __init__(self, vbd, _str):
2541 self.vbd = vbd
2542 self.str = _str
2544 @override
2545 def __str__(self) -> str:
2546 return "Backend %s " % self.vbd + \
2547 "has %s = %s" % (self.KEY, self.str)
2549 class PhysicalDeviceError(XenstoreValueError):
2550 KEY = "physical-device"
2552 class PhysicalDevice(object):
2554 def __init__(self, major, minor):
2555 self.major = int(major)
2556 self.minor = int(minor)
2558 @classmethod
2559 def from_xbdev(cls, xbdev):
2561 phy = xbdev.read("physical-device")
2563 try:
2564 major, minor = phy.split(':')
2565 major = int(major, 0x10)
2566 minor = int(minor, 0x10)
2567 except Exception as e:
2568 raise xbdev.PhysicalDeviceError(xbdev, phy)
2570 return cls(major, minor)
2572 def makedev(self):
2573 return os.makedev(self.major, self.minor)
2575 def is_tap(self):
2576 return self.major == Tapdisk.major()
2578 @override
2579 def __str__(self) -> str:
2580 return "%s:%s" % (self.major, self.minor)
2582 @override
2583 def __eq__(self, other) -> bool:
2584 return \
2585 self.major == other.major and \
2586 self.minor == other.minor
2588 def get_physical_device(self):
2589 if not self._phy:
2590 self._phy = self.PhysicalDevice.from_xbdev(self)
2591 return self._phy
2593 class QueueEvents(Attribute):
2594 """Blkback sysfs node to select queue-state event
2595 notifications emitted."""
2597 SYSFS_NODENAME = "queue_events"
2599 QUEUE_RUNNING = (1 << 0)
2600 QUEUE_PAUSE_DONE = (1 << 1)
2601 QUEUE_SHUTDOWN_DONE = (1 << 2)
2602 QUEUE_PAUSE_REQUEST = (1 << 3)
2603 QUEUE_SHUTDOWN_REQUEST = (1 << 4)
2605 def get_mask(self):
2606 return int(self.readline(), 0x10)
2608 def set_mask(self, mask):
2609 self.writeline("0x%x" % mask)
2611 def get_queue_events(self):
2612 if not self._q_events:
2613 self._q_events = self.QueueEvents.from_kobject(self)
2614 return self._q_events
2616 def get_vdi_uuid(self):
2617 if not self._vdi_uuid:
2618 self._vdi_uuid = self.read("sm-data/vdi-uuid")
2619 return self._vdi_uuid
2621 def pause_requested(self):
2622 return self.has_xs_key("pause")
2624 def shutdown_requested(self):
2625 return self.has_xs_key("shutdown-request")
2627 def shutdown_done(self):
2628 return self.has_xs_key("shutdown-done")
2630 def running(self):
2631 return self.has_xs_key('queue-0/kthread-pid')
2633 @classmethod
2634 def find_by_physical_device(cls, phy):
2635 for dev in cls.find():
2636 try:
2637 _phy = dev.get_physical_device()
2638 except cls.PhysicalDeviceError:
2639 continue
2641 if _phy == phy:
2642 yield dev
2644 @classmethod
2645 def find_by_tap_minor(cls, minor):
2646 phy = cls.PhysicalDevice(Tapdisk.major(), minor)
2647 return cls.find_by_physical_device(phy)
2649 @classmethod
2650 def find_by_tap(cls, tapdisk):
2651 return cls.find_by_tap_minor(tapdisk.minor)
2653 def has_tap(self):
2655 if not self.can_tap():
2656 return False
2658 phy = self.get_physical_device()
2659 if phy:
2660 return phy.is_tap()
2662 return False
2664 def is_bare_hvm(self):
2665 """File VDIs for bare HVM. These are directly accessible by Qemu."""
2666 try:
2667 self.get_physical_device()
2669 except self.PhysicalDeviceError as e:
2670 vdi_type = self.read("type")
2672 self.info("HVM VDI: type=%s" % vdi_type)
2674 if e.str is not None or vdi_type != 'file':
2675 raise
2677 return True
2679 return False
2681 def can_tap(self):
2682 return not self.is_bare_hvm()
2685class BlkbackEventHandler(UEventHandler):
2687 LOG_FACILITY = _syslog.LOG_DAEMON
2689 def __init__(self, ident=None, action=None):
2690 if not ident:
2691 ident = self.__class__.__name__
2693 self.ident = ident
2694 self._vbd = None
2695 self._tapdisk = None
2697 UEventHandler.__init__(self)
2699 @override
2700 def run(self) -> None:
2702 self.xs_path = self.getenv('XENBUS_PATH')
2703 openlog(str(self), 0, self.LOG_FACILITY)
2705 UEventHandler.run(self)
2707 @override
2708 def __str__(self) -> str:
2710 try:
2711 path = self.xs_path
2712 except:
2713 path = None
2715 try:
2716 action = self.get_action()
2717 except:
2718 action = None
2720 return "%s[%s](%s)" % (self.ident, action, path)
2722 def _log(self, prio, msg):
2723 syslog(prio, msg)
2724 util.SMlog("%s: " % self + msg)
2726 def info(self, msg):
2727 self._log(_syslog.LOG_INFO, msg)
2729 def warn(self, msg):
2730 self._log(_syslog.LOG_WARNING, "WARNING: " + msg)
2732 def error(self, msg):
2733 self._log(_syslog.LOG_ERR, "ERROR: " + msg)
2735 def get_vbd(self):
2736 if not self._vbd:
2737 self._vbd = Blkback.from_xs_path(self.xs_path)
2738 return self._vbd
2740 def get_tapdisk(self):
2741 if not self._tapdisk:
2742 minor = self.get_vbd().get_physical_device().minor
2743 self._tapdisk = Tapdisk.from_minor(minor)
2744 return self._tapdisk
2745 #
2746 # Events
2747 #
2749 def __add(self):
2750 vbd = self.get_vbd()
2751 # Manage blkback transitions
2752 # self._manage_vbd()
2754 vbd.create_physical_device()
2756 vbd.signal_hotplug()
2758 @retried(backoff=.5, limit=10)
2759 def add(self):
2760 try:
2761 self.__add()
2762 except Attribute.NoSuchAttribute as e:
2763 #
2764 # FIXME: KOBJ_ADD is racing backend.probe, which
2765 # registers device attributes. So poll a little.
2766 #
2767 self.warn("%s, still trying." % e)
2768 raise RetryLoop.TransientFailure(e)
2770 def __change(self):
2771 vbd = self.get_vbd()
2773 # 1. Pause or resume tapdisk (if there is one)
2775 if vbd.has_tap():
2776 pass
2777 #self._pause_update_tap()
2779 # 2. Signal Xapi.VBD.pause/resume completion
2781 self._signal_xapi()
2783 def change(self):
2784 vbd = self.get_vbd()
2786 # NB. Beware of spurious change events between shutdown
2787 # completion and device removal. Also, Xapi.VM.migrate will
2788 # hammer a couple extra shutdown-requests into the source VBD.
2790 while True:
2791 vbd.begin()
2793 if not vbd.exists() or \
2794 vbd.shutdown_done():
2795 break
2797 self.__change()
2799 if vbd.commit():
2800 return
2802 vbd.abort()
2803 self.info("spurious uevent, ignored.")
2805 def remove(self):
2806 vbd = self.get_vbd()
2808 vbd.signal_hotplug(False)
2810 ACTIONS = {'add': add,
2811 'change': change,
2812 'remove': remove}
2813 #
2814 # VDI.pause
2815 #
2817 def _tap_should_pause(self):
2818 """Enumerate all VBDs on our tapdisk. Returns true iff any was
2819 paused"""
2821 tapdisk = self.get_tapdisk()
2822 TapState = Tapdisk.PauseState
2824 PAUSED = 'P'
2825 RUNNING = 'R'
2826 PAUSED_SHUTDOWN = 'P,S'
2827 # NB. Shutdown/paused is special. We know it's not going
2828 # to restart again, so it's a RUNNING. Still better than
2829 # backtracking a removed device during Vbd.unplug completion.
2831 next = TapState.RUNNING
2832 vbds = {}
2834 for vbd in Blkback.find_by_tap(tapdisk):
2835 name = str(vbd)
2837 pausing = vbd.pause_requested()
2838 closing = vbd.shutdown_requested()
2839 running = vbd.running()
2841 if pausing:
2842 if closing and not running:
2843 vbds[name] = PAUSED_SHUTDOWN
2844 else:
2845 vbds[name] = PAUSED
2846 next = TapState.PAUSED
2848 else:
2849 vbds[name] = RUNNING
2851 self.info("tapdev%d (%s): %s -> %s"
2852 % (tapdisk.minor, tapdisk.pause_state(),
2853 vbds, next))
2855 return next == TapState.PAUSED
2857 def _pause_update_tap(self):
2858 vbd = self.get_vbd()
2860 if self._tap_should_pause():
2861 self._pause_tap()
2862 else:
2863 self._resume_tap()
2865 def _pause_tap(self):
2866 tapdisk = self.get_tapdisk()
2868 if not tapdisk.is_paused():
2869 self.info("pausing %s" % tapdisk)
2870 tapdisk.pause()
2872 def _resume_tap(self):
2873 tapdisk = self.get_tapdisk()
2875 # NB. Raw VDI snapshots. Refresh the physical path and
2876 # type while resuming.
2877 vbd = self.get_vbd()
2878 vdi_uuid = vbd.get_vdi_uuid()
2880 if tapdisk.is_paused():
2881 self.info("loading vdi uuid=%s" % vdi_uuid)
2882 vdi = VDI.from_cli(vdi_uuid)
2883 _type = vdi.get_tap_type()
2884 path = vdi.get_phy_path()
2885 self.info("resuming %s on %s:%s" % (tapdisk, _type, path))
2886 tapdisk.unpause(_type, path)
2887 #
2888 # VBD.pause/shutdown
2889 #
2891 def _manage_vbd(self):
2892 vbd = self.get_vbd()
2893 # NB. Hook into VBD state transitions.
2895 events = vbd.get_queue_events()
2897 mask = 0
2898 mask |= events.QUEUE_PAUSE_DONE # pause/unpause
2899 mask |= events.QUEUE_SHUTDOWN_DONE # shutdown
2900 # TODO: mask |= events.QUEUE_SHUTDOWN_REQUEST, for shutdown=force
2901 # TODO: mask |= events.QUEUE_RUNNING, for ionice updates etc
2903 events.set_mask(mask)
2904 self.info("wrote %s = %#02x" % (events.path, mask))
2906 def _signal_xapi(self):
2907 vbd = self.get_vbd()
2909 pausing = vbd.pause_requested()
2910 closing = vbd.shutdown_requested()
2911 running = vbd.running()
2913 handled = 0
2915 if pausing and not running:
2916 if 'pause-done' not in vbd:
2917 vbd.write('pause-done', '')
2918 handled += 1
2920 if not pausing:
2921 if 'pause-done' in vbd:
2922 vbd.rm('pause-done')
2923 handled += 1
2925 if closing and not running:
2926 if 'shutdown-done' not in vbd:
2927 vbd.write('shutdown-done', '')
2928 handled += 1
2930 if handled > 1:
2931 self.warn("handled %d events, " % handled +
2932 "pausing=%s closing=%s running=%s" % \
2933 (pausing, closing, running))
2935if __name__ == '__main__': 2935 ↛ 2937line 2935 didn't jump to line 2937, because the condition on line 2935 was never true
2937 import sys
2938 prog = os.path.basename(sys.argv[0])
2940 #
2941 # Simple CLI interface for manual operation
2942 #
2943 # tap.* level calls go down to local Tapdisk()s (by physical path)
2944 # vdi.* level calls run the plugin calls across host boundaries.
2945 #
2947 def usage(stream):
2948 print("usage: %s tap.{list|major}" % prog, file=stream)
2949 print(" %s tap.{launch|find|get|pause|" % prog + \
2950 "unpause|shutdown|stats} {[<tt>:]<path>} | [minor=]<int> | .. }", file=stream)
2951 print(" %s vbd.uevent" % prog, file=stream)
2953 try:
2954 cmd = sys.argv[1]
2955 except IndexError:
2956 usage(sys.stderr)
2957 sys.exit(1)
2959 try:
2960 _class, method = cmd.split('.')
2961 except:
2962 usage(sys.stderr)
2963 sys.exit(1)
2965 #
2966 # Local Tapdisks
2967 #
2969 if cmd == 'tap.major':
2971 print("%d" % Tapdisk.major())
2973 elif cmd == 'tap.launch':
2975 tapdisk = Tapdisk.launch_from_arg(sys.argv[2])
2976 print("Launched %s" % tapdisk, file=sys.stderr)
2978 elif _class == 'tap':
2980 attrs: Dict[str, Any] = {}
2981 for item in sys.argv[2:]:
2982 try:
2983 key, val = item.split('=')
2984 attrs[key] = val
2985 continue
2986 except ValueError:
2987 pass
2989 try:
2990 attrs['minor'] = int(item)
2991 continue
2992 except ValueError:
2993 pass
2995 try:
2996 arg = Tapdisk.Arg.parse(item)
2997 attrs['_type'] = arg.type
2998 attrs['path'] = arg.path
2999 continue
3000 except Tapdisk.Arg.InvalidArgument:
3001 pass
3003 attrs['path'] = item
3005 if cmd == 'tap.list':
3007 for tapdisk in Tapdisk.list( ** attrs):
3008 blktap = tapdisk.get_blktap()
3009 print(tapdisk, end=' ')
3010 print("%s: task=%s pool=%s" % \
3011 (blktap,
3012 blktap.get_task_pid(),
3013 blktap.get_pool_name()))
3015 elif cmd == 'tap.vbds':
3016 # Find all Blkback instances for a given tapdisk
3018 for tapdisk in Tapdisk.list( ** attrs):
3019 print("%s:" % tapdisk, end=' ')
3020 for vbd in Blkback.find_by_tap(tapdisk):
3021 print(vbd, end=' ')
3022 print()
3024 else:
3026 if not attrs:
3027 usage(sys.stderr)
3028 sys.exit(1)
3030 try:
3031 tapdisk = Tapdisk.get( ** attrs)
3032 except TypeError:
3033 usage(sys.stderr)
3034 sys.exit(1)
3036 if cmd == 'tap.shutdown':
3037 # Shutdown a running tapdisk, or raise
3038 tapdisk.shutdown()
3039 print("Shut down %s" % tapdisk, file=sys.stderr)
3041 elif cmd == 'tap.pause':
3042 # Pause an unpaused tapdisk, or raise
3043 tapdisk.pause()
3044 print("Paused %s" % tapdisk, file=sys.stderr)
3046 elif cmd == 'tap.unpause':
3047 # Unpause a paused tapdisk, or raise
3048 tapdisk.unpause()
3049 print("Unpaused %s" % tapdisk, file=sys.stderr)
3051 elif cmd == 'tap.stats':
3052 # Gather tapdisk status
3053 stats = tapdisk.stats()
3054 print("%s:" % tapdisk)
3055 print(json.dumps(stats, indent=True))
3057 else:
3058 usage(sys.stderr)
3059 sys.exit(1)
3061 elif cmd == 'vbd.uevent':
3063 hnd = BlkbackEventHandler(cmd)
3065 if not sys.stdin.isatty():
3066 try:
3067 hnd.run()
3068 except Exception as e:
3069 hnd.error("Unhandled Exception: %s" % e)
3071 import traceback
3072 _type, value, tb = sys.exc_info()
3073 trace = traceback.format_exception(_type, value, tb)
3074 for entry in trace:
3075 for line in entry.rstrip().split('\n'):
3076 util.SMlog(line)
3077 else:
3078 hnd.run()
3080 elif cmd == 'vbd.list':
3082 for vbd in Blkback.find():
3083 print(vbd, \
3084 "physical-device=%s" % vbd.get_physical_device(), \
3085 "pause=%s" % vbd.pause_requested())
3087 else:
3088 usage(sys.stderr)
3089 sys.exit(1)