示例#1
0
class MyModel(models.Driver):

    field = base.ParamField(default=10)
    number = base.ParamField(default=None, choices=(None, 'one', 'two'))
    multi = base.ParamMultiField(
        sub1=base.ParamField(default='abc'),
        sub2=base.ParamField(default=15, choices=(11, 13, 15)),
    )
示例#2
0
class DiskDevice(base.ParamedModel):
    class Meta(object):
        db_table = 'devops_diskdevice'
        app_label = 'devops'

    node = models.ForeignKey('Node', null=False)
    volume = models.ForeignKey('Volume', null=True)

    # TODO(astudenov): temporarily added for ipmi driver
    # and driverless testcase. These fields should be removed
    # after refactoring of volume section in themplate
    device = base.ParamField()
    type = base.ParamField()
    bus = base.ParamField()
    target_dev = base.ParamField()
class IronicDriver(driver.Driver):
    """Ironic driver

    Note: This class is imported as Driver at .__init__.py
    """

    os_auth_token = base.ParamField()
    ironic_url = base.ParamField(default="http://localhost:6385/")

    agent_kernel_url = base.ParamField()
    agent_ramdisk_url = base.ParamField()

    @property
    def conn(self):
        """Connection to ironic api"""
        logger.debug("Ironic client is connecting to {0}".format(
            self.ironic_url))
        kwargs = {
            'os_auth_token': self.os_auth_token,
            'ironic_url': self.ironic_url
        }
        return client.get_client(1, **kwargs)
class IronicVolume(volume.Volume):
    """Note: This class is imported as Volume at .__init__.py """

    capacity = base.ParamField(default=None)  # in gigabytes
    format = base.ParamField(default='qcow2', choices=('qcow2', 'raw'))
    source_image = base.ParamField(default=None)
    source_image_checksum = base.ParamField(default=None)
    cloudinit_meta_data = base.ParamField(default=None)
    cloudinit_user_data = base.ParamField(default=None)
示例#5
0
class DummyDriver(driver.Driver):
    """Example of driver implementation

    Note: This class is imported as Driver at .__init__.py

    This class should contain parameters specified in template
    group['driver']['params']

    yaml example:

    groups:
    - name: rack-01
      driver:
        name: devops.driver.dummy.dummy_driver
        params:
          dummy_parameter: 15
          choice_parameter: two
          nested:
            parameter: abc

    """

    # example parameters
    dummy_parameter = base.ParamField(default=10)
    choice_parameter = base.ParamField(default='one',
                                       choices=('one', 'two', 'three'))
    nested = base.ParamMultiField(parameter=base.ParamField())

    def get_allocated_networks(self):
        """This methods return list of already allocated networks.

        implement it if your driver knows which network pools
        are already taken by the driver
        """
        # example format
        return ['192.168.0.0/24', '192.168.1.0/24']
示例#6
0
class DummyVolume(volume.Volume):
    """Example implementation of volume

    Note: This class is imported as Volume at .__init__.py

    Volume is image or disk which should be mounted to a specific Node
    """

    # example parameter
    size = base.ParamField(default=1024)

    def define(self):
        """Define

        Define method is called one time after environment successfully
        saved from template to database. It should contain something to prepare
        an instance of Volume before usage in Node class
        """
        # driver instance is available from property self.driver
        # so you can use any parameters defined in driver
        print(self.driver)
        print(self.driver.dummy_parameter)

        print('Do something before define')
        super(DummyVolume, self).define()
        print('Do something after define')

    def erase(self):
        """Erase

        Erase method is called one time when you want remove existing
        volume
        """
        print('Do something before erase')
        super(DummyVolume, self).erase()
        print('Do something after erase')
示例#7
0
class DummyL2NetworkDevice(network.L2NetworkDevice):
    """Example implementation of l2 network device.

    Note: This class is imported as L2NetworkDevice at .__init__.py

    L2NetworkDevice represents node/device which acts like switch or router

    yaml example:

    l2_network_devices:
      admin:
        address_pool: fuelweb_admin-pool01
        dhcp: false
        other: abcd

      public:
        address_pool: public-pool01
        dhcp: false
        other: efgh

    """

    # example parameter
    dhcp = base.ParamField(default=False)
    other = base.ParamField(default='')

    def define(self):
        """Define

        Define method is called one time after environment successfully
        saved from template to database. It should contain something to prepare
        an instance of L2NetworkDevice before start
        """

        print('Do something before define')

        # driver instance is available from property self.driver
        # so you can use any parameters defined in driver
        print(self.driver)
        print(self.driver.dummy_parameter)
        print(self.driver.nested.parameter)

        # parameters of L2NetworkDevice
        print(self.dhcp)
        print(self.other)

        # name of L2NetworkDevice
        print(self.name)

        # associated adress pool
        print(self.address_pool)
        # and network
        print(self.address_pool.ip_network)

        super(DummyL2NetworkDevice, self).define()
        print('Do something after define')

    def start(self):
        """Start

        Start method is called every time you want to boot up previsously
        saved and defined l2 network device
        """

        print('implementation of start')

    def destroy(self):
        """Destroy

        Destroy method is called every time you want to power off
        previsously started l2 network device
        """
        print('implementation of destroy')

    def erase(self):
        """Erase

        Erase method is called one time when you want remove existing
        l2 network device
        """
        super(DummyL2NetworkDevice, self).erase()
        print('Do something after erase')
示例#8
0
class DummyNode(node.Node):
    """Example implementation of node

    Note: This class is imported as Node at .__init__.py

    Node is a server which will be used for deloying of openstack depending on
    node role

    yaml example:

    nodes:
    - name: slave
      role: fuel_slave
      params:
        cpu: 2
        memory: 3072
        boot:
          - hd
          - cdrom
        volumes:
         - name: system
           size: 75
           format: qcow2
        interfaces:
         - label: eth0
           l2_network_device: admin
           interface_model: e1000
        network_config:
          eth0:
            networks:
             - fuelweb_admin
    """

    cpu = base.ParamField(default=1)
    memory = base.ParamField(default=1024)
    boot = base.ParamField(default=['network', 'cdrom', 'hd'])

    def define(self):
        """Define

        Define method is called one time after environment successfully
        saved from template to database. It should contain something to
        prepare an instance of Node before start
        """

        # driver instance is available from property self.driver
        print(self.driver)

        # node parameters
        print(self.cpu)
        print(self.memory)
        print(self.boot)

        # list of disk devices
        print(self.disk_devices)

        # list of network intefraces
        print(self.interfaces)

        print('Do something before define')
        super(DummyNode, self).define()
        print('Do something after define')

    def start(self):
        """Start method is called every time you want to boot up node"""
        print('implementation of start')

    def destroy(self):
        """Destroy

        Destroy method is called every time you want to power off
        previsously started node
        """
        print('implementation of destroy')

    def erase(self):
        """Erase

        Erase method is called one time when you want remove existing
        node
        """
        print('Do something before erase')
        super(DummyNode, self).erase()
        print('Do something after erase')
示例#9
0
class Interface(base.ParamedModel):
    """Describes a network interface configuration

    Specify the abstract label of the interface (you can use labels
    that match the real interface names or use any suitable names).

    'l2_network_device' describes the switch name (virtual or hardware)
    to which the interface is connected.

    'features' is a json list with any strings you want use to mark
    interfaces with specific features and use these marks in 3rd-party
    libraries to perform highlevel configuration of the right interfaces
    for your product.

    Template example (interfaces):
    ---------------------------------
      - name: some_node_name
        params:
          ...
          interfaces:
            - label: iface0
              l2_network_device: admin
            - label: iface1
              l2_network_device: data
              mac_address: !os_env IFACE_MAC_ADDRESS, 00:11:22:33:44:55
              features: ['dpdk', 'dpdk_pci: 0000:05:00.1']
    """
    class Meta(object):
        db_table = 'devops_interface'
        app_label = 'devops'

    node = models.ForeignKey('Node')
    l2_network_device = models.ForeignKey('L2NetworkDevice', null=True)
    label = models.CharField(max_length=255, null=True)
    mac_address = models.CharField(max_length=255, unique=True, null=False)
    type = models.CharField(max_length=255, null=False)
    model = base.choices('virtio', 'e1000', 'pcnet', 'rtl8139', 'ne2k_pci')
    features = base.ParamField(default=[])

    @property
    def driver(self):
        return self.node.driver

    # LEGACY, for fuel-qa compatibility if MULTIPLE_NETWORKS enabled
    @property
    def network(self):
        return self.l2_network_device

    @property
    def target_dev(self):
        return self.label

    @property
    def addresses(self):
        return self.address_set.all()

    @property
    def network_config(self):
        return self.node.networkconfig_set.get(label=self.label)

    def define(self):
        self.save()

    def remove(self):
        self.delete()

    def add_address(self):
        """Assign an IP address to the interface

        Try to get an IP from reserved IP with name '<group>_<node>'
        , or generate next IP if reserved IP wasn't found.
        Next IP is generated from the DHCP ip_range, or from the
        network range [+2:-2] of all available addresses in the address pool.
        """
        reserved_ip_name = helpers.underscored(self.node.group.name,
                                               self.node.name)
        reserved_ip = self.l2_network_device.address_pool.get_ip(
            reserved_ip_name)
        ip = reserved_ip or self.l2_network_device.address_pool.next_ip()
        Address.objects.create(
            ip_address=str(ip),
            interface=self,
        )

    @property
    def is_blocked(self):
        """Show state of interface"""
        return False

    def block(self):
        """Block traffic on interface"""
        pass

    def unblock(self):
        """Unblock traffic on interface"""
        pass

    @classmethod
    def interface_create(cls, l2_network_device, node, label,
                         if_type='network', mac_address=None, model='virtio',
                         features=None):
        """Create interface

        :rtype : Interface
        """
        interface = cls.objects.create(
            l2_network_device=l2_network_device,
            node=node,
            label=label,
            type=if_type,
            mac_address=mac_address or helpers.generate_mac(),
            model=model,
            features=features or [])
        if (interface.l2_network_device and
                interface.l2_network_device.address_pool is not None):
            interface.add_address()
        return interface
示例#10
0
class AddressPool(base.ParamedModel, base.BaseModel):
    """Address pool

    address_pools:
      <address_pool_name>:
        net: <IPNetwork[:prefix]>
        params:  # Optional params for the address pool
          vlan_start: <int>
          vlan_end: <int>
          ip_reserved:
            <'gateway'>:<int|IPAddress>            # Reserved for gateway.
            <'l2_network_device'>:<int|IPAddress>  # Reserved for local IP
                                                   #   for libvirt networks.
            <'groupname_nodename'>:<int|IPAddress> # Reserved for specific node
                                                   #   IP address for iface in
                                                   #   this address pool.
            ...  # user-defined IPs (for fuel-qa)
          ip_ranges:
            <group_name>: [<int|IPAddress>, <int|IPAddress>]
            ...  # user-defined ranges (for fuel-qa, 'floating' for example)


    Template example (address_pools):
    ---------------------------------

    address_pools:

      fuelweb_admin-pool01:
        net: 172.0.0.0/16:24
        params:
          ip_reserved:
            gateway: 1
            l2_network_device: 1  # l2_network_device will get the
                                  # IP address = 172.0.*.1  (net + 1)
          ip_ranges:
            default: [2, -2]     # admin IP range for 'default' nodegroup name

      public-pool01:
        net: 12.34.56.0/26    # Some WAN routed to the test host.
        params:
          vlan_start: 100
          ip_reserved:
            gateway: 12.34.56.1
            l2_network_device: 12.34.56.62 # l2_network_device will be assumed
                                           # with this IP address.
                                           # It will be used for create libvirt
                                           # network if libvirt driver is used.
          ip_ranges:
            default: [2, 127]   # public IP range for 'default' nodegroup name
            floating: [128, -2] # floating IP range

      storage-pool01:
        net: 172.0.0.0/16:24
        params:
          vlan_start: 101
          ip_reserved:
            l2_network_device: 1  # 172.0.*.1

      management-pool01:
        net: 172.0.0.0/16:24
        params:
          vlan_start: 102
          ip_reserved:
            l2_network_device: 1  # 172.0.*.1

      private-pool01:
        net: 192.168.0.0/24:26
        params:
          vlan_start: 103
          ip_reserved:
            l2_network_device: 1  # 192.168.*.1

    """
    class Meta(object):
        unique_together = ('name', 'environment')
        db_table = 'devops_address_pool'
        app_label = 'devops'

    environment = models.ForeignKey('Environment')
    name = models.CharField(max_length=255)
    net = models.CharField(max_length=255, unique=True)
    vlan_start = base.ParamField()
    vlan_end = base.ParamField()
    tag = base.ParamField()  # DEPRECATED, use vlan_start instead

    # ip_reserved = {'l2_network_device': 'm.m.m.50',
    #                'gateway': 'n.n.n.254', ...}
    ip_reserved = base.ParamField(default={})

    # ip_ranges = {'range_a': ('x.x.x.x', 'y.y.y.y'),
    #              'range_b': ('a.a.a.a', 'b.b.b.b'), ...}
    ip_ranges = base.ParamField(default={})

    # NEW. Warning: Old implementation returned self.net
    @property
    def ip_network(self):
        """Return IPNetwork representation of self.ip_network field.

        :return: IPNetwork()
        """
        return netaddr.IPNetwork(self.net)

    @property
    def gateway(self):
        """Get the default network gateway

        This property returns only default network gateway.

        :return: reserved IP address with key 'gateway', or
                 the first address in the address pool
                 (for fuel-qa compatibility).
        """
        return self.get_ip('gateway') or str(self.ip_network[1])

    def ip_range_start(self, range_name):
        """Return the IP address of start the IP range 'range_name'

        :return: str(IP) or None
        """
        if range_name in self.ip_ranges:
            return str(self.ip_ranges.get(range_name)[0])
        else:
            logger.debug("IP range '{0}' not found in the "
                         "address pool {1}".format(range_name, self.name))
            return None

    def ip_range_end(self, range_name):
        """Return the IP address of end the IP range 'range_name'

        :return: str(IP) or None
        """
        if range_name in self.ip_ranges:
            return str(self.ip_ranges.get(range_name)[1])
        else:
            logger.debug("IP range '{0}' not found in the "
                         "address pool {1}".format(range_name, self.name))
            return None

    def ip_range_set(self, range_name, ip_range_start, ip_range_end):
        """Set IP range in the address pool

        :param range_name: str, name of the range
        :param ip_range_start: str, first IP of the range
        :param ip_range_end: str, last IP of the range
        :rtype: None or exception DevopsError

        If range_name already exists, then DevopsError raises.
        """
        if range_name in self.ip_ranges:
            raise error.DevopsError(
                "Setting IP range '{0}' for address pool '{1}' failed: range "
                "already exists".format(range_name, self.name))
        self.ip_ranges[range_name] = (ip_range_start, ip_range_end)
        self.save()

    def get_ip(self, ip_name):
        """Return the reserved IP

           For example, 'gateway' is one of the common reserved IPs

        :return: str(IP) or None
        """
        if ip_name in self.ip_reserved:
            return str(self.ip_reserved.get(ip_name))
        else:
            logger.debug("Reserved IP '{0}' not found in the "
                         "address pool {1}".format(ip_name, self.name))
            return None

    def next_ip(self):
        """Get next IP address from the address pool

        If 'dhcp' ip_range specified for the address pool, then the
        IP addresses will be taken from this pool.
        Else, IP addresses will be taken from the range
        [ x.x.x.x + 2 : x.x.x.x - 2 ]
        """
        range_start = netaddr.IPAddress(
            self.ip_range_start('dhcp') or self.ip_network[2])
        range_end = netaddr.IPAddress(
            self.ip_range_end('dhcp') or self.ip_network[-2])
        for ip in self.ip_network.iter_hosts():
            # if ip < self.ip_pool_start or ip > self.ip_pool_end:
            # Skip net, gw and broadcast addresses in the address pool
            if ip < range_start or ip > range_end:
                continue
            already_exists = Address.objects.filter(
                interface__l2_network_device__address_pool=self,
                ip_address=str(ip)).exists()
            if already_exists:
                continue
            return ip
        raise error.DevopsError(
            "No more free addresses in the address pool {0}"
            " with CIDR {1}".format(self.name, self.net))

    @classmethod
    def _safe_create_network(cls, name, pool, environment, **params):
        for ip_network in pool:
            if cls.objects.filter(net=str(ip_network)).exists():
                continue

            new_params = deepcopy(params)
            new_params['net'] = ip_network
            try:
                with transaction.atomic():
                    return cls.objects.create(
                        environment=environment,
                        name=name,
                        **new_params
                    )
            except IntegrityError as e:
                logger.debug(e)
                if 'name' in str(e):
                    raise error.DevopsError(
                        'AddressPool with name "{}" already exists'
                        ''.format(name))
                continue

        raise error.DevopsError(
            "There is no network pool available for creating "
            "address pool {}".format(name))

    @classmethod
    def address_pool_create(cls, name, environment, pool=None, **params):
        """Create network

        :rtype : Network
        """
        if pool is None:
            pool = network.IpNetworksPool(
                networks=[netaddr.IPNetwork('10.0.0.0/16')],
                prefix=24,
                allocated_networks=environment.get_allocated_networks())

        address_pool = cls._safe_create_network(
            environment=environment,
            name=name,
            pool=pool,
            **params
        )

        # Translate indexes into IP addresses for ip_reserved and ip_ranges
        def _relative_to_ip(ip_network, ip_id):
            """Get an IP from IPNetwork ip's list by index

            :param ip_network: IPNetwork object
            :param ip_id: string, if contains '+' or '-' then it is
                          used as index of an IP address in ip_network,
                          else it is considered as IP address.

            :rtype : str(IP)
            """
            if isinstance(ip_id, int):
                return str(ip_network[int(ip_id)])
            else:
                return str(ip_id)

        if 'ip_reserved' in params:
            for ip_res in params['ip_reserved'].keys():
                ip = _relative_to_ip(address_pool.ip_network,
                                     params['ip_reserved'][ip_res])
                params['ip_reserved'][ip_res] = ip      # Store to template
                address_pool.ip_reserved[ip_res] = ip   # Store to the object

        if 'ip_ranges' in params:
            for ip_range in params['ip_ranges']:
                ipr_start = _relative_to_ip(address_pool.ip_network,
                                            params['ip_ranges'][ip_range][0])
                ipr_end = _relative_to_ip(address_pool.ip_network,
                                          params['ip_ranges'][ip_range][1])
                params['ip_ranges'][ip_range] = (ipr_start, ipr_end)
                address_pool.ip_ranges[ip_range] = (ipr_start, ipr_end)

        address_pool.save()
        return address_pool
示例#11
0
class MyMultiModel(models.Driver):

    multi = base.ParamMultiField(sub1=base.ParamField(default='abc'),
                                 multi2=base.ParamMultiField(
                                     sub2=base.ParamField(default=13), ))
示例#12
0
class Interface(base.ParamedModel):
    class Meta(object):
        db_table = 'devops_interface'
        app_label = 'devops'

    node = models.ForeignKey('Node')
    l2_network_device = models.ForeignKey('L2NetworkDevice', null=True)
    label = models.CharField(max_length=255, null=True)
    mac_address = models.CharField(max_length=255, unique=True, null=False)
    type = models.CharField(max_length=255, null=False)
    model = base.choices('virtio', 'e1000', 'pcnet', 'rtl8139', 'ne2k_pci')
    features = base.ParamField(default=[])

    @property
    def driver(self):
        return self.node.driver

    # LEGACY, for fuel-qa compatibility if MULTIPLE_NETWORKS enabled
    @property
    def network(self):
        return self.l2_network_device

    @property
    def target_dev(self):
        return self.label

    @property
    def addresses(self):
        return self.address_set.all()

    @property
    def network_config(self):
        return self.node.networkconfig_set.get(label=self.label)

    def define(self):
        self.save()

    def remove(self):
        self.delete()

    def add_address(self):
        ip = self.l2_network_device.address_pool.next_ip()
        Address.objects.create(
            ip_address=str(ip),
            interface=self,
        )

    @property
    def is_blocked(self):
        """Show state of interface"""
        return False

    def block(self):
        """Block traffic on interface"""
        pass

    def unblock(self):
        """Unblock traffic on interface"""
        pass

    @classmethod
    def interface_create(cls, l2_network_device, node, label,
                         if_type='network', mac_address=None, model='virtio',
                         features=None):
        """Create interface

        :rtype : Interface
        """
        interface = cls.objects.create(
            l2_network_device=l2_network_device,
            node=node,
            label=label,
            type=if_type,
            mac_address=mac_address or helpers.generate_mac(),
            model=model,
            features=features or [])
        if (interface.l2_network_device and
                interface.l2_network_device.address_pool is not None):
            interface.add_address()
        return interface
示例#13
0
class Node(
    six.with_metaclass(
        ExtendableNodeType,
        base.ParamedModel,
        base.BaseModel)):
    class Meta(object):
        unique_together = ('name', 'group')
        db_table = 'devops_node'
        app_label = 'devops'

    group = models.ForeignKey('Group', null=True)
    name = models.CharField(max_length=255, unique=False, null=False)
    role = models.CharField(max_length=255, null=True)

    kernel_cmd = base.ParamField()
    ssh_port = base.ParamField(default=22)
    bootstrap_timeout = base.ParamField(default=600)
    deploy_timeout = base.ParamField(default=3600)
    deploy_check_cmd = base.ParamField()

    @property
    def driver(self):
        drv = self.group.driver

        # LEGACY (fuel-qa compatibility requires), TO REMOVE
        def node_active(node):
            return node.is_active()
        drv.node_active = node_active

        return drv

    @functional.cached_property
    def ext(self):
        try:
            # noinspection PyPep8Naming
            ExtCls = loader.load_class(
                'devops.models.node_ext.{ext_name}:NodeExtension'
                ''.format(ext_name=self.role or 'default'))
            return ExtCls(node=self)
        except ImportError:
            logger.debug('NodeExtension is not found for role: {!r}'
                         ''.format(self.role))
            return None

    def define(self, *args, **kwargs):
        for iface in self.interfaces:
            iface.define()
        self.save()

    def start(self, *args, **kwargs):
        pass

    def destroy(self, *args, **kwargs):
        ssh_client.SSHClient.close_connections()

    def erase(self, *args, **kwargs):
        self.remove()

    def remove(self, *args, **kwargs):
        ssh_client.SSHClient.close_connections()
        self.erase_volumes()
        for iface in self.interfaces:
            iface.remove()
        self.delete()

    def suspend(self, *args, **kwargs):
        ssh_client.SSHClient.close_connections()

    def resume(self, *args, **kwargs):
        pass

    def is_active(self):
        return False

    def snapshot(self, *args, **kwargs):
        ssh_client.SSHClient.close_connections()

    def revert(self, *args, **kwargs):
        ssh_client.SSHClient.close_connections()

    # for fuel-qa compatibility
    def has_snapshot(self, *args, **kwargs):
        return True

    def reboot(self):
        pass

    def shutdown(self):
        ssh_client.SSHClient.close_connections()

    def reset(self):
        ssh_client.SSHClient.close_connections()

    def get_vnc_port(self):
        return None

    # for fuel-qa compatibility
    def get_snapshots(self):
        """Return full snapshots objects"""
        return []

    @property
    def disk_devices(self):
        return self.diskdevice_set.all()

    @property
    def interfaces(self):
        return self.interface_set.order_by('id')

    @property
    def network_configs(self):
        return self.networkconfig_set.all()

    # LEGACY, for fuel-qa compatibility
    @property
    def is_admin(self):
        return 'master' in str(self.role)

    # LEGACY, for fuel-qa compatibility
    @property
    def is_slave(self):
        return self.role == 'fuel_slave'

    def next_disk_name(self):
        disk_names = ('sd' + c for c in list('abcdefghijklmnopqrstuvwxyz'))
        for disk_name in disk_names:
            if not self.disk_devices.filter(target_dev=disk_name).exists():
                return disk_name

    # TODO(astudenov): LEGACY, TO REMOVE
    def interface_by_network_name(self, network_name):
        logger.warning('interface_by_network_name is deprecated in favor of '
                       'get_interface_by_network_name')
        warnings.warn(
            "'Node.interface_by_network_name' is deprecated. "
            "Use 'Node.get_interface_by_network_name' instead.",
            DeprecationWarning
            )
        return self.get_interface_by_network_name(network_name=network_name)

    def get_interface_by_network_name(self, network_name):
        return self.interface_set.get(
            l2_network_device__name=network_name)

    def get_interface_by_nailgun_network_name(self, name):
        for net_conf in self.networkconfig_set.all():
            if name in net_conf.networks:
                label = net_conf.label
                break
        else:
            return None
        return self.interface_set.get(label=label)

    # NOTE: this method works only for master node
    def get_ip_address_by_network_name(self, name, interface=None):
        interface = interface or self.interface_set.filter(
            l2_network_device__name=name).order_by('id')[0]
        return interface.address_set.get(interface=interface).ip_address

    # NOTE: this method works only for master node
    def get_ip_address_by_nailgun_network_name(self, name):
        interface = self.get_interface_by_nailgun_network_name(name)
        return interface.address_set.first().ip_address

    # LEGACY
    def remote(
            self, network_name, login=None, password=None, private_keys=None,
            auth=None):
        """Create SSH-connection to the network

        NOTE: this method works only for master node

        :rtype : SSHClient
        """
        return ssh_client.SSHClient(
            self.get_ip_address_by_network_name(network_name),
            username=login,
            password=password, private_keys=private_keys, auth=auth)

    # LEGACY
    # NOTE: this method works only for master node
    def await(self, network_name, timeout=120, by_port=22):
        helpers.wait_pass(
            lambda: helpers.tcp_ping_(
                self.get_ip_address_by_network_name(network_name), by_port),
            timeout=timeout)

    # NEW
    def add_interfaces(self, interfaces):
        for interface in interfaces:
            label = interface['label']
            l2_network_device_name = interface.get('l2_network_device')
            interface_model = interface.get('interface_model', 'virtio')
            mac_address = interface.get('mac_address')
            features = interface.get('features', None)
            self.add_interface(
                label=label,
                l2_network_device_name=l2_network_device_name,
                mac_address=mac_address,
                interface_model=interface_model,
                features=features)

    # NEW
    def add_interface(self, label, l2_network_device_name,
                      interface_model, mac_address=None,
                      features=None):
        if l2_network_device_name:
            env = self.group.environment
            l2_network_device = env.get_env_l2_network_device(
                name=l2_network_device_name)
        else:
            l2_network_device = None

        cls = self.driver.get_model_class('Interface')
        return cls.interface_create(
            node=self,
            label=label,
            l2_network_device=l2_network_device,
            mac_address=mac_address,
            model=interface_model,
            features=features,
        )

    # NEW
    def add_network_configs(self, network_configs):
        for label, data in network_configs.items():
            self.add_network_config(
                label=label,
                networks=data.get('networks', []),
                aggregation=data.get('aggregation'),
                parents=data.get('parents', []),
            )

    # NEW
    def add_network_config(self, label, networks=None, aggregation=None,
                           parents=None):
        if networks is None:
            networks = []
        if parents is None:
            parents = []
        network.NetworkConfig.objects.create(
            node=self,
            label=label,
            networks=networks,
            aggregation=aggregation,
            parents=parents,
        )

    # NEW
    def add_volumes(self, volumes):
        for vol_params in volumes:
            self.add_volume(
                **vol_params
            )

    # NEW
    def add_volume(self, name, device='disk', bus='virtio', **params):
        cls = self.driver.get_model_class('Volume')

        if 'backing_store' in params:
            # Backing storage volume have to be defined in group
            params['backing_store'] = self.group.get_volume(
                name=params['backing_store'])

        volume = cls.objects.create(
            node=self,
            name=name,
            **params
        )
        # TODO(astudenov): make a separete section in template for disk devices
        self.attach_volume(
            volume=volume,
            device=device,
            bus=bus,
        )
        return volume

    # NEW
    def attach_volume(self, volume, device='disk', type='file',
                      bus='virtio', target_dev=None):
        """Attach volume to node

        :rtype : DiskDevice
        """
        cls = self.driver.get_model_class('DiskDevice')
        return cls.objects.create(
            device=device, type=type, bus=bus,
            target_dev=target_dev or self.next_disk_name(),
            volume=volume, node=self)

    # NEW
    def get_volume(self, **kwargs):
        try:
            return self.volume_set.get(**kwargs)
        except volume.Volume.DoesNotExist:
            raise error.DevopsObjNotFound(volume.Volume, **kwargs)

    # NEW
    def get_volumes(self, **kwargs):
        return self.volume_set.filter(**kwargs).order_by('id')

    # NEW
    def erase_volumes(self):
        for vol in self.get_volumes():
            vol.erase()
示例#14
0
class IpmiNode(node.Node):
    """IPMI Node

        Intel IPMI specification:
        http://www.intel.ru/content/dam/www/public/us/en/documents/
        product-briefs/ipmi-second-gen-interface-spec-v2-rev1-1.pdf

        The Node shall provide ability to manage remote
        baremetal node through ipmi interface by using
        ipmitool: http://sourceforge.net/projects/ipmitool/
        Take into account that it is suitable tool
        according to licence criteria.
        More info can be found here:
        http://ipmiutil.sourceforge.net/docs/ipmisw-compare.htm

        Note:
        Power management - on/off/reset
        User management - user list
        Chassis management - chassis info
        Virtual Storage management - ISO attache
        Sensors management - get sensors info
        Node management - start/stop/reset

        :param ipmi_user: str - the user login for IPMI board
        :param ipmi_password: str - the user password
        :param ipmi_previlegies: str - the user privileges level (OPERATOR)
        :param ipmi_host: str - remote host name
        :param ipmi_port: int - remote port number
        :param ipmi_lan_interface: str - the lan interface (lan, lanplus)
    """

    uuid = base.ParamField()  # LEGACY, for compatibility reason
    boot = base.ParamField(default='pxe')
    force_set_boot = base.ParamField(default=True)
    ipmi_user = base.ParamField()
    ipmi_password = base.ParamField()
    ipmi_previlegies = base.ParamField(default='OPERATOR')
    ipmi_host = base.ParamField()
    ipmi_lan_interface = base.ParamField(default="lanplus")
    ipmi_port = base.ParamField(default=623)

    @functional.cached_property
    def conn(self):
        """Connection to ipmi api"""
        return ipmi_client.IpmiClient(self.ipmi_user, self.ipmi_password,
                                      self.ipmi_host, self.ipmi_previlegies,
                                      self.ipmi_lan_interface, self.ipmi_port,
                                      self.name)

    def _wait_power_off(self):
        helpers.wait(
            lambda: not self.is_active(),
            timeout=60,
            timeout_msg="Node {0} / {1} wasn't stopped in 60 sec".format(
                self.name, self.ipmi_host))

    def exists(self):
        """Check if node exists

        :param: None
        :return: bool - True if successful, False otherwise
        """
        return self.conn.check_remote_host()

    def is_active(self):
        """Check if node is active

        Note: we have to check power on and
              we have take into account that OS is working on remote host
        TODO: let's double check remote OS despite power is on

        :param: None
        :return: bool - True if successful, False otherwise.
        """
        return 0 == self.conn.power_status()

    def define(self):
        """Prepare node to start

        TODO: Mount ISO
        TODO: Set boot device
        Note: need to set boot device at first. Create record in DB
        """
        self.uuid = uuid.uuid4()
        super(IpmiNode, self).define()

    def start(self):
        """Node start. Power on """

        if self.force_set_boot:
            # Boot device is not stored in bios, so it should
            # be set every time when node starts.
            self.conn.chassis_set_boot(self.boot)

        if self.is_active():
            # Re-start active node
            self.reboot()
        else:
            self.conn.power_on()
        helpers.wait(
            self.is_active,
            timeout=60,
            timeout_msg="Node {0} / {1} wasn't started in 60 sec".format(
                self.name, self.ipmi_host))

    def destroy(self):
        """Node destroy. Power off """
        self.conn.power_off()
        self._wait_power_off()
        super(IpmiNode, self).destroy()

    def remove(self):
        """Node remove. Power off """
        if self.is_active():
            self.conn.power_off()
            self._wait_power_off()
        super(IpmiNode, self).remove()

    def reset(self):
        """Node reset. Power reset """
        self.conn.power_reset()

    def reboot(self):
        """Node reboot. Power reset """
        self.conn.power_reset()

    def shutdown(self):
        """Shutdown Node """
        self.conn.power_off()
        self._wait_power_off()
        super(IpmiNode, self).shutdown()
class IronicNode(node.Node):
    """Note: This class is imported as Node at .__init__.py """

    uuid = base.ParamField()
    bootmenu_timeout = base.ParamField(default=0)
    numa = base.ParamField(default=[])
    root_volume_name = base.ParamField()
    cloud_init_volume_name = base.ParamField()
    cloud_init_iface_up = base.ParamField()

    ironic_driver = base.ParamField(default='agent_ipmitool')

    boot = base.ParamField(default='pxe')
    force_set_boot = base.ParamField(default=True)
    ipmi_user = base.ParamField()
    ipmi_password = base.ParamField()
    ipmi_previlegies = base.ParamField(default='OPERATOR')
    ipmi_host = base.ParamField()
    ipmi_lan_interface = base.ParamField(default="lanplus")
    ipmi_port = base.ParamField(default=623)

    # Required in cases of changed provisioning states
    wait_active_timeout = base.ParamField(default=900)

    def exists(self, timeout=5):
        """Check if node exists

            :rtype : Boolean
        """
        try:
            with helpers.RunLimit(timeout):
                return any([
                    self.uuid == node.uuid
                    for node in self.driver.conn.node.list()
                ])
        except error.TimeoutError:
            logger.error("Ironic API is not responded for {0}sec, assuming "
                         "that node {1} is absent".format(timeout, self.name))
            return False

    def is_active(self, timeout=5):
        """Check if node is active

            :rtype : Boolean
        """
        try:
            with helpers.RunLimit(timeout):
                states = self.driver.conn.node.states(self.uuid)
                return (states['provision_state'] == 'active'
                        and states['power_state'] == 'power on')
        except error.TimeoutError:
            logger.error("Ironic API is not responded for {0}sec, assuming "
                         "that node {1} is absent".format(timeout, self.name))
            return False

    @property
    def ironic_node_name(self):
        #return helpers.underscored(
        #    helpers.deepgetattr(self, 'group.environment.name'),
        #    self.name,
        #).replace("_", "-")
        return self.name.replace("_", "-")

    def define(self):
        """Define node

            :rtype : None
        """

        root_volume = self.get_volume(name=self.root_volume_name)

        # Necessary only once, when node is registered to ironic
        node = self.driver.conn.node.create(driver=self.ironic_driver,
                                            name=self.ironic_node_name,
                                            driver_info={
                                                'deploy_kernel':
                                                self.driver.agent_kernel_url,
                                                'deploy_ramdisk':
                                                self.driver.agent_ramdisk_url,
                                                'ipmi_address':
                                                self.ipmi_host,
                                                'ipmi_username':
                                                self.ipmi_user,
                                                'ipmi_password':
                                                self.ipmi_password,
                                                'ipmi_priv_level':
                                                self.ipmi_previlegies,
                                            })
        logger.debug("Created Ironic node: {0}".format(node))

        for interface in self.interfaces:
            if interface.mac_address:
                port = self.driver.conn.port.create(
                    node_uuid=node.uuid,
                    address=interface.mac_address,
                )
                logger.debug("Created Ironic node port: {0}".format(port))

        # Necessary for each deploy/redeploy
        patch = [{
            'path': '/instance_info/root_gb',
            'value': root_volume.capacity,
            'op': 'add'
        }, {
            'path': '/instance_info/image_source',
            'value': root_volume.source_image,
            'op': 'add'
        }, {
            'path': '/instance_info/image_checksum',
            'value': root_volume.source_image_checksum,
            'op': 'add'
        }]

        logger.debug("Updating Ironic node with: {0}".format(patch))
        updated_node = self.driver.conn.node.update(
            node.uuid,
            patch=patch,
        )
        logger.debug("Updated Ironic node: {0}".format(updated_node))

        # TODO(ddmitriev): node-set-provision-state

        if self.cloud_init_volume_name is not None:
            configdrive = self.__create_configdrive()
        else:
            configdrive = None

        self.driver.conn.node.set_provision_state(
            node_uuid=node.uuid,
            configdrive=configdrive,
            state='active',
        )
        logger.debug("Set provision state to 'active' for node {0} {1}".format(
            node.name, node.uuid))

        self.uuid = node.uuid
        super(IronicNode, self).define()

    def wait_for_state(self, expected_state, timeout=600):
        threshold = time.time() + timeout
        while not timeout or time.time() < threshold:
            try:
                self.driver.conn.node.wait_for_provision_state(
                    node_ident=self.uuid,
                    expected_state='active',
                    timeout=self.wait_active_timeout)
                return
            except exc.StateTransitionFailed:
                # When node is deploying, there can be non-critical errors
                # during state transitions, let's skip them.
                time.sleep(10)
        raise exc.StateTransitionTimeout(
            'Node {0} with uuid={1} failed to reach state {2} in {3} seconds'.
            format(self.name, self.uuid, expected_state, timeout))

    def start(self):
        """Start the node (power on)"""
        logger.info(
            "Starting Ironic node {0}(uuid={1}) with timeout={2}".format(
                self.name, self.uuid, self.wait_active_timeout))

        self.wait_for_state(expected_state='active',
                            timeout=self.wait_active_timeout)

        self.driver.conn.node.set_power_state(
            node_id=self.uuid,
            state='on',
        )
        super(IronicNode, self).start()

    def destroy(self, *args, **kwargs):
        """Stop the node (power off)"""
        if self.is_active():
            self.driver.conn.node.set_power_state(
                node_id=self.uuid,
                state='off',
                soft=False,
            )
        super(IronicNode, self).destroy()

    @decorators.retry(Exception, count=10, delay=20)
    def remove(self, *args, **kwargs):
        if self.uuid:
            if self.exists():
                #    self.destroy()
                logger.info("Removing Ironic node {0}(uuid={1})".format(
                    self.name, self.uuid))
                try:
                    self.driver.conn.node.set_maintenance(
                        node_id=self.uuid,
                        state=True,
                        maint_reason="Removing the node from devops environment"
                    )
                    self.driver.conn.node.delete(self.uuid)
                except common.apiclient.exceptions.BadRequest:
                    # Allow to remove node from fuel-devops if ironic API down
                    pass
        super(IronicNode, self).remove()

    def reboot(self):
        """Reboot node gracefully

            :rtype : None
        """

        self.wait_for_state(expected_state='active',
                            timeout=self.wait_active_timeout)

        self.driver.conn.node.set_power_state(
            node_id=self.uuid,
            state='reboot',
            soft=True,
        )
        super(IronicNode, self).reboot()

    def shutdown(self):
        """Shutdown node gracefully

            :rtype : None
        """
        super(IronicNode, self).shutdown()

        self.wait_for_state(expected_state='active',
                            timeout=self.wait_active_timeout)

        self.driver.conn.node.set_power_state(
            node_id=self.uuid,
            state='off',
            soft=True,
        )

    def reset(self):
        """Reboot node"""
        super(IronicNode, self).reset()

        self.wait_for_state(expected_state='active',
                            timeout=self.wait_active_timeout)

        self.driver.conn.node.set_power_state(
            node_id=self.uuid,
            state='reboot',
            soft=False,
        )

    def __create_configdrive(self):
        """Builds setting iso to send basic configuration for cloud image

        Returns a gzipped, base64-encoded configuration drive string.
        """

        if self.cloud_init_volume_name is None:
            return None

        volume = self.get_volume(name=self.cloud_init_volume_name)

        interface = self.interface_set.get(label=self.cloud_init_iface_up)
        admin_ip = self.get_ip_address_by_network_name(name=None,
                                                       interface=interface)

        env_name = self.group.environment.name
        dir_path = os.path.join(settings.CLOUD_IMAGE_DIR, env_name)
        cloud_image_settings_path = os.path.join(
            dir_path, 'configdrive_{0}.iso'.format(self.ironic_node_name))
        meta_data_path = os.path.join(dir_path, "meta-data")
        user_data_path = os.path.join(dir_path, "user-data")

        interface_name = interface.label
        admin_ap = interface.l2_network_device.address_pool
        gateway = str(admin_ap.gateway)
        admin_netmask = str(admin_ap.ip_network.netmask)
        admin_network = str(admin_ap.ip_network)
        hostname = self.name

        cloud_image_settings.generate_cloud_image_settings(
            cloud_image_settings_path=cloud_image_settings_path,
            meta_data_path=meta_data_path,
            user_data_path=user_data_path,
            admin_network=admin_network,
            interface_name=interface_name,
            admin_ip=admin_ip,
            admin_netmask=admin_netmask,
            gateway=gateway,
            hostname=hostname,
            meta_data_content=volume.cloudinit_meta_data,
            user_data_content=volume.cloudinit_user_data,
        )

        cmd = 'gzip -9 -c {0} | base64'.format(cloud_image_settings_path)
        result = subprocess_runner.Subprocess.check_call(cmd)

        # Clear temporary files
        if os.path.exists(dir_path):
            shutil.rmtree(dir_path)

        return ''.join(result['stdout'])