Bond attributes in interface API

Remove restrictions on set of BOND properties. Now BOND can have
any attributes.
Remove redundant `bond_properties`.

Change-Id: I60d1a0628d84c5ba49bb5b45824d660297dacccc
Implements: blueprint nics-and-nodes-attributes-via-plugin
This commit is contained in:
Andriy Popovych 2016-07-15 12:53:55 +03:00
parent 040c1b4544
commit 1b9dff2dac
23 changed files with 682 additions and 305 deletions

View File

@ -208,15 +208,6 @@ BOND_MODES = Enum(
)
)
BOND_PROPERTIES = Enum(
'mode',
'xmit_hash_policy',
'lacp_rate',
'lacp',
# not for orchestrator input
'type__'
)
BOND_XMIT_HASH_POLICY = Enum(
'layer2',
'layer2+3',

View File

@ -36,6 +36,7 @@ from nailgun.logger import logger
from nailgun import objects
from nailgun.settings import settings
from nailgun import utils as nailgun_utils
from nailgun.utils import get_in
from nailgun.utils.restrictions import RestrictionBase
@ -778,8 +779,13 @@ class NetworkManager(object):
objects.NIC.update(current_iface, update)
objects.Node.clear_bonds(node_db)
for bond in bond_interfaces:
if bond.get('bond_properties', {}).get('mode'):
# updated via API
if get_in(bond, 'attributes', 'mode', 'value', 'value'):
mode = bond['attributes']['mode']['value']['value']
# updated via network templates
elif get_in(bond, 'bond_properties', 'mode'):
mode = bond['bond_properties']['mode']
else:
mode = bond['mode']
@ -787,9 +793,7 @@ class NetworkManager(object):
'node': node_db,
'name': bond['name'],
'mode': mode,
'mac': bond.get('mac'),
'bond_properties': bond.get('bond_properties', {}),
'attributes': bond.get('attributes', {})
'mac': bond.get('mac')
}
bond_db = objects.Bond.create(data)
@ -802,9 +806,19 @@ class NetworkManager(object):
node_nics = {nic['name']: nic for nic in node_db.nic_interfaces}
slaves = [node_nics[n['name']] for n in bond['slaves']]
bond_attributes = bond.get('attributes', {})
nailgun_utils.remove_key_from_dict(
bond_attributes, 'bond_plugin_id')
bond_attributes = nailgun_utils.dict_merge(
objects.Bond.get_attributes(bond_db),
bond_attributes
)
update = {
'slaves': slaves,
'offloading_modes': bond.get('offloading_modes', {})
'offloading_modes': bond.get('offloading_modes', {}),
'attributes': bond_attributes
}
objects.Bond.update(bond_db, update)
@ -1354,20 +1368,33 @@ class NetworkManager(object):
@classmethod
def get_lnx_bond_properties(cls, bond):
properties = {'mode': bond.mode}
properties.update(bond.bond_properties)
to_drop = [k for k in properties.keys() if k.endswith('__')]
for prop in to_drop:
properties.pop(prop)
properties = {}
attributes = nailgun_utils.dict_merge(
{'mode': {'value': {'value': bond.mode}}}, bond.attributes)
def set_property(*args):
if get_in(attributes, *args):
temp_attrs = attributes
for arg in args:
value = temp_attrs[arg]
temp_attrs = value
properties[args[0]] = value
keys = (('mode', 'value', 'value'),
('lacp', 'value', 'value'),
('lacp_rate', 'value', 'value'),
('xmit_hash_policy', 'value', 'value'))
for key in keys:
set_property(*key)
return properties
@classmethod
def get_iface_properties(cls, iface):
properties = {}
if iface.attributes.get('mtu', {}).get('value', {}).get('value'):
if get_in(iface.attributes, 'mtu', 'value', 'value'):
properties['mtu'] = iface.attributes['mtu']['value']['value']
if iface.attributes.get('offloading', {}).get('disable', {}).get(
'value'):
if get_in(iface.attributes, 'offloading', 'disable', 'value'):
properties['vendor_specific'] = {
'disable_offloading':
iface.attributes['offloading']['disable']['value']

View File

@ -59,8 +59,15 @@ class Bond(DPDKMixin, NailgunObject):
:param data: dictionary of key-value pairs as object fields
:returns: instance of an object (model)
"""
instance.update(data)
attributes = data.pop('attributes', None)
if attributes:
PluginManager.update_bond_attributes(attributes)
instance.attributes = utils.dict_merge(
instance.attributes, attributes)
instance = super(Bond, cls).update(instance, data)
instance.offloading_modes = data.get('offloading_modes', {})
return instance
@classmethod
@ -99,7 +106,7 @@ class Bond(DPDKMixin, NailgunObject):
return attributes
@classmethod
def get_bond_default_attributes(cls, cluster):
def get_default_attributes(cls, cluster):
"""Get native and plugin default attributes for bond.
:param cluster: A cluster instance

View File

@ -287,7 +287,7 @@ class NIC(DPDKMixin, NailgunObject):
:type instance: NodeNICInterface model
:param data: Data to update
:type data: dict
:returns: None
:returns: instance of an object (model)
"""
attributes = data.pop('attributes', None)
if attributes:

View File

@ -43,9 +43,7 @@ class NodeInterfacesSerializer(BasicSerializer):
'mac',
'name',
'type',
'interface_properties',
'mode',
'bond_properties',
'state',
'assigned_networks',
'offloading_modes'
@ -68,7 +66,6 @@ class NodeInterfacesSerializer(BasicSerializer):
'name',
'type',
'mode',
'bond_properties',
'state',
'assigned_networks'
)

View File

@ -881,14 +881,14 @@ class TestPublicNetworkAssigment(BaseIntegrationTest):
3)
self.env.make_bond_via_api(
'ovsbond0', consts.BOND_MODES.balance_tcp, ['eth1', 'eth2'],
node1['id'], bond_properties={'type__': consts.BOND_TYPES.ovs})
node1['id'], attrs={'type__': {'value': consts.BOND_TYPES.ovs}})
node2, macs2 = self.create_node_with_preset_macs(cluster,
['cinder'],
3)
self.env.make_bond_via_api(
'ovsbond0', consts.BOND_MODES.balance_tcp, ['eth1', 'eth2'],
node2['id'], bond_properties={'type__': consts.BOND_TYPES.ovs})
node2['id'], attrs={'type__': {'value': consts.BOND_TYPES.ovs}})
self.check_network_assigments(node1, {
'eth0': self.default_networks,

View File

@ -294,45 +294,57 @@ class TestHandlers(BaseIntegrationTest):
'weight': 30
},
'enabled': {
'label': 'SR-IOV enabled',
'label': 'Enable SR-IOV',
'description': 'Single-root I/O Virtualization (SR-IOV) '
'is a specification that, when implemented '
'by a physical PCIe device, enables it to '
'appear as multiple separate PCIe devices. '
'This enables multiple virtualized guests '
'to share direct access to the physical '
'device, offering improved performance '
'over an equivalent virtual device.',
'weight': 10,
'type': 'checkbox',
'value': False
'value': False,
'restrictions': [{
"settings:common.libvirt_type.value != 'kvm'":
"Only KVM hypervisor works with SR-IOV"
}]
},
'numvfs': {
'label': 'Virtual functions',
'label': 'Number of Virtual Functions',
'weight': 20,
'type': 'number',
'min': 0,
'value': None
'min': 1,
'value': None,
'restrictions': [
"nic_attributes:sriov.enabled.value == false"]
},
'physnet': {
'label': 'Physical network',
'label': 'Physical Network Name',
'weight': 30,
'type': 'text',
'value': 'physnet2'
}
})
self.assertEqual(
resp_nic['meta']['sriov'],
{
'available': True,
'pci_id': '1234:5678',
'totalvfs': 8
})
self.assertEqual(
resp_nic['attributes']['dpdk'],
{
'metadata': {
'label': 'DPDK',
'weight': 40
},
'enabled': {
'label': 'DPDK enabled',
'weight': 10,
'type': 'checkbox',
'value': False
'value': 'physnet2',
'regex': {
'source': "^[A-Za-z0-9 _]*[A-Za-z0-9][A-Za-z0-9 _]*$",
'error': "Invalid physical network name"
},
'restrictions': [
"nic_attributes:sriov.enabled.value == false",
{
'condition': "nic_attributes:sriov.physnet.value "
"!= 'physnet2'",
'message': "Only \"physnet2\" will be configured "
"by Fuel in Neutron. Configuration of "
"other physical networks is up to "
"Operator or plugin. Fuel will just "
"configure appropriate "
"pci_passthrough_whitelist option in "
"nova.conf for such interface and "
"physical networks.",
'action': "none"
}
]
}
})
self.assertEqual(
@ -385,10 +397,17 @@ class TestHandlers(BaseIntegrationTest):
'weight': 40
},
'enabled': {
'label': 'DPDK enabled',
'label': 'Enable DPDK',
'description': 'The Data Plane Development Kit (DPDK) '
'provides high-performance packet '
'processing libraries and user space '
'drivers.',
'weight': 10,
'type': 'checkbox',
'value': False
'value': False,
'restrictions': [{
"settings:common.libvirt_type.value != 'kvm'":
"Only KVM hypervisor works with DPDK"}]
}
})
self.assertEqual(
@ -631,7 +650,9 @@ class TestHandlers(BaseIntegrationTest):
'label': 'MTU',
'weight': 10,
'type': 'number',
'value': 1500
'value': 1500,
'min': 42,
'max': 65536
}
}
nodes_list = [{'id': node['id'], 'interfaces': [nic]}]
@ -662,7 +683,9 @@ class TestHandlers(BaseIntegrationTest):
'label': 'MTU',
'weight': 10,
'type': 'number',
'value': 1500
'value': 1500,
'min': 42,
'max': 65536
}
})
@ -1174,7 +1197,7 @@ class TestNICAttributesHandlers(BaseIntegrationTest):
'offloading': {
'disable': {
'value': False,
'label': 'Disable offloading',
'label': 'Disable Offloading',
'type': 'checkbox',
'weight': 10
},
@ -1183,9 +1206,8 @@ class TestNICAttributesHandlers(BaseIntegrationTest):
'weight': 10
},
'modes': {
'description': 'Offloading modes',
'value': {},
'label': 'Offloading modes',
'label': 'Offloading Modes',
'type': 'offloading_modes',
'weight': 20
}
@ -1195,7 +1217,9 @@ class TestNICAttributesHandlers(BaseIntegrationTest):
'value': None,
'label': 'MTU',
'type': 'number',
'weight': 10
'weight': 10,
'min': 42,
'max': 65536
},
'metadata': {
'label': 'MTU',
@ -1203,40 +1227,81 @@ class TestNICAttributesHandlers(BaseIntegrationTest):
}
},
'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
},
'enabled': {
'label': 'Enable SR-IOV',
'description': 'Single-root I/O Virtualization (SR-IOV) '
'is a specification that, when implemented '
'by a physical PCIe device, enables it to '
'appear as multiple separate PCIe devices. '
'This enables multiple virtualized guests '
'to share direct access to the physical '
'device, offering improved performance '
'over an equivalent virtual device.',
'weight': 10,
'type': 'checkbox',
'value': False,
'restrictions': [{
"settings:common.libvirt_type.value != 'kvm'":
"Only KVM hypervisor works with SR-IOV"
}]
},
'numvfs': {
'value': None,
'label': 'Virtual functions',
'label': 'Number of Virtual Functions',
'weight': 20,
'type': 'number',
'min': 0
'min': 1,
'value': None,
'restrictions': [
"nic_attributes:sriov.enabled.value == false"]
},
'physnet': {
'label': 'Physical Network Name',
'weight': 30,
'type': 'text',
'value': 'physnet2',
'regex': {
'source': "^[A-Za-z0-9 _]*[A-Za-z0-9][A-Za-z0-9 _]*$",
'error': "Invalid physical network name"
},
'restrictions': [
"nic_attributes:sriov.enabled.value == false",
{
'condition': "nic_attributes:sriov.physnet.value "
"!= 'physnet2'",
'message': "Only \"physnet2\" will be configured "
"by Fuel in Neutron. Configuration of "
"other physical networks is up to "
"Operator or plugin. Fuel will just "
"configure appropriate "
"pci_passthrough_whitelist option in "
"nova.conf for such interface and "
"physical networks.",
'action': "none"
}
]
}
},
'dpdk': {
'enabled': {
'value': False,
'label': 'DPDK enabled',
'type': 'checkbox',
'weight': 10
},
'metadata': {
'label': 'DPDK',
'weight': 40
},
'enabled': {
'label': 'Enable DPDK',
'description': 'The Data Plane Development Kit (DPDK) '
'provides high-performance packet '
'processing libraries and user space '
'drivers.',
'weight': 10,
'type': 'checkbox',
'value': False,
'restrictions': [{
"settings:common.libvirt_type.value != 'kvm'":
"Only KVM hypervisor works with DPDK"}]
}
},
'plugin_a_with_nic_attributes': {

View File

@ -163,20 +163,23 @@ class TestNodeNICsBonding(BaseIntegrationTest):
if iface_props is None:
iface_props = {}
attributes = {
'mode': {'value': {'value': bond_mode}},
'xmit_hash_policy': {
'value': {'value': BOND_XMIT_HASH_POLICY.layer2_3}},
'lacp_rate': {'value': {'value': 'slow'}},
'type__': {'value': bond_type}
}
attributes.update(iface_props)
self.data.append({
"name": bond_name,
"type": NETWORK_INTERFACE_TYPES.bond,
"bond_properties": {
"mode": bond_mode,
"xmit_hash_policy": BOND_XMIT_HASH_POLICY.layer2_3,
"lacp_rate": "slow",
"type__": bond_type
},
"attributes": iface_props,
"slaves": [
{"name": self.other_nic["name"]},
{"name": self.empty_nic["name"]}],
"assigned_networks": self.other_nic["assigned_networks"]
'name': bond_name,
'type': NETWORK_INTERFACE_TYPES.bond,
'attributes': attributes,
'slaves': [
{'name': self.other_nic['name']},
{'name': self.empty_nic['name']}],
'assigned_networks': self.other_nic['assigned_networks']
})
self.other_nic["assigned_networks"] = []
@ -279,8 +282,8 @@ 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['bond_properties']['type__'] = BOND_TYPES.dpdkovs
bond['bond_properties']['mode'] = BOND_MODES.balance_tcp
bond['attributes']['type__']['value'] = BOND_TYPES.dpdkovs
bond['attributes']['mode']['value']['value'] = BOND_MODES.balance_tcp
self.node_nics_put_check_error(
"Bond interface '{0}': DPDK should be enabled for 'dpdkovs' bond"
" type".format(bond_name))
@ -298,7 +301,9 @@ 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['attributes'] = {'dpdk': {'enabled': {'value': True}}}
bond['attributes'] = {
'type__': {'value': BOND_TYPES.linux},
'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))
@ -372,8 +377,8 @@ class TestNodeNICsBonding(BaseIntegrationTest):
"name": 'ovs-bond0',
"type": NETWORK_INTERFACE_TYPES.bond,
"mode": "unknown",
"bond_properties": {
'type__': BOND_TYPES.linux
"attributes": {
'type__': {'value': BOND_TYPES.linux}
},
"slaves": [
{"name": self.other_nic["name"]},
@ -391,8 +396,8 @@ class TestNodeNICsBonding(BaseIntegrationTest):
self.data.append({
"name": 'ovs-bond0',
"type": NETWORK_INTERFACE_TYPES.bond,
"bond_properties": {
'type__': BOND_TYPES.linux
"attributes": {
'type__': {'value': BOND_TYPES.linux}
},
"slaves": [
{"name": self.other_nic["name"]},
@ -409,9 +414,10 @@ class TestNodeNICsBonding(BaseIntegrationTest):
self.data.append({
"name": 'bond0',
"type": NETWORK_INTERFACE_TYPES.bond,
"bond_properties": {
"xmit_hash_policy": BOND_XMIT_HASH_POLICY.layer2_3,
'type__': BOND_TYPES.linux
"attributes": {
'xmit_hash_policy': {
'value': {'value': BOND_XMIT_HASH_POLICY.layer2_3}},
'type__': {'value': BOND_TYPES.linux}
},
"slaves": [
{"name": self.other_nic["name"]},
@ -428,9 +434,9 @@ class TestNodeNICsBonding(BaseIntegrationTest):
self.data.append({
"name": 'bond0',
"type": NETWORK_INTERFACE_TYPES.bond,
"bond_properties": {
"mode": 'unknown',
'type__': BOND_TYPES.linux
"attributes": {
'type__': {'value': BOND_TYPES.linux},
'mode': {'value': {'value': 'unknown'}}
},
"slaves": [
{"name": self.other_nic["name"]},
@ -443,26 +449,6 @@ class TestNodeNICsBonding(BaseIntegrationTest):
"Node '{0}': bond interface 'bond0' has unknown mode "
"'unknown'".format(self.env.nodes[0]["id"]))
def test_nics_bond_create_failed_unknown_property(self):
self.data.append({
"name": 'bond0',
"type": NETWORK_INTERFACE_TYPES.bond,
"bond_properties": {
"mode": BOND_MODES.balance_xor,
'type__': BOND_TYPES.linux,
"policy": BOND_XMIT_HASH_POLICY.layer2_3
},
"slaves": [
{"name": self.other_nic["name"]},
{"name": self.empty_nic["name"]}],
"assigned_networks": self.other_nic["assigned_networks"]
})
self.other_nic["assigned_networks"] = []
self.node_nics_put_check_error(
"Node '{0}', interface 'bond0': unknown bond property "
"'policy'".format(self.env.nodes[0]["id"]))
def test_nics_bond_create_failed_no_slaves(self):
self.data.append({
"name": 'ovs-bond0',
@ -497,8 +483,8 @@ class TestNodeNICsBonding(BaseIntegrationTest):
self.data.append({
"name": 'ovs-bond0',
"type": NETWORK_INTERFACE_TYPES.bond,
"bond_properties": {
"type__": BOND_TYPES.ovs
"attributes": {
"type__": {'value': BOND_TYPES.ovs}
},
"mode": BOND_MODES.balance_slb,
"slaves": [
@ -516,8 +502,8 @@ class TestNodeNICsBonding(BaseIntegrationTest):
self.data.append({
"name": 'ovs-bond0',
"type": NETWORK_INTERFACE_TYPES.bond,
"bond_properties": {
"type__": BOND_TYPES.ovs
"attributes": {
"type__": {'value': BOND_TYPES.ovs}
},
"mode": BOND_MODES.balance_slb,
"slaves": [
@ -537,8 +523,8 @@ class TestNodeNICsBonding(BaseIntegrationTest):
"name": 'ovs-bond0',
"type": NETWORK_INTERFACE_TYPES.bond,
"mode": BOND_MODES.balance_slb,
"bond_properties": {
"type__": BOND_TYPES.ovs
"attributes": {
"type__": {'value': BOND_TYPES.ovs}
},
"slaves": [
{"name": self.other_nic["name"]},
@ -559,8 +545,8 @@ class TestNodeNICsBonding(BaseIntegrationTest):
"name": 'ovs-bond0',
"type": NETWORK_INTERFACE_TYPES.bond,
"mode": BOND_MODES.balance_slb,
"bond_properties": {
"type__": BOND_TYPES.ovs
"attributes": {
"type__": {'value': BOND_TYPES.ovs}
},
"slaves": [
{"name": self.other_nic["name"]},
@ -578,8 +564,8 @@ class TestNodeNICsBonding(BaseIntegrationTest):
self.data.append({
"name": 'ovs-bond0',
"type": NETWORK_INTERFACE_TYPES.bond,
"bond_properties": {
"type__": BOND_TYPES.ovs
"attributes": {
"type__": {'value': BOND_TYPES.ovs}
},
"mode": BOND_MODES.balance_slb,
"slaves": [
@ -598,8 +584,8 @@ class TestNodeNICsBonding(BaseIntegrationTest):
self.data.append({
"name": 'ovs-bond0',
"type": NETWORK_INTERFACE_TYPES.bond,
"bond_properties": {
"type__": BOND_TYPES.ovs
"attributes": {
"type__": {'value': BOND_TYPES.ovs}
},
"mode": BOND_MODES.balance_slb,
"slaves": [
@ -622,8 +608,8 @@ class TestNodeNICsBonding(BaseIntegrationTest):
self.data.append({
"name": 'lnx-bond0',
"type": NETWORK_INTERFACE_TYPES.bond,
"bond_properties": {
"type__": BOND_TYPES.linux
"attributes": {
"type__": {'value': BOND_TYPES.linux}
},
"mode": mode,
"slaves": [
@ -648,8 +634,8 @@ class TestNodeNICsBonding(BaseIntegrationTest):
self.data.append({
"name": 'ovs-bond0',
"type": NETWORK_INTERFACE_TYPES.bond,
"bond_properties": {
"type__": BOND_TYPES.ovs
"attributes": {
"type__": {'value': BOND_TYPES.ovs}
},
"mode": mode,
"slaves": [
@ -673,8 +659,8 @@ class TestNodeNICsBonding(BaseIntegrationTest):
self.data.append({
"name": 'ovs-bond0',
"type": NETWORK_INTERFACE_TYPES.bond,
"bond_properties": {
"type__": BOND_TYPES.ovs
"attributes": {
"type__": {'value': BOND_TYPES.ovs}
},
"mode": mode,
"slaves": [
@ -697,8 +683,8 @@ class TestNodeNICsBonding(BaseIntegrationTest):
self.data.append({
"name": 'ovs-bond0',
"type": NETWORK_INTERFACE_TYPES.bond,
"bond_properties": {
"type__": BOND_TYPES.ovs
"attributes": {
"type__": {'value': BOND_TYPES.ovs}
},
"mode": mode,
"slaves": [
@ -761,8 +747,8 @@ class TestNodeNICsBonding(BaseIntegrationTest):
self.data.append({
"name": 'ovs-bond0',
"type": NETWORK_INTERFACE_TYPES.bond,
"bond_properties": {
"type__": BOND_TYPES.ovs
"attributes": {
"type__": {'value': BOND_TYPES.ovs}
},
"mode": BOND_MODES.balance_slb,
"slaves": [
@ -789,8 +775,8 @@ class TestNodeNICsBonding(BaseIntegrationTest):
self.data.append({
"name": 'ovs-bond0',
"type": NETWORK_INTERFACE_TYPES.bond,
"bond_properties": {
"mode": BOND_MODES.balance_slb
"attributes": {
"mode": {'value': BOND_MODES.balance_slb}
},
"slaves": [
{"name": self.admin_nic["name"]},
@ -799,9 +785,9 @@ class TestNodeNICsBonding(BaseIntegrationTest):
})
self.node_nics_put_check_error(
"Node '{0}', bond interface 'ovs-bond0': doesn't have "
"bond_properties.type__".format(self.env.nodes[0]["id"]))
"attributes.type__".format(self.env.nodes[0]["id"]))
def test_nics_bond_create_failed_without_bond_properties(self):
def test_nics_bond_create_failed_without_attributes(self):
self.data.append({
"name": 'ovs-bond0',
"type": NETWORK_INTERFACE_TYPES.bond,
@ -813,15 +799,15 @@ class TestNodeNICsBonding(BaseIntegrationTest):
})
self.node_nics_put_check_error(
"Node '{0}', bond interface 'ovs-bond0': doesn't have "
"bond_properties".format(self.env.nodes[0]["id"]))
"attributes".format(self.env.nodes[0]["id"]))
def test_nics_bond_create_failed_with_unexpected_type__(self):
self.data.append({
"name": 'ovs-bond0',
"type": NETWORK_INTERFACE_TYPES.bond,
"bond_properties": {
"mode": BOND_MODES.balance_slb,
"type__": 'unexpected_type',
"attributes": {
"mode": {'value': {'value': BOND_MODES.balance_slb}},
"type__": {'value': 'unexpected_type'},
},
"slaves": [
{"name": self.admin_nic["name"]},
@ -844,9 +830,9 @@ class TestNodeNICsBonding(BaseIntegrationTest):
self.data.append({
"name": 'ovs-bond0',
"type": NETWORK_INTERFACE_TYPES.bond,
"bond_properties": {
"mode": BOND_MODES.balance_rr,
"type__": BOND_TYPES.ovs,
"attributes": {
"mode": {'value': {'value': BOND_MODES.balance_rr}},
"type__": {'value': BOND_TYPES.ovs},
},
"slaves": [
{"name": self.admin_nic["name"]},
@ -869,15 +855,18 @@ class TestNodeNICsBonding(BaseIntegrationTest):
class TestBondAttributesDefaultsHandler(BaseIntegrationTest):
EXPECTED_ATTRIBUTES = {
'type__': {
'value': None,
'type': 'hidden'
},
'mode': {
'value': {
'weight': 10,
'type': 'select',
'value': 'balance-rr',
'label': 'Mode',
'values': [
{'data': 'balance-rr', 'label': 'balance-rr'}
]
'value': '',
'label': 'Mode'
},
'metadata': {
'weight': 10,
@ -893,13 +882,13 @@ class TestBondAttributesDefaultsHandler(BaseIntegrationTest):
'weight': 10,
'type': 'checkbox',
'value': False,
'label': 'Disable offloading'
'label': 'Disable Offloading'
},
'modes': {
'weight': 20,
'type': 'offloading_modes',
'value': {},
'label': 'Offloading modes',
'label': 'Offloading Modes',
'description': 'Offloading modes'
}
},
@ -908,6 +897,84 @@ class TestBondAttributesDefaultsHandler(BaseIntegrationTest):
'weight': 30,
'label': 'MTU'
},
'value': {
'weight': 10,
'type': 'number',
'value': None,
'label': 'MTU',
'min': 42,
'max': 65536
}
},
'dpdk': {
'enabled': {
'value': False,
'label': 'Enable DPDK',
'description': 'The Data Plane Development Kit (DPDK) '
'provides high-performance packet processing '
'libraries and user space drivers.',
'type': 'checkbox',
'weight': 10,
'restrictions': [{
"settings:common.libvirt_type.value != 'kvm'":
"Only KVM hypervisor works with DPDK"
}]
},
'metadata': {
'label': 'DPDK',
'weight': 40
}
},
'lacp': {
'metadata': {
'weight': 50,
'label': 'Lacp'
},
'value': {
'weight': 10,
'type': 'select',
'value': '',
'label': 'Lacp'
}
},
'lacp_rate': {
'metadata': {
'weight': 60,
'value': '',
'label': 'Lacp'
}
},
'lacp_rate': {
'metadata': {
'weight': 60,
'label': 'Lacp rate'
},
'value': {
'weight': 10,
'type': 'select',
'value': '',
'label': 'Lacp rate'
}
},
'xmit_hash_policy': {
'metadata': {
'weight': 70,
'label': 'Xmit hash policy'
},
'value': {
'weight': 10,
'type': 'select',
'value': '',
'label': 'Xmit hash policy'
}
},
'plugin_a_with_bond_attributes': {
'plugin_name_text': {
'weight': 25,
'type': 'text',
'value': 'value',
'label': 'label',
},
'value': {
'weight': 10,
'type': 'number',

View File

@ -181,6 +181,22 @@ class TestDPDKValidation(BaseNetAssignmentValidatorTest):
'mode': "active-backup",
'assigned_networks': nets,
'attributes': {
'type__': {
'type': 'hidden',
'value': 'dpdkovs'
},
'mode': {
'metadata': {
'label': "Mode",
'weight': 10
},
'value': {
'label': "Mode",
'weight': 10,
'type': "select",
'value': 'active-backup'
}
},
'offloading': {
'disable': {
'value': True,
@ -225,10 +241,6 @@ class TestDPDKValidation(BaseNetAssignmentValidatorTest):
}
}
},
'bond_properties': {
'mode': "active-backup",
'type__': "dpdkovs"
},
'slaves': [
{'name': nic_2['name']},
{'name': nic_3['name']}

View File

@ -389,28 +389,21 @@ class NetAssignmentValidator(BasicValidator):
"must have name".format(node['id'], iface['name']),
log_message=True
)
if 'bond_properties' not in iface:
if 'attributes' not in iface:
raise errors.InvalidData(
"Node '{0}', bond interface '{1}': doesn't have "
"bond_properties".format(node['id'], iface['name']),
"attributes".format(node['id'], iface['name']),
log_message=True
)
for k in iface['bond_properties']:
if k not in consts.BOND_PROPERTIES:
raise errors.InvalidData(
"Node '{0}', interface '{1}': unknown bond "
"property '{2}'".format(
node['id'], iface['name'], k),
log_message=True
)
if 'type__' not in iface['bond_properties']:
if 'type__' not in iface['attributes']:
raise errors.InvalidData(
"Node '{0}', bond interface '{1}': doesn't have "
"bond_properties.type__".format(
"attributes.type__".format(
node['id'], iface['name']),
log_message=True
)
bond_type = iface['bond_properties']['type__']
bond_type = iface['attributes']['type__']['value']
if bond_type not in consts.BOND_TYPES:
raise errors.InvalidData(
"Node '{0}', interface '{1}': unknown type__ '{2}'. "
@ -487,8 +480,8 @@ class NetAssignmentValidator(BasicValidator):
bond_mode = None
if 'mode' in iface:
bond_mode = iface['mode']
if 'mode' in iface.get('bond_properties', {}):
bond_mode = iface['bond_properties']['mode']
if 'mode' in iface.get('attributes', {}):
bond_mode = iface['attributes']['mode']['value']['value']
return bond_mode
@classmethod
@ -647,6 +640,8 @@ class NetAssignmentValidator(BasicValidator):
if db_iface is None:
db_iface = cls._get_iface_by_name(iface['name'], db_interfaces)
attributes = iface.get('attributes', {})
bond_type = attributes.get('type__', {}).get('value')
if db_iface is None:
# looks like user creates new bond
# let's check every slave in input data
@ -659,21 +654,16 @@ class NetAssignmentValidator(BasicValidator):
hw_available &= objects.NIC.dpdk_available(
slave_iface, dpdk_drivers)
attributes = iface.get('attributes', {})
bond_type = iface.get('bond_properties', {}).get('type__')
else:
if iface['type'] == consts.NETWORK_INTERFACE_TYPES.ether:
iface_cls = objects.NIC
elif iface['type'] == consts.NETWORK_INTERFACE_TYPES.bond:
iface_cls = objects.Bond
bond_type = iface.get('bond_properties', {}).get(
'type__', db_iface.bond_properties.get('type__'))
bond_type = bond_type or db_iface.attributes.get(
'type__', {}).get('value')
hw_available = iface_cls.dpdk_available(db_iface, dpdk_drivers)
attributes = utils.dict_merge(
db_iface.attributes,
iface.get('attributes', {})
)
db_iface.attributes, attributes)
enabled = attributes.get('dpdk', {}).get('enabled', {}).get(
'value', False)

View File

@ -435,8 +435,8 @@
bonding:
availability:
- dpdkovs: "'experimental' in version:feature_groups and interface:pxe == false and
interface:attributes.dpdk.enabled.value and not interface:attributes.sriov.enabled.value"
- linux: "not interface:attributes.sriov.enabled.value"
nic_attributes:dpdk.enabled.value and not nic_attributes:sriov.enabled.value"
- linux: "not nic_attributes:sriov.enabled.value"
properties:
linux:
mode:
@ -2110,14 +2110,13 @@
label: "Offloading"
weight: 10
disable:
label: "Disable offloading"
label: "Disable Offloading"
weight: 10
type: "checkbox"
value: False
modes:
label: "Offloading modes"
label: "Offloading Modes"
weight: 20
description: "Offloading modes"
type: "offloading_modes"
value: {}
mtu:
@ -2129,36 +2128,57 @@
weight: 10
type: "number"
value: null
min: 42
max: 65536
sriov:
metadata:
label: "SR-IOV"
weight: 30
enabled:
label: "SR-IOV enabled"
label: "Enable SR-IOV"
description: 'Single-root I/O Virtualization (SR-IOV) is a specification that, when implemented by a physical PCIe device, enables it to appear as multiple separate PCIe devices. This enables multiple virtualized guests to share direct access to the physical device, offering improved performance over an equivalent virtual device.'
weight: 10
type: "checkbox"
value: False
restrictions:
- "settings:common.libvirt_type.value != 'kvm'": "Only KVM hypervisor works with SR-IOV"
numvfs:
label: "Virtual functions"
label: "Number of Virtual Functions"
weight: 20
type: "number"
min: 0
min: 1
value: null
restrictions:
- "nic_attributes:sriov.enabled.value == false"
physnet:
label: "Physical network"
label: "Physical Network Name"
weight: 30
type: "text"
value: ''
value: ""
regex:
source: "^[A-Za-z0-9 _]*[A-Za-z0-9][A-Za-z0-9 _]*$"
error: "Invalid physical network name"
restrictions:
- "nic_attributes:sriov.enabled.value == false"
- condition: "nic_attributes:sriov.physnet.value != 'physnet2'"
message: "Only \"physnet2\" will be configured by Fuel in Neutron. Configuration of other physical networks is up to Operator or plugin. Fuel will just configure appropriate pci_passthrough_whitelist option in nova.conf for such interface and physical networks."
action: "none"
dpdk:
metadata:
label: "DPDK"
weight: 40
enabled:
label: "DPDK enabled"
label: "Enable DPDK"
description: 'The Data Plane Development Kit (DPDK) provides high-performance packet processing libraries and user space drivers.'
weight: 10
type: "checkbox"
value: False
restrictions:
- "settings:common.libvirt_type.value != 'kvm'": "Only KVM hypervisor works with DPDK"
bond_attributes:
type__:
type: 'hidden'
value: null
mode:
metadata:
label: "Mode"
@ -2167,22 +2187,18 @@
label: "Mode"
weight: 10
type: "select"
values:
-
label: balance-rr
data: balance-rr
value: balance-rr
value: ''
offloading:
metadata:
label: "Offloading"
weight: 20
disable:
label: "Disable offloading"
label: "Disable Offloading"
weight: 10
type: "checkbox"
value: False
modes:
label: "Offloading modes"
label: "Offloading Modes"
weight: 20
description: "Offloading modes"
type: "offloading_modes"
@ -2196,6 +2212,47 @@
weight: 10
type: "number"
value: null
min: 42
max: 65536
dpdk:
metadata:
label: "DPDK"
weight: 40
enabled:
label: "Enable DPDK"
description: 'The Data Plane Development Kit (DPDK) provides high-performance packet processing libraries and user space drivers.'
weight: 10
type: "checkbox"
value: False
restrictions:
- "settings:common.libvirt_type.value != 'kvm'": "Only KVM hypervisor works with DPDK"
lacp:
metadata:
label: "Lacp"
weight: 50
value:
label: "Lacp"
weight: 10
type: "select"
value: ''
lacp_rate:
metadata:
label: "Lacp rate"
weight: 60
value:
label: "Lacp rate"
weight: 10
type: "select"
value: ''
xmit_hash_policy:
metadata:
label: "Xmit hash policy"
weight: 70
value:
label: "Xmit hash policy"
weight: 10
type: "select"
value: ''
modes: ['ha_compact']
extensions: ['volume_manager', 'network_manager']
- pk: 1

View File

@ -1332,7 +1332,7 @@ class Node(NailgunObject):
instance.full_name))
return
return Bond.get_bond_default_attributes(instance.cluster)
return Bond.get_default_attributes(instance.cluster)
@classmethod
def create_nic_attributes(cls, instance):

View File

@ -539,6 +539,7 @@ class NodeNICInterfaceClusterPlugin(BasicNodeClusterPlugin):
cls.model.attributes
).join(
models.ClusterPlugin,
models.Plugin
).filter(
cls.model.interface_id == interface.id
).filter(

View File

@ -500,7 +500,6 @@ 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.
@ -550,7 +549,7 @@ class PluginManager(object):
for plugin in plugins:
metadata = plugin.pop('metadata')
NodeNICInterfaceClusterPlugin.\
NodeBondInterfaceClusterPlugin.\
set_attributes(
metadata['bond_plugin_id'],
plugin

View File

@ -1328,9 +1328,8 @@ class EnvironmentManager(object):
"Nothing to verify - try creating cluster"
)
def make_bond_via_api(self, bond_name, bond_mode, nic_names, node_id=None,
bond_properties=None, interface_properties=None,
attrs=None):
def make_bond_via_api(self, bond_name, bond_mode, nic_names,
node_id=None, attrs=None):
if not node_id:
node_id = self.nodes[0]["id"]
resp = self.app.get(
@ -1361,15 +1360,6 @@ class EnvironmentManager(object):
"assigned_networks": assigned_nets,
"attributes": attrs or {}
}
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

@ -623,9 +623,10 @@ 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})
attrs={
'type__': {'value': consts.BOND_TYPES.linux},
'mode': {
'value': {'value': consts.BOND_MODES.balance_rr}}})
serializer = self.create_serializer(cluster)
facts = serializer.serialize(cluster, cluster.nodes)['nodes']
for node in facts:
@ -671,9 +672,10 @@ 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})
attrs={
'type__': {'value': consts.BOND_TYPES.linux},
'mode': {
'value': {'value': consts.BOND_MODES.balance_rr}}})
serializer = self.create_serializer(cluster)
facts = serializer.serialize(cluster, cluster.nodes)['nodes']
for node in facts:
@ -946,12 +948,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
},
attrs={'mtu': {'value': {'value': 9000}}},
interface_properties={'mtu': 9000}
attrs={
'type__': {'value': consts.BOND_TYPES.linux},
'mtu': {'value': {'value': 9000}},
'mode': {'value': {'value': consts.BOND_MODES.balance_rr}}
}
)
serializer = self.create_serializer(cluster)
facts = serializer.serialize(cluster, cluster.nodes)['nodes']
@ -1092,14 +1093,22 @@ 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.l_802_3ad,
'xmit_hash_policy': consts.BOND_XMIT_HASH_POLICY.layer2,
'lacp_rate': consts.BOND_LACP_RATES.slow,
'type__': consts.BOND_TYPES.linux
},
attrs={'mtu': {'value': {'value': 9000}}},
interface_properties={'mtu': 9000}
attrs={
'mtu': {
'value': {
'value': 9000}},
'mode': {
'value': {
'value': consts.BOND_MODES.l_802_3ad}},
'xmit_hash_policy': {
'value': {
'value': consts.BOND_XMIT_HASH_POLICY.layer2}},
'lacp_rate': {
'value': {
'value': consts.BOND_LACP_RATES.slow}},
'type__': {
'value': consts.BOND_TYPES.linux}
}
)
serializer = self.create_serializer(cluster)
facts = serializer.serialize(cluster, cluster.nodes)['nodes']
@ -2235,13 +2244,9 @@ class TestNeutronOrchestratorSerializerBonds(OrchestratorSerializerTestBase):
def check_bond_with_mode(self, mode, bond_type):
cluster = self.create_env()
for node in cluster.nodes:
self.env.make_bond_via_api('ovsbond0',
mode,
['eth1', 'eth2'],
node.id,
bond_properties={
'type__': bond_type}
)
self.env.make_bond_via_api(
'ovsbond0', mode, ['eth1', 'eth2'], node.id,
attrs={'type__': {'value': bond_type}})
facts = self.serialize(cluster)
for node in facts['nodes']:
transforms = node['network_scheme']['transformations']

View File

@ -202,7 +202,7 @@ class TestDeploymentAttributesSerialization90(
self._check_dpdk_serializing(has_vlan_tag=True)
@mock.patch('nailgun.objects.Release.get_supported_dpdk_drivers')
def _check_dpdk_bond_serializing(self, bond_properties, drivers_mock):
def _check_dpdk_bond_serializing(self, attributes, drivers_mock):
drivers_mock.return_value = {
'driver_1': ['test_id:1', 'test_id:2']
}
@ -237,13 +237,14 @@ class TestDeploymentAttributesSerialization90(
if net['name'] == 'private':
networks_for_bond.append(first_nic_networks.pop(i))
break
attributes.update(
{'dpdk': {'enabled': {'value': True}}})
bond_interface = {
'name': bond_interface_name,
'type': consts.NETWORK_INTERFACE_TYPES.bond,
'slaves': nics_for_bond,
'assigned_networks': networks_for_bond,
'bond_properties': bond_properties,
'attributes': {'dpdk': {'enabled': {'value': True}}}}
'attributes': attributes}
interfaces.append(bond_interface)
self.env.node_nics_put(node.id, interfaces)
objects.Cluster.prepare_for_deployment(self.cluster_db)
@ -271,52 +272,58 @@ class TestDeploymentAttributesSerialization90(
transformations)
self.assertEqual(len(dpdk_bonds), 1)
self.assertEqual(dpdk_bonds[0]['bridge'], br_name)
self.assertEqual(private_br.get('vendor_specific'),
vendor_specific)
self.assertEqual(dpdk_bonds[0].get('provider'),
consts.NEUTRON_L23_PROVIDERS.dpdkovs)
self.assertEqual(dpdk_bonds[0].get('bond_properties').get('mode'),
bond_interface['bond_properties'].get('mode'))
self.assertEqual(
private_br.get('vendor_specific'),
vendor_specific)
self.assertEqual(
dpdk_bonds[0].get('provider'),
consts.NEUTRON_L23_PROVIDERS.dpdkovs)
self.assertEqual(
dpdk_bonds[0].get('bond_properties').get('mode'),
attributes.get('mode', {}).get('value', {}).get('value'))
interfaces = serialised_node['network_scheme']['interfaces']
for iface in nics_for_bond:
dpdk_interface = interfaces[iface['name']]
vendor_specific = dpdk_interface.get('vendor_specific', {})
self.assertEqual(vendor_specific.get('dpdk_driver'), 'driver_1')
def test_serialization_with_dpdk_on_bond(self):
bond_properties = {
'mode': consts.BOND_MODES.balance_slb,
'type__': consts.BOND_TYPES.dpdkovs,
attributes = {
'mode': {'value': {'value': consts.BOND_MODES.balance_slb}},
'type__': {'value': consts.BOND_TYPES.dpdkovs}
}
self._check_dpdk_bond_serializing(bond_properties)
self._check_dpdk_bond_serializing(attributes)
def test_serialization_with_dpdk_on_lacp_bond(self):
bond_properties = {
'mode': consts.BOND_MODES.balance_tcp,
'lacp': 'active',
'lacp_rate': 'fast',
'xmit_hash_policy': 'layer2',
'type__': consts.BOND_TYPES.dpdkovs}
self._check_dpdk_bond_serializing(bond_properties)
attributes = {
'mode': {'value': {'value': consts.BOND_MODES.balance_tcp}},
'lacp': {'value': {'value': 'active'}},
'lacp_rate': {'value': {'value': 'fast'}},
'xmit_hash_policy': {'value': {'value': 'layer2'}},
'type__': {'value': consts.BOND_TYPES.dpdkovs}
}
self._check_dpdk_bond_serializing(attributes)
def test_serialization_with_vxlan_dpdk_on_bond(self):
self._create_cluster_with_vxlan()
bond_properties = {
'mode': consts.BOND_MODES.balance_slb,
'type__': consts.BOND_TYPES.dpdkovs,
attributes = {
'mode': {'value': {'value': consts.BOND_MODES.balance_slb}},
'type__': {'value': consts.BOND_TYPES.dpdkovs}
}
self._check_dpdk_bond_serializing(bond_properties)
self._check_dpdk_bond_serializing(attributes)
def test_serialization_with_vxlan_dpdk_on_lacp_bond(self):
self._create_cluster_with_vxlan()
bond_properties = {
'mode': consts.BOND_MODES.balance_tcp,
'lacp': 'active',
'lacp_rate': 'fast',
'xmit_hash_policy': 'layer2',
'type__': consts.BOND_TYPES.dpdkovs}
self._check_dpdk_bond_serializing(bond_properties)
attributes = {
'mode': {'value': {'value': consts.BOND_MODES.balance_tcp}},
'lacp': {'value': {'value': 'active'}},
'lacp_rate': {'value': {'value': 'fast'}},
'xmit_hash_policy': {'value': {'value': 'layer2'}},
'type__': {'value': consts.BOND_TYPES.dpdkovs}
}
self._check_dpdk_bond_serializing(attributes)
def test_attributes_cpu_pinning(self):
numa_nodes = [

View File

@ -209,14 +209,11 @@ class TestProvisioningSerializer(BaseIntegrationTest):
# get node from db
node_db = objects.Node.get_by_uid(node['id'])
# bond admin iface
self.env.make_bond_via_api('lnx_bond',
'',
['eth1', 'eth4'],
node['id'],
bond_properties={
'mode': consts.BOND_MODES.balance_rr,
'type__': consts.BOND_TYPES.linux
})
self.env.make_bond_via_api(
'lnx_bond', '', ['eth1', 'eth4'], node['id'],
attrs={
'type__': {'value': consts.BOND_TYPES.linux},
'mode': {'value': {'value': consts.BOND_MODES.balance_rr}}})
# check serialized data
serialized_node = ps.serialize(self.cluster_db, [node_db])['nodes'][0]
out_mac = serialized_node['kernel_options']['netcfg/choose_interface']

View File

@ -289,7 +289,7 @@ class TestNetworkVerificationWithBonds(BaseIntegrationTest):
self.env.make_bond_via_api(
"ovs-bond0", consts.BOND_MODES.balance_slb,
[other_nic["name"], empty_nic["name"]], node["id"],
bond_properties={'type__': consts.BOND_TYPES.ovs})
attrs={'type__': {'value': consts.BOND_TYPES.ovs}})
self.verify_bonds(node)
def verify_nics(self, node):

View File

@ -261,7 +261,7 @@ class TestInstallationInfo(BaseTestCase):
self.env.make_bond_via_api(
'bond0', consts.BOND_MODES.active_backup,
['eth1', 'eth2'], node_id=self.env.nodes[0].id,
bond_properties={'type__': consts.BOND_TYPES.linux})
attrs={'type__': {'value': consts.BOND_TYPES.linux}})
nodes_info = info.get_nodes_info(self.env.nodes)
self.assertEquals(len(self.env.nodes), len(nodes_info))
for idx, node in enumerate(self.env.nodes):

View File

@ -485,6 +485,138 @@ class TestNodeNICInterfaceClusterPlugin(ExtraFunctions):
class TestNodeBondInterfaceClusterPlugin(ExtraFunctions):
def test_get_all_attributes_by_bond_with_enabled_plugin(self):
plugin_bond_config = self.env.get_default_plugin_bond_config()
plugin = self.env.create_plugin(
name='plugin_a_with_bond_attributes',
package_version='5.0.0',
bond_attributes_metadata=plugin_bond_config)
cluster = self._create_test_cluster()
node = self.env.create_nodes_w_interfaces_count(
1, 2, **{"cluster_id": cluster.id})[0]
bond_config = {
'type__': {'value': consts.BOND_TYPES.linux},
'mode': {'value': {'value': consts.BOND_MODES.balance_rr}}}
nic_names = [iface.name for iface in node.nic_interfaces]
self.env.make_bond_via_api(
'lnx_bond', '', nic_names, node.id, attrs=bond_config)
bond = node.bond_interfaces[0]
bond_plugin_id = node.node_bond_interface_cluster_plugins[0].id
ClusterPlugin.set_attributes(cluster.id, plugin.id, enabled=True)
attributes = NodeBondInterfaceClusterPlugin.\
get_all_enabled_attributes_by_bond(bond)
expected_attributes = {
'plugin_a_with_bond_attributes': {
'metadata': {
'label': 'Test plugin',
'bond_plugin_id': bond_plugin_id,
'class': 'plugin'}}}
expected_attributes['plugin_a_with_bond_attributes'].update(
plugin_bond_config)
self.assertEqual(expected_attributes, attributes)
def test_get_all_attributes_by_bond_with_disabled_plugin(self):
plugin_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=plugin_bond_config)
cluster = self._create_test_cluster()
node = self.env.create_nodes_w_interfaces_count(
1, 2, **{"cluster_id": cluster.id})[0]
bond_config = {
'type__': {'value': consts.BOND_TYPES.linux},
'mode': {'value': {'value': consts.BOND_MODES.balance_rr}}}
nic_names = [iface.name for iface in node.nic_interfaces]
self.env.make_bond_via_api(
'lnx_bond', '', nic_names, node.id, attrs=bond_config)
bond = node.bond_interfaces[0]
attributes = NodeBondInterfaceClusterPlugin.\
get_all_enabled_attributes_by_bond(bond)
self.assertDictEqual({}, attributes)
def test_populate_bond_with_plugin_attributes(self):
meta = base.reflect_db_metadata()
plugin_bond_config = self.env.get_default_plugin_bond_config()
cluster = self._create_test_cluster(
nodes=[{'roles': ['controller']}, {'roles': ['compute']}])
bond_config = {
'type__': {'value': consts.BOND_TYPES.linux},
'mode': {'value': {'value': consts.BOND_MODES.balance_rr}}}
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, attrs=bond_config)
self.env.create_plugin(
name='plugin_a_with_bond_attributes',
package_version='5.0.0',
bond_attributes_metadata=plugin_bond_config)
node_bond_interface_cluster_plugins = self.db.execute(
meta.tables['node_bond_interface_cluster_plugins'].select()
).fetchall()
self.assertEqual(2, len(node_bond_interface_cluster_plugins))
for item in node_bond_interface_cluster_plugins:
self.assertDictEqual(
plugin_bond_config, jsonutils.loads(item.attributes))
def test_populate_bond_with_empty_plugin_attributes(self):
meta = base.reflect_db_metadata()
cluster = self._create_test_cluster(
nodes=[{'roles': ['controller']}, {'roles': ['compute']}])
bond_config = {
'type__': {'value': consts.BOND_TYPES.linux},
'mode': {'value': {'value': consts.BOND_MODES.balance_rr}}}
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, attrs=bond_config)
self.env.create_plugin(
name='plugin_a_with_bond_attributes',
package_version='5.0.0',
bond_attributes_metadata={})
node_bond_interface_cluster_plugins = self.db.execute(
meta.tables['node_bond_interface_cluster_plugins'].select()
).fetchall()
self.assertEqual(0, len(node_bond_interface_cluster_plugins))
def test_add_cluster_plugin_for_node_bond(self):
meta = base.reflect_db_metadata()
plugin_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=plugin_bond_config)
self.env.create_plugin(
name='plugin_b_with_bond_attributes',
package_version='5.0.0',
bond_attributes_metadata={})
cluster = self._create_test_cluster(
nodes=[{'roles': ['controller']}, {'roles': ['compute']}])
bond_config = {
'type__': {'value': consts.BOND_TYPES.linux},
'mode': {'value': {'value': consts.BOND_MODES.balance_rr}}}
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, attrs=bond_config)
node_bond_interface_cluster_plugins = self.db.execute(
meta.tables['node_bond_interface_cluster_plugins'].select()
).fetchall()
self.assertEqual(2, len(node_bond_interface_cluster_plugins))
for item in node_bond_interface_cluster_plugins:
self.assertDictEqual(
plugin_bond_config, jsonutils.loads(item.attributes))
def test_set_attributes(self):
meta = base.reflect_db_metadata()
bond_config = self.env.get_default_plugin_bond_config()
@ -495,14 +627,13 @@ class TestNodeBondInterfaceClusterPlugin(ExtraFunctions):
cluster = self._create_test_cluster(
nodes=[{'roles': ['controller']}])
bond_config.update({
'type__': {'value': consts.BOND_TYPES.linux},
'mode': {'value': {'value': consts.BOND_MODES.balance_rr}}})
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)
'lnx_bond', '', nic_names, node.id, attrs=bond_config)
node_bond_interface_cluster_plugin = self.db.execute(
meta.tables['node_bond_interface_cluster_plugins'].select()

View File

@ -27,6 +27,7 @@ from nailgun.utils import flatten
from nailgun.utils import get_lines
from nailgun.utils import grouper
from nailgun.utils import parse_bool
from nailgun.utils import remove_key_from_dict
from nailgun.utils import text_format_safe
from nailgun.utils import traverse
@ -151,6 +152,19 @@ class TestUtils(base.BaseIntegrationTest):
self.assertRaises(ValueError, parse_bool, 'tru')
self.assertRaises(ValueError, parse_bool, 'fals')
def test_remove_key_from_dict(self):
input_dict = {
'spam': {
'egg': {
'key_to_remove': 'a'
}
},
'key_to_remove': 'b'
}
output_dict = remove_key_from_dict(input_dict, 'key_to_remove')
self.assertDictEqual({'spam': {'egg': {}}}, output_dict)
class TestTraverse(base.BaseUnitTest):

View File

@ -378,3 +378,23 @@ def is_feature_supported(rel_version, support_version):
return StrictVersion(version) >= StrictVersion(support_version)
except (AttributeError, ValueError):
return False
def remove_key_from_dict(target_dict, key):
"""Recursively remove specific key from dict
:param target_dict: target dict to remove key in
:type target_dict: dict
:param key: key to remove
:type key: string
"returns: dict -- target_dict without key
"""
try:
del target_dict[key]
except KeyError:
pass
for v in target_dict.values():
if isinstance(v, dict):
remove_key_from_dict(v, key)
return target_dict