Пример #1
0
class TestVM(vanir.vm.BaseVM):
    qid = vanir.property('qid', type=int)
    name = vanir.property('name')
    uuid = uuid.uuid5(uuid.NAMESPACE_DNS, 'testvm')

    def __lt__(self, other):
        try:
            return self.name < other.name
        except AttributeError:
            return NotImplemented

    class MockLibvirt(object):
        def undefine(self):
            pass

    libvirt_domain = MockLibvirt()

    def is_halted(self):
        return True

    def get_power_state(self):
        return "Halted"
Пример #2
0
class AdminVM(vanir.vm.BaseVM):
    '''Dom0'''

    dir_path = None

    name = vanir.property('name',
                          default='dom0',
                          setter=vanir.property.forbidden)

    qid = vanir.property('qid',
                         default=0,
                         type=int,
                         setter=vanir.property.forbidden)

    uuid = vanir.property('uuid',
                          default='00000000-0000-0000-0000-000000000000',
                          setter=vanir.property.forbidden)

    default_dispvm = vanir.VMProperty(
        'default_dispvm',
        load_stage=4,
        allow_none=True,
        default=(lambda self: self.app.default_dispvm),
        doc='Default VM to be used as Disposable VM for service calls.')

    include_in_backups = vanir.property(
        'include_in_backups',
        default=True,
        type=bool,
        doc='If this domain is to be included in default backup.')

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self._qdb_connection = None
        self._libvirt_domain = None

        if not self.app.vmm.offline_mode:
            self.start_qdb_watch()

    def __str__(self):
        return self.name

    def __lt__(self, other):
        # order dom0 before anything
        return self.name != other.name

    @property
    def attached_volumes(self):
        return []

    @property
    def xid(self):
        '''Always ``0``.
        .. seealso:
           :py:attr:`vanir.vm.vanirvm.VanirVM.xid`
        '''
        return 0

    @property
    def libvirt_domain(self):
        '''Libvirt object for dom0.
        .. seealso:
           :py:attr:`vanir.vm.vanirvm.VanirVM.libvirt_domain`
        '''
        if self._libvirt_domain is None:
            self._libvirt_domain = self.app.vmm.libvirt_conn.lookupByID(0)
        return self._libvirt_domain

    @staticmethod
    def is_running():
        '''Always :py:obj:`True`.
        .. seealso:
           :py:meth:`vanir.vm.vanirvm.VanirVM.is_running`
        '''
        return True

    @staticmethod
    def is_halted():
        '''Always :py:obj:`False`.
        .. seealso:
           :py:meth:`vanir.vm.vanirvm.VanirVM.is_halted`
        '''
        return False

    @staticmethod
    def get_power_state():
        '''Always ``'Running'``.
        .. seealso:
           :py:meth:`vanir.vm.vanirvm.VanirVM.get_power_state`
        '''
        return 'Running'

    @staticmethod
    def get_mem():
        '''Get current memory usage of Dom0.
        Unit is KiB.
        .. seealso:
           :py:meth:`vanir.vm.vanirvm.VanirVM.get_mem`
        '''

        # return psutil.virtual_memory().total/1024
        with open('/proc/meminfo') as file:
            for line in file:
                if line.startswith('MemTotal:'):
                    return int(line.split(':')[1].strip().split()[0])
        raise NotImplementedError()

    def get_mem_static_max(self):
        '''Get maximum memory available to Dom0.
        .. seealso:
           :py:meth:`vanir.vm.vanirvm.VanirVM.get_mem_static_max`
        '''
        if self.app.vmm.offline_mode:
            # default value passed on xen cmdline
            return 4096
        try:
            return self.app.vmm.libvirt_conn.getInfo()[1]
        except libvirt.libvirtError as e:
            self.log.warning('Failed to get memory limit for dom0: %s', e)
            return 4096

    def verify_files(self):
        '''Always :py:obj:`True`
        .. seealso:
           :py:meth:`vanir.vm.vanirvm.VanirVM.verify_files`
        '''  # pylint: disable=no-self-use
        return True

    def start(self, start_guid=True, notify_function=None, mem_required=None):
        '''Always raises an exception.
        .. seealso:
           :py:meth:`vanir.vm.vanirvm.VanirVM.start`
        '''  # pylint: disable=unused-argument,arguments-differ
        raise vanir.exc.VanirVMError(self, 'Cannot start Dom0 fake domain!')

    def suspend(self):
        '''Does nothing.
        .. seealso:
           :py:meth:`vanir.vm.vanirvm.VanirVM.suspend`
        '''
        raise vanir.exc.VanirVMError(self, 'Cannot suspend Dom0 fake domain!')

    def shutdown(self):
        '''Does nothing.
        .. seealso:
           :py:meth:`vanir.vm.vanirvm.VanirVM.shutdown`
        '''
        raise vanir.exc.VanirVMError(self, 'Cannot shutdown Dom0 fake domain!')

    def kill(self):
        '''Does nothing.
        .. seealso:
           :py:meth:`vanir.vm.vanirvm.VanirVM.kill`
        '''
        raise vanir.exc.VanirVMError(self, 'Cannot kill Dom0 fake domain!')

    @property
    def icon_path(self):
        pass

    @property
    def untrusted_qdb(self):
        '''Vanirdb handle for this domain.'''
        if self._qdb_connection is None:
            import vanirdb  # pylint: disable=import-error
            self._qdb_connection = vanirdb.Vanirdb(self.name)
        return self._qdb_connection


#   def __init__(self, **kwargs):
#       super(VanirAdminVm, self).__init__(qid=0, name="dom0",
#                                            dir_path=None,
#                                            private_img = None,
#                                            template = None,
#                                            maxmem = 0,
#                                            vcpus = 0,
#                                            label = defaults["template_label"],
#                                            **kwargs)
Пример #3
0
 class MyTestHolder(vanir.tests.TestEmitter, vanir.PropertyHolder):
     testprop1 = vanir.property('testprop1',
         default=(lambda self: 'defaultvalue'))
Пример #4
0
 def test_002_eq(self):
     self.assertEqual(vanir.property('testprop2'),
         vanir.property('testprop2'))
Пример #5
0
 class MyTestHolder(vanir.tests.TestEmitter, vanir.PropertyHolder):
     testprop1 = vanir.property('testprop1')
Пример #6
0
 class MyTestHolder(vanir.tests.TestEmitter, vanir.PropertyHolder):
     testprop1 = vanir.property('testprop1', write_once=True)
Пример #7
0
class TestHolder(vanir.tests.TestEmitter, vanir.PropertyHolder):
    testprop1 = vanir.property('testprop1', order=0)
    testprop2 = vanir.property('testprop2', order=1, save_via_ref=True)
    testprop3 = vanir.property('testprop3', order=2, default='testdefault')
    testprop4 = vanir.property('testprop4', order=3)
Пример #8
0
class BaseVM(vanir.PropertyHolder):
    '''Base class for all VMs
    :param app: vanir application context
    :type app: :py:class:`vanir.vanir`
    :param xml: xml node from which to deserialise
    :type xml: :py:class:`lxml.etree._Element` or :py:obj:`None`
    This class is responsible for serializing and deserialising machines and
    provides basic framework. It contains no management logic. For that, see
    :py:class:`vanir.vm.vanirvm.VanirVM`.
    '''
    # pylint: disable=no-member

    uuid = vanir.property('uuid',
                          type=uuid.UUID,
                          write_once=True,
                          clone=False,
                          doc='UUID from libvirt.')

    name = vanir.property('name',
                          type=str,
                          write_once=True,
                          clone=False,
                          doc='User-specified name of the domain.')

    qid = vanir.property(
        'qid',
        type=int,
        write_once=True,
        setter=_setter_qid,
        clone=False,
        doc='''Internal, persistent identificator of particular domain. Note
            this is different from Xen domid.''')

    label = vanir.property(
        'label',
        setter=setter_label,
        doc='''Colourful label assigned to VM. This is where the colour of the
            padlock is set.''')

    def __init__(self,
                 app,
                 xml,
                 features=None,
                 devices=None,
                 tags=None,
                 **kwargs):
        # pylint: disable=redefined-outer-name

        self._qdb_watch_paths = set()
        self._qdb_connection_watch = None

        # self.app must be set before super().__init__, because some property
        # setters need working .app attribute
        #: mother :py:class:`vanir.vanir` object
        self.app = app

        super(BaseVM, self).__init__(xml, **kwargs)

        #: dictionary of features of this qube
        self.features = vanir.features.Features(self, features)

        #: :py:class:`DeviceManager` object keeping devices that are attached to
        #: this domain
        self.devices = devices or vanir.devices.DeviceManager(self)

        #: user-specified tags
        self.tags = Tags(self, tags or ())

        #: logger instance for logging messages related to this VM
        self.log = None

        #: storage volumes
        self.volumes = {}

        #: storage manager
        self.storage = None

        if hasattr(self, 'name'):
            self.init_log()

    def close(self):
        super().close()

        if self._qdb_connection_watch is not None:
            asyncio.get_event_loop().remove_reader(
                self._qdb_connection_watch.watch_fd())
            self._qdb_connection_watch.close()
            del self._qdb_connection_watch

        del self.app
        del self.features
        del self.storage
        # TODO storage may have circ references, but it doesn't leak fds
        del self.devices
        del self.tags

    def load_extras(self):
        if self.xml is None:
            return

        # features
        for node in self.xml.xpath('./features/feature'):
            self.features[node.get('name')] = node.text

        # devices (pci, usb, ...)
        for parent in self.xml.xpath('./devices'):
            devclass = parent.get('class')
            for node in parent.xpath('./device'):
                options = {}
                for option in node.xpath('./option'):
                    options[option.get('name')] = option.text

                device_assignment = vanir.devices.DeviceAssignment(
                    self.app.domains[node.get('backend-domain')],
                    node.get('id'),
                    options,
                    persistent=True)
                self.devices[devclass].load_persistent(device_assignment)

        # tags
        for node in self.xml.xpath('./tags/tag'):
            self.tags.add(node.get('name'))

        # SEE:1815 firewall, policy.

    def init_log(self):
        '''Initialise logger for this domain.'''
        self.log = vanir.log.get_vm_logger(self.name)

    def __xml__(self):
        element = lxml.etree.Element('domain')
        element.set('id', 'domain-' + str(self.qid))
        element.set('class', self.__class__.__name__)

        element.append(self.xml_properties())

        features = lxml.etree.Element('features')
        for feature in self.features:
            node = lxml.etree.Element('feature', name=feature)
            node.text = self.features[feature]
            features.append(node)
        element.append(features)

        for devclass in self.devices:
            devices = lxml.etree.Element('devices')
            devices.set('class', devclass)
            for device in self.devices[devclass].assignments(persistent=True):
                node = lxml.etree.Element('device')
                node.set('backend-domain', device.backend_domain.name)
                node.set('id', device.ident)
                for key, val in device.options.items():
                    option_node = lxml.etree.Element('option')
                    option_node.set('name', key)
                    option_node.text = val
                    node.append(option_node)
                devices.append(node)
            element.append(devices)

        tags = lxml.etree.Element('tags')
        for tag in self.tags:
            node = lxml.etree.Element('tag', name=tag)
            tags.append(node)
        element.append(tags)

        return element

    def __repr__(self):
        proprepr = []
        for prop in self.property_list():
            if prop.__name__ in ('name', 'qid'):
                continue
            try:
                proprepr.append('{}={!s}'.format(prop.__name__,
                                                 getattr(self, prop.__name__)))
            except AttributeError:
                continue

        return '<{} at {:#x} name={!r} qid={!r} {}>'.format(
            type(self).__name__, id(self), self.name, self.qid,
            ' '.join(proprepr))

    #
    # xml serialising methods
    #

    def create_config_file(self):
        '''Create libvirt's XML domain config file
        '''
        domain_config = self.app.env.select_template([
            'libvirt/xen/by-name/{}.xml'.format(self.name),
            'libvirt/xen-user.xml',
            'libvirt/xen-dist.xml',
            'libvirt/xen.xml',
        ]).render(vm=self)
        return domain_config

    def watch_qdb_path(self, path):
        '''Add a VanirDB path to be watched.
        Each change to the path will cause `domain-qdb-change:path` event to be
        fired.
        You can call this method for example in response to
        `domain-init` and `domain-load` events.
        '''

        if path not in self._qdb_watch_paths:
            self._qdb_watch_paths.add(path)
            if self._qdb_connection_watch:
                self._qdb_connection_watch.watch(path)

    def _qdb_watch_reader(self, loop):
        '''Callback when self._qdb_connection_watch.watch_fd() FD is
        readable.
        Read reported event (watched path change) and fire appropriate event.
        '''
        import VanirDB  # pylint: disable=import-error
        try:
            path = self._qdb_connection_watch.read_watch()
            for watched_path in self._qdb_watch_paths:
                if watched_path == path or (watched_path.endswith('/')
                                            and path.startswith(watched_path)):
                    self.fire_event('domain-qdb-change:' + watched_path,
                                    path=path)
        except VanirDB.DisconnectedError:
            loop.remove_reader(self._qdb_connection_watch.watch_fd())
            self._qdb_connection_watch.close()
            self._qdb_connection_watch = None

    def start_qdb_watch(self, loop=None):
        '''Start watching VanirDB
        Calling this method in appropriate time is responsibility of child
        class.
        '''
        # cleanup old watch connection first, if any
        if self._qdb_connection_watch is not None:
            asyncio.get_event_loop().remove_reader(
                self._qdb_connection_watch.watch_fd())
            self._qdb_connection_watch.close()

        import VanirDB  # pylint: disable=import-error
        self._qdb_connection_watch = VanirDB.VanirDB(self.name)
        if loop is None:
            loop = asyncio.get_event_loop()
        loop.add_reader(self._qdb_connection_watch.watch_fd(),
                        self._qdb_watch_reader, loop)
        for path in self._qdb_watch_paths:
            self._qdb_connection_watch.watch(path)

    @vanir.stateless_property
    def klass(self):
        '''Domain class name'''
        return type(self).__name__
Пример #9
0
class Rule(vanir.PropertyHolder):
    def __init__(self, xml=None, **kwargs):
        '''Single firewall rule
        :param xml: XML element describing rule, or None
        :param kwargs: rule elements
        '''
        super(Rule, self).__init__(xml, **kwargs)
        self.load_properties()
        self.events_enabled = True
        # validate dependencies
        if self.dstports:
            self.on_set_dstports('property-set:dstports', 'dstports',
                                 self.dstports, None)
        if self.icmptype:
            self.on_set_icmptype('property-set:icmptype', 'icmptype',
                                 self.icmptype, None)
        self.property_require('action', False, True)

    action = vanir.property('action', type=Action, order=0, doc='rule action')

    proto = vanir.property('proto',
                           type=Proto,
                           default=None,
                           order=1,
                           doc='protocol to match')

    dsthost = vanir.property('dsthost',
                             type=DstHost,
                             default=None,
                             order=1,
                             doc='destination host/network')

    dstports = vanir.property(
        'dstports',
        type=DstPorts,
        default=None,
        order=2,
        doc='Destination port(s) (for \'tcp\' and \'udp\' protocol only)')

    icmptype = vanir.property(
        'icmptype',
        type=IcmpType,
        default=None,
        order=2,
        doc='ICMP packet type (for \'icmp\' protocol only)')

    specialtarget = vanir.property(
        'specialtarget',
        type=SpecialTarget,
        default=None,
        order=1,
        doc='Special target, for now only \'dns\' supported')

    expire = vanir.property(
        'expire',
        type=Expire,
        default=None,
        doc='Timestamp (UNIX epoch) on which this rule expire')

    comment = vanir.property('comment',
                             type=Comment,
                             default=None,
                             doc='User comment')

    # noinspection PyUnusedLocal
    @vanir.events.handler('property-pre-set:dstports')
    def on_set_dstports(self, event, name, newvalue, oldvalue=None):
        # pylint: disable=unused-argument
        if self.proto not in ('tcp', 'udp'):
            raise ValueError(
                'dstports valid only for \'tcp\' and \'udp\' protocols')

    # noinspection PyUnusedLocal
    @vanir.events.handler('property-pre-set:icmptype')
    def on_set_icmptype(self, event, name, newvalue, oldvalue=None):
        # pylint: disable=unused-argument
        if self.proto not in ('icmp', ):
            raise ValueError('icmptype valid only for \'icmp\' protocol')

    # noinspection PyUnusedLocal
    @vanir.events.handler('property-set:proto')
    def on_set_proto(self, event, name, newvalue, oldvalue=None):
        # pylint: disable=unused-argument
        if newvalue not in ('tcp', 'udp'):
            self.dstports = vanir.property.DEFAULT
        if newvalue not in ('icmp', ):
            self.icmptype = vanir.property.DEFAULT

    @vanir.events.handler('property-del:proto')
    def on_del_proto(self, event, name, oldvalue):
        # pylint: disable=unused-argument
        self.dstports = vanir.property.DEFAULT
        self.icmptype = vanir.property.DEFAULT

    @property
    def rule(self):
        if self.expire and self.expire.expired:
            return None
        values = []
        for prop in self.property_list():
            value = getattr(self, prop.__name__)
            if value is None:
                continue
            if value.rule is None:
                continue
            values.append(value.rule)
        return ' '.join(values)

    @property
    def api_rule(self):
        values = []
        if self.expire and self.expire.expired:
            return None
        # put comment at the end
        for prop in sorted(self.property_list(),
                           key=(lambda p: p.__name__ == 'comment')):
            value = getattr(self, prop.__name__)
            if value is None:
                continue
            if value.api_rule is None:
                continue
            values.append(value.api_rule)
        return ' '.join(values)

    @classmethod
    def from_xml_v1(cls, node, action):
        netmask = node.get('netmask')
        if netmask is None:
            netmask = 32
        else:
            netmask = int(netmask)
        address = node.get('address')
        if address:
            dsthost = DstHost(address, netmask)
        else:
            dsthost = None

        proto = node.get('proto')

        port = node.get('port')
        toport = node.get('toport')
        if port and toport:
            dstports = port + '-' + toport
        elif port:
            dstports = port
        else:
            dstports = None

        # backward compatibility: protocol defaults to TCP if port is specified
        if dstports and not proto:
            proto = 'tcp'

        if proto == 'any':
            proto = None

        expire = node.get('expire')

        kwargs = {
            'action': action,
        }
        if dsthost:
            kwargs['dsthost'] = dsthost
        if dstports:
            kwargs['dstports'] = dstports
        if proto:
            kwargs['proto'] = proto
        if expire:
            kwargs['expire'] = expire

        return cls(**kwargs)

    @classmethod
    def from_api_string(cls, untrusted_rule):
        '''Parse a single line of firewall rule'''
        # comment is allowed to have spaces
        untrusted_options, _, untrusted_comment = untrusted_rule.partition(
            'comment=')
        # appropriate handlers in __init__ of individual options will perform
        #  option-specific validation
        kwargs = {}
        if untrusted_comment:
            kwargs['comment'] = Comment(untrusted_value=untrusted_comment)

        for untrusted_option in untrusted_options.strip().split(' '):
            untrusted_key, untrusted_value = untrusted_option.split('=', 1)
            if untrusted_key in kwargs:
                raise ValueError(
                    'Option \'{}\' already set'.format(untrusted_key))
            if untrusted_key in [str(prop) for prop in cls.property_list()]:
                kwargs[untrusted_key] = cls.property_get_def(
                    untrusted_key).type(untrusted_value=untrusted_value)
            elif untrusted_key in ('dst4', 'dst6', 'dstname'):
                if 'dsthost' in kwargs:
                    raise ValueError(
                        'Option \'{}\' already set'.format('dsthost'))
                kwargs['dsthost'] = DstHost(untrusted_value=untrusted_value)
            else:
                raise ValueError('Unknown firewall option')

        return cls(**kwargs)

    def __eq__(self, other):
        if isinstance(other, Rule):
            return self.api_rule == other.api_rule
        return self.api_rule == str(other)

    def __hash__(self):
        return hash(self.api_rule)
Пример #10
0
class NetVMMixin(vanir.events.Emitter):
    ''' Mixin containing network functionality '''
    mac = vanir.property('mac',
                         type=str,
                         default='00:16:3E:5E:6C:00',
                         setter=_setter_mac,
                         doc='MAC address of the NIC emulated inside VM')

    ip = vanir.property('ip',
                        type=ipaddress.IPv4Address,
                        default=_default_ip,
                        doc='IP address of this domain.')

    ip6 = vanir.property('ip6',
                         type=ipaddress.IPv6Address,
                         default=_default_ip6,
                         doc='IPv6 address of this domain.')

    # CORE2: swallowed uses_default_netvm
    netvm = vanir.VMProperty(
        'netvm',
        load_stage=4,
        allow_none=True,
        default=(lambda self: self.app.default_netvm),
        setter=_setter_netvm,
        doc='''VM that provides network connection to this domain. When
            `None`, machine is disconnected. When absent, domain uses default
            NetVM.''')

    provides_network = vanir.property(
        'provides_network',
        default=False,
        type=bool,
        setter=_setter_provides_network,
        doc='''If this domain can act as network provider (formerly known as
            NetVM or ProxyVM)''')

    @property
    def firewall_conf(self):
        return 'firewall.xml'

    #
    # used in networked appvms or proxyvms (netvm is not None)
    #

    @vanir.stateless_property
    def visible_ip(self):
        '''IP address of this domain as seen by the domain.'''
        return self.features.check_with_template('net.fake-ip', None) or \
            self.ip

    @vanir.stateless_property
    def visible_ip6(self):
        '''IPv6 address of this domain as seen by the domain.'''
        return self.ip6

    @vanir.stateless_property
    def visible_gateway(self):
        '''Default gateway of this domain as seen by the domain.'''
        return self.features.check_with_template('net.fake-gateway', None) or \
            (self.netvm.gateway if self.netvm else None)

    @vanir.stateless_property
    def visible_gateway6(self):
        '''Default (IPv6) gateway of this domain as seen by the domain.'''
        if self.features.check_with_netvm('ipv6', False):
            return self.netvm.gateway6 if self.netvm else None
        return None

    @vanir.stateless_property
    def visible_netmask(self):
        '''Netmask as seen by the domain.'''
        return self.features.check_with_template('net.fake-netmask', None) or \
            (self.netvm.netmask if self.netvm else None)

    #
    # used in netvms (provides_network=True)
    # those properties and methods are most likely accessed as vm.netvm.<prop>
    #

    @staticmethod
    def get_ip_for_vm(vm):
        '''Get IP address for (appvm) domain connected to this (netvm) domain.
        '''
        import vanir.vm.dispvm  # pylint: disable=redefined-outer-name
        if isinstance(vm, vanir.vm.dispvm.DispVM):
            return ipaddress.IPv4Address('10.138.{}.{}'.format(
                (vm.dispid >> 8) & 0xff, vm.dispid & 0xff))

        # VM technically can get address which ends in '.0'. This currently
        # does not happen, because qid < 253, but may happen in the future.
        return ipaddress.IPv4Address('10.137.{}.{}'.format(
            (vm.qid >> 8) & 0xff, vm.qid & 0xff))

    @staticmethod
    def get_ip6_for_vm(vm):
        '''Get IPv6 address for (appvm) domain connected to this (netvm) domain.
        Default address is constructed with vanir-specific site-local prefix,
        and IPv4 suffix (0xa89 is 10.137.).
        '''
        import vanir.vm.dispvm  # pylint: disable=redefined-outer-name
        if isinstance(vm, vanir.vm.dispvm.DispVM):
            return ipaddress.IPv6Address('{}::a8a:{:x}'.format(
                vanir.config.vanir_ipv6_prefix, vm.dispid))

        return ipaddress.IPv6Address('{}::a89:{:x}'.format(
            vanir.config.vanir_ipv6_prefix, vm.qid))

    @vanir.stateless_property
    def gateway(self):
        '''Gateway for other domains that use this domain as netvm.'''
        return self.visible_ip if self.provides_network else None

    @vanir.stateless_property
    def gateway6(self):
        '''Gateway (IPv6) for other domains that use this domain as netvm.'''
        if self.features.check_with_netvm('ipv6', False):
            return self.visible_ip6 if self.provides_network else \
                None
        return None

    @property
    def netmask(self):
        '''Netmask for gateway address.'''
        return '255.255.255.255' if self.is_networked() else None

    @property
    def connected_vms(self):
        ''' Return a generator containing all domains connected to the current
            NetVM.
        '''
        for vm in self.app.domains:
            if getattr(vm, 'netvm', None) is self:
                yield vm

    #
    # used in both
    #

    @property
    def dns(self):
        '''Secondary DNS server set up for this domain.'''
        if self.netvm is not None or self.provides_network:
            return (
                '10.139.1.0',
                '10.139.1.1',
                '10.139.1.2',
                '10.139.1.3',
                '10.139.1.4',
                '10.139.1.5',
            )

        return None

    def __init__(self, *args, **kwargs):
        self._firewall = None
        super(NetVMMixin, self).__init__(*args, **kwargs)

    @vanir.events.handler('domain-load')
    def on_domain_load_netvm_loop_check(self, event):
        # pylint: disable=unused-argument
        # make sure there are no netvm loops - which could cause vanirsd
        # looping infinitely
        if self is self.netvm:
            self.log.error(
                'vm \'%s\' network-connected to itself, breaking the '
                'connection', self.name)
            self.netvm = None
        elif self.netvm in self.app.domains.get_vms_connected_to(self):
            self.log.error(
                'netvm loop detected on \'%s\', breaking the connection',
                self.name)
            self.netvm = None

    @vanir.events.handler('domain-shutdown')
    def on_domain_shutdown(self, event, **kwargs):
        '''Cleanup network interfaces of connected, running VMs.
        This will allow re-reconnecting them cleanly later.
        '''
        # pylint: disable=unused-argument
        for vm in self.connected_vms:
            if not vm.is_running():
                continue
            try:
                vm.detach_network()
            except (vanir.exc.VanirException, libvirt.libvirtError):
                # ignore errors
                pass

    @vanir.events.handler('domain-start')
    def on_domain_started(self, event, **kwargs):
        '''Connect this domain to its downstream domains. Also reload firewall
        in its netvm.
        This is needed when starting netvm *after* its connected domains.
        '''  # pylint: disable=unused-argument

        if self.netvm:
            self.netvm.reload_firewall_for_vm(self)  # pylint: disable=no-member

        for vm in self.connected_vms:
            if not vm.is_running():
                continue
            vm.log.info('Attaching network')
            try:
                vm.attach_network()
            except (vanir.exc.VanirException, libvirt.libvirtError):
                vm.log.warning('Cannot attach network', exc_info=1)

    @vanir.events.handler('domain-pre-shutdown')
    def on_domain_pre_shutdown(self, event, force=False):
        ''' Checks before NetVM shutdown if any connected domains are running.
            If `force` is `True` tries to detach network interfaces of connected
            vms
        '''  # pylint: disable=unused-argument

        connected_vms = [vm for vm in self.connected_vms if vm.is_running()]
        if connected_vms and not force:
            raise vanir.exc.VanirVMError(
                self, 'There are other VMs connected to this VM: {}'.format(
                    ', '.join(vm.name for vm in connected_vms)))

    def attach_network(self):
        '''Attach network in this machine to it's netvm.'''

        if not self.is_running():
            raise vanir.exc.VanirVMNotRunningError(self)
        if self.netvm is None:
            raise vanir.exc.VanirVMError(
                self, 'netvm should not be {}'.format(self.netvm))

        if not self.netvm.is_running():  # pylint: disable=no-member
            # pylint: disable=no-member
            self.log.info('Starting NetVM ({0})'.format(self.netvm.name))
            self.netvm.start()

        self.netvm.set_mapped_ip_info_for_vm(self)
        self.libvirt_domain.attachDevice(
            self.app.env.get_template('libvirt/devices/net.xml').render(
                vm=self))

    def detach_network(self):
        '''Detach machine from it's netvm'''

        if not self.is_running():
            raise vanir.exc.VanirVMNotRunningError(self)
        if self.netvm is None:
            raise vanir.exc.VanirVMError(
                self, 'netvm should not be {}'.format(self.netvm))

        self.libvirt_domain.detachDevice(
            self.app.env.get_template('libvirt/devices/net.xml').render(
                vm=self))

    def is_networked(self):
        '''Check whether this VM can reach network (firewall notwithstanding).
        :returns: :py:obj:`True` if is machine can reach network, \
            :py:obj:`False` otherwise.
        :rtype: bool
        '''

        if self.provides_network:
            return True

        return self.netvm is not None

    def reload_firewall_for_vm(self, vm):
        ''' Reload the firewall rules for the vm '''
        if not self.is_running():
            return

        for addr_family in (4, 6):
            ip = vm.ip6 if addr_family == 6 else vm.ip
            if ip is None:
                continue
            base_dir = '/vanir-firewall/{}/'.format(ip)
            # remove old entries if any (but don't touch base empty entry - it
            # would trigger reload right away
            self.untrusted_qdb.rm(base_dir)
            # write new rules
            for key, value in vm.firewall.qdb_entries(
                    addr_family=addr_family).items():
                self.untrusted_qdb.write(base_dir + key, value)
            # signal its done
            self.untrusted_qdb.write(base_dir[:-1], '')

    def set_mapped_ip_info_for_vm(self, vm):
        '''
        Set configuration to possibly hide real IP from the VM.
        This needs to be done before executing 'script'
        (`/etc/xen/scripts/vif-route-vanir`) in network providing VM
        '''
        # add info about remapped IPs (VM IP hidden from the VM itself)
        mapped_ip_base = '/mapped-ip/{}'.format(vm.ip)
        if vm.visible_ip:
            self.untrusted_qdb.write(mapped_ip_base + '/visible-ip',
                                     str(vm.visible_ip))
        else:
            self.untrusted_qdb.rm(mapped_ip_base + '/visible-ip')
        if vm.visible_gateway:
            self.untrusted_qdb.write(mapped_ip_base + '/visible-gateway',
                                     str(vm.visible_gateway))
        else:
            self.untrusted_qdb.rm(mapped_ip_base + '/visible-gateway')

    @vanir.events.handler('property-pre-del:netvm')
    def on_property_pre_del_netvm(self, event, name, oldvalue=None):
        ''' Sets the the NetVM to default NetVM '''
        # pylint: disable=unused-argument
        # we are changing to default netvm
        newvalue = type(self).netvm.get_default(self)
        # check for netvm loop
        _setter_netvm(self, type(self).netvm, newvalue)
        if newvalue == oldvalue:
            return
        self.fire_event('property-pre-set:netvm',
                        pre_event=True,
                        name='netvm',
                        newvalue=newvalue,
                        oldvalue=oldvalue)

    @vanir.events.handler('property-del:netvm')
    def on_property_del_netvm(self, event, name, oldvalue=None):
        ''' Sets the the NetVM to default NetVM '''
        # pylint: disable=unused-argument
        # we are changing to default netvm
        newvalue = self.netvm
        if newvalue == oldvalue:
            return
        self.fire_event('property-set:netvm',
                        name='netvm',
                        newvalue=newvalue,
                        oldvalue=oldvalue)

    @vanir.events.handler('property-pre-set:netvm')
    def on_property_pre_set_netvm(self, event, name, newvalue, oldvalue=None):
        ''' Run sanity checks before setting a new NetVM '''
        # pylint: disable=unused-argument
        if newvalue is not None:
            if not self.app.vmm.offline_mode \
                    and self.is_running() and not newvalue.is_running():
                raise vanir.exc.VanirVMNotStartedError(
                    newvalue,
                    'Cannot dynamically attach to stopped NetVM: {!r}'.format(
                        newvalue))

        # don't check oldvalue, because it's missing if it was default
        if self.netvm is not None:
            if self.is_running():
                self.detach_network()

    @vanir.events.handler('property-set:netvm')
    def on_property_set_netvm(self, event, name, newvalue, oldvalue=None):
        ''' Replaces the current NetVM with a new one and fires
            net-domain-connect event
        '''
        # pylint: disable=unused-argument

        if newvalue is None:
            return

        if self.is_running():
            # refresh IP, DNS etc
            self.create_qdb_entries()
            self.attach_network()

            newvalue.fire_event('net-domain-connect', vm=self)

    @vanir.events.handler('net-domain-connect')
    def on_net_domain_connect(self, event, vm):
        ''' Reloads the firewall config for vm '''
        # pylint: disable=unused-argument
        self.reload_firewall_for_vm(vm)

    @vanir.events.handler('domain-qdb-create')
    def on_domain_qdb_create(self, event):
        ''' Fills the VanirDB with firewall entries. '''
        # pylint: disable=unused-argument
        for vm in self.connected_vms:
            if vm.is_running():
                # keep in sync with on_firewall_changed
                self.set_mapped_ip_info_for_vm(vm)
                self.reload_firewall_for_vm(vm)

    @vanir.events.handler('firewall-changed', 'domain-spawn')
    def on_firewall_changed(self, event, **kwargs):
        ''' Reloads the firewall if vm is running and has a NetVM assigned '''
        # pylint: disable=unused-argument
        if self.is_running() and self.netvm:
            self.netvm.set_mapped_ip_info_for_vm(self)
            self.netvm.reload_firewall_for_vm(self)  # pylint: disable=no-member

    # CORE2: swallowed get_firewall_conf, write_firewall_conf,
    # get_firewall_defaults
    @property
    def firewall(self):
        if self._firewall is None:
            self._firewall = vanir.firewall.Firewall(self)
        return self._firewall

    def has_firewall(self):
        ''' Return `True` if there are some vm specific firewall rules set '''
        return os.path.exists(os.path.join(self.dir_path, self.firewall_conf))
Пример #11
0
class vanir(vanir.PropertyHolder):
    '''Main vanir application
    :param str store: path to ``vanir.xml``
    The store is loaded in stages:
    1.  In the first stage there are loaded some basic features from store
        (currently labels).
    2.  In the second stage stubs for all VMs are loaded. They are filled
        with their basic properties, like ``qid`` and ``name``.
    3.  In the third stage all global properties are loaded. They often
        reference VMs, like default netvm, so they should be filled after
        loading VMs.
    4.  In the fourth stage all remaining VM properties are loaded. They
        also need all VMs loaded, because they represent dependencies
        between VMs like aforementioned netvm.
    5.  In the fifth stage there are some fixups to ensure sane system
        operation.
    This class emits following events:
        .. event:: domain-add (subject, event, vm)
            When domain is added.
            :param subject: Event emitter
            :param event: Event name (``'domain-add'``)
            :param vm: Domain object
        .. event:: domain-pre-delete (subject, event, vm)
            When domain is deleted. VM still has reference to ``app`` object,
            and is contained within VMCollection. You may prevent removal by
            raising an exception.
            :param subject: Event emitter
            :param event: Event name (``'domain-pre-delete'``)
            :param vm: Domain object
        .. event:: domain-delete (subject, event, vm)
            When domain is deleted. VM still has reference to ``app`` object,
            but is not contained within VMCollection.
            :param subject: Event emitter
            :param event: Event name (``'domain-delete'``)
            :param vm: Domain object
        .. event:: pool-add (subject, event, pool)
            When storage pool is added.
            Handler for this event can be asynchronous (a coroutine).
            :param subject: Event emitter
            :param event: Event name (``'pool-add'``)
            :param pool: Pool object
        .. event:: pool-pre-delete (subject, event, pool)
            When pool is deleted. Pool is still contained within app.pools
            dictionary. You may prevent removal by raising an exception.
            Handler for this event can be asynchronous (a coroutine).
            :param subject: Event emitter
            :param event: Event name (``'pool-pre-delete'``)
            :param pool: Pool object
        .. event:: pool-delete (subject, event, pool)
            When storage pool is deleted. The pool is already removed at this
            point.
            Handler for this event can be asynchronous (a coroutine).
            :param subject: Event emitter
            :param event: Event name (``'pool-delete'``)
            :param pool: Pool object
    Methods and attributes:
    '''

    default_netvm = vanir.VMProperty('default_netvm', load_stage=3,
        default=None, allow_none=True,
        setter=_setter_default_netvm,
        doc='''Default NetVM for AppVMs. Initial state is `None`, which means
            that AppVMs are not connected to the Internet.''')
    default_template = vanir.VMProperty('default_template', load_stage=3,
        vmclass=vanir.vm.templatevm.TemplateVM,
        doc='Default template for new AppVMs',
        allow_none=True)
    updatevm = vanir.VMProperty('updatevm', load_stage=3,
        default=None, allow_none=True,
        doc='''Which VM to use as `yum` proxy for updating AdminVM and
            TemplateVMs''')
    clockvm = vanir.VMProperty('clockvm', load_stage=3,
        default=None, allow_none=True,
        doc='Which VM to use as NTP proxy for updating AdminVM')
    default_kernel = vanir.property('default_kernel', load_stage=3,
        doc='Which kernel to use when not overriden in VM')
    default_dispvm = vanir.VMProperty('default_dispvm', load_stage=3,
        default=None,
        doc='Default DispVM base for service calls', allow_none=True)

    management_dispvm = vanir.VMProperty('management_dispvm', load_stage=3,
        default=None,
        doc='Default DispVM base for managing VMs', allow_none=True)

    default_pool = vanir.property('default_pool', load_stage=3,
        default=_default_pool,
        setter=_setter_pool,
        doc='Default storage pool')

    default_pool_private = vanir.property('default_pool_private', load_stage=3,
        default=lambda app: app.default_pool,
        setter=_setter_pool,
        doc='Default storage pool for private volumes')

    default_pool_root = vanir.property('default_pool_root', load_stage=3,
        default=lambda app: app.default_pool,
        setter=_setter_pool,
        doc='Default storage pool for root volumes')

    default_pool_volatile = vanir.property('default_pool_volatile',
        load_stage=3,
        default=lambda app: app.default_pool,
        setter=_setter_pool,
        doc='Default storage pool for volatile volumes')

    default_pool_kernel = vanir.property('default_pool_kernel', load_stage=3,
        default=lambda app: app.default_pool,
        setter=_setter_pool,
        doc='Default storage pool for kernel volumes')

    default_qrexec_timeout = vanir.property('default_qrexec_timeout',
        load_stage=3,
        default=60,
        type=int,
        doc='''Default time in seconds after which qrexec connection attempt is
            deemed failed''')

    default_shutdown_timeout = vanir.property('default_shutdown_timeout',
        load_stage=3,
        default=60,
        type=int,
        doc='''Default time in seconds for VM shutdown to complete''')

    stats_interval = vanir.property('stats_interval',
        default=3,
        type=int,
        doc='Interval in seconds for VM stats reporting (memory, CPU usage)')

    # TODO #1637 #892
    check_updates_vm = vanir.property('check_updates_vm',
        type=bool, setter=vanir.property.bool,
        default=True,
        doc='check for updates inside vanir')

    def __init__(self, store=None, load=True, offline_mode=None, lock=False,
            **kwargs):
        #: logger instance for logging global messages
        self.log = logging.getLogger('app')
        self.log.debug('init() -> %#x', id(self))
        self.log.debug('stack:')
        for frame in traceback.extract_stack():
            self.log.debug('%s', frame)

        self._extensions = vanir.ext.get_extensions()

        #: collection of all VMs managed by this vanir instance
        self.domains = VMCollection(self)

        #: collection of all available labels for VMs
        self.labels = {}

        #: collection of all pools
        self.pools = {}

        #: Connection to VMM
        self.vmm = VMMConnection(offline_mode=offline_mode)

        #: Information about host system
        self.host = VanirHost(self)

        if store is not None:
            self._store = store
        else:
            self._store = os.environ.get('VANIR_XML_PATH',
                os.path.join(
                    vanir.config.vanir_base_dir,
                    vanir.config.system_path['vanir_store_filename']))

        super(vanir, self).__init__(xml=None, **kwargs)

        self.__load_timestamp = None
        self.__locked_fh = None
        self._domain_event_callback_id = None

        #: jinja2 environment for libvirt XML templates
        self.env = jinja2.Environment(
            loader=jinja2.FileSystemLoader([
                '/etc/vanir/templates',
                '/usr/share/vanir/templates',
            ]),
            undefined=jinja2.StrictUndefined)

        if load:
            self.load(lock=lock)

        self.events_enabled = True

    @property
    def store(self):
        return self._store

    def _migrate_global_properties(self):
        '''Migrate renamed/dropped properties'''
        if self.xml is None:
            return

        # drop default_fw_netvm
        node_default_fw_netvm = self.xml.find(
            './properties/property[@name=\'default_fw_netvm\']')
        if node_default_fw_netvm is not None:
            node_default_netvm = self.xml.find(
                './properties/property[@name=\'default_netvm\']')
            try:
                default_fw_netvm = self.domains[node_default_fw_netvm.text]
                if node_default_netvm is None:
                    default_netvm = None
                else:
                    default_netvm = self.domains[node_default_netvm.text]
                if default_netvm != default_fw_netvm:
                    for vm in self.domains:
                        if not hasattr(vm, 'netvm'):
                            continue
                        if not getattr(vm, 'provides_network', False):
                            continue
                        node_netvm = vm.xml.find(
                            './properties/property[@name=\'netvm\']')
                        if node_netvm is not None:
                            # non-default netvm
                            continue
                        # this will unfortunately break "being default"
                        # property state, but the alternative (changing
                        # value behind user's back) is worse
                        properties = vm.xml.find('./properties')
                        element = lxml.etree.Element('property',
                            name='netvm')
                        element.text = default_fw_netvm.name
                        # manipulate xml directly, before loading netvm
                        # property, to avoid hitting netvm loop detection
                        properties.append(element)
            except KeyError:
                # if default_fw_netvm was set to invalid value, simply
                # drop it
                pass
            node_default_fw_netvm.getparent().remove(node_default_fw_netvm)

    def load(self, lock=False):
        '''Open vanir.xml
        :throws EnvironmentError: failure on parsing store
        :throws xml.parsers.expat.ExpatError: failure on parsing store
        :raises lxml.etree.XMLSyntaxError: on syntax error in vanir.xml
        '''

        fh = self._acquire_lock()
        self.xml = lxml.etree.parse(fh)

        # stage 1: load labels and pools
        for node in self.xml.xpath('./labels/label'):
            label = vanir.Label.fromxml(node)
            self.labels[label.index] = label

        for node in self.xml.xpath('./pools/pool'):
            name = node.get('name')
            assert name, "Pool name '%s' is invalid " % name
            try:
                self.pools[name] = self._get_pool(**node.attrib)
            except vanir.exc.VanirException as e:
                self.log.error(str(e))

        # stage 2: load VMs
        for node in self.xml.xpath('./domains/domain'):
            # pylint: disable=no-member
            cls = self.get_vm_class(node.get('class'))
            vm = cls(self, node)
            vm.load_properties(load_stage=2)
            vm.init_log()
            self.domains.add(vm, _enable_events=False)

        if 0 not in self.domains:
            self.domains.add(
                vanir.vm.adminvm.AdminVM(self, None),
                _enable_events=False)

        self._migrate_global_properties()

        # stage 3: load global properties
        self.load_properties(load_stage=3)

        # stage 4: fill all remaining VM properties
        for vm in self.domains:
            vm.load_properties(load_stage=4)
            vm.load_extras()

        # stage 5: misc fixups

        self.property_require('default_netvm', allow_none=True)
        self.property_require('default_template')
        self.property_require('clockvm', allow_none=True)
        self.property_require('updatevm', allow_none=True)

        for vm in self.domains:
            vm.events_enabled = True
            vm.fire_event('domain-load')

        # get a file timestamp (before closing it - still holding the lock!),
        #  to detect whether anyone else have modified it in the meantime
        self.__load_timestamp = os.path.getmtime(self._store)

        if not lock:
            self._release_lock()


    def __xml__(self):
        element = lxml.etree.Element('vanir')

        element.append(self.xml_labels())

        pools_xml = lxml.etree.Element('pools')
        for pool in self.pools.values():
            xml = pool.__xml__()
            if xml is not None:
                pools_xml.append(xml)

        element.append(pools_xml)

        element.append(self.xml_properties())

        domains = lxml.etree.Element('domains')
        for vm in self.domains:
            domains.append(vm.__xml__())
        element.append(domains)

        return element

    def __str__(self):
        return type(self).__name__

    def save(self, lock=True):
        '''Save all data to vanir.xml
        There are several problems with saving :file:`vanir.xml` which must be
        mitigated:
        - Running out of disk space. No space left should not result in empty
          file. This is done by writing to temporary file and then renaming.
        - Attempts to write two or more files concurrently. This is done by
          sophisticated locking.
        :param bool lock: keep file locked after saving
        :throws EnvironmentError: failure on saving
        '''

        if not self.__locked_fh:
            self._acquire_lock(for_save=True)

        fh_new = tempfile.NamedTemporaryFile(
            prefix=self._store, delete=False)
        lxml.etree.ElementTree(self.__xml__()).write(
            fh_new, encoding='utf-8', pretty_print=True)
        fh_new.flush()
        try:
            os.chown(fh_new.name, -1, grp.getgrnam('vanir').gr_gid)
            os.chmod(fh_new.name, 0o660)
        except KeyError:  # group 'vanir' not found
            # don't change mode if no 'vanir' group in the system
            pass
        os.rename(fh_new.name, self._store)

        # update stored mtime, in case of multiple save() calls without
        # loading vanir.xml again
        self.__load_timestamp = os.path.getmtime(self._store)

        # this releases lock for all other processes,
        # but they should instantly block on the new descriptor
        self.__locked_fh.close()
        self.__locked_fh = fh_new

        if not lock:
            self._release_lock()


    def close(self):
        '''Deconstruct the object and break circular references
        After calling this the object is unusable, not even for saving.'''

        self.log.debug('close() <- %#x', id(self))
        for frame in traceback.extract_stack():
            self.log.debug('%s', frame)

        super().close()

        if self._domain_event_callback_id is not None:
            self.vmm.libvirt_conn.domainEventDeregisterAny(
                self._domain_event_callback_id)
            self._domain_event_callback_id = None

        # Only our Lord, The God Almighty, knows what references
        # are kept in extensions.
        del self._extensions

        for vm in self.domains:
            vm.close()
        self.domains.close()
        del self.domains

        self.vmm.close()
        del self.vmm

        del self.host

        if self.__locked_fh:
            self._release_lock()


    def _acquire_lock(self, for_save=False):
        assert self.__locked_fh is None, 'double lock'

        while True:
            try:
                fd = os.open(self._store,
                    os.O_RDWR | (os.O_CREAT * int(for_save)))
            except FileNotFoundError:
                if not for_save:
                    raise vanir.exc.VanirException(
                        'vanir XML store {!r} is missing; '
                        'use vanir-create tool'.format(self._store))
                raise

            # While we were waiting for lock, someone could have unlink()ed
            # (or rename()d) our file out of the filesystem. We have to
            # ensure we got lock on something linked to filesystem.
            # If not, try again.
            if os.fstat(fd) != os.stat(self._store):
                os.close(fd)
                continue

            if self.__load_timestamp and \
                    os.path.getmtime(self._store) != self.__load_timestamp:
                os.close(fd)
                raise vanir.exc.VanirException(
                    'Someone else modified vanir.xml in the meantime')

            break

        if os.name == 'posix':
            fcntl.lockf(fd, fcntl.LOCK_EX)
        elif os.name == 'nt':
            # pylint: disable=protected-access
            overlapped = pywintypes.OVERLAPPED()
            win32file.LockFileEx(
                win32file._get_osfhandle(fd),
                win32con.LOCKFILE_EXCLUSIVE_LOCK, 0, -0x10000, overlapped)

        self.__locked_fh = os.fdopen(fd, 'r+b')
        return self.__locked_fh


    def _release_lock(self):
        assert self.__locked_fh is not None, 'double release'

        # intentionally do not call explicit unlock to not unlock the file
        # before all buffers are flushed
        self.__locked_fh.close()
        self.__locked_fh = None


    def load_initial_values(self):
        self.labels = {
            1: vanir.Label(1, '0xcc0000', 'red'),
            2: vanir.Label(2, '0xf57900', 'orange'),
            3: vanir.Label(3, '0xedd400', 'yellow'),
            4: vanir.Label(4, '0x73d216', 'green'),
            5: vanir.Label(5, '0x555753', 'gray'),
            6: vanir.Label(6, '0x3465a4', 'blue'),
            7: vanir.Label(7, '0x75507b', 'purple'),
            8: vanir.Label(8, '0x000000', 'black'),
        }
        assert max(self.labels.keys()) == vanir.config.max_default_label

        pool_configs = copy.deepcopy(vanir.config.defaults['pool_configs'])

        root_volume_group, root_thin_pool = \
            vanir.storage.DirectoryThinPool.thin_pool('/')
        if root_thin_pool:
            lvm_config = {
                'name': 'lvm',
                'driver': 'lvm_thin',
                'volume_group': root_volume_group,
                'thin_pool': root_thin_pool
            }
            pool_configs[lvm_config['name']] = lvm_config

        for name, config in pool_configs.items():
            if 'driver' not in config and 'dir_path' in config:
                config['driver'] = 'file'
                try:
                    os.makedirs(config['dir_path'], exist_ok=True)
                    if vanir.storage.reflink.is_supported(config['dir_path']):
                        config['driver'] = 'file-reflink'
                        config['setup_check'] = 'no'  # don't check twice
                except PermissionError:  # looks like a testing environment
                    pass  # stay with 'file'
            self.pools[name] = self._get_pool(**config)

        self.default_pool_kernel = 'linux-kernel'

        self.domains.add(
            vanir.vm.adminvm.AdminVM(self, None, label='black'))

    @classmethod
    def create_empty_store(cls, *args, **kwargs):
        self = cls(*args, load=False, **kwargs)
        if os.path.exists(self.store):
            raise vanir.exc.VanirException(
                '{} already exists, aborting'.format(self.store))
        self.load_initial_values()
        # TODO py3 get lock= as keyword-only arg
        self.save(kwargs.get('lock'))

        return self


    def xml_labels(self):
        '''Serialise labels
        :rtype: lxml.etree._Element
        '''

        labels = lxml.etree.Element('labels')
        for label in sorted(self.labels.values(), key=lambda labl: labl.index):
            labels.append(label.__xml__())
        return labels

    @staticmethod
    def get_vm_class(clsname):
        '''Find the class for a domain.
        Classes are registered as setuptools' entry points in ``vanir.vm``
        group. Any package may supply their own classes.
        :param str clsname: name of the class
        :return type: class
        '''

        try:
            return vanir.utils.get_entry_point_one(
                vanir.vm.VM_ENTRY_POINT, clsname)
        except KeyError:
            raise vanir.exc.VanirException(
                'no such VM class: {!r}'.format(clsname))
        # don't catch TypeError

    def add_new_vm(self, cls, qid=None, **kwargs):
        '''Add new Virtual Machine to collection
        '''

        if qid is None:
            qid = self.domains.get_new_unused_qid()

        if isinstance(cls, str):
            cls = self.get_vm_class(cls)
        # handle default template; specifically allow template=None (do not
        # override it with default template)
        if 'template' not in kwargs and hasattr(cls, 'template'):
            if cls == self.get_vm_class('DispVM'):
                kwargs['template'] = self.default_dispvm
            else:
                kwargs['template'] = self.default_template
            if kwargs['template'] is None:
                raise vanir.exc.VanirValueError(
                    'Template for the Vanir not specified, nor default '
                    'template set.')
        elif 'template' in kwargs and isinstance(kwargs['template'], str):
            kwargs['template'] = self.domains[kwargs['template']]

        return self.domains.add(cls(self, None, qid=qid, **kwargs))

    def get_label(self, label):
        '''Get label as identified by index or name
        :throws KeyError: when label is not found
        '''

        # first search for index, verbatim
        try:
            return self.labels[label]
        except KeyError:
            pass

        # then search for name
        for i in self.labels.values():
            if i.name == label:
                return i

        # last call, if label is a number represented as str, search in indices
        try:
            return self.labels[int(label)]
        except (KeyError, ValueError):
            pass

        raise KeyError(label)

    def setup_pools(self):
        """ Run implementation specific setup for each storage pool. """
        for pool in self.pools.values():
            pool.setup()

    @asyncio.coroutine
    def add_pool(self, name, **kwargs):
        """ Add a storage pool to config."""

        if name in self.pools.keys():
            raise vanir.exc.VanirException('pool named %s already exists \n' %
                                           name)

        kwargs['name'] = name
        pool = self._get_pool(**kwargs)
        ret = pool.setup()
        if asyncio.iscoroutine(ret):
            yield from ret
        self.pools[name] = pool
        yield from self.fire_event_async('pool-add', pool=pool)
        return pool

    @asyncio.coroutine
    def remove_pool(self, name):
        """ Remove a storage pool from config file.  """
        try:
            pool = self.pools[name]
            volumes = [(vm, volume) for vm in self.domains
                for volume in vm.volumes.values()
                    if volume.pool is pool]
            if volumes:
                raise vanir.exc.VanirPoolInUseError(pool)
            prop_suffixes = ['', '_kernel', '_private', '_root', '_volatile']
            for suffix in prop_suffixes:
                if getattr(self, 'default_pool' + suffix, None) is pool:
                    raise vanir.exc.VanirPoolInUseError(pool,
                        'Storage pool is in use: set as {}'.format(
                            'default_pool' + suffix))
            yield from self.fire_event_async('pool-pre-delete',
                pre_event=True, pool=pool)
            del self.pools[name]
            ret = pool.destroy()
            if asyncio.iscoroutine(ret):
                yield from ret
            yield from self.fire_event_async('pool-delete', pool=pool)
        except KeyError:
            return


    def get_pool(self, pool):
        '''  Returns a :py:class:`vanir.storage.Pool` instance '''
        if isinstance(pool, vanir.storage.Pool):
            return pool
        try:
            return self.pools[pool]
        except KeyError:
            raise vanir.exc.VanirException('Unknown storage pool ' + pool)

    @staticmethod
    def _get_pool(**kwargs):
        try:
            name = kwargs['name']
            assert name, 'Name needs to be an non empty string'
        except KeyError:
            raise vanir.exc.VanirException('No pool name for pool')

        try:
            driver = kwargs['driver']
        except KeyError:
            raise vanir.exc.VanirException('No driver specified for pool ' +
                                           name)
        try:
            klass = vanir.utils.get_entry_point_one(
                vanir.storage.STORAGE_ENTRY_POINT, driver)
            del kwargs['driver']
            return klass(**kwargs)
        except KeyError:
            raise vanir.exc.VanirException('No driver %s for pool %s' %
                                           (driver, name))

    def register_event_handlers(self):
        '''Register libvirt event handlers, which will translate libvirt
        events into vanir.events. This function should be called only in
        'vanirsd' process and only when mainloop has been already set.
        '''
        self._domain_event_callback_id = (
            self.vmm.libvirt_conn.domainEventRegisterAny(
                None,  # any domain
                libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE,
                self._domain_event_callback,
                None))

    def _domain_event_callback(self, _conn, domain, event, _detail, _opaque):
        '''Generic libvirt event handler (virConnectDomainEventCallback),
        translate libvirt event into vanir.events.
        '''
        if not self.events_enabled:
            return

        try:
            vm = self.domains[domain.name()]
        except KeyError:
            # ignore events for unknown domains
            return

        if event == libvirt.VIR_DOMAIN_EVENT_STOPPED:
            vm.on_libvirt_domain_stopped()
        elif event == libvirt.VIR_DOMAIN_EVENT_SUSPENDED:
            try:
                vm.fire_event('domain-paused')
            except Exception:  # pylint: disable=broad-except
                self.log.exception(
                    'Uncaught exception from domain-paused handler '
                    'for domain %s', vm.name)
        elif event == libvirt.VIR_DOMAIN_EVENT_RESUMED:
            try:
                vm.fire_event('domain-unpaused')
            except Exception:  # pylint: disable=broad-except
                self.log.exception(
                    'Uncaught exception from domain-unpaused handler '
                    'for domain %s', vm.name)

    @vanir.events.handler('domain-pre-delete')
    def on_domain_pre_deleted(self, event, vm):
        # pylint: disable=unused-argument
        for obj in itertools.chain(self.domains, (self,)):
            if obj is vm:
                # allow removed VM to reference itself
                continue
            for prop in obj.property_list():
                try:
                    if isinstance(prop, vanir.vm.VMProperty) and \
                            getattr(obj, prop.__name__) == vm:
                        self.log.error(
                            'Cannot remove %s, used by %s.%s',
                            vm, obj, prop.__name__)
                        raise vanir.exc.VanirVMInUseError(vm, 'Domain is in '
                        'use: {!r}; see /var/log/vanir/vanir.log in dom0 for '
                        'details'.format(vm.name))
                except AttributeError:
                    pass

    @vanir.events.handler('domain-delete')
    def on_domain_deleted(self, event, vm):
        # pylint: disable=unused-argument
        for propname in (
                'default_netvm',
                'default_fw_netvm',
                'clockvm',
                'updatevm',
                'default_template',
                ):
            try:
                if getattr(self, propname) == vm:
                    delattr(self, propname)
            except AttributeError:
                pass


    @vanir.events.handler('property-pre-set:clockvm')
    def on_property_pre_set_clockvm(self, event, name, newvalue, oldvalue=None):
        # pylint: disable=unused-argument,no-self-use
        if newvalue is None:
            return
        if 'service.clocksync' not in newvalue.features:
            newvalue.features['service.clocksync'] = True

    @vanir.events.handler('property-set:clockvm')
    def on_property_set_clockvm(self, event, name, newvalue, oldvalue=None):
        # pylint: disable=unused-argument,no-self-use
        if oldvalue and oldvalue.features.get('service.clocksync', False):
            del oldvalue.features['service.clocksync']

    @vanir.events.handler('property-pre-set:default_netvm')
    def on_property_pre_set_default_netvm(self, event, name, newvalue,
            oldvalue=None):
        # pylint: disable=unused-argument,invalid-name
        if newvalue is not None and oldvalue is not None \
                and oldvalue.is_running() and not newvalue.is_running() \
                and self.domains.get_vms_connected_to(oldvalue):
            raise vanir.exc.VanirVMNotRunningError(newvalue,
                'Cannot change {!r} to domain that '
                'is not running ({!r}).'.format(name, newvalue.name))


    @vanir.events.handler('property-set:default_fw_netvm')
    def on_property_set_default_fw_netvm(self, event, name, newvalue,
            oldvalue=None):
        # pylint: disable=unused-argument,invalid-name
        for vm in self.domains:
            if hasattr(vm, 'provides_network') and vm.provides_network and \
                    hasattr(vm, 'netvm') and vm.property_is_default('netvm'):
                # fire property-del:netvm as it is responsible for resetting
                # netvm to it's default value
                vm.fire_event('property-pre-del:netvm', pre_event=True,
                    name='netvm', oldvalue=oldvalue)
                vm.fire_event('property-del:netvm',
                    name='netvm', oldvalue=oldvalue)


    @vanir.events.handler('property-set:default_netvm')
    def on_property_set_default_netvm(self, event, name, newvalue,
            oldvalue=None):
        # pylint: disable=unused-argument
        for vm in self.domains:
            if hasattr(vm, 'provides_network') and not vm.provides_network and \
                    hasattr(vm, 'netvm') and vm.property_is_default('netvm'):
                # fire property-del:netvm as it is responsible for resetting
                # netvm to it's default value
                vm.fire_event('property-pre-del:netvm', pre_event=True,
                    name='netvm', oldvalue=oldvalue)
                vm.fire_event('property-del:netvm',
                    name='netvm', oldvalue=oldvalue)
Пример #12
0
class TestVM(vanir.vm.BaseVM):
    qid = vanir.property('qid', type=int)
    name = vanir.property('name')
    testprop = vanir.property('testprop')
    testlabel = vanir.property('testlabel')
    defaultprop = vanir.property('defaultprop', default='defaultvalue')