Add new introspection commands for interface data including lldp

This adds two new baremetal introspection commands to display
interface data, including switch information from lldp packets, as
described in the lldp-reporting.rst specification
https://specs.openstack.org/openstack/ironic-inspector-specs/
specs/lldp-reporting.html.

The new commands are "interface list <uuid>" and "interface show
<uuid> <interface>".  The list command will show specific interface
fields for all interfaces in a node; additional fields can be
displayed using the "--fields" argument.  The show command will
show all interface fields for a particular node and interface.

Change-Id: I40d348c4fefffb85affb14f3d224a8d4bf4fb297
Closes-Bug: 1626253
Related-Bug: 1647515
Depends-On: I854826787ff045ffb2807970deaba8b77cbe277d
This commit is contained in:
Bob Fournier 2017-01-11 16:11:48 -05:00
parent f3163834fd
commit e4513c9b83
9 changed files with 698 additions and 1 deletions

View File

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

View File

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

View File

@ -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'
@ -281,3 +281,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())))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -35,6 +35,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