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 except OSError as e:
225 raise cls.CommandFailure(cmd, errno=e.errno)
227 return cls(cmd, p)
229 def _errmsg(self, stderr):
230 output = map(str.rstrip, stderr)
231 return "; ".join(output)
233 def _wait(self, quiet=False, text_mode=True):
234 """
235 Reap the child tap-ctl process of this invocation.
236 Raises a TapCtl.CommandFailure on non-zero exit status.
237 """
238 stdout, stderr = self._p.communicate()
239 status = self._p.returncode
240 if not quiet:
241 util.SMlog(" = %d" % status)
243 if status == 0:
244 return stdout
246 info = {'errmsg': self._errmsg(
247 stderr if text_mode else stderr.decode()),
248 'pid': self._p.pid}
250 if status < 0:
251 info['signal'] = -status
252 else:
253 info['status'] = status
255 raise self.CommandFailure(self.cmd, ** info)
257 @classmethod
258 def _pread(cls, args, quiet=False, input=None, text_mode=True):
259 """
260 Spawn a tap-ctl invocation and read a single line.
261 """
262 tapctl = cls._call(args=args, quiet=quiet, input=input,
263 text_mode=text_mode)
265 output = tapctl._wait(quiet=quiet, text_mode=text_mode)
266 return output
268 @staticmethod
269 def _maybe(opt, parm):
270 if parm is not None:
271 return [opt, parm]
272 return []
274 @classmethod
275 def __list(cls, minor=None, pid=None, _type=None, path=None):
276 args = ["list"]
277 args += cls._maybe("-m", minor)
278 args += cls._maybe("-p", pid)
279 args += cls._maybe("-t", _type)
280 args += cls._maybe("-f", path)
282 tapctl = cls._call(args, quiet=True)
283 stdout = tapctl._wait(quiet=True)
285 for stdout_line in stdout.splitlines():
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 @classmethod
312 @retried(backoff=.5, limit=10)
313 def list(cls, **args):
315 # FIXME. We typically get an EPROTO when uevents interleave
316 # with SM ops and a tapdisk shuts down under our feet. Should
317 # be fixed in SM.
319 try:
320 return list(cls.__list( ** args))
322 except cls.CommandFailure as e:
323 transient = [errno.EPROTO, errno.ENOENT]
324 if e.has_status and e.status in transient:
325 raise RetryLoop.TransientFailure(e)
326 raise
328 @classmethod
329 def allocate(cls, devpath=None):
330 args = ["allocate"]
331 args += cls._maybe("-d", devpath)
332 return cls._pread(args)
334 @classmethod
335 def free(cls, minor):
336 args = ["free", "-m", minor]
337 cls._pread(args)
339 @classmethod
340 @retried(backoff=.5, limit=10)
341 def spawn(cls):
342 args = ["spawn"]
343 try:
344 pid = cls._pread(args)
345 return int(pid)
346 except cls.CommandFailure as ce:
347 # intermittent failures to spawn. CA-292268
348 if ce.status == 1:
349 raise RetryLoop.TransientFailure(ce)
350 raise
352 @classmethod
353 def attach(cls, pid, minor):
354 args = ["attach", "-p", pid, "-m", minor]
355 cls._pread(args)
357 @classmethod
358 def detach(cls, pid, minor):
359 args = ["detach", "-p", pid, "-m", minor]
360 cls._pread(args)
362 @classmethod
363 def _load_key(cls, key_hash, vdi_uuid):
364 import plugins
366 return plugins.load_key(key_hash, vdi_uuid)
368 @classmethod
369 def open(cls, pid, minor, _type, _file, options):
370 params = Tapdisk.Arg(_type, _file)
371 args = ["open", "-p", pid, "-m", minor, '-a', str(params)]
372 text_mode = True
373 input = None
374 if options.get("rdonly"):
375 args.append('-R')
376 if options.get("lcache"):
377 args.append("-r")
378 if options.get("existing_prt") is not None:
379 args.append("-e")
380 args.append(str(options["existing_prt"]))
381 if options.get("secondary"):
382 args.append("-2")
383 args.append(options["secondary"])
384 if options.get("standby"):
385 args.append("-s")
386 if options.get("timeout"):
387 args.append("-t")
388 args.append(str(options["timeout"]))
389 if not options.get("o_direct", True):
390 args.append("-D")
391 if options.get('cbtlog'):
392 args.extend(['-C', options['cbtlog']])
393 if options.get('key_hash'):
394 key_hash = options['key_hash']
395 vdi_uuid = options['vdi_uuid']
396 key = cls._load_key(key_hash, vdi_uuid)
398 if not key:
399 raise util.SMException("No key found with key hash {}".format(key_hash))
400 input = key
401 text_mode = False
402 args.append('-E')
404 cls._pread(args=args, input=input, text_mode=text_mode)
406 @classmethod
407 def close(cls, pid, minor, force=False):
408 args = ["close", "-p", pid, "-m", minor, "-t", "120"]
409 if force:
410 args += ["-f"]
411 cls._pread(args)
413 @classmethod
414 def pause(cls, pid, minor):
415 args = ["pause", "-p", pid, "-m", minor]
416 cls._pread(args)
418 @classmethod
419 def unpause(cls, pid, minor, _type=None, _file=None, mirror=None,
420 cbtlog=None):
421 args = ["unpause", "-p", pid, "-m", minor]
422 if mirror:
423 args.extend(["-2", mirror])
424 if _type and _file:
425 params = Tapdisk.Arg(_type, _file)
426 args += ["-a", str(params)]
427 if cbtlog:
428 args.extend(["-c", cbtlog])
429 cls._pread(args)
431 @classmethod
432 def shutdown(cls, pid):
433 # TODO: This should be a real tap-ctl command
434 os.kill(pid, signal.SIGTERM)
435 os.waitpid(pid, 0)
437 @classmethod
438 def stats(cls, pid, minor):
439 args = ["stats", "-p", pid, "-m", minor]
440 return cls._pread(args, quiet=True)
442 @classmethod
443 def major(cls):
444 args = ["major"]
445 major = cls._pread(args)
446 return int(major)
448 @classmethod
449 def commit(cls, pid, minor, vdi_type, path):
450 args = ["commit", "-p", pid, "-m", minor, "-a", path]
451 cls._pread(args)
453 @classmethod
454 def query(cls, pid, minor, quiet=False):
455 args = ["query", "-p", pid, "-m", minor]
456 output = cls._pread(args, quiet=quiet)
457 m = re.match(r"Commit status '(.+)' \((\d+)\/(\d+)\)", output)
458 status = m.group(1)
459 coalesced = int(m.group(2))
460 total_coalesce = int(m.group(3))
461 return (status, coalesced, total_coalesce)
463 @classmethod
464 def cancel_commit(cls, pid, minor, wait=True):
465 args = ["cancel", "-p", pid, "-m", minor]
466 if wait:
467 args.append("-w")
468 cls._pread(args)
470class TapdiskExists(Exception):
471 """Tapdisk already running."""
473 def __init__(self, tapdisk):
474 self.tapdisk = tapdisk
476 @override
477 def __str__(self) -> str:
478 return "%s already running" % self.tapdisk
481class TapdiskNotRunning(Exception):
482 """No such Tapdisk."""
484 def __init__(self, **attrs):
485 self.attrs = attrs
487 @override
488 def __str__(self) -> str:
489 items = iter(self.attrs.items())
490 attrs = ", ".join("%s=%s" % attr
491 for attr in items)
492 return "No such Tapdisk(%s)" % attrs
495class TapdiskNotUnique(Exception):
496 """More than one tapdisk on one path."""
498 def __init__(self, tapdisks):
499 self.tapdisks = tapdisks
501 @override
502 def __str__(self) -> str:
503 tapdisks = map(str, self.tapdisks)
504 return "Found multiple tapdisks: %s" % tapdisks
507class TapdiskFailed(Exception):
508 """Tapdisk launch failure."""
510 def __init__(self, arg, err):
511 self.arg = arg
512 self.err = err
514 @override
515 def __str__(self) -> str:
516 return "Tapdisk(%s): %s" % (self.arg, self.err)
518 def get_error(self):
519 return self.err
522class TapdiskInvalidState(Exception):
523 """Tapdisk pause/unpause failure"""
525 def __init__(self, tapdisk):
526 self.tapdisk = tapdisk
528 @override
529 def __str__(self) -> str:
530 return str(self.tapdisk)
533def mkdirs(path, mode=0o777):
534 if not os.path.exists(path):
535 parent, subdir = os.path.split(path)
536 assert parent != path
537 try:
538 if parent:
539 mkdirs(parent, mode)
540 if subdir:
541 os.mkdir(path, mode)
542 except OSError as e:
543 if e.errno != errno.EEXIST:
544 raise
547class KObject(object):
549 SYSFS_CLASSTYPE: ClassVar[str] = ""
551 @abstractmethod
552 def sysfs_devname(self) -> str:
553 pass
556class Attribute(object):
558 SYSFS_NODENAME: ClassVar[str] = ""
560 def __init__(self, path):
561 self.path = path
563 @classmethod
564 def from_kobject(cls, kobj):
565 path = "%s/%s" % (kobj.sysfs_path(), cls.SYSFS_NODENAME)
566 return cls(path)
568 class NoSuchAttribute(Exception):
569 def __init__(self, name):
570 self.name = name
572 @override
573 def __str__(self) -> str:
574 return "No such attribute: %s" % self.name
576 def _open(self, mode='r'):
577 try:
578 return open(self.path, mode)
579 except IOError as e:
580 if e.errno == errno.ENOENT:
581 raise self.NoSuchAttribute(self)
582 raise
584 def readline(self):
585 f = self._open('r')
586 s = f.readline().rstrip()
587 f.close()
588 return s
590 def writeline(self, val):
591 f = self._open('w')
592 f.write(val)
593 f.close()
596class ClassDevice(KObject):
598 @classmethod
599 def sysfs_class_path(cls):
600 return "/sys/class/%s" % cls.SYSFS_CLASSTYPE
602 def sysfs_path(self):
603 return "%s/%s" % (self.sysfs_class_path(),
604 self.sysfs_devname())
607class Blktap(ClassDevice):
609 DEV_BASEDIR = '/dev/xen/blktap-2'
611 SYSFS_CLASSTYPE = "blktap2"
613 def __init__(self, minor):
614 self.minor = minor
615 self._pool = None
616 self._task = None
618 @classmethod
619 def allocate(cls):
620 # FIXME. Should rather go into init.
621 mkdirs(cls.DEV_BASEDIR)
623 devname = TapCtl.allocate()
624 minor = Tapdisk._parse_minor(devname)
625 return cls(minor)
627 def free(self):
628 TapCtl.free(self.minor)
630 @override
631 def __str__(self) -> str:
632 return "%s(minor=%d)" % (self.__class__.__name__, self.minor)
634 @override
635 def sysfs_devname(self) -> str:
636 return "blktap!blktap%d" % self.minor
638 class Pool(Attribute):
639 SYSFS_NODENAME = "pool"
641 def get_pool_attr(self):
642 if not self._pool:
643 self._pool = self.Pool.from_kobject(self)
644 return self._pool
646 def get_pool_name(self):
647 return self.get_pool_attr().readline()
649 def set_pool_name(self, name):
650 self.get_pool_attr().writeline(name)
652 def set_pool_size(self, pages):
653 self.get_pool().set_size(pages)
655 def get_pool(self):
656 return BlktapControl.get_pool(self.get_pool_name())
658 def set_pool(self, pool):
659 self.set_pool_name(pool.name)
661 class Task(Attribute):
662 SYSFS_NODENAME = "task"
664 def get_task_attr(self):
665 if not self._task:
666 self._task = self.Task.from_kobject(self)
667 return self._task
669 def get_task_pid(self):
670 pid = self.get_task_attr().readline()
671 try:
672 return int(pid)
673 except ValueError:
674 return None
676 def find_tapdisk(self):
677 pid = self.get_task_pid()
678 if pid is None:
679 return None
681 return Tapdisk.find(pid=pid, minor=self.minor)
683 def get_tapdisk(self):
684 tapdisk = self.find_tapdisk()
685 if not tapdisk:
686 raise TapdiskNotRunning(minor=self.minor)
687 return tapdisk
690class Tapdisk(object):
692 TYPES = ['aio', 'vhd', 'qcow2']
694 def __init__(self, pid, minor, _type, path, state):
695 self.pid = pid
696 self.minor = minor
697 self.type = _type
698 self.path = path
699 self.state = state
700 self._dirty = False
701 self._blktap = None
703 @override
704 def __str__(self) -> str:
705 state = self.pause_state()
706 return "Tapdisk(%s, pid=%d, minor=%s, state=%s)" % \
707 (self.get_arg(), self.pid, self.minor, state)
709 @classmethod
710 def list(cls, **args):
712 for row in TapCtl.list( ** args):
714 args = {'pid': None,
715 'minor': None,
716 'state': None,
717 '_type': None,
718 'path': None}
720 for key, val in row.items():
721 if key in args:
722 args[key] = val
724 if 'args' in row: 724 ↛ 729line 724 didn't jump to line 729, because the condition on line 724 was never false
725 image = Tapdisk.Arg.parse(row['args'])
726 args['_type'] = image.type
727 args['path'] = image.path
729 if None in args.values(): 729 ↛ 730line 729 didn't jump to line 730, because the condition on line 729 was never true
730 continue
732 yield Tapdisk( ** args)
734 @classmethod
735 def find(cls, **args):
737 found = list(cls.list( ** args))
739 if len(found) > 1: 739 ↛ 740line 739 didn't jump to line 740, because the condition on line 739 was never true
740 raise TapdiskNotUnique(found)
742 if found:
743 return found[0]
745 return None
747 @classmethod
748 def find_by_path(cls, path):
749 return cls.find(path=path)
751 @classmethod
752 def find_by_minor(cls, minor):
753 return cls.find(minor=minor)
755 @classmethod
756 def get(cls, **attrs):
758 tapdisk = cls.find( ** attrs)
760 if not tapdisk: 760 ↛ 761line 760 didn't jump to line 761, because the condition on line 760 was never true
761 raise TapdiskNotRunning( ** attrs)
763 return tapdisk
765 @classmethod
766 def from_path(cls, path):
767 return cls.get(path=path)
769 @classmethod
770 def get_pid_for_path(cls, path: str) -> str:
771 return util.pread2(['/usr/sbin/lsof', '-t', path]).strip()
773 @classmethod
774 def from_minor(cls, minor):
775 pid = None
776 dev_path = os.path.join(Blktap.DEV_BASEDIR, f"blktap{minor}")
777 if os.path.exists(dev_path): 777 ↛ 780line 777 didn't jump to line 780, because the condition on line 777 was never false
778 pid = cls.get_pid_for_path(dev_path)
780 return cls.get(minor=minor, pid=pid)
782 @classmethod
783 def __from_blktap(cls, blktap):
784 tapdisk = cls.from_minor(minor=blktap.minor)
785 tapdisk._blktap = blktap
786 return tapdisk
788 def get_blktap(self):
789 if not self._blktap:
790 self._blktap = Blktap(self.minor)
791 return self._blktap
793 class Arg:
795 def __init__(self, _type, path):
796 self.type = _type
797 self.path = path
799 @override
800 def __str__(self) -> str:
801 return "%s:%s" % (self.type, self.path)
803 @classmethod
804 def parse(cls, arg):
806 try:
807 _type, path = arg.split(":", 1)
808 except ValueError:
809 raise cls.InvalidArgument(arg)
811 if _type not in Tapdisk.TYPES: 811 ↛ 812line 811 didn't jump to line 812, because the condition on line 811 was never true
812 raise cls.InvalidType(_type)
814 return cls(_type, path)
816 class InvalidType(Exception):
817 def __init__(self, _type):
818 self.type = _type
820 @override
821 def __str__(self) -> str:
822 return "Not a Tapdisk type: %s" % self.type
824 class InvalidArgument(Exception):
825 def __init__(self, arg):
826 self.arg = arg
828 @override
829 def __str__(self) -> str:
830 return "Not a Tapdisk image: %s" % self.arg
832 def get_arg(self):
833 return self.Arg(self.type, self.path)
835 def get_devpath(self):
836 return "%s/tapdev%d" % (Blktap.DEV_BASEDIR, self.minor)
838 @classmethod
839 def launch_from_arg(cls, arg):
840 arg = cls.Arg.parse(arg)
841 return cls.launch(arg.path, arg.type, False)
843 @staticmethod
844 def cgclassify(pid):
846 # We dont provide any <controllers>:<path>
847 # so cgclassify uses /etc/cgrules.conf which
848 # we have configured in the spec file.
849 cmd = ["cgclassify", str(pid)]
850 try:
851 util.pread2(cmd)
852 except util.CommandException as e:
853 util.logException(e)
855 @classmethod
856 def launch_on_tap(cls, blktap, path, _type, options):
858 tapdisk = cls.find_by_path(path)
859 if tapdisk: 859 ↛ 860line 859 didn't jump to line 860, because the condition on line 859 was never true
860 raise TapdiskExists(tapdisk)
862 minor = blktap.minor
863 try:
864 pid = TapCtl.spawn()
865 cls.cgclassify(pid)
866 try:
867 TapCtl.attach(pid, minor)
869 try:
870 retry_open = 0
871 while True:
872 try:
873 TapCtl.open(pid, minor, _type, path, options)
874 break
875 except TapCtl.CommandFailure as e:
876 err = (
877 'status' in e.info and e.info['status']
878 ) or None
879 if err in (errno.EIO, errno.EROFS, errno.EAGAIN): 879 ↛ 880line 879 didn't jump to line 880, because the condition on line 879 was never true
880 if retry_open < 5:
881 retry_open += 1
882 time.sleep(1)
883 continue
884 if LINSTOR_AVAILABLE and err == errno.EROFS:
885 log_drbd_openers(path)
886 raise
887 try:
888 tapdisk = cls.__from_blktap(blktap)
889 node = '/sys/dev/block/%d:%d' % (tapdisk.major(), tapdisk.minor)
890 util.set_scheduler_sysfs_node(node, ['none', 'noop'])
891 return tapdisk
892 except:
893 TapCtl.close(pid, minor)
894 raise
896 except:
897 TapCtl.detach(pid, minor)
898 raise
900 except:
901 try:
902 TapCtl.shutdown(pid)
903 except:
904 # Best effort to shutdown
905 pass
906 raise
908 except TapCtl.CommandFailure as ctl:
909 util.logException(ctl)
910 if ((path.startswith('/dev/xapi/cd/') or path.startswith('/dev/sr')) and 910 ↛ 914line 910 didn't jump to line 914, because the condition on line 910 was never false
911 ctl.has_status and ctl.get_error_code() == 123): # ENOMEDIUM (No medium found)
912 raise xs_errors.XenError('TapdiskDriveEmpty')
913 else:
914 raise TapdiskFailed(cls.Arg(_type, path), ctl)
916 @classmethod
917 def launch(cls, path, _type, rdonly):
918 blktap = Blktap.allocate()
919 try:
920 return cls.launch_on_tap(blktap, path, _type, {"rdonly": rdonly})
921 except:
922 blktap.free()
923 raise
925 def shutdown(self, force=False):
927 TapCtl.close(self.pid, self.minor, force)
929 TapCtl.detach(self.pid, self.minor)
931 self.get_blktap().free()
933 def pause(self):
935 if not self.is_running():
936 raise TapdiskInvalidState(self)
938 TapCtl.pause(self.pid, self.minor)
940 self._set_dirty()
942 def unpause(self, _type=None, path=None, mirror=None, cbtlog=None):
944 if not self.is_paused():
945 raise TapdiskInvalidState(self)
947 # FIXME: should the arguments be optional?
948 if _type is None:
949 _type = self.type
950 if path is None:
951 path = self.path
953 TapCtl.unpause(self.pid, self.minor, _type, path, mirror=mirror,
954 cbtlog=cbtlog)
956 self._set_dirty()
958 def stats(self):
959 return json.loads(TapCtl.stats(self.pid, self.minor))
960 #
961 # NB. dirty/refresh: reload attributes on next access
962 #
964 def _set_dirty(self):
965 self._dirty = True
967 def _refresh(self, __get):
968 t = self.from_minor(__get('minor'))
969 self.__init__(t.pid, t.minor, t.type, t.path, t.state)
971 @override
972 def __getattribute__(self, name) -> Any:
973 def __get(name):
974 # NB. avoid(rec(ursion)
975 return object.__getattribute__(self, name)
977 if __get('_dirty') and \ 977 ↛ 979line 977 didn't jump to line 979, because the condition on line 977 was never true
978 name in ['minor', 'type', 'path', 'state']:
979 self._refresh(__get)
980 self._dirty = False
982 return __get(name)
984 class PauseState:
985 RUNNING = 'R'
986 PAUSING = 'r'
987 PAUSED = 'P'
989 class Flags:
990 DEAD = 0x0001
991 CLOSED = 0x0002
992 QUIESCE_REQUESTED = 0x0004
993 QUIESCED = 0x0008
994 PAUSE_REQUESTED = 0x0010
995 PAUSED = 0x0020
996 SHUTDOWN_REQUESTED = 0x0040
997 LOCKING = 0x0080
998 RETRY_NEEDED = 0x0100
999 LOG_DROPPED = 0x0200
1001 PAUSE_MASK = PAUSE_REQUESTED | PAUSED
1003 def is_paused(self):
1004 return not not (self.state & self.Flags.PAUSED)
1006 def is_running(self):
1007 return not (self.state & self.Flags.PAUSE_MASK)
1009 def pause_state(self):
1010 if self.state & self.Flags.PAUSED: 1010 ↛ 1011line 1010 didn't jump to line 1011, because the condition on line 1010 was never true
1011 return self.PauseState.PAUSED
1013 if self.state & self.Flags.PAUSE_REQUESTED: 1013 ↛ 1014line 1013 didn't jump to line 1014, because the condition on line 1013 was never true
1014 return self.PauseState.PAUSING
1016 return self.PauseState.RUNNING
1018 @staticmethod
1019 def _parse_minor(devpath):
1020 regex = r'%s/(blktap|tapdev)(\d+)$' % Blktap.DEV_BASEDIR
1021 pattern = re.compile(regex)
1022 groups = pattern.search(devpath)
1023 if not groups:
1024 raise Exception("malformed tap device: '%s' (%s) " % (devpath, regex))
1026 minor = groups.group(2)
1027 return int(minor)
1029 _major = None
1031 @classmethod
1032 def major(cls):
1033 if cls._major:
1034 return cls._major
1036 devices = open("/proc/devices")
1037 for line in devices:
1039 row = line.rstrip().split(' ')
1040 if len(row) != 2:
1041 continue
1043 major, name = row
1044 if name != 'tapdev':
1045 continue
1047 cls._major = int(major)
1048 break
1050 devices.close()
1051 return cls._major
1054class VDI(object):
1055 """SR.vdi driver decorator for blktap2"""
1057 CONF_KEY_ALLOW_CACHING = "vdi_allow_caching"
1058 CONF_KEY_MODE_ON_BOOT = "vdi_on_boot"
1059 CONF_KEY_CACHE_SR = "local_cache_sr"
1060 CONF_KEY_O_DIRECT = "o_direct"
1061 LOCK_CACHE_SETUP = "cachesetup"
1063 ATTACH_DETACH_RETRY_SECS = 120
1065 def __init__(self, uuid, target, driver_info):
1066 self.target = self.TargetDriver(target, driver_info)
1067 self._vdi_uuid = uuid
1068 self._session = target.session
1069 self.xenstore_data = scsiutil.update_XS_SCSIdata(uuid, scsiutil.gen_synthetic_page_data(uuid))
1070 self.__o_direct = None
1071 self.__o_direct_reason = None
1072 self.lock = Lock("vdi", uuid)
1073 self.tap = None
1075 def get_o_direct_capability(self, options):
1076 """Returns True/False based on licensing and caching_params"""
1077 if self.__o_direct is not None: 1077 ↛ 1078line 1077 didn't jump to line 1078, because the condition on line 1077 was never true
1078 return self.__o_direct, self.__o_direct_reason
1080 if util.read_caching_is_restricted(self._session): 1080 ↛ 1081line 1080 didn't jump to line 1081, because the condition on line 1080 was never true
1081 self.__o_direct = True
1082 self.__o_direct_reason = "LICENSE_RESTRICTION"
1083 elif not ((self.target.vdi.sr.handles("nfs") or self.target.vdi.sr.handles("ext") or self.target.vdi.sr.handles("smb"))): 1083 ↛ 1086line 1083 didn't jump to line 1086, because the condition on line 1083 was never false
1084 self.__o_direct = True
1085 self.__o_direct_reason = "SR_NOT_SUPPORTED"
1086 elif options.get("rdonly") and not self.target.vdi.parent:
1087 self.__o_direct = True
1088 self.__o_direct_reason = "RO_WITH_NO_PARENT"
1089 elif options.get(self.CONF_KEY_O_DIRECT):
1090 self.__o_direct = True
1091 self.__o_direct_reason = "SR_OVERRIDE"
1093 if self.__o_direct is None: 1093 ↛ 1094line 1093 didn't jump to line 1094, because the condition on line 1093 was never true
1094 self.__o_direct = False
1095 self.__o_direct_reason = ""
1097 return self.__o_direct, self.__o_direct_reason
1099 @classmethod
1100 def from_cli(cls, uuid):
1101 session = XenAPI.xapi_local()
1102 session.xenapi.login_with_password('root', '', '', 'SM')
1104 target = sm.VDI.from_uuid(session, uuid)
1105 driver_info = target.sr.srcmd.driver_info
1107 session.xenapi.session.logout()
1109 return cls(uuid, target, driver_info)
1111 @staticmethod
1112 def _tap_type(vdi_type):
1113 """Map a VDI type (e.g. 'raw') to a tapdisk driver type (e.g. 'aio')"""
1114 return {
1115 'raw': 'aio',
1116 'vhd': 'vhd',
1117 'qcow2': 'qcow2',
1118 'iso': 'aio', # for ISO SR
1119 'aio': 'aio', # for LVHD
1120 'file': 'aio',
1121 'phy': 'aio'
1122 }[vdi_type]
1124 def get_tap_type(self):
1125 vdi_type = self.target.get_vdi_type()
1126 return VDI._tap_type(vdi_type)
1128 def get_phy_path(self):
1129 return self.target.get_vdi_path()
1131 class UnexpectedVDIType(Exception):
1133 def __init__(self, vdi_type, target):
1134 self.vdi_type = vdi_type
1135 self.target = target
1137 @override
1138 def __str__(self) -> str:
1139 return \
1140 "Target %s has unexpected VDI type '%s'" % \
1141 (type(self.target), self.vdi_type)
1143 VDI_PLUG_TYPE = {'phy': 'phy', # for NETAPP
1144 'raw': 'phy',
1145 'aio': 'tap', # for LVM raw nodes
1146 'iso': 'tap', # for ISOSR
1147 'file': 'tap',
1148 'vhd': 'tap',
1149 'qcow2': 'tap'}
1151 def tap_wanted(self):
1152 # 1. Let the target vdi_type decide
1154 vdi_type = self.target.get_vdi_type()
1156 try:
1157 plug_type = self.VDI_PLUG_TYPE[vdi_type]
1158 except KeyError:
1159 raise self.UnexpectedVDIType(vdi_type,
1160 self.target.vdi)
1162 if plug_type == 'tap': 1162 ↛ 1164line 1162 didn't jump to line 1164, because the condition on line 1162 was never false
1163 return True
1164 elif self.target.vdi.sr.handles('udev'):
1165 return True
1166 # 2. Otherwise, there may be more reasons
1167 #
1168 # .. TBD
1170 return False
1172 class TargetDriver:
1173 """Safe target driver access."""
1174 # NB. *Must* test caps for optional calls. Some targets
1175 # actually implement some slots, but do not enable them. Just
1176 # try/except would risk breaking compatibility.
1178 def __init__(self, vdi, driver_info):
1179 self.vdi = vdi
1180 self._caps = driver_info['capabilities']
1182 def has_cap(self, cap):
1183 """Determine if target has given capability"""
1184 return cap in self._caps
1186 def attach(self, sr_uuid, vdi_uuid):
1187 #assert self.has_cap("VDI_ATTACH")
1188 return self.vdi.attach(sr_uuid, vdi_uuid)
1190 def detach(self, sr_uuid, vdi_uuid):
1191 #assert self.has_cap("VDI_DETACH")
1192 self.vdi.detach(sr_uuid, vdi_uuid)
1194 def activate(self, sr_uuid, vdi_uuid):
1195 if self.has_cap("VDI_ACTIVATE"):
1196 return self.vdi.activate(sr_uuid, vdi_uuid)
1198 def deactivate(self, sr_uuid, vdi_uuid):
1199 if self.has_cap("VDI_DEACTIVATE"):
1200 self.vdi.deactivate(sr_uuid, vdi_uuid)
1201 #def resize(self, sr_uuid, vdi_uuid, size):
1202 # return self.vdi.resize(sr_uuid, vdi_uuid, size)
1204 def get_vdi_type(self):
1205 _type = self.vdi.vdi_type
1206 if not _type:
1207 raise VDI.UnexpectedVDIType(_type, self.vdi)
1208 return _type
1210 def get_vdi_path(self):
1211 return self.vdi.path
1213 class Link(object):
1214 """Relink a node under a common name"""
1215 # NB. We have to provide the device node path during
1216 # VDI.attach, but currently do not allocate the tapdisk minor
1217 # before VDI.activate. Therefore those link steps where we
1218 # relink existing devices under deterministic path names.
1220 BASEDIR: ClassVar[str] = ""
1222 def _mklink(self, target) -> None:
1223 pass
1225 @abstractmethod
1226 def _equals(self, target) -> bool:
1227 pass
1229 def __init__(self, path):
1230 self._path = path
1232 @classmethod
1233 def from_name(cls, name):
1234 path = "%s/%s" % (cls.BASEDIR, name)
1235 return cls(path)
1237 @classmethod
1238 def from_uuid(cls, sr_uuid, vdi_uuid):
1239 name = "%s/%s" % (sr_uuid, vdi_uuid)
1240 return cls.from_name(name)
1242 def path(self):
1243 return self._path
1245 def stat(self):
1246 return os.stat(self.path())
1248 def mklink(self, target) -> None:
1250 path = self.path()
1251 util.SMlog("%s -> %s" % (self, target))
1253 mkdirs(os.path.dirname(path))
1254 try:
1255 self._mklink(target)
1256 except OSError as e:
1257 # We do unlink during teardown, but have to stay
1258 # idempotent. However, a *wrong* target should never
1259 # be seen.
1260 if e.errno != errno.EEXIST:
1261 raise
1262 assert self._equals(target), "'%s' not equal to '%s'" % (path, target)
1264 def unlink(self):
1265 try:
1266 os.unlink(self.path())
1267 except OSError as e:
1268 if e.errno != errno.ENOENT:
1269 raise
1271 @override
1272 def __str__(self) -> str:
1273 path = self.path()
1274 return "%s(%s)" % (self.__class__.__name__, path)
1276 class SymLink(Link):
1277 """Symlink some file to a common name"""
1279 def readlink(self):
1280 return os.readlink(self.path())
1282 def symlink(self):
1283 return self.path()
1285 @override
1286 def _mklink(self, target) -> None:
1287 os.symlink(target, self.path())
1289 @override
1290 def _equals(self, target) -> bool:
1291 return self.readlink() == target
1293 class DeviceNode(Link):
1294 """Relink a block device node to a common name"""
1296 @classmethod
1297 def _real_stat(cls, target):
1298 """stat() not on @target, but its realpath()"""
1299 _target = os.path.realpath(target)
1300 return os.stat(_target)
1302 @classmethod
1303 def is_block(cls, target):
1304 """Whether @target refers to a block device."""
1305 return S_ISBLK(cls._real_stat(target).st_mode)
1307 @override
1308 def _mklink(self, target) -> None:
1310 st = self._real_stat(target)
1311 if not S_ISBLK(st.st_mode):
1312 raise self.NotABlockDevice(target, st)
1314 # set group read for disk group as well as root
1315 os.mknod(self.path(), st.st_mode | stat.S_IRGRP, st.st_rdev)
1316 os.chown(self.path(), st.st_uid, grp.getgrnam("disk").gr_gid)
1318 @override
1319 def _equals(self, target) -> bool:
1320 target_rdev = self._real_stat(target).st_rdev
1321 return self.stat().st_rdev == target_rdev
1323 def rdev(self):
1324 st = self.stat()
1325 assert S_ISBLK(st.st_mode)
1326 return os.major(st.st_rdev), os.minor(st.st_rdev)
1328 class NotABlockDevice(Exception):
1330 def __init__(self, path, st):
1331 self.path = path
1332 self.st = st
1334 @override
1335 def __str__(self) -> str:
1336 return "%s is not a block device: %s" % (self.path, self.st)
1338 class Hybrid(Link):
1340 def __init__(self, path):
1341 VDI.Link.__init__(self, path)
1342 self._devnode = VDI.DeviceNode(path)
1343 self._symlink = VDI.SymLink(path)
1345 def rdev(self):
1346 st = self.stat()
1347 if S_ISBLK(st.st_mode):
1348 return self._devnode.rdev()
1349 raise self._devnode.NotABlockDevice(self.path(), st)
1351 @override
1352 def mklink(self, target) -> None:
1353 if self._devnode.is_block(target):
1354 self._obj = self._devnode
1355 else:
1356 self._obj = self._symlink
1357 self._obj.mklink(target)
1359 @override
1360 def _equals(self, target) -> bool:
1361 return self._obj._equals(target)
1363 class PhyLink(SymLink):
1364 BASEDIR = "/dev/sm/phy"
1365 # NB. Cannot use DeviceNodes, e.g. FileVDIs aren't bdevs.
1367 class NBDLink(SymLink):
1369 BASEDIR = "/run/blktap-control/nbd"
1371 class BackendLink(Hybrid):
1372 BASEDIR = "/dev/sm/backend"
1373 # NB. Could be SymLinks as well, but saving major,minor pairs in
1374 # Links enables neat state capturing when managing Tapdisks. Note
1375 # that we essentially have a tap-ctl list replacement here. For
1376 # now make it a 'Hybrid'. Likely to collapse into a DeviceNode as
1377 # soon as ISOs are tapdisks.
1379 @staticmethod
1380 def _tap_activate(phy_path, vdi_type, sr_uuid, options, pool_size=None):
1382 tapdisk = Tapdisk.find_by_path(phy_path)
1383 if not tapdisk: 1383 ↛ 1384line 1383 didn't jump to line 1384, because the condition on line 1383 was never true
1384 blktap = Blktap.allocate()
1385 blktap.set_pool_name(sr_uuid)
1386 if pool_size:
1387 blktap.set_pool_size(pool_size)
1389 try:
1390 tapdisk = \
1391 Tapdisk.launch_on_tap(blktap,
1392 phy_path,
1393 VDI._tap_type(vdi_type),
1394 options)
1395 except:
1396 blktap.free()
1397 raise
1398 util.SMlog("tap.activate: Launched %s" % tapdisk)
1400 else:
1401 util.SMlog("tap.activate: Found %s" % tapdisk)
1403 return tapdisk.get_devpath(), tapdisk
1405 @staticmethod
1406 def _tap_deactivate(minor):
1408 try:
1409 tapdisk = Tapdisk.from_minor(minor)
1410 except TapdiskNotRunning as e:
1411 util.SMlog("tap.deactivate: Warning, %s" % e)
1412 # NB. Should not be here unless the agent refcount
1413 # broke. Also, a clean shutdown should not have leaked
1414 # the recorded minor.
1415 else:
1416 tapdisk.shutdown()
1417 util.SMlog("tap.deactivate: Shut down %s" % tapdisk)
1419 @classmethod
1420 def tap_pause(cls, session, sr_uuid, vdi_uuid, failfast=False):
1421 """
1422 Pauses the tapdisk.
1424 session: a XAPI session
1425 sr_uuid: the UUID of the SR on which VDI lives
1426 vdi_uuid: the UUID of the VDI to pause
1427 failfast: controls whether the VDI lock should be acquired in a
1428 non-blocking manner
1429 """
1430 util.SMlog("Pause request for %s" % vdi_uuid)
1431 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1432 session.xenapi.VDI.add_to_sm_config(vdi_ref, 'paused', 'true')
1433 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1434 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 1434 ↛ 1435line 1434 didn't jump to line 1435, because the loop on line 1434 never started
1435 host_ref = key[len('host_'):]
1436 util.SMlog("Calling tap-pause on host %s" % host_ref)
1437 if not cls.call_pluginhandler(session, host_ref,
1438 sr_uuid, vdi_uuid, "pause", failfast=failfast):
1439 # Failed to pause node
1440 session.xenapi.VDI.remove_from_sm_config(vdi_ref, 'paused')
1441 return False
1442 return True
1444 @classmethod
1445 def tap_unpause(cls, session, sr_uuid, vdi_uuid, secondary=None,
1446 activate_parents=False):
1447 util.SMlog("Unpause request for %s secondary=%s" % (vdi_uuid, secondary))
1448 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1449 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1450 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 1450 ↛ 1451line 1450 didn't jump to line 1451, because the loop on line 1450 never started
1451 host_ref = key[len('host_'):]
1452 util.SMlog("Calling tap-unpause on host %s" % host_ref)
1453 if not cls.call_pluginhandler(session, host_ref,
1454 sr_uuid, vdi_uuid, "unpause", secondary, activate_parents):
1455 # Failed to unpause node
1456 return False
1457 session.xenapi.VDI.remove_from_sm_config(vdi_ref, 'paused')
1458 return True
1460 @classmethod
1461 def tap_refresh(cls, session, sr_uuid, vdi_uuid, activate_parents=False):
1462 util.SMlog("Refresh request for %s" % vdi_uuid)
1463 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1464 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1465 for key in [x for x in sm_config.keys() if x.startswith('host_')]:
1466 host_ref = key[len('host_'):]
1467 util.SMlog("Calling tap-refresh on host %s" % host_ref)
1468 if not cls.call_pluginhandler(session, host_ref,
1469 sr_uuid, vdi_uuid, "refresh", None,
1470 activate_parents=activate_parents):
1471 # Failed to refresh node
1472 return False
1473 return True
1475 @classmethod
1476 def tap_status(cls, session, vdi_uuid):
1477 """Return True if disk is attached, false if it isn't"""
1478 util.SMlog("Disk status request for %s" % vdi_uuid)
1479 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1480 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1481 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 1481 ↛ 1482line 1481 didn't jump to line 1482, because the loop on line 1481 never started
1482 return True
1483 return False
1485 @classmethod
1486 def call_pluginhandler(cls, session, host_ref, sr_uuid, vdi_uuid, action,
1487 secondary=None, activate_parents=False, failfast=False):
1488 """Optionally, activate the parent LV before unpausing"""
1489 try:
1490 args = {"sr_uuid": sr_uuid, "vdi_uuid": vdi_uuid,
1491 "failfast": str(failfast)}
1492 if secondary:
1493 args["secondary"] = secondary
1494 if activate_parents:
1495 args["activate_parents"] = "true"
1496 ret = session.xenapi.host.call_plugin(
1497 host_ref, PLUGIN_TAP_PAUSE, action,
1498 args)
1499 return ret == "True"
1500 except Exception as e:
1501 util.logException("BLKTAP2:call_pluginhandler %s" % e)
1502 return False
1504 def _add_tag(self, vdi_uuid, writable):
1505 util.SMlog("Adding tag to: %s" % vdi_uuid)
1506 attach_mode = "RO"
1507 if writable:
1508 attach_mode = "RW"
1509 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1510 host_ref = self._session.xenapi.host.get_by_uuid(util.get_this_host())
1511 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1512 attached_as = util.attached_as(sm_config)
1513 if NO_MULTIPLE_ATTACH and (attached_as == "RW" or \ 1513 ↛ 1515line 1513 didn't jump to line 1515, because the condition on line 1513 was never true
1514 (attached_as == "RO" and attach_mode == "RW")):
1515 util.SMlog("need to reset VDI %s" % vdi_uuid)
1516 if not resetvdis.reset_vdi(self._session, vdi_uuid, force=False,
1517 term_output=False, writable=writable):
1518 raise util.SMException("VDI %s not detached cleanly" % vdi_uuid)
1519 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1520 if 'relinking' in sm_config:
1521 util.SMlog("Relinking key found, back-off and retry" % sm_config)
1522 return False
1523 if 'paused' in sm_config:
1524 util.SMlog("Paused or host_ref key found [%s]" % sm_config)
1525 return False
1526 try:
1527 self._session.xenapi.VDI.add_to_sm_config(
1528 vdi_ref, 'activating', 'True')
1529 except XenAPI.Failure as e:
1530 if e.details[0] == 'MAP_DUPLICATE_KEY' and not writable:
1531 # Someone else is activating - a retry might succeed
1532 return False
1533 raise
1534 host_key = "host_%s" % host_ref
1535 assert host_key not in sm_config
1536 self._session.xenapi.VDI.add_to_sm_config(vdi_ref, host_key,
1537 attach_mode)
1538 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1539 if 'paused' in sm_config or 'relinking' in sm_config:
1540 util.SMlog("Found %s key, aborting" % (
1541 'paused' if 'paused' in sm_config else 'relinking'))
1542 self._session.xenapi.VDI.remove_from_sm_config(vdi_ref, host_key)
1543 self._session.xenapi.VDI.remove_from_sm_config(
1544 vdi_ref, 'activating')
1545 return False
1546 util.SMlog("Activate lock succeeded")
1547 return True
1549 def _check_tag(self, vdi_uuid):
1550 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1551 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1552 if 'paused' in sm_config:
1553 util.SMlog("Paused key found [%s]" % sm_config)
1554 return False
1555 return True
1557 def _remove_tag(self, vdi_uuid):
1558 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1559 host_ref = self._session.xenapi.host.get_by_uuid(util.get_this_host())
1560 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1561 host_key = "host_%s" % host_ref
1562 if host_key in sm_config:
1563 self._session.xenapi.VDI.remove_from_sm_config(vdi_ref, host_key)
1564 util.SMlog("Removed host key %s for %s" % (host_key, vdi_uuid))
1565 else:
1566 util.SMlog("_remove_tag: host key %s not found, ignore" % host_key)
1568 def _get_pool_config(self, pool_name):
1569 pool_info = dict()
1570 vdi_ref = self.target.vdi.sr.srcmd.params.get('vdi_ref')
1571 if not vdi_ref: 1571 ↛ 1574line 1571 didn't jump to line 1574, because the condition on line 1571 was never true
1572 # attach_from_config context: HA disks don't need to be in any
1573 # special pool
1574 return pool_info
1576 sr_ref = self.target.vdi.sr.srcmd.params.get('sr_ref')
1577 sr_config = self._session.xenapi.SR.get_other_config(sr_ref)
1578 vdi_config = self._session.xenapi.VDI.get_other_config(vdi_ref)
1579 pool_size_str = sr_config.get(POOL_SIZE_KEY)
1580 pool_name_override = vdi_config.get(POOL_NAME_KEY)
1581 if pool_name_override: 1581 ↛ 1586line 1581 didn't jump to line 1586, because the condition on line 1581 was never false
1582 pool_name = pool_name_override
1583 pool_size_override = vdi_config.get(POOL_SIZE_KEY)
1584 if pool_size_override: 1584 ↛ 1586line 1584 didn't jump to line 1586, because the condition on line 1584 was never false
1585 pool_size_str = pool_size_override
1586 pool_size = 0
1587 if pool_size_str: 1587 ↛ 1597line 1587 didn't jump to line 1597, because the condition on line 1587 was never false
1588 try:
1589 pool_size = int(pool_size_str)
1590 if pool_size < 1 or pool_size > MAX_FULL_RINGS: 1590 ↛ 1591line 1590 didn't jump to line 1591, because the condition on line 1590 was never true
1591 raise ValueError("outside of range")
1592 pool_size = NUM_PAGES_PER_RING * pool_size
1593 except ValueError:
1594 util.SMlog("Error: invalid mem-pool-size %s" % pool_size_str)
1595 pool_size = 0
1597 pool_info["mem-pool"] = pool_name
1598 if pool_size: 1598 ↛ 1601line 1598 didn't jump to line 1601, because the condition on line 1598 was never false
1599 pool_info["mem-pool-size"] = str(pool_size)
1601 return pool_info
1603 def linkNBD(self, sr_uuid, vdi_uuid):
1604 if self.tap:
1605 nbd_path = '/run/blktap-control/nbd%d.%d' % (int(self.tap.pid),
1606 int(self.tap.minor))
1607 VDI.NBDLink.from_uuid(sr_uuid, vdi_uuid).mklink(nbd_path)
1609 def attach(self, sr_uuid, vdi_uuid, writable, activate=False, caching_params={}):
1610 """Return/dev/sm/backend symlink path"""
1611 self.xenstore_data.update(self._get_pool_config(sr_uuid))
1612 if not self.target.has_cap("ATOMIC_PAUSE") or activate:
1613 util.SMlog("Attach & activate")
1614 self._attach(sr_uuid, vdi_uuid)
1615 dev_path = self._activate(sr_uuid, vdi_uuid,
1616 {"rdonly": not writable})
1617 self.BackendLink.from_uuid(sr_uuid, vdi_uuid).mklink(dev_path)
1618 self.linkNBD(sr_uuid, vdi_uuid)
1620 # Return backend/ link
1621 back_path = self.BackendLink.from_uuid(sr_uuid, vdi_uuid).path()
1622 if self.tap_wanted():
1623 # Only have NBD if we also have a tap
1624 nbd_path = "nbd:unix:{}:exportname={}".format(
1625 VDI.NBDLink.from_uuid(sr_uuid, vdi_uuid).path(),
1626 vdi_uuid)
1627 else:
1628 nbd_path = ""
1630 options = {"rdonly": not writable}
1631 options.update(caching_params)
1632 o_direct, o_direct_reason = self.get_o_direct_capability(options)
1633 struct = {'params': back_path,
1634 'params_nbd': nbd_path,
1635 'o_direct': o_direct,
1636 'o_direct_reason': o_direct_reason,
1637 'xenstore_data': self.xenstore_data}
1638 util.SMlog('result: %s' % struct)
1640 try:
1641 f = open("%s.attach_info" % back_path, 'a')
1642 f.write(xmlrpc.client.dumps((struct, ), "", True))
1643 f.close()
1644 except:
1645 pass
1647 return xmlrpc.client.dumps((struct, ), "", True)
1649 def activate(self, sr_uuid, vdi_uuid, writable, caching_params):
1650 util.SMlog("blktap2.activate")
1651 options = {"rdonly": not writable}
1652 options.update(caching_params)
1654 sr_ref = self.target.vdi.sr.srcmd.params.get('sr_ref')
1655 sr_other_config = self._session.xenapi.SR.get_other_config(sr_ref)
1656 for i in range(self.ATTACH_DETACH_RETRY_SECS): 1656 ↛ 1663line 1656 didn't jump to line 1663, because the loop on line 1656 didn't complete
1657 try:
1658 if self._activate_locked(sr_uuid, vdi_uuid, options):
1659 return
1660 except util.SRBusyException:
1661 util.SMlog("SR locked, retrying")
1662 time.sleep(1)
1663 raise util.SMException("VDI %s locked" % vdi_uuid)
1665 def _get_sr_master_host_ref(self) -> str:
1666 """
1667 Give the host ref of the one responsible for Garbage Collection for a SR.
1668 Meaning this host for a local SR, the master for a shared SR.
1669 """
1670 sr = self.target.vdi.sr
1671 if sr.is_shared():
1672 host_ref = util.get_master_ref(self._session)
1673 else:
1674 host_ref = sr.host_ref
1675 return host_ref
1677 def _get_vdi_chain(self, cowutil, extractUuid) -> List[str]:
1678 vdi_chain = []
1679 path = self.target.get_vdi_path()
1681 #TODO: Need to add handling of error for getParentNoCheck, e.g. corrupted VDI where we can't read parent
1682 vdi_chain.append(extractUuid(path))
1683 parent = cowutil.getParentNoCheck(path)
1684 while parent:
1685 vdi_chain.append(extractUuid(parent))
1686 parent = cowutil.getParentNoCheck(parent)
1687 vdi_chain.reverse()
1688 return vdi_chain
1690 def _check_journal_coalesce_chain(self, sr_uuid: str, vdi_uuid: str) -> bool:
1691 vdi_type = self.target.get_vdi_type()
1692 cowutil = getCowUtil(vdi_type)
1694 if not cowutil.isCoalesceableOnRemote(): #We only need to stop the coalesce in case of QCOW2
1695 return True
1697 path = self.target.get_vdi_path()
1699 import fjournaler
1700 import journaler
1701 from lvmcowutil import LvmCowUtil
1702 from FileSR import FileVDI
1703 import lvmcache
1705 journal: Union[journaler.Journaler, fjournaler.Journaler]
1706 # Different extractUUID & journaler function for LVMSR and FileSR
1707 if path.startswith("/dev/"): #TODO: How to identify SR type easily, we could ask XAPI since we have the sruuid (and even ref)
1708 vgName = "VG_XenStorage-{}".format(sr_uuid)
1709 lvmCache = lvmcache.LVMCache(vgName)
1710 journal = journaler.Journaler(lvmCache)
1712 extractUuid = LvmCowUtil.extractUuid
1713 else:
1714 journal = fjournaler.Journaler(os.getcwd())
1715 extractUuid = FileVDI.extractUuid
1717 # Get the VDI chain
1718 vdi_chain = self._get_vdi_chain(cowutil, extractUuid)
1720 if len(vdi_chain) == 1:
1721 # We only have a leaf, do nothing
1722 util.SMlog("VDI {} is only a leaf, continuing...".format(vdi_uuid))
1723 return True
1725 # Log the chain of active VDI
1726 level = 0
1727 util.SMlog("VDI chain:")
1728 for vdi in vdi_chain:
1729 prefix = " " * level
1730 level += 1
1731 util.SMlog("{}{}".format(prefix, vdi))
1733 vdi_to_cancel = []
1734 for entry in journal.getAll("coalesce").keys():
1735 if entry in vdi_chain:
1736 vdi_to_cancel.append(entry)
1737 util.SMlog("Coalescing VDI {} in chain".format(entry))
1739 # Get the host_ref from the host doing the GC work
1740 host_ref = self._get_sr_master_host_ref()
1741 for vdi in vdi_to_cancel:
1742 args = {"sr_uuid": sr_uuid, "vdi_uuid": vdi}
1743 util.SMlog("Calling cancel_coalesce_master with args: {}".format(args))
1744 self._session.xenapi.host.call_plugin(\
1745 host_ref, PLUGIN_ON_SLAVE, "cancel_coalesce_master", args)
1747 return True
1749 @locking("VDIUnavailable")
1750 def _activate_locked(self, sr_uuid, vdi_uuid, options):
1751 """Wraps target.activate and adds a tapdisk"""
1753 #util.SMlog("VDI.activate %s" % vdi_uuid)
1754 refresh = False
1755 if self.tap_wanted(): 1755 ↛ 1760line 1755 didn't jump to line 1760, because the condition on line 1755 was never false
1756 if not self._add_tag(vdi_uuid, not options["rdonly"]):
1757 return False
1758 refresh = True
1760 try:
1761 if refresh: 1761 ↛ 1772line 1761 didn't jump to line 1772, because the condition on line 1761 was never false
1762 # it is possible that while the VDI was paused some of its
1763 # attributes have changed (e.g. its size if it was inflated; or its
1764 # path if it was leaf-coalesced onto a raw LV), so refresh the
1765 # object completely
1766 params = self.target.vdi.sr.srcmd.params
1767 target = sm.VDI.from_uuid(self.target.vdi.session, vdi_uuid)
1768 target.sr.srcmd.params = params
1769 driver_info = target.sr.srcmd.driver_info
1770 self.target = self.TargetDriver(target, driver_info)
1772 util.fistpoint.activate_custom_fn( 1772 ↛ exitline 1772 didn't jump to the function exit
1773 "blktap_activate_inject_failure",
1774 lambda: util.inject_failure())
1776 # Attach the physical node
1777 if self.target.has_cap("ATOMIC_PAUSE"): 1777 ↛ 1778line 1777 didn't jump to line 1778, because the condition on line 1777 was never true
1778 self._attach(sr_uuid, vdi_uuid)
1780 vdi_type = self.target.get_vdi_type()
1782 if not self._check_journal_coalesce_chain(sr_uuid, vdi_uuid): 1782 ↛ 1783line 1782 didn't jump to line 1783, because the condition on line 1782 was never true
1783 return False
1785 # Take lvchange-p Lock before running
1786 # tap-ctl open
1787 # Needed to avoid race with lvchange -p which is
1788 # now taking the same lock
1789 # This is a fix for CA-155766
1790 if hasattr(self.target.vdi.sr, 'DRIVER_TYPE') and \ 1790 ↛ 1793line 1790 didn't jump to line 1793, because the condition on line 1790 was never true
1791 self.target.vdi.sr.DRIVER_TYPE == 'lvhd' and \
1792 VdiType.isCowImage(vdi_type):
1793 lock = Lock("lvchange-p", NS_PREFIX_LVM + sr_uuid)
1794 lock.acquire()
1796 # When we attach a static VDI for HA, we cannot communicate with
1797 # xapi, because has not started yet. These VDIs are raw.
1798 if VdiType.isCowImage(vdi_type): 1798 ↛ 1799line 1798 didn't jump to line 1799, because the condition on line 1798 was never true
1799 session = self.target.vdi.session
1800 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1801 # pylint: disable=used-before-assignment
1802 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1803 if 'key_hash' in sm_config:
1804 key_hash = sm_config['key_hash']
1805 options['key_hash'] = key_hash
1806 options['vdi_uuid'] = vdi_uuid
1807 util.SMlog('Using key with hash {} for VDI {}'.format(key_hash, vdi_uuid))
1808 # Activate the physical node
1809 dev_path = self._activate(sr_uuid, vdi_uuid, options)
1811 if hasattr(self.target.vdi.sr, 'DRIVER_TYPE') and \ 1811 ↛ 1814line 1811 didn't jump to line 1814, because the condition on line 1811 was never true
1812 self.target.vdi.sr.DRIVER_TYPE == 'lvhd' and \
1813 VdiType.isCowImage(self.target.get_vdi_type()):
1814 lock.release()
1815 except:
1816 util.SMlog("Exception in activate/attach")
1817 if self.tap_wanted():
1818 util.fistpoint.activate_custom_fn(
1819 "blktap_activate_error_handling",
1820 lambda: time.sleep(30))
1821 while True:
1822 try:
1823 self._remove_tag(vdi_uuid)
1824 break
1825 except xmlrpc.client.ProtocolError as e:
1826 # If there's a connection error, keep trying forever.
1827 if e.errcode == http.HTTPStatus.INTERNAL_SERVER_ERROR.value:
1828 continue
1829 else:
1830 util.SMlog('failed to remove tag: %s' % e)
1831 break
1832 except Exception as e:
1833 util.SMlog('failed to remove tag: %s' % e)
1834 break
1835 raise
1836 finally:
1837 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1838 self._session.xenapi.VDI.remove_from_sm_config(
1839 vdi_ref, 'activating')
1840 util.SMlog("Removed activating flag from %s" % vdi_uuid) 1840 ↛ exitline 1840 didn't except from function '_activate_locked', because the raise on line 1835 wasn't executed or line 1840 didn't return from function '_activate_locked', because the return on line 1783 wasn't executed
1842 # Link result to backend/
1843 self.BackendLink.from_uuid(sr_uuid, vdi_uuid).mklink(dev_path)
1844 self.linkNBD(sr_uuid, vdi_uuid)
1845 return True
1847 def _activate(self, sr_uuid, vdi_uuid, options):
1848 vdi_options = self.target.activate(sr_uuid, vdi_uuid)
1850 dev_path = self.setup_cache(sr_uuid, vdi_uuid, options)
1851 if not dev_path: 1851 ↛ 1865line 1851 didn't jump to line 1865, because the condition on line 1851 was never false
1852 phy_path = self.PhyLink.from_uuid(sr_uuid, vdi_uuid).readlink()
1853 # Maybe launch a tapdisk on the physical link
1854 if self.tap_wanted(): 1854 ↛ 1863line 1854 didn't jump to line 1863, because the condition on line 1854 was never false
1855 vdi_type = self.target.get_vdi_type()
1856 options["o_direct"] = self.get_o_direct_capability(options)[0]
1857 if vdi_options: 1857 ↛ 1859line 1857 didn't jump to line 1859, because the condition on line 1857 was never false
1858 options.update(vdi_options)
1859 dev_path, self.tap = self._tap_activate(phy_path, vdi_type,
1860 sr_uuid, options,
1861 self._get_pool_config(sr_uuid).get("mem-pool-size"))
1862 else:
1863 dev_path = phy_path # Just reuse phy
1865 return dev_path
1867 def _attach(self, sr_uuid, vdi_uuid):
1868 attach_info = xmlrpc.client.loads(self.target.attach(sr_uuid, vdi_uuid))[0][0]
1869 params = attach_info['params']
1870 xenstore_data = attach_info['xenstore_data']
1871 phy_path = util.to_plain_string(params)
1872 self.xenstore_data.update(xenstore_data)
1873 # Save it to phy/
1874 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).mklink(phy_path)
1876 def deactivate(self, sr_uuid, vdi_uuid, caching_params):
1877 util.SMlog("blktap2.deactivate")
1878 for i in range(self.ATTACH_DETACH_RETRY_SECS):
1879 try:
1880 if self._deactivate_locked(sr_uuid, vdi_uuid, caching_params):
1881 return
1882 except util.SRBusyException as e:
1883 util.SMlog("SR locked, retrying")
1884 time.sleep(1)
1885 raise util.SMException("VDI %s locked" % vdi_uuid)
1887 @locking("VDIUnavailable")
1888 def _deactivate_locked(self, sr_uuid, vdi_uuid, caching_params):
1889 """Wraps target.deactivate and removes a tapdisk"""
1891 #util.SMlog("VDI.deactivate %s" % vdi_uuid)
1892 if self.tap_wanted() and not self._check_tag(vdi_uuid):
1893 return False
1895 self._deactivate(sr_uuid, vdi_uuid, caching_params)
1896 if self.target.has_cap("ATOMIC_PAUSE"):
1897 self._detach(sr_uuid, vdi_uuid)
1898 if self.tap_wanted():
1899 self._remove_tag(vdi_uuid)
1901 return True
1903 def _resetPhylink(self, sr_uuid, vdi_uuid, path):
1904 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).mklink(path)
1906 def detach(self, sr_uuid, vdi_uuid, deactivate=False, caching_params={}):
1907 if not self.target.has_cap("ATOMIC_PAUSE") or deactivate:
1908 util.SMlog("Deactivate & detach")
1909 self._deactivate(sr_uuid, vdi_uuid, caching_params)
1910 self._detach(sr_uuid, vdi_uuid)
1911 else:
1912 pass # nothing to do
1914 def _deactivate(self, sr_uuid, vdi_uuid, caching_params):
1915 # Shutdown tapdisk
1916 back_link = self.BackendLink.from_uuid(sr_uuid, vdi_uuid)
1918 if not util.pathexists(back_link.path()):
1919 util.SMlog("Backend path %s does not exist" % back_link.path())
1920 return
1922 try:
1923 attach_info_path = "%s.attach_info" % (back_link.path())
1924 os.unlink(attach_info_path)
1925 except:
1926 util.SMlog("unlink of attach_info failed")
1928 try:
1929 major, minor = back_link.rdev()
1930 except self.DeviceNode.NotABlockDevice:
1931 pass
1932 else:
1933 if major == Tapdisk.major():
1934 self._tap_deactivate(minor)
1935 self.remove_cache(caching_params)
1937 # Remove the backend link
1938 back_link.unlink()
1939 VDI.NBDLink.from_uuid(sr_uuid, vdi_uuid).unlink()
1941 # Deactivate & detach the physical node
1942 if self.tap_wanted() and self.target.vdi.session is not None:
1943 # it is possible that while the VDI was paused some of its
1944 # attributes have changed (e.g. its size if it was inflated; or its
1945 # path if it was leaf-coalesced onto a raw LV), so refresh the
1946 # object completely
1947 target = sm.VDI.from_uuid(self.target.vdi.session, vdi_uuid)
1948 driver_info = target.sr.srcmd.driver_info
1949 self.target = self.TargetDriver(target, driver_info)
1951 self.target.deactivate(sr_uuid, vdi_uuid)
1953 def _detach(self, sr_uuid, vdi_uuid):
1954 self.target.detach(sr_uuid, vdi_uuid)
1956 # Remove phy/
1957 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).unlink()
1959 def _updateCacheRecord(self, session, vdi_uuid, on_boot, caching):
1960 # Remove existing VDI.sm_config fields
1961 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1962 for key in ["on_boot", "caching"]:
1963 session.xenapi.VDI.remove_from_sm_config(vdi_ref, key)
1964 if not on_boot is None: 1964 ↛ 1965line 1964 didn't jump to line 1965, because the condition on line 1964 was never true
1965 session.xenapi.VDI.add_to_sm_config(vdi_ref, 'on_boot', on_boot)
1966 if not caching is None:
1967 session.xenapi.VDI.add_to_sm_config(vdi_ref, 'caching', caching)
1969 def setup_cache(self, sr_uuid, vdi_uuid, params):
1970 if params.get(self.CONF_KEY_ALLOW_CACHING) != "true":
1971 return
1973 util.SMlog("Requested local caching")
1974 if not self.target.has_cap("SR_CACHING"):
1975 util.SMlog("Error: local caching not supported by this SR")
1976 return
1978 scratch_mode = False
1979 if params.get(self.CONF_KEY_MODE_ON_BOOT) == "reset":
1980 scratch_mode = True
1981 util.SMlog("Requested scratch mode")
1982 if not self.target.has_cap("VDI_RESET_ON_BOOT/2"): 1982 ↛ 1986line 1982 didn't jump to line 1986, because the condition on line 1982 was never false
1983 util.SMlog("Error: scratch mode not supported by this SR")
1984 return
1986 dev_path = None
1987 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR)
1988 if not local_sr_uuid:
1989 util.SMlog("ERROR: Local cache SR not specified, not enabling")
1990 return
1991 dev_path = self._setup_cache(self._session, sr_uuid, vdi_uuid,
1992 local_sr_uuid, scratch_mode, params)
1994 if dev_path:
1995 self._updateCacheRecord(self._session, self.target.vdi.uuid,
1996 params.get(self.CONF_KEY_MODE_ON_BOOT),
1997 params.get(self.CONF_KEY_ALLOW_CACHING))
1999 return dev_path
2001 def alert_no_cache(self, session, vdi_uuid, cache_sr_uuid, err):
2002 vm_uuid = None
2003 vm_label = ""
2004 try:
2005 cache_sr_ref = session.xenapi.SR.get_by_uuid(cache_sr_uuid)
2006 cache_sr_rec = session.xenapi.SR.get_record(cache_sr_ref)
2007 cache_sr_label = cache_sr_rec.get("name_label")
2009 host_ref = session.xenapi.host.get_by_uuid(util.get_this_host())
2010 host_rec = session.xenapi.host.get_record(host_ref)
2011 host_label = host_rec.get("name_label")
2013 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
2014 vbds = session.xenapi.VBD.get_all_records_where( \
2015 "field \"VDI\" = \"%s\"" % vdi_ref)
2016 for vbd_rec in vbds.values():
2017 vm_ref = vbd_rec.get("VM")
2018 vm_rec = session.xenapi.VM.get_record(vm_ref)
2019 vm_uuid = vm_rec.get("uuid")
2020 vm_label = vm_rec.get("name_label")
2021 except:
2022 util.logException("alert_no_cache")
2024 alert_obj = "SR"
2025 alert_uuid = str(cache_sr_uuid)
2026 alert_str = "No space left in Local Cache SR %s" % cache_sr_uuid
2027 if vm_uuid:
2028 alert_obj = "VM"
2029 alert_uuid = vm_uuid
2030 reason = ""
2031 if err == errno.ENOSPC:
2032 reason = "because there is no space left"
2033 alert_str = "The VM \"%s\" is not using IntelliCache %s on the Local Cache SR (\"%s\") on host \"%s\"" % \
2034 (vm_label, reason, cache_sr_label, host_label)
2036 util.SMlog("Creating alert: (%s, %s, \"%s\")" % \
2037 (alert_obj, alert_uuid, alert_str))
2038 session.xenapi.message.create("No space left in local cache", "3",
2039 alert_obj, alert_uuid, alert_str)
2041 def _setup_cache(self, session, sr_uuid, vdi_uuid, local_sr_uuid,
2042 scratch_mode, options):
2043 import SR
2044 import EXTSR
2046 if self._no_parent(self.target.vdi): 2046 ↛ 2047line 2046 didn't jump to line 2047, because the condition on line 2046 was never true
2047 util.SMlog("ERROR: VDI %s has no parent, not enabling" %
2048 self.target.vdi.uuid)
2049 return
2051 util.SMlog("Setting up cache")
2052 shared_target = self.target.vdi.sr.vdi(self.target.vdi.parent)
2054 if shared_target.parent:
2055 util.SMlog("ERROR: Parent VDI %s has parent, not enabling" %
2056 shared_target.uuid)
2057 return
2059 SR.registerSR(EXTSR.EXTSR)
2060 local_sr = SR.SR.from_uuid(session, local_sr_uuid)
2062 vdi_type = self.target.get_vdi_type()
2063 tap_type = VDI._tap_type(vdi_type)
2064 cowutil = getCowUtil(vdi_type)
2066 lock = Lock(self.LOCK_CACHE_SETUP, shared_target.uuid)
2067 lock.acquire()
2069 # read cache
2070 read_cache_path = "%s/%s.vhdcache" % (local_sr.path, shared_target.uuid)
2071 if util.pathexists(read_cache_path): 2071 ↛ 2075line 2071 didn't jump to line 2075, because the condition on line 2071 was never false
2072 util.SMlog("Read cache node (%s) already exists, not creating" %
2073 read_cache_path)
2074 else:
2075 try:
2076 cowutil.snapshot(read_cache_path, shared_target.path, False)
2077 except util.CommandException as e:
2078 util.SMlog("Error creating parent cache: %s" % e)
2079 self.alert_no_cache(session, vdi_uuid, local_sr_uuid, e.code)
2080 return None
2082 # local write node
2083 leaf_size = cowutil.getSizeVirt(self.target.vdi.path)
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("Local leaf node (%s) already exists, deleting" %
2088 local_leaf_path)
2089 os.unlink(local_leaf_path)
2090 try:
2091 cowutil.snapshot(local_leaf_path, read_cache_path, False,
2092 msize=leaf_size, checkEmpty=False)
2093 except util.CommandException as e:
2094 util.SMlog("Error creating leaf cache: %s" % e)
2095 self.alert_no_cache(session, vdi_uuid, local_sr_uuid, e.code)
2096 return None
2098 local_leaf_size = cowutil.getSizeVirt(local_leaf_path)
2099 if leaf_size > local_leaf_size: 2099 ↛ 2100line 2099 didn't jump to line 2100, because the condition on line 2099 was never true
2100 util.SMlog("Leaf size %d > local leaf cache size %d, resizing" %
2101 (leaf_size, local_leaf_size))
2102 cowutil.setSizeVirtFast(local_leaf_path, leaf_size)
2104 prt_tapdisk = Tapdisk.find_by_path(read_cache_path)
2105 if not prt_tapdisk:
2106 parent_options = copy.deepcopy(options)
2107 parent_options["rdonly"] = False
2108 parent_options["lcache"] = True
2110 blktap = Blktap.allocate()
2111 try:
2112 blktap.set_pool_name("lcache-parent-pool-%s" % blktap.minor)
2113 # no need to change pool_size since each parent tapdisk is in
2114 # its own pool
2115 prt_tapdisk = Tapdisk.launch_on_tap(blktap, read_cache_path, tap_type, parent_options)
2116 except:
2117 blktap.free()
2118 raise
2120 secondary = "%s:%s" % (vdi_type, self.PhyLink.from_uuid(sr_uuid, vdi_uuid).readlink())
2122 util.SMlog("Parent tapdisk: %s" % prt_tapdisk)
2123 leaf_tapdisk = Tapdisk.find_by_path(local_leaf_path)
2124 if not leaf_tapdisk: 2124 ↛ 2140line 2124 didn't jump to line 2140, because the condition on line 2124 was never false
2125 blktap = Blktap.allocate()
2126 child_options = copy.deepcopy(options)
2127 child_options["rdonly"] = False
2128 child_options["lcache"] = (not scratch_mode)
2129 child_options["existing_prt"] = prt_tapdisk.minor
2130 child_options["secondary"] = secondary
2131 child_options["standby"] = scratch_mode
2132 # Disable memory read caching
2133 child_options.pop("o_direct", None)
2134 try:
2135 leaf_tapdisk = Tapdisk.launch_on_tap(blktap, local_leaf_path, tap_type, child_options)
2136 except:
2137 blktap.free()
2138 raise
2140 lock.release()
2142 util.SMlog("Local read cache: %s, local leaf: %s" %
2143 (read_cache_path, local_leaf_path))
2145 self.tap = leaf_tapdisk
2146 return leaf_tapdisk.get_devpath()
2148 def remove_cache(self, params):
2149 if not self.target.has_cap("SR_CACHING"):
2150 return
2152 caching = params.get(self.CONF_KEY_ALLOW_CACHING) == "true"
2154 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR)
2155 if caching and not local_sr_uuid:
2156 util.SMlog("ERROR: Local cache SR not specified, ignore")
2157 return
2159 if caching: 2159 ↛ 2162line 2159 didn't jump to line 2162, because the condition on line 2159 was never false
2160 self._remove_cache(self._session, local_sr_uuid)
2162 if self._session is not None: 2162 ↛ exitline 2162 didn't return from function 'remove_cache', because the condition on line 2162 was never false
2163 self._updateCacheRecord(self._session, self.target.vdi.uuid, None, None)
2165 def _is_tapdisk_in_use(self, minor):
2166 retVal, links, sockets = util.findRunningProcessOrOpenFile("tapdisk")
2167 if not retVal:
2168 # err on the side of caution
2169 return True
2171 for link in links:
2172 if link.find("tapdev%d" % minor) != -1:
2173 return True
2175 socket_re = re.compile(r'^/.*/nbd\d+\.%d' % minor)
2176 for s in sockets:
2177 if socket_re.match(s):
2178 return True
2180 return False
2182 def _remove_cache(self, session, local_sr_uuid):
2183 import SR
2184 import EXTSR
2186 if self._no_parent(self.target.vdi):
2187 util.SMlog("ERROR: No parent for VDI %s, ignore" %
2188 self.target.vdi.uuid)
2189 return
2191 util.SMlog("Tearing down the cache")
2193 shared_target = self.target.vdi.sr.vdi(self.target.vdi.parent)
2195 SR.registerSR(EXTSR.EXTSR)
2196 local_sr = SR.SR.from_uuid(session, local_sr_uuid)
2198 lock = Lock(self.LOCK_CACHE_SETUP, shared_target.uuid)
2199 lock.acquire()
2201 # local write node
2202 local_leaf_path = "%s/%s.vhdcache" % \
2203 (local_sr.path, self.target.vdi.uuid)
2204 if util.pathexists(local_leaf_path): 2204 ↛ 2208line 2204 didn't jump to line 2208, because the condition on line 2204 was never false
2205 util.SMlog("Deleting local leaf node %s" % local_leaf_path)
2206 os.unlink(local_leaf_path)
2208 read_cache_path = "%s/%s.vhdcache" % (local_sr.path, shared_target.uuid)
2209 prt_tapdisk = Tapdisk.find_by_path(read_cache_path)
2210 if not prt_tapdisk: 2210 ↛ 2211line 2210 didn't jump to line 2211, because the condition on line 2210 was never true
2211 util.SMlog("Parent tapdisk not found")
2212 elif not self._is_tapdisk_in_use(prt_tapdisk.minor): 2212 ↛ 2220line 2212 didn't jump to line 2220, because the condition on line 2212 was never false
2213 util.SMlog("Parent tapdisk not in use: shutting down %s" %
2214 read_cache_path)
2215 try:
2216 prt_tapdisk.shutdown()
2217 except:
2218 util.logException("shutting down parent tapdisk")
2219 else:
2220 util.SMlog("Parent tapdisk still in use: %s" % read_cache_path)
2221 # the parent cache files are removed during the local SR's background
2222 # GC run
2224 lock.release()
2226 @staticmethod
2227 def _no_parent(vdi):
2228 return vdi.parent is None or vdi.parent == ''
2231PythonKeyError = KeyError
2234class UEventHandler(object):
2236 def __init__(self):
2237 self._action = None
2239 class KeyError(PythonKeyError):
2240 def __init__(self, args):
2241 super().__init__(args)
2242 self.key = args[0]
2244 @override
2245 def __str__(self) -> str:
2246 return \
2247 "Key '%s' missing in environment. " % self.key + \
2248 "Not called in udev context?"
2250 @classmethod
2251 def getenv(cls, key):
2252 try:
2253 return os.environ[key]
2254 except KeyError as e:
2255 raise cls.KeyError(e.args[0])
2257 def get_action(self):
2258 if not self._action:
2259 self._action = self.getenv('ACTION')
2260 return self._action
2262 class UnhandledEvent(Exception):
2264 def __init__(self, event, handler):
2265 self.event = event
2266 self.handler = handler
2268 @override
2269 def __str__(self) -> str:
2270 return "Uevent '%s' not handled by %s" % \
2271 (self.event, self.handler.__class__.__name__)
2273 ACTIONS: Dict[str, Callable] = {}
2275 def run(self):
2277 action = self.get_action()
2278 try:
2279 fn = self.ACTIONS[action]
2280 except KeyError:
2281 raise self.UnhandledEvent(action, self)
2283 return fn(self)
2285 @override
2286 def __str__(self) -> str:
2287 try:
2288 action = self.get_action()
2289 except:
2290 action = None
2291 return "%s[%s]" % (self.__class__.__name__, action)
2294class __BlktapControl(ClassDevice):
2295 SYSFS_CLASSTYPE = "misc"
2297 def __init__(self):
2298 ClassDevice.__init__(self)
2299 self._default_pool = None
2301 @override
2302 def sysfs_devname(self) -> str:
2303 return "blktap!control"
2305 class DefaultPool(Attribute):
2306 SYSFS_NODENAME = "default_pool"
2308 def get_default_pool_attr(self):
2309 if not self._default_pool:
2310 self._default_pool = self.DefaultPool.from_kobject(self)
2311 return self._default_pool
2313 def get_default_pool_name(self):
2314 return self.get_default_pool_attr().readline()
2316 def set_default_pool_name(self, name):
2317 self.get_default_pool_attr().writeline(name)
2319 def get_default_pool(self):
2320 return BlktapControl.get_pool(self.get_default_pool_name())
2322 def set_default_pool(self, pool):
2323 self.set_default_pool_name(pool.name)
2325 class NoSuchPool(Exception):
2326 def __init__(self, name):
2327 self.name = name
2329 @override
2330 def __str__(self) -> str:
2331 return "No such pool: {}".format(self.name)
2333 def get_pool(self, name):
2334 path = "%s/pools/%s" % (self.sysfs_path(), name)
2336 if not os.path.isdir(path):
2337 raise self.NoSuchPool(name)
2339 return PagePool(path)
2341BlktapControl = __BlktapControl()
2344class PagePool(KObject):
2346 def __init__(self, path):
2347 self.path = path
2348 self._size = None
2350 @override
2351 def sysfs_devname(self) -> str:
2352 return ''
2354 def sysfs_path(self):
2355 return self.path
2357 class Size(Attribute):
2358 SYSFS_NODENAME = "size"
2360 def get_size_attr(self):
2361 if not self._size:
2362 self._size = self.Size.from_kobject(self)
2363 return self._size
2365 def set_size(self, pages):
2366 pages = str(pages)
2367 self.get_size_attr().writeline(pages)
2369 def get_size(self):
2370 pages = self.get_size_attr().readline()
2371 return int(pages)
2374class BusDevice(KObject):
2376 SYSFS_BUSTYPE: ClassVar[str] = ""
2378 @classmethod
2379 def sysfs_bus_path(cls):
2380 return "/sys/bus/%s" % cls.SYSFS_BUSTYPE
2382 def sysfs_path(self):
2383 path = "%s/devices/%s" % (self.sysfs_bus_path(),
2384 self.sysfs_devname())
2386 return path
2389class XenbusDevice(BusDevice):
2390 """Xenbus device, in XS and sysfs"""
2392 XBT_NIL = ""
2394 XENBUS_DEVTYPE: ClassVar[str] = ""
2396 def __init__(self, domid, devid):
2397 self.domid = int(domid)
2398 self.devid = int(devid)
2399 self._xbt = XenbusDevice.XBT_NIL
2401 import xen.lowlevel.xs # pylint: disable=import-error
2402 self.xs = xen.lowlevel.xs.xs()
2404 def xs_path(self, key=None):
2405 path = "backend/%s/%d/%d" % (self.XENBUS_DEVTYPE,
2406 self.domid,
2407 self.devid)
2408 if key is not None:
2409 path = "%s/%s" % (path, key)
2411 return path
2413 def _log(self, prio, msg):
2414 syslog(prio, msg)
2416 def info(self, msg):
2417 self._log(_syslog.LOG_INFO, msg)
2419 def warn(self, msg):
2420 self._log(_syslog.LOG_WARNING, "WARNING: " + msg)
2422 def _xs_read_path(self, path):
2423 val = self.xs.read(self._xbt, path)
2424 #self.info("read %s = '%s'" % (path, val))
2425 return val
2427 def _xs_write_path(self, path, val):
2428 self.xs.write(self._xbt, path, val)
2429 self.info("wrote %s = '%s'" % (path, val))
2431 def _xs_rm_path(self, path):
2432 self.xs.rm(self._xbt, path)
2433 self.info("removed %s" % path)
2435 def read(self, key):
2436 return self._xs_read_path(self.xs_path(key))
2438 def has_xs_key(self, key):
2439 return self.read(key) is not None
2441 def write(self, key, val):
2442 self._xs_write_path(self.xs_path(key), val)
2444 def rm(self, key):
2445 self._xs_rm_path(self.xs_path(key))
2447 def exists(self):
2448 return self.has_xs_key(None)
2450 def begin(self):
2451 assert(self._xbt == XenbusDevice.XBT_NIL)
2452 self._xbt = self.xs.transaction_start()
2454 def commit(self):
2455 ok = self.xs.transaction_end(self._xbt, 0)
2456 self._xbt = XenbusDevice.XBT_NIL
2457 return ok
2459 def abort(self):
2460 ok = self.xs.transaction_end(self._xbt, 1)
2461 assert(ok == True)
2462 self._xbt = XenbusDevice.XBT_NIL
2464 def create_physical_device(self):
2465 """The standard protocol is: toolstack writes 'params', linux hotplug
2466 script translates this into physical-device=%x:%x"""
2467 if self.has_xs_key("physical-device"):
2468 return
2469 try:
2470 params = self.read("params")
2471 frontend = self.read("frontend")
2472 is_cdrom = self._xs_read_path("%s/device-type") == "cdrom"
2473 # We don't have PV drivers for CDROM devices, so we prevent blkback
2474 # from opening the physical-device
2475 if not(is_cdrom):
2476 major_minor = os.stat(params).st_rdev
2477 major, minor = divmod(major_minor, 256)
2478 self.write("physical-device", "%x:%x" % (major, minor))
2479 except:
2480 util.logException("BLKTAP2:create_physical_device")
2482 def signal_hotplug(self, online=True):
2483 xapi_path = "/xapi/%d/hotplug/%s/%d/hotplug" % (self.domid,
2484 self.XENBUS_DEVTYPE,
2485 self.devid)
2486 upstream_path = self.xs_path("hotplug-status")
2487 if online:
2488 self._xs_write_path(xapi_path, "online")
2489 self._xs_write_path(upstream_path, "connected")
2490 else:
2491 self._xs_rm_path(xapi_path)
2492 self._xs_rm_path(upstream_path)
2494 @override
2495 def sysfs_devname(self) -> str:
2496 return "%s-%d-%d" % (self.XENBUS_DEVTYPE,
2497 self.domid, self.devid)
2499 @override
2500 def __str__(self) -> str:
2501 return self.sysfs_devname()
2503 @classmethod
2504 def find(cls):
2505 pattern = "/sys/bus/%s/devices/%s*" % (cls.SYSFS_BUSTYPE,
2506 cls.XENBUS_DEVTYPE)
2507 for path in glob.glob(pattern):
2509 name = os.path.basename(path)
2510 (_type, domid, devid) = name.split('-')
2512 yield cls(domid, devid)
2515class XenBackendDevice(XenbusDevice):
2516 """Xenbus backend device"""
2517 SYSFS_BUSTYPE = "xen-backend"
2519 @classmethod
2520 def from_xs_path(cls, _path):
2521 (_backend, _type, domid, devid) = _path.split('/')
2523 assert _backend == 'backend'
2524 assert _type == cls.XENBUS_DEVTYPE
2526 domid = int(domid)
2527 devid = int(devid)
2529 return cls(domid, devid)
2532class Blkback(XenBackendDevice):
2533 """A blkback VBD"""
2535 XENBUS_DEVTYPE = "vbd"
2537 def __init__(self, domid, devid):
2538 XenBackendDevice.__init__(self, domid, devid)
2539 self._phy = None
2540 self._vdi_uuid = None
2541 self._q_state = None
2542 self._q_events = None
2544 class XenstoreValueError(Exception):
2545 KEY: ClassVar[str] = ""
2547 def __init__(self, vbd, _str):
2548 self.vbd = vbd
2549 self.str = _str
2551 @override
2552 def __str__(self) -> str:
2553 return "Backend %s " % self.vbd + \
2554 "has %s = %s" % (self.KEY, self.str)
2556 class PhysicalDeviceError(XenstoreValueError):
2557 KEY = "physical-device"
2559 class PhysicalDevice(object):
2561 def __init__(self, major, minor):
2562 self.major = int(major)
2563 self.minor = int(minor)
2565 @classmethod
2566 def from_xbdev(cls, xbdev):
2568 phy = xbdev.read("physical-device")
2570 try:
2571 major, minor = phy.split(':')
2572 major = int(major, 0x10)
2573 minor = int(minor, 0x10)
2574 except Exception as e:
2575 raise xbdev.PhysicalDeviceError(xbdev, phy)
2577 return cls(major, minor)
2579 def makedev(self):
2580 return os.makedev(self.major, self.minor)
2582 def is_tap(self):
2583 return self.major == Tapdisk.major()
2585 @override
2586 def __str__(self) -> str:
2587 return "%s:%s" % (self.major, self.minor)
2589 @override
2590 def __eq__(self, other) -> bool:
2591 return \
2592 self.major == other.major and \
2593 self.minor == other.minor
2595 def get_physical_device(self):
2596 if not self._phy:
2597 self._phy = self.PhysicalDevice.from_xbdev(self)
2598 return self._phy
2600 class QueueEvents(Attribute):
2601 """Blkback sysfs node to select queue-state event
2602 notifications emitted."""
2604 SYSFS_NODENAME = "queue_events"
2606 QUEUE_RUNNING = (1 << 0)
2607 QUEUE_PAUSE_DONE = (1 << 1)
2608 QUEUE_SHUTDOWN_DONE = (1 << 2)
2609 QUEUE_PAUSE_REQUEST = (1 << 3)
2610 QUEUE_SHUTDOWN_REQUEST = (1 << 4)
2612 def get_mask(self):
2613 return int(self.readline(), 0x10)
2615 def set_mask(self, mask):
2616 self.writeline("0x%x" % mask)
2618 def get_queue_events(self):
2619 if not self._q_events:
2620 self._q_events = self.QueueEvents.from_kobject(self)
2621 return self._q_events
2623 def get_vdi_uuid(self):
2624 if not self._vdi_uuid:
2625 self._vdi_uuid = self.read("sm-data/vdi-uuid")
2626 return self._vdi_uuid
2628 def pause_requested(self):
2629 return self.has_xs_key("pause")
2631 def shutdown_requested(self):
2632 return self.has_xs_key("shutdown-request")
2634 def shutdown_done(self):
2635 return self.has_xs_key("shutdown-done")
2637 def running(self):
2638 return self.has_xs_key('queue-0/kthread-pid')
2640 @classmethod
2641 def find_by_physical_device(cls, phy):
2642 for dev in cls.find():
2643 try:
2644 _phy = dev.get_physical_device()
2645 except cls.PhysicalDeviceError:
2646 continue
2648 if _phy == phy:
2649 yield dev
2651 @classmethod
2652 def find_by_tap_minor(cls, minor):
2653 phy = cls.PhysicalDevice(Tapdisk.major(), minor)
2654 return cls.find_by_physical_device(phy)
2656 @classmethod
2657 def find_by_tap(cls, tapdisk):
2658 return cls.find_by_tap_minor(tapdisk.minor)
2660 def has_tap(self):
2662 if not self.can_tap():
2663 return False
2665 phy = self.get_physical_device()
2666 if phy:
2667 return phy.is_tap()
2669 return False
2671 def is_bare_hvm(self):
2672 """File VDIs for bare HVM. These are directly accessible by Qemu."""
2673 try:
2674 self.get_physical_device()
2676 except self.PhysicalDeviceError as e:
2677 vdi_type = self.read("type")
2679 self.info("HVM VDI: type=%s" % vdi_type)
2681 if e.str is not None or vdi_type != 'file':
2682 raise
2684 return True
2686 return False
2688 def can_tap(self):
2689 return not self.is_bare_hvm()
2692class BlkbackEventHandler(UEventHandler):
2694 LOG_FACILITY = _syslog.LOG_DAEMON
2696 def __init__(self, ident=None, action=None):
2697 if not ident:
2698 ident = self.__class__.__name__
2700 self.ident = ident
2701 self._vbd = None
2702 self._tapdisk = None
2704 UEventHandler.__init__(self)
2706 @override
2707 def run(self) -> None:
2709 self.xs_path = self.getenv('XENBUS_PATH')
2710 openlog(str(self), 0, self.LOG_FACILITY)
2712 UEventHandler.run(self)
2714 @override
2715 def __str__(self) -> str:
2717 try:
2718 path = self.xs_path
2719 except:
2720 path = None
2722 try:
2723 action = self.get_action()
2724 except:
2725 action = None
2727 return "%s[%s](%s)" % (self.ident, action, path)
2729 def _log(self, prio, msg):
2730 syslog(prio, msg)
2731 util.SMlog("%s: " % self + msg)
2733 def info(self, msg):
2734 self._log(_syslog.LOG_INFO, msg)
2736 def warn(self, msg):
2737 self._log(_syslog.LOG_WARNING, "WARNING: " + msg)
2739 def error(self, msg):
2740 self._log(_syslog.LOG_ERR, "ERROR: " + msg)
2742 def get_vbd(self):
2743 if not self._vbd:
2744 self._vbd = Blkback.from_xs_path(self.xs_path)
2745 return self._vbd
2747 def get_tapdisk(self):
2748 if not self._tapdisk:
2749 minor = self.get_vbd().get_physical_device().minor
2750 self._tapdisk = Tapdisk.from_minor(minor)
2751 return self._tapdisk
2752 #
2753 # Events
2754 #
2756 def __add(self):
2757 vbd = self.get_vbd()
2758 # Manage blkback transitions
2759 # self._manage_vbd()
2761 vbd.create_physical_device()
2763 vbd.signal_hotplug()
2765 @retried(backoff=.5, limit=10)
2766 def add(self):
2767 try:
2768 self.__add()
2769 except Attribute.NoSuchAttribute as e:
2770 #
2771 # FIXME: KOBJ_ADD is racing backend.probe, which
2772 # registers device attributes. So poll a little.
2773 #
2774 self.warn("%s, still trying." % e)
2775 raise RetryLoop.TransientFailure(e)
2777 def __change(self):
2778 vbd = self.get_vbd()
2780 # 1. Pause or resume tapdisk (if there is one)
2782 if vbd.has_tap():
2783 pass
2784 #self._pause_update_tap()
2786 # 2. Signal Xapi.VBD.pause/resume completion
2788 self._signal_xapi()
2790 def change(self):
2791 vbd = self.get_vbd()
2793 # NB. Beware of spurious change events between shutdown
2794 # completion and device removal. Also, Xapi.VM.migrate will
2795 # hammer a couple extra shutdown-requests into the source VBD.
2797 while True:
2798 vbd.begin()
2800 if not vbd.exists() or \
2801 vbd.shutdown_done():
2802 break
2804 self.__change()
2806 if vbd.commit():
2807 return
2809 vbd.abort()
2810 self.info("spurious uevent, ignored.")
2812 def remove(self):
2813 vbd = self.get_vbd()
2815 vbd.signal_hotplug(False)
2817 ACTIONS = {'add': add,
2818 'change': change,
2819 'remove': remove}
2820 #
2821 # VDI.pause
2822 #
2824 def _tap_should_pause(self):
2825 """Enumerate all VBDs on our tapdisk. Returns true iff any was
2826 paused"""
2828 tapdisk = self.get_tapdisk()
2829 TapState = Tapdisk.PauseState
2831 PAUSED = 'P'
2832 RUNNING = 'R'
2833 PAUSED_SHUTDOWN = 'P,S'
2834 # NB. Shutdown/paused is special. We know it's not going
2835 # to restart again, so it's a RUNNING. Still better than
2836 # backtracking a removed device during Vbd.unplug completion.
2838 next = TapState.RUNNING
2839 vbds = {}
2841 for vbd in Blkback.find_by_tap(tapdisk):
2842 name = str(vbd)
2844 pausing = vbd.pause_requested()
2845 closing = vbd.shutdown_requested()
2846 running = vbd.running()
2848 if pausing:
2849 if closing and not running:
2850 vbds[name] = PAUSED_SHUTDOWN
2851 else:
2852 vbds[name] = PAUSED
2853 next = TapState.PAUSED
2855 else:
2856 vbds[name] = RUNNING
2858 self.info("tapdev%d (%s): %s -> %s"
2859 % (tapdisk.minor, tapdisk.pause_state(),
2860 vbds, next))
2862 return next == TapState.PAUSED
2864 def _pause_update_tap(self):
2865 vbd = self.get_vbd()
2867 if self._tap_should_pause():
2868 self._pause_tap()
2869 else:
2870 self._resume_tap()
2872 def _pause_tap(self):
2873 tapdisk = self.get_tapdisk()
2875 if not tapdisk.is_paused():
2876 self.info("pausing %s" % tapdisk)
2877 tapdisk.pause()
2879 def _resume_tap(self):
2880 tapdisk = self.get_tapdisk()
2882 # NB. Raw VDI snapshots. Refresh the physical path and
2883 # type while resuming.
2884 vbd = self.get_vbd()
2885 vdi_uuid = vbd.get_vdi_uuid()
2887 if tapdisk.is_paused():
2888 self.info("loading vdi uuid=%s" % vdi_uuid)
2889 vdi = VDI.from_cli(vdi_uuid)
2890 _type = vdi.get_tap_type()
2891 path = vdi.get_phy_path()
2892 self.info("resuming %s on %s:%s" % (tapdisk, _type, path))
2893 tapdisk.unpause(_type, path)
2894 #
2895 # VBD.pause/shutdown
2896 #
2898 def _manage_vbd(self):
2899 vbd = self.get_vbd()
2900 # NB. Hook into VBD state transitions.
2902 events = vbd.get_queue_events()
2904 mask = 0
2905 mask |= events.QUEUE_PAUSE_DONE # pause/unpause
2906 mask |= events.QUEUE_SHUTDOWN_DONE # shutdown
2907 # TODO: mask |= events.QUEUE_SHUTDOWN_REQUEST, for shutdown=force
2908 # TODO: mask |= events.QUEUE_RUNNING, for ionice updates etc
2910 events.set_mask(mask)
2911 self.info("wrote %s = %#02x" % (events.path, mask))
2913 def _signal_xapi(self):
2914 vbd = self.get_vbd()
2916 pausing = vbd.pause_requested()
2917 closing = vbd.shutdown_requested()
2918 running = vbd.running()
2920 handled = 0
2922 if pausing and not running:
2923 if 'pause-done' not in vbd:
2924 vbd.write('pause-done', '')
2925 handled += 1
2927 if not pausing:
2928 if 'pause-done' in vbd:
2929 vbd.rm('pause-done')
2930 handled += 1
2932 if closing and not running:
2933 if 'shutdown-done' not in vbd:
2934 vbd.write('shutdown-done', '')
2935 handled += 1
2937 if handled > 1:
2938 self.warn("handled %d events, " % handled +
2939 "pausing=%s closing=%s running=%s" % \
2940 (pausing, closing, running))
2942if __name__ == '__main__': 2942 ↛ 2944line 2942 didn't jump to line 2944, because the condition on line 2942 was never true
2944 import sys
2945 prog = os.path.basename(sys.argv[0])
2947 #
2948 # Simple CLI interface for manual operation
2949 #
2950 # tap.* level calls go down to local Tapdisk()s (by physical path)
2951 # vdi.* level calls run the plugin calls across host boundaries.
2952 #
2954 def usage(stream):
2955 print("usage: %s tap.{list|major}" % prog, file=stream)
2956 print(" %s tap.{launch|find|get|pause|" % prog + \
2957 "unpause|shutdown|stats} {[<tt>:]<path>} | [minor=]<int> | .. }", file=stream)
2958 print(" %s vbd.uevent" % prog, file=stream)
2960 try:
2961 cmd = sys.argv[1]
2962 except IndexError:
2963 usage(sys.stderr)
2964 sys.exit(1)
2966 try:
2967 _class, method = cmd.split('.')
2968 except:
2969 usage(sys.stderr)
2970 sys.exit(1)
2972 #
2973 # Local Tapdisks
2974 #
2976 if cmd == 'tap.major':
2978 print("%d" % Tapdisk.major())
2980 elif cmd == 'tap.launch':
2982 tapdisk = Tapdisk.launch_from_arg(sys.argv[2])
2983 print("Launched %s" % tapdisk, file=sys.stderr)
2985 elif _class == 'tap':
2987 attrs: Dict[str, Any] = {}
2988 for item in sys.argv[2:]:
2989 try:
2990 key, val = item.split('=')
2991 attrs[key] = val
2992 continue
2993 except ValueError:
2994 pass
2996 try:
2997 attrs['minor'] = int(item)
2998 continue
2999 except ValueError:
3000 pass
3002 try:
3003 arg = Tapdisk.Arg.parse(item)
3004 attrs['_type'] = arg.type
3005 attrs['path'] = arg.path
3006 continue
3007 except Tapdisk.Arg.InvalidArgument:
3008 pass
3010 attrs['path'] = item
3012 if cmd == 'tap.list':
3014 for tapdisk in Tapdisk.list( ** attrs):
3015 blktap = tapdisk.get_blktap()
3016 print(tapdisk, end=' ')
3017 print("%s: task=%s pool=%s" % \
3018 (blktap,
3019 blktap.get_task_pid(),
3020 blktap.get_pool_name()))
3022 elif cmd == 'tap.vbds':
3023 # Find all Blkback instances for a given tapdisk
3025 for tapdisk in Tapdisk.list( ** attrs):
3026 print("%s:" % tapdisk, end=' ')
3027 for vbd in Blkback.find_by_tap(tapdisk):
3028 print(vbd, end=' ')
3029 print()
3031 else:
3033 if not attrs:
3034 usage(sys.stderr)
3035 sys.exit(1)
3037 try:
3038 tapdisk = Tapdisk.get( ** attrs)
3039 except TypeError:
3040 usage(sys.stderr)
3041 sys.exit(1)
3043 if cmd == 'tap.shutdown':
3044 # Shutdown a running tapdisk, or raise
3045 tapdisk.shutdown()
3046 print("Shut down %s" % tapdisk, file=sys.stderr)
3048 elif cmd == 'tap.pause':
3049 # Pause an unpaused tapdisk, or raise
3050 tapdisk.pause()
3051 print("Paused %s" % tapdisk, file=sys.stderr)
3053 elif cmd == 'tap.unpause':
3054 # Unpause a paused tapdisk, or raise
3055 tapdisk.unpause()
3056 print("Unpaused %s" % tapdisk, file=sys.stderr)
3058 elif cmd == 'tap.stats':
3059 # Gather tapdisk status
3060 stats = tapdisk.stats()
3061 print("%s:" % tapdisk)
3062 print(json.dumps(stats, indent=True))
3064 else:
3065 usage(sys.stderr)
3066 sys.exit(1)
3068 elif cmd == 'vbd.uevent':
3070 hnd = BlkbackEventHandler(cmd)
3072 if not sys.stdin.isatty():
3073 try:
3074 hnd.run()
3075 except Exception as e:
3076 hnd.error("Unhandled Exception: %s" % e)
3078 import traceback
3079 _type, value, tb = sys.exc_info()
3080 trace = traceback.format_exception(_type, value, tb)
3081 for entry in trace:
3082 for line in entry.rstrip().split('\n'):
3083 util.SMlog(line)
3084 else:
3085 hnd.run()
3087 elif cmd == 'vbd.list':
3089 for vbd in Blkback.find():
3090 print(vbd, \
3091 "physical-device=%s" % vbd.get_physical_device(), \
3092 "pause=%s" % vbd.pause_requested())
3094 else:
3095 usage(sys.stderr)
3096 sys.exit(1)