Coverage for libs/sm/blktap2.py: 55%
1656 statements
« prev ^ index » next coverage.py v7.3.2, created at 2026-07-10 07:59 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2026-07-10 07:59 +0000
1# Copyright (C) Citrix Systems Inc.
2#
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU Lesser General Public License as published
5# by the Free Software Foundation; version 2.1 only.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU Lesser General Public License for more details.
11#
12# You should have received a copy of the GNU Lesser General Public License
13# along with this program; if not, write to the Free Software Foundation, Inc.,
14# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15#
16# blktap2: blktap/tapdisk management layer
17#
18import grp
19import os
20import re
21import stat
22import time
23import copy
24import xmlrpc.client
25import http.client
26import errno
27import signal
28import subprocess
29import syslog as _syslog
30import glob
31import json
32from syslog import openlog, syslog
33from stat import * # S_ISBLK(), ...
35import XenAPI # pylint: disable=import-error
36from sm.core.lock import Lock
37from sm.core import util
38from sm.core import xs_errors
39from sm.core import scsiutil
40from sm import nfs
41from sm import resetvdis
42from sm import vhdutil
43from sm import lvhdutil
44from sm import VDI as sm
46# For RRDD Plugin Registration
47from xmlrpc.client import ServerProxy, Transport
48from socket import socket, AF_UNIX, SOCK_STREAM
50PLUGIN_TAP_PAUSE = "tapdisk-pause"
52SOCKPATH = "/var/xapi/xcp-rrdd"
54NUM_PAGES_PER_RING = 32 * 11
55MAX_FULL_RINGS = 8
57ENABLE_MULTIPLE_ATTACH = "/etc/xensource/allow_multiple_vdi_attach"
58NO_MULTIPLE_ATTACH = not (os.path.exists(ENABLE_MULTIPLE_ATTACH))
61def locking(excType, override=True):
62 def locking2(op):
63 def wrapper(self, *args):
64 self.lock.acquire()
65 try:
66 try:
67 ret = op(self, * args)
68 except (util.CommandException, util.SMException, XenAPI.Failure) as e: 68 ↛ 78line 68 didn't jump to line 78
69 util.logException("BLKTAP2:%s" % op)
70 msg = str(e)
71 if isinstance(e, util.CommandException): 71 ↛ 72line 71 didn't jump to line 72, because the condition on line 71 was never true
72 msg = "Command %s failed (%s): %s" % \
73 (e.cmd, e.code, e.reason)
74 if override: 74 ↛ 77line 74 didn't jump to line 77, because the condition on line 74 was never false
75 raise xs_errors.XenError(excType, opterr=msg)
76 else:
77 raise
78 except:
79 util.logException("BLKTAP2:%s" % op)
80 raise
81 finally:
82 self.lock.release()
83 return ret
84 return wrapper
85 return locking2
88class RetryLoop(object):
90 def __init__(self, backoff, limit):
91 self.backoff = backoff
92 self.limit = limit
94 def __call__(self, f):
96 def loop(*__t, **__d):
97 attempt = 0
99 while True:
100 attempt += 1
102 try:
103 return f( * __t, ** __d)
105 except self.TransientFailure as e:
106 e = e.exception
108 if attempt >= self.limit: 108 ↛ 109line 108 didn't jump to line 109, because the condition on line 108 was never true
109 raise e
111 time.sleep(self.backoff)
113 return loop
115 class TransientFailure(Exception):
116 def __init__(self, exception):
117 self.exception = exception
120def retried(**args):
121 return RetryLoop( ** args)
124class TapCtl(object):
125 """Tapdisk IPC utility calls."""
127 PATH = "/usr/sbin/tap-ctl"
129 def __init__(self, cmd, p):
130 self.cmd = cmd
131 self._p = p
132 self.stdout = p.stdout
134 class CommandFailure(Exception):
135 """TapCtl cmd failure."""
137 def __init__(self, cmd, **info):
138 self.cmd = cmd
139 self.info = info
141 def __str__(self):
142 items = self.info.items()
143 info = ", ".join("%s=%s" % item
144 for item in items)
145 return "%s failed: %s" % (self.cmd, info)
147 # Trying to get a non-existent attribute throws an AttributeError
148 # exception
149 def __getattr__(self, key):
150 if key in self.info: 150 ↛ 152line 150 didn't jump to line 152, because the condition on line 150 was never false
151 return self.info[key]
152 return object.__getattribute__(self, key)
154 @property
155 def has_status(self):
156 return 'status' in self.info
158 @property
159 def has_signal(self):
160 return 'signal' in self.info
162 # Retrieves the error code returned by the command. If the error code
163 # was not supplied at object-construction time, zero is returned.
164 def get_error_code(self):
165 key = 'status'
166 if key in self.info: 166 ↛ 169line 166 didn't jump to line 169, because the condition on line 166 was never false
167 return self.info[key]
168 else:
169 return 0
171 @classmethod
172 def __mkcmd_real(cls, args):
173 return [cls.PATH] + [str(x) for x in args]
175 __next_mkcmd = __mkcmd_real
177 @classmethod
178 def _mkcmd(cls, args):
180 __next_mkcmd = cls.__next_mkcmd
181 cls.__next_mkcmd = cls.__mkcmd_real
183 return __next_mkcmd(args)
185 @classmethod
186 def _call(cls, args, quiet=False, input=None, text_mode=True):
187 """
188 Spawn a tap-ctl process. Return a TapCtl invocation.
189 Raises a TapCtl.CommandFailure if subprocess creation failed.
190 """
191 cmd = cls._mkcmd(args)
193 if not quiet:
194 util.SMlog(cmd)
195 try:
196 p = subprocess.Popen(cmd,
197 stdin=subprocess.PIPE,
198 stdout=subprocess.PIPE,
199 stderr=subprocess.PIPE,
200 close_fds=True,
201 universal_newlines=text_mode)
202 if input:
203 p.stdin.write(input)
204 p.stdin.close()
205 except OSError as e:
206 raise cls.CommandFailure(cmd, errno=e.errno)
208 return cls(cmd, p)
210 def _errmsg(self):
211 output = map(str.rstrip, self._p.stderr)
212 return "; ".join(output)
214 def _wait(self, quiet=False):
215 """
216 Reap the child tap-ctl process of this invocation.
217 Raises a TapCtl.CommandFailure on non-zero exit status.
218 """
219 status = self._p.wait()
220 if not quiet:
221 util.SMlog(" = %d" % status)
223 if status == 0:
224 return
226 info = {'errmsg': self._errmsg(),
227 'pid': self._p.pid}
229 if status < 0:
230 info['signal'] = -status
231 else:
232 info['status'] = status
234 raise self.CommandFailure(self.cmd, ** info)
236 @classmethod
237 def _pread(cls, args, quiet=False, input=None, text_mode=True):
238 """
239 Spawn a tap-ctl invocation and read a single line.
240 """
241 tapctl = cls._call(args=args, quiet=quiet, input=input,
242 text_mode=text_mode)
244 output = tapctl.stdout.readline().rstrip()
246 tapctl._wait(quiet)
247 return output
249 @staticmethod
250 def _maybe(opt, parm):
251 if parm is not None:
252 return [opt, parm]
253 return []
255 @classmethod
256 def __list(cls, minor=None, pid=None, _type=None, path=None):
257 args = ["list"]
258 args += cls._maybe("-m", minor)
259 args += cls._maybe("-p", pid)
260 args += cls._maybe("-t", _type)
261 args += cls._maybe("-f", path)
263 tapctl = cls._call(args, True)
265 for stdout_line in tapctl.stdout:
266 # FIXME: tap-ctl writes error messages to stdout and
267 # confuses this parser
268 if stdout_line == "blktap kernel module not installed\n": 268 ↛ 271line 268 didn't jump to line 271, because the condition on line 268 was never true
269 # This isn't pretty but (a) neither is confusing stdout/stderr
270 # and at least causes the error to describe the fix
271 raise Exception("blktap kernel module not installed: try 'modprobe blktap'")
272 row = {}
274 for field in stdout_line.rstrip().split(' ', 3):
275 bits = field.split('=')
276 if len(bits) == 2: 276 ↛ 288line 276 didn't jump to line 288, because the condition on line 276 was never false
277 key, val = field.split('=')
279 if key in ('pid', 'minor'):
280 row[key] = int(val, 10)
282 elif key in ('state'):
283 row[key] = int(val, 0x10)
285 else:
286 row[key] = val
287 else:
288 util.SMlog("Ignoring unexpected tap-ctl output: %s" % repr(field))
289 yield row
291 tapctl._wait(True)
293 @classmethod
294 @retried(backoff=.5, limit=10)
295 def list(cls, **args):
297 # FIXME. We typically get an EPROTO when uevents interleave
298 # with SM ops and a tapdisk shuts down under our feet. Should
299 # be fixed in SM.
301 try:
302 return list(cls.__list( ** args))
304 except cls.CommandFailure as e:
305 transient = [errno.EPROTO, errno.ENOENT]
306 if e.has_status and e.status in transient:
307 raise RetryLoop.TransientFailure(e)
308 raise
310 @classmethod
311 def allocate(cls, devpath=None):
312 args = ["allocate"]
313 args += cls._maybe("-d", devpath)
314 return cls._pread(args)
316 @classmethod
317 def free(cls, minor):
318 args = ["free", "-m", minor]
319 cls._pread(args)
321 @classmethod
322 @retried(backoff=.5, limit=10)
323 def spawn(cls):
324 args = ["spawn"]
325 try:
326 pid = cls._pread(args)
327 return int(pid)
328 except cls.CommandFailure as ce:
329 # intermittent failures to spawn. CA-292268
330 if ce.status == 1:
331 raise RetryLoop.TransientFailure(ce)
332 raise
334 @classmethod
335 def attach(cls, pid, minor):
336 args = ["attach", "-p", pid, "-m", minor]
337 cls._pread(args)
339 @classmethod
340 def detach(cls, pid, minor):
341 args = ["detach", "-p", pid, "-m", minor]
342 cls._pread(args)
344 @classmethod
345 def _load_key(cls, key_hash, vdi_uuid):
346 from sm import plugins
348 return plugins.load_key(key_hash, vdi_uuid)
350 @classmethod
351 def open(cls, pid, minor, _type, _file, options):
352 params = Tapdisk.Arg(_type, _file)
353 args = ["open", "-p", pid, "-m", minor, '-a', str(params)]
354 text_mode = True
355 input = None
356 if options.get("rdonly"):
357 args.append('-R')
358 if options.get("lcache"):
359 args.append("-r")
360 if options.get("existing_prt") is not None:
361 args.append("-e")
362 args.append(str(options["existing_prt"]))
363 if options.get("secondary"):
364 args.append("-2")
365 args.append(options["secondary"])
366 if options.get("standby"):
367 args.append("-s")
368 if options.get("timeout"):
369 args.append("-t")
370 args.append(str(options["timeout"]))
371 if not options.get("o_direct", True):
372 args.append("-D")
373 if options.get('cbtlog'):
374 args.extend(['-C', options['cbtlog']])
375 if options.get('key_hash'):
376 key_hash = options['key_hash']
377 vdi_uuid = options['vdi_uuid']
378 key = cls._load_key(key_hash, vdi_uuid)
380 if not key:
381 raise util.SMException("No key found with key hash {}".format(key_hash))
382 input = key
383 text_mode = False
384 args.append('-E')
386 cls._pread(args=args, input=input, text_mode=text_mode)
388 @classmethod
389 def close(cls, pid, minor, force=False):
390 args = ["close", "-p", pid, "-m", minor, "-t", "120"]
391 if force:
392 args += ["-f"]
393 cls._pread(args)
395 @classmethod
396 def pause(cls, pid, minor):
397 args = ["pause", "-p", pid, "-m", minor]
398 cls._pread(args)
400 @classmethod
401 def unpause(cls, pid, minor, _type=None, _file=None, mirror=None,
402 cbtlog=None):
403 args = ["unpause", "-p", pid, "-m", minor]
404 if mirror:
405 args.extend(["-2", mirror])
406 if _type and _file:
407 params = Tapdisk.Arg(_type, _file)
408 args += ["-a", str(params)]
409 if cbtlog:
410 args.extend(["-c", cbtlog])
411 cls._pread(args)
413 @classmethod
414 def shutdown(cls, pid):
415 # TODO: This should be a real tap-ctl command
416 os.kill(pid, signal.SIGTERM)
417 os.waitpid(pid, 0)
419 @classmethod
420 def stats(cls, pid, minor):
421 args = ["stats", "-p", pid, "-m", minor]
422 return cls._pread(args, quiet=True)
424 @classmethod
425 def major(cls):
426 args = ["major"]
427 major = cls._pread(args)
428 return int(major)
431class TapdiskExists(Exception):
432 """Tapdisk already running."""
434 def __init__(self, tapdisk):
435 self.tapdisk = tapdisk
437 def __str__(self):
438 return "%s already running" % self.tapdisk
441class TapdiskNotRunning(Exception):
442 """No such Tapdisk."""
444 def __init__(self, **attrs):
445 self.attrs = attrs
447 def __str__(self):
448 items = iter(self.attrs.items())
449 attrs = ", ".join("%s=%s" % attr
450 for attr in items)
451 return "No such Tapdisk(%s)" % attrs
454class TapdiskNotUnique(Exception):
455 """More than one tapdisk on one path."""
457 def __init__(self, tapdisks):
458 self.tapdisks = tapdisks
460 def __str__(self):
461 tapdisks = map(str, self.tapdisks)
462 return "Found multiple tapdisks: %s" % tapdisks
465class TapdiskFailed(Exception):
466 """Tapdisk launch failure."""
468 def __init__(self, arg, err):
469 self.arg = arg
470 self.err = err
472 def __str__(self):
473 return "Tapdisk(%s): %s" % (self.arg, self.err)
475 def get_error(self):
476 return self.err
479class TapdiskInvalidState(Exception):
480 """Tapdisk pause/unpause failure"""
482 def __init__(self, tapdisk):
483 self.tapdisk = tapdisk
485 def __str__(self):
486 return str(self.tapdisk)
489def mkdirs(path, mode=0o777):
490 if not os.path.exists(path):
491 parent, subdir = os.path.split(path)
492 assert parent != path
493 try:
494 if parent:
495 mkdirs(parent, mode)
496 if subdir:
497 os.mkdir(path, mode)
498 except OSError as e:
499 if e.errno != errno.EEXIST:
500 raise
503class KObject(object):
505 SYSFS_CLASSTYPE = None
507 def sysfs_devname(self):
508 raise NotImplementedError("sysfs_devname is undefined")
511class Attribute(object):
513 SYSFS_NODENAME = None
515 def __init__(self, path):
516 self.path = path
518 @classmethod
519 def from_kobject(cls, kobj):
520 path = "%s/%s" % (kobj.sysfs_path(), cls.SYSFS_NODENAME)
521 return cls(path)
523 class NoSuchAttribute(Exception):
524 def __init__(self, name):
525 self.name = name
527 def __str__(self):
528 return "No such attribute: %s" % self.name
530 def _open(self, mode='r'):
531 try:
532 return open(self.path, mode)
533 except IOError as e:
534 if e.errno == errno.ENOENT:
535 raise self.NoSuchAttribute(self)
536 raise
538 def readline(self):
539 f = self._open('r')
540 s = f.readline().rstrip()
541 f.close()
542 return s
544 def writeline(self, val):
545 f = self._open('w')
546 f.write(val)
547 f.close()
550class ClassDevice(KObject):
552 @classmethod
553 def sysfs_class_path(cls):
554 return "/sys/class/%s" % cls.SYSFS_CLASSTYPE
556 def sysfs_path(self):
557 return "%s/%s" % (self.sysfs_class_path(),
558 self.sysfs_devname())
561class Blktap(ClassDevice):
563 DEV_BASEDIR = '/dev/xen/blktap-2'
564 TAP_MINOR_BASE = '/run/blktap-control/tapdisk'
566 SYSFS_CLASSTYPE = "blktap2"
568 def __init__(self, minor):
569 self.minor = minor
570 self._task = None
572 @classmethod
573 def allocate(cls):
574 # FIXME. Should rather go into init.
575 mkdirs(cls.DEV_BASEDIR)
577 devname = TapCtl.allocate()
578 minor = Tapdisk._parse_minor(devname)
579 return cls(minor)
581 def free(self):
582 TapCtl.free(self.minor)
584 def __str__(self):
585 return "%s(minor=%d)" % (self.__class__.__name__, self.minor)
587 def sysfs_devname(self):
588 return "blktap!blktap%d" % self.minor
590 class Task(Attribute):
591 SYSFS_NODENAME = "task"
593 def get_task_attr(self):
594 if not self._task:
595 self._task = self.Task.from_kobject(self)
596 return self._task
598 def get_task_pid(self):
599 pid = self.get_task_attr().readline()
600 try:
601 return int(pid)
602 except ValueError:
603 return None
605 def find_tapdisk(self):
606 pid = self.get_task_pid()
607 if pid is None:
608 return None
610 return Tapdisk.find(pid=pid, minor=self.minor)
612 def get_tapdisk(self):
613 tapdisk = self.find_tapdisk()
614 if not tapdisk:
615 raise TapdiskNotRunning(minor=self.minor)
616 return tapdisk
619class Tapdisk(object):
621 TYPES = ['aio', 'vhd']
623 def __init__(self, pid, minor, _type, path, state):
624 self.pid = pid
625 self.minor = minor
626 self.type = _type
627 self.path = path
628 self.state = state
629 self._dirty = False
630 self._blktap = None
632 def __str__(self):
633 state = self.pause_state()
634 return "Tapdisk(%s, pid=%d, minor=%s, state=%s)" % \
635 (self.get_arg(), self.pid, self.minor, state)
637 @classmethod
638 def list(cls, **args):
640 for row in TapCtl.list( ** args):
642 args = {'pid': None,
643 'minor': None,
644 'state': None,
645 '_type': None,
646 'path': None}
648 for key, val in row.items():
649 if key in args:
650 args[key] = val
652 if 'args' in row: 652 ↛ 657line 652 didn't jump to line 657, because the condition on line 652 was never false
653 image = Tapdisk.Arg.parse(row['args'])
654 args['_type'] = image.type
655 args['path'] = image.path
657 if None in args.values(): 657 ↛ 658line 657 didn't jump to line 658, because the condition on line 657 was never true
658 continue
660 yield Tapdisk( ** args)
662 @classmethod
663 def find(cls, **args):
665 found = list(cls.list( ** args))
667 if len(found) > 1: 667 ↛ 668line 667 didn't jump to line 668, because the condition on line 667 was never true
668 raise TapdiskNotUnique(found)
670 if found: 670 ↛ 671line 670 didn't jump to line 671, because the condition on line 670 was never true
671 return found[0]
673 return None
675 @classmethod
676 def find_by_path(cls, path):
677 return cls.find(path=path)
679 @classmethod
680 def find_by_minor(cls, minor):
681 return cls.find(minor=minor)
683 @classmethod
684 def get(cls, **attrs):
686 tapdisk = cls.find( ** attrs)
688 if not tapdisk:
689 raise TapdiskNotRunning( ** attrs)
691 return tapdisk
693 @classmethod
694 def from_path(cls, path):
695 return cls.get(path=path)
697 @classmethod
698 def from_minor(cls, minor):
699 return cls.get(minor=minor)
701 @classmethod
702 def __from_blktap(cls, blktap):
703 tapdisk = cls.from_minor(minor=blktap.minor)
704 tapdisk._blktap = blktap
705 return tapdisk
707 def get_blktap(self):
708 if not self._blktap:
709 self._blktap = Blktap(self.minor)
710 return self._blktap
712 class Arg:
714 def __init__(self, _type, path):
715 self.type = _type
716 self.path = path
718 def __str__(self):
719 return "%s:%s" % (self.type, self.path)
721 @classmethod
722 def parse(cls, arg):
724 try:
725 _type, path = arg.split(":", 1)
726 except ValueError:
727 raise cls.InvalidArgument(arg)
729 if _type not in Tapdisk.TYPES: 729 ↛ 730line 729 didn't jump to line 730, because the condition on line 729 was never true
730 raise cls.InvalidType(_type)
732 return cls(_type, path)
734 class InvalidType(Exception):
735 def __init__(self, _type):
736 self.type = _type
738 def __str__(self):
739 return "Not a Tapdisk type: %s" % self.type
741 class InvalidArgument(Exception):
742 def __init__(self, arg):
743 self.arg = arg
745 def __str__(self):
746 return "Not a Tapdisk image: %s" % self.arg
748 def get_arg(self):
749 return self.Arg(self.type, self.path)
751 def get_devpath(self):
752 return "%s/tapdev%d" % (Blktap.DEV_BASEDIR, self.minor)
754 @classmethod
755 def launch_from_arg(cls, arg):
756 arg = cls.Arg.parse(arg)
757 return cls.launch(arg.path, arg.type, False)
759 @staticmethod
760 def cgclassify(pid):
762 # We dont provide any <controllers>:<path>
763 # so cgclassify uses /etc/cgrules.conf which
764 # we have configured in the spec file.
765 cmd = ["cgclassify", str(pid)]
766 try:
767 util.pread2(cmd)
768 except util.CommandException as e:
769 util.logException(e)
771 @classmethod
772 def launch_on_tap(cls, blktap, path, _type, options):
774 tapdisk = cls.find_by_path(path)
775 if tapdisk: 775 ↛ 776line 775 didn't jump to line 776, because the condition on line 775 was never true
776 raise TapdiskExists(tapdisk)
778 minor = blktap.minor
779 try:
780 pid = TapCtl.spawn()
781 cls.cgclassify(pid)
782 try:
783 TapCtl.attach(pid, minor)
785 try:
786 TapCtl.open(pid, minor, _type, path, options)
787 try:
788 return cls.__from_blktap(blktap)
789 except:
790 TapCtl.close(pid, minor)
791 raise
793 except:
794 TapCtl.detach(pid, minor)
795 raise
797 except:
798 try:
799 TapCtl.shutdown(pid)
800 except:
801 # Best effort to shutdown
802 pass
803 raise
805 except TapCtl.CommandFailure as ctl:
806 util.logException(ctl)
807 if ((path.startswith('/dev/xapi/cd/') or path.startswith('/dev/sr')) and 807 ↛ 811line 807 didn't jump to line 811, because the condition on line 807 was never false
808 ctl.has_status and ctl.get_error_code() == 123): # ENOMEDIUM (No medium found)
809 raise xs_errors.XenError('TapdiskDriveEmpty')
810 else:
811 raise TapdiskFailed(cls.Arg(_type, path), ctl)
813 @classmethod
814 def launch(cls, path, _type, rdonly):
815 blktap = Blktap.allocate()
816 try:
817 return cls.launch_on_tap(blktap, path, _type, {"rdonly": rdonly})
818 except:
819 blktap.free()
820 raise
822 def shutdown(self, force=False):
824 TapCtl.close(self.pid, self.minor, force)
826 TapCtl.detach(self.pid, self.minor)
828 self.get_blktap().free()
830 def pause(self):
832 if not self.is_running():
833 raise TapdiskInvalidState(self)
835 TapCtl.pause(self.pid, self.minor)
837 self._set_dirty()
839 def unpause(self, _type=None, path=None, mirror=None, cbtlog=None):
841 if not self.is_paused():
842 raise TapdiskInvalidState(self)
844 # FIXME: should the arguments be optional?
845 if _type is None:
846 _type = self.type
847 if path is None:
848 path = self.path
850 TapCtl.unpause(self.pid, self.minor, _type, path, mirror=mirror,
851 cbtlog=cbtlog)
853 self._set_dirty()
855 def stats(self):
856 return json.loads(TapCtl.stats(self.pid, self.minor))
857 #
858 # NB. dirty/refresh: reload attributes on next access
859 #
861 def _set_dirty(self):
862 self._dirty = True
864 def _refresh(self, __get):
865 t = self.from_minor(__get('minor'))
866 self.__init__(t.pid, t.minor, t.type, t.path, t.state)
868 def __getattribute__(self, name):
869 def __get(name):
870 # NB. avoid(rec(ursion)
871 return object.__getattribute__(self, name)
873 if __get('_dirty') and \ 873 ↛ 875line 873 didn't jump to line 875, because the condition on line 873 was never true
874 name in ['minor', 'type', 'path', 'state']:
875 self._refresh(__get)
876 self._dirty = False
878 return __get(name)
880 class PauseState:
881 RUNNING = 'R'
882 PAUSING = 'r'
883 PAUSED = 'P'
885 class Flags:
886 DEAD = 0x0001
887 CLOSED = 0x0002
888 QUIESCE_REQUESTED = 0x0004
889 QUIESCED = 0x0008
890 PAUSE_REQUESTED = 0x0010
891 PAUSED = 0x0020
892 SHUTDOWN_REQUESTED = 0x0040
893 LOCKING = 0x0080
894 RETRY_NEEDED = 0x0100
895 LOG_DROPPED = 0x0200
897 PAUSE_MASK = PAUSE_REQUESTED | PAUSED
899 def is_paused(self):
900 return not not (self.state & self.Flags.PAUSED)
902 def is_running(self):
903 return not (self.state & self.Flags.PAUSE_MASK)
905 def pause_state(self):
906 if self.state & self.Flags.PAUSED: 906 ↛ 907line 906 didn't jump to line 907, because the condition on line 906 was never true
907 return self.PauseState.PAUSED
909 if self.state & self.Flags.PAUSE_REQUESTED: 909 ↛ 910line 909 didn't jump to line 910, because the condition on line 909 was never true
910 return self.PauseState.PAUSING
912 return self.PauseState.RUNNING
914 @staticmethod
915 def _parse_minor(devpath):
916 regex = r'%s/tapdisk-(\d+)$' % Blktap.TAP_MINOR_BASE
917 pattern = re.compile(regex)
918 groups = pattern.search(devpath)
919 if not groups:
920 raise Exception("malformed tap device: '%s' (%s) " % (devpath, regex))
922 minor = int(groups.group(1))
923 return minor
925 _major = None
927 @classmethod
928 def major(cls):
929 if cls._major:
930 return cls._major
932 devices = open("/proc/devices")
933 for line in devices:
935 row = line.rstrip().split(' ')
936 if len(row) != 2:
937 continue
939 major, name = row
940 if name != 'tapdev':
941 continue
943 cls._major = int(major)
944 break
946 devices.close()
947 return cls._major
950class VDI(object):
951 """SR.vdi driver decorator for blktap2"""
953 CONF_KEY_ALLOW_CACHING = "vdi_allow_caching"
954 CONF_KEY_MODE_ON_BOOT = "vdi_on_boot"
955 CONF_KEY_CACHE_SR = "local_cache_sr"
956 CONF_KEY_O_DIRECT = "o_direct"
957 LOCK_CACHE_SETUP = "cachesetup"
959 ATTACH_DETACH_RETRY_SECS = 120
961 def __init__(self, uuid, target, driver_info):
962 self.target = self.TargetDriver(target, driver_info)
963 self._vdi_uuid = uuid
964 self._session = target.session
965 self.xenstore_data = scsiutil.update_XS_SCSIdata(uuid, scsiutil.gen_synthetic_page_data(uuid))
966 self.__o_direct = None
967 self.__o_direct_reason = None
968 self.lock = Lock("vdi", uuid)
969 self.tap = None
971 def get_o_direct_capability(self, options):
972 """Returns True/False based on licensing and caching_params"""
973 if self.__o_direct is not None: 973 ↛ 974line 973 didn't jump to line 974, because the condition on line 973 was never true
974 return self.__o_direct, self.__o_direct_reason
976 if util.read_caching_is_restricted(self._session): 976 ↛ 977line 976 didn't jump to line 977, because the condition on line 976 was never true
977 self.__o_direct = True
978 self.__o_direct_reason = "LICENSE_RESTRICTION"
979 elif not ((self.target.vdi.sr.handles("nfs") or self.target.vdi.sr.handles("ext") or self.target.vdi.sr.handles("smb"))): 979 ↛ 982line 979 didn't jump to line 982, because the condition on line 979 was never false
980 self.__o_direct = True
981 self.__o_direct_reason = "SR_NOT_SUPPORTED"
982 elif options.get("rdonly") and not self.target.vdi.parent:
983 self.__o_direct = True
984 self.__o_direct_reason = "RO_WITH_NO_PARENT"
985 elif options.get(self.CONF_KEY_O_DIRECT):
986 self.__o_direct = True
987 self.__o_direct_reason = "SR_OVERRIDE"
989 if self.__o_direct is None: 989 ↛ 990line 989 didn't jump to line 990, because the condition on line 989 was never true
990 self.__o_direct = False
991 self.__o_direct_reason = ""
993 return self.__o_direct, self.__o_direct_reason
995 @classmethod
996 def from_cli(cls, uuid):
997 session = XenAPI.xapi_local()
998 session.xenapi.login_with_password('root', '', '', 'SM')
1000 target = sm.VDI.from_uuid(session, uuid)
1001 driver_info = target.sr.srcmd.driver_info
1003 session.xenapi.session.logout()
1005 return cls(uuid, target, driver_info)
1007 @staticmethod
1008 def _tap_type(vdi_type):
1009 """Map a VDI type (e.g. 'raw') to a tapdisk driver type (e.g. 'aio')"""
1010 return {
1011 'raw': 'aio',
1012 'vhd': 'vhd',
1013 'iso': 'aio', # for ISO SR
1014 'aio': 'aio', # for LVHD
1015 'file': 'aio',
1016 'phy': 'aio'
1017 }[vdi_type]
1019 def get_tap_type(self):
1020 vdi_type = self.target.get_vdi_type()
1021 return VDI._tap_type(vdi_type)
1023 def get_phy_path(self):
1024 return self.target.get_vdi_path()
1026 class UnexpectedVDIType(Exception):
1028 def __init__(self, vdi_type, target):
1029 self.vdi_type = vdi_type
1030 self.target = target
1032 def __str__(self):
1033 return \
1034 "Target %s has unexpected VDI type '%s'" % \
1035 (type(self.target), self.vdi_type)
1037 VDI_PLUG_TYPE = {'phy': 'phy', # for NETAPP
1038 'raw': 'phy',
1039 'aio': 'tap', # for LVHD raw nodes
1040 'iso': 'tap', # for ISOSR
1041 'file': 'tap',
1042 'vhd': 'tap'}
1044 def tap_wanted(self):
1045 # 1. Let the target vdi_type decide
1047 vdi_type = self.target.get_vdi_type()
1049 try:
1050 plug_type = self.VDI_PLUG_TYPE[vdi_type]
1051 except KeyError:
1052 raise self.UnexpectedVDIType(vdi_type,
1053 self.target.vdi)
1055 if plug_type == 'tap': 1055 ↛ 1056line 1055 didn't jump to line 1056, because the condition on line 1055 was never true
1056 return True
1057 elif self.target.vdi.sr.handles('udev'): 1057 ↛ 1063line 1057 didn't jump to line 1063, because the condition on line 1057 was never false
1058 return True
1059 # 2. Otherwise, there may be more reasons
1060 #
1061 # .. TBD
1063 return False
1065 class TargetDriver:
1066 """Safe target driver access."""
1067 # NB. *Must* test caps for optional calls. Some targets
1068 # actually implement some slots, but do not enable them. Just
1069 # try/except would risk breaking compatibility.
1071 def __init__(self, vdi, driver_info):
1072 self.vdi = vdi
1073 self._caps = driver_info['capabilities']
1075 def has_cap(self, cap):
1076 """Determine if target has given capability"""
1077 return cap in self._caps
1079 def attach(self, sr_uuid, vdi_uuid):
1080 #assert self.has_cap("VDI_ATTACH")
1081 return self.vdi.attach(sr_uuid, vdi_uuid)
1083 def detach(self, sr_uuid, vdi_uuid):
1084 #assert self.has_cap("VDI_DETACH")
1085 self.vdi.detach(sr_uuid, vdi_uuid)
1087 def activate(self, sr_uuid, vdi_uuid):
1088 if self.has_cap("VDI_ACTIVATE"):
1089 return self.vdi.activate(sr_uuid, vdi_uuid)
1091 def deactivate(self, sr_uuid, vdi_uuid):
1092 if self.has_cap("VDI_DEACTIVATE"):
1093 self.vdi.deactivate(sr_uuid, vdi_uuid)
1094 #def resize(self, sr_uuid, vdi_uuid, size):
1095 # return self.vdi.resize(sr_uuid, vdi_uuid, size)
1097 def get_vdi_type(self):
1098 _type = self.vdi.vdi_type
1099 if not _type:
1100 _type = self.vdi.sr.sr_vditype
1101 if not _type:
1102 raise VDI.UnexpectedVDIType(_type, self.vdi)
1103 return _type
1105 def get_vdi_path(self):
1106 return self.vdi.path
1108 class Link(object):
1109 """Relink a node under a common name"""
1110 # NB. We have to provide the device node path during
1111 # VDI.attach, but currently do not allocate the tapdisk minor
1112 # before VDI.activate. Therefore those link steps where we
1113 # relink existing devices under deterministic path names.
1115 BASEDIR = None
1117 def _mklink(self, target):
1118 raise NotImplementedError("_mklink is not defined")
1120 def _equals(self, target):
1121 raise NotImplementedError("_equals is not defined")
1123 def __init__(self, path):
1124 self._path = path
1126 @classmethod
1127 def from_name(cls, name):
1128 path = "%s/%s" % (cls.BASEDIR, name)
1129 return cls(path)
1131 @classmethod
1132 def from_uuid(cls, sr_uuid, vdi_uuid):
1133 name = "%s/%s" % (sr_uuid, vdi_uuid)
1134 return cls.from_name(name)
1136 def path(self):
1137 return self._path
1139 def stat(self):
1140 return os.stat(self.path())
1142 def mklink(self, target):
1144 path = self.path()
1145 util.SMlog("%s -> %s" % (self, target))
1147 mkdirs(os.path.dirname(path))
1148 try:
1149 self._mklink(target)
1150 except OSError as e:
1151 # We do unlink during teardown, but have to stay
1152 # idempotent. However, a *wrong* target should never
1153 # be seen.
1154 if e.errno != errno.EEXIST:
1155 raise
1156 assert self._equals(target), "'%s' not equal to '%s'" % (path, target)
1158 def unlink(self):
1159 try:
1160 os.unlink(self.path())
1161 except OSError as e:
1162 if e.errno != errno.ENOENT:
1163 raise
1165 def __str__(self):
1166 path = self.path()
1167 return "%s(%s)" % (self.__class__.__name__, path)
1169 class SymLink(Link):
1170 """Symlink some file to a common name"""
1172 def readlink(self):
1173 return os.readlink(self.path())
1175 def symlink(self):
1176 return self.path()
1178 def _mklink(self, target):
1179 os.symlink(target, self.path())
1181 def _equals(self, target):
1182 return self.readlink() == target
1185 class PhyLink(SymLink):
1186 BASEDIR = "/dev/sm/phy"
1189 class NBDLink(SymLink):
1191 BASEDIR = "/run/blktap-control/nbd"
1193 def read_minor_from_path(self):
1194 regex = r'.*/nbd\d+.(\d+)$'
1195 match = re.search(regex, os.path.realpath(self._path))
1196 if not match:
1197 raise Exception("Failed to parse minor from %s" % self._path)
1199 return int(match.group(1))
1202 @staticmethod
1203 def _tap_activate(phy_path, vdi_type, sr_uuid, options):
1205 tapdisk = Tapdisk.find_by_path(phy_path)
1206 if not tapdisk: 1206 ↛ 1207line 1206 didn't jump to line 1207, because the condition on line 1206 was never true
1207 blktap = Blktap.allocate()
1209 try:
1210 tapdisk = \
1211 Tapdisk.launch_on_tap(blktap,
1212 phy_path,
1213 VDI._tap_type(vdi_type),
1214 options)
1215 except:
1216 blktap.free()
1217 raise
1218 util.SMlog("tap.activate: Launched %s" % tapdisk)
1220 else:
1221 util.SMlog("tap.activate: Found %s" % tapdisk)
1223 return tapdisk.get_devpath(), tapdisk
1225 @staticmethod
1226 def _tap_deactivate(minor):
1228 try:
1229 tapdisk = Tapdisk.from_minor(minor)
1230 except TapdiskNotRunning as e:
1231 util.SMlog("tap.deactivate: Warning, %s" % e)
1232 # NB. Should not be here unless the agent refcount
1233 # broke. Also, a clean shutdown should not have leaked
1234 # the recorded minor.
1235 else:
1236 tapdisk.shutdown()
1237 util.SMlog("tap.deactivate: Shut down %s" % tapdisk)
1239 @classmethod
1240 def tap_pause(cls, session, sr_uuid, vdi_uuid, failfast=False):
1241 """
1242 Pauses the tapdisk.
1244 session: a XAPI session
1245 sr_uuid: the UUID of the SR on which VDI lives
1246 vdi_uuid: the UUID of the VDI to pause
1247 failfast: controls whether the VDI lock should be acquired in a
1248 non-blocking manner
1249 """
1250 util.SMlog("Pause request for %s" % vdi_uuid)
1251 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1252 session.xenapi.VDI.add_to_sm_config(vdi_ref, 'paused', 'true')
1253 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1254 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 1254 ↛ 1255line 1254 didn't jump to line 1255, because the loop on line 1254 never started
1255 host_ref = key[len('host_'):]
1256 util.SMlog("Calling tap-pause on host %s" % host_ref)
1257 if not cls.call_pluginhandler(session, host_ref,
1258 sr_uuid, vdi_uuid, "pause", failfast=failfast):
1259 # Failed to pause node
1260 session.xenapi.VDI.remove_from_sm_config(vdi_ref, 'paused')
1261 return False
1262 return True
1264 @classmethod
1265 def tap_unpause(cls, session, sr_uuid, vdi_uuid, secondary=None,
1266 activate_parents=False):
1267 util.SMlog("Unpause request for %s secondary=%s" % (vdi_uuid, secondary))
1268 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1269 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1270 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 1270 ↛ 1271line 1270 didn't jump to line 1271, because the loop on line 1270 never started
1271 host_ref = key[len('host_'):]
1272 util.SMlog("Calling tap-unpause on host %s" % host_ref)
1273 if not cls.call_pluginhandler(session, host_ref,
1274 sr_uuid, vdi_uuid, "unpause", secondary, activate_parents):
1275 # Failed to unpause node
1276 return False
1277 session.xenapi.VDI.remove_from_sm_config(vdi_ref, 'paused')
1278 return True
1280 @classmethod
1281 def tap_refresh(cls, session, sr_uuid, vdi_uuid, activate_parents=False):
1282 util.SMlog("Refresh request for %s" % vdi_uuid)
1283 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1284 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1285 for key in [x for x in sm_config.keys() if x.startswith('host_')]:
1286 host_ref = key[len('host_'):]
1287 util.SMlog("Calling tap-refresh on host %s" % host_ref)
1288 if not cls.call_pluginhandler(session, host_ref,
1289 sr_uuid, vdi_uuid, "refresh", None,
1290 activate_parents=activate_parents):
1291 # Failed to refresh node
1292 return False
1293 return True
1295 @classmethod
1296 def tap_status(cls, session, vdi_uuid):
1297 """Return True if disk is attached, false if it isn't"""
1298 util.SMlog("Disk status request for %s" % vdi_uuid)
1299 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1300 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1301 for key in [x for x in sm_config.keys() if x.startswith('host_')]: 1301 ↛ 1302line 1301 didn't jump to line 1302, because the loop on line 1301 never started
1302 return True
1303 return False
1305 @classmethod
1306 def call_pluginhandler(cls, session, host_ref, sr_uuid, vdi_uuid, action,
1307 secondary=None, activate_parents=False, failfast=False):
1308 """Optionally, activate the parent LV before unpausing"""
1309 try:
1310 args = {"sr_uuid": sr_uuid, "vdi_uuid": vdi_uuid,
1311 "failfast": str(failfast)}
1312 if secondary:
1313 args["secondary"] = secondary
1314 if activate_parents:
1315 args["activate_parents"] = "true"
1316 ret = session.xenapi.host.call_plugin(
1317 host_ref, PLUGIN_TAP_PAUSE, action,
1318 args)
1319 return ret == "True"
1320 except Exception as e:
1321 util.logException("BLKTAP2:call_pluginhandler %s" % e)
1322 return False
1324 def _add_tag(self, vdi_uuid, writable):
1325 util.SMlog("Adding tag to: %s" % vdi_uuid)
1326 attach_mode = "RO"
1327 if writable:
1328 attach_mode = "RW"
1329 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1330 host_ref = self._session.xenapi.host.get_by_uuid(util.get_this_host())
1331 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1332 attached_as = util.attached_as(sm_config)
1333 if NO_MULTIPLE_ATTACH and (attached_as == "RW" or \ 1333 ↛ 1335line 1333 didn't jump to line 1335, because the condition on line 1333 was never true
1334 (attached_as == "RO" and attach_mode == "RW")):
1335 util.SMlog("need to reset VDI %s" % vdi_uuid)
1336 if not resetvdis.reset_vdi(self._session, vdi_uuid, force=False,
1337 term_output=False, writable=writable):
1338 raise util.SMException("VDI %s not detached cleanly" % vdi_uuid)
1339 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1340 if 'relinking' in sm_config:
1341 util.SMlog("Relinking key found, back-off and retry" % sm_config)
1342 return False
1343 if 'paused' in sm_config:
1344 util.SMlog("Paused or host_ref key found [%s]" % sm_config)
1345 return False
1346 try:
1347 self._session.xenapi.VDI.add_to_sm_config(
1348 vdi_ref, 'activating', 'True')
1349 except XenAPI.Failure as e:
1350 if e.details[0] == 'MAP_DUPLICATE_KEY' and not writable:
1351 # Someone else is activating - a retry might succeed
1352 return False
1353 raise
1354 host_key = "host_%s" % host_ref
1355 assert host_key not in sm_config
1356 self._session.xenapi.VDI.add_to_sm_config(vdi_ref, host_key,
1357 attach_mode)
1358 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1359 if 'paused' in sm_config or 'relinking' in sm_config:
1360 util.SMlog("Found %s key, aborting" % (
1361 'paused' if 'paused' in sm_config else 'relinking'))
1362 self._session.xenapi.VDI.remove_from_sm_config(vdi_ref, host_key)
1363 self._session.xenapi.VDI.remove_from_sm_config(
1364 vdi_ref, 'activating')
1365 return False
1366 util.SMlog("Activate lock succeeded")
1367 return True
1369 def _check_tag(self, vdi_uuid):
1370 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1371 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1372 if 'paused' in sm_config:
1373 util.SMlog("Paused key found [%s]" % sm_config)
1374 return False
1375 return True
1377 def _remove_tag(self, vdi_uuid):
1378 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1379 host_ref = self._session.xenapi.host.get_by_uuid(util.get_this_host())
1380 sm_config = self._session.xenapi.VDI.get_sm_config(vdi_ref)
1381 host_key = "host_%s" % host_ref
1382 if host_key in sm_config:
1383 self._session.xenapi.VDI.remove_from_sm_config(vdi_ref, host_key)
1384 util.SMlog("Removed host key %s for %s" % (host_key, vdi_uuid))
1385 else:
1386 util.SMlog("_remove_tag: host key %s not found, ignore" % host_key)
1388 def linkNBD(self, sr_uuid, vdi_uuid):
1389 if self.tap:
1390 nbd_path = '/run/blktap-control/nbd%d.%d' % (int(self.tap.pid),
1391 int(self.tap.minor))
1392 VDI.NBDLink.from_uuid(sr_uuid, vdi_uuid).mklink(nbd_path)
1394 def attach(self, sr_uuid, vdi_uuid, writable, activate=False, caching_params={}):
1395 """Return attach details to allow access to storage datapath"""
1396 if not self.target.has_cap("ATOMIC_PAUSE") or activate:
1397 util.SMlog("Attach & activate")
1398 self._attach(sr_uuid, vdi_uuid)
1399 dev_path = self._activate(sr_uuid, vdi_uuid,
1400 {"rdonly": not writable})
1401 self.linkNBD(sr_uuid, vdi_uuid)
1403 # Return NBD link
1404 if self.tap_wanted():
1405 # Only have NBD if we also have a tap
1406 nbd_path = "nbd:unix:{}:exportname={}".format(
1407 VDI.NBDLink.from_uuid(sr_uuid, vdi_uuid).path(),
1408 vdi_uuid)
1409 else:
1410 nbd_path = ""
1412 options = {"rdonly": not writable}
1413 options.update(caching_params)
1414 o_direct, o_direct_reason = self.get_o_direct_capability(options)
1415 struct = {'params_nbd': nbd_path,
1416 'o_direct': o_direct,
1417 'o_direct_reason': o_direct_reason,
1418 'xenstore_data': self.xenstore_data}
1419 util.SMlog('result: %s' % struct)
1421 return xmlrpc.client.dumps((struct, ), "", True)
1423 def activate(self, sr_uuid, vdi_uuid, writable, caching_params):
1424 util.SMlog("blktap2.activate")
1425 options = {"rdonly": not writable}
1426 options.update(caching_params)
1428 sr_ref = self.target.vdi.sr.srcmd.params.get('sr_ref')
1429 sr_other_config = self._session.xenapi.SR.get_other_config(sr_ref)
1430 for i in range(self.ATTACH_DETACH_RETRY_SECS): 1430 ↛ 1437line 1430 didn't jump to line 1437, because the loop on line 1430 didn't complete
1431 try:
1432 if self._activate_locked(sr_uuid, vdi_uuid, options):
1433 return
1434 except util.SRBusyException:
1435 util.SMlog("SR locked, retrying")
1436 time.sleep(1)
1437 raise util.SMException("VDI %s locked" % vdi_uuid)
1439 @locking("VDIUnavailable")
1440 def _activate_locked(self, sr_uuid, vdi_uuid, options):
1441 """Wraps target.activate and adds a tapdisk"""
1443 #util.SMlog("VDI.activate %s" % vdi_uuid)
1444 refresh = False
1445 if self.tap_wanted(): 1445 ↛ 1450line 1445 didn't jump to line 1450, because the condition on line 1445 was never false
1446 if not self._add_tag(vdi_uuid, not options["rdonly"]):
1447 return False
1448 refresh = True
1450 try:
1451 if refresh: 1451 ↛ 1462line 1451 didn't jump to line 1462, because the condition on line 1451 was never false
1452 # it is possible that while the VDI was paused some of its
1453 # attributes have changed (e.g. its size if it was inflated; or its
1454 # path if it was leaf-coalesced onto a raw LV), so refresh the
1455 # object completely
1456 params = self.target.vdi.sr.srcmd.params
1457 target = sm.VDI.from_uuid(self.target.vdi.session, vdi_uuid)
1458 target.sr.srcmd.params = params
1459 driver_info = target.sr.srcmd.driver_info
1460 self.target = self.TargetDriver(target, driver_info)
1462 util.fistpoint.activate_custom_fn( 1462 ↛ exitline 1462 didn't jump to the function exit
1463 "blktap_activate_inject_failure",
1464 lambda: util.inject_failure())
1466 # Attach the physical node
1467 if self.target.has_cap("ATOMIC_PAUSE"): 1467 ↛ 1468line 1467 didn't jump to line 1468, because the condition on line 1467 was never true
1468 self._attach(sr_uuid, vdi_uuid)
1470 vdi_type = self.target.get_vdi_type()
1472 # Take lvchange-p Lock before running
1473 # tap-ctl open
1474 # Needed to avoid race with lvchange -p which is
1475 # now taking the same lock
1476 # This is a fix for CA-155766
1477 if hasattr(self.target.vdi.sr, 'DRIVER_TYPE') and \ 1477 ↛ 1480line 1477 didn't jump to line 1480, because the condition on line 1477 was never true
1478 self.target.vdi.sr.DRIVER_TYPE == 'lvhd' and \
1479 vdi_type == vhdutil.VDI_TYPE_VHD:
1480 lock = Lock("lvchange-p", lvhdutil.NS_PREFIX_LVM + sr_uuid)
1481 lock.acquire()
1483 # When we attach a static VDI for HA, we cannot communicate with
1484 # xapi, because has not started yet. These VDIs are raw.
1485 if vdi_type != vhdutil.VDI_TYPE_RAW: 1485 ↛ 1496line 1485 didn't jump to line 1496, because the condition on line 1485 was never false
1486 session = self.target.vdi.session
1487 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1488 # pylint: disable=used-before-assignment
1489 sm_config = session.xenapi.VDI.get_sm_config(vdi_ref)
1490 if 'key_hash' in sm_config: 1490 ↛ 1491line 1490 didn't jump to line 1491, because the condition on line 1490 was never true
1491 key_hash = sm_config['key_hash']
1492 options['key_hash'] = key_hash
1493 options['vdi_uuid'] = vdi_uuid
1494 util.SMlog('Using key with hash {} for VDI {}'.format(key_hash, vdi_uuid))
1495 # Activate the physical node
1496 dev_path = self._activate(sr_uuid, vdi_uuid, options)
1498 if hasattr(self.target.vdi.sr, 'DRIVER_TYPE') and \ 1498 ↛ 1501line 1498 didn't jump to line 1501, because the condition on line 1498 was never true
1499 self.target.vdi.sr.DRIVER_TYPE == 'lvhd' and \
1500 self.target.get_vdi_type() == vhdutil.VDI_TYPE_VHD:
1501 lock.release()
1502 except:
1503 util.SMlog("Exception in activate/attach")
1504 if self.tap_wanted():
1505 util.fistpoint.activate_custom_fn(
1506 "blktap_activate_error_handling",
1507 lambda: time.sleep(30))
1508 while True:
1509 try:
1510 self._remove_tag(vdi_uuid)
1511 break
1512 except xmlrpc.client.ProtocolError as e:
1513 # If there's a connection error, keep trying forever.
1514 if e.errcode == http.HTTPStatus.INTERNAL_SERVER_ERROR.value:
1515 continue
1516 else:
1517 util.SMlog('failed to remove tag: %s' % e)
1518 break
1519 except Exception as e:
1520 util.SMlog('failed to remove tag: %s' % e)
1521 break
1522 raise
1523 finally:
1524 vdi_ref = self._session.xenapi.VDI.get_by_uuid(vdi_uuid)
1525 self._session.xenapi.VDI.remove_from_sm_config(
1526 vdi_ref, 'activating')
1527 util.SMlog("Removed activating flag from %s" % vdi_uuid) 1527 ↛ exitline 1527 didn't except from function '_activate_locked', because the raise on line 1522 wasn't executed
1529 # Link result to backend/
1530 self.linkNBD(sr_uuid, vdi_uuid)
1531 return True
1533 def _activate(self, sr_uuid, vdi_uuid, options):
1534 vdi_options = self.target.activate(sr_uuid, vdi_uuid)
1536 dev_path = self.setup_cache(sr_uuid, vdi_uuid, options)
1537 if not dev_path: 1537 ↛ 1550line 1537 didn't jump to line 1550, because the condition on line 1537 was never false
1538 phy_path = self.PhyLink.from_uuid(sr_uuid, vdi_uuid).readlink()
1539 # Maybe launch a tapdisk on the physical link
1540 if self.tap_wanted(): 1540 ↛ 1548line 1540 didn't jump to line 1548, because the condition on line 1540 was never false
1541 vdi_type = self.target.get_vdi_type()
1542 options["o_direct"] = self.get_o_direct_capability(options)[0]
1543 if vdi_options: 1543 ↛ 1545line 1543 didn't jump to line 1545, because the condition on line 1543 was never false
1544 options.update(vdi_options)
1545 dev_path, self.tap = self._tap_activate(phy_path, vdi_type,
1546 sr_uuid, options)
1547 else:
1548 dev_path = phy_path # Just reuse phy
1550 return dev_path
1552 def _attach(self, sr_uuid, vdi_uuid):
1553 attach_info = xmlrpc.client.loads(self.target.attach(sr_uuid, vdi_uuid))[0][0]
1554 params = attach_info['params']
1555 xenstore_data = attach_info['xenstore_data']
1556 phy_path = util.to_plain_string(params)
1557 self.xenstore_data.update(xenstore_data)
1558 # Save it to phy/
1559 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).mklink(phy_path)
1561 def deactivate(self, sr_uuid, vdi_uuid, caching_params):
1562 util.SMlog("blktap2.deactivate")
1563 for i in range(self.ATTACH_DETACH_RETRY_SECS):
1564 try:
1565 if self._deactivate_locked(sr_uuid, vdi_uuid, caching_params):
1566 return
1567 except util.SRBusyException as e:
1568 util.SMlog("SR locked, retrying")
1569 time.sleep(1)
1570 raise util.SMException("VDI %s locked" % vdi_uuid)
1572 @locking("VDIUnavailable")
1573 def _deactivate_locked(self, sr_uuid, vdi_uuid, caching_params):
1574 """Wraps target.deactivate and removes a tapdisk"""
1576 #util.SMlog("VDI.deactivate %s" % vdi_uuid)
1577 if self.tap_wanted() and not self._check_tag(vdi_uuid):
1578 return False
1580 self._deactivate(sr_uuid, vdi_uuid, caching_params)
1581 if self.target.has_cap("ATOMIC_PAUSE"):
1582 self._detach(sr_uuid, vdi_uuid)
1583 if self.tap_wanted():
1584 self._remove_tag(vdi_uuid)
1586 return True
1588 def _resetPhylink(self, sr_uuid, vdi_uuid, path):
1589 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).mklink(path)
1591 def detach(self, sr_uuid, vdi_uuid, deactivate=False, caching_params={}):
1592 if not self.target.has_cap("ATOMIC_PAUSE") or deactivate:
1593 util.SMlog("Deactivate & detach")
1594 self._deactivate(sr_uuid, vdi_uuid, caching_params)
1595 self._detach(sr_uuid, vdi_uuid)
1596 else:
1597 pass # nothing to do
1599 def _deactivate(self, sr_uuid, vdi_uuid, caching_params):
1600 # Shutdown tapdisk
1601 nbd_link = self.NBDLink.from_uuid(sr_uuid, vdi_uuid)
1603 if not util.pathexists(nbd_link.path()):
1604 util.SMlog("Nbd path %s does not exist" % nbd_link.path())
1605 return
1607 minor = nbd_link.read_minor_from_path()
1608 self._tap_deactivate(minor)
1609 self.remove_cache(caching_params)
1611 # Remove the backend link
1612 nbd_link.unlink()
1614 # Deactivate & detach the physical node
1615 if self.tap_wanted() and self.target.vdi.session is not None:
1616 # it is possible that while the VDI was paused some of its
1617 # attributes have changed (e.g. its size if it was inflated; or its
1618 # path if it was leaf-coalesced onto a raw LV), so refresh the
1619 # object completely
1620 target = sm.VDI.from_uuid(self.target.vdi.session, vdi_uuid)
1621 driver_info = target.sr.srcmd.driver_info
1622 self.target = self.TargetDriver(target, driver_info)
1624 self.target.deactivate(sr_uuid, vdi_uuid)
1626 def _detach(self, sr_uuid, vdi_uuid):
1627 self.target.detach(sr_uuid, vdi_uuid)
1629 # Remove phy/
1630 self.PhyLink.from_uuid(sr_uuid, vdi_uuid).unlink()
1632 def _updateCacheRecord(self, session, vdi_uuid, on_boot, caching):
1633 # Remove existing VDI.sm_config fields
1634 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1635 for key in ["on_boot", "caching"]:
1636 session.xenapi.VDI.remove_from_sm_config(vdi_ref, key)
1637 if not on_boot is None: 1637 ↛ 1638line 1637 didn't jump to line 1638, because the condition on line 1637 was never true
1638 session.xenapi.VDI.add_to_sm_config(vdi_ref, 'on_boot', on_boot)
1639 if not caching is None:
1640 session.xenapi.VDI.add_to_sm_config(vdi_ref, 'caching', caching)
1642 def setup_cache(self, sr_uuid, vdi_uuid, params):
1643 if params.get(self.CONF_KEY_ALLOW_CACHING) != "true":
1644 return
1646 util.SMlog("Requested local caching")
1647 if not self.target.has_cap("SR_CACHING"):
1648 util.SMlog("Error: local caching not supported by this SR")
1649 return
1651 scratch_mode = False
1652 if params.get(self.CONF_KEY_MODE_ON_BOOT) == "reset":
1653 scratch_mode = True
1654 util.SMlog("Requested scratch mode")
1655 if not self.target.has_cap("VDI_RESET_ON_BOOT/2"): 1655 ↛ 1659line 1655 didn't jump to line 1659, because the condition on line 1655 was never false
1656 util.SMlog("Error: scratch mode not supported by this SR")
1657 return
1659 dev_path = None
1660 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR)
1661 if not local_sr_uuid:
1662 util.SMlog("ERROR: Local cache SR not specified, not enabling")
1663 return
1664 dev_path = self._setup_cache(self._session, sr_uuid, vdi_uuid,
1665 local_sr_uuid, scratch_mode, params)
1667 if dev_path:
1668 self._updateCacheRecord(self._session, self.target.vdi.uuid,
1669 params.get(self.CONF_KEY_MODE_ON_BOOT),
1670 params.get(self.CONF_KEY_ALLOW_CACHING))
1672 return dev_path
1674 def alert_no_cache(self, session, vdi_uuid, cache_sr_uuid, err):
1675 vm_uuid = None
1676 vm_label = ""
1677 try:
1678 cache_sr_ref = session.xenapi.SR.get_by_uuid(cache_sr_uuid)
1679 cache_sr_rec = session.xenapi.SR.get_record(cache_sr_ref)
1680 cache_sr_label = cache_sr_rec.get("name_label")
1682 host_ref = session.xenapi.host.get_by_uuid(util.get_this_host())
1683 host_rec = session.xenapi.host.get_record(host_ref)
1684 host_label = host_rec.get("name_label")
1686 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
1687 vbds = session.xenapi.VBD.get_all_records_where( \
1688 "field \"VDI\" = \"%s\"" % vdi_ref)
1689 for vbd_rec in vbds.values():
1690 vm_ref = vbd_rec.get("VM")
1691 vm_rec = session.xenapi.VM.get_record(vm_ref)
1692 vm_uuid = vm_rec.get("uuid")
1693 vm_label = vm_rec.get("name_label")
1694 except:
1695 util.logException("alert_no_cache")
1697 alert_obj = "SR"
1698 alert_uuid = str(cache_sr_uuid)
1699 alert_str = "No space left in Local Cache SR %s" % cache_sr_uuid
1700 if vm_uuid:
1701 alert_obj = "VM"
1702 alert_uuid = vm_uuid
1703 reason = ""
1704 if err == errno.ENOSPC:
1705 reason = "because there is no space left"
1706 alert_str = "The VM \"%s\" is not using IntelliCache %s on the Local Cache SR (\"%s\") on host \"%s\"" % \
1707 (vm_label, reason, cache_sr_label, host_label)
1709 util.SMlog("Creating alert: (%s, %s, \"%s\")" % \
1710 (alert_obj, alert_uuid, alert_str))
1711 session.xenapi.message.create("No space left in local cache", "3",
1712 alert_obj, alert_uuid, alert_str)
1714 def _setup_cache(self, session, sr_uuid, vdi_uuid, local_sr_uuid,
1715 scratch_mode, options):
1716 from sm import SR
1717 from sm.drivers import EXTSR
1719 if self._no_parent(self.target.vdi): 1719 ↛ 1720line 1719 didn't jump to line 1720, because the condition on line 1719 was never true
1720 util.SMlog("ERROR: VDI %s has no parent, not enabling" %
1721 self.target.vdi.uuid)
1722 return
1724 util.SMlog("Setting up cache")
1725 shared_target = self.target.vdi.sr.vdi(self.target.vdi.parent)
1727 if shared_target.parent:
1728 util.SMlog("ERROR: Parent VDI %s has parent, not enabling" %
1729 shared_target.uuid)
1730 return
1732 SR.registerSR(EXTSR.EXTSR)
1733 local_sr = SR.SR.from_uuid(session, local_sr_uuid)
1735 lock = Lock(self.LOCK_CACHE_SETUP, shared_target.uuid)
1736 lock.acquire()
1738 # read cache
1739 read_cache_path = "%s/%s.vhdcache" % (local_sr.path, shared_target.uuid)
1740 if util.pathexists(read_cache_path): 1740 ↛ 1744line 1740 didn't jump to line 1744, because the condition on line 1740 was never false
1741 util.SMlog("Read cache node (%s) already exists, not creating" %
1742 read_cache_path)
1743 else:
1744 try:
1745 vhdutil.snapshot(read_cache_path, shared_target.path, False)
1746 except util.CommandException as e:
1747 util.SMlog("Error creating parent cache: %s" % e)
1748 self.alert_no_cache(session, vdi_uuid, local_sr_uuid, e.code)
1749 return None
1751 # local write node
1752 leaf_size = vhdutil.getSizeVirt(self.target.vdi.path)
1753 local_leaf_path = "%s/%s.vhdcache" % \
1754 (local_sr.path, self.target.vdi.uuid)
1755 if util.pathexists(local_leaf_path): 1755 ↛ 1759line 1755 didn't jump to line 1759, because the condition on line 1755 was never false
1756 util.SMlog("Local leaf node (%s) already exists, deleting" %
1757 local_leaf_path)
1758 os.unlink(local_leaf_path)
1759 try:
1760 vhdutil.snapshot(local_leaf_path, read_cache_path, False,
1761 msize=leaf_size // 1024 // 1024, checkEmpty=False)
1762 except util.CommandException as e:
1763 util.SMlog("Error creating leaf cache: %s" % e)
1764 self.alert_no_cache(session, vdi_uuid, local_sr_uuid, e.code)
1765 return None
1767 local_leaf_size = vhdutil.getSizeVirt(local_leaf_path)
1768 if leaf_size > local_leaf_size: 1768 ↛ 1769line 1768 didn't jump to line 1769, because the condition on line 1768 was never true
1769 util.SMlog("Leaf size %d > local leaf cache size %d, resizing" %
1770 (leaf_size, local_leaf_size))
1771 vhdutil.setSizeVirtFast(local_leaf_path, leaf_size)
1773 prt_tapdisk = Tapdisk.find_by_path(read_cache_path)
1774 if not prt_tapdisk:
1775 parent_options = copy.deepcopy(options)
1776 parent_options["rdonly"] = False
1777 parent_options["lcache"] = True
1779 blktap = Blktap.allocate()
1780 try:
1781 prt_tapdisk = \
1782 Tapdisk.launch_on_tap(blktap, read_cache_path,
1783 'vhd', parent_options)
1784 except:
1785 blktap.free()
1786 raise
1788 secondary = "%s:%s" % (self.target.get_vdi_type(),
1789 (self.PhyLink.from_uuid(sr_uuid, vdi_uuid).readlink()))
1791 util.SMlog("Parent tapdisk: %s" % prt_tapdisk)
1792 leaf_tapdisk = Tapdisk.find_by_path(local_leaf_path)
1793 if not leaf_tapdisk: 1793 ↛ 1811line 1793 didn't jump to line 1811, because the condition on line 1793 was never false
1794 blktap = Blktap.allocate()
1795 child_options = copy.deepcopy(options)
1796 child_options["rdonly"] = False
1797 child_options["lcache"] = (not scratch_mode)
1798 child_options["existing_prt"] = prt_tapdisk.minor
1799 child_options["secondary"] = secondary
1800 child_options["standby"] = scratch_mode
1801 # Disable memory read caching
1802 child_options.pop("o_direct", None)
1803 try:
1804 leaf_tapdisk = \
1805 Tapdisk.launch_on_tap(blktap, local_leaf_path,
1806 'vhd', child_options)
1807 except:
1808 blktap.free()
1809 raise
1811 lock.release()
1813 util.SMlog("Local read cache: %s, local leaf: %s" %
1814 (read_cache_path, local_leaf_path))
1816 self.tap = leaf_tapdisk
1817 return leaf_tapdisk.get_devpath()
1819 def remove_cache(self, params):
1820 if not self.target.has_cap("SR_CACHING"):
1821 return
1823 caching = params.get(self.CONF_KEY_ALLOW_CACHING) == "true"
1825 local_sr_uuid = params.get(self.CONF_KEY_CACHE_SR)
1826 if caching and not local_sr_uuid:
1827 util.SMlog("ERROR: Local cache SR not specified, ignore")
1828 return
1830 if caching: 1830 ↛ 1833line 1830 didn't jump to line 1833, because the condition on line 1830 was never false
1831 self._remove_cache(self._session, local_sr_uuid)
1833 if self._session is not None: 1833 ↛ exitline 1833 didn't return from function 'remove_cache', because the condition on line 1833 was never false
1834 self._updateCacheRecord(self._session, self.target.vdi.uuid, None, None)
1836 def _is_tapdisk_in_use(self, minor):
1837 retVal, links, sockets = util.findRunningProcessOrOpenFile("tapdisk")
1838 if not retVal:
1839 # err on the side of caution
1840 return True
1842 for link in links:
1843 if link.find("tapdev%d" % minor) != -1:
1844 return True
1846 socket_re = re.compile(r'^/.*/nbd\d+\.%d' % minor)
1847 for s in sockets:
1848 if socket_re.match(s):
1849 return True
1851 return False
1853 def _remove_cache(self, session, local_sr_uuid):
1854 from sm import SR
1855 from sm.drivers import EXTSR
1857 if self._no_parent(self.target.vdi):
1858 util.SMlog("ERROR: No parent for VDI %s, ignore" %
1859 self.target.vdi.uuid)
1860 return
1862 util.SMlog("Tearing down the cache")
1864 shared_target = self.target.vdi.sr.vdi(self.target.vdi.parent)
1866 SR.registerSR(EXTSR.EXTSR)
1867 local_sr = SR.SR.from_uuid(session, local_sr_uuid)
1869 lock = Lock(self.LOCK_CACHE_SETUP, shared_target.uuid)
1870 lock.acquire()
1872 # local write node
1873 local_leaf_path = "%s/%s.vhdcache" % \
1874 (local_sr.path, self.target.vdi.uuid)
1875 if util.pathexists(local_leaf_path): 1875 ↛ 1879line 1875 didn't jump to line 1879, because the condition on line 1875 was never false
1876 util.SMlog("Deleting local leaf node %s" % local_leaf_path)
1877 os.unlink(local_leaf_path)
1879 read_cache_path = "%s/%s.vhdcache" % (local_sr.path, shared_target.uuid)
1880 prt_tapdisk = Tapdisk.find_by_path(read_cache_path)
1881 if not prt_tapdisk: 1881 ↛ 1882line 1881 didn't jump to line 1882, because the condition on line 1881 was never true
1882 util.SMlog("Parent tapdisk not found")
1883 elif not self._is_tapdisk_in_use(prt_tapdisk.minor): 1883 ↛ 1891line 1883 didn't jump to line 1891, because the condition on line 1883 was never false
1884 util.SMlog("Parent tapdisk not in use: shutting down %s" %
1885 read_cache_path)
1886 try:
1887 prt_tapdisk.shutdown()
1888 except:
1889 util.logException("shutting down parent tapdisk")
1890 else:
1891 util.SMlog("Parent tapdisk still in use: %s" % read_cache_path)
1892 # the parent cache files are removed during the local SR's background
1893 # GC run
1895 lock.release()
1897 @staticmethod
1898 def _no_parent(vdi):
1899 return vdi.parent is None or vdi.parent == ''
1902PythonKeyError = KeyError
1905class UEventHandler(object):
1907 def __init__(self):
1908 self._action = None
1910 class KeyError(PythonKeyError):
1911 def __init__(self, args):
1912 super().__init__(args)
1913 self.key = args[0]
1915 def __str__(self):
1916 return \
1917 "Key '%s' missing in environment. " % self.key + \
1918 "Not called in udev context?"
1920 @classmethod
1921 def getenv(cls, key):
1922 try:
1923 return os.environ[key]
1924 except KeyError as e:
1925 raise cls.KeyError(e.args[0])
1927 def get_action(self):
1928 if not self._action:
1929 self._action = self.getenv('ACTION')
1930 return self._action
1932 class UnhandledEvent(Exception):
1934 def __init__(self, event, handler):
1935 self.event = event
1936 self.handler = handler
1938 def __str__(self):
1939 return "Uevent '%s' not handled by %s" % \
1940 (self.event, self.handler.__class__.__name__)
1942 ACTIONS = {}
1944 def run(self):
1946 action = self.get_action()
1947 try:
1948 fn = self.ACTIONS[action]
1949 except KeyError:
1950 raise self.UnhandledEvent(action, self)
1952 return fn(self)
1954 def __str__(self):
1955 try:
1956 action = self.get_action()
1957 except:
1958 action = None
1959 return "%s[%s]" % (self.__class__.__name__, action)
1962class __BlktapControl(ClassDevice):
1963 SYSFS_CLASSTYPE = "misc"
1965 def __init__(self):
1966 ClassDevice.__init__(self)
1968 def sysfs_devname(self):
1969 return "blktap!control"
1972BlktapControl = __BlktapControl()
1975class BusDevice(KObject):
1977 SYSFS_BUSTYPE = None
1979 @classmethod
1980 def sysfs_bus_path(cls):
1981 return "/sys/bus/%s" % cls.SYSFS_BUSTYPE
1983 def sysfs_path(self):
1984 path = "%s/devices/%s" % (self.sysfs_bus_path(),
1985 self.sysfs_devname())
1987 return path
1990class XenbusDevice(BusDevice):
1991 """Xenbus device, in XS and sysfs"""
1993 XBT_NIL = ""
1995 XENBUS_DEVTYPE = None
1997 def __init__(self, domid, devid):
1998 self.domid = int(domid)
1999 self.devid = int(devid)
2000 self._xbt = XenbusDevice.XBT_NIL
2002 import xen.lowlevel.xs # pylint: disable=import-error
2003 self.xs = xen.lowlevel.xs.xs()
2005 def xs_path(self, key=None):
2006 path = "backend/%s/%d/%d" % (self.XENBUS_DEVTYPE,
2007 self.domid,
2008 self.devid)
2009 if key is not None:
2010 path = "%s/%s" % (path, key)
2012 return path
2014 def _log(self, prio, msg):
2015 syslog(prio, msg)
2017 def info(self, msg):
2018 self._log(_syslog.LOG_INFO, msg)
2020 def warn(self, msg):
2021 self._log(_syslog.LOG_WARNING, "WARNING: " + msg)
2023 def _xs_read_path(self, path):
2024 val = self.xs.read(self._xbt, path)
2025 #self.info("read %s = '%s'" % (path, val))
2026 return val
2028 def _xs_write_path(self, path, val):
2029 self.xs.write(self._xbt, path, val)
2030 self.info("wrote %s = '%s'" % (path, val))
2032 def _xs_rm_path(self, path):
2033 self.xs.rm(self._xbt, path)
2034 self.info("removed %s" % path)
2036 def read(self, key):
2037 return self._xs_read_path(self.xs_path(key))
2039 def has_xs_key(self, key):
2040 return self.read(key) is not None
2042 def write(self, key, val):
2043 self._xs_write_path(self.xs_path(key), val)
2045 def rm(self, key):
2046 self._xs_rm_path(self.xs_path(key))
2048 def exists(self):
2049 return self.has_xs_key(None)
2051 def begin(self):
2052 assert(self._xbt == XenbusDevice.XBT_NIL)
2053 self._xbt = self.xs.transaction_start()
2055 def commit(self):
2056 ok = self.xs.transaction_end(self._xbt, 0)
2057 self._xbt = XenbusDevice.XBT_NIL
2058 return ok
2060 def abort(self):
2061 ok = self.xs.transaction_end(self._xbt, 1)
2062 assert(ok == True)
2063 self._xbt = XenbusDevice.XBT_NIL
2065 def create_physical_device(self):
2066 """The standard protocol is: toolstack writes 'params', linux hotplug
2067 script translates this into physical-device=%x:%x"""
2068 if self.has_xs_key("physical-device"):
2069 return
2070 try:
2071 params = self.read("params")
2072 frontend = self.read("frontend")
2073 is_cdrom = self._xs_read_path("%s/device-type") == "cdrom"
2074 # We don't have PV drivers for CDROM devices, so we prevent blkback
2075 # from opening the physical-device
2076 if not(is_cdrom):
2077 major_minor = os.stat(params).st_rdev
2078 major, minor = divmod(major_minor, 256)
2079 self.write("physical-device", "%x:%x" % (major, minor))
2080 except:
2081 util.logException("BLKTAP2:create_physical_device")
2083 def signal_hotplug(self, online=True):
2084 xapi_path = "/xapi/%d/hotplug/%s/%d/hotplug" % (self.domid,
2085 self.XENBUS_DEVTYPE,
2086 self.devid)
2087 upstream_path = self.xs_path("hotplug-status")
2088 if online:
2089 self._xs_write_path(xapi_path, "online")
2090 self._xs_write_path(upstream_path, "connected")
2091 else:
2092 self._xs_rm_path(xapi_path)
2093 self._xs_rm_path(upstream_path)
2095 def sysfs_devname(self):
2096 return "%s-%d-%d" % (self.XENBUS_DEVTYPE,
2097 self.domid, self.devid)
2099 def __str__(self):
2100 return self.sysfs_devname()
2102 @classmethod
2103 def find(cls):
2104 pattern = "/sys/bus/%s/devices/%s*" % (cls.SYSFS_BUSTYPE,
2105 cls.XENBUS_DEVTYPE)
2106 for path in glob.glob(pattern):
2108 name = os.path.basename(path)
2109 (_type, domid, devid) = name.split('-')
2111 yield cls(domid, devid)
2114class XenBackendDevice(XenbusDevice):
2115 """Xenbus backend device"""
2116 SYSFS_BUSTYPE = "xen-backend"
2118 @classmethod
2119 def from_xs_path(cls, _path):
2120 (_backend, _type, domid, devid) = _path.split('/')
2122 assert _backend == 'backend'
2123 assert _type == cls.XENBUS_DEVTYPE
2125 domid = int(domid)
2126 devid = int(devid)
2128 return cls(domid, devid)
2131class Blkback(XenBackendDevice):
2132 """A blkback VBD"""
2134 XENBUS_DEVTYPE = "vbd"
2136 def __init__(self, domid, devid):
2137 XenBackendDevice.__init__(self, domid, devid)
2138 self._phy = None
2139 self._vdi_uuid = None
2140 self._q_state = None
2141 self._q_events = None
2143 class XenstoreValueError(Exception):
2144 KEY = None
2146 def __init__(self, vbd, _str):
2147 self.vbd = vbd
2148 self.str = _str
2150 def __str__(self):
2151 return "Backend %s " % self.vbd + \
2152 "has %s = %s" % (self.KEY, self.str)
2154 class PhysicalDeviceError(XenstoreValueError):
2155 KEY = "physical-device"
2157 class PhysicalDevice(object):
2159 def __init__(self, major, minor):
2160 self.major = int(major)
2161 self.minor = int(minor)
2163 @classmethod
2164 def from_xbdev(cls, xbdev):
2166 phy = xbdev.read("physical-device")
2168 try:
2169 major, minor = phy.split(':')
2170 major = int(major, 0x10)
2171 minor = int(minor, 0x10)
2172 except Exception as e:
2173 raise xbdev.PhysicalDeviceError(xbdev, phy)
2175 return cls(major, minor)
2177 def makedev(self):
2178 return os.makedev(self.major, self.minor)
2180 def is_tap(self):
2181 return self.major == Tapdisk.major()
2183 def __str__(self):
2184 return "%s:%s" % (self.major, self.minor)
2186 def __eq__(self, other):
2187 return \
2188 self.major == other.major and \
2189 self.minor == other.minor
2191 def get_physical_device(self):
2192 if not self._phy:
2193 self._phy = self.PhysicalDevice.from_xbdev(self)
2194 return self._phy
2196 class QueueEvents(Attribute):
2197 """Blkback sysfs node to select queue-state event
2198 notifications emitted."""
2200 SYSFS_NODENAME = "queue_events"
2202 QUEUE_RUNNING = (1 << 0)
2203 QUEUE_PAUSE_DONE = (1 << 1)
2204 QUEUE_SHUTDOWN_DONE = (1 << 2)
2205 QUEUE_PAUSE_REQUEST = (1 << 3)
2206 QUEUE_SHUTDOWN_REQUEST = (1 << 4)
2208 def get_mask(self):
2209 return int(self.readline(), 0x10)
2211 def set_mask(self, mask):
2212 self.writeline("0x%x" % mask)
2214 def get_queue_events(self):
2215 if not self._q_events:
2216 self._q_events = self.QueueEvents.from_kobject(self)
2217 return self._q_events
2219 def get_vdi_uuid(self):
2220 if not self._vdi_uuid:
2221 self._vdi_uuid = self.read("sm-data/vdi-uuid")
2222 return self._vdi_uuid
2224 def pause_requested(self):
2225 return self.has_xs_key("pause")
2227 def shutdown_requested(self):
2228 return self.has_xs_key("shutdown-request")
2230 def shutdown_done(self):
2231 return self.has_xs_key("shutdown-done")
2233 def running(self):
2234 return self.has_xs_key('queue-0/kthread-pid')
2236 @classmethod
2237 def find_by_physical_device(cls, phy):
2238 for dev in cls.find():
2239 try:
2240 _phy = dev.get_physical_device()
2241 except cls.PhysicalDeviceError:
2242 continue
2244 if _phy == phy:
2245 yield dev
2247 @classmethod
2248 def find_by_tap_minor(cls, minor):
2249 phy = cls.PhysicalDevice(Tapdisk.major(), minor)
2250 return cls.find_by_physical_device(phy)
2252 @classmethod
2253 def find_by_tap(cls, tapdisk):
2254 return cls.find_by_tap_minor(tapdisk.minor)
2256 def has_tap(self):
2258 if not self.can_tap():
2259 return False
2261 phy = self.get_physical_device()
2262 if phy:
2263 return phy.is_tap()
2265 return False
2267 def is_bare_hvm(self):
2268 """File VDIs for bare HVM. These are directly accessible by Qemu."""
2269 try:
2270 self.get_physical_device()
2272 except self.PhysicalDeviceError as e:
2273 vdi_type = self.read("type")
2275 self.info("HVM VDI: type=%s" % vdi_type)
2277 if e.str is not None or vdi_type != 'file':
2278 raise
2280 return True
2282 return False
2284 def can_tap(self):
2285 return not self.is_bare_hvm()
2288class BlkbackEventHandler(UEventHandler):
2290 LOG_FACILITY = _syslog.LOG_DAEMON
2292 def __init__(self, ident=None, action=None):
2293 if not ident:
2294 ident = self.__class__.__name__
2296 self.ident = ident
2297 self._vbd = None
2298 self._tapdisk = None
2300 UEventHandler.__init__(self)
2302 def run(self):
2304 self.xs_path = self.getenv('XENBUS_PATH')
2305 openlog(str(self), 0, self.LOG_FACILITY)
2307 UEventHandler.run(self)
2309 def __str__(self):
2311 try:
2312 path = self.xs_path
2313 except:
2314 path = None
2316 try:
2317 action = self.get_action()
2318 except:
2319 action = None
2321 return "%s[%s](%s)" % (self.ident, action, path)
2323 def _log(self, prio, msg):
2324 syslog(prio, msg)
2325 util.SMlog("%s: " % self + msg)
2327 def info(self, msg):
2328 self._log(_syslog.LOG_INFO, msg)
2330 def warn(self, msg):
2331 self._log(_syslog.LOG_WARNING, "WARNING: " + msg)
2333 def error(self, msg):
2334 self._log(_syslog.LOG_ERR, "ERROR: " + msg)
2336 def get_vbd(self):
2337 if not self._vbd:
2338 self._vbd = Blkback.from_xs_path(self.xs_path)
2339 return self._vbd
2341 def get_tapdisk(self):
2342 if not self._tapdisk:
2343 minor = self.get_vbd().get_physical_device().minor
2344 self._tapdisk = Tapdisk.from_minor(minor)
2345 return self._tapdisk
2346 #
2347 # Events
2348 #
2350 def __add(self):
2351 vbd = self.get_vbd()
2352 # Manage blkback transitions
2353 # self._manage_vbd()
2355 vbd.create_physical_device()
2357 vbd.signal_hotplug()
2359 @retried(backoff=.5, limit=10)
2360 def add(self):
2361 try:
2362 self.__add()
2363 except Attribute.NoSuchAttribute as e:
2364 #
2365 # FIXME: KOBJ_ADD is racing backend.probe, which
2366 # registers device attributes. So poll a little.
2367 #
2368 self.warn("%s, still trying." % e)
2369 raise RetryLoop.TransientFailure(e)
2371 def __change(self):
2372 vbd = self.get_vbd()
2374 # 1. Pause or resume tapdisk (if there is one)
2376 if vbd.has_tap():
2377 pass
2378 #self._pause_update_tap()
2380 # 2. Signal Xapi.VBD.pause/resume completion
2382 self._signal_xapi()
2384 def change(self):
2385 vbd = self.get_vbd()
2387 # NB. Beware of spurious change events between shutdown
2388 # completion and device removal. Also, Xapi.VM.migrate will
2389 # hammer a couple extra shutdown-requests into the source VBD.
2391 while True:
2392 vbd.begin()
2394 if not vbd.exists() or \
2395 vbd.shutdown_done():
2396 break
2398 self.__change()
2400 if vbd.commit():
2401 return
2403 vbd.abort()
2404 self.info("spurious uevent, ignored.")
2406 def remove(self):
2407 vbd = self.get_vbd()
2409 vbd.signal_hotplug(False)
2411 ACTIONS = {'add': add,
2412 'change': change,
2413 'remove': remove}
2414 #
2415 # VDI.pause
2416 #
2418 def _tap_should_pause(self):
2419 """Enumerate all VBDs on our tapdisk. Returns true iff any was
2420 paused"""
2422 tapdisk = self.get_tapdisk()
2423 TapState = Tapdisk.PauseState
2425 PAUSED = 'P'
2426 RUNNING = 'R'
2427 PAUSED_SHUTDOWN = 'P,S'
2428 # NB. Shutdown/paused is special. We know it's not going
2429 # to restart again, so it's a RUNNING. Still better than
2430 # backtracking a removed device during Vbd.unplug completion.
2432 next = TapState.RUNNING
2433 vbds = {}
2435 for vbd in Blkback.find_by_tap(tapdisk):
2436 name = str(vbd)
2438 pausing = vbd.pause_requested()
2439 closing = vbd.shutdown_requested()
2440 running = vbd.running()
2442 if pausing:
2443 if closing and not running:
2444 vbds[name] = PAUSED_SHUTDOWN
2445 else:
2446 vbds[name] = PAUSED
2447 next = TapState.PAUSED
2449 else:
2450 vbds[name] = RUNNING
2452 self.info("tapdev%d (%s): %s -> %s"
2453 % (tapdisk.minor, tapdisk.pause_state(),
2454 vbds, next))
2456 return next == TapState.PAUSED
2458 def _pause_update_tap(self):
2459 vbd = self.get_vbd()
2461 if self._tap_should_pause():
2462 self._pause_tap()
2463 else:
2464 self._resume_tap()
2466 def _pause_tap(self):
2467 tapdisk = self.get_tapdisk()
2469 if not tapdisk.is_paused():
2470 self.info("pausing %s" % tapdisk)
2471 tapdisk.pause()
2473 def _resume_tap(self):
2474 tapdisk = self.get_tapdisk()
2476 # NB. Raw VDI snapshots. Refresh the physical path and
2477 # type while resuming.
2478 vbd = self.get_vbd()
2479 vdi_uuid = vbd.get_vdi_uuid()
2481 if tapdisk.is_paused():
2482 self.info("loading vdi uuid=%s" % vdi_uuid)
2483 vdi = VDI.from_cli(vdi_uuid)
2484 _type = vdi.get_tap_type()
2485 path = vdi.get_phy_path()
2486 self.info("resuming %s on %s:%s" % (tapdisk, _type, path))
2487 tapdisk.unpause(_type, path)
2488 #
2489 # VBD.pause/shutdown
2490 #
2492 def _manage_vbd(self):
2493 vbd = self.get_vbd()
2494 # NB. Hook into VBD state transitions.
2496 events = vbd.get_queue_events()
2498 mask = 0
2499 mask |= events.QUEUE_PAUSE_DONE # pause/unpause
2500 mask |= events.QUEUE_SHUTDOWN_DONE # shutdown
2501 # TODO: mask |= events.QUEUE_SHUTDOWN_REQUEST, for shutdown=force
2502 # TODO: mask |= events.QUEUE_RUNNING, for ionice updates etc
2504 events.set_mask(mask)
2505 self.info("wrote %s = %#02x" % (events.path, mask))
2507 def _signal_xapi(self):
2508 vbd = self.get_vbd()
2510 pausing = vbd.pause_requested()
2511 closing = vbd.shutdown_requested()
2512 running = vbd.running()
2514 handled = 0
2516 if pausing and not running:
2517 if 'pause-done' not in vbd:
2518 vbd.write('pause-done', '')
2519 handled += 1
2521 if not pausing:
2522 if 'pause-done' in vbd:
2523 vbd.rm('pause-done')
2524 handled += 1
2526 if closing and not running:
2527 if 'shutdown-done' not in vbd:
2528 vbd.write('shutdown-done', '')
2529 handled += 1
2531 if handled > 1:
2532 self.warn("handled %d events, " % handled +
2533 "pausing=%s closing=%s running=%s" % \
2534 (pausing, closing, running))