Coverage for drivers/blktap2.py : 41%
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
48import nfs
50import resetvdis
52import VDI as sm
54from cowutil import getCowUtil
56# For RRDD Plugin Registration
57from xmlrpc.client import ServerProxy, Transport
58from socket import socket, AF_UNIX, SOCK_STREAM
61try:
62 from linstorvolumemanager import log_drbd_openers
63 LINSTOR_AVAILABLE = True
64except ImportError:
65 LINSTOR_AVAILABLE = False
67PLUGIN_TAP_PAUSE = "tapdisk-pause"
68PLUGIN_ON_SLAVE = "on-slave"
70SOCKPATH = "/var/xapi/xcp-rrdd"
72NUM_PAGES_PER_RING = 32 * 11
73MAX_FULL_RINGS = 8
74POOL_NAME_KEY = "mem-pool"
75POOL_SIZE_KEY = "mem-pool-size-rings"
77ENABLE_MULTIPLE_ATTACH = "/etc/xensource/allow_multiple_vdi_attach"
78NO_MULTIPLE_ATTACH = not (os.path.exists(ENABLE_MULTIPLE_ATTACH))
81def locking(excType, override=True):
82 def locking2(op):
83 def wrapper(self, *args):
84 self.lock.acquire()
85 try:
86 try:
87 ret = op(self, * args)
88 except (util.CommandException, util.SMException, XenAPI.Failure) as e: 88 ↛ 98line 88 didn't jump to line 98
89 util.logException("BLKTAP2:%s" % op)
90 msg = str(e)
91 if isinstance(e, util.CommandException): 91 ↛ 92line 91 didn't jump to line 92, because the condition on line 91 was never true
92 msg = "Command %s failed (%s): %s" % \
93 (e.cmd, e.code, e.reason)
94 if override: 94 ↛ 97line 94 didn't jump to line 97, because the condition on line 94 was never false
95 raise xs_errors.XenError(excType, opterr=msg)
96 else:
97 raise
98 except:
99 util.logException("BLKTAP2:%s" % op)
100 raise
101 finally:
102 self.lock.release()
103 return ret
104 return wrapper
105 return locking2
108class RetryLoop(object):
110 def __init__(self, backoff, limit):
111 self.backoff = backoff
112 self.limit = limit
114 def __call__(self, f):
116 def loop(*__t, **__d):
117 attempt = 0
119 while True:
120 attempt += 1
122 try:
123 return f( * __t, ** __d)
125 except self.TransientFailure as e:
126 e = e.exception
128 if attempt >= self.limit: 128 ↛ 129line 128 didn't jump to line 129, because the condition on line 128 was never true
129 raise e
131 time.sleep(self.backoff)
133 return loop
135 class TransientFailure(Exception):
136 def __init__(self, exception):
137 self.exception = exception
140def retried(**args):
141 return RetryLoop( ** args)
144class TapCtl(object):
145 """Tapdisk IPC utility calls."""
147 PATH = "/usr/sbin/tap-ctl"
149 def __init__(self, cmd, p):
150 self.cmd = cmd
151 self._p = p
152 self.stdout = p.stdout
154 class CommandFailure(Exception):
155 """TapCtl cmd failure."""
157 def __init__(self, cmd, **info):
158 self.cmd = cmd
159 self.info = info
161 @override
162 def __str__(self) -> str:
163 items = self.info.items()
164 info = ", ".join("%s=%s" % item
165 for item in items)
166 return "%s failed: %s" % (self.cmd, info)
168 # Trying to get a non-existent attribute throws an AttributeError
169 # exception
170 def __getattr__(self, key):
171 if key in self.info: 171 ↛ 173line 171 didn't jump to line 173, because the condition on line 171 was never false
172 return self.info[key]
173 return object.__getattribute__(self, key)
175 @property
176 def has_status(self):
177 return 'status' in self.info
179 @property
180 def has_signal(self):
181 return 'signal' in self.info
183 # Retrieves the error code returned by the command. If the error code
184 # was not supplied at object-construction time, zero is returned.
185 def get_error_code(self):
186 key = 'status'
187 if key in self.info: 187 ↛ 190line 187 didn't jump to line 190, because the condition on line 187 was never false
188 return self.info[key]
189 else:
190 return 0
192 @classmethod
193 def __mkcmd_real(cls, args):
194 return [cls.PATH] + [str(x) for x in args]
196 __next_mkcmd = __mkcmd_real
198 @classmethod
199 def _mkcmd(cls, args):
201 __next_mkcmd = cls.__next_mkcmd
202 cls.__next_mkcmd = cls.__mkcmd_real
204 return __next_mkcmd(args)
206 @classmethod
207 def _call(cls, args, quiet=False, input=None, text_mode=True):
208 """
209 Spawn a tap-ctl process. Return a TapCtl invocation.
210 Raises a TapCtl.CommandFailure if subprocess creation failed.
211 """
212 cmd = cls._mkcmd(args)
214 if not quiet:
215 util.SMlog(cmd)
216 try:
217 p = subprocess.Popen(cmd,
218 stdin=subprocess.PIPE,
219 stdout=subprocess.PIPE,
220 stderr=subprocess.PIPE,
221 close_fds=True,
222 universal_newlines=text_mode)
223 if input:
224 p.stdin.write(input)
225 p.stdin.close()
226 except OSError as e:
227 raise cls.CommandFailure(cmd, errno=e.errno)
229 return cls(cmd, p)
231 def _errmsg(self):
232 output = map(str.rstrip, self._p.stderr)
233 return "; ".join(output)
235 def _wait(self, quiet=False):
236 """
237 Reap the child tap-ctl process of this invocation.
238 Raises a TapCtl.CommandFailure on non-zero exit status.
239 """
240 status = self._p.wait()
241 if not quiet:
242 util.SMlog(" = %d" % status)
244 if status == 0:
245 return
247 info = {'errmsg': self._errmsg(),
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.stdout.readline().rstrip()
267 tapctl._wait(quiet)
268 return output
270 @staticmethod
271 def _maybe(opt, parm):
272 if parm is not None:
273 return [opt, parm]
274 return []
276 @classmethod
277 def __list(cls, minor=None, pid=None, _type=None, path=None):
278 args = ["list"]
279 args += cls._maybe("-m", minor)
280 args += cls._maybe("-p", pid)
281 args += cls._maybe("-t", _type)
282 args += cls._maybe("-f", path)
284 tapctl = cls._call(args, True)
286 for stdout_line in tapctl.stdout:
287 # FIXME: tap-ctl writes error messages to stdout and
288 # confuses this parser
289 if stdout_line == "blktap kernel module not installed\n": 289 ↛ 292line 289 didn't jump to line 292, because the condition on line 289 was never true
290 # This isn't pretty but (a) neither is confusing stdout/stderr
291 # and at least causes the error to describe the fix
292 raise Exception("blktap kernel module not installed: try 'modprobe blktap'")
293 row = {}
295 for field in stdout_line.rstrip().split(' ', 3):
296 bits = field.split('=')
297 if len(bits) == 2: 297 ↛ 309line 297 didn't jump to line 309, because the condition on line 297 was never false
298 key, val = field.split('=')
300 if key in ('pid', 'minor'):
301 row[key] = int(val, 10)
303 elif key in ('state'):
304 row[key] = int(val, 0x10)
306 else:
307 row[key] = val
308 else:
309 util.SMlog("Ignoring unexpected tap-ctl output: %s" % repr(field))
310 yield row
312 tapctl._wait(True)
314 @classmethod
315 @retried(backoff=.5, limit=10)
316 def list(cls, **args):
318 # FIXME. We typically get an EPROTO when uevents interleave
319 # with SM ops and a tapdisk shuts down under our feet. Should
320 # be fixed in SM.
322 try:
323 return list(cls.__list( ** args))
325 except cls.CommandFailure as e:
326 transient = [errno.EPROTO, errno.ENOENT]
327 if e.has_status and e.status in transient:
328 raise RetryLoop.TransientFailure(e)
329 raise
331 @classmethod
332 def allocate(cls, devpath=None):
333 args = ["allocate"]
334 args += cls._maybe("-d", devpath)
335 return cls._pread(args)
337 @classmethod
338 def free(cls, minor):
339 args = ["free", "-m", minor]
340 cls._pread(args)
342 @classmethod
343 @retried(backoff=.5, limit=10)
344 def spawn(cls):
345 args = ["spawn"]
346 try:
347 pid = cls._pread(args)
348 return int(pid)
349 except cls.CommandFailure as ce:
350 # intermittent failures to spawn. CA-292268
351 if ce.status == 1:
352 raise RetryLoop.TransientFailure(ce)
353 raise
355 @classmethod
356 def attach(cls, pid, minor):
357 args = ["attach", "-p", pid, "-m", minor]
358 cls._pread(args)
360 @classmethod
361 def detach(cls, pid, minor):
362 args = ["detach", "-p", pid, "-m", minor]
363 cls._pread(args)
365 @classmethod
366 def _load_key(cls, key_hash, vdi_uuid):
367 import plugins
369 return plugins.load_key(key_hash, vdi_uuid)
371 @classmethod
372 def open(cls, pid, minor, _type, _file, options):
373 params = Tapdisk.Arg(_type, _file)
374 args = ["open", "-p", pid, "-m", minor, '-a', str(params)]
375 text_mode = True
376 input = None
377 if options.get("rdonly"):
378 args.append('-R')
379 if options.get("lcache"):
380 args.append("-r")
381 if options.get("existing_prt") is not None:
382 args.append("-e")
383 args.append(str(options["existing_prt"]))
384 if options.get("secondary"):
385 args.append("-2")
386 args.append(options["secondary"])
387 if options.get("standby"):
388 args.append("-s")
389 if options.get("timeout"):
390 args.append("-t")
391 args.append(str(options["timeout"]))
392 if not options.get("o_direct", True):
393 args.append("-D")
394 if options.get('cbtlog'):
395 args.extend(['-C', options['cbtlog']])
396 if options.get('key_hash'):
397 key_hash = options['key_hash']
398 vdi_uuid = options['vdi_uuid']
399 key = cls._load_key(key_hash, vdi_uuid)
401 if not key:
402 raise util.SMException("No key found with key hash {}".format(key_hash))
403 input = key
404 text_mode = False
405 args.append('-E')
407 cls._pread(args=args, input=input, text_mode=text_mode)
409 @classmethod
410 def close(cls, pid, minor, force=False):
411 args = ["close", "-p", pid, "-m", minor, "-t", "120"]
412 if force:
413 args += ["-f"]
414 cls._pread(args)
416 @classmethod
417 def pause(cls, pid, minor):
418 args = ["pause", "-p", pid, "-m", minor]
419 cls._pread(args)
421 @classmethod
422 def unpause(cls, pid, minor, _type=None, _file=None, mirror=None,
423 cbtlog=None):
424 args = ["unpause", "-p", pid, "-m", minor]
425 if mirror:
426 args.extend(["-2", mirror])
427 if _type and _file:
428 params = Tapdisk.Arg(_type, _file)
429 args += ["-a", str(params)]
430 if cbtlog:
431 args.extend(["-c", cbtlog])
432 cls._pread(args)
434 @classmethod
435 def shutdown(cls, pid):
436 # TODO: This should be a real tap-ctl command
437 os.kill(pid, signal.SIGTERM)
438 os.waitpid(pid, 0)
440 @classmethod
441 def stats(cls, pid, minor):
442 args = ["stats", "-p", pid, "-m", minor]
443 return cls._pread(args, quiet=True)
445 @classmethod
446 def major(cls):
447 args = ["major"]
448 major = cls._pread(args)
449 return int(major)
451 @classmethod
452 def commit(cls, pid, minor, vdi_type, path):
453 args = ["commit", "-p", pid, "-m", minor, "-a", path]
454 cls._pread(args)
456 @classmethod
457 def query(cls, pid, minor, quiet=False):
458 args = ["query", "-p", pid, "-m", minor]
459 output = cls._pread(args, quiet=quiet)
460 m = re.match(r"Commit status '(.+)' \((\d+)\/(\d+)\)", output)
461 status = m.group(1)
462 coalesced = int(m.group(2))
463 total_coalesce = int(m.group(3))
464 return (status, coalesced, total_coalesce)
466 @classmethod
467 def cancel_commit(cls, pid, minor, wait=True):
468 args = ["cancel", "-p", pid, "-m", minor]
469 if wait:
470 args.append("-w")
471 cls._pread(args)
473class TapdiskExists(Exception):
474 """Tapdisk already running."""
476 def __init__(self, tapdisk):
477 self.tapdisk = tapdisk
479 @override
480 def __str__(self) -> str:
481 return "%s already running" % self.tapdisk
484class TapdiskNotRunning(Exception):
485 """No such Tapdisk."""
487 def __init__(self, **attrs):
488 self.attrs = attrs
490 @override
491 def __str__(self) -> str:
492 items = iter(self.attrs.items())
493 attrs = ", ".join("%s=%s" % attr
494 for attr in items)
495 return "No such Tapdisk(%s)" % attrs
498class TapdiskNotUnique(Exception):
499 """More than one tapdisk on one path."""
501 def __init__(self, tapdisks):
502 self.tapdisks = tapdisks
504 @override
505 def __str__(self) -> str:
506 tapdisks = map(str, self.tapdisks)
507 return "Found multiple tapdisks: %s" % tapdisks
510class TapdiskFailed(Exception):
511 """Tapdisk launch failure."""
513 def __init__(self, arg, err):
514 self.arg = arg
515 self.err = err
517 @override
518 def __str__(self) -> str:
519 return "Tapdisk(%s): %s" % (self.arg, self.err)
521 def get_error(self):
522 return self.err
525class TapdiskInvalidState(Exception):
526 """Tapdisk pause/unpause failure"""
528 def __init__(self, tapdisk):
529 self.tapdisk = tapdisk
531 @override
532 def __str__(self) -> str:
533 return str(self.tapdisk)
536def mkdirs(path, mode=0o777):
537 if not os.path.exists(path):
538 parent, subdir = os.path.split(path)
539 assert parent != path
540 try:
541 if parent:
542 mkdirs(parent, mode)
543 if subdir:
544 os.mkdir(path, mode)
545 except OSError as e:
546 if e.errno != errno.EEXIST:
547 raise
550class KObject(object):
552 SYSFS_CLASSTYPE: ClassVar[str] = ""
554 @abstractmethod
555 def sysfs_devname(self) -> str:
556 pass
559class Attribute(object):
561 SYSFS_NODENAME: ClassVar[str] = ""
563 def __init__(self, path):
564 self.path = path
566 @classmethod
567 def from_kobject(cls, kobj):
568 path = "%s/%s" % (kobj.sysfs_path(), cls.SYSFS_NODENAME)
569 return cls(path)
571 class NoSuchAttribute(Exception):
572 def __init__(self, name):
573 self.name = name
575 @override
576 def __str__(self) -> str:
577 return "No such attribute: %s" % self.name
579 def _open(self, mode='r'):
580 try:
581 return open(self.path, mode)
582 except IOError as e:
583 if e.errno == errno.ENOENT:
584 raise self.NoSuchAttribute(self)
585 raise
587 def readline(self):
588 f = self._open('r')
589 s = f.readline().rstrip()
590 f.close()
591 return s
593 def writeline(self, val):
594 f = self._open('w')
595 f.write(val)
596 f.close()
599class ClassDevice(KObject):
601 @classmethod
602 def sysfs_class_path(cls):
603 return "/sys/class/%s" % cls.SYSFS_CLASSTYPE
605 def sysfs_path(self):
606 return "%s/%s" % (self.sysfs_class_path(),
607 self.sysfs_devname())
610class Blktap(ClassDevice):
612 DEV_BASEDIR = '/dev/xen/blktap-2'
614 SYSFS_CLASSTYPE = "blktap2"
616 def __init__(self, minor):
617 self.minor = minor
618 self._pool = None
619 self._task = None
621 @classmethod
622 def allocate(cls):
623 # FIXME. Should rather go into init.
624 mkdirs(cls.DEV_BASEDIR)
626 devname = TapCtl.allocate()
627 minor = Tapdisk._parse_minor(devname)
628 return cls(minor)
630 def free(self):
631 TapCtl.free(self.minor)
633 @override
634 def __str__(self) -> str:
635 return "%s(minor=%d)" % (self.__class__.__name__, self.minor)
637 @override
638 def sysfs_devname(self) -> str:
639 return "blktap!blktap%d" % self.minor
641 class Pool(Attribute):
642 SYSFS_NODENAME = "pool"
644 def get_pool_attr(self):
645 if not self._pool:
646 self._pool = self.Pool.from_kobject(self)
647 return self._pool
649 def get_pool_name(self):
650 return self.get_pool_attr().readline()
652 def set_pool_name(self, name):
653 self.get_pool_attr().writeline(name)
655 def set_pool_size(self, pages):
656 self.get_pool().set_size(pages)
658 def get_pool(self):
659 return BlktapControl.get_pool(self.get_pool_name())
661 def set_pool(self, pool):
662 self.set_pool_name(pool.name)
664 class Task(Attribute):
665 SYSFS_NODENAME = "task"
667 def get_task_attr(self):
668 if not self._task:
669 self._task = self.Task.from_kobject(self)
670 return self._task
672 def get_task_pid(self):
673 pid = self.get_task_attr().readline()
674 try:
675 return int(pid)
676 except ValueError:
677 return None
679 def find_tapdisk(self):
680 pid = self.get_task_pid()
681 if pid is None:
682 return None
684 return Tapdisk.find(pid=pid, minor=self.minor)
686 def get_tapdisk(self):
687 tapdisk = self.find_tapdisk()
688 if not tapdisk:
689 raise TapdiskNotRunning(minor=self.minor)
690 return tapdisk
693class Tapdisk(object):
695 TYPES = ['aio', 'vhd', 'qcow2']
697 def __init__(self, pid, minor, _type, path, state):
698 self.pid = pid
699 self.minor = minor
700 self.type = _type
701 self.path = path
702 self.state = state
703 self._dirty = False
704 self._blktap = None
706 @override
707 def __str__(self) -> str:
708 state = self.pause_state()
709 return "Tapdisk(%s, pid=%d, minor=%s, state=%s)" % \
710 (self.get_arg(), self.pid, self.minor, state)
712 @classmethod
713 def list(cls, **args):
715 for row in TapCtl.list( ** args):
717 args = {'pid': None,
718 'minor': None,
719 'state': None,
720 '_type': None,
721 'path': None}
723 for key, val in row.items():
724 if key in args:
725 args[key] = val
727 if 'args' in row: 727 ↛ 732line 727 didn't jump to line 732, because the condition on line 727 was never false
728 image = Tapdisk.Arg.parse(row['args'])
729 args['_type'] = image.type
730 args['path'] = image.path
732 if None in args.values(): 732 ↛ 733line 732 didn't jump to line 733, because the condition on line 732 was never true
733 continue
735 yield Tapdisk( ** args)
737 @classmethod
738 def find(cls, **args):
740 found = list(cls.list( ** args))
742 if len(found) > 1: 742 ↛ 743line 742 didn't jump to line 743, because the condition on line 742 was never true
743 raise TapdiskNotUnique(found)
745 if found: 745 ↛ 746line 745 didn't jump to line 746, because the condition on line 745 was never true
746 return found[0]
748 return None
750 @classmethod
751 def find_by_path(cls, path):
752 return cls.find(path=path)
754 @classmethod
755 def find_by_minor(cls, minor):
756 return cls.find(minor=minor)
758 @classmethod
759 def get(cls, **attrs):
761 tapdisk = cls.find( ** attrs)
763 if not tapdisk:
764 raise TapdiskNotRunning( ** attrs)
766 return tapdisk
768 @classmethod
769 def from_path(cls, path):
770 return cls.get(path=path)
772 @classmethod
773 def from_minor(cls, minor):
774 return cls.get(minor=minor)
776 @classmethod
777 def __from_blktap(cls, blktap):
778 tapdisk = cls.from_minor(minor=blktap.minor)
779 tapdisk._blktap = blktap
780 return tapdisk
782 def get_blktap(self):
783 if not self._blktap:
784 self._blktap = Blktap(self.minor)
785 return self._blktap
787 class Arg:
789 def __init__(self, _type, path):
790 self.type = _type
791 self.path = path
793 @override
794 def __str__(self) -> str:
795 return "%s:%s" % (self.type, self.path)
797 @classmethod
798 def parse(cls, arg):
800 try:
801 _type, path = arg.split(":", 1)
802 except ValueError:
803 raise cls.InvalidArgument(arg)
805 if _type not in Tapdisk.TYPES: 805 ↛ 806line 805 didn't jump to line 806, because the condition on line 805 was never true
806 raise cls.InvalidType(_type)
808 return cls(_type, path)
810 class InvalidType(Exception):
811 def __init__(self, _type):
812 self.type = _type
814 @override
815 def __str__(self) -> str:
816 return "Not a Tapdisk type: %s" % self.type
818 class InvalidArgument(Exception):
819 def __init__(self, arg):
820 self.arg = arg
822 @override
823 def __str__(self) -> str:
824 return "Not a Tapdisk image: %s" % self.arg
826 def get_arg(self):
827 return self.Arg(self.type, self.path)
829 def get_devpath(self):
830 return "%s/tapdev%d" % (Blktap.DEV_BASEDIR, self.minor)
832 @classmethod
833 def launch_from_arg(cls, arg):
834 arg = cls.Arg.parse(arg)
835 return cls.launch(arg.path, arg.type, False)
837 @staticmethod
838 def cgclassify(pid):
840 # We dont provide any <controllers>:<path>
841 # so cgclassify uses /etc/cgrules.conf which
842 # we have configured in the spec file.
843 cmd = ["cgclassify", str(pid)]
844 try:
845 util.pread2(cmd)
846 except util.CommandException as e:
847 util.logException(e)
849 @classmethod
850 def launch_on_tap(cls, blktap, path, _type, options):
852 tapdisk = cls.find_by_path(path)
853 if tapdisk: 853 ↛ 854line 853 didn't jump to line 854, because the condition on line 853 was never true
854 raise TapdiskExists(tapdisk)
856 minor = blktap.minor
857 try:
858 pid = TapCtl.spawn()
859 cls.cgclassify(pid)
860 try:
861 TapCtl.attach(pid, minor)
863 try:
864 retry_open = 0
865 while True:
866 try:
867 TapCtl.open(pid, minor, _type, path, options)
868 break
869 except TapCtl.CommandFailure as e:
870 err = (
871 'status' in e.info and e.info['status']
872 ) or None
873 if err in (errno.EIO, errno.EROFS, errno.EAGAIN): 873 ↛ 874line 873 didn't jump to line 874, because the condition on line 873 was never true
874 if retry_open < 5:
875 retry_open += 1
876 time.sleep(1)
877 continue
878 if LINSTOR_AVAILABLE and err == errno.EROFS:
879 log_drbd_openers(path)
880 raise
881 try:
882 tapdisk = cls.__from_blktap(blktap)
883 node = '/sys/dev/block/%d:%d' % (tapdisk.major(), tapdisk.minor)
884 util.set_scheduler_sysfs_node(node, ['none', 'noop'])
885 return tapdisk
886 except:
887 TapCtl.close(pid, minor)
888 raise
890 except:
891 TapCtl.detach(pid, minor)
892 raise
894 except:
895 try:
896 TapCtl.shutdown(pid)
897 except:
898 # Best effort to shutdown
899 pass
900 raise
902 except TapCtl.CommandFailure as ctl:
903 util.logException(ctl)
904 if ((path.startswith('/dev/xapi/cd/') or path.startswith('/dev/sr')) and 904 ↛ 908line 904 didn't jump to line 908, because the condition on line 904 was never false
905 ctl.has_status and ctl.get_error_code() == 123): # ENOMEDIUM (No medium found)
906 raise xs_errors.XenError('TapdiskDriveEmpty')
907 else:
908 raise TapdiskFailed(cls.Arg(_type, path), ctl)
910 @classmethod
911 def launch(cls, path, _type, rdonly):
912 blktap = Blktap.allocate()
913 try:
914 return cls.launch_on_tap(blktap, path, _type, {"rdonly": rdonly})
915 except:
916 blktap.free()
917 raise
919 def shutdown(self, force=False):
921 TapCtl.close(self.pid, self.minor, force)
923 TapCtl.detach(self.pid, self.minor)
925 self.get_blktap().free()
927 def pause(self):
929 if not self.is_running():
930 raise TapdiskInvalidState(self)
932 TapCtl.pause(self.pid, self.minor)
934 self._set_dirty()
936 def unpause(self, _type=None, path=None, mirror=None, cbtlog=None):
938 if not self.is_paused():
939 raise TapdiskInvalidState(self)
941 # FIXME: should the arguments be optional?
942 if _type is None:
943 _type = self.type
944 if path is None:
945 path = self.path
947 TapCtl.unpause(self.pid, self.minor, _type, path, mirror=mirror,
948 cbtlog=cbtlog)
950 self._set_dirty()
952 def stats(self):
953 return json.loads(TapCtl.stats(self.pid, self.minor))
954 #
955 # NB. dirty/refresh: reload attributes on next access
956 #
958 def _set_dirty(self):
959 self._dirty = True
961 def _refresh(self, __get):
962 t = self.from_minor(__get('minor'))
963 self.__init__(t.pid, t.minor, t.type, t.path, t.state)
965 @override
966 def __getattribute__(self, name) -> Any:
967 def __get(name):
968 # NB. avoid(rec(ursion)
969 return object.__getattribute__(self, name)
971 if __get('_dirty') and \ 971 ↛ 973line 971 didn't jump to line 973, because the condition on line 971 was never true
972 name in ['minor', 'type', 'path', 'state']:
973 self._refresh(__get)
974 self._dirty = False
976 return __get(name)
978 class PauseState:
979 RUNNING = 'R'
980 PAUSING = 'r'
981 PAUSED = 'P'
983 class Flags:
984 DEAD = 0x0001
985 CLOSED = 0x0002
986 QUIESCE_REQUESTED = 0x0004
987 QUIESCED = 0x0008
988 PAUSE_REQUESTED = 0x0010
989 PAUSED = 0x0020
990 SHUTDOWN_REQUESTED = 0x0040
991 LOCKING = 0x0080
992 RETRY_NEEDED = 0x0100
993 LOG_DROPPED = 0x0200
995 PAUSE_MASK = PAUSE_REQUESTED | PAUSED
997 def is_paused(self):
998 return not not (self.state & self.Flags.PAUSED)
1000 def is_running(self):
1001 return not (self.state & self.Flags.PAUSE_MASK)
1003 def pause_state(self):
1004 if self.state & self.Flags.PAUSED:
1005 return self.PauseState.PAUSED
1007 if self.state & self.Flags.PAUSE_REQUESTED:
1008 return self.PauseState.PAUSING
1010 return self.PauseState.RUNNING
1012 @staticmethod
1013 def _parse_minor(devpath):
1014 regex = r'%s/(blktap|tapdev)(\d+)$' % Blktap.DEV_BASEDIR
1015 pattern = re.compile(regex)
1016 groups = pattern.search(devpath)
1017 if not groups:
1018 raise Exception("malformed tap device: '%s' (%s) " % (devpath, regex))
1020 minor = groups.group(2)
1021 return int(minor)
1023 _major = None
1025 @classmethod
1026 def major(cls):
1027 if cls._major:
1028 return cls._major
1030 devices = open("/proc/devices")
1031 for line in devices:
1033 row = line.rstrip().split(' ')
1034 if len(row) != 2:
1035 continue
1037 major, name = row
1038 if name != 'tapdev':
1039 continue
1041 cls._major = int(major)
1042 break
1044 devices.close()
1045 return cls._major
1048class VDI(object):
1049 """SR.vdi driver decorator for blktap2"""
1051 CONF_KEY_ALLOW_CACHING = "vdi_allow_caching"
1052 CONF_KEY_MODE_ON_BOOT = "vdi_on_boot"
1053 CONF_KEY_CACHE_SR = "local_cache_sr"
1054 CONF_KEY_O_DIRECT = "o_direct"
1055 LOCK_CACHE_SETUP = "cachesetup"
1057 ATTACH_DETACH_RETRY_SECS = 120
1059 def __init__(self, uuid, target, driver_info):
1060 self.target = self.TargetDriver(target, driver_info)
1061 self._vdi_uuid = uuid
1062 self._session = target.session
1063 self.xenstore_data = scsiutil.update_XS_SCSIdata(uuid, scsiutil.gen_synthetic_page_data(uuid))
1064 self.__o_direct = None
1065 self.__o_direct_reason = None
1066 self.lock = Lock("vdi", uuid)
1067 self.tap = None
1069 def get_o_direct_capability(self, options):
1070 """Returns True/False based on licensing and caching_params"""
1071 if self.__o_direct is not None: 1071 ↛ 1072line 1071 didn't jump to line 1072, because the condition on line 1071 was never true
1072 return self.__o_direct, self.__o_direct_reason
1074 if util.read_caching_is_restricted(self._session): 1074 ↛ 1075line 1074 didn't jump to line 1075, because the condition on line 1074 was never true
1075 self.__o_direct = True
1076 self.__o_direct_reason = "LICENSE_RESTRICTION"
1077 elif not ((self.target.vdi.sr.handles("nfs") or self.target.vdi.sr.handles("ext") or self.target.vdi.sr.handles("smb"))): 1077 ↛ 1080line 1077 didn't jump to line 1080, because the condition on line 1077 was never false
1078 self.__o_direct = True
1079 self.__o_direct_reason = "SR_NOT_SUPPORTED"
1080 elif options.get("rdonly") and not self.target.vdi.parent:
1081 self.__o_direct = True
1082 self.__o_direct_reason = "RO_WITH_NO_PARENT"
1083 elif options.get(self.CONF_KEY_O_DIRECT):
1084 self.__o_direct = True
1085 self.__o_direct_reason = "SR_OVERRIDE"
1087 if self.__o_direct is None: 1087 ↛ 1088line 1087 didn't jump to line 1088, because the condition on line 1087 was never true
1088 self.__o_direct = False
1089 self.__o_direct_reason = ""
1091 return self.__o_direct, self.__o_direct_reason
1093 @classmethod
1094 def from_cli(cls, uuid):
1095 import VDI as sm
1097 session = XenAPI.xapi_local()
1098 session.xenapi.login_with_password('root', '', '', 'SM')
1100 target = sm.VDI.from_uuid(session, uuid)
1101 driver_info = target.sr.srcmd.driver_info
1103 session.xenapi.session.logout()
1105 return cls(uuid, target, driver_info)
1107 @staticmethod
1108 def _tap_type(vdi_type):
1109 """Map a VDI type (e.g. 'raw') to a tapdisk driver type (e.g. 'aio')"""
1110 return {
1111 'raw': 'aio',
1112 'vhd': 'vhd',
1113 'qcow2': 'qcow2',
1114 'iso': 'aio', # for ISO SR
1115 'aio': 'aio', # for LVHD
1116 'file': 'aio',
1117 'phy': 'aio'
1118 }[vdi_type]
1120 def get_tap_type(self):
1121 vdi_type = self.target.get_vdi_type()
1122 return VDI._tap_type(vdi_type)
1124 def get_phy_path(self):
1125 return self.target.get_vdi_path()
1127 class UnexpectedVDIType(Exception):
1129 def __init__(self, vdi_type, target):
1130 self.vdi_type = vdi_type
1131 self.target = target
1133 @override
1134 def __str__(self) -> str:
1135 return \
1136 "Target %s has unexpected VDI type '%s'" % \
1137 (type(self.target), self.vdi_type)
1139 VDI_PLUG_TYPE = {'phy': 'phy', # for NETAPP
1140 'raw': 'phy',
1141 'aio': 'tap', # for LVM raw nodes
1142 'iso': 'tap', # for ISOSR
1143 'file': 'tap',
1144 'vhd': 'tap',
1145 'qcow2': 'tap'}
1147 def tap_wanted(self):
1148 # 1. Let the target vdi_type decide
1150 vdi_type = self.target.get_vdi_type()
1152 try:
1153 plug_type = self.VDI_PLUG_TYPE[vdi_type]
1154 except KeyError:
1155 raise self.UnexpectedVDIType(vdi_type,
1156 self.target.vdi)
1158 if plug_type == 'tap': 1158 ↛ 1159line 1158 didn't jump to line 1159, because the condition on line 1158 was never true
1159 return True
1160 elif self.target.vdi.sr.handles('udev'): 1160 ↛ 1166line 1160 didn't jump to line 1166, because the condition on line 1160 was never false
1161 return True
1162 # 2. Otherwise, there may be more reasons
1163 #
1164 # .. TBD
1166 return False
1168 class TargetDriver:
1169 """Safe target driver access."""
1170 # NB. *Must* test caps for optional calls. Some targets
1171 # actually implement some slots, but do not enable them. Just
1172 # try/except would risk breaking compatibility.
1174 def __init__(self, vdi, driver_info):
1175 self.vdi = vdi
1176 self._caps = driver_info['capabilities']
1178 def has_cap(self, cap):
1179 """Determine if target has given capability"""
1180 return cap in self._caps
1182 def attach(self, sr_uuid, vdi_uuid):
1183 #assert self.has_cap("VDI_ATTACH")
1184 return self.vdi.attach(sr_uuid, vdi_uuid)
1186 def detach(self, sr_uuid, vdi_uuid):
1187 #assert self.has_cap("VDI_DETACH")
1188 self.vdi.detach(sr_uuid, vdi_uuid)
1190 def activate(self, sr_uuid, vdi_uuid):
1191 if self.has_cap("VDI_ACTIVATE"):
1192 return self.vdi.activate(sr_uuid, vdi_uuid)
1194 def deactivate(self, sr_uuid, vdi_uuid):
1195 if self.has_cap("VDI_DEACTIVATE"):
1196 self.vdi.deactivate(sr_uuid, vdi_uuid)
1197 #def resize(self, sr_uuid, vdi_uuid, size):
1198 # return self.vdi.resize(sr_uuid, vdi_uuid, size)
1200 def get_vdi_type(self):
1201 _type = self.vdi.vdi_type
1202 if not _type:
1203 raise VDI.UnexpectedVDIType(_type, self.vdi)
1204 return _type
1206 def get_vdi_path(self):
1207 return self.vdi.path
1209 class Link(object):
1210 """Relink a node under a common name"""
1211 # NB. We have to provide the device node path during
1212 # VDI.attach, but currently do not allocate the tapdisk minor
1213 # before VDI.activate. Therefore those link steps where we
1214 # relink existing devices under deterministic path names.
1216 BASEDIR: ClassVar[str] = ""
1218 def _mklink(self, target) -> None:
1219 pass
1221 @abstractmethod
1222 def _equals(self, target) -> bool:
1223 pass
1225 def __init__(self, path):
1226 self._path = path
1228 @classmethod
1229 def from_name(cls, name):
1230 path = "%s/%s" % (cls.BASEDIR, name)
1231 return cls(path)
1233 @classmethod
1234 def from_uuid(cls, sr_uuid, vdi_uuid):
1235 name = "%s/%s" % (sr_uuid, vdi_uuid)
1236 return cls.from_name(name)
1238 def path(self):
1239 return self._path
1241 def stat(self):
1242 return os.stat(self.path())
1244 def mklink(self, target) -> None:
1246 path = self.path()
1247 util.SMlog("%s -> %s" % (self, target))
1249 mkdirs(os.path.dirname(path))
1250 try:
1251 self._mklink(target)
1252 except OSError as e:
1253 # We do unlink during teardown, but have to stay
1254 # idempotent. However, a *wrong* target should never
1255 # be seen.
1256 if e.errno != errno.EEXIST:
1257 raise
1258 assert self._equals(target), "'%s' not equal to '%s'" % (path, target)
1260 def unlink(self):
1261 try:
1262 os.unlink(self.path())
1263 except OSError as e:
1264 if e.errno != errno.ENOENT:
1265 raise
1267 @override
1268 def __str__(self) -> str:
1269 path = self.path()
1270 return "%s(%s)" % (self.__class__.__name__, path)
1272 class SymLink(Link):
1273 """Symlink some file to a common name"""
1275 def readlink(self):
1276 return os.readlink(self.path())
1278 def symlink(self):
1279 return self.path()
1281 @override
1282 def _mklink(self, target) -> None:
1283 os.symlink(target, self.path())
1285 @override
1286 def _equals(self, target) -> bool:
1287 return self.readlink() == target
1289 class DeviceNode(Link):
1290 """Relink a block device node to a common name"""
1292 @classmethod
1293 def _real_stat(cls, target):
1294 """stat() not on @target, but its realpath()"""
1295 _target = os.path.realpath(target)
1296 return os.stat(_target)
1298 @classmethod
1299 def is_block(cls, target):
1300 """Whether @target refers to a block device."""
1301 return S_ISBLK(cls._real_stat(target).st_mode)
1303 @override
1304 def _mklink(self, target) -> None:
1306 st = self._real_stat(target)
1307 if not S_ISBLK(st.st_mode):
1308 raise self.NotABlockDevice(target, st)
1310 # set group read for disk group as well as root
1311 os.mknod(self.path(), st.st_mode | stat.S_IRGRP, st.st_rdev)
1312 os.chown(self.path(), st.st_uid, grp.getgrnam("disk").gr_gid)
1314 @override
1315 def _equals(self, target) -> bool:
1316 target_rdev = self._real_stat(target).st_rdev
1317 return self.stat().st_rdev == target_rdev
1319 def rdev(self):
1320 st = self.stat()
1321 assert S_ISBLK(st.st_mode)
1322 return os.major(st.st_rdev), os.minor(st.st_rdev)
1324 class NotABlockDevice(Exception):
1326 def __init__(self, path, st):
1327 self.path = path
1328 self.st = st
1330 @override
1331 def __str__(self) -> str:
1332 return "%s is not a block device: %s" % (self.path, self.st)
1334 class Hybrid(Link):
1336 def __init__(self, path):
1337 VDI.Link.__init__(self, path)
1338 self._devnode = VDI.DeviceNode(path)
1339 self._symlink = VDI.SymLink(path)
1341 def rdev(self):
1342 st = self.stat()
1343 if S_ISBLK(st.st_mode):
1344 return self._devnode.rdev()
1345 raise self._devnode.NotABlockDevice(self.path(), st)
1347 @override
1348 def mklink(self, target) -> None:
1349 if self._devnode.is_block(target):
1350 self._obj = self._devnode
1351 else:
1352 self._obj = self._symlink
1353 self._obj.mklink(target)
1355 @override
1356 def _equals(self, target) -> bool:
1357 return self._obj._equals(target)
1359 class PhyLink(SymLink):
1360 BASEDIR = "/dev/sm/phy"
1361 # NB. Cannot use DeviceNodes, e.g. FileVDIs aren't bdevs.
1363 class NBDLink(SymLink):
1365 BASEDIR = "/run/blktap-control/nbd"
1367 class BackendLink(Hybrid):
1368 BASEDIR = "/dev/sm/backend"
1369 # NB. Could be SymLinks as well, but saving major,minor pairs in
1370 # Links enables neat state capturing when managing Tapdisks. Note
1371 # that we essentially have a tap-ctl list replacement here. For
1372 # now make it a 'Hybrid'. Likely to collapse into a DeviceNode as
1373 # soon as ISOs are tapdisks.
1375 @staticmethod
1376 def _tap_activate(phy_path, vdi_type, sr_uuid, options, pool_size=None):
1378 tapdisk = Tapdisk.find_by_path(phy_path)
1379 if not tapdisk: 1379 ↛ 1380line 1379 didn't jump to line 1380, because the condition on line 1379 was never true
1380 blktap = Blktap.allocate()
1381 blktap.set_pool_name(sr_uuid)
1382 if pool_size:
1383 blktap.set_pool_size(pool_size)
1385 try:
1386 tapdisk = \
1387 Tapdisk.launch_on_tap(blktap,
1388 phy_path,
1389 VDI._tap_type(vdi_type),
1390 options)
1391 except:
1392 blktap.free()
1393 raise
1394 util.SMlog("tap.activate: Launched %s" % tapdisk)
1396 else:
1397 util.SMlog("tap.activate: Found %s" % tapdisk)
1399 return tapdisk.get_devpath(), tapdisk
1401 @staticmethod
1402 def _tap_deactivate(minor):
1404 try:
1405 tapdisk = Tapdisk.from_minor(minor)
1406 except TapdiskNotRunning as e:
1407 util.SMlog("tap.deactivate: Warning, %s" % e)
1408 # NB. Should not be here unless the agent refcount
1409 # broke. Also, a clean shutdown should not have leaked
1410 # the recorded minor.
1411 else:
1412 tapdisk.shutdown()
1413 util.SMlog("tap.deactivate: Shut down %s" % tapdisk)
1415 @classmethod
1416 def tap_pause(cls, session, sr_uuid, vdi_uuid, failfast=False):
1417 """
1418 Pauses the tapdisk.
1420 session: a XAPI session
1421 sr_uuid: the UUID of the SR on which VDI lives
1422 vdi_uuid: the UUID of the VDI to pause
1423 failfast: controls whether the VDI lock should be acquired in a
1424 non-blocking manner
1425 """
1426 util.SMlog("Pause request for %s" % vdi_uuid)
1427 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1428 session.xenapi.VDI.add_to_sm_config(vdi_ref, 'paused', 'true')
1429 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1430 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 1430 ↛ 1431line 1430 didn't jump to line 1431, because the loop on line 1430 never started
1431 host_ref = key[len('host_'):]
1432 util.SMlog("Calling tap-pause on host %s" % host_ref)
1433 if not cls.call_pluginhandler(session, host_ref,
1434 sr_uuid, vdi_uuid, "pause", failfast=failfast):
1435 # Failed to pause node
1436 session.xenapi.VDI.remove_from_sm_config(vdi_ref, 'paused')
1437 return False
1438 return True
1440 @classmethod
1441 def tap_unpause(cls, session, sr_uuid, vdi_uuid, secondary=None,
1442 activate_parents=False):
1443 util.SMlog("Unpause request for %s secondary=%s" % (vdi_uuid, secondary))
1444 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1445 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1446 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 1446 ↛ 1447line 1446 didn't jump to line 1447, because the loop on line 1446 never started
1447 host_ref = key[len('host_'):]
1448 util.SMlog("Calling tap-unpause on host %s" % host_ref)
1449 if not cls.call_pluginhandler(session, host_ref,
1450 sr_uuid, vdi_uuid, "unpause", secondary, activate_parents):
1451 # Failed to unpause node
1452 return False
1453 session.xenapi.VDI.remove_from_sm_config(vdi_ref, 'paused')
1454 return True
1456 @classmethod
1457 def tap_refresh(cls, session, sr_uuid, vdi_uuid, activate_parents=False):
1458 util.SMlog("Refresh request for %s" % vdi_uuid)
1459 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1460 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1461 for key in [x for x in sm_config.keys() if x.startswith('host_')]:
1462 host_ref = key[len('host_'):]
1463 util.SMlog("Calling tap-refresh on host %s" % host_ref)
1464 if not cls.call_pluginhandler(session, host_ref,
1465 sr_uuid, vdi_uuid, "refresh", None,
1466 activate_parents=activate_parents):
1467 # Failed to refresh node
1468 return False
1469 return True
1471 @classmethod
1472 def tap_status(cls, session, vdi_uuid):
1473 """Return True if disk is attached, false if it isn't"""
1474 util.SMlog("Disk status request for %s" % vdi_uuid)
1475 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1476 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1477 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 1477 ↛ 1478line 1477 didn't jump to line 1478, because the loop on line 1477 never started
1478 return True
1479 return False
1481 @classmethod
1482 def call_pluginhandler(cls, session, host_ref, sr_uuid, vdi_uuid, action,
1483 secondary=None, activate_parents=False, failfast=False):
1484 """Optionally, activate the parent LV before unpausing"""
1485 try:
1486 args = {"sr_uuid": sr_uuid, "vdi_uuid": vdi_uuid,
1487 "failfast": str(failfast)}
1488 if secondary:
1489 args["secondary"] = secondary
1490 if activate_parents:
1491 args["activate_parents"] = "true"
1492 ret = session.xenapi.host.call_plugin(
1493 host_ref, PLUGIN_TAP_PAUSE, action,
1494 args)
1495 return ret == "True"
1496 except Exception as e:
1497 util.logException("BLKTAP2:call_pluginhandler %s" % e)
1498 return False
1500 def _add_tag(self, vdi_uuid, writable):
1501 util.SMlog("Adding tag to: %s" % vdi_uuid)
1502 attach_mode = "RO"
1503 if writable:
1504 attach_mode = "RW"
1505 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1506 host_ref = self._session.xenapi.host.get_by_uuid(util.get_this_host())
1507 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1508 attached_as = util.attached_as(sm_config)
1509 if NO_MULTIPLE_ATTACH and (attached_as == "RW" or \ 1509 ↛ 1511line 1509 didn't jump to line 1511, because the condition on line 1509 was never true
1510 (attached_as == "RO" and attach_mode == "RW")):
1511 util.SMlog("need to reset VDI %s" % vdi_uuid)
1512 if not resetvdis.reset_vdi(self._session, vdi_uuid, force=False,
1513 term_output=False, writable=writable):
1514 raise util.SMException("VDI %s not detached cleanly" % vdi_uuid)
1515 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1516 if 'relinking' in sm_config:
1517 util.SMlog("Relinking key found, back-off and retry" % sm_config)
1518 return False
1519 if 'paused' in sm_config:
1520 util.SMlog("Paused or host_ref key found [%s]" % sm_config)
1521 return False
1522 try:
1523 self._session.xenapi.VDI.add_to_sm_config(
1524 vdi_ref, 'activating', 'True')
1525 except XenAPI.Failure as e:
1526 if e.details[0] == 'MAP_DUPLICATE_KEY' and not writable:
1527 # Someone else is activating - a retry might succeed
1528 return False
1529 raise
1530 host_key = "host_%s" % host_ref
1531 assert host_key not in sm_config
1532 self._session.xenapi.VDI.add_to_sm_config(vdi_ref, host_key,
1533 attach_mode)
1534 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1535 if 'paused' in sm_config or 'relinking' in sm_config:
1536 util.SMlog("Found %s key, aborting" % (
1537 'paused' if 'paused' in sm_config else 'relinking'))
1538 self._session.xenapi.VDI.remove_from_sm_config(vdi_ref, host_key)
1539 self._session.xenapi.VDI.remove_from_sm_config(
1540 vdi_ref, 'activating')
1541 return False
1542 util.SMlog("Activate lock succeeded")
1543 return True
1545 def _check_tag(self, vdi_uuid):
1546 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1547 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1548 if 'paused' in sm_config:
1549 util.SMlog("Paused key found [%s]" % sm_config)
1550 return False
1551 return True
1553 def _remove_tag(self, vdi_uuid):
1554 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1555 host_ref = self._session.xenapi.host.get_by_uuid(util.get_this_host())
1556 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1557 host_key = "host_%s" % host_ref
1558 if host_key in sm_config:
1559 self._session.xenapi.VDI.remove_from_sm_config(vdi_ref, host_key)
1560 util.SMlog("Removed host key %s for %s" % (host_key, vdi_uuid))
1561 else:
1562 util.SMlog("_remove_tag: host key %s not found, ignore" % host_key)
1564 def _get_pool_config(self, pool_name):
1565 pool_info = dict()
1566 vdi_ref = self.target.vdi.sr.srcmd.params.get('vdi_ref')
1567 if not vdi_ref: 1567 ↛ 1570line 1567 didn't jump to line 1570, because the condition on line 1567 was never true
1568 # attach_from_config context: HA disks don't need to be in any
1569 # special pool
1570 return pool_info
1572 sr_ref = self.target.vdi.sr.srcmd.params.get('sr_ref')
1573 sr_config = self._session.xenapi.SR.get_other_config(sr_ref)
1574 vdi_config = self._session.xenapi.VDI.get_other_config(vdi_ref)
1575 pool_size_str = sr_config.get(POOL_SIZE_KEY)
1576 pool_name_override = vdi_config.get(POOL_NAME_KEY)
1577 if pool_name_override: 1577 ↛ 1582line 1577 didn't jump to line 1582, because the condition on line 1577 was never false
1578 pool_name = pool_name_override
1579 pool_size_override = vdi_config.get(POOL_SIZE_KEY)
1580 if pool_size_override: 1580 ↛ 1582line 1580 didn't jump to line 1582, because the condition on line 1580 was never false
1581 pool_size_str = pool_size_override
1582 pool_size = 0
1583 if pool_size_str: 1583 ↛ 1593line 1583 didn't jump to line 1593, because the condition on line 1583 was never false
1584 try:
1585 pool_size = int(pool_size_str)
1586 if pool_size < 1 or pool_size > MAX_FULL_RINGS: 1586 ↛ 1587line 1586 didn't jump to line 1587, because the condition on line 1586 was never true
1587 raise ValueError("outside of range")
1588 pool_size = NUM_PAGES_PER_RING * pool_size
1589 except ValueError:
1590 util.SMlog("Error: invalid mem-pool-size %s" % pool_size_str)
1591 pool_size = 0
1593 pool_info["mem-pool"] = pool_name
1594 if pool_size: 1594 ↛ 1597line 1594 didn't jump to line 1597, because the condition on line 1594 was never false
1595 pool_info["mem-pool-size"] = str(pool_size)
1597 return pool_info
1599 def linkNBD(self, sr_uuid, vdi_uuid):
1600 if self.tap:
1601 nbd_path = '/run/blktap-control/nbd%d.%d' % (int(self.tap.pid),
1602 int(self.tap.minor))
1603 VDI.NBDLink.from_uuid(sr_uuid, vdi_uuid).mklink(nbd_path)
1605 def attach(self, sr_uuid, vdi_uuid, writable, activate=False, caching_params={}):
1606 """Return/dev/sm/backend symlink path"""
1607 self.xenstore_data.update(self._get_pool_config(sr_uuid))
1608 if not self.target.has_cap("ATOMIC_PAUSE") or activate:
1609 util.SMlog("Attach & activate")
1610 self._attach(sr_uuid, vdi_uuid)
1611 dev_path = self._activate(sr_uuid, vdi_uuid,
1612 {"rdonly": not writable})
1613 self.BackendLink.from_uuid(sr_uuid, vdi_uuid).mklink(dev_path)
1614 self.linkNBD(sr_uuid, vdi_uuid)
1616 # Return backend/ link
1617 back_path = self.BackendLink.from_uuid(sr_uuid, vdi_uuid).path()
1618 if self.tap_wanted():
1619 # Only have NBD if we also have a tap
1620 nbd_path = "nbd:unix:{}:exportname={}".format(
1621 VDI.NBDLink.from_uuid(sr_uuid, vdi_uuid).path(),
1622 vdi_uuid)
1623 else:
1624 nbd_path = ""
1626 options = {"rdonly": not writable}
1627 options.update(caching_params)
1628 o_direct, o_direct_reason = self.get_o_direct_capability(options)
1629 struct = {'params': back_path,
1630 'params_nbd': nbd_path,
1631 'o_direct': o_direct,
1632 'o_direct_reason': o_direct_reason,
1633 'xenstore_data': self.xenstore_data}
1634 util.SMlog('result: %s' % struct)
1636 try:
1637 f = open("%s.attach_info" % back_path, 'a')
1638 f.write(xmlrpc.client.dumps((struct, ), "", True))
1639 f.close()
1640 except:
1641 pass
1643 return xmlrpc.client.dumps((struct, ), "", True)
1645 def activate(self, sr_uuid, vdi_uuid, writable, caching_params):
1646 util.SMlog("blktap2.activate")
1647 options = {"rdonly": not writable}
1648 options.update(caching_params)
1650 sr_ref = self.target.vdi.sr.srcmd.params.get('sr_ref')
1651 sr_other_config = self._session.xenapi.SR.get_other_config(sr_ref)
1652 for i in range(self.ATTACH_DETACH_RETRY_SECS): 1652 ↛ 1659line 1652 didn't jump to line 1659, because the loop on line 1652 didn't complete
1653 try:
1654 if self._activate_locked(sr_uuid, vdi_uuid, options):
1655 return
1656 except util.SRBusyException:
1657 util.SMlog("SR locked, retrying")
1658 time.sleep(1)
1659 raise util.SMException("VDI %s locked" % vdi_uuid)
1661 def _get_sr_master_host_ref(self) -> str:
1662 """
1663 Give the host ref of the one responsible for Garbage Collection for a SR.
1664 Meaning this host for a local SR, the master for a shared SR.
1665 """
1666 sr = self.target.vdi.sr
1667 if sr.is_shared():
1668 host_ref = util.get_master_ref(self._session)
1669 else:
1670 host_ref = sr.host_ref
1671 return host_ref
1673 def _get_vdi_chain(self, cowutil, extractUuid) -> List[str]:
1674 vdi_chain = []
1675 path = self.target.get_vdi_path()
1677 #TODO: Need to add handling of error for getParentNoCheck, e.g. corrupted VDI where we can't read parent
1678 vdi_chain.append(extractUuid(path))
1679 parent = cowutil.getParentNoCheck(path)
1680 while parent:
1681 vdi_chain.append(extractUuid(parent))
1682 parent = cowutil.getParentNoCheck(parent)
1683 vdi_chain.reverse()
1684 return vdi_chain
1686 def _check_journal_coalesce_chain(self, sr_uuid: str, vdi_uuid: str) -> bool:
1687 vdi_type = self.target.get_vdi_type()
1688 cowutil = getCowUtil(vdi_type)
1690 if not cowutil.isCoalesceableOnRemote(): #We only need to stop the coalesce in case of QCOW2 1690 ↛ 1693line 1690 didn't jump to line 1693, because the condition on line 1690 was never false
1691 return True
1693 path = self.target.get_vdi_path()
1695 import fjournaler
1696 import journaler
1697 from lvmcowutil import LvmCowUtil
1698 from FileSR import FileVDI
1699 import lvmcache
1701 journal: Union[journaler.Journaler, fjournaler.Journaler]
1702 # Different extractUUID & journaler function for LVMSR and FileSR
1703 if path.startswith("/dev/"): #TODO: How to identify SR type easily, we could ask XAPI since we have the sruuid (and even ref)
1704 vgName = "VG_XenStorage-{}".format(sr_uuid)
1705 lvmCache = lvmcache.LVMCache(vgName)
1706 journal = journaler.Journaler(lvmCache)
1708 extractUuid = LvmCowUtil.extractUuid
1709 else:
1710 journal = fjournaler.Journaler(os.getcwd())
1711 extractUuid = FileVDI.extractUuid
1713 # Get the VDI chain
1714 vdi_chain = self._get_vdi_chain(cowutil, extractUuid)
1716 if len(vdi_chain) == 1:
1717 # We only have a leaf, do nothing
1718 util.SMlog("VDI {} is only a leaf, continuing...".format(vdi_uuid))
1719 return True
1721 # Log the chain of active VDI
1722 level = 0
1723 util.SMlog("VDI chain:")
1724 for vdi in vdi_chain:
1725 prefix = " " * level
1726 level += 1
1727 util.SMlog("{}{}".format(prefix, vdi))
1729 vdi_to_cancel = []
1730 for entry in journal.getAll("coalesce").keys():
1731 if entry in vdi_chain:
1732 vdi_to_cancel.append(entry)
1733 util.SMlog("Coalescing VDI {} in chain".format(entry))
1735 # Get the host_ref from the host doing the GC work
1736 host_ref = self._get_sr_master_host_ref()
1737 for vdi in vdi_to_cancel:
1738 args = {"sr_uuid": sr_uuid, "vdi_uuid": vdi}
1739 util.SMlog("Calling cancel_coalesce_master with args: {}".format(args))
1740 self._session.xenapi.host.call_plugin(\
1741 host_ref, PLUGIN_ON_SLAVE, "cancel_coalesce_master", args)
1743 return True
1745 @locking("VDIUnavailable")
1746 def _activate_locked(self, sr_uuid, vdi_uuid, options):
1747 """Wraps target.activate and adds a tapdisk"""
1749 #util.SMlog("VDI.activate %s" % vdi_uuid)
1750 refresh = False
1751 if self.tap_wanted(): 1751 ↛ 1756line 1751 didn't jump to line 1756, because the condition on line 1751 was never false
1752 if not self._add_tag(vdi_uuid, not options["rdonly"]):
1753 return False
1754 refresh = True
1756 try:
1757 if refresh: 1757 ↛ 1768line 1757 didn't jump to line 1768, because the condition on line 1757 was never false
1758 # it is possible that while the VDI was paused some of its
1759 # attributes have changed (e.g. its size if it was inflated; or its
1760 # path if it was leaf-coalesced onto a raw LV), so refresh the
1761 # object completely
1762 params = self.target.vdi.sr.srcmd.params
1763 target = sm.VDI.from_uuid(self.target.vdi.session, vdi_uuid)
1764 target.sr.srcmd.params = params
1765 driver_info = target.sr.srcmd.driver_info
1766 self.target = self.TargetDriver(target, driver_info)
1768 util.fistpoint.activate_custom_fn( 1768 ↛ exitline 1768 didn't jump to the function exit
1769 "blktap_activate_inject_failure",
1770 lambda: util.inject_failure())
1772 # Attach the physical node
1773 if self.target.has_cap("ATOMIC_PAUSE"): 1773 ↛ 1776line 1773 didn't jump to line 1776, because the condition on line 1773 was never false
1774 self._attach(sr_uuid, vdi_uuid)
1776 vdi_type = self.target.get_vdi_type()
1779 # Take lvchange-p Lock before running
1780 # tap-ctl open
1781 # Needed to avoid race with lvchange -p which is
1782 # now taking the same lock
1783 # This is a fix for CA-155766
1784 if hasattr(self.target.vdi.sr, 'DRIVER_TYPE') and \ 1784 ↛ 1787line 1784 didn't jump to line 1787, because the condition on line 1784 was never true
1785 self.target.vdi.sr.DRIVER_TYPE == 'lvhd' and \
1786 VdiType.isCowImage(vdi_type):
1787 lock = Lock("lvchange-p", NS_PREFIX_LVM + sr_uuid)
1788 lock.acquire()
1790 if not self._check_journal_coalesce_chain(sr_uuid, vdi_uuid): 1790 ↛ 1791line 1790 didn't jump to line 1791, because the condition on line 1790 was never true
1791 return False
1792 # we could return false from here if we need to retry after relink
1793 # #TODO: handling error here
1795 # When we attach a static VDI for HA, we cannot communicate with
1796 # xapi, because has not started yet. These VDIs are raw.
1797 if VdiType.isCowImage(vdi_type): 1797 ↛ 1798line 1797 didn't jump to line 1798, because the condition on line 1797 was never true
1798 session = self.target.vdi.session
1799 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1800 # pylint: disable=used-before-assignment
1801 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1802 if 'key_hash' in sm_config:
1803 key_hash = sm_config['key_hash']
1804 options['key_hash'] = key_hash
1805 options['vdi_uuid'] = vdi_uuid
1806 util.SMlog('Using key with hash {} for VDI {}'.format(key_hash, vdi_uuid))
1807 # Activate the physical node
1808 dev_path = self._activate(sr_uuid, vdi_uuid, options)
1810 if hasattr(self.target.vdi.sr, 'DRIVER_TYPE') and \ 1810 ↛ 1813line 1810 didn't jump to line 1813, because the condition on line 1810 was never true
1811 self.target.vdi.sr.DRIVER_TYPE == 'lvhd' and \
1812 VdiType.isCowImage(self.target.get_vdi_type()):
1813 lock.release()
1814 except:
1815 util.SMlog("Exception in activate/attach")
1816 if self.tap_wanted():
1817 util.fistpoint.activate_custom_fn(
1818 "blktap_activate_error_handling",
1819 lambda: time.sleep(30))
1820 while True:
1821 try:
1822 self._remove_tag(vdi_uuid)
1823 break
1824 except xmlrpc.client.ProtocolError as e:
1825 # If there's a connection error, keep trying forever.
1826 if e.errcode == http.HTTPStatus.INTERNAL_SERVER_ERROR.value:
1827 continue
1828 else:
1829 util.SMlog('failed to remove tag: %s' % e)
1830 break
1831 except Exception as e:
1832 util.SMlog('failed to remove tag: %s' % e)
1833 break
1834 raise
1835 finally:
1836 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1837 self._session.xenapi.VDI.remove_from_sm_config(
1838 vdi_ref, 'activating')
1839 util.SMlog("Removed activating flag from %s" % vdi_uuid) 1839 ↛ exitline 1839 didn't except from function '_activate_locked', because the raise on line 1834 wasn't executed or line 1839 didn't return from function '_activate_locked', because the return on line 1791 wasn't executed
1841 # Link result to backend/
1842 self.BackendLink.from_uuid(sr_uuid, vdi_uuid).mklink(dev_path)
1843 self.linkNBD(sr_uuid, vdi_uuid)
1844 return True
1846 def _activate(self, sr_uuid, vdi_uuid, options):
1847 vdi_options = self.target.activate(sr_uuid, vdi_uuid)
1849 dev_path = self.setup_cache(sr_uuid, vdi_uuid, options)
1850 if not dev_path: 1850 ↛ 1864line 1850 didn't jump to line 1864, because the condition on line 1850 was never false
1851 phy_path = self.PhyLink.from_uuid(sr_uuid, vdi_uuid).readlink()
1852 # Maybe launch a tapdisk on the physical link
1853 if self.tap_wanted(): 1853 ↛ 1862line 1853 didn't jump to line 1862, because the condition on line 1853 was never false
1854 vdi_type = self.target.get_vdi_type()
1855 options["o_direct"] = self.get_o_direct_capability(options)[0]
1856 if vdi_options: 1856 ↛ 1858line 1856 didn't jump to line 1858, because the condition on line 1856 was never false
1857 options.update(vdi_options)
1858 dev_path, self.tap = self._tap_activate(phy_path, vdi_type,
1859 sr_uuid, options,
1860 self._get_pool_config(sr_uuid).get("mem-pool-size"))
1861 else:
1862 dev_path = phy_path # Just reuse phy
1864 return dev_path
1866 def _attach(self, sr_uuid, vdi_uuid):
1867 attach_info = xmlrpc.client.loads(self.target.attach(sr_uuid, vdi_uuid))[0][0]
1868 params = attach_info['params']
1869 xenstore_data = attach_info['xenstore_data']
1870 phy_path = util.to_plain_string(params)
1871 self.xenstore_data.update(xenstore_data)
1872 # Save it to phy/
1873 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).mklink(phy_path)
1875 def deactivate(self, sr_uuid, vdi_uuid, caching_params):
1876 util.SMlog("blktap2.deactivate")
1877 for i in range(self.ATTACH_DETACH_RETRY_SECS):
1878 try:
1879 if self._deactivate_locked(sr_uuid, vdi_uuid, caching_params):
1880 return
1881 except util.SRBusyException as e:
1882 util.SMlog("SR locked, retrying")
1883 time.sleep(1)
1884 raise util.SMException("VDI %s locked" % vdi_uuid)
1886 @locking("VDIUnavailable")
1887 def _deactivate_locked(self, sr_uuid, vdi_uuid, caching_params):
1888 """Wraps target.deactivate and removes a tapdisk"""
1890 #util.SMlog("VDI.deactivate %s" % vdi_uuid)
1891 if self.tap_wanted() and not self._check_tag(vdi_uuid):
1892 return False
1894 self._deactivate(sr_uuid, vdi_uuid, caching_params)
1895 if self.target.has_cap("ATOMIC_PAUSE"):
1896 self._detach(sr_uuid, vdi_uuid)
1897 if self.tap_wanted():
1898 self._remove_tag(vdi_uuid)
1900 return True
1902 def _resetPhylink(self, sr_uuid, vdi_uuid, path):
1903 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).mklink(path)
1905 def detach(self, sr_uuid, vdi_uuid, deactivate=False, caching_params={}):
1906 if not self.target.has_cap("ATOMIC_PAUSE") or deactivate:
1907 util.SMlog("Deactivate & detach")
1908 self._deactivate(sr_uuid, vdi_uuid, caching_params)
1909 self._detach(sr_uuid, vdi_uuid)
1910 else:
1911 pass # nothing to do
1913 def _deactivate(self, sr_uuid, vdi_uuid, caching_params):
1914 import VDI as sm
1916 # Shutdown tapdisk
1917 back_link = self.BackendLink.from_uuid(sr_uuid, vdi_uuid)
1919 if not util.pathexists(back_link.path()):
1920 util.SMlog("Backend path %s does not exist" % back_link.path())
1921 return
1923 try:
1924 attach_info_path = "%s.attach_info" % (back_link.path())
1925 os.unlink(attach_info_path)
1926 except:
1927 util.SMlog("unlink of attach_info failed")
1929 try:
1930 major, minor = back_link.rdev()
1931 except self.DeviceNode.NotABlockDevice:
1932 pass
1933 else:
1934 if major == Tapdisk.major():
1935 self._tap_deactivate(minor)
1936 self.remove_cache(sr_uuid, vdi_uuid, caching_params)
1938 # Remove the backend link
1939 back_link.unlink()
1940 VDI.NBDLink.from_uuid(sr_uuid, vdi_uuid).unlink()
1942 # Deactivate & detach the physical node
1943 if self.tap_wanted() and self.target.vdi.session is not None:
1944 # it is possible that while the VDI was paused some of its
1945 # attributes have changed (e.g. its size if it was inflated; or its
1946 # path if it was leaf-coalesced onto a raw LV), so refresh the
1947 # object completely
1948 target = sm.VDI.from_uuid(self.target.vdi.session, vdi_uuid)
1949 driver_info = target.sr.srcmd.driver_info
1950 self.target = self.TargetDriver(target, driver_info)
1952 self.target.deactivate(sr_uuid, vdi_uuid)
1954 def _detach(self, sr_uuid, vdi_uuid):
1955 self.target.detach(sr_uuid, vdi_uuid)
1957 # Remove phy/
1958 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).unlink()
1960 def _updateCacheRecord(self, session, vdi_uuid, on_boot, caching):
1961 # Remove existing VDI.sm_config fields
1962 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1963 for key in ["on_boot", "caching"]:
1964 session.xenapi.VDI.remove_from_sm_config(vdi_ref, key)
1965 if not on_boot is None:
1966 session.xenapi.VDI.add_to_sm_config(vdi_ref, 'on_boot', on_boot)
1967 if not caching is None:
1968 session.xenapi.VDI.add_to_sm_config(vdi_ref, 'caching', caching)
1970 def setup_cache(self, sr_uuid, vdi_uuid, params):
1971 if params.get(self.CONF_KEY_ALLOW_CACHING) != "true": 1971 ↛ 1974line 1971 didn't jump to line 1974, because the condition on line 1971 was never false
1972 return
1974 util.SMlog("Requested local caching")
1975 if not self.target.has_cap("SR_CACHING"):
1976 util.SMlog("Error: local caching not supported by this SR")
1977 return
1979 scratch_mode = False
1980 if params.get(self.CONF_KEY_MODE_ON_BOOT) == "reset":
1981 scratch_mode = True
1982 util.SMlog("Requested scratch mode")
1983 if not self.target.has_cap("VDI_RESET_ON_BOOT/2"):
1984 util.SMlog("Error: scratch mode not supported by this SR")
1985 return
1987 dev_path = None
1988 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR)
1989 if not local_sr_uuid:
1990 util.SMlog("ERROR: Local cache SR not specified, not enabling")
1991 return
1992 dev_path = self._setup_cache(self._session, sr_uuid, vdi_uuid,
1993 local_sr_uuid, scratch_mode, params)
1995 if dev_path:
1996 self._updateCacheRecord(self._session, self.target.vdi.uuid,
1997 params.get(self.CONF_KEY_MODE_ON_BOOT),
1998 params.get(self.CONF_KEY_ALLOW_CACHING))
2000 return dev_path
2002 def alert_no_cache(self, session, vdi_uuid, cache_sr_uuid, err):
2003 vm_uuid = None
2004 vm_label = ""
2005 try:
2006 cache_sr_ref = session.xenapi.SR.get_by_uuid(cache_sr_uuid)
2007 cache_sr_rec = session.xenapi.SR.get_record(cache_sr_ref)
2008 cache_sr_label = cache_sr_rec.get("name_label")
2010 host_ref = session.xenapi.host.get_by_uuid(util.get_this_host())
2011 host_rec = session.xenapi.host.get_record(host_ref)
2012 host_label = host_rec.get("name_label")
2014 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
2015 vbds = session.xenapi.VBD.get_all_records_where( \
2016 "field \"VDI\" = \"%s\"" % vdi_ref)
2017 for vbd_rec in vbds.values():
2018 vm_ref = vbd_rec.get("VM")
2019 vm_rec = session.xenapi.VM.get_record(vm_ref)
2020 vm_uuid = vm_rec.get("uuid")
2021 vm_label = vm_rec.get("name_label")
2022 except:
2023 util.logException("alert_no_cache")
2025 alert_obj = "SR"
2026 alert_uuid = str(cache_sr_uuid)
2027 alert_str = "No space left in Local Cache SR %s" % cache_sr_uuid
2028 if vm_uuid:
2029 alert_obj = "VM"
2030 alert_uuid = vm_uuid
2031 reason = ""
2032 if err == errno.ENOSPC:
2033 reason = "because there is no space left"
2034 alert_str = "The VM \"%s\" is not using IntelliCache %s on the Local Cache SR (\"%s\") on host \"%s\"" % \
2035 (vm_label, reason, cache_sr_label, host_label)
2037 util.SMlog("Creating alert: (%s, %s, \"%s\")" % \
2038 (alert_obj, alert_uuid, alert_str))
2039 session.xenapi.message.create("No space left in local cache", "3",
2040 alert_obj, alert_uuid, alert_str)
2042 def _setup_cache(self, session, sr_uuid, vdi_uuid, local_sr_uuid,
2043 scratch_mode, options):
2044 import SR
2045 import EXTSR
2046 import NFSSR
2047 from lock import Lock
2048 from FileSR import FileVDI
2050 vdi_type = self.target.get_vdi_type()
2051 tap_type = VDI._tap_type(vdi_type)
2052 cowutil = getCowUtil(vdi_type)
2054 parent_uuid = cowutil.getParent(self.target.vdi.path, FileVDI.extractUuid)
2055 if not parent_uuid:
2056 util.SMlog("ERROR: VDI %s has no parent, not enabling" % \
2057 self.target.vdi.uuid)
2058 return
2060 util.SMlog("Setting up cache")
2061 parent_uuid = parent_uuid.strip()
2062 shared_target = NFSSR.NFSFileVDI(self.target.vdi.sr, parent_uuid)
2064 if shared_target.parent:
2065 util.SMlog("ERROR: Parent VDI %s has parent, not enabling" %
2066 shared_target.uuid)
2067 return
2069 SR.registerSR(EXTSR.EXTSR)
2070 local_sr = SR.SR.from_uuid(session, local_sr_uuid)
2072 lock = Lock(self.LOCK_CACHE_SETUP, parent_uuid)
2073 lock.acquire()
2075 # read cache
2076 read_cache_path = "%s/%s.vhdcache" % (local_sr.path, shared_target.uuid)
2077 if util.pathexists(read_cache_path):
2078 util.SMlog("Read cache node (%s) already exists, not creating" % \
2079 read_cache_path)
2080 else:
2081 try:
2082 cowutil.snapshot(read_cache_path, shared_target.path, False)
2083 except util.CommandException as e:
2084 util.SMlog("Error creating parent cache: %s" % e)
2085 self.alert_no_cache(session, vdi_uuid, local_sr_uuid, e.code)
2086 return None
2088 # local write node
2089 leaf_size = cowutil.getSizeVirt(self.target.vdi.path)
2090 local_leaf_path = "%s/%s.vhdcache" % \
2091 (local_sr.path, self.target.vdi.uuid)
2092 if util.pathexists(local_leaf_path):
2093 util.SMlog("Local leaf node (%s) already exists, deleting" % \
2094 local_leaf_path)
2095 os.unlink(local_leaf_path)
2096 try:
2097 cowutil.snapshot(local_leaf_path, read_cache_path, False,
2098 msize=leaf_size, checkEmpty=False)
2099 except util.CommandException as e:
2100 util.SMlog("Error creating leaf cache: %s" % e)
2101 self.alert_no_cache(session, vdi_uuid, local_sr_uuid, e.code)
2102 return None
2104 local_leaf_size = cowutil.getSizeVirt(local_leaf_path)
2105 if leaf_size > local_leaf_size:
2106 util.SMlog("Leaf size %d > local leaf cache size %d, resizing" %
2107 (leaf_size, local_leaf_size))
2108 cowutil.setSizeVirtFast(local_leaf_path, leaf_size)
2110 prt_tapdisk = Tapdisk.find_by_path(read_cache_path)
2111 if not prt_tapdisk:
2112 parent_options = copy.deepcopy(options)
2113 parent_options["rdonly"] = False
2114 parent_options["lcache"] = True
2116 blktap = Blktap.allocate()
2117 try:
2118 blktap.set_pool_name("lcache-parent-pool-%s" % blktap.minor)
2119 # no need to change pool_size since each parent tapdisk is in
2120 # its own pool
2121 prt_tapdisk = Tapdisk.launch_on_tap(blktap, read_cache_path, tap_type, parent_options)
2122 except:
2123 blktap.free()
2124 raise
2126 secondary = "%s:%s" % (vdi_type, self.PhyLink.from_uuid(sr_uuid, vdi_uuid).readlink())
2128 util.SMlog("Parent tapdisk: %s" % prt_tapdisk)
2129 leaf_tapdisk = Tapdisk.find_by_path(local_leaf_path)
2130 if not leaf_tapdisk:
2131 blktap = Blktap.allocate()
2132 child_options = copy.deepcopy(options)
2133 child_options["rdonly"] = False
2134 child_options["lcache"] = (not scratch_mode)
2135 child_options["existing_prt"] = prt_tapdisk.minor
2136 child_options["secondary"] = secondary
2137 child_options["standby"] = scratch_mode
2138 try:
2139 leaf_tapdisk = Tapdisk.launch_on_tap(blktap, local_leaf_path, tap_type, child_options)
2140 except:
2141 blktap.free()
2142 raise
2144 lock.release()
2146 util.SMlog("Local read cache: %s, local leaf: %s" % \
2147 (read_cache_path, local_leaf_path))
2149 self.tap = leaf_tapdisk
2150 return leaf_tapdisk.get_devpath()
2152 def remove_cache(self, sr_uuid, vdi_uuid, params):
2153 if not self.target.has_cap("SR_CACHING"):
2154 return
2156 caching = params.get(self.CONF_KEY_ALLOW_CACHING) == "true"
2158 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR)
2159 if caching and not local_sr_uuid:
2160 util.SMlog("ERROR: Local cache SR not specified, ignore")
2161 return
2163 if caching:
2164 self._remove_cache(self._session, local_sr_uuid)
2166 if self._session is not None:
2167 self._updateCacheRecord(self._session, self.target.vdi.uuid, None, None)
2169 def _is_tapdisk_in_use(self, minor):
2170 retVal, links, sockets = util.findRunningProcessOrOpenFile("tapdisk")
2171 if not retVal:
2172 # err on the side of caution
2173 return True
2175 for link in links:
2176 if link.find("tapdev%d" % minor) != -1:
2177 return True
2179 socket_re = re.compile(r'^/.*/nbd\d+\.%d' % minor)
2180 for s in sockets:
2181 if socket_re.match(s):
2182 return True
2184 return False
2186 def _remove_cache(self, session, local_sr_uuid):
2187 import SR
2188 import EXTSR
2189 import NFSSR
2190 from lock import Lock
2191 from FileSR import FileVDI
2193 vdi_type = self.target.get_vdi_type()
2194 parent_uuid = getCowUtil(vdi_type).getParent(self.target.vdi.path, FileVDI.extractUuid)
2195 if not parent_uuid:
2196 util.SMlog("ERROR: No parent for VDI %s, ignore" % \
2197 self.target.vdi.uuid)
2198 return
2200 util.SMlog("Tearing down the cache")
2202 parent_uuid = parent_uuid.strip()
2203 shared_target = NFSSR.NFSFileVDI(self.target.vdi.sr, parent_uuid)
2205 SR.registerSR(EXTSR.EXTSR)
2206 local_sr = SR.SR.from_uuid(session, local_sr_uuid)
2208 lock = Lock(self.LOCK_CACHE_SETUP, parent_uuid)
2209 lock.acquire()
2211 # local write node
2212 local_leaf_path = "%s/%s.vhdcache" % \
2213 (local_sr.path, self.target.vdi.uuid)
2214 if util.pathexists(local_leaf_path):
2215 util.SMlog("Deleting local leaf node %s" % local_leaf_path)
2216 os.unlink(local_leaf_path)
2218 read_cache_path = "%s/%s.vhdcache" % (local_sr.path, shared_target.uuid)
2219 prt_tapdisk = Tapdisk.find_by_path(read_cache_path)
2220 if not prt_tapdisk:
2221 util.SMlog("Parent tapdisk not found")
2222 elif not self._is_tapdisk_in_use(prt_tapdisk.minor):
2223 util.SMlog("Parent tapdisk not in use: shutting down %s" % \
2224 read_cache_path)
2225 try:
2226 prt_tapdisk.shutdown()
2227 except:
2228 util.logException("shutting down parent tapdisk")
2229 else:
2230 util.SMlog("Parent tapdisk still in use: %s" % read_cache_path)
2231 # the parent cache files are removed during the local SR's background
2232 # GC run
2234 lock.release()
2236PythonKeyError = KeyError
2239class UEventHandler(object):
2241 def __init__(self):
2242 self._action = None
2244 class KeyError(PythonKeyError):
2245 def __init__(self, args):
2246 super().__init__(args)
2247 self.key = args[0]
2249 @override
2250 def __str__(self) -> str:
2251 return \
2252 "Key '%s' missing in environment. " % self.key + \
2253 "Not called in udev context?"
2255 @classmethod
2256 def getenv(cls, key):
2257 try:
2258 return os.environ[key]
2259 except KeyError as e:
2260 raise cls.KeyError(e.args[0])
2262 def get_action(self):
2263 if not self._action:
2264 self._action = self.getenv('ACTION')
2265 return self._action
2267 class UnhandledEvent(Exception):
2269 def __init__(self, event, handler):
2270 self.event = event
2271 self.handler = handler
2273 @override
2274 def __str__(self) -> str:
2275 return "Uevent '%s' not handled by %s" % \
2276 (self.event, self.handler.__class__.__name__)
2278 ACTIONS: Dict[str, Callable] = {}
2280 def run(self):
2282 action = self.get_action()
2283 try:
2284 fn = self.ACTIONS[action]
2285 except KeyError:
2286 raise self.UnhandledEvent(action, self)
2288 return fn(self)
2290 @override
2291 def __str__(self) -> str:
2292 try:
2293 action = self.get_action()
2294 except:
2295 action = None
2296 return "%s[%s]" % (self.__class__.__name__, action)
2299class __BlktapControl(ClassDevice):
2300 SYSFS_CLASSTYPE = "misc"
2302 def __init__(self):
2303 ClassDevice.__init__(self)
2304 self._default_pool = None
2306 @override
2307 def sysfs_devname(self) -> str:
2308 return "blktap!control"
2310 class DefaultPool(Attribute):
2311 SYSFS_NODENAME = "default_pool"
2313 def get_default_pool_attr(self):
2314 if not self._default_pool:
2315 self._default_pool = self.DefaultPool.from_kobject(self)
2316 return self._default_pool
2318 def get_default_pool_name(self):
2319 return self.get_default_pool_attr().readline()
2321 def set_default_pool_name(self, name):
2322 self.get_default_pool_attr().writeline(name)
2324 def get_default_pool(self):
2325 return BlktapControl.get_pool(self.get_default_pool_name())
2327 def set_default_pool(self, pool):
2328 self.set_default_pool_name(pool.name)
2330 class NoSuchPool(Exception):
2331 def __init__(self, name):
2332 self.name = name
2334 @override
2335 def __str__(self) -> str:
2336 return "No such pool: {}".format(self.name)
2338 def get_pool(self, name):
2339 path = "%s/pools/%s" % (self.sysfs_path(), name)
2341 if not os.path.isdir(path):
2342 raise self.NoSuchPool(name)
2344 return PagePool(path)
2346BlktapControl = __BlktapControl()
2349class PagePool(KObject):
2351 def __init__(self, path):
2352 self.path = path
2353 self._size = None
2355 @override
2356 def sysfs_devname(self) -> str:
2357 return ''
2359 def sysfs_path(self):
2360 return self.path
2362 class Size(Attribute):
2363 SYSFS_NODENAME = "size"
2365 def get_size_attr(self):
2366 if not self._size:
2367 self._size = self.Size.from_kobject(self)
2368 return self._size
2370 def set_size(self, pages):
2371 pages = str(pages)
2372 self.get_size_attr().writeline(pages)
2374 def get_size(self):
2375 pages = self.get_size_attr().readline()
2376 return int(pages)
2379class BusDevice(KObject):
2381 SYSFS_BUSTYPE: ClassVar[str] = ""
2383 @classmethod
2384 def sysfs_bus_path(cls):
2385 return "/sys/bus/%s" % cls.SYSFS_BUSTYPE
2387 def sysfs_path(self):
2388 path = "%s/devices/%s" % (self.sysfs_bus_path(),
2389 self.sysfs_devname())
2391 return path
2394class XenbusDevice(BusDevice):
2395 """Xenbus device, in XS and sysfs"""
2397 XBT_NIL = ""
2399 XENBUS_DEVTYPE: ClassVar[str] = ""
2401 def __init__(self, domid, devid):
2402 self.domid = int(domid)
2403 self.devid = int(devid)
2404 self._xbt = XenbusDevice.XBT_NIL
2406 import xen.lowlevel.xs # pylint: disable=import-error
2407 self.xs = xen.lowlevel.xs.xs()
2409 def xs_path(self, key=None):
2410 path = "backend/%s/%d/%d" % (self.XENBUS_DEVTYPE,
2411 self.domid,
2412 self.devid)
2413 if key is not None:
2414 path = "%s/%s" % (path, key)
2416 return path
2418 def _log(self, prio, msg):
2419 syslog(prio, msg)
2421 def info(self, msg):
2422 self._log(_syslog.LOG_INFO, msg)
2424 def warn(self, msg):
2425 self._log(_syslog.LOG_WARNING, "WARNING: " + msg)
2427 def _xs_read_path(self, path):
2428 val = self.xs.read(self._xbt, path)
2429 #self.info("read %s = '%s'" % (path, val))
2430 return val
2432 def _xs_write_path(self, path, val):
2433 self.xs.write(self._xbt, path, val)
2434 self.info("wrote %s = '%s'" % (path, val))
2436 def _xs_rm_path(self, path):
2437 self.xs.rm(self._xbt, path)
2438 self.info("removed %s" % path)
2440 def read(self, key):
2441 return self._xs_read_path(self.xs_path(key))
2443 def has_xs_key(self, key):
2444 return self.read(key) is not None
2446 def write(self, key, val):
2447 self._xs_write_path(self.xs_path(key), val)
2449 def rm(self, key):
2450 self._xs_rm_path(self.xs_path(key))
2452 def exists(self):
2453 return self.has_xs_key(None)
2455 def begin(self):
2456 assert(self._xbt == XenbusDevice.XBT_NIL)
2457 self._xbt = self.xs.transaction_start()
2459 def commit(self):
2460 ok = self.xs.transaction_end(self._xbt, 0)
2461 self._xbt = XenbusDevice.XBT_NIL
2462 return ok
2464 def abort(self):
2465 ok = self.xs.transaction_end(self._xbt, 1)
2466 assert(ok == True)
2467 self._xbt = XenbusDevice.XBT_NIL
2469 def create_physical_device(self):
2470 """The standard protocol is: toolstack writes 'params', linux hotplug
2471 script translates this into physical-device=%x:%x"""
2472 if self.has_xs_key("physical-device"):
2473 return
2474 try:
2475 params = self.read("params")
2476 frontend = self.read("frontend")
2477 is_cdrom = self._xs_read_path("%s/device-type") == "cdrom"
2478 # We don't have PV drivers for CDROM devices, so we prevent blkback
2479 # from opening the physical-device
2480 if not(is_cdrom):
2481 major_minor = os.stat(params).st_rdev
2482 major, minor = divmod(major_minor, 256)
2483 self.write("physical-device", "%x:%x" % (major, minor))
2484 except:
2485 util.logException("BLKTAP2:create_physical_device")
2487 def signal_hotplug(self, online=True):
2488 xapi_path = "/xapi/%d/hotplug/%s/%d/hotplug" % (self.domid,
2489 self.XENBUS_DEVTYPE,
2490 self.devid)
2491 upstream_path = self.xs_path("hotplug-status")
2492 if online:
2493 self._xs_write_path(xapi_path, "online")
2494 self._xs_write_path(upstream_path, "connected")
2495 else:
2496 self._xs_rm_path(xapi_path)
2497 self._xs_rm_path(upstream_path)
2499 @override
2500 def sysfs_devname(self) -> str:
2501 return "%s-%d-%d" % (self.XENBUS_DEVTYPE,
2502 self.domid, self.devid)
2504 @override
2505 def __str__(self) -> str:
2506 return self.sysfs_devname()
2508 @classmethod
2509 def find(cls):
2510 pattern = "/sys/bus/%s/devices/%s*" % (cls.SYSFS_BUSTYPE,
2511 cls.XENBUS_DEVTYPE)
2512 for path in glob.glob(pattern):
2514 name = os.path.basename(path)
2515 (_type, domid, devid) = name.split('-')
2517 yield cls(domid, devid)
2520class XenBackendDevice(XenbusDevice):
2521 """Xenbus backend device"""
2522 SYSFS_BUSTYPE = "xen-backend"
2524 @classmethod
2525 def from_xs_path(cls, _path):
2526 (_backend, _type, domid, devid) = _path.split('/')
2528 assert _backend == 'backend'
2529 assert _type == cls.XENBUS_DEVTYPE
2531 domid = int(domid)
2532 devid = int(devid)
2534 return cls(domid, devid)
2537class Blkback(XenBackendDevice):
2538 """A blkback VBD"""
2540 XENBUS_DEVTYPE = "vbd"
2542 def __init__(self, domid, devid):
2543 XenBackendDevice.__init__(self, domid, devid)
2544 self._phy = None
2545 self._vdi_uuid = None
2546 self._q_state = None
2547 self._q_events = None
2549 class XenstoreValueError(Exception):
2550 KEY: ClassVar[str] = ""
2552 def __init__(self, vbd, _str):
2553 self.vbd = vbd
2554 self.str = _str
2556 @override
2557 def __str__(self) -> str:
2558 return "Backend %s " % self.vbd + \
2559 "has %s = %s" % (self.KEY, self.str)
2561 class PhysicalDeviceError(XenstoreValueError):
2562 KEY = "physical-device"
2564 class PhysicalDevice(object):
2566 def __init__(self, major, minor):
2567 self.major = int(major)
2568 self.minor = int(minor)
2570 @classmethod
2571 def from_xbdev(cls, xbdev):
2573 phy = xbdev.read("physical-device")
2575 try:
2576 major, minor = phy.split(':')
2577 major = int(major, 0x10)
2578 minor = int(minor, 0x10)
2579 except Exception as e:
2580 raise xbdev.PhysicalDeviceError(xbdev, phy)
2582 return cls(major, minor)
2584 def makedev(self):
2585 return os.makedev(self.major, self.minor)
2587 def is_tap(self):
2588 return self.major == Tapdisk.major()
2590 @override
2591 def __str__(self) -> str:
2592 return "%s:%s" % (self.major, self.minor)
2594 @override
2595 def __eq__(self, other) -> bool:
2596 return \
2597 self.major == other.major and \
2598 self.minor == other.minor
2600 def get_physical_device(self):
2601 if not self._phy:
2602 self._phy = self.PhysicalDevice.from_xbdev(self)
2603 return self._phy
2605 class QueueEvents(Attribute):
2606 """Blkback sysfs node to select queue-state event
2607 notifications emitted."""
2609 SYSFS_NODENAME = "queue_events"
2611 QUEUE_RUNNING = (1 << 0)
2612 QUEUE_PAUSE_DONE = (1 << 1)
2613 QUEUE_SHUTDOWN_DONE = (1 << 2)
2614 QUEUE_PAUSE_REQUEST = (1 << 3)
2615 QUEUE_SHUTDOWN_REQUEST = (1 << 4)
2617 def get_mask(self):
2618 return int(self.readline(), 0x10)
2620 def set_mask(self, mask):
2621 self.writeline("0x%x" % mask)
2623 def get_queue_events(self):
2624 if not self._q_events:
2625 self._q_events = self.QueueEvents.from_kobject(self)
2626 return self._q_events
2628 def get_vdi_uuid(self):
2629 if not self._vdi_uuid:
2630 self._vdi_uuid = self.read("sm-data/vdi-uuid")
2631 return self._vdi_uuid
2633 def pause_requested(self):
2634 return self.has_xs_key("pause")
2636 def shutdown_requested(self):
2637 return self.has_xs_key("shutdown-request")
2639 def shutdown_done(self):
2640 return self.has_xs_key("shutdown-done")
2642 def running(self):
2643 return self.has_xs_key('queue-0/kthread-pid')
2645 @classmethod
2646 def find_by_physical_device(cls, phy):
2647 for dev in cls.find():
2648 try:
2649 _phy = dev.get_physical_device()
2650 except cls.PhysicalDeviceError:
2651 continue
2653 if _phy == phy:
2654 yield dev
2656 @classmethod
2657 def find_by_tap_minor(cls, minor):
2658 phy = cls.PhysicalDevice(Tapdisk.major(), minor)
2659 return cls.find_by_physical_device(phy)
2661 @classmethod
2662 def find_by_tap(cls, tapdisk):
2663 return cls.find_by_tap_minor(tapdisk.minor)
2665 def has_tap(self):
2667 if not self.can_tap():
2668 return False
2670 phy = self.get_physical_device()
2671 if phy:
2672 return phy.is_tap()
2674 return False
2676 def is_bare_hvm(self):
2677 """File VDIs for bare HVM. These are directly accessible by Qemu."""
2678 try:
2679 self.get_physical_device()
2681 except self.PhysicalDeviceError as e:
2682 vdi_type = self.read("type")
2684 self.info("HVM VDI: type=%s" % vdi_type)
2686 if e.str is not None or vdi_type != 'file':
2687 raise
2689 return True
2691 return False
2693 def can_tap(self):
2694 return not self.is_bare_hvm()
2697class BlkbackEventHandler(UEventHandler):
2699 LOG_FACILITY = _syslog.LOG_DAEMON
2701 def __init__(self, ident=None, action=None):
2702 if not ident:
2703 ident = self.__class__.__name__
2705 self.ident = ident
2706 self._vbd = None
2707 self._tapdisk = None
2709 UEventHandler.__init__(self)
2711 @override
2712 def run(self) -> None:
2714 self.xs_path = self.getenv('XENBUS_PATH')
2715 openlog(str(self), 0, self.LOG_FACILITY)
2717 UEventHandler.run(self)
2719 @override
2720 def __str__(self) -> str:
2722 try:
2723 path = self.xs_path
2724 except:
2725 path = None
2727 try:
2728 action = self.get_action()
2729 except:
2730 action = None
2732 return "%s[%s](%s)" % (self.ident, action, path)
2734 def _log(self, prio, msg):
2735 syslog(prio, msg)
2736 util.SMlog("%s: " % self + msg)
2738 def info(self, msg):
2739 self._log(_syslog.LOG_INFO, msg)
2741 def warn(self, msg):
2742 self._log(_syslog.LOG_WARNING, "WARNING: " + msg)
2744 def error(self, msg):
2745 self._log(_syslog.LOG_ERR, "ERROR: " + msg)
2747 def get_vbd(self):
2748 if not self._vbd:
2749 self._vbd = Blkback.from_xs_path(self.xs_path)
2750 return self._vbd
2752 def get_tapdisk(self):
2753 if not self._tapdisk:
2754 minor = self.get_vbd().get_physical_device().minor
2755 self._tapdisk = Tapdisk.from_minor(minor)
2756 return self._tapdisk
2757 #
2758 # Events
2759 #
2761 def __add(self):
2762 vbd = self.get_vbd()
2763 # Manage blkback transitions
2764 # self._manage_vbd()
2766 vbd.create_physical_device()
2768 vbd.signal_hotplug()
2770 @retried(backoff=.5, limit=10)
2771 def add(self):
2772 try:
2773 self.__add()
2774 except Attribute.NoSuchAttribute as e:
2775 #
2776 # FIXME: KOBJ_ADD is racing backend.probe, which
2777 # registers device attributes. So poll a little.
2778 #
2779 self.warn("%s, still trying." % e)
2780 raise RetryLoop.TransientFailure(e)
2782 def __change(self):
2783 vbd = self.get_vbd()
2785 # 1. Pause or resume tapdisk (if there is one)
2787 if vbd.has_tap():
2788 pass
2789 #self._pause_update_tap()
2791 # 2. Signal Xapi.VBD.pause/resume completion
2793 self._signal_xapi()
2795 def change(self):
2796 vbd = self.get_vbd()
2798 # NB. Beware of spurious change events between shutdown
2799 # completion and device removal. Also, Xapi.VM.migrate will
2800 # hammer a couple extra shutdown-requests into the source VBD.
2802 while True:
2803 vbd.begin()
2805 if not vbd.exists() or \
2806 vbd.shutdown_done():
2807 break
2809 self.__change()
2811 if vbd.commit():
2812 return
2814 vbd.abort()
2815 self.info("spurious uevent, ignored.")
2817 def remove(self):
2818 vbd = self.get_vbd()
2820 vbd.signal_hotplug(False)
2822 ACTIONS = {'add': add,
2823 'change': change,
2824 'remove': remove}
2825 #
2826 # VDI.pause
2827 #
2829 def _tap_should_pause(self):
2830 """Enumerate all VBDs on our tapdisk. Returns true iff any was
2831 paused"""
2833 tapdisk = self.get_tapdisk()
2834 TapState = Tapdisk.PauseState
2836 PAUSED = 'P'
2837 RUNNING = 'R'
2838 PAUSED_SHUTDOWN = 'P,S'
2839 # NB. Shutdown/paused is special. We know it's not going
2840 # to restart again, so it's a RUNNING. Still better than
2841 # backtracking a removed device during Vbd.unplug completion.
2843 next = TapState.RUNNING
2844 vbds = {}
2846 for vbd in Blkback.find_by_tap(tapdisk):
2847 name = str(vbd)
2849 pausing = vbd.pause_requested()
2850 closing = vbd.shutdown_requested()
2851 running = vbd.running()
2853 if pausing:
2854 if closing and not running:
2855 vbds[name] = PAUSED_SHUTDOWN
2856 else:
2857 vbds[name] = PAUSED
2858 next = TapState.PAUSED
2860 else:
2861 vbds[name] = RUNNING
2863 self.info("tapdev%d (%s): %s -> %s"
2864 % (tapdisk.minor, tapdisk.pause_state(),
2865 vbds, next))
2867 return next == TapState.PAUSED
2869 def _pause_update_tap(self):
2870 vbd = self.get_vbd()
2872 if self._tap_should_pause():
2873 self._pause_tap()
2874 else:
2875 self._resume_tap()
2877 def _pause_tap(self):
2878 tapdisk = self.get_tapdisk()
2880 if not tapdisk.is_paused():
2881 self.info("pausing %s" % tapdisk)
2882 tapdisk.pause()
2884 def _resume_tap(self):
2885 tapdisk = self.get_tapdisk()
2887 # NB. Raw VDI snapshots. Refresh the physical path and
2888 # type while resuming.
2889 vbd = self.get_vbd()
2890 vdi_uuid = vbd.get_vdi_uuid()
2892 if tapdisk.is_paused():
2893 self.info("loading vdi uuid=%s" % vdi_uuid)
2894 vdi = VDI.from_cli(vdi_uuid)
2895 _type = vdi.get_tap_type()
2896 path = vdi.get_phy_path()
2897 self.info("resuming %s on %s:%s" % (tapdisk, _type, path))
2898 tapdisk.unpause(_type, path)
2899 #
2900 # VBD.pause/shutdown
2901 #
2903 def _manage_vbd(self):
2904 vbd = self.get_vbd()
2905 # NB. Hook into VBD state transitions.
2907 events = vbd.get_queue_events()
2909 mask = 0
2910 mask |= events.QUEUE_PAUSE_DONE # pause/unpause
2911 mask |= events.QUEUE_SHUTDOWN_DONE # shutdown
2912 # TODO: mask |= events.QUEUE_SHUTDOWN_REQUEST, for shutdown=force
2913 # TODO: mask |= events.QUEUE_RUNNING, for ionice updates etc
2915 events.set_mask(mask)
2916 self.info("wrote %s = %#02x" % (events.path, mask))
2918 def _signal_xapi(self):
2919 vbd = self.get_vbd()
2921 pausing = vbd.pause_requested()
2922 closing = vbd.shutdown_requested()
2923 running = vbd.running()
2925 handled = 0
2927 if pausing and not running:
2928 if 'pause-done' not in vbd:
2929 vbd.write('pause-done', '')
2930 handled += 1
2932 if not pausing:
2933 if 'pause-done' in vbd:
2934 vbd.rm('pause-done')
2935 handled += 1
2937 if closing and not running:
2938 if 'shutdown-done' not in vbd:
2939 vbd.write('shutdown-done', '')
2940 handled += 1
2942 if handled > 1:
2943 self.warn("handled %d events, " % handled +
2944 "pausing=%s closing=%s running=%s" % \
2945 (pausing, closing, running))
2947if __name__ == '__main__': 2947 ↛ 2949line 2947 didn't jump to line 2949, because the condition on line 2947 was never true
2949 import sys
2950 prog = os.path.basename(sys.argv[0])
2952 #
2953 # Simple CLI interface for manual operation
2954 #
2955 # tap.* level calls go down to local Tapdisk()s (by physical path)
2956 # vdi.* level calls run the plugin calls across host boundaries.
2957 #
2959 def usage(stream):
2960 print("usage: %s tap.{list|major}" % prog, file=stream)
2961 print(" %s tap.{launch|find|get|pause|" % prog + \
2962 "unpause|shutdown|stats} {[<tt>:]<path>} | [minor=]<int> | .. }", file=stream)
2963 print(" %s vbd.uevent" % prog, file=stream)
2965 try:
2966 cmd = sys.argv[1]
2967 except IndexError:
2968 usage(sys.stderr)
2969 sys.exit(1)
2971 try:
2972 _class, method = cmd.split('.')
2973 except:
2974 usage(sys.stderr)
2975 sys.exit(1)
2977 #
2978 # Local Tapdisks
2979 #
2981 if cmd == 'tap.major':
2983 print("%d" % Tapdisk.major())
2985 elif cmd == 'tap.launch':
2987 tapdisk = Tapdisk.launch_from_arg(sys.argv[2])
2988 print("Launched %s" % tapdisk, file=sys.stderr)
2990 elif _class == 'tap':
2992 attrs: Dict[str, Any] = {}
2993 for item in sys.argv[2:]:
2994 try:
2995 key, val = item.split('=')
2996 attrs[key] = val
2997 continue
2998 except ValueError:
2999 pass
3001 try:
3002 attrs['minor'] = int(item)
3003 continue
3004 except ValueError:
3005 pass
3007 try:
3008 arg = Tapdisk.Arg.parse(item)
3009 attrs['_type'] = arg.type
3010 attrs['path'] = arg.path
3011 continue
3012 except Tapdisk.Arg.InvalidArgument:
3013 pass
3015 attrs['path'] = item
3017 if cmd == 'tap.list':
3019 for tapdisk in Tapdisk.list( ** attrs):
3020 blktap = tapdisk.get_blktap()
3021 print(tapdisk, end=' ')
3022 print("%s: task=%s pool=%s" % \
3023 (blktap,
3024 blktap.get_task_pid(),
3025 blktap.get_pool_name()))
3027 elif cmd == 'tap.vbds':
3028 # Find all Blkback instances for a given tapdisk
3030 for tapdisk in Tapdisk.list( ** attrs):
3031 print("%s:" % tapdisk, end=' ')
3032 for vbd in Blkback.find_by_tap(tapdisk):
3033 print(vbd, end=' ')
3034 print()
3036 else:
3038 if not attrs:
3039 usage(sys.stderr)
3040 sys.exit(1)
3042 try:
3043 tapdisk = Tapdisk.get( ** attrs)
3044 except TypeError:
3045 usage(sys.stderr)
3046 sys.exit(1)
3048 if cmd == 'tap.shutdown':
3049 # Shutdown a running tapdisk, or raise
3050 tapdisk.shutdown()
3051 print("Shut down %s" % tapdisk, file=sys.stderr)
3053 elif cmd == 'tap.pause':
3054 # Pause an unpaused tapdisk, or raise
3055 tapdisk.pause()
3056 print("Paused %s" % tapdisk, file=sys.stderr)
3058 elif cmd == 'tap.unpause':
3059 # Unpause a paused tapdisk, or raise
3060 tapdisk.unpause()
3061 print("Unpaused %s" % tapdisk, file=sys.stderr)
3063 elif cmd == 'tap.stats':
3064 # Gather tapdisk status
3065 stats = tapdisk.stats()
3066 print("%s:" % tapdisk)
3067 print(json.dumps(stats, indent=True))
3069 else:
3070 usage(sys.stderr)
3071 sys.exit(1)
3073 elif cmd == 'vbd.uevent':
3075 hnd = BlkbackEventHandler(cmd)
3077 if not sys.stdin.isatty():
3078 try:
3079 hnd.run()
3080 except Exception as e:
3081 hnd.error("Unhandled Exception: %s" % e)
3083 import traceback
3084 _type, value, tb = sys.exc_info()
3085 trace = traceback.format_exception(_type, value, tb)
3086 for entry in trace:
3087 for line in entry.rstrip().split('\n'):
3088 util.SMlog(line)
3089 else:
3090 hnd.run()
3092 elif cmd == 'vbd.list':
3094 for vbd in Blkback.find():
3095 print(vbd, \
3096 "physical-device=%s" % vbd.get_physical_device(), \
3097 "pause=%s" % vbd.pause_requested())
3099 else:
3100 usage(sys.stderr)
3101 sys.exit(1)