Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ packages =
ironic.inspection.hooks =
ib_physnet = stackhpc_inspector_plugins.plugins.ib_physnet:IBPhysnetHook
system_name_physnet = stackhpc_inspector_plugins.plugins.ib_physnet:SystemNamePhysnetHook
port_groups = stackhpc_inspector_plugins.plugins.port_group:PortGroupsHook
14 changes: 14 additions & 0 deletions stackhpc_inspector_plugins/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,25 @@
'system name.')),
]

PORT_GROUP_OPTS = [
cfg.ListOpt(
'port_group_switches',
default=[],
help=('A list of switch names, where two ports are connected to '
'switches in this list, those ports will be added to a port '
'group.')),
cfg.StrOpt(
'port_group_mode',
default='active-backup',
help=('The port group mode to use when creating port groups.')),
]

cfg.CONF.register_opts(PORT_PHYSNET_OPTS, group='port_physnet')
cfg.CONF.register_opts(PORT_GROUP_OPTS, group='port_group')


def list_opts():
return [
('port_physnet', PORT_PHYSNET_OPTS),
('port_group', PORT_GROUP_OPTS),
]
89 changes: 89 additions & 0 deletions stackhpc_inspector_plugins/plugins/port_group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# 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.

from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import uuidutils

from ironic.drivers.modules.inspector.hooks import base
from ironic import objects

CONF = cfg.CONF
LOG = logging.getLogger(__name__)


class PortGroupsHook(base.InspectionHook):
"""Hook to set add port groups based on switch name"""

dependencies = ['validate-interfaces']

def __call__(self, task, inventory, plugin_data):
"""Process inspection data and patch the port's physical network."""

node_port_groups = objects.Portgroup.list_by_node_id(
task.context, task.node.id)
if len(node_port_groups) > 0:
LOG.debug("Node %s already has port groups defined, skipping "
"port group creation.", task.node.uuid)
return

candidate_ports = []
node_ports = objects.Port.list_by_node_id(task.context, task.node.id)
for port in node_ports:
if not port.local_link_connection:
LOG.debug("Port %s has no local link connection data, "
"skipping.", port.uuid)
continue

switch_name = port.local_link_connection.get('switch_info', {})
if not switch_name:
LOG.debug("Port %s has no switch info in local link "
"connection data, skipping.", port.uuid)
continue

if switch_name not in CONF.port_group.port_group_switches:
LOG.debug("Port %s connected to switch %s which is not in "
"the configured port group switches, skipping.",
port.uuid, switch_name)
continue

candidate_ports.append(port)

if len(candidate_ports) != 2:
LOG.debug("Found %d candidate ports for port group creation on "
"node %s, need exactly 2, skipping port group "
"creation.", len(candidate_ports), task.node.uuid)
return

# sort list by mac address
candidate_ports.sort(key=lambda p: p.address)

# create port group
new_port_group = objects.Portgroup(
task.context,
uuid=uuidutils.generate_uuid(),
mode=CONF.port_group.port_group_mode,
node_id=task.node.id,
address=candidate_ports[0].address,
standalone_ports_supported=True,
physical_network=candidate_ports[0].physical_network)
new_port_group.create()
new_port_group.refresh()
LOG.info("Created port group %s on node %s",
new_port_group.uuid, task.node.uuid)

for port in candidate_ports:
port.portgroup_id = new_port_group.id
port.save()
LOG.info("Added port %s to port group %s",
port.uuid, new_port_group.uuid)
142 changes: 142 additions & 0 deletions stackhpc_inspector_plugins/tests/unit/test_plugins_port_group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# 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.

from unittest import mock

from ironic.conductor import task_manager
from ironic import objects
from ironic.tests.unit.db import base as db_base
from ironic.tests.unit.objects import utils as obj_utils
from ironic.conf import CONF

from stackhpc_inspector_plugins.plugins import port_group


class TestPortGroupHook(db_base.DbTestCase):
def setUp(self):
super().setUp()
CONF.set_override('enabled_inspect_interfaces',
['agent', 'no-inspect'])
self.node = obj_utils.create_test_node(self.context,
inspect_interface='agent')

@mock.patch.object(objects.Port, 'list_by_node_id', autospec=True)
def test_port_group_works(self, mock_list_by_nodeid):
CONF.set_override('port_group_switches',
['my_switch_1', 'my_switch_2'],
group='port_group')
with task_manager.acquire(self.context, self.node.id) as task:
port1 = obj_utils.create_test_port(self.context,
address='11:11:11:11:11:11',
node_id=self.node.id)
port2 = obj_utils.create_test_port(
self.context, id=988,
uuid='2be26c0b-03f2-4d2e-ae87-c02d7f33c781',
address='22:22:22:22:22:22', node_id=self.node.id,
local_link_connection={
'switch_info': 'my_switch_1',
})
port3 = obj_utils.create_test_port(
self.context, id=989,
uuid='2be26c0b-03f2-4d2e-ae87-c02d7f33c784',
address='22:22:22:22:22:23', node_id=self.node.id,
local_link_connection={
'switch_info': 'my_switch_2',
})
port4 = obj_utils.create_test_port(
self.context, id=990,
uuid='2be26c0b-03f2-4d2e-ae87-c02d7f33c786',
address='22:22:22:22:22:24', node_id=self.node.id,
local_link_connection={
'switch_info': 'my_switch_3',
})
port5 = obj_utils.create_test_port(
self.context, id=991,
uuid='2be26c0b-03f2-4d2e-ae87-c02d7f33c788',
address='22:22:22:22:22:25', node_id=self.node.id,
local_link_connection={
'foo': 'bar',
})
ports = [port1, port2, port3, port4, port5]
mock_list_by_nodeid.return_value = ports

port_group.PortGroupsHook().__call__(
task, {}, {})

port1.refresh()
port2.refresh()
port3.refresh()
self.assertEqual(port1.portgroup_id, None)

new_port_groups = objects.Portgroup.list_by_node_id(
self.context, self.node.id)
self.assertEqual(len(new_port_groups), 1)
new_port_group = new_port_groups[0]

self.assertEqual(port2.portgroup_id, new_port_group.id)
self.assertEqual(port3.portgroup_id, new_port_group.id)
self.assertEqual(port2.address, new_port_group.address)

@mock.patch.object(objects.Port, 'list_by_node_id', autospec=True)
def test_port_group_skip_three_ports(self, mock_list_by_nodeid):
CONF.set_override('port_group_switches',
['my_switch_1', 'my_switch_2'],
group='port_group')
with task_manager.acquire(self.context, self.node.id) as task:
port1 = obj_utils.create_test_port(self.context,
address='11:11:11:11:11:11',
node_id=self.node.id)
port2 = obj_utils.create_test_port(
self.context, id=988,
uuid='2be26c0b-03f2-4d2e-ae87-c02d7f33c781',
address='22:22:22:22:22:22', node_id=self.node.id,
local_link_connection={
'switch_info': 'my_switch_1',
})
port3 = obj_utils.create_test_port(
self.context, id=989,
uuid='2be26c0b-03f2-4d2e-ae87-c02d7f33c784',
address='22:22:22:22:22:23', node_id=self.node.id,
local_link_connection={
'switch_info': 'my_switch_1',
})
port4 = obj_utils.create_test_port(
self.context, id=990,
uuid='2be26c0b-03f2-4d2e-ae87-c02d7f33c786',
address='22:22:22:22:22:24', node_id=self.node.id,
local_link_connection={
'switch_info': 'my_switch_1',
})
port5 = obj_utils.create_test_port(
self.context, id=991,
uuid='2be26c0b-03f2-4d2e-ae87-c02d7f33c788',
address='22:22:22:22:22:25', node_id=self.node.id,
local_link_connection={
'foo': 'bar',
})
ports = [port1, port2, port3, port4, port5]
mock_list_by_nodeid.return_value = ports

port_group.PortGroupsHook().__call__(
task, {}, {})

port1.refresh()
port2.refresh()
port3.refresh()
self.assertEqual(port1.portgroup_id, None)
# check no port group created
new_port_groups = objects.Portgroup.list_by_node_id(
self.context, self.node.id)
self.assertEqual(len(new_port_groups), 0)
self.assertIsNone(port2.portgroup_id)
self.assertIsNone(port3.portgroup_id)