Merge "Add new introspection commands for interface data including lldp"
This commit is contained in:
commit
79a282a397
|
@ -160,3 +160,34 @@ calls.
|
|||
|
||||
|
||||
.. _introspection rules documentation: http://docs.openstack.org/developer/ironic-inspector/usage.html#introspection-rules
|
||||
|
||||
|
||||
List interface data
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
$ openstack baremetal introspection interface list NODE_IDENT
|
||||
[--fields=<field>] [--vlan=<vlan>]
|
||||
|
||||
* ``NODE_IDENT`` - Ironic node UUID or name
|
||||
* ``fields`` - name of one or more interface columns to display.
|
||||
* ``vlan`` - list only interfaces configured for this vlan id
|
||||
|
||||
Returns a list of interface data, including attached switch information,
|
||||
for each interface on the node.
|
||||
|
||||
Show interface data
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
$ openstack baremetal introspection interface show NODE_IDENT INTERFACE
|
||||
[--fields=<field>]
|
||||
|
||||
* ``NODE_IDENT`` - Ironic node UUID or name
|
||||
* ``INTERFACE`` - interface name on this node
|
||||
* ``fields`` - name of one or more interface rows to display.
|
||||
|
||||
Show interface data, including attached switch information,
|
||||
for a particular node and interface.
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
class InterfaceResource(object):
|
||||
"""InterfaceResource class
|
||||
|
||||
This class is used to manage the fields including Link Layer Discovery
|
||||
Protocols (LLDP) fields, that an interface contains. An individual
|
||||
field consists of a 'field_id' (key) and a 'label' (value).
|
||||
"""
|
||||
|
||||
FIELDS = {
|
||||
'interface': 'Interface',
|
||||
'mac': 'MAC Address',
|
||||
'node_ident': 'Node',
|
||||
'switch_capabilities_enabled': 'Switch Capabilities Enabled',
|
||||
'switch_capabilities_support': 'Switch Capabilities Supported',
|
||||
'switch_chassis_id': 'Switch Chassis ID',
|
||||
'switch_port_autonegotiation_enabled':
|
||||
'Switch Port Autonegotiation Enabled',
|
||||
'switch_port_autonegotiation_support':
|
||||
'Switch Port Autonegotiation Supported',
|
||||
'switch_port_description': 'Switch Port Description',
|
||||
'switch_port_id': 'Switch Port ID',
|
||||
'switch_port_link_aggregation_enabled':
|
||||
'Switch Port Link Aggregation Enabled',
|
||||
'switch_port_link_aggregation_support':
|
||||
'Switch Port Link Aggregation Supported',
|
||||
'switch_port_link_aggregation_id': 'Switch Port Link Aggregation ID',
|
||||
'switch_port_management_vlan_id': 'Switch Port Mgmt VLAN ID',
|
||||
'switch_port_mau_type': 'Switch Port Mau Type',
|
||||
'switch_port_mtu': 'Switch Port MTU',
|
||||
'switch_port_physical_capabilities':
|
||||
'Switch Port Physical Capabilities',
|
||||
'switch_port_protocol_vlan_enabled':
|
||||
'Switch Port Protocol VLAN Enabled',
|
||||
'switch_port_protocol_vlan_support':
|
||||
'Switch Port Protocol VLAN Supported',
|
||||
'switch_port_protocol_vlan_ids': 'Switch Port Protocol VLAN IDs',
|
||||
'switch_port_untagged_vlan_id': 'Switch Port Untagged VLAN',
|
||||
'switch_port_vlans': 'Switch Port VLANs',
|
||||
'switch_port_vlan_ids': 'Switch Port VLAN IDs',
|
||||
'switch_protocol_identities': 'Switch Protocol Identities',
|
||||
'switch_system_name': 'Switch System Name'
|
||||
}
|
||||
|
||||
DEFAULT_FIELD_IDS = ['interface',
|
||||
'mac',
|
||||
'switch_port_vlan_ids',
|
||||
'switch_chassis_id',
|
||||
'switch_port_id']
|
||||
|
||||
def __init__(self, field_ids=None, detailed=False):
|
||||
"""Create an InterfaceResource object
|
||||
|
||||
:param field_ids: A list of strings that the Resource object will
|
||||
contain. Each string must match an existing key in
|
||||
FIELDS.
|
||||
:param detailed: If True, use the all of the keys in FIELDS instead
|
||||
of input field_ids
|
||||
"""
|
||||
if field_ids is None:
|
||||
# Default field set in logical format, so don't sort
|
||||
field_ids = self.DEFAULT_FIELD_IDS
|
||||
|
||||
if detailed:
|
||||
field_ids = sorted(self.FIELDS.keys())
|
||||
|
||||
self._fields = tuple(field_ids)
|
||||
self._labels = tuple(self.FIELDS[x] for x in field_ids)
|
||||
|
||||
@property
|
||||
def fields(self):
|
||||
return self._fields
|
||||
|
||||
@property
|
||||
def labels(self):
|
||||
return self._labels
|
||||
|
||||
|
||||
INTERFACE_DEFAULT = InterfaceResource()
|
|
@ -22,7 +22,7 @@ from osc_lib.command import command
|
|||
from osc_lib import utils
|
||||
|
||||
import ironic_inspector_client
|
||||
|
||||
from ironic_inspector_client import resource as res
|
||||
|
||||
API_NAME = 'baremetal_introspection'
|
||||
API_VERSION_OPTION = 'inspector_api_version'
|
||||
|
@ -282,3 +282,77 @@ class DataSaveCommand(command.Command):
|
|||
fp.write(data)
|
||||
else:
|
||||
json.dump(data, sys.stdout)
|
||||
|
||||
|
||||
class InterfaceListCommand(command.Lister):
|
||||
"""List interface data including attached switch port information."""
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(InterfaceListCommand, self).get_parser(prog_name)
|
||||
parser.add_argument('node_ident', help='baremetal node UUID or name')
|
||||
parser.add_argument("--vlan",
|
||||
action='append',
|
||||
default=[], type=int,
|
||||
help="List only interfaces configured "
|
||||
"for this vlan id, can be repeated")
|
||||
display_group = parser.add_mutually_exclusive_group()
|
||||
display_group.add_argument(
|
||||
'--long', dest='detail',
|
||||
action='store_true', default=False,
|
||||
help="Show detailed information about interfaces.")
|
||||
display_group.add_argument(
|
||||
'--fields', nargs='+', dest='fields',
|
||||
metavar='<field>',
|
||||
choices=sorted(res.InterfaceResource(detailed=True).fields),
|
||||
help="Display one or more fields. "
|
||||
"Can not be used when '--long' is specified")
|
||||
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
|
||||
client = self.app.client_manager.baremetal_introspection
|
||||
|
||||
# If --long defined, use all fields
|
||||
interface_res = res.InterfaceResource(parsed_args.fields,
|
||||
parsed_args.detail)
|
||||
|
||||
rows = client.get_all_interface_data(parsed_args.node_ident,
|
||||
interface_res.fields,
|
||||
vlan=parsed_args.vlan)
|
||||
|
||||
return interface_res.labels, rows
|
||||
|
||||
|
||||
class InterfaceShowCommand(command.ShowOne):
|
||||
"""Show interface data including attached switch port information."""
|
||||
|
||||
COLUMNS = ("Field", "Value")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(InterfaceShowCommand, self).get_parser(prog_name)
|
||||
parser.add_argument('node_ident', help='baremetal node UUID or name')
|
||||
parser.add_argument('interface', help='interface name')
|
||||
parser.add_argument(
|
||||
'--fields', nargs='+', dest='fields',
|
||||
metavar='<field>',
|
||||
choices=sorted(res.InterfaceResource(detailed=True).fields),
|
||||
help="Display one or more fields.")
|
||||
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
|
||||
client = self.app.client_manager.baremetal_introspection
|
||||
|
||||
if parsed_args.fields:
|
||||
interface_res = res.InterfaceResource(parsed_args.fields)
|
||||
else:
|
||||
# Show all fields in detailed resource
|
||||
interface_res = res.InterfaceResource(detailed=True)
|
||||
|
||||
iface_dict = client.get_interface_data(parsed_args.node_ident,
|
||||
parsed_args.interface,
|
||||
interface_res.fields)
|
||||
|
||||
return tuple(zip(*(iface_dict.items())))
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
import eventlet
|
||||
eventlet.monkey_patch()
|
||||
|
||||
import copy
|
||||
import json
|
||||
import mock
|
||||
import os
|
||||
|
@ -331,6 +332,34 @@ class BaseCLITest(functional.Base):
|
|||
|
||||
|
||||
class TestCLI(BaseCLITest):
|
||||
def setup_lldp(self):
|
||||
functional.cfg.CONF.set_override('store_data', 'swift', 'processing')
|
||||
|
||||
self.all_interfaces = {
|
||||
'eth1': {'mac': self.macs[0], 'ip': self.ips[0],
|
||||
'client_id': None, 'lldp_processed':
|
||||
{'switch_chassis_id': "11:22:33:aa:bb:cc",
|
||||
'switch_port_vlans':
|
||||
[{"name": "vlan101", "id": 101},
|
||||
{"name": "vlan102", "id": 102},
|
||||
{"name": "vlan104", "id": 104},
|
||||
{"name": "vlan201", "id": 201},
|
||||
{"name": "vlan203", "id": 203}],
|
||||
'switch_port_id': "554",
|
||||
'switch_port_mtu': 1514}},
|
||||
'eth3': {'mac': self.macs[1], 'ip': None,
|
||||
'client_id': None, 'lldp_processed':
|
||||
{'switch_chassis_id': "11:22:33:aa:bb:cc",
|
||||
'switch_port_vlans':
|
||||
[{"name": "vlan101", "id": 101},
|
||||
{"name": "vlan102", "id": 102},
|
||||
{"name": "vlan104", "id": 106}],
|
||||
'switch_port_id': "557",
|
||||
'switch_port_mtu': 9216}}
|
||||
}
|
||||
|
||||
self.data['all_interfaces'] = self.all_interfaces
|
||||
|
||||
def _fake_status(self, **kwargs):
|
||||
# to remove the hidden fields
|
||||
hidden_status_items = shell.StatusCommand.hidden_status_items
|
||||
|
@ -352,6 +381,9 @@ class TestCLI(BaseCLITest):
|
|||
err = self.run_cli('rule', 'delete', 'uuid', expect_error=True)
|
||||
self.assertIn('not found', err)
|
||||
|
||||
err = self.run_cli('interface', 'list', expect_error=True)
|
||||
self.assertIn('too few arguments', err)
|
||||
|
||||
def test_introspect_get_status(self):
|
||||
self.run_cli('start', self.uuid)
|
||||
eventlet.greenthread.sleep(functional.DEFAULT_SLEEP)
|
||||
|
@ -410,6 +442,62 @@ class TestCLI(BaseCLITest):
|
|||
res = self.run_cli('rule', 'list', parse_json=True)
|
||||
self.assertEqual([], res)
|
||||
|
||||
@mock.patch.object(swift, 'get_introspection_data', autospec=True)
|
||||
def test_interface_list(self, get_mock):
|
||||
self.setup_lldp()
|
||||
get_mock.return_value = json.dumps(copy.deepcopy(self.data))
|
||||
|
||||
expected_eth1 = {u'Interface': u'eth1',
|
||||
u'MAC Address': u'11:22:33:44:55:66',
|
||||
u'Switch Chassis ID': u'11:22:33:aa:bb:cc',
|
||||
u'Switch Port ID': u'554',
|
||||
u'Switch Port VLAN IDs': [101, 102, 104, 201, 203]}
|
||||
expected_eth3 = {u'Interface': u'eth3',
|
||||
u'MAC Address': u'66:55:44:33:22:11',
|
||||
u'Switch Chassis ID': u'11:22:33:aa:bb:cc',
|
||||
u'Switch Port ID': u'557',
|
||||
u'Switch Port VLAN IDs': [101, 102, 106]}
|
||||
|
||||
res = self.run_cli('interface', 'list', self.uuid, parse_json=True)
|
||||
res.sort()
|
||||
self.assertEqual(expected_eth1, res[0])
|
||||
self.assertEqual(expected_eth3, res[1])
|
||||
|
||||
# Filter on vlan
|
||||
res = self.run_cli('interface', 'list', self.uuid, '--vlan', '106',
|
||||
parse_json=True)
|
||||
res.sort()
|
||||
self.assertEqual(expected_eth3, res[0])
|
||||
|
||||
# Select fields
|
||||
res = self.run_cli('interface', 'list', self.uuid, '--fields',
|
||||
'switch_port_mtu',
|
||||
parse_json=True)
|
||||
res.sort()
|
||||
self.assertEqual({u'Switch Port MTU': 1514}, res[0])
|
||||
self.assertEqual({u'Switch Port MTU': 9216}, res[1])
|
||||
|
||||
@mock.patch.object(swift, 'get_introspection_data', autospec=True)
|
||||
def test_interface_show(self, get_mock):
|
||||
self.setup_lldp()
|
||||
get_mock.return_value = json.dumps(copy.deepcopy(self.data))
|
||||
|
||||
res = self.run_cli('interface', 'show', self.uuid, "eth1",
|
||||
parse_json=True)
|
||||
|
||||
expected = {u'interface': u'eth1',
|
||||
u'mac': u'11:22:33:44:55:66',
|
||||
u'switch_chassis_id': u'11:22:33:aa:bb:cc',
|
||||
u'switch_port_id': u'554',
|
||||
u'switch_port_mtu': 1514,
|
||||
u'switch_port_vlan_ids': [101, 102, 104, 201, 203],
|
||||
u'switch_port_vlans': [{u'id': 101, u'name': u'vlan101'},
|
||||
{u'id': 102, u'name': u'vlan102'},
|
||||
{u'id': 104, u'name': u'vlan104'},
|
||||
{u'id': 201, u'name': u'vlan201'},
|
||||
{u'id': 203, u'name': u'vlan203'}]}
|
||||
|
||||
self.assertDictContainsSubset(expected, res)
|
||||
|
||||
if __name__ == '__main__':
|
||||
with functional.mocked_server():
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
import sys
|
||||
|
||||
from collections import OrderedDict
|
||||
import mock
|
||||
from osc_lib.tests import utils
|
||||
import six
|
||||
|
@ -351,3 +352,204 @@ class TestDataSave(BaseTest):
|
|||
|
||||
self.assertEqual(b'{"answer": 42}', content)
|
||||
self.client.get_data.assert_called_once_with('uuid1', raw=True)
|
||||
|
||||
|
||||
class TestInterfaceCmds(BaseTest):
|
||||
def setUp(self):
|
||||
super(TestInterfaceCmds, self).setUp()
|
||||
|
||||
self.inspector_db = {
|
||||
"all_interfaces":
|
||||
{
|
||||
'em1': {'mac': "00:11:22:33:44:55", 'ip': "10.10.1.1",
|
||||
"lldp_processed": {
|
||||
"switch_chassis_id": "99:aa:bb:cc:dd:ff",
|
||||
"switch_port_id": "555",
|
||||
"switch_port_vlans":
|
||||
[{"id": 101, "name": "vlan101"},
|
||||
{"id": 102, "name": "vlan102"},
|
||||
{"id": 104, "name": "vlan104"},
|
||||
{"id": 201, "name": "vlan201"},
|
||||
{"id": 203, "name": "vlan203"}],
|
||||
"switch_port_mtu": 1514
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def test_list(self):
|
||||
self.client.get_all_interface_data.return_value = [
|
||||
["em1", "00:11:22:33:44:55", [101, 102, 104, 201, 203],
|
||||
"99:aa:bb:cc:dd:ff", "555"],
|
||||
["em2", "00:11:22:66:77:88", [201, 203],
|
||||
"99:aa:bb:cc:dd:ff", "777"],
|
||||
["em3", "00:11:22:aa:bb:cc", '', '', '']]
|
||||
|
||||
arglist = ['uuid1']
|
||||
verifylist = [('node_ident', 'uuid1')]
|
||||
|
||||
cmd = shell.InterfaceListCommand(self.app, None)
|
||||
parsed_args = self.check_parser(cmd, arglist, verifylist)
|
||||
cols, values = cmd.take_action(parsed_args)
|
||||
|
||||
expected_cols = ("Interface", "MAC Address", "Switch Port VLAN IDs",
|
||||
"Switch Chassis ID", "Switch Port ID")
|
||||
|
||||
# Note that em3 has no lldp data
|
||||
expected_rows = [["em1", "00:11:22:33:44:55",
|
||||
[101, 102, 104, 201, 203],
|
||||
"99:aa:bb:cc:dd:ff",
|
||||
"555"],
|
||||
["em2", "00:11:22:66:77:88",
|
||||
[201, 203],
|
||||
"99:aa:bb:cc:dd:ff",
|
||||
"777"],
|
||||
["em3", "00:11:22:aa:bb:cc", '', '', '']]
|
||||
|
||||
self.assertEqual(expected_cols, cols)
|
||||
self.assertEqual(expected_rows, values)
|
||||
|
||||
def test_list_field(self):
|
||||
self.client.get_all_interface_data.return_value = [
|
||||
["em1", 1514],
|
||||
["em2", 9216],
|
||||
["em3", '']]
|
||||
|
||||
arglist = ['uuid1', '--fields', 'interface',
|
||||
"switch_port_mtu"]
|
||||
verifylist = [('node_ident', 'uuid1'),
|
||||
('fields', ["interface", "switch_port_mtu"])]
|
||||
|
||||
cmd = shell.InterfaceListCommand(self.app, None)
|
||||
parsed_args = self.check_parser(cmd, arglist, verifylist)
|
||||
cols, values = cmd.take_action(parsed_args)
|
||||
|
||||
expected_cols = ("Interface", "Switch Port MTU")
|
||||
expected_rows = [["em1", 1514],
|
||||
["em2", 9216],
|
||||
["em3", '']]
|
||||
|
||||
self.assertEqual(expected_cols, cols)
|
||||
self.assertEqual(expected_rows, values)
|
||||
|
||||
def test_list_filtered(self):
|
||||
self.client.get_all_interface_data.return_value = [
|
||||
["em1",
|
||||
"00:11:22:33:44:55",
|
||||
[101, 102, 104, 201, 203],
|
||||
"99:aa:bb:cc:dd:ff",
|
||||
"555"]]
|
||||
|
||||
arglist = ['uuid1', '--vlan', '104']
|
||||
verifylist = [('node_ident', 'uuid1'),
|
||||
('vlan', [104])]
|
||||
|
||||
cmd = shell.InterfaceListCommand(self.app, None)
|
||||
parsed_args = self.check_parser(cmd, arglist, verifylist)
|
||||
cols, values = cmd.take_action(parsed_args)
|
||||
|
||||
expected_cols = ("Interface", "MAC Address", "Switch Port VLAN IDs",
|
||||
"Switch Chassis ID", "Switch Port ID")
|
||||
expected_rows = [["em1", "00:11:22:33:44:55",
|
||||
[101, 102, 104, 201, 203],
|
||||
"99:aa:bb:cc:dd:ff",
|
||||
"555"]]
|
||||
|
||||
self.assertEqual(expected_cols, cols)
|
||||
self.assertEqual(expected_rows, values)
|
||||
|
||||
def test_list_no_data(self):
|
||||
self.client.get_all_interface_data.return_value = [[]]
|
||||
|
||||
arglist = ['uuid1']
|
||||
verifylist = [('node_ident', 'uuid1')]
|
||||
|
||||
cmd = shell.InterfaceListCommand(self.app, None)
|
||||
parsed_args = self.check_parser(cmd, arglist, verifylist)
|
||||
cols, values = cmd.take_action(parsed_args)
|
||||
|
||||
expected_cols = ("Interface", "MAC Address", "Switch Port VLAN IDs",
|
||||
"Switch Chassis ID", "Switch Port ID")
|
||||
expected_rows = [[]]
|
||||
|
||||
self.assertEqual(expected_cols, cols)
|
||||
self.assertEqual(expected_rows, values)
|
||||
|
||||
def test_show(self):
|
||||
self.client.get_data.return_value = self.inspector_db
|
||||
|
||||
data = OrderedDict(
|
||||
[('node_ident', "uuid1"),
|
||||
('interface', "em1"),
|
||||
('mac', "00:11:22:33:44:55"),
|
||||
('switch_chassis_id', "99:aa:bb:cc:dd:ff"),
|
||||
('switch_port_id', "555"),
|
||||
('switch_port_mtu', 1514),
|
||||
('switch_port_vlans',
|
||||
[{"id": 101, "name": "vlan101"},
|
||||
{"id": 102, "name": "vlan102"},
|
||||
{"id": 104, "name": "vlan104"},
|
||||
{"id": 201, "name": "vlan201"},
|
||||
{"id": 203, "name": "vlan203"}])]
|
||||
)
|
||||
|
||||
self.client.get_interface_data.return_value = data
|
||||
|
||||
arglist = ['uuid1', 'em1']
|
||||
verifylist = [('node_ident', 'uuid1'), ('interface', 'em1')]
|
||||
|
||||
cmd = shell.InterfaceShowCommand(self.app, None)
|
||||
parsed_args = self.check_parser(cmd, arglist, verifylist)
|
||||
cols, values = cmd.take_action(parsed_args)
|
||||
|
||||
expected_cols = ("node_ident", "interface", "mac",
|
||||
"switch_chassis_id", "switch_port_id",
|
||||
"switch_port_mtu", "switch_port_vlans")
|
||||
|
||||
expected_rows = ("uuid1", "em1", "00:11:22:33:44:55",
|
||||
"99:aa:bb:cc:dd:ff", "555", 1514,
|
||||
[{"id": 101, "name": "vlan101"},
|
||||
{"id": 102, "name": "vlan102"},
|
||||
{"id": 104, "name": "vlan104"},
|
||||
{"id": 201, "name": "vlan201"},
|
||||
{"id": 203, "name": "vlan203"}])
|
||||
|
||||
self.assertEqual(expected_cols, cols)
|
||||
self.assertEqual(expected_rows, values)
|
||||
|
||||
def test_show_field(self):
|
||||
self.client.get_data.return_value = self.inspector_db
|
||||
|
||||
data = OrderedDict([('node_ident', "uuid1"),
|
||||
('interface', "em1"),
|
||||
('switch_port_vlans',
|
||||
[{"id": 101, "name": "vlan101"},
|
||||
{"id": 102, "name": "vlan102"},
|
||||
{"id": 104, "name": "vlan104"},
|
||||
{"id": 201, "name": "vlan201"},
|
||||
{"id": 203, "name": "vlan203"}])
|
||||
])
|
||||
|
||||
self.client.get_interface_data.return_value = data
|
||||
|
||||
arglist = ['uuid1', 'em1', '--fields', 'node_ident', 'interface',
|
||||
"switch_port_vlans"]
|
||||
verifylist = [('node_ident', 'uuid1'), ('interface', 'em1'),
|
||||
('fields', ["node_ident", "interface",
|
||||
"switch_port_vlans"])]
|
||||
|
||||
cmd = shell.InterfaceShowCommand(self.app, None)
|
||||
parsed_args = self.check_parser(cmd, arglist, verifylist)
|
||||
cols, values = cmd.take_action(parsed_args)
|
||||
|
||||
expected_cols = ("node_ident", "interface", "switch_port_vlans")
|
||||
|
||||
expected_rows = ("uuid1", "em1",
|
||||
[{"id": 101, "name": "vlan101"},
|
||||
{"id": 102, "name": "vlan102"},
|
||||
{"id": 104, "name": "vlan104"},
|
||||
{"id": 201, "name": "vlan201"},
|
||||
{"id": 203, "name": "vlan203"}])
|
||||
|
||||
self.assertEqual(expected_cols, cols)
|
||||
self.assertEqual(expected_rows, values)
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from collections import OrderedDict
|
||||
import six
|
||||
import unittest
|
||||
|
||||
|
@ -325,3 +326,122 @@ class TestAbort(BaseTest):
|
|||
|
||||
def test_invalid_input(self, _):
|
||||
self.assertRaises(TypeError, self.get_client().abort, 42)
|
||||
|
||||
|
||||
@mock.patch.object(http.BaseClient, 'request')
|
||||
class TestInterfaceApi(BaseTest):
|
||||
def setUp(self):
|
||||
super(TestInterfaceApi, self).setUp()
|
||||
|
||||
self.inspector_db = {
|
||||
"all_interfaces": {
|
||||
'em1': {'mac': "00:11:22:33:44:55", 'ip': "10.10.1.1",
|
||||
"lldp_processed": {
|
||||
"switch_chassis_id": "99:aa:bb:cc:dd:ff",
|
||||
"switch_port_id": "555",
|
||||
"switch_port_vlans":
|
||||
[{"id": 101, "name": "vlan101"},
|
||||
{"id": 102, "name": "vlan102"},
|
||||
{"id": 104, "name": "vlan104"},
|
||||
{"id": 201, "name": "vlan201"},
|
||||
{"id": 203, "name": "vlan203"}],
|
||||
"switch_port_mtu": 1514}
|
||||
},
|
||||
'em2': {'mac': "00:11:22:66:77:88", 'ip': "10.10.1.2",
|
||||
"lldp_processed": {
|
||||
"switch_chassis_id": "99:aa:bb:cc:dd:ff",
|
||||
"switch_port_id": "777",
|
||||
"switch_port_vlans":
|
||||
[{"id": 201, "name": "vlan201"},
|
||||
{"id": 203, "name": "vlan203"}],
|
||||
"switch_port_mtu": 9216}
|
||||
},
|
||||
'em3': {'mac': "00:11:22:aa:bb:cc", 'ip': "10.10.1.2"}
|
||||
}
|
||||
}
|
||||
|
||||
def test_all_interfaces(self, mock_req):
|
||||
mock_req.return_value.json.return_value = self.inspector_db
|
||||
|
||||
fields = ['interface', 'mac', 'switch_chassis_id', 'switch_port_id',
|
||||
'switch_port_vlans']
|
||||
expected = [['em1', '00:11:22:33:44:55', '99:aa:bb:cc:dd:ff', '555',
|
||||
[{"id": 101, "name": "vlan101"},
|
||||
{"id": 102, "name": "vlan102"},
|
||||
{"id": 104, "name": "vlan104"},
|
||||
{"id": 201, "name": "vlan201"},
|
||||
{"id": 203, "name": "vlan203"}]],
|
||||
['em2', '00:11:22:66:77:88', '99:aa:bb:cc:dd:ff', '777',
|
||||
[{"id": 201, "name": "vlan201"},
|
||||
{"id": 203, "name": "vlan203"}]],
|
||||
['em3', '00:11:22:aa:bb:cc', None, None, None]]
|
||||
|
||||
actual = self.get_client().get_all_interface_data(self.uuid,
|
||||
fields)
|
||||
|
||||
self.assertEqual(sorted(expected), sorted(actual))
|
||||
|
||||
# Change fields
|
||||
fields = ['interface', 'switch_port_mtu']
|
||||
expected = [
|
||||
['em1', 1514],
|
||||
['em2', 9216],
|
||||
['em3', None]]
|
||||
|
||||
actual = self.get_client().get_all_interface_data(self.uuid, fields)
|
||||
self.assertEqual(expected, sorted(actual))
|
||||
|
||||
def test_all_interfaces_filtered(self, mock_req):
|
||||
mock_req.return_value.json.return_value = self.inspector_db
|
||||
|
||||
fields = ['interface', 'mac', 'switch_chassis_id', 'switch_port_id',
|
||||
'switch_port_vlan_ids']
|
||||
expected = [['em1', '00:11:22:33:44:55', '99:aa:bb:cc:dd:ff', '555',
|
||||
[101, 102, 104, 201, 203]]]
|
||||
|
||||
# Filter on expected VLAN
|
||||
vlan = [104]
|
||||
actual = self.get_client().get_all_interface_data(self.uuid,
|
||||
fields, vlan=vlan)
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
# VLANs don't match existing vlans
|
||||
vlan = [111, 555]
|
||||
actual = self.get_client().get_all_interface_data(self.uuid,
|
||||
fields, vlan=vlan)
|
||||
self.assertEqual([], actual)
|
||||
|
||||
def test_one_interface(self, mock_req):
|
||||
mock_req.return_value.json.return_value = self.inspector_db
|
||||
|
||||
# Note that a value for 'switch_foo' will not be found
|
||||
fields = ["node_ident", "interface", "mac", "switch_port_vlan_ids",
|
||||
"switch_chassis_id", "switch_port_id",
|
||||
"switch_port_mtu", "switch_port_vlans", "switch_foo"]
|
||||
|
||||
expected_values = OrderedDict(
|
||||
[('node_ident', self.uuid),
|
||||
('interface', "em1"),
|
||||
('mac', "00:11:22:33:44:55"),
|
||||
('switch_port_vlan_ids',
|
||||
[101, 102, 104, 201, 203]),
|
||||
('switch_chassis_id', "99:aa:bb:cc:dd:ff"),
|
||||
('switch_port_id', "555"),
|
||||
('switch_port_mtu', 1514),
|
||||
('switch_port_vlans',
|
||||
[{"id": 101, "name": "vlan101"},
|
||||
{"id": 102, "name": "vlan102"},
|
||||
{"id": 104, "name": "vlan104"},
|
||||
{"id": 201, "name": "vlan201"},
|
||||
{"id": 203, "name": "vlan203"}]),
|
||||
("switch_foo", None)])
|
||||
|
||||
iface_dict = self.get_client().get_interface_data(
|
||||
self.uuid, "em1", fields)
|
||||
self.assertEqual(expected_values, iface_dict)
|
||||
|
||||
# Test interface name not in 'all_interfaces'
|
||||
expected_values = OrderedDict()
|
||||
iface_dict = self.get_client().get_interface_data(
|
||||
self.uuid, "em55", fields)
|
||||
self.assertEqual(expected_values, iface_dict)
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
"""Client for V1 API."""
|
||||
|
||||
from collections import OrderedDict
|
||||
import logging
|
||||
import time
|
||||
|
||||
|
@ -274,6 +275,84 @@ class ClientV1(http.BaseClient):
|
|||
|
||||
return self.request('post', '/introspection/%s/abort' % uuid)
|
||||
|
||||
def get_interface_data(self, node_ident, interface, field_sel):
|
||||
"""Get interface data for the input node and interface
|
||||
|
||||
To get LLDP data, collection must be enabled by the kernel parameter
|
||||
ipa-collect-lldp=1, and the inspector plugin ``basic_lldp`` must
|
||||
be enabled.
|
||||
|
||||
:param node_ident: node UUID or name
|
||||
:param interface: interface name
|
||||
:param field_sel: list of all fields for which to get data
|
||||
:returns interface data in OrderedDict
|
||||
"""
|
||||
# Use OrderedDict to maintain order of user-entered fields
|
||||
iface_data = OrderedDict()
|
||||
|
||||
data = self.get_data(node_ident)
|
||||
all_interfaces = data.get('all_interfaces', [])
|
||||
|
||||
# Make sure interface name is valid
|
||||
if interface not in all_interfaces:
|
||||
return iface_data
|
||||
|
||||
# If lldp data not available this will still return interface,
|
||||
# mac, node_ident etc.
|
||||
lldp_proc = all_interfaces[interface].get('lldp_processed', {})
|
||||
|
||||
for f in field_sel:
|
||||
if f == 'node_ident':
|
||||
iface_data[f] = node_ident
|
||||
elif f == 'interface':
|
||||
iface_data[f] = interface
|
||||
elif f == 'mac':
|
||||
iface_data[f] = all_interfaces[interface].get(f)
|
||||
elif f == 'switch_port_vlan_ids':
|
||||
iface_data[f] = [item['id'] for item in
|
||||
lldp_proc.get('switch_port_vlans', [])]
|
||||
else:
|
||||
iface_data[f] = lldp_proc.get(f)
|
||||
|
||||
return iface_data
|
||||
|
||||
def get_all_interface_data(self, node_ident,
|
||||
field_sel, vlan=None):
|
||||
"""Get interface data for all of the interfaces on this node
|
||||
|
||||
:param node_ident: node UUID or name
|
||||
:param field_sel: list of all fields for which to get data
|
||||
:param vlan: list of vlans used to filter the lists returned
|
||||
:returns list of interface data, each interface in a list
|
||||
"""
|
||||
|
||||
# Get inventory data for this node
|
||||
data = self.get_data(node_ident)
|
||||
all_interfaces = data.get('all_interfaces', [])
|
||||
|
||||
rows = []
|
||||
if vlan:
|
||||
vlan = set(vlan)
|
||||
# walk all interfaces, appending data to row if not filtered
|
||||
for interface in all_interfaces:
|
||||
iface_dict = self.get_interface_data(node_ident,
|
||||
interface,
|
||||
field_sel)
|
||||
|
||||
values = list(iface_dict.values())
|
||||
|
||||
# Use (optional) vlans to filter row
|
||||
if not vlan:
|
||||
rows.append(values)
|
||||
continue
|
||||
|
||||
# curr_vlans may be None
|
||||
curr_vlans = iface_dict.get('switch_port_vlan_ids', [])
|
||||
if curr_vlans and (vlan & set(curr_vlans)):
|
||||
rows.append(values) # vlan matches, display this row
|
||||
|
||||
return rows
|
||||
|
||||
|
||||
class RulesAPI(object):
|
||||
"""Introspection rules API.
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
features:
|
||||
- Add ``introspection interface list`` and ``introspection interface show``
|
||||
commands to display stored introspection data for the node including
|
||||
attached switch port information. In order to get switch data,
|
||||
LLDP data collection must be enabled by the kernel parameter
|
||||
``ipa-collect-lldp=1`` and the inspector plugin ``basic_lldp`` must
|
||||
be enabled.
|
|
@ -34,6 +34,8 @@ openstack.baremetal_introspection.v1 =
|
|||
baremetal_introspection_rule_show = ironic_inspector_client.shell:RuleShowCommand
|
||||
baremetal_introspection_rule_delete = ironic_inspector_client.shell:RuleDeleteCommand
|
||||
baremetal_introspection_rule_purge = ironic_inspector_client.shell:RulePurgeCommand
|
||||
baremetal_introspection_interface_list = ironic_inspector_client.shell:InterfaceListCommand
|
||||
baremetal_introspection_interface_show = ironic_inspector_client.shell:InterfaceShowCommand
|
||||
|
||||
[pbr]
|
||||
autodoc_index_modules = True
|
||||
|
|
Loading…
Reference in New Issue