From 703290986ff458ba1256dffde4697c8d30dce2ea Mon Sep 17 00:00:00 2001 From: Andriy Popovych Date: Wed, 2 Mar 2016 19:47:31 +0200 Subject: [PATCH] Support for plugin NIC attributes Refactor interface logic: * remove interface_properties * CRUD operations for NIC attributes * default values for NIC meta and attributes Change-Id: I26106f1b55c704a9e79d01fadc48c88a92ccc414 Implements: blueprint nics-and-nodes-attributes-via-plugin --- .../v1/validators/json_schema/interface.py | 44 ++- .../extensions/network_manager/extension.py | 4 + .../network_manager/handlers/nic.py | 16 + .../extensions/network_manager/manager.py | 100 ++--- .../network_manager/objects/bond.py | 40 ++ .../network_manager/objects/interface.py | 173 ++++++++- .../objects/serializers/nic.py | 22 +- .../network_manager/serializers/base.py | 6 +- .../serializers/neutron_serializers.py | 8 +- .../tests/test_network_manager.py | 18 - .../tests/test_node_nic_assignment.py | 23 +- .../tests/test_node_nic_handler.py | 361 +++++++++++++++--- .../tests/test_node_nic_handlers_w_bonding.py | 106 ++++- .../test_verify_node_interface_update.py | 94 ++++- .../network_manager/validators/network.py | 79 ++-- nailgun/nailgun/fixtures/openstack.yaml | 97 ++++- nailgun/nailgun/objects/__init__.py | 6 +- nailgun/nailgun/objects/cluster.py | 1 + nailgun/nailgun/objects/node.py | 83 ++-- nailgun/nailgun/objects/plugin.py | 192 +++++++++- nailgun/nailgun/plugins/adapters.py | 2 +- nailgun/nailgun/plugins/manager.py | 111 ++++++ nailgun/nailgun/test/base.py | 18 +- .../test_orchestrator_serializer.py | 39 +- .../test_orchestrator_serializer_90.py | 36 +- .../test/integration/test_plugin_manager.py | 59 +++ .../test_provisioning_serializer.py | 4 +- .../nailgun/test/unit/test_network_verify.py | 8 +- .../nailgun/test/unit/test_node_attributes.py | 4 +- .../nailgun/test/unit/test_object_plugin.py | 201 +++++++++- nailgun/nailgun/test/unit/test_task.py | 22 +- 31 files changed, 1596 insertions(+), 381 deletions(-) diff --git a/nailgun/nailgun/api/v1/validators/json_schema/interface.py b/nailgun/nailgun/api/v1/validators/json_schema/interface.py index c0dd138878..ac2bc2d3d6 100644 --- a/nailgun/nailgun/api/v1/validators/json_schema/interface.py +++ b/nailgun/nailgun/api/v1/validators/json_schema/interface.py @@ -46,19 +46,47 @@ INTERFACES = { } }, "pxe": {"type": "boolean"}, - "interface_properties": { + "attributes": { "type": "object", "properties": { + "offloading": { + "type": "object"}, + "mtu": { + "type": "object"}, "sriov": { "type": "object", "properties": { - "enabled": base_types.NULLABLE_BOOL, - "available": {"type": "boolean"}, - "sriov_numvfs": - base_types.NULLABLE_POSITIVE_INTEGER, - "sriov_totalvfs": base_types.NON_NEGATIVE_INTEGER, - "pci_id": {"type": "string"}, - "physnet": {"type": "string"} + "enabled": { + "type": "object", + "properties": { + "value": + base_types.NULLABLE_BOOL + } + }, + "numvfs": { + "type": "object", + "properties": { + "value": + base_types.NULLABLE_POSITIVE_INTEGER + } + }, + "physnet": { + "type": "object", + "properties": { + "value": {"type": "string"} + } + } + } + }, + "dpdk": { + "properties": { + "enabled": { + "type": "object", + "properties": { + "value": + base_types.NULLABLE_BOOL + } + } } } } diff --git a/nailgun/nailgun/extensions/network_manager/extension.py b/nailgun/nailgun/extensions/network_manager/extension.py index 7ee4c450d2..2db6dda8fb 100644 --- a/nailgun/nailgun/extensions/network_manager/extension.py +++ b/nailgun/nailgun/extensions/network_manager/extension.py @@ -35,6 +35,8 @@ from nailgun.extensions.network_manager.handlers.vip import \ ClusterVIPCollectionHandler from nailgun.extensions.network_manager.handlers.vip import ClusterVIPHandler +from nailgun.extensions.network_manager.handlers.nic import \ + NodeBondAttributesDefaultsHandler from nailgun.extensions.network_manager.handlers.nic import \ NodeCollectionNICsDefaultHandler from nailgun.extensions.network_manager.handlers.nic import \ @@ -79,6 +81,8 @@ class NetworkManagerExtension(BaseExtension): {'uri': r'/clusters/(?P\d+)/network_configuration/' r'nova_network/verify/?$', 'handler': NovaNetworkConfigurationVerifyHandler}, + {'uri': r'/nodes/(?P\d+)/bonds/attributes/defaults/?$', + 'handler': NodeBondAttributesDefaultsHandler}, {'uri': r'/nodes/interfaces/?$', 'handler': NodeCollectionNICsHandler}, {'uri': r'/nodes/interfaces/default_assignment/?$', diff --git a/nailgun/nailgun/extensions/network_manager/handlers/nic.py b/nailgun/nailgun/extensions/network_manager/handlers/nic.py index 38028a62f7..38dd2ac4d1 100644 --- a/nailgun/nailgun/extensions/network_manager/handlers/nic.py +++ b/nailgun/nailgun/extensions/network_manager/handlers/nic.py @@ -157,3 +157,19 @@ class NodeCollectionNICsDefaultHandler(NodeNICsDefaultHandler): nodes = objects.NodeCollection.all() return filter(lambda x: x is not None, map(self.get_default, nodes)) + + +class NodeBondAttributesDefaultsHandler(BaseHandler): + """Bond default attributes handler""" + + @handle_errors + @validate + @serialize + def GET(self, node_id): + """:returns: JSONized Bond default attributes. + + :http: * 200 (OK) + * 404 (node not found in db) + """ + node = self.get_object_or_404(objects.Node, node_id) + return objects.Node.get_bond_default_attributes(node) diff --git a/nailgun/nailgun/extensions/network_manager/manager.py b/nailgun/nailgun/extensions/network_manager/manager.py index d3337d06d5..41da504133 100644 --- a/nailgun/nailgun/extensions/network_manager/manager.py +++ b/nailgun/nailgun/extensions/network_manager/manager.py @@ -542,48 +542,6 @@ class NetworkManager(object): def clear_bond_configuration(cls, node): objects.Bond.bulk_delete([bond.id for bond in node.bond_interfaces]) - @classmethod - def get_default_interface_properties(cls, interface_properties=None): - """Interface properties defaults. - - Some interface's "defaults" properties come from hardware (for - example pci_id). If interface_properties parameter is provided - than hardware dependent properties would be get from it. - - :param interface_properties: interface properties dict - - """ - data = { - 'mtu': None, - 'disable_offloading': False, - 'sriov': { - 'enabled': False, - 'sriov_numvfs': None, - 'physnet': 'physnet2', - }, - 'dpdk': { - 'enabled': False, - } - } - if interface_properties is None: - interface_properties = {} - - sriov = interface_properties.get('sriov', {}) - dpdk = interface_properties.get('dpdk', {}) - - hw_default_properties = { - 'sriov': { - 'pci_id': sriov.get('pci_id', ''), - 'available': sriov.get('available', False), - 'sriov_totalvfs': sriov.get('sriov_totalvfs', 0), - }, - 'dpdk': { - 'available': dpdk.get('available', False), - } - } - - return nailgun_utils.dict_merge(data, hw_default_properties) - @classmethod def assign_network_to_interface_by_default(cls, ng): """Assign network to interface by default for all nodes in node group @@ -639,12 +597,9 @@ class NetworkManager(object): ), None) for nic in node.nic_interfaces: nic_dict = NodeInterfacesSerializer.serialize(nic) - if 'interface_properties' in nic_dict: - default_properties = cls.get_default_interface_properties( - nic_dict['interface_properties']) - nic_dict['interface_properties'] = nailgun_utils.dict_merge( - nic_dict['interface_properties'], - default_properties) + if 'attributes' in nic_dict: + default_attributes = objects.NIC.get_default_attributes(nic) + nic_dict['attributes'] = default_attributes nic_dict['assigned_networks'] = [] if to_assign_ids: @@ -812,17 +767,17 @@ class NetworkManager(object): in iface['assigned_networks']] objects.NIC.assign_networks(current_iface, nets_to_assign) update = {} - if 'interface_properties' in iface: - update['interface_properties'] = nailgun_utils.dict_merge( - current_iface.interface_properties, - iface['interface_properties'] - ) if 'offloading_modes' in iface: update['offloading_modes'] = iface['offloading_modes'] + if 'attributes' in iface: + update['attributes'] = nailgun_utils.dict_merge( + current_iface.attributes, + iface['attributes'] + ) objects.NIC.update(current_iface, update) - objects.Node.clear_bonds(node_db) + objects.Node.clear_bonds(node_db) for bond in bond_interfaces: if bond.get('bond_properties', {}).get('mode'): mode = bond['bond_properties']['mode'] @@ -834,7 +789,7 @@ class NetworkManager(object): 'mode': mode, 'mac': bond.get('mac'), 'bond_properties': bond.get('bond_properties', {}), - 'interface_properties': bond.get('interface_properties', {}), + 'attributes': bond.get('attributes', {}) } bond_db = objects.Bond.create(data) @@ -869,6 +824,7 @@ class NetworkManager(object): except errors.InvalidInterfacesInfo as e: logger.debug("Cannot update interfaces: %s", e.message) return + pxe_iface_name = cls._get_pxe_iface_name(node) for interface in node.meta["interfaces"]: # set 'pxe' property for appropriate iface @@ -1007,20 +963,21 @@ class NetworkManager(object): interface.driver = interface_attrs.get('driver') interface.bus_info = interface_attrs.get('bus_info') interface.pxe = interface_attrs.get('pxe', False) - - interface_properties = nailgun_utils.dict_merge( - cls.get_default_interface_properties(), - interface.interface_properties or {} + meta = nailgun_utils.dict_merge( + objects.NIC.get_default_meta(), + interface.meta or {} ) - if interface_attrs.get('interface_properties'): - interface_properties = nailgun_utils.dict_merge( - interface_properties, - interface_attrs['interface_properties'] + meta = nailgun_utils.dict_merge( + meta, + objects.NIC.get_default_meta(interface_attrs[ + 'interface_properties']) ) - # update interface_properties in DB only if something was changed - if interface.interface_properties != interface_properties: - interface.interface_properties = interface_properties + if interface_attrs.get('offloading_modes'): + meta['offloading_modes'] = interface_attrs['offloading_modes'] + + if interface.meta != meta: + interface.meta = meta @classmethod def __delete_not_found_interfaces(cls, node, interfaces): @@ -1407,13 +1364,16 @@ class NetworkManager(object): @classmethod def get_iface_properties(cls, iface): properties = {} - if iface.interface_properties.get('mtu'): - properties['mtu'] = iface.interface_properties['mtu'] - if iface.interface_properties.get('disable_offloading'): + if iface.attributes.get('mtu', {}).get('value', {}).get('value'): + properties['mtu'] = iface.attributes['mtu']['value']['value'] + if iface.attributes.get('offloading', {}).get('disable', {}).get( + 'value'): properties['vendor_specific'] = { 'disable_offloading': - iface.interface_properties['disable_offloading'] + iface.attributes['offloading']['disable']['value'] } + + # TODO(apopovych): rewrite to get offloading data from attributes if iface.offloading_modes: modified_offloading_modes = \ cls._get_modified_offloading_modes(iface.offloading_modes) diff --git a/nailgun/nailgun/extensions/network_manager/objects/bond.py b/nailgun/nailgun/extensions/network_manager/objects/bond.py index 31f227c56f..f45155a979 100644 --- a/nailgun/nailgun/extensions/network_manager/objects/bond.py +++ b/nailgun/nailgun/extensions/network_manager/objects/bond.py @@ -14,6 +14,7 @@ # License for the specific language governing permissions and limitations # under the License. +import copy from nailgun.db import db from nailgun.db.sqlalchemy import models @@ -22,6 +23,8 @@ from nailgun.extensions.network_manager.objects.interface import NIC from nailgun.objects import NailgunCollection from nailgun.objects import NailgunObject from nailgun.objects.serializers.base import BasicSerializer +from nailgun.plugins.manager import PluginManager +from nailgun import utils class Bond(DPDKMixin, NailgunObject): @@ -29,6 +32,13 @@ class Bond(DPDKMixin, NailgunObject): model = models.NodeBondInterface serializer = BasicSerializer + @classmethod + def create(cls, data): + bond = super(Bond, cls).create(data) + bond = PluginManager.add_plugin_attributes_for_bond(bond) + + return bond + @classmethod def assign_networks(cls, instance, networks): """Assigns networks to specified Bond interface. @@ -73,6 +83,36 @@ class Bond(DPDKMixin, NailgunObject): aliased=True).filter(models.NetworkGroup.id.in_(networks)) return bond_interfaces_query.all() + @classmethod + def get_attributes(cls, instance): + """Get native and plugin attributes for bond. + + :param instance: NodeBondInterface instance + :type instance: NodeBondInterface model + :returns: dict -- Object of bond attributes + """ + attributes = copy.deepcopy(instance.attributes) + attributes = utils.dict_merge( + attributes, + PluginManager.get_bond_attributes(instance)) + + return attributes + + @classmethod + def get_bond_default_attributes(cls, cluster): + """Get native and plugin default attributes for bond. + + :param cluster: A cluster instance + :type cluster: Cluster model + :returns: dict -- Object of bond default attributes + """ + default_attributes = cluster.release.bond_attributes + default_attributes = utils.dict_merge( + default_attributes, + PluginManager.get_bond_default_attributes(cluster)) + + return default_attributes + class BondCollection(NailgunCollection): diff --git a/nailgun/nailgun/extensions/network_manager/objects/interface.py b/nailgun/nailgun/extensions/network_manager/objects/interface.py index e3c0eb5c3d..4109684f8d 100644 --- a/nailgun/nailgun/extensions/network_manager/objects/interface.py +++ b/nailgun/nailgun/extensions/network_manager/objects/interface.py @@ -14,6 +14,8 @@ # License for the specific language governing permissions and limitations # under the License. +import copy + import six from sqlalchemy.sql import not_ @@ -23,6 +25,8 @@ from nailgun.objects import Cluster from nailgun.objects import NailgunCollection from nailgun.objects import NailgunObject from nailgun.objects.serializers.base import BasicSerializer +from nailgun.plugins.manager import PluginManager +from nailgun import utils class DPDKMixin(object): @@ -33,21 +37,24 @@ class DPDKMixin(object): @classmethod def dpdk_enabled(cls, instance): - dpdk = instance.interface_properties.get('dpdk') - return bool(dpdk and dpdk.get('enabled')) + return bool(instance.attributes.get('dpdk', {}).get( + 'enabled', {}).get('value')) @classmethod def refresh_interface_dpdk_properties(cls, interface, dpdk_drivers): - interface_properties = interface.interface_properties - dpdk_properties = interface_properties.get('dpdk', {}).copy() - dpdk_properties['available'] = cls.dpdk_available(interface, - dpdk_drivers) - if (not dpdk_properties['available'] and - dpdk_properties.get('enabled')): - dpdk_properties['enabled'] = False - # update interface_properties in DB only if something was changed - if interface_properties.get('dpdk', {}) != dpdk_properties: - interface_properties['dpdk'] = dpdk_properties + attributes = interface.attributes + meta = interface.meta + dpdk_attributes = copy.deepcopy(attributes.get('dpdk', {})) + dpdk_available = cls.dpdk_available(interface, dpdk_drivers) + + if (not dpdk_available and dpdk_attributes.get( + 'enabled', {}).get('value')): + dpdk_attributes['enabled']['value'] = False + # update attributes in DB only if something was changed + if attributes.get('dpdk', {}) != dpdk_attributes: + attributes['dpdk'] = dpdk_attributes + if meta.get('dpdk', {}) != {'available': dpdk_available}: + meta['dpdk'] = {'available': dpdk_available} class NIC(DPDKMixin, NailgunObject): @@ -70,7 +77,7 @@ class NIC(DPDKMixin, NailgunObject): @classmethod def get_dpdk_driver(cls, instance, dpdk_drivers): - pci_id = instance.interface_properties.get('pci_id', '').lower() + pci_id = instance.meta.get('pci_id', '').lower() for driver, device_ids in six.iteritems(dpdk_drivers): if pci_id in device_ids: return driver @@ -97,8 +104,8 @@ class NIC(DPDKMixin, NailgunObject): @classmethod def is_sriov_enabled(cls, instance): - sriov = instance.interface_properties.get('sriov') - return sriov and sriov['enabled'] + enabled = instance.attributes.get('sriov', {}).get('enabled') + return enabled and enabled['value'] @classmethod def update_offloading_modes(cls, instance, new_modes, keep_states=False): @@ -220,6 +227,142 @@ class NIC(DPDKMixin, NailgunObject): return nic + @classmethod + def create_attributes(cls, instance): + """Create attributes for interface with default values. + + :param instance: NodeNICInterface instance + :type instance: NodeNICInterface model + :returns: None + """ + instance.attributes = cls._get_default_attributes(instance) + PluginManager.add_plugin_attributes_for_interface(instance) + + db().flush() + + @classmethod + def get_attributes(cls, instance): + """Get all attributes for interface. + + :param instance: NodeNICInterface instance + :type instance: NodeNICInterface model + :returns: dict -- Object of interface attributes + """ + attributes = copy.deepcopy(instance.attributes) + attributes = utils.dict_merge( + attributes, + PluginManager.get_nic_attributes(instance)) + + return attributes + + @classmethod + def get_default_attributes(cls, instance): + """Get default attributes for interface. + + :param instance: NodeNICInterface instance + :type instance: NodeNICInterface model + :returns: dict -- Dict object of NIC attributes + """ + attributes = cls._get_default_attributes(instance) + attributes = utils.dict_merge( + attributes, + PluginManager.get_nic_attributes(instance)) + attributes = utils.dict_merge( + attributes, + PluginManager.get_nic_default_attributes( + instance.node.cluster)) + + return attributes + + @classmethod + def update(cls, instance, data): + """Update data of native and plugin attributes for interface. + + :param instance: NodeNICInterface instance + :type instance: NodeNICInterface model + :param data: Data to update + :type data: dict + :returns: None + """ + attributes = data.pop('attributes', None) + if attributes: + PluginManager.update_nic_attributes(attributes) + instance.attributes = utils.dict_merge( + instance.attributes, attributes) + + return super(NIC, cls).update(instance, data) + + @classmethod + def offloading_modes_as_flat_dict(cls, modes): + """Represents multilevel structure of offloading modes as flat dict + + This is done to ease merging + :param modes: list of offloading modes + :return: flat dictionary {mode['name']: mode['state']} + """ + result = dict() + if modes is None: + return result + for mode in modes: + result[mode["name"]] = mode["state"] + if mode["sub"]: + result.update(cls.offloading_modes_as_flat_dict(mode["sub"])) + return result + + @classmethod + def _get_default_attributes(cls, instance): + attributes = copy.deepcopy( + instance.node.cluster.release.nic_attributes) + + mac = instance.mac + meta = instance.node.meta + name = instance.name + offloading_modes = [] + properties = {} + + for interface in meta.get('interfaces', []): + if interface['name'] == name and interface['mac'] == mac: + properties = interface.get('interface_properties', {}) + offloading_modes = interface.get('offloading_modes', []) + + attributes['mtu']['value']['value'] = properties.get('mtu') + attributes['sriov']['enabled']['value'] = \ + properties.get('sriov', {}).get('enabled', False) + attributes['sriov']['numvfs']['value'] = \ + properties.get('sriov', {}).get('sriov_numvfs') + attributes['sriov']['physnet']['value'] = \ + properties.get('sriov', {}).get('physnet', 'physnet2') + attributes['dpdk']['enabled']['value'] = \ + properties.get('dpdk', {}).get('enabled', False) + + offloading_modes_values = cls.offloading_modes_as_flat_dict( + offloading_modes) + attributes['offloading']['disable']['value'] = \ + properties.get('disable_offloading', False) + attributes['offloading']['modes']['value'] = offloading_modes_values + + return attributes + + @classmethod + def get_default_meta(cls, interface_properties={}): + return { + 'offloading_modes': [], + 'sriov': { + 'available': interface_properties.get( + 'sriov', {}).get('available', False), + 'pci_id': interface_properties.get( + 'sriov', {}).get('pci_id', ''), + 'totalvfs': interface_properties.get( + 'sriov', {}).get('sriov_totalvfs', 0) + }, + 'dpdk': { + 'available': interface_properties.get( + 'dpdk', {}).get('available', False) + }, + 'pci_id': interface_properties.get('pci_id', ''), + 'numa_node': interface_properties.get('numa_node') + } + class NICCollection(NailgunCollection): diff --git a/nailgun/nailgun/extensions/network_manager/objects/serializers/nic.py b/nailgun/nailgun/extensions/network_manager/objects/serializers/nic.py index 15d412387a..2f6c3fdde4 100644 --- a/nailgun/nailgun/extensions/network_manager/objects/serializers/nic.py +++ b/nailgun/nailgun/extensions/network_manager/objects/serializers/nic.py @@ -29,13 +29,13 @@ class NodeInterfacesSerializer(BasicSerializer): 'mac', 'name', 'type', - 'interface_properties', 'state', 'current_speed', 'max_speed', 'assigned_networks', 'driver', 'bus_info', + 'meta', 'offloading_modes', 'pxe' ) @@ -85,31 +85,31 @@ class NodeInterfacesSerializer(BasicSerializer): @classmethod def serialize_nic_interface(cls, instance, fields=None): + from nailgun.extensions.network_manager.objects.interface import NIC if not fields: if StrictVersion(cls._get_env_version(instance)) < \ StrictVersion('6.1'): fields = cls.nic_fields_60 else: fields = cls.nic_fields - return BasicSerializer.serialize( - instance, - fields=fields - ) + data_dict = BasicSerializer.serialize(instance, fields=fields) + data_dict['attributes'] = NIC.get_attributes(instance) + + return data_dict @classmethod def serialize_bond_interface(cls, instance, fields=None): + from nailgun.extensions.network_manager.objects.bond import Bond if not fields: if StrictVersion(cls._get_env_version(instance)) < \ StrictVersion('6.1'): fields = cls.bond_fields_60 else: fields = cls.bond_fields - data_dict = BasicSerializer.serialize( - instance, - fields=fields - ) - data_dict['slaves'] = [{'name': slave.name} - for slave in instance.slaves] + data_dict = BasicSerializer.serialize(instance, fields=fields) + data_dict['slaves'] = [{'name': s.name} for s in instance.slaves] + data_dict['attributes'] = Bond.get_attributes(instance) + return data_dict @classmethod diff --git a/nailgun/nailgun/extensions/network_manager/serializers/base.py b/nailgun/nailgun/extensions/network_manager/serializers/base.py index 4ce43897be..cacabb1790 100644 --- a/nailgun/nailgun/extensions/network_manager/serializers/base.py +++ b/nailgun/nailgun/extensions/network_manager/serializers/base.py @@ -192,10 +192,11 @@ class NetworkDeploymentSerializer(object): 'name': iface.name, 'interfaces': sorted(x['name'] for x in iface.slaves), } - if iface.interface_properties.get('mtu'): - bond['mtu'] = iface.interface_properties['mtu'] + if iface.attributes.get('mtu', {}).get('value', {}).get('value'): + bond['mtu'] = iface.attributes['mtu']['value']['value'] if parameters: bond.update(parameters) + return bond @classmethod @@ -214,4 +215,5 @@ class NetworkDeploymentSerializer(object): patch['provider'] = provider if mtu: patch['mtu'] = mtu + return patch diff --git a/nailgun/nailgun/extensions/network_manager/serializers/neutron_serializers.py b/nailgun/nailgun/extensions/network_manager/serializers/neutron_serializers.py index 29ff38bbd4..478a93ef20 100644 --- a/nailgun/nailgun/extensions/network_manager/serializers/neutron_serializers.py +++ b/nailgun/nailgun/extensions/network_manager/serializers/neutron_serializers.py @@ -1536,7 +1536,7 @@ class SriovSerializerMixin90(object): for n in cluster.nodes: for nic in n.nic_interfaces: if objects.NIC.is_sriov_enabled(nic): - pci_ids.add(nic.interface_properties['sriov']['pci_id']) + pci_ids.add(nic.meta['sriov']['pci_id']) if pci_ids: attrs['supported_pci_vendor_devs'] = list(pci_ids) return attrs @@ -1561,10 +1561,10 @@ class NeutronNetworkDeploymentSerializer90( # add ports with SR-IOV settings for SR-IOV enabled NICs if (not iface.bond and iface.name not in nets_by_ifaces and objects.NIC.is_sriov_enabled(iface)): - sriov = iface.interface_properties['sriov'] + sriov = iface.attributes['sriov'] config = { - 'sriov_numvfs': sriov['sriov_numvfs'], - 'physnet': sriov['physnet'] + 'sriov_numvfs': sriov['numvfs']['value'], + 'physnet': sriov['physnet']['value'] } transformations.append(cls.add_port( iface.name, bridge=None, provider='sriov', diff --git a/nailgun/nailgun/extensions/network_manager/tests/test_network_manager.py b/nailgun/nailgun/extensions/network_manager/tests/test_network_manager.py index e2aab71233..ee84f60d95 100644 --- a/nailgun/nailgun/extensions/network_manager/tests/test_network_manager.py +++ b/nailgun/nailgun/extensions/network_manager/tests/test_network_manager.py @@ -1060,24 +1060,6 @@ class TestNetworkManager(BaseIntegrationTest): for iface in node.interfaces: self.assertEqual([], iface.assigned_networks_list) - def test_get_default_interface_properties(self): - defaults = self.env.network_manager.get_default_interface_properties() - hw_data = { - 'sriov': { - 'pci_id': '123:456', - 'available': True, - 'sriov_totalvfs': 100500, - }, - 'dpdk': { - 'available': True, - } - } - expected = nailgun.utils.dict_merge(defaults, hw_data) - test = self.env.network_manager.get_default_interface_properties( - hw_data - ) - self.assertEqual(test, expected) - class TestNovaNetworkManager(BaseIntegrationTest): diff --git a/nailgun/nailgun/extensions/network_manager/tests/test_node_nic_assignment.py b/nailgun/nailgun/extensions/network_manager/tests/test_node_nic_assignment.py index abcaa879f1..9c906a6728 100644 --- a/nailgun/nailgun/extensions/network_manager/tests/test_node_nic_assignment.py +++ b/nailgun/nailgun/extensions/network_manager/tests/test_node_nic_assignment.py @@ -467,15 +467,16 @@ class TestNodeHandlers(BaseIntegrationTest): class TestNodeNICsSerialization(BaseIntegrationTest): - versions = [('2014.1', False), - ('2014.1.1-5.0.1', False), - ('2014.1.1-5.1', False), - ('2014.1.1-5.1.1', False), - ('2014.2-6.0', False), - ('2014.2-6.0.1', False), - ('2014.2-6.1', True)] + versions = [('2014.1', True), + ('2014.1.1-5.0.1', True), + ('2014.1.1-5.1', True), + ('2014.1.1-5.1.1', True), + ('2014.2-6.0', True), + ('2014.2-6.0.1', True), + ('2014.2-6.1', True), + ('newton-10.0', True)] - def check_nics_interface_properties(self, handler): + def check_nics_interface_attributes(self, handler): for ver, present in self.versions: cluster = self.env.create( release_kwargs={'version': ver}, @@ -492,7 +493,7 @@ class TestNodeNICsSerialization(BaseIntegrationTest): headers=self.default_headers ) self.assertEqual(resp.status_code, 200) - self.assertEqual('interface_properties' in resp.json_body[0], + self.assertEqual('attributes' in resp.json_body[0], present) objects.Node.delete(node) objects.Cluster.delete(cluster) @@ -500,10 +501,10 @@ class TestNodeNICsSerialization(BaseIntegrationTest): self.env.clusters = [] def test_interface_properties_in_default_nic_information(self): - self.check_nics_interface_properties('NodeNICsDefaultHandler') + self.check_nics_interface_attributes('NodeNICsDefaultHandler') def test_interface_properties_in_current_nic_information(self): - self.check_nics_interface_properties('NodeNICsHandler') + self.check_nics_interface_attributes('NodeNICsHandler') class TestNodeNICAdminAssigning(BaseIntegrationTest): diff --git a/nailgun/nailgun/extensions/network_manager/tests/test_node_nic_handler.py b/nailgun/nailgun/extensions/network_manager/tests/test_node_nic_handler.py index e40ae00ece..f2a1dacbd1 100644 --- a/nailgun/nailgun/extensions/network_manager/tests/test_node_nic_handler.py +++ b/nailgun/nailgun/extensions/network_manager/tests/test_node_nic_handler.py @@ -15,6 +15,7 @@ # under the License. from mock import patch +import uuid from copy import deepcopy from oslo_serialization import jsonutils @@ -241,9 +242,13 @@ class TestHandlers(BaseIntegrationTest): def test_NIC_updates_by_agent(self): meta = self.env.default_metadata() - self.env.set_interfaces_in_meta(meta, [ - {'name': 'eth0', 'mac': '00:00:00:00:00:00', 'current_speed': 1, - 'state': 'up'}]) + self.env.set_interfaces_in_meta(meta, [{ + 'name': 'eth0', + 'mac': '00:00:00:00:00:00', + 'current_speed': 1, + 'state': 'up'} + ]) + node = self.env.create_node(api=True, meta=meta) new_meta = self.env.default_metadata() self.env.set_interfaces_in_meta(new_meta, [ @@ -268,7 +273,8 @@ class TestHandlers(BaseIntegrationTest): jsonutils.dumps(node_data), headers=self.default_headers) self.assertEqual(resp.status_code, 200) - + # add node into cluster + self.env.create_cluster(nodes=[node['id']]) resp = self.app.get( reverse('NodeNICsHandler', kwargs={'node_id': node['id']}), headers=self.default_headers) @@ -281,23 +287,56 @@ class TestHandlers(BaseIntegrationTest): self.assertEqual(resp_nic['max_speed'], nic['max_speed']) self.assertEqual(resp_nic['state'], nic['state']) self.assertEqual( - resp_nic['interface_properties']['sriov'], + resp_nic['attributes']['sriov'], { - 'enabled': False, - 'sriov_numvfs': None, - 'sriov_totalvfs': 8, - 'available': True, - 'pci_id': '1234:5678', - 'physnet': 'physnet2' + 'metadata': { + 'label': 'SR-IOV', + 'weight': 30 + }, + 'enabled': { + 'label': 'SR-IOV enabled', + 'weight': 10, + 'type': 'checkbox', + 'value': False + }, + 'numvfs': { + 'label': 'Virtual functions', + 'weight': 20, + 'type': 'number', + 'min': 0, + 'value': None + }, + 'physnet': { + 'label': 'Physical network', + 'weight': 30, + 'type': 'text', + 'value': 'physnet2' + } }) self.assertEqual( - resp_nic['interface_properties']['dpdk'], + resp_nic['meta']['sriov'], { - 'enabled': False, - 'available': False + 'available': True, + 'pci_id': '1234:5678', + 'totalvfs': 8 }) - for conn in ('assigned_networks', ): - self.assertEqual(resp_nic[conn], []) + + self.assertEqual( + resp_nic['attributes']['dpdk'], + { + 'metadata': { + 'label': 'DPDK', + 'weight': 40 + }, + 'enabled': { + 'label': 'DPDK enabled', + 'weight': 10, + 'type': 'checkbox', + 'value': False + } + }) + self.assertEqual( + resp_nic['meta']['dpdk'], {'available': False}) def create_cluster_and_node_with_dpdk_support(self, segment_type, drivers_mock): @@ -339,17 +378,27 @@ class TestHandlers(BaseIntegrationTest): self.assertEqual(len(resp.json_body), 1) resp_nic = resp.json_body[0] self.assertEqual( - resp_nic['interface_properties']['dpdk'], + resp_nic['attributes']['dpdk'], { - 'enabled': False, - 'available': dpdk_available + 'metadata': { + 'label': 'DPDK', + 'weight': 40 + }, + 'enabled': { + 'label': 'DPDK enabled', + 'weight': 10, + 'type': 'checkbox', + 'value': False + } }) + self.assertEqual( + resp_nic['meta']['dpdk'], {'available': dpdk_available}) return resp.json_body def check_put_request_passes_without_dpdk_section(self, node, nics): # remove 'dpdk' section from all interfaces for nic in nics: - nic['interface_properties'].pop('dpdk', None) + nic['attributes'].pop('dpdk', None) resp = self.app.put( reverse("NodeNICsHandler", kwargs={"node_id": node['id']}), jsonutils.dumps(nics), @@ -553,7 +602,7 @@ class TestHandlers(BaseIntegrationTest): @patch.dict('nailgun.api.v1.handlers.version.settings.VERSION', { 'release': '6.1'}) - def test_interface_properties_after_update_by_agent(self): + def test_interface_attributes_after_update_by_agent(self): meta = self.env.default_metadata() self.env.set_interfaces_in_meta(meta, [ {'name': 'eth0', 'mac': '00:00:00:00:00:00', 'current_speed': 1, @@ -571,12 +620,20 @@ class TestHandlers(BaseIntegrationTest): headers=self.default_headers) self.assertEqual(resp.status_code, 200) nic = resp.json_body[0] - self.assertEqual( - nic['interface_properties'], - self.env.network_manager.get_default_interface_properties() - ) + # change mtu - nic['interface_properties']['mtu'] = 1500 + nic['attributes']['mtu'] = { + 'metadata': { + 'label': 'MTU', + 'weight': 20 + }, + 'value': { + 'label': 'MTU', + 'weight': 10, + 'type': 'number', + 'value': 1500 + } + } nodes_list = [{'id': node['id'], 'interfaces': [nic]}] resp_put = self.app.put( reverse('NodeCollectionNICsHandler'), @@ -596,7 +653,18 @@ class TestHandlers(BaseIntegrationTest): headers=self.default_headers) self.assertEqual(resp.status_code, 200) resp_nic = resp.json_body[0] - self.assertEqual(resp_nic['interface_properties']['mtu'], 1500) + self.assertEqual(resp_nic['attributes']['mtu'], { + 'metadata': { + 'label': 'MTU', + 'weight': 20 + }, + 'value': { + 'label': 'MTU', + 'weight': 10, + 'type': 'number', + 'value': 1500 + } + }) def test_nic_adds_by_agent(self): meta = self.env.default_metadata() @@ -894,27 +962,22 @@ class TestSriovHandlers(BaseIntegrationTest): return resp.json_body def test_get_update_sriov_properties(self): - sriov_default = \ - self.env.network_manager.get_default_interface_properties()[ - 'sriov'] # Use NIC #2 as SR-IOV can be enabled only on NICs that have no # assigned networks. First two NICs have assigned networks. - self.assertEqual(self.nics[2]['interface_properties']['sriov'], - sriov_default) # change NIC properties in DB as SR-IOV parameters can be set up only # for NICs that have hardware SR-IOV support nic = self.env.nodes[0].nic_interfaces[2] - nic.interface_properties['sriov']['available'] = True - nic.interface_properties['sriov']['sriov_totalvfs'] = 8 - nic.interface_properties.changed() + nic.meta['sriov']['available'] = True + nic.meta['sriov']['totalvfs'] = 8 + nic.meta.changed() nics = self.get_node_interfaces() - nics[2]['interface_properties']['sriov'].update({ - 'enabled': True, - 'sriov_numvfs': 8, - 'physnet': 'new_physnet' - }) + sriov = nics[2]['attributes']['sriov'] + sriov['enabled']['value'] = True + sriov['numvfs']['value'] = 8 + sriov['physnet']['value'] = 'new_physnet' + resp = self.app.put( reverse("NodeNICsHandler", kwargs={"node_id": self.env.nodes[0].id}), @@ -922,13 +985,13 @@ class TestSriovHandlers(BaseIntegrationTest): headers=self.default_headers) self.assertEqual(resp.status_code, 200) - sriov = resp.json_body[2]['interface_properties']['sriov'] - self.assertEqual(sriov['enabled'], True) - self.assertEqual(sriov['sriov_numvfs'], 8) - self.assertEqual(sriov['physnet'], 'new_physnet') + sriov = resp.json_body[2]['attributes']['sriov'] + self.assertEqual(sriov['enabled']['value'], True) + self.assertEqual(sriov['numvfs']['value'], 8) + self.assertEqual(sriov['physnet']['value'], 'new_physnet') def test_update_readonly_sriov_properties_failed(self): - self.nics[0]['interface_properties']['sriov']['available'] = True + self.nics[0]['meta']['sriov']['available'] = True resp = self.app.put( reverse("NodeNICsHandler", @@ -944,7 +1007,7 @@ class TestSriovHandlers(BaseIntegrationTest): self.env.nodes[0].id, self.nics[0]['name'])) def test_enable_sriov_failed_when_not_available(self): - self.nics[0]['interface_properties']['sriov']['enabled'] = True + self.nics[0]['attributes']['sriov']['enabled']['value'] = True resp = self.app.put( reverse("NodeNICsHandler", @@ -960,7 +1023,7 @@ class TestSriovHandlers(BaseIntegrationTest): self.env.nodes[0].id, self.nics[0]['name'])) def test_set_sriov_numvfs_failed(self): - self.nics[0]['interface_properties']['sriov']['sriov_numvfs'] = 8 + self.nics[0]['attributes']['sriov']['numvfs']['value'] = 8 resp = self.app.put( reverse("NodeNICsHandler", @@ -976,7 +1039,7 @@ class TestSriovHandlers(BaseIntegrationTest): self.env.nodes[0].id, self.nics[0]['name'])) def test_set_sriov_numvfs_failed_negative_value(self): - self.nics[0]['interface_properties']['sriov']['sriov_numvfs'] = -40 + self.nics[0]['attributes']['sriov']['numvfs']['value'] = -40 resp = self.app.put( reverse("NodeNICsHandler", @@ -991,7 +1054,7 @@ class TestSriovHandlers(BaseIntegrationTest): ) def test_set_sriov_numvfs_failed_float_value(self): - self.nics[0]['interface_properties']['sriov']['sriov_numvfs'] = 2.5 + self.nics[0]['attributes']['sriov']['numvfs']['value'] = 2.5 resp = self.app.put( reverse("NodeNICsHandler", @@ -1006,7 +1069,7 @@ class TestSriovHandlers(BaseIntegrationTest): ) def test_set_sriov_numvfs_zero_value(self): - self.nics[0]['interface_properties']['sriov']['sriov_numvfs'] = 0 + self.nics[0]['attributes']['sriov']['numvfs']['value'] = 0 resp = self.app.put( reverse("NodeNICsHandler", @@ -1024,12 +1087,12 @@ class TestSriovHandlers(BaseIntegrationTest): # change NIC properties in DB as SR-IOV parameters can be set up only # for NICs that have hardware SR-IOV support nic = objects.NIC.get_by_uid(self.env.nodes[0].nic_interfaces[0].id) - nic.interface_properties['sriov']['available'] = True - nic.interface_properties['sriov']['sriov_totalvfs'] = 8 - nic.interface_properties.changed() + nic.meta['sriov']['available'] = True + nic.meta['sriov']['totalvfs'] = 8 + nic.meta.changed() nics = self.get_node_interfaces() - nics[0]['interface_properties']['sriov']['enabled'] = True + nics[0]['attributes']['sriov']['enabled']['value'] = True resp = self.app.put( reverse("NodeNICsHandler", @@ -1067,7 +1130,7 @@ class TestSriovHandlers(BaseIntegrationTest): headers=self.default_headers) self.assertEqual(resp.status_code, 200) nics = resp.json_body - nics[0]['interface_properties']['sriov']['enabled'] = True + nics[0]['attributes']['sriov']['enabled']['value'] = True resp = self.app.put( reverse("NodeNICsHandler", @@ -1083,13 +1146,13 @@ class TestSriovHandlers(BaseIntegrationTest): def test_enable_sriov_failed_when_nic_has_networks_assigned(self): nic = self.env.nodes[0].nic_interfaces[0] - nic.interface_properties['sriov']['available'] = True - nic.interface_properties['sriov']['sriov_totalvfs'] = 8 - nic.interface_properties.changed() + nic.meta['sriov']['available'] = True + nic.meta['sriov']['totalvfs'] = 8 + nic.meta.changed() nics = self.get_node_interfaces() - nics[0]['interface_properties']['sriov']['enabled'] = True - nics[0]['interface_properties']['sriov']['sriov_numvfs'] = 8 + nics[0]['attributes']['sriov']['enabled']['value'] = True + nics[0]['attributes']['sriov']['numvfs']['value'] = 8 resp = self.app.put( reverse("NodeNICsHandler", @@ -1103,3 +1166,181 @@ class TestSriovHandlers(BaseIntegrationTest): "Node '{0}' interface '{1}': SR-IOV cannot be enabled when " "networks are assigned to the interface".format( self.env.nodes[0].id, nics[0]['name'])) + + +class TestNICAttributesHandlers(BaseIntegrationTest): + + EXPECTED_ATTRIBUTES = { + 'offloading': { + 'disable': { + 'value': False, + 'label': 'Disable offloading', + 'type': 'checkbox', + 'weight': 10 + }, + 'metadata': { + 'label': 'Offloading', + 'weight': 10 + }, + 'modes': { + 'description': 'Offloading modes', + 'value': {}, + 'label': 'Offloading modes', + 'type': 'offloading_modes', + 'weight': 20 + } + }, + 'mtu': { + 'value': { + 'value': None, + 'label': 'MTU', + 'type': 'number', + 'weight': 10 + }, + 'metadata': { + 'label': 'MTU', + 'weight': 20 + } + }, + 'sriov': { + 'enabled': { + 'value': False, + 'label': 'SR-IOV enabled', + 'type': 'checkbox', + 'weight': 10 + }, + 'physnet': { + 'value': 'physnet2', + 'label': 'Physical network', + 'type': 'text', + 'weight': 30 + }, + 'metadata': { + 'label': 'SR-IOV', + 'weight': 30 + }, + 'numvfs': { + 'value': None, + 'label': 'Virtual functions', + 'weight': 20, + 'type': 'number', + 'min': 0 + }, + }, + 'dpdk': { + 'enabled': { + 'value': False, + 'label': 'DPDK enabled', + 'type': 'checkbox', + 'weight': 10 + }, + 'metadata': { + 'label': 'DPDK', + 'weight': 40 + } + }, + 'plugin_a_with_nic_attributes': { + 'plugin_name_text': { + 'value': 'value', + 'weight': 25, + 'type': 'text', + 'label': 'label', + 'description': 'Some description' + }, + 'metadata': { + 'label': 'Test plugin', + 'class': 'plugin' + } + } + } + + OFFLOADING_MODES = { + 'eth0': { + 'rx-checksumming': None, + 'tx-checksum-sctp': False, + 'tx-checksumming': True, + 'tx-checksum-ipv4': None, + 'tx-checksum-ipv6': True + }, + 'eth1': { + 'tx-checksum-sctp': None, + 'rx-checksumming': None, + 'tx-checksumming': None, + 'tx-checksum-ipv6': False, + 'tx-checksum-ipv4': None + } + } + + def setUp(self): + super(TestNICAttributesHandlers, self).setUp() + self.cluster = self.env.create( + release_kwargs={ + 'name': uuid.uuid4().get_hex(), + 'version': 'newton-10.0', + 'operating_system': 'Ubuntu', + 'modes': [consts.CLUSTER_MODES.ha_compact]}, + nodes_kwargs=[{'roles': ['controller']}]) + self.node = self.env.nodes[0] + self.env.create_plugin( + name='plugin_a_with_nic_attributes', + package_version='5.0.0', + cluster=self.cluster, + nic_attributes_metadata=self.env.get_default_plugin_nic_config()) + + def test_get_nic_attributes(self): + resp = self.app.get( + reverse("NodeNICsHandler", kwargs={"node_id": self.node.id}), + headers=self.default_headers) + self.assertEqual(resp.status_code, 200) + expected_attributes = deepcopy(self.EXPECTED_ATTRIBUTES) + + for nic in resp.json_body: + expected_attributes['offloading']['modes']['value'] = \ + self.OFFLOADING_MODES[nic['name']] + del nic['attributes']['plugin_a_with_nic_attributes'][ + 'metadata']['nic_plugin_id'] + self.assertEqual(expected_attributes, nic['attributes']) + + def test_put_nic_attributes(self): + resp = self.app.get( + reverse("NodeNICsHandler", kwargs={"node_id": self.node.id}), + headers=self.default_headers) + self.assertEqual(resp.status_code, 200) + nics = deepcopy(resp.json_body) + expected_attributes = deepcopy(self.EXPECTED_ATTRIBUTES) + + for nic in nics: + nic['attributes']['mtu']['value']['value'] = 1000 + nic['attributes']['plugin_a_with_nic_attributes'][ + 'plugin_name_text']['value'] = 'test' + + resp = self.app.put( + reverse("NodeNICsHandler", + kwargs={"node_id": self.node.id}), + jsonutils.dumps(nics), + headers=self.default_headers) + self.assertEqual(resp.status_code, 200) + + for nic in resp.json_body: + expected_attributes['offloading']['modes']['value'] = \ + self.OFFLOADING_MODES[nic['name']] + expected_attributes['mtu']['value']['value'] = 1000 + expected_attributes['plugin_a_with_nic_attributes'][ + 'plugin_name_text']['value'] = 'test' + del nic['attributes']['plugin_a_with_nic_attributes'][ + 'metadata']['nic_plugin_id'] + self.assertEqual(expected_attributes, nic['attributes']) + + def test_get_default_attributes(self): + resp = self.app.get( + reverse("NodeNICsDefaultHandler", + kwargs={"node_id": self.node.id}), + headers=self.default_headers) + expected_attributes = deepcopy(self.EXPECTED_ATTRIBUTES) + + for nic in resp.json_body: + expected_attributes['offloading']['modes']['value'] = \ + self.OFFLOADING_MODES[nic['name']] + del nic['attributes']['plugin_a_with_nic_attributes'][ + 'metadata']['nic_plugin_id'] + self.assertEqual(expected_attributes, nic['attributes']) diff --git a/nailgun/nailgun/extensions/network_manager/tests/test_node_nic_handlers_w_bonding.py b/nailgun/nailgun/extensions/network_manager/tests/test_node_nic_handlers_w_bonding.py index d9db32a32a..91c76922d9 100644 --- a/nailgun/nailgun/extensions/network_manager/tests/test_node_nic_handlers_w_bonding.py +++ b/nailgun/nailgun/extensions/network_manager/tests/test_node_nic_handlers_w_bonding.py @@ -15,11 +15,14 @@ # under the License. import mock +import uuid + from oslo_serialization import jsonutils from nailgun.consts import BOND_MODES from nailgun.consts import BOND_TYPES from nailgun.consts import BOND_XMIT_HASH_POLICY +from nailgun.consts import CLUSTER_MODES from nailgun.consts import HYPERVISORS from nailgun.consts import NETWORK_INTERFACE_TYPES from nailgun.db import db @@ -125,7 +128,7 @@ class TestNodeNICsBonding(BaseIntegrationTest): self.admin_nic = nic elif net_names: self.other_nic = nic - elif nic['interface_properties']['sriov']['available']: + elif nic['meta']['sriov']['available']: self.sriov_nic = nic else: self.empty_nic = nic @@ -169,7 +172,7 @@ class TestNodeNICsBonding(BaseIntegrationTest): "lacp_rate": "slow", "type__": bond_type }, - "interface_properties": iface_props, + "attributes": iface_props, "slaves": [ {"name": self.other_nic["name"]}, {"name": self.empty_nic["name"]}], @@ -257,9 +260,10 @@ class TestNodeNICsBonding(BaseIntegrationTest): def test_nics_lnx_bond_create_failed_with_dpdk(self, m_dpdk_available): m_dpdk_available.return_value = True bond_name = 'bond0' - self.prepare_bond_w_props(bond_name=bond_name, - bond_type=BOND_TYPES.linux, - iface_props={'dpdk': {'enabled': True}}) + self.prepare_bond_w_props( + bond_name=bond_name, + bond_type=BOND_TYPES.linux, + iface_props={'dpdk': {'enabled': {'value': True}}}) self.node_nics_put_check_error("Bond interface '{0}': DPDK can be" " enabled only for 'dpdkovs' bond type". format(bond_name)) @@ -294,7 +298,7 @@ class TestNodeNICsBonding(BaseIntegrationTest): self.data = self.env.node_nics_get(node.id).json_body bond = [iface for iface in self.data if iface['type'] == NETWORK_INTERFACE_TYPES.bond][0] - bond['interface_properties'] = {'dpdk': {'enabled': True}} + bond['attributes'] = {'dpdk': {'enabled': {'value': True}}} self.node_nics_put_check_error( "Bond interface '{0}': DPDK can be enabled only for 'dpdkovs' bond" " type".format(bond_name)) @@ -766,8 +770,8 @@ class TestNodeNICsBonding(BaseIntegrationTest): {"name": self.sriov_nic["name"]}], "assigned_networks": self.sriov_nic["assigned_networks"] }) - self.sriov_nic['interface_properties']['sriov']['enabled'] = True - self.sriov_nic['interface_properties']['sriov']['sriov_numvfs'] = 2 + self.sriov_nic['attributes']['sriov']['enabled']['value'] = True + self.sriov_nic['attributes']['sriov']['numvfs']['value'] = 2 cluster_db = self.env.clusters[-1] cluster_attrs = objects.Cluster.get_editable_attributes(cluster_db) cluster_attrs['common']['libvirt_type']['value'] = HYPERVISORS.kvm @@ -860,3 +864,89 @@ class TestNodeNICsBonding(BaseIntegrationTest): "type: '{3}'".format( self.env.nodes[0]["id"], BOND_MODES.balance_rr, BOND_TYPES.ovs, allowed_modes)) + + +class TestBondAttributesDefaultsHandler(BaseIntegrationTest): + + EXPECTED_ATTRIBUTES = { + 'mode': { + 'value': { + 'weight': 10, + 'type': 'select', + 'value': 'balance-rr', + 'label': 'Mode', + 'values': [ + {'data': 'balance-rr', 'label': 'balance-rr'} + ] + }, + 'metadata': { + 'weight': 10, + 'label': 'Mode' + } + }, + 'offloading': { + 'metadata': { + 'weight': 20, + 'label': 'Offloading' + }, + 'disable': { + 'weight': 10, + 'type': 'checkbox', + 'value': False, + 'label': 'Disable offloading' + }, + 'modes': { + 'weight': 20, + 'type': 'offloading_modes', + 'value': {}, + 'label': 'Offloading modes', + 'description': 'Offloading modes' + } + }, + 'mtu': { + 'metadata': { + 'weight': 30, + 'label': 'MTU' + }, + 'value': { + 'weight': 10, + 'type': 'number', + 'value': None, + 'label': 'MTU' + } + }, + 'plugin_a_with_bond_attributes': { + 'plugin_name_text': { + 'weight': 25, + 'type': 'text', + 'value': 'value', + 'label': 'label', + 'description': 'Some description' + } + } + } + + def setUp(self): + super(TestBondAttributesDefaultsHandler, self).setUp() + self.cluster = self.env.create( + release_kwargs={ + 'name': uuid.uuid4().get_hex(), + 'version': 'newton-10.0', + 'operating_system': 'Ubuntu', + 'modes': [CLUSTER_MODES.ha_compact]}, + nodes_kwargs=[{'roles': ['controller']}]) + self.node = self.env.nodes[0] + self.env.create_plugin( + name='plugin_a_with_bond_attributes', + package_version='5.0.0', + cluster=self.cluster, + bond_attributes_metadata=self.env.get_default_plugin_bond_config()) + + def test_get_bond_default_attributes(self): + resp = self.app.get( + reverse( + "NodeBondAttributesDefaultsHandler", + kwargs={"node_id": self.node.id}), + headers=self.default_headers) + self.assertEqual(resp.status_code, 200) + self.assertDictEqual(self.EXPECTED_ATTRIBUTES, resp.json_body) diff --git a/nailgun/nailgun/extensions/network_manager/tests/test_verify_node_interface_update.py b/nailgun/nailgun/extensions/network_manager/tests/test_verify_node_interface_update.py index 2237563239..c6155eb2ab 100644 --- a/nailgun/nailgun/extensions/network_manager/tests/test_verify_node_interface_update.py +++ b/nailgun/nailgun/extensions/network_manager/tests/test_verify_node_interface_update.py @@ -91,16 +91,31 @@ class TestDPDKValidation(BaseNetAssignmentValidatorTest): objects.NIC.assign_networks(nic_3, []) dpdk_settings = { - 'dpdk': {'enabled': True, - 'available': True}, - 'pci_id': 'test_id:2', + 'attributes': { + 'dpdk': { + 'enabled': {'value': True}}, + }, + 'meta': { + 'dpdk': { + 'available': True, + }, + 'pci_id': "test_id:2" + } } - objects.NIC.update(nic_2, {'interface_properties': utils.dict_merge( - nic_2.interface_properties, dpdk_settings)}) + objects.NIC.update(nic_2, { + 'attributes': utils.dict_merge( + nic_2.attributes, dpdk_settings['attributes']), + 'meta': utils.dict_merge( + nic_2.meta, dpdk_settings['meta']) + }) - dpdk_settings['dpdk']['enabled'] = False - objects.NIC.update(nic_3, {'interface_properties': utils.dict_merge( - nic_3.interface_properties, dpdk_settings)}) + dpdk_settings['attributes']['dpdk']['enabled']['value'] = False + objects.NIC.update(nic_3, { + 'attributes': utils.dict_merge( + nic_3.attributes, dpdk_settings['attributes']), + 'meta': utils.dict_merge( + nic_3.meta, dpdk_settings['meta']) + }) self.cluster_attrs = objects.Cluster.get_editable_attributes( self.cluster_db) @@ -138,17 +153,17 @@ class TestDPDKValidation(BaseNetAssignmentValidatorTest): def test_change_pci_id(self): iface = self.data['interfaces'][1] - iface['interface_properties']['pci_id'] = '123:345' + iface['meta']['pci_id'] = '123:345' self.check_fail("PCI-ID .* can't be changed manually") def test_wrong_mtu(self): iface = self.data['interfaces'][1] - iface['interface_properties']['mtu'] = 1501 + iface['attributes']['mtu']['value']['value'] = 1501 self.check_fail("MTU size must be less than 1500 bytes") def test_non_default_mtu(self): iface = self.data['interfaces'][1] - iface['interface_properties']['mtu'] = 1500 + iface['attributes']['mtu']['value']['value'] = 1500 self.assertNotRaises(errors.InvalidData, self.validator, self.data) @@ -158,18 +173,56 @@ class TestDPDKValidation(BaseNetAssignmentValidatorTest): nets = nic_2['assigned_networks'] nic_2['assigned_networks'] = [] - nic_3['interface_properties']['dpdk']['enabled'] = True + nic_3['attributes']['dpdk']['enabled']['value'] = True bond_data = { 'type': "bond", 'name': "ovs-bond0", 'mode': "active-backup", 'assigned_networks': nets, - 'interface_properties': { - 'mtu': None, - 'disable_offloading': True, + 'attributes': { + 'offloading': { + 'disable': { + 'value': True, + 'label': 'Disable offloading', + 'type': 'checkbox', + 'weight': 10 + }, + 'metadata': { + 'label': 'Offloading', + 'weight': 10 + }, + 'offloading_modes': { + 'description': 'Offloading modes', + 'value': {}, + 'label': 'Offloading modes', + 'type': 'offloading_modes', + 'weight': 20 + } + }, + 'mtu': { + 'value': { + 'value': None, + 'label': 'MTU', + 'type': 'number', + 'weight': 10 + }, + 'metadata': { + 'label': 'MTU', + 'weight': 20 + } + }, 'dpdk': { - 'enabled': True, + 'enabled': { + 'value': True, + 'label': 'DPDK enabled', + 'type': 'checkbox', + 'weight': 10 + }, + 'metadata': { + 'label': 'DPDK', + 'weight': 40 + } } }, 'bond_properties': { @@ -194,9 +247,12 @@ class TestDPDKValidation(BaseNetAssignmentValidatorTest): self._create_bond_data() nic_3 = self.node.nic_interfaces[2] - dpdk_settings = {'dpdk': {'available': False}, 'pci_id': ''} - objects.NIC.update(nic_3, {'interface_properties': utils.dict_merge( - nic_3.interface_properties, dpdk_settings)}) + meta = { + 'dpdk': {'available': False}, + 'pci_id': '' + } + objects.NIC.update(nic_3, { + 'meta': utils.dict_merge(nic_3.meta, meta)}) self.assertRaisesRegexp( errors.InvalidData, diff --git a/nailgun/nailgun/extensions/network_manager/validators/network.py b/nailgun/nailgun/extensions/network_manager/validators/network.py index edcf62f172..b12b2fd49f 100644 --- a/nailgun/nailgun/extensions/network_manager/validators/network.py +++ b/nailgun/nailgun/extensions/network_manager/validators/network.py @@ -538,13 +538,14 @@ class NetAssignmentValidator(BasicValidator): @classmethod def _verify_sriov_properties(cls, iface, data, db_node): - non_changeable = ['sriov_totalvfs', 'available', 'pci_id'] - sriov_data = data['interface_properties']['sriov'] - sriov_db = iface.interface_properties['sriov'] + non_changeable = ['totalvfs', 'available', 'pci_id'] + sriov_data = data['attributes']['sriov'] + sriov_db = iface.attributes['sriov'] + sriov_meta = iface.meta['sriov'] sriov_new = sriov_db.copy() sriov_new.update(sriov_data) - if sriov_new['enabled']: + if sriov_new['enabled']['value']: # check hypervisor type h_type = objects.Cluster.get_editable_attributes( db_node.cluster)['common']['libvirt_type']['value'] @@ -552,36 +553,39 @@ class NetAssignmentValidator(BasicValidator): raise errors.InvalidData( 'Only KVM hypervisor works with SR-IOV.') - for param_name in non_changeable: - if sriov_db[param_name] != sriov_new[param_name]: - raise errors.InvalidData( - "Node '{0}' interface '{1}': SR-IOV parameter '{2}' cannot" - " be changed through API".format( - db_node.id, iface.name, param_name), - log_message=True - ) - if not sriov_db['available'] and sriov_new['enabled']: + if 'meta' in data: + sriov_meta_data = data['meta']['sriov'] + for param_name in non_changeable: + if sriov_meta[param_name] != sriov_meta_data[param_name]: + raise errors.InvalidData( + "Node '{0}' interface '{1}': SR-IOV parameter '{2}' " + "cannot be changed through API".format( + db_node.id, iface.name, param_name), + log_message=True + ) + + if not sriov_meta['available'] and sriov_new['enabled']['value']: raise errors.InvalidData( "Node '{0}' interface '{1}': SR-IOV cannot be enabled as it is" " not available".format(db_node.id, iface.name), log_message=True ) - if not sriov_new['sriov_numvfs'] and sriov_new['enabled']: + if not sriov_new['numvfs']['value'] and sriov_new['enabled']['value']: raise errors.InvalidData( "Node '{0}' interface '{1}': virtual functions can not be" " enabled for interface when 'sriov_numfs' option is not" " specified!".format(db_node.id, iface.name), log_message=True ) - if sriov_db['sriov_totalvfs'] < sriov_new['sriov_numvfs']: + if sriov_meta['totalvfs'] < sriov_new['numvfs']['value']: raise errors.InvalidData( - "Node '{0}' interface '{1}': '{2}' virtual functions was " - "requested but just '{3}' are available".format( - db_node.id, iface.name, sriov_new['sriov_numvfs'], - sriov_db['sriov_totalvfs']), + "Node '{0}' interface '{1}': '{2}' virtual functions was" + " requested but just '{3}' are available".format( + db_node.id, iface.name, sriov_new['numvfs']['value'], + sriov_meta['totalvfs']), log_message=True ) - if (sriov_new['enabled'] and + if (sriov_new['enabled']['value'] and data.get('assigned_networks', iface.assigned_networks)): raise errors.InvalidData( "Node '{0}' interface '{1}': SR-IOV cannot be enabled when " @@ -656,10 +660,7 @@ class NetAssignmentValidator(BasicValidator): hw_available &= objects.NIC.dpdk_available( slave_iface, dpdk_drivers) - interface_properties = iface.get('interface_properties', {}) - enabled = interface_properties.get('dpdk', {}).get( - 'enabled', False) - + attributes = iface.get('attributes', {}) bond_type = iface.get('bond_properties', {}).get('type__') else: if iface['type'] == consts.NETWORK_INTERFACE_TYPES.ether: @@ -669,13 +670,13 @@ class NetAssignmentValidator(BasicValidator): bond_type = iface.get('bond_properties', {}).get( 'type__', db_iface.bond_properties.get('type__')) hw_available = iface_cls.dpdk_available(db_iface, dpdk_drivers) - - interface_properties = utils.dict_merge( - db_iface.interface_properties, - iface.get('interface_properties', {}) + attributes = utils.dict_merge( + db_iface.attributes, + iface.get('attributes', {}) ) - enabled = interface_properties.get('dpdk', {}).get( - 'enabled', False) + + enabled = attributes.get('dpdk', {}).get('enabled', {}).get( + 'value', False) # check basic parameters if not hw_available and enabled: @@ -696,9 +697,10 @@ class NetAssignmentValidator(BasicValidator): log_message=True ) - if db_iface is not None: - pci_id = interface_properties.get('pci_id') - db_pci_id = db_iface.interface_properties.get('pci_id') + if (db_iface is not None and iface['type'] != + consts.NETWORK_INTERFACE_TYPES.bond): + pci_id = iface.get('meta', {}).get('pci_id', '') + db_pci_id = db_iface.meta.get('pci_id', '') if pci_id != db_pci_id: raise errors.InvalidData( @@ -718,7 +720,7 @@ class NetAssignmentValidator(BasicValidator): # check mtu <= 1500 # github.com/openvswitch/ovs/blob/master/INSTALL.DPDK.md#restrictions - mtu = interface_properties.get('mtu') + mtu = attributes.get('mtu', {}).get('value', {}).get('value') if enabled and mtu is not None and mtu > 1500: raise errors.InvalidData( "For interface '{}' with enabled DPDK MTU" @@ -794,7 +796,7 @@ class NetAssignmentValidator(BasicValidator): iface['name']), log_message=True ) - if iface.get('interface_properties', {}).get('sriov'): + if iface.get('attributes', {}).get('sriov'): cls._verify_sriov_properties(db_iface, iface, db_node) elif iface['type'] == consts.NETWORK_INTERFACE_TYPES.bond: @@ -824,9 +826,10 @@ class NetAssignmentValidator(BasicValidator): ) cur_iface = interfaces_by_name.get(slave['name'], {}) iface_props = utils.dict_merge( - db_slave.interface_properties, - cur_iface.get('interface_properties', {})) - if iface_props.get('sriov', {}).get('enabled'): + db_slave.attributes, + cur_iface.get('attributes', {})) + if iface_props.get('sriov', {}).get('enabled').get( + 'value'): raise errors.InvalidData( "Node '{0}': bond '{1}' cannot contain SRIOV " "enabled interface '{2}'".format( diff --git a/nailgun/nailgun/fixtures/openstack.yaml b/nailgun/nailgun/fixtures/openstack.yaml index d0733b4259..0e6c48cec4 100644 --- a/nailgun/nailgun/fixtures/openstack.yaml +++ b/nailgun/nailgun/fixtures/openstack.yaml @@ -435,8 +435,8 @@ bonding: availability: - dpdkovs: "'experimental' in version:feature_groups and interface:pxe == false and - interface:interface_properties.dpdk.enabled and not interface:interface_properties.sriov.enabled" - - linux: "not interface:interface_properties.sriov.enabled" + interface:attributes.dpdk.enabled.value and not interface:attributes.sriov.enabled.value" + - linux: "not interface:attributes.sriov.enabled.value" properties: linux: mode: @@ -1151,7 +1151,6 @@ group: "security" weight: 20 type: "radio" - public_network_assignment: metadata: weight: 10 @@ -2104,6 +2103,98 @@ - hypervisor - network - storage + nic_attributes: + offloading: + metadata: + label: "Offloading" + weight: 10 + disable: + label: "Disable offloading" + weight: 10 + type: "checkbox" + value: False + modes: + label: "Offloading modes" + weight: 20 + description: "Offloading modes" + type: "offloading_modes" + value: {} + mtu: + metadata: + label: "MTU" + weight: 20 + value: + label: "MTU" + weight: 10 + type: "number" + value: null + sriov: + metadata: + label: "SR-IOV" + weight: 30 + enabled: + label: "SR-IOV enabled" + weight: 10 + type: "checkbox" + value: False + numvfs: + label: "Virtual functions" + weight: 20 + type: "number" + min: 0 + value: null + physnet: + label: "Physical network" + weight: 30 + type: "text" + value: '' + dpdk: + metadata: + label: "DPDK" + weight: 40 + enabled: + label: "DPDK enabled" + weight: 10 + type: "checkbox" + value: False + bond_attributes: + mode: + metadata: + label: "Mode" + weight: 10 + value: + label: "Mode" + weight: 10 + type: "select" + values: + - + label: balance-rr + data: balance-rr + value: balance-rr + offloading: + metadata: + label: "Offloading" + weight: 20 + disable: + label: "Disable offloading" + weight: 10 + type: "checkbox" + value: False + modes: + label: "Offloading modes" + weight: 20 + description: "Offloading modes" + type: "offloading_modes" + value: {} + mtu: + metadata: + label: "MTU" + weight: 30 + value: + label: "MTU" + weight: 10 + type: "number" + value: null modes: ['ha_compact'] extensions: ['volume_manager', 'network_manager'] - pk: 1 diff --git a/nailgun/nailgun/objects/__init__.py b/nailgun/nailgun/objects/__init__.py index 6d6e113dca..69d6f80021 100644 --- a/nailgun/nailgun/objects/__init__.py +++ b/nailgun/nailgun/objects/__init__.py @@ -77,10 +77,12 @@ from nailgun.objects.master_node_settings import MasterNodeSettings from nailgun.objects.node_group import NodeGroup from nailgun.objects.node_group import NodeGroupCollection +from nailgun.objects.plugin import ClusterPlugin +from nailgun.objects.plugin import NodeBondInterfaceClusterPlugin +from nailgun.objects.plugin import NodeClusterPlugin +from nailgun.objects.plugin import NodeNICInterfaceClusterPlugin from nailgun.objects.plugin import Plugin from nailgun.objects.plugin import PluginCollection -from nailgun.objects.plugin import ClusterPlugin -from nailgun.objects.plugin import NodeClusterPlugin from nailgun.objects.plugin_link import PluginLink from nailgun.objects.plugin_link import PluginLinkCollection diff --git a/nailgun/nailgun/objects/cluster.py b/nailgun/nailgun/objects/cluster.py index 458ec7bb6c..ff95cf2742 100644 --- a/nailgun/nailgun/objects/cluster.py +++ b/nailgun/nailgun/objects/cluster.py @@ -696,6 +696,7 @@ class Cluster(NailgunObject): objects.Node.assign_group(node) net_manager.assign_networks_by_default(node) objects.Node.set_default_attributes(node) + objects.Node.create_nic_attributes(node) objects.Node.refresh_dpdk_properties(node) cls.update_nodes_network_template(instance, nodes_to_add) db().flush() diff --git a/nailgun/nailgun/objects/node.py b/nailgun/nailgun/objects/node.py index 3e3d67b5b4..87ca04f8f2 100644 --- a/nailgun/nailgun/objects/node.py +++ b/nailgun/nailgun/objects/node.py @@ -528,7 +528,6 @@ class Node(NailgunObject): try: network_manager = Cluster.get_network_manager(instance.cluster) network_manager.update_interfaces_info(instance) - db().refresh(instance) except errors.InvalidInterfacesInfo as exc: logger.warning( @@ -964,6 +963,7 @@ class Node(NailgunObject): cls.add_pending_change(instance, consts.CLUSTER_CHANGES.interfaces) cls.set_network_template(instance) cls.set_default_attributes(instance) + cls.create_nic_attributes(instance) @classmethod def set_network_template(cls, instance): @@ -1220,34 +1220,6 @@ class Node(NailgunObject): vm['created'] = False node.vms_conf.changed() - @classmethod - def set_default_attributes(cls, instance): - if not instance.cluster_id: - logger.warning( - u"Attempting to update attributes of node " - u"'{0}' which isn't added to any cluster".format( - instance.full_name)) - return - - instance.attributes = copy.deepcopy( - instance.cluster.release.node_attributes) - NodeAttributes.set_default_hugepages(instance) - PluginManager.add_plugin_attributes_for_node(instance) - - @classmethod - def dpdk_enabled(cls, instance): - network_manager = Cluster.get_network_manager(instance.cluster) - if not hasattr(network_manager, 'dpdk_enabled_for_node'): - return False - return network_manager.dpdk_enabled_for_node(instance) - - @classmethod - def sriov_enabled(cls, instance): - for iface in instance.nic_interfaces: - if NIC.is_sriov_enabled(iface): - return True - return False - @classmethod def get_attributes(cls, instance): attributes = copy.deepcopy(instance.attributes) @@ -1281,6 +1253,34 @@ class Node(NailgunObject): return attributes + @classmethod + def set_default_attributes(cls, instance): + if not instance.cluster_id: + logger.warning( + u"Attempting to get default attributes of node " + u"'{0}' which isn't added to any cluster".format( + instance.full_name)) + return + + instance.attributes = copy.deepcopy( + instance.cluster.release.node_attributes) + NodeAttributes.set_default_hugepages(instance) + PluginManager.add_plugin_attributes_for_node(instance) + + @classmethod + def dpdk_enabled(cls, instance): + network_manager = Cluster.get_network_manager(instance.cluster) + if not hasattr(network_manager, 'dpdk_enabled_for_node'): + return False + return network_manager.dpdk_enabled_for_node(instance) + + @classmethod + def sriov_enabled(cls, instance): + for iface in instance.nic_interfaces: + if NIC.is_sriov_enabled(iface): + return True + return False + @classmethod def refresh_dpdk_properties(cls, instance): if not instance.cluster: @@ -1319,6 +1319,29 @@ class Node(NailgunObject): nm = Cluster.get_network_manager(instance.cluster) return nm.dpdk_nics(instance) + @classmethod + def get_bond_default_attributes(cls, instance): + if not instance.cluster_id: + logger.warning( + u"Attempting to get bond default attributes of node " + u"'{0}' which isn't added to any cluster".format( + instance.full_name)) + return + + return Bond.get_bond_default_attributes(instance.cluster) + + @classmethod + def create_nic_attributes(cls, instance): + if not instance.cluster_id: + logger.warning( + u"Attempting to create NIC attributes of node " + u"'{0}' which isn't added to any cluster".format( + instance.full_name)) + return + + for nic_interface in instance.nic_interfaces: + NIC.create_attributes(nic_interface) + class NodeCollection(NailgunCollection): """Node collection""" @@ -1434,7 +1457,7 @@ class NodeAttributes(object): for nic in dpdk_nics: # NIC may have numa_node equal to null, in that case # we assume that it belongs to first NUMA - nics_numas.append(nic.interface_properties.get('numa_node') or 0) + nics_numas.append(nic.meta.get('numa_node') or 0) return cpu_distribution.distribute_node_cpus( numa_nodes, components, nics_numas) diff --git a/nailgun/nailgun/objects/plugin.py b/nailgun/nailgun/objects/plugin.py index d94499f824..57b335cf2a 100644 --- a/nailgun/nailgun/objects/plugin.py +++ b/nailgun/nailgun/objects/plugin.py @@ -259,7 +259,14 @@ class ClusterPlugin(NailgunObject): 'enabled': False, 'attributes': plugin_attributes }) - NodeClusterPlugin.add_nodes_for_cluster_plugin(cluster_plugin) + # Set default attributes for Nodes, NICs and Bonds during + # plugin installation + NodeClusterPlugin.add_nodes_for_cluster_plugin( + cluster_plugin) + NodeNICInterfaceClusterPlugin.add_node_nics_for_cluster_plugin( + cluster_plugin) + NodeBondInterfaceClusterPlugin.add_node_bonds_for_cluster_plugin( + cluster_plugin) db().flush() @@ -477,3 +484,186 @@ class NodeClusterPlugin(BasicNodeClusterPlugin): }) db().flush() + + +class NodeNICInterfaceClusterPlugin(BasicNodeClusterPlugin): + + model = models.NodeNICInterfaceClusterPlugin + + @classmethod + def get_all_enabled_attributes_by_interface(cls, interface): + """Returns plugin enabled attributes for specific NIC. + + :param interface: Interface instance + :type interface: models.node.NodeNICInterface + :returns: dict -- Dict object with plugin NIC attributes + """ + nic_attributes = {} + nic_plugin_attributes_query = db().query( + cls.model.id, + models.Plugin.name, + models.Plugin.title, + cls.model.attributes + ).join( + models.ClusterPlugin, + ).filter( + cls.model.interface_id == interface.id + ).filter( + models.ClusterPlugin.enabled.is_(True)).all() + + for nic_plugin_id, name, title, attributes \ + in nic_plugin_attributes_query: + nic_attributes[name] = { + 'metadata': { + 'label': title, + 'nic_plugin_id': nic_plugin_id, + 'class': 'plugin' + } + } + # TODO(apopovych): resolve conflicts of same attribute names + # for different plugins + nic_attributes[name].update(attributes) + + return nic_attributes + + @classmethod + def add_node_nics_for_cluster_plugin(cls, cluster_plugin): + """Populates 'node_nic_interface_cluster_plugins' table. + + :param cluster_plugin: ClusterPlugin instance + :type cluster_plugin: models.cluster.ClusterPlugin + :returns: None + """ + nic_attributes = dict( + cluster_plugin.plugin.nic_attributes_metadata) + for node in cluster_plugin.cluster.nodes: + for interface in node.nic_interfaces: + if nic_attributes: + cls.create({ + 'cluster_plugin_id': cluster_plugin.id, + 'interface_id': interface.id, + 'node_id': node.id, + 'attributes': nic_attributes + }) + + db().flush() + + @classmethod + def add_cluster_plugins_for_node_nic(cls, interface): + """Populates 'node_nic_interface_cluster_plugins' table. + + :param interface: Interface instance + :type interface: models.node.NodeNICInterface + :returns: None + """ + node = interface.node + # remove old relations for interfaces + nic_cluster_plugin_ids = set( + item.id for item in node.node_nic_interface_cluster_plugins + if item.interface_id == interface.id) + cls.bulk_delete(nic_cluster_plugin_ids) + + for cluster_plugin in node.cluster.cluster_plugins: + nic_attributes = dict( + cluster_plugin.plugin.nic_attributes_metadata) + if nic_attributes: + cls.create({ + 'cluster_plugin_id': cluster_plugin.id, + 'interface_id': interface.id, + 'node_id': node.id, + 'attributes': nic_attributes + }) + + db().flush() + + +class NodeBondInterfaceClusterPlugin(BasicNodeClusterPlugin): + + model = models.NodeBondInterfaceClusterPlugin + + @classmethod + def get_all_enabled_attributes_by_bond(cls, bond): + """Returns plugin enabled attributes for specific Bond. + + :param interface: Bond instance + :type interface: models.node.NodeBondInterface + :returns: dict -- Dict object with plugin Bond attributes + """ + bond_attributes = {} + bond_plugin_attributes_query = db().query( + cls.model.id, + models.Plugin.name, + models.Plugin.title, + cls.model.attributes + ).join( + models.ClusterPlugin, + models.Plugin + ).filter( + cls.model.bond_id == bond.id + ).filter( + models.ClusterPlugin.enabled.is_(True)) + + for bond_plugin_id, name, title, attributes \ + in bond_plugin_attributes_query: + bond_attributes[name] = { + 'metadata': { + 'label': title, + 'bond_plugin_id': bond_plugin_id, + 'class': 'plugin' + } + } + # TODO(apopovych): resolve conflicts of same attribute names + # for different plugins + bond_attributes[name].update(attributes) + + return bond_attributes + + @classmethod + def add_node_bonds_for_cluster_plugin(cls, cluster_plugin): + """Populates 'node_bond_interface_cluster_plugins' table with bonds. + + :param cluster_plugin: ClusterPlugin instance + :type cluster_plugin: models.cluster.ClusterPlugin + :returns: None + """ + bond_attributes = dict( + cluster_plugin.plugin.bond_attributes_metadata) + for node in cluster_plugin.cluster.nodes: + for bond in node.bond_interfaces: + if bond_attributes: + cls.create({ + 'cluster_plugin_id': cluster_plugin.id, + 'bond_id': bond.id, + 'node_id': node.id, + 'attributes': bond_attributes + }) + + db().flush() + + @classmethod + def add_cluster_plugins_for_node_bond(cls, bond): + """Populates 'node_bond_interface_cluster_plugins' table. + + :param interface: Bond instance + :type interface: models.node.NodeBondInterface + :returns: None + """ + node = bond.node + # remove old relations for bonds + bond_cluster_plugin_ids = set( + item.id for item in node.node_bond_interface_cluster_plugins + if item.bond_id == bond.id) + cls.bulk_delete(bond_cluster_plugin_ids) + + for cluster_plugin in node.cluster.cluster_plugins: + bond_attributes = dict( + cluster_plugin.plugin.bond_attributes_metadata) + if bond_attributes: + cls.create({ + 'cluster_plugin_id': cluster_plugin.id, + 'bond_id': bond.id, + 'node_id': node.id, + 'attributes': bond_attributes + }) + + db().flush() diff --git a/nailgun/nailgun/plugins/adapters.py b/nailgun/nailgun/plugins/adapters.py index a35fe22dde..b083aeaf88 100644 --- a/nailgun/nailgun/plugins/adapters.py +++ b/nailgun/nailgun/plugins/adapters.py @@ -186,7 +186,7 @@ class PluginAdapterBase(object): @property def nic_attributes_metadata(self): - return self.plugin.bond_attributes_metadata + return self.plugin.nic_attributes_metadata @property def node_attributes_metadata(self): diff --git a/nailgun/nailgun/plugins/manager.py b/nailgun/nailgun/plugins/manager.py index ad8b908706..74f6c2ac28 100644 --- a/nailgun/nailgun/plugins/manager.py +++ b/nailgun/nailgun/plugins/manager.py @@ -28,7 +28,9 @@ from nailgun import consts from nailgun import errors from nailgun.logger import logger from nailgun.objects.plugin import ClusterPlugin +from nailgun.objects.plugin import NodeBondInterfaceClusterPlugin from nailgun.objects.plugin import NodeClusterPlugin +from nailgun.objects.plugin import NodeNICInterfaceClusterPlugin from nailgun.objects.plugin import Plugin from nailgun.objects.plugin import PluginCollection from nailgun.settings import settings @@ -499,6 +501,115 @@ class PluginManager(object): NodeClusterPlugin.add_cluster_plugins_for_node(node) # ENTRY POINT + @classmethod + def get_bond_default_attributes(cls, cluster): + """Get plugin bond attributes metadata for cluster. + + :param cluster: A cluster instance + :type cluster: Cluster model + :returns: dict -- Object with bond attributes + """ + plugins_bond_metadata = {} + enabled_plugins = ClusterPlugin.get_enabled(cluster.id) + for plugin_adapter in six.moves.map(wrap_plugin, enabled_plugins): + metadata = plugin_adapter.bond_attributes_metadata + if metadata: + plugins_bond_metadata[plugin_adapter.name] = metadata + + return plugins_bond_metadata + + @classmethod + def get_bond_attributes(cls, bond): + """Return plugin related attributes for Bond. + + :param interface: A BOND instance + :type interface: Bond model + """ + return NodeBondInterfaceClusterPlugin.\ + get_all_enabled_attributes_by_bond(bond) + + @classmethod + def add_plugin_attributes_for_bond(cls, bond): + """Add plugin related attributes for Bond. + + :param interface: A BOND instance + :type interface: Bond model + :returns: object -- Bond model instance + """ + NodeBondInterfaceClusterPlugin.\ + add_cluster_plugins_for_node_bond(bond) + + return bond + + @classmethod + def update_bond_attributes(cls, attributes): + plugins = [] + for k in list(attributes): + if cls.is_plugin_data(attributes[k]): + plugins.append(attributes.pop(k)) + + for plugin in plugins: + metadata = plugin.pop('metadata') + NodeNICInterfaceClusterPlugin.\ + set_attributes( + metadata['bond_plugin_id'], + plugin + ) + + @classmethod + def get_nic_default_attributes(cls, cluster): + """Get default plugin nic attributes for cluster. + + :param cluster: A cluster instance + :type cluster: Cluster model + :returns: dict -- Object with nic attributes + """ + plugins_nic_metadata = {} + enabled_plugins = ClusterPlugin.get_enabled(cluster.id) + for plugin_adapter in six.moves.map(wrap_plugin, enabled_plugins): + metadata = plugin_adapter.nic_attributes_metadata + if metadata: + plugins_nic_metadata[plugin_adapter.name] = metadata + + return plugins_nic_metadata + + @classmethod + def get_nic_attributes(cls, interface): + """Return plugin related attributes for NIC. + + :param interface: A NIC instance + :type interface: Interface model + :returns: + """ + return NodeNICInterfaceClusterPlugin.\ + get_all_enabled_attributes_by_interface(interface) + + @classmethod + def update_nic_attributes(cls, attributes): + plugins = [] + for k in list(attributes): + if cls.is_plugin_data(attributes[k]): + plugins.append(attributes.pop(k)) + + for plugin in plugins: + metadata = plugin.pop('metadata') + NodeNICInterfaceClusterPlugin.\ + set_attributes( + metadata['nic_plugin_id'], + plugin + ) + + @classmethod + def add_plugin_attributes_for_interface(cls, interface): + """Add plugin related attributes for NIC. + + :param interface: A NIC instance + :type interface: Interface model + :returns: None + """ + NodeNICInterfaceClusterPlugin.\ + add_cluster_plugins_for_node_nic(interface) + @classmethod def sync_plugins_metadata(cls, plugin_ids=None): """Sync or install metadata for plugins by given IDs. diff --git a/nailgun/nailgun/test/base.py b/nailgun/nailgun/test/base.py index 24fbf5249d..b6d16fd321 100644 --- a/nailgun/nailgun/test/base.py +++ b/nailgun/nailgun/test/base.py @@ -630,9 +630,9 @@ class EnvironmentManager(object): deployment_tasks = plugin_data.pop('deployment_tasks', None) tasks = plugin_data.pop('tasks', None) components = plugin_data.pop('components', None) - nic_config = plugin_data.pop('nic_config', None) - bond_config = plugin_data.pop('bond_config', None) - node_config = plugin_data.pop('node_config', None) + nic_config = plugin_data.pop('nic_attributes_metadata', None) + bond_config = plugin_data.pop('bond_attributes_metadata', None) + node_config = plugin_data.pop('node_attributes_metadata', None) mocked_metadata = { 'metadata.*': plugin_data, @@ -1028,7 +1028,11 @@ class EnvironmentManager(object): 'deployment_scripts_path': 'deployment_scripts/'}, {'repository_path': 'repositories/ubuntu', 'version': 'mitaka-9.0', 'os': 'ubuntu', - 'mode': ['ha', 'multinode'], + 'mode': ['ha'], + 'deployment_scripts_path': 'deployment_scripts/'}, + {'repository_path': 'repositories/ubuntu', + 'version': 'newton-10.0', 'os': 'ubuntu', + 'mode': ['ha'], 'deployment_scripts_path': 'deployment_scripts/'}, {'repository_path': 'repositories/ubuntu', 'version': 'newton-10.0', 'os': 'ubuntu', @@ -1323,7 +1327,8 @@ class EnvironmentManager(object): ) def make_bond_via_api(self, bond_name, bond_mode, nic_names, node_id=None, - bond_properties=None, interface_properties=None): + bond_properties=None, interface_properties=None, + attrs=None): if not node_id: node_id = self.nodes[0]["id"] resp = self.app.get( @@ -1351,7 +1356,8 @@ class EnvironmentManager(object): "type": NETWORK_INTERFACE_TYPES.bond, "mode": bond_mode, "slaves": slaves, - "assigned_networks": assigned_nets + "assigned_networks": assigned_nets, + "attributes": attrs or {} } if bond_properties: bond_dict["bond_properties"] = bond_properties diff --git a/nailgun/nailgun/test/integration/test_orchestrator_serializer.py b/nailgun/nailgun/test/integration/test_orchestrator_serializer.py index f399602be7..2a804e24e0 100644 --- a/nailgun/nailgun/test/integration/test_orchestrator_serializer.py +++ b/nailgun/nailgun/test/integration/test_orchestrator_serializer.py @@ -623,9 +623,9 @@ class TestNovaNetworkOrchestratorSerializer61(OrchestratorSerializerTestBase): self.move_network(node.id, 'management', 'eth0', 'eth1') self.env.make_bond_via_api( 'lnx_bond', '', ['eth1', 'eth2'], node.id, - bond_properties={'mode': consts.BOND_MODES.balance_rr, - 'type__': consts.BOND_TYPES.linux} - ) + bond_properties={ + 'mode': consts.BOND_MODES.balance_rr, + 'type__': consts.BOND_TYPES.linux}) serializer = self.create_serializer(cluster) facts = serializer.serialize(cluster, cluster.nodes)['nodes'] for node in facts: @@ -671,9 +671,9 @@ class TestNovaNetworkOrchestratorSerializer61(OrchestratorSerializerTestBase): self.move_network(node.id, 'fixed', 'eth0', 'eth1') self.env.make_bond_via_api( 'lnx_bond', '', ['eth1', 'eth2'], node.id, - bond_properties={'mode': consts.BOND_MODES.balance_rr, - 'type__': consts.BOND_TYPES.linux} - ) + bond_properties={ + 'mode': consts.BOND_MODES.balance_rr, + 'type__': consts.BOND_TYPES.linux}) serializer = self.create_serializer(cluster) facts = serializer.serialize(cluster, cluster.nodes)['nodes'] for node in facts: @@ -757,13 +757,10 @@ class TestNeutronOrchestratorSerializer61(OrchestratorSerializerTestBase): self.assertEquals(200, resp.status_code) interfaces = jsonutils.loads(resp.body) for iface in interfaces: - self.assertEqual( - iface['interface_properties'], - self.env.network_manager.get_default_interface_properties() - ) if iface['name'] == 'eth0': - iface['interface_properties']['mtu'] = 1500 - iface['interface_properties']['disable_offloading'] = True + iface['attributes']['mtu']['value']['value'] = 1500 + iface['attributes']['offloading'][ + 'disable']['value'] = True nodes_list.append({'id': node.id, 'interfaces': interfaces}) resp_put = self.app.put( reverse('NodeCollectionNICsHandler'), @@ -949,8 +946,11 @@ class TestNeutronOrchestratorSerializer61(OrchestratorSerializerTestBase): self.move_network(node.id, 'storage', 'eth0', 'eth1') self.env.make_bond_via_api( 'lnx_bond', '', ['eth1', 'eth2'], node.id, - bond_properties={'mode': consts.BOND_MODES.balance_rr, - 'type__': consts.BOND_TYPES.linux}, + bond_properties={ + 'mode': consts.BOND_MODES.balance_rr, + 'type__': consts.BOND_TYPES.linux + }, + attrs={'mtu': {'value': {'value': 9000}}}, interface_properties={'mtu': 9000} ) serializer = self.create_serializer(cluster) @@ -1091,19 +1091,16 @@ class TestNeutronOrchestratorSerializer61(OrchestratorSerializerTestBase): for node in cluster.nodes: self.move_network(node.id, 'storage', 'eth0', 'eth1') self.env.make_bond_via_api( - 'lnx_bond', - '', - ['eth1', 'eth2'], - node.id, + 'lnx_bond', '', ['eth1', 'eth2'], node.id, bond_properties={ 'mode': consts.BOND_MODES.l_802_3ad, 'xmit_hash_policy': consts.BOND_XMIT_HASH_POLICY.layer2, 'lacp_rate': consts.BOND_LACP_RATES.slow, 'type__': consts.BOND_TYPES.linux }, - interface_properties={ - 'mtu': 9000 - }) + attrs={'mtu': {'value': {'value': 9000}}}, + interface_properties={'mtu': 9000} + ) serializer = self.create_serializer(cluster) facts = serializer.serialize(cluster, cluster.nodes)['nodes'] for node in facts: diff --git a/nailgun/nailgun/test/integration/test_orchestrator_serializer_90.py b/nailgun/nailgun/test/integration/test_orchestrator_serializer_90.py index f6307a7870..f072267483 100644 --- a/nailgun/nailgun/test/integration/test_orchestrator_serializer_90.py +++ b/nailgun/nailgun/test/integration/test_orchestrator_serializer_90.py @@ -97,13 +97,13 @@ class TestDeploymentAttributesSerialization90( objects.NIC.assign_networks(other_nic, other_nets) objects.NIC.assign_networks(dpdk_nic, dpdk_nets) - objects.NIC.update(dpdk_nic, - {'interface_properties': - { - 'dpdk': {'enabled': True, - 'available': True}, - 'pci_id': 'test_id:2' - }}) + objects.NIC.update(dpdk_nic, { + 'meta': { + 'dpdk': {'available': True}, + 'pci_id': 'test_id:2' + }, + 'attributes': {'dpdk': {'enabled': {'value': True}}} + }) def _create_cluster_with_vxlan(self): release_id = self.cluster_db.release.id @@ -214,8 +214,8 @@ class TestDeploymentAttributesSerialization90( node.cluster, {'editable': cluster_attrs}) for iface in node.interfaces: - iface['interface_properties'].update({'pci_id': 'test_id:1'}) - iface['interface_properties']['dpdk']['available'] = True + iface['meta'].update({'pci_id': 'test_id:1'}) + iface['meta']['dpdk']['available'] = True interfaces = self.env.node_nics_get(node.id).json_body first_nic = interfaces[0] @@ -233,7 +233,7 @@ class TestDeploymentAttributesSerialization90( 'slaves': nics_for_bond, 'assigned_networks': networks_for_bond, 'bond_properties': bond_properties, - 'interface_properties': {'dpdk': {'enabled': True}}} + 'attributes': {'dpdk': {'enabled': {'value': True}}}} interfaces.append(bond_interface) self.env.node_nics_put(node.id, interfaces) objects.Cluster.prepare_for_deployment(self.cluster_db) @@ -969,16 +969,18 @@ class TestSriovSerialization90( for nic in self.env.nodes[0].nic_interfaces: if not nic.assigned_networks_list: nic_sriov = nic - nic.interface_properties['sriov'] = { - 'enabled': True, - 'sriov_numvfs': 8, - 'sriov_totalvfs': 8, + nic.attributes['sriov'] = { + 'enabled': {'value': True}, + 'numvfs': {'value': 8}, + 'physnet': {'value': 'new_physnet'} + } + nic.meta['sriov'] = { 'available': True, - 'pci_id': '1234:5678', - 'physnet': 'new_physnet' + 'totalvfs': 8, + 'pci_id': '1234:5678' } objects.NIC.update( - nic, {'interface_properties': nic.interface_properties}) + nic, {'attributes': nic.attributes, 'meta': nic.meta}) break else: self.fail('NIC without assigned networks was not found') diff --git a/nailgun/nailgun/test/integration/test_plugin_manager.py b/nailgun/nailgun/test/integration/test_plugin_manager.py index 143eb5f32b..8ffb0e8881 100644 --- a/nailgun/nailgun/test/integration/test_plugin_manager.py +++ b/nailgun/nailgun/test/integration/test_plugin_manager.py @@ -570,6 +570,7 @@ class TestClusterPluginIntegration(base.BaseTestCase): class TestNodeClusterPluginIntegration(base.BaseTestCase): + def setUp(self): super(TestNodeClusterPluginIntegration, self).setUp() @@ -712,3 +713,61 @@ class TestNodeClusterPluginIntegration(base.BaseTestCase): }, attributes ) + + +class TestNICIntegration(base.BaseTestCase): + + def setUp(self): + super(TestNICIntegration, self).setUp() + + self.cluster = self.env.create( + release_kwargs={ + 'operating_system': consts.RELEASE_OS.ubuntu, + 'version': '2015.1-8.0'}) + + self.env.create_plugin( + name='plugin_a', + cluster=self.cluster, + enabled=True, + package_version='5.0.0', + nic_attributes_metadata={'attr_a': {'value': 'test_a'}}) + self.node = self.env.create_nodes_w_interfaces_count( + 1, 1, **{"cluster_id": self.cluster.id})[0] + self.interface = self.node.nic_interfaces[0] + + def test_get_nic_default_attributes(self): + self.env.create_plugin( + name='plugin_b', + cluster=self.cluster, + enabled=True, + package_version='5.0.0', + nic_attributes_metadata={'attr_b': {'value': 'test_b'}}) + default_attributes = PluginManager.get_nic_default_attributes( + self.cluster) + self.assertDictEqual({ + 'plugin_a': {'attr_a': {'value': 'test_a'}}, + 'plugin_b': {'attr_b': {'value': 'test_b'}} + }, default_attributes) + + def test_get_nic_plugin_atributes(self): + attributes = PluginManager.get_nic_attributes(self.interface) + del attributes['plugin_a']['metadata']['nic_plugin_id'] + self.assertDictEqual( + {'plugin_a': { + 'attr_a': {'value': 'test_a'}, + 'metadata': { + 'class': 'plugin', + 'label': 'Test plugin'}}}, attributes) + + def test_update_nic_attributes(self): + new_attrs = PluginManager.get_nic_attributes(self.interface) + new_attrs['plugin_a']['attr_a']['value'] = {} + PluginManager.update_nic_attributes(new_attrs) + attributes = PluginManager.get_nic_attributes(self.interface) + del attributes['plugin_a']['metadata']['nic_plugin_id'] + self.assertDictEqual( + {'plugin_a': { + 'attr_a': {'value': {}}, + 'metadata': { + 'class': 'plugin', + 'label': 'Test plugin'}}}, attributes) diff --git a/nailgun/nailgun/test/integration/test_provisioning_serializer.py b/nailgun/nailgun/test/integration/test_provisioning_serializer.py index 2f4c6e559a..69d8667d5b 100644 --- a/nailgun/nailgun/test/integration/test_provisioning_serializer.py +++ b/nailgun/nailgun/test/integration/test_provisioning_serializer.py @@ -477,8 +477,8 @@ class TestProvisioningSerializer90(BaseIntegrationTest): ) sriov_nic = self.env.nodes[0].nic_interfaces[0] - sriov_nic.interface_properties['sriov']['available'] = True - sriov_nic.interface_properties['sriov']['enabled'] = True + sriov_nic.meta['sriov']['available'] = True + sriov_nic.attributes['sriov']['enabled']['value'] = True objects.NIC.update(sriov_nic, {}) serialized_node = self.serializer.serialize( diff --git a/nailgun/nailgun/test/unit/test_network_verify.py b/nailgun/nailgun/test/unit/test_network_verify.py index 8f88aa278b..350f6b3b5d 100644 --- a/nailgun/nailgun/test/unit/test_network_verify.py +++ b/nailgun/nailgun/test/unit/test_network_verify.py @@ -55,7 +55,7 @@ class TestNetworkVerification(BaseTestCase): dpdk_iface = iface network_config.append({'name': ng.name, 'vlans': vlans}) - dpdk_iface.interface_properties['dpdk']['enabled'] = True + dpdk_iface.attributes['dpdk']['enabled']['value'] = True task = VerifyNetworksTask(None, network_config) task.get_ifaces_on_deployed_node(node, node_json, []) @@ -107,7 +107,7 @@ class TestNetworkVerification(BaseTestCase): if ng.name == consts.NETWORKS.private: private_ifaces[node.name] = iface.name - dpdk_iface.interface_properties['dpdk']['enabled'] = True + dpdk_iface.attributes['dpdk']['enabled']['value'] = True task = models.Task( name=consts.TASK_NAMES.check_networks, @@ -147,8 +147,8 @@ class TestNetworkVerification(BaseTestCase): continue network_config.append({'name': ng.name, 'vlans': vlans}) - dpdk_iface.interface_properties['dpdk'].update({'available': True, - 'enabled': True}) + dpdk_iface.attributes['dpdk']['enabled']['value'] = True + dpdk_iface.meta['dpdk']['available'] = True private_iface.assigned_networks_list = [private_ng] task = VerifyNetworksTask(None, network_config) diff --git a/nailgun/nailgun/test/unit/test_node_attributes.py b/nailgun/nailgun/test/unit/test_node_attributes.py index ae378f283e..ccf75f64e2 100644 --- a/nailgun/nailgun/test/unit/test_node_attributes.py +++ b/nailgun/nailgun/test/unit/test_node_attributes.py @@ -37,8 +37,8 @@ class TestNodeAttributes(base.BaseUnitTest): 'components': comp_entity } m_dpdk_nics.return_value = [ - mock.Mock(interface_properties={'numa_node': None}), - mock.Mock(interface_properties={'numa_node': 1}), + mock.Mock(meta={'numa_node': None}), + mock.Mock(meta={'numa_node': 1}), ] node = mock.Mock( diff --git a/nailgun/nailgun/test/unit/test_object_plugin.py b/nailgun/nailgun/test/unit/test_object_plugin.py index 2bd195f67b..353f2ad3af 100644 --- a/nailgun/nailgun/test/unit/test_object_plugin.py +++ b/nailgun/nailgun/test/unit/test_object_plugin.py @@ -22,7 +22,9 @@ from oslo_serialization import jsonutils from nailgun import consts from nailgun.objects import ClusterPlugin +from nailgun.objects import NodeBondInterfaceClusterPlugin from nailgun.objects import NodeClusterPlugin +from nailgun.objects import NodeNICInterfaceClusterPlugin from nailgun.objects import Plugin from nailgun.objects import PluginCollection from nailgun.test import base @@ -31,29 +33,19 @@ from nailgun.test import base class ExtraFunctions(base.BaseTestCase): def _create_test_plugins(self): - plugin_ids = [] for version in ['1.0.0', '2.0.0', '0.0.1', '3.0.0', '4.0.0', '5.0.0']: - plugin_data = self.env.get_default_plugin_metadata( + self.env.create_plugin( version=version, name='multiversion_plugin') - plugin = Plugin.create(plugin_data) - plugin_ids.append(plugin.id) - - single_plugin_data = self.env.get_default_plugin_metadata( + self.env.create_plugin( name='single_plugin') - plugin = Plugin.create(single_plugin_data) - plugin_ids.append(plugin.id) - - incompatible_plugin_data = self.env.get_default_plugin_metadata( + self.env.create_plugin( name='incompatible_plugin', - releases=[] - ) - plugin = Plugin.create(incompatible_plugin_data) - plugin_ids.append(plugin.id) + releases=[]) - return plugin_ids + return [p.id for p in self.env.plugins] - def _create_test_cluster(self): + def _create_test_cluster(self, nodes=[]): return self.env.create( cluster_kwargs={'mode': consts.CLUSTER_MODES.multinode}, release_kwargs={ @@ -61,17 +53,16 @@ class ExtraFunctions(base.BaseTestCase): 'version': '2015.1-8.0', 'operating_system': 'Ubuntu', 'modes': [consts.CLUSTER_MODES.multinode, - consts.CLUSTER_MODES.ha_compact]}) + consts.CLUSTER_MODES.ha_compact]}, + nodes_kwargs=nodes) class TestPluginCollection(ExtraFunctions): def test_all_newest(self): self._create_test_plugins() - newest_plugins = PluginCollection.all_newest() self.assertEqual(len(newest_plugins), 3) - single_plugin = filter( lambda p: p.name == 'single_plugin', newest_plugins) @@ -81,7 +72,6 @@ class TestPluginCollection(ExtraFunctions): self.assertEqual(len(single_plugin), 1) self.assertEqual(len(multiversion_plugin), 1) - self.assertEqual(multiversion_plugin[0].version, '5.0.0') def test_get_by_uids(self): @@ -358,3 +348,174 @@ class TestNodeClusterPlugin(ExtraFunctions): self.assertDictEqual( attributes, jsonutils.loads(node_attributes_cluster_plugin[1])) + + +class TestNodeNICInterfaceClusterPlugin(ExtraFunctions): + + def test_get_all_attributes_by_interface_with_enabled_plugin(self): + nic_config = self.env.get_default_plugin_nic_config() + plugin = self.env.create_plugin( + name='plugin_a_with_nic_attributes', + package_version='5.0.0', + nic_attributes_metadata=nic_config) + cluster = self._create_test_cluster() + # create node with 1 interface for easy testing + node = self.env.create_nodes_w_interfaces_count( + 1, 1, **{"cluster_id": cluster.id})[0] + interface = node.nic_interfaces[0] + nic_plugin_id = node.node_nic_interface_cluster_plugins[0].id + ClusterPlugin.set_attributes(cluster.id, plugin.id, enabled=True) + + attributes = NodeNICInterfaceClusterPlugin.\ + get_all_enabled_attributes_by_interface(interface) + expected_attributes = { + 'plugin_a_with_nic_attributes': { + 'metadata': { + 'label': 'Test plugin', + 'nic_plugin_id': nic_plugin_id, + 'class': 'plugin'}}} + expected_attributes['plugin_a_with_nic_attributes'].update(nic_config) + + self.assertEqual(expected_attributes, attributes) + + def test_get_all_attributes_by_interface_with_disabled_plugin(self): + nic_config = self.env.get_default_plugin_nic_config() + self.env.create_plugin( + name='plugin_a_with_nic_attributes', + package_version='5.0.0', + nic_attributes_metadata=nic_config) + cluster = self._create_test_cluster() + node = self.env.create_nodes_w_interfaces_count( + 1, 1, **{"cluster_id": cluster.id})[0] + interface = node.nic_interfaces[0] + + attributes = NodeNICInterfaceClusterPlugin.\ + get_all_enabled_attributes_by_interface(interface) + + self.assertDictEqual({}, attributes) + + def test_populate_nic_with_plugin_attributes(self): + # create cluster with 2 nodes + # install plugin with nic attributes which compatible with cluster + meta = base.reflect_db_metadata() + nic_config = self.env.get_default_plugin_nic_config() + self._create_test_cluster( + nodes=[{'roles': ['controller']}, {'roles': ['compute']}]) + self.env.create_plugin( + name='plugin_a_with_nic_attributes', + package_version='5.0.0', + nic_attributes_metadata=nic_config) + + node_nic_interface_cluster_plugins = self.db.execute( + meta.tables['node_nic_interface_cluster_plugins'].select() + ).fetchall() + + self.assertEqual(4, len(node_nic_interface_cluster_plugins)) + for item in node_nic_interface_cluster_plugins: + self.assertDictEqual(nic_config, jsonutils.loads(item.attributes)) + + def test_populate_nic_with_empty_plugin_attributes(self): + # create cluster with 2 nodes + # install plugin without nic attributes which compatible with cluster + meta = base.reflect_db_metadata() + self._create_test_cluster( + nodes=[{'roles': ['controller']}, {'roles': ['compute']}]) + self.env.create_plugin( + name='plugin_b_with_nic_attributes', + package_version='5.0.0', + nic_attributes_metadata={}) + + node_nic_interface_cluster_plugins = self.db.execute( + meta.tables['node_nic_interface_cluster_plugins'].select() + ).fetchall() + + self.assertEqual(0, len(node_nic_interface_cluster_plugins)) + + def test_add_cluster_plugin_for_node_nic(self): + # install plugins compatible with cluster + # populate cluster with node + meta = base.reflect_db_metadata() + nic_config = self.env.get_default_plugin_nic_config() + self.env.create_plugin( + name='plugin_a_with_nic_attributes', + package_version='5.0.0', + nic_attributes_metadata=nic_config) + self.env.create_plugin( + name='plugin_b_with_nic_attributes', + package_version='5.0.0', + nic_attributes_metadata={}) + self._create_test_cluster( + nodes=[{'roles': ['controller']}, {'roles': ['compute']}]) + + node_nic_interface_cluster_plugins = self.db.execute( + meta.tables['node_nic_interface_cluster_plugins'].select() + ).fetchall() + + self.assertEqual(4, len(node_nic_interface_cluster_plugins)) + for item in node_nic_interface_cluster_plugins: + self.assertDictEqual(nic_config, jsonutils.loads(item.attributes)) + + def test_set_attributes(self): + meta = base.reflect_db_metadata() + nic_config = self.env.get_default_plugin_nic_config() + self.env.create_plugin( + name='plugin_a_with_nic_attributes', + package_version='5.0.0', + nic_attributes_metadata=nic_config) + cluster = self._create_test_cluster() + self.env.create_nodes_w_interfaces_count( + 1, 1, **{"cluster_id": cluster.id})[0] + + node_nic_interface_cluster_plugin = self.db.execute( + meta.tables['node_nic_interface_cluster_plugins'].select() + ).fetchall()[0] + + _id = node_nic_interface_cluster_plugin.id + attributes = {'test_attr': 'a'} + NodeNICInterfaceClusterPlugin.set_attributes(_id, attributes) + + node_nic_interface_cluster_plugin = self.db.execute( + meta.tables['node_nic_interface_cluster_plugins'].select() + ).fetchall()[0] + + self.assertDictEqual( + attributes, + jsonutils.loads(node_nic_interface_cluster_plugin[1])) + + +class TestNodeBondInterfaceClusterPlugin(ExtraFunctions): + + def test_set_attributes(self): + meta = base.reflect_db_metadata() + bond_config = self.env.get_default_plugin_bond_config() + self.env.create_plugin( + name='plugin_a_with_bond_attributes', + package_version='5.0.0', + bond_attributes_metadata=bond_config) + cluster = self._create_test_cluster( + nodes=[{'roles': ['controller']}]) + + for node in cluster.nodes: + nic_names = [iface.name for iface in node.nic_interfaces] + self.env.make_bond_via_api( + 'lnx_bond', '', nic_names, node.id, + bond_properties={ + 'type__': consts.BOND_TYPES.linux, + 'mode': consts.BOND_MODES.balance_rr}, + attrs=bond_config) + + node_bond_interface_cluster_plugin = self.db.execute( + meta.tables['node_bond_interface_cluster_plugins'].select() + ).fetchall()[0] + + _id = node_bond_interface_cluster_plugin.id + attributes = {'test_attr': 'a'} + NodeBondInterfaceClusterPlugin.set_attributes(_id, attributes) + + node_bond_interface_cluster_plugin = self.db.execute( + meta.tables['node_bond_interface_cluster_plugins'].select() + ).fetchall()[0] + + self.assertDictEqual( + attributes, + jsonutils.loads(node_bond_interface_cluster_plugin[1])) diff --git a/nailgun/nailgun/test/unit/test_task.py b/nailgun/nailgun/test/unit/test_task.py index 107bcee99f..1603a3f0d4 100644 --- a/nailgun/nailgun/test/unit/test_task.py +++ b/nailgun/nailgun/test/unit/test_task.py @@ -423,14 +423,20 @@ class TestCheckBeforeDeploymentTask(BaseTestCase): ) def test_sriov_is_enabled_with_non_kvm_hypervisor(self): - objects.NIC.update(self.node.nic_interfaces[0], - {'interface_properties': - { - 'sriov': {'enabled': True, - 'sriov_totalvfs': 4, - 'sriov_numfs': 2, - 'available': True}, - }}) + objects.NIC.update(self.node.nic_interfaces[0], { + 'attributes': { + 'sriov': { + 'enabled': {'value': True}, + 'numfs': {'value': 2} + } + }, + 'meta': { + 'sriov': { + 'available': True, + 'totalvfs': 4, + } + } + }) self.assertRaisesRegexp( errors.InvalidData,