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
This commit is contained in:
Andriy Popovych 2016-03-02 19:47:31 +02:00
parent 7184b2229e
commit 703290986f
31 changed files with 1596 additions and 381 deletions

View File

@ -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
}
}
}
}
}

View File

@ -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<cluster_id>\d+)/network_configuration/'
r'nova_network/verify/?$',
'handler': NovaNetworkConfigurationVerifyHandler},
{'uri': r'/nodes/(?P<node_id>\d+)/bonds/attributes/defaults/?$',
'handler': NodeBondAttributesDefaultsHandler},
{'uri': r'/nodes/interfaces/?$',
'handler': NodeCollectionNICsHandler},
{'uri': r'/nodes/interfaces/default_assignment/?$',

View File

@ -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)

View File

@ -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)

View File

@ -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):

View File

@ -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):

View File

@ -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

View File

@ -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

View File

@ -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',

View File

@ -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):

View File

@ -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):

View File

@ -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'])

View File

@ -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)

View File

@ -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,

View File

@ -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(

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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)

View File

@ -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()

View File

@ -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):

View File

@ -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.

View File

@ -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

View File

@ -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:

View File

@ -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')

View File

@ -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)

View File

@ -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(

View File

@ -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)

View File

@ -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(

View File

@ -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]))

View File

@ -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,