Browse Source
Adds a new plugin to distinguish PCI devices returned by Ironic Python Agent. Recognized PCI devices are then registered in node capabilities and later can be used by nova flavors. Change-Id: I6565b8c4aa76de240a6c4d795635300ff2d0c30b Partial-Bug: #1580893changes/95/352295/9
8 changed files with 207 additions and 2 deletions
@ -0,0 +1,87 @@
|
||||
# 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. |
||||
|
||||
"""Gather and distinguish PCI devices from inventory.""" |
||||
|
||||
import collections |
||||
import json |
||||
|
||||
from oslo_config import cfg |
||||
|
||||
from ironic_inspector.common.i18n import _LI, _LW, _LE |
||||
from ironic_inspector.plugins import base |
||||
from ironic_inspector import utils |
||||
|
||||
PCI_DEVICES_OPTS = [ |
||||
cfg.MultiStrOpt('alias', |
||||
default=[], |
||||
help='An alias for PCI device identified by \'vendor_id\' ' |
||||
'and \'product_id\' fields. Format: ' |
||||
'{"vendor_id": "1234", "product_id": "5678", "name": ' |
||||
'"pci_dev1"}'), |
||||
] |
||||
|
||||
|
||||
def list_opts(): |
||||
return [ |
||||
('pci_devices', PCI_DEVICES_OPTS) |
||||
] |
||||
|
||||
CONF = cfg.CONF |
||||
CONF.register_opts(PCI_DEVICES_OPTS, group='pci_devices') |
||||
|
||||
LOG = utils.getProcessingLogger(__name__) |
||||
|
||||
|
||||
def _parse_pci_alias_entry(): |
||||
parsed_pci_devices = [] |
||||
for pci_alias_entry in CONF.pci_devices.alias: |
||||
try: |
||||
parsed_entry = json.loads(pci_alias_entry) |
||||
if set(parsed_entry) != {'vendor_id', 'product_id', 'name'}: |
||||
raise KeyError(_LE("The 'alias' entry should contain " |
||||
"exactly 'vendor_id', 'product_id' and " |
||||
"'name' keys")) |
||||
parsed_pci_devices.append(parsed_entry) |
||||
except (ValueError, KeyError) as ex: |
||||
LOG.error(_LE("Error parsing 'alias' option: %s"), ex) |
||||
return {(dev['vendor_id'], dev['product_id']): dev['name'] |
||||
for dev in parsed_pci_devices} |
||||
|
||||
|
||||
class PciDevicesHook(base.ProcessingHook): |
||||
"""Processing hook for counting and distinguishing various PCI devices. |
||||
|
||||
That information can be later used by nova for node scheduling. |
||||
""" |
||||
aliases = _parse_pci_alias_entry() |
||||
|
||||
def _found_pci_devices_count(self, found_pci_devices): |
||||
return collections.Counter([(dev['vendor_id'], dev['product_id']) |
||||
for dev in found_pci_devices |
||||
if (dev['vendor_id'], dev['product_id']) |
||||
in self.aliases]) |
||||
|
||||
def before_update(self, introspection_data, node_info, **kwargs): |
||||
if 'pci_devices' not in introspection_data: |
||||
if CONF.pci_devices.alias: |
||||
LOG.warning(_LW('No PCI devices information was received from ' |
||||
'the ramdisk.')) |
||||
return |
||||
alias_count = {self.aliases[id_pair]: count for id_pair, count in |
||||
self._found_pci_devices_count( |
||||
introspection_data['pci_devices']).items()} |
||||
if alias_count: |
||||
node_info.update_capabilities(**alias_count) |
||||
LOG.info(_LI('Found the following PCI devices: %s'), |
||||
alias_count) |
@ -0,0 +1,102 @@
|
||||
# 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. |
||||
|
||||
import mock |
||||
|
||||
from ironic_inspector import node_cache |
||||
from ironic_inspector.plugins import base |
||||
from ironic_inspector.plugins import pci_devices |
||||
from ironic_inspector.test import base as test_base |
||||
|
||||
|
||||
class TestPciDevicesHook(test_base.NodeTest): |
||||
hook = pci_devices.PciDevicesHook() |
||||
|
||||
def test_parse_pci_alias_entry(self): |
||||
pci_alias = ['{"vendor_id": "foo1", "product_id": "bar1",' |
||||
' "name": "baz1"}', |
||||
'{"vendor_id": "foo2", "product_id": "bar2",' |
||||
' "name": "baz2"}'] |
||||
valid_pci_entry = {("foo1", "bar1"): "baz1", ("foo2", "bar2"): "baz2"} |
||||
base.CONF.set_override('alias', pci_alias, 'pci_devices') |
||||
parsed_pci_entry = pci_devices._parse_pci_alias_entry() |
||||
self.assertDictEqual(valid_pci_entry, parsed_pci_entry) |
||||
|
||||
def test_parse_pci_alias_entry_no_entries(self): |
||||
pci_alias = [] |
||||
base.CONF.set_override('alias', pci_alias, 'pci_devices') |
||||
parsed_pci_alias = pci_devices._parse_pci_alias_entry() |
||||
self.assertFalse(parsed_pci_alias) |
||||
|
||||
@mock.patch('ironic_inspector.plugins.pci_devices.LOG') |
||||
def test_parse_pci_alias_entry_invalid_json(self, mock_oslo_log): |
||||
pci_alias = ['{"vendor_id": "foo1", "product_id": "bar1",' |
||||
' "name": "baz1"}', '{"invalid" = "entry"}'] |
||||
base.CONF.set_override('alias', pci_alias, 'pci_devices') |
||||
valid_pci_alias = {("foo1", "bar1"): "baz1"} |
||||
parsed_pci_alias = pci_devices._parse_pci_alias_entry() |
||||
self.assertDictEqual(valid_pci_alias, parsed_pci_alias) |
||||
mock_oslo_log.error.assert_called_once() |
||||
|
||||
@mock.patch('ironic_inspector.plugins.pci_devices.LOG') |
||||
def test_parse_pci_alias_entry_invalid_keys(self, mock_oslo_log): |
||||
pci_alias = ['{"vendor_id": "foo1", "product_id": "bar1",' |
||||
' "name": "baz1"}', '{"invalid": "keys"}'] |
||||
base.CONF.set_override('alias', pci_alias, 'pci_devices') |
||||
valid_pci_alias = {("foo1", "bar1"): "baz1"} |
||||
parsed_pci_alias = pci_devices._parse_pci_alias_entry() |
||||
self.assertDictEqual(valid_pci_alias, parsed_pci_alias) |
||||
mock_oslo_log.error.assert_called_once() |
||||
|
||||
@mock.patch.object(hook, 'aliases', {("1234", "5678"): "pci_dev1", |
||||
("9876", "5432"): "pci_dev2"}) |
||||
@mock.patch.object(node_cache.NodeInfo, 'update_capabilities', |
||||
autospec=True) |
||||
def test_before_update(self, mock_update_props): |
||||
self.data['pci_devices'] = [ |
||||
{"vendor_id": "1234", "product_id": "5678"}, |
||||
{"vendor_id": "1234", "product_id": "5678"}, |
||||
{"vendor_id": "1234", "product_id": "7890"}, |
||||
{"vendor_id": "9876", "product_id": "5432"} |
||||
] |
||||
expected_pci_devices_count = {"pci_dev1": 2, "pci_dev2": 1} |
||||
self.hook.before_update(self.data, self.node_info) |
||||
mock_update_props.assert_called_once_with(self.node_info, |
||||
**expected_pci_devices_count) |
||||
|
||||
@mock.patch('ironic_inspector.plugins.pci_devices.LOG') |
||||
@mock.patch.object(node_cache.NodeInfo, 'update_capabilities', |
||||
autospec=True) |
||||
def test_before_update_no_pci_info_from_ipa(self, mock_update_props, |
||||
mock_oslo_log): |
||||
pci_alias = ['{"vendor_id": "foo1", "product_id": "bar1",' |
||||
' "name": "baz1"}'] |
||||
base.CONF.set_override('alias', pci_alias, 'pci_devices') |
||||
self.hook.before_update(self.data, self.node_info) |
||||
mock_oslo_log.warning.assert_called_once() |
||||
self.assertFalse(mock_update_props.called) |
||||
|
||||
@mock.patch.object(pci_devices, '_parse_pci_alias_entry') |
||||
@mock.patch('ironic_inspector.plugins.pci_devices.LOG') |
||||
@mock.patch.object(node_cache.NodeInfo, 'update_capabilities', |
||||
autospec=True) |
||||
def test_before_update_no_match(self, mock_update_props, mock_oslo_log, |
||||
mock_parse_pci_alias): |
||||
self.data['pci_devices'] = [ |
||||
{"vendor_id": "1234", "product_id": "5678"}, |
||||
{"vendor_id": "1234", "product_id": "7890"}, |
||||
] |
||||
mock_parse_pci_alias.return_value = {("9876", "5432"): "pci_dev"} |
||||
self.hook.before_update(self.data, self.node_info) |
||||
self.assertFalse(mock_update_props.called) |
||||
self.assertFalse(mock_oslo_log.info.called) |
@ -0,0 +1,7 @@
|
||||
--- |
||||
features: |
||||
- Adds new processing hook pci_devices for setting node |
||||
capabilities based on PCI devices present on a node |
||||
and rules in the [pci_devices] aliases configuration |
||||
option. Requires "pci-devices" collector to be enabled |
||||
in IPA. |
Loading…
Reference in new issue