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:
Nikita Zubkov 2016-03-22 16:48:09 +03:00
parent 7c9b145490
commit 433eb9fdc6
6 changed files with 267 additions and 12 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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