Add API validator for DPDK
NetAssignmentValidator checks that DPDK enabled only for supported interfaces, hugepages are configured and hypervisor type is kvm Change-Id: I50a22cd837e0765f9b21da1973f2407660f62cd2 Partial-Bug: #1559224 Related to blueprint support-dpdk
This commit is contained in:
parent
7c9b145490
commit
433eb9fdc6
|
@ -31,6 +31,7 @@ from nailgun.db.sqlalchemy.models import Node
|
|||
from nailgun.db.sqlalchemy.models import NodeGroup
|
||||
from nailgun.errors import errors
|
||||
from nailgun import objects
|
||||
from nailgun import utils
|
||||
|
||||
|
||||
class NetworkConfigurationValidator(BasicValidator):
|
||||
|
@ -479,6 +480,106 @@ class NetAssignmentValidator(BasicValidator):
|
|||
log_message=True
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _find_iface(cls, db_interfaces, default=None, **kwargs):
|
||||
return next(
|
||||
objects.NICCollection.filter_by(db_interfaces, **kwargs),
|
||||
default
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _get_iface_by_id(cls, id_, db_interfaces, default=None):
|
||||
return cls._find_iface(db_interfaces, default, id=id_)
|
||||
|
||||
@classmethod
|
||||
def _get_iface_by_name(cls, name, db_interfaces, default=None):
|
||||
return cls._find_iface(db_interfaces, default, name=name)
|
||||
|
||||
@classmethod
|
||||
def _verify_iface_dpdk_properties(cls, iface, db_interfaces, dpdk_drivers):
|
||||
db_iface = cls._get_iface_by_id(iface.get('id'), db_interfaces)
|
||||
if db_iface is None:
|
||||
db_iface = cls._get_iface_by_name(iface['name'], db_interfaces)
|
||||
|
||||
if iface['type'] == consts.NETWORK_INTERFACE_TYPES.ether:
|
||||
iface_cls = objects.NIC
|
||||
elif iface['type'] == consts.NETWORK_INTERFACE_TYPES.bond:
|
||||
iface_cls = objects.Bond
|
||||
|
||||
if db_iface is None:
|
||||
# looks like user create new bond
|
||||
# lets check every slave in input data
|
||||
slaves = iface['slaves']
|
||||
hw_available = bool(slaves)
|
||||
|
||||
for slave in slaves:
|
||||
slave_iface = cls._get_iface_by_name(
|
||||
slave['name'], db_interfaces)
|
||||
|
||||
hw_available &= objects.NIC.dpdk_available(
|
||||
slave_iface, dpdk_drivers)
|
||||
|
||||
interface_properties = iface.get('interface_properties', {})
|
||||
|
||||
else:
|
||||
hw_available = iface_cls.dpdk_available(db_iface, dpdk_drivers)
|
||||
interface_properties = utils.dict_merge(
|
||||
db_iface.interface_properties,
|
||||
iface.get('interface_properties', {})
|
||||
)
|
||||
|
||||
# sanity checks
|
||||
available = interface_properties.get('dpdk', {}).get('available')
|
||||
enabled = interface_properties.get('dpdk', {}).get('enabled', False)
|
||||
|
||||
if available is not None and hw_available != available:
|
||||
raise errors.InvalidData(
|
||||
"DPDK availability on interface '{}' is hardware property"
|
||||
" and can't be changed manually.".format(iface['name']))
|
||||
|
||||
if not hw_available and enabled:
|
||||
raise errors.InvalidData("DPDK is not available for '{}'".format(
|
||||
iface['name']))
|
||||
|
||||
if db_iface is not None:
|
||||
pci_id = interface_properties.get('pci_id')
|
||||
db_pci_id = db_iface.interface_properties.get('pci_id')
|
||||
|
||||
if pci_id is not None and pci_id != db_pci_id:
|
||||
raise errors.InvalidData(
|
||||
"PCI-ID of '{}' can't be changed manually".format(
|
||||
iface['name']))
|
||||
|
||||
# check that dpdk interface have only one network == 'private'
|
||||
nets = iface['assigned_networks']
|
||||
if enabled and not (
|
||||
len(nets) == 1 and
|
||||
nets[0]['name'] == consts.NETWORKS.private
|
||||
):
|
||||
raise errors.InvalidData(
|
||||
"Only private network could be assigned"
|
||||
" to interface '{}' where DPDK is enabled".format(
|
||||
iface['name']))
|
||||
|
||||
return enabled
|
||||
|
||||
@classmethod
|
||||
def _verify_node_dpdk_properties(cls, db_node, node):
|
||||
if not objects.NodeAttributes.is_dpdk_hugepages_enabled(db_node):
|
||||
raise errors.InvalidData("Hugepages for DPDK are not configured"
|
||||
" for node '{}'".format(db_node.id))
|
||||
|
||||
if not objects.NodeAttributes.is_nova_hugepages_enabled(db_node):
|
||||
raise errors.InvalidData("Hugepages for Nova are not configured"
|
||||
" for node '{}'".format(db_node.id))
|
||||
|
||||
# check hypervisor type
|
||||
h_type = objects.Cluster.get_editable_attributes(
|
||||
db_node.cluster)['common']['libvirt_type']['value']
|
||||
|
||||
if h_type != 'kvm':
|
||||
raise errors.InvalidData('Only KVM hypervisor works with DPDK.')
|
||||
|
||||
@classmethod
|
||||
def verify_data_correctness(cls, node):
|
||||
db_node = db().query(Node).filter_by(id=node['id']).first()
|
||||
|
@ -491,6 +592,7 @@ class NetAssignmentValidator(BasicValidator):
|
|||
raise errors.InvalidData(
|
||||
"Node '{0}': Interfaces configuration can't be changed after "
|
||||
"or during deployment.".format(db_node.id))
|
||||
|
||||
interfaces = node['interfaces']
|
||||
db_interfaces = db_node.nic_interfaces
|
||||
net_manager = objects.Cluster.get_network_manager(db_node.cluster)
|
||||
|
@ -521,10 +623,7 @@ class NetAssignmentValidator(BasicValidator):
|
|||
)
|
||||
|
||||
if iface['type'] == consts.NETWORK_INTERFACE_TYPES.ether:
|
||||
db_iface = next(six.moves.filter(
|
||||
lambda i: i.id == iface['id'],
|
||||
db_interfaces
|
||||
), None)
|
||||
db_iface = cls._get_iface_by_id(iface['id'], db_interfaces)
|
||||
if not db_iface:
|
||||
raise errors.InvalidData(
|
||||
"Node '{0}': there is no interface with ID '{1}'"
|
||||
|
@ -541,22 +640,25 @@ class NetAssignmentValidator(BasicValidator):
|
|||
)
|
||||
if iface.get('interface_properties', {}).get('sriov'):
|
||||
cls._verify_sriov_properties(db_iface, iface, node['id'])
|
||||
|
||||
elif iface['type'] == consts.NETWORK_INTERFACE_TYPES.bond:
|
||||
pxe_iface_present = False
|
||||
for slave in iface['slaves']:
|
||||
iface_id = [i.id for i in db_interfaces
|
||||
if i.name == slave['name']]
|
||||
db_slave = cls._get_iface_by_name(
|
||||
slave['name'], db_interfaces)
|
||||
|
||||
if slave["name"] == pxe_iface_name:
|
||||
pxe_iface_present = True
|
||||
if iface_id:
|
||||
if iface_id[0] in bonded_eth_ids:
|
||||
|
||||
if db_slave is not None:
|
||||
if db_slave.id in bonded_eth_ids:
|
||||
raise errors.InvalidData(
|
||||
"Node '{0}': interface '{1}' is used in bonds "
|
||||
"more than once".format(
|
||||
node['id'], iface_id[0]),
|
||||
node['id'], db_slave.id),
|
||||
log_message=True
|
||||
)
|
||||
bonded_eth_ids.add(iface_id[0])
|
||||
bonded_eth_ids.add(db_slave.id)
|
||||
else:
|
||||
raise errors.InvalidData(
|
||||
"Node '{0}': there is no interface '{1}' found "
|
||||
|
@ -585,6 +687,15 @@ class NetAssignmentValidator(BasicValidator):
|
|||
log_message=True
|
||||
)
|
||||
|
||||
dpdk_enabled = False
|
||||
|
||||
if db_node.cluster is not None:
|
||||
dpdk_drivers = objects.Release.get_supported_dpdk_drivers(
|
||||
db_node.cluster.release)
|
||||
else:
|
||||
dpdk_drivers = {}
|
||||
db_interfaces = db_node.interfaces
|
||||
|
||||
for iface in interfaces:
|
||||
if iface['type'] == consts.NETWORK_INTERFACE_TYPES.ether \
|
||||
and iface['id'] in bonded_eth_ids \
|
||||
|
@ -596,6 +707,14 @@ class NetAssignmentValidator(BasicValidator):
|
|||
log_message=True
|
||||
)
|
||||
|
||||
# checks dpdk settings for every interface
|
||||
dpdk_enabled |= cls._verify_iface_dpdk_properties(
|
||||
iface, db_interfaces, dpdk_drivers)
|
||||
|
||||
# run node validations if dpdk enabled on node
|
||||
if dpdk_enabled:
|
||||
cls._verify_node_dpdk_properties(db_node, node)
|
||||
|
||||
if db_node.cluster:
|
||||
cls.check_networks_are_acceptable_for_node_to_assign(interfaces,
|
||||
db_node)
|
||||
|
|
|
@ -33,7 +33,7 @@ class DPDKMixin(object):
|
|||
@classmethod
|
||||
def dpdk_enabled(cls, instance):
|
||||
dpdk = instance.interface_properties.get('dpdk')
|
||||
return dpdk and dpdk.get('enabled')
|
||||
return bool(dpdk and dpdk.get('enabled'))
|
||||
|
||||
@classmethod
|
||||
def refresh_interface_dpdk_properties(cls, interface, dpdk_drivers):
|
||||
|
|
|
@ -1447,6 +1447,11 @@ class NodeAttributes(object):
|
|||
return ('nova' in hugepages and
|
||||
any(six.itervalues(hugepages['nova']['value'])))
|
||||
|
||||
@classmethod
|
||||
def is_dpdk_hugepages_enabled(cls, node):
|
||||
return int(Node.get_attributes(
|
||||
node)['hugepages']['dpdk']['value']) != 0
|
||||
|
||||
@classmethod
|
||||
def dpdk_hugepages_attrs(cls, node):
|
||||
"""Return hugepages configuration for DPDK
|
||||
|
|
|
@ -1111,8 +1111,13 @@ class EnvironmentManager(object):
|
|||
}
|
||||
if bond_properties:
|
||||
bond_dict["bond_properties"] = bond_properties
|
||||
else:
|
||||
bond_dict["bond_properties"] = {}
|
||||
|
||||
if interface_properties:
|
||||
bond_dict["interface_properties"] = interface_properties
|
||||
else:
|
||||
bond_dict["interface_properties"] = {}
|
||||
data.append(bond_dict)
|
||||
resp = self.node_nics_put(node_id, data)
|
||||
self.tester.assertEqual(resp.status_code, 200)
|
||||
|
|
|
@ -122,8 +122,20 @@ class TestDeploymentAttributesSerialization90(
|
|||
1, 4,
|
||||
cluster_id=self.cluster_db.id,
|
||||
roles=['compute'])[0]
|
||||
|
||||
node.attributes['hugepages'] = {
|
||||
'dpdk': {'type': 'text', 'value': '1024'},
|
||||
'nova': {'type': 'custom_hugepages', 'value': {'2048': 1}}
|
||||
}
|
||||
cluster_attrs = objects.Cluster.get_editable_attributes(node.cluster)
|
||||
cluster_attrs['common']['libvirt_type']['value'] = 'kvm'
|
||||
objects.Cluster.update_attributes(
|
||||
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
|
||||
|
||||
interfaces = self.env.node_nics_get(node.id).json_body
|
||||
first_nic = interfaces[0]
|
||||
nics_for_bond = [interfaces.pop(), interfaces.pop()]
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
# Copyright 2015 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
|
@ -13,8 +12,12 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
|
||||
from nailgun.api.v1.validators.network import NetAssignmentValidator
|
||||
from nailgun import consts
|
||||
from nailgun.errors import errors
|
||||
from nailgun import objects
|
||||
from nailgun.test.base import BaseValidatorTest
|
||||
|
||||
|
||||
|
@ -37,3 +40,114 @@ class TestNetAssignmentValidator(BaseNetAssignmentValidatorTest):
|
|||
self.env.create_node()
|
||||
node_data = self.env.nodes[0]
|
||||
self.validator(node_data)
|
||||
|
||||
|
||||
class TestDPDKValidation(BaseNetAssignmentValidatorTest):
|
||||
def setUp(self):
|
||||
super(TestDPDKValidation, self).setUp()
|
||||
self.patcher = mock.patch(
|
||||
'nailgun.objects.Release.get_supported_dpdk_drivers',
|
||||
return_value={'driver_1': ['test_id:1', 'test_id:2']}
|
||||
)
|
||||
self.patcher.start()
|
||||
|
||||
cluster = self.env.create(
|
||||
api=False,
|
||||
cluster_kwargs={
|
||||
'net_provider': consts.CLUSTER_NET_PROVIDERS.neutron,
|
||||
'net_segment_type': consts.NEUTRON_SEGMENT_TYPES.vlan,
|
||||
}
|
||||
)
|
||||
|
||||
self.cluster_db = self.env.clusters[-1]
|
||||
node = self.env.create_nodes_w_interfaces_count(
|
||||
1, 2, roles=['compute'], cluster_id=cluster['id'])[0]
|
||||
|
||||
nic_1 = node.nic_interfaces[0]
|
||||
nic_2 = node.nic_interfaces[1]
|
||||
nets_1 = nic_1.assigned_networks_list
|
||||
nets_2 = nic_2.assigned_networks_list
|
||||
|
||||
nets_1.extend(nets_2)
|
||||
nets_2 = nic_2.assigned_networks_list = []
|
||||
|
||||
for i, net in enumerate(nets_1):
|
||||
if net['name'] == 'private':
|
||||
nets_2.append(nets_1.pop(i))
|
||||
break
|
||||
|
||||
objects.NIC.assign_networks(nic_1, nets_1)
|
||||
objects.NIC.assign_networks(nic_2, nets_2)
|
||||
|
||||
objects.NIC.update(nic_2,
|
||||
{'interface_properties':
|
||||
{
|
||||
'dpdk': {'enabled': True,
|
||||
'available': True},
|
||||
'pci_id': 'test_id:2',
|
||||
}})
|
||||
|
||||
node.attributes['hugepages'] = {
|
||||
'nova': {'type': 'custom_hugepages', 'value': {'2048': 1}},
|
||||
'dpdk': {'type': 'text', 'value': '1025'},
|
||||
}
|
||||
self.cluster_attrs = objects.Cluster.get_editable_attributes(
|
||||
self.cluster_db)
|
||||
|
||||
self.cluster_attrs['common']['libvirt_type']['value'] = 'kvm'
|
||||
objects.Cluster.update_attributes(
|
||||
self.cluster_db, {'editable': self.cluster_attrs})
|
||||
|
||||
self.node = node
|
||||
|
||||
self.data = {'id': node.id,
|
||||
'interfaces': self.env.node_nics_get(node.id).json_body}
|
||||
|
||||
def tearDown(self):
|
||||
super(TestDPDKValidation, self).tearDown()
|
||||
self.patcher.stop()
|
||||
|
||||
def check_fail(self, msg, data=None):
|
||||
if data is None:
|
||||
data = self.data
|
||||
|
||||
self.assertRaisesRegexp(errors.InvalidData,
|
||||
msg,
|
||||
self.validator, data)
|
||||
|
||||
def test_valid_dpdk_configuration(self):
|
||||
self.validator(self.node)
|
||||
|
||||
def test_invalid_hypervisor(self):
|
||||
self.cluster_attrs['common']['libvirt_type']['value'] = 'quemu'
|
||||
objects.Cluster.update_attributes(
|
||||
self.cluster_db, {'editable': self.cluster_attrs})
|
||||
|
||||
self.check_fail('Only KVM hypervisor works with DPDK')
|
||||
|
||||
def test_hugepages_not_configured(self):
|
||||
self.node.attributes['hugepages']['nova'] = {'value': {}}
|
||||
self.check_fail('Hugepages for Nova are not configured', self.node)
|
||||
|
||||
def test_hugepages_not_configured2(self):
|
||||
self.node.attributes['hugepages']['dpdk'] = {'value': '0'}
|
||||
self.check_fail('Hugepages for DPDK are not configured')
|
||||
|
||||
def test_iface_cant_be_availible(self):
|
||||
props = self.data['interfaces'][0]['interface_properties']
|
||||
props['pci_id'] = 'test_id:2'
|
||||
|
||||
dpdk = props.get('dpdk', {})
|
||||
dpdk['available'] = True
|
||||
props['dpdk'] = dpdk
|
||||
|
||||
self.check_fail("DPDK .* can't be changed manually")
|
||||
|
||||
def test_wrong_network(self):
|
||||
self.data['interfaces'][1]['assigned_networks'] = []
|
||||
self.check_fail('Only private network could be assigned')
|
||||
|
||||
def test_change_pci_id(self):
|
||||
iface = self.data['interfaces'][1]
|
||||
iface['interface_properties']['pci_id'] = '123:345'
|
||||
self.check_fail("PCI-ID .* can't be changed manually")
|
||||
|
|
Loading…
Reference in New Issue