Merge "Generate network plan based on trait based networking config"
This commit is contained in:
99
ironic/common/trait_based_networking/plan.py
Normal file
99
ironic/common/trait_based_networking/plan.py
Normal file
@@ -0,0 +1,99 @@
|
||||
# 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 ironic.common.i18n import _
|
||||
from ironic.common.trait_based_networking import base
|
||||
|
||||
from collections.abc import Callable
|
||||
import itertools
|
||||
|
||||
|
||||
def plan_network(
|
||||
network_trait: base.NetworkTrait,
|
||||
node_uuid: str,
|
||||
node_ports: list[base.Port],
|
||||
node_portgroups: list[base.Portgroup],
|
||||
node_networks: list[base.Network]) -> list[base.RenderedAction]:
|
||||
rendered_actions = []
|
||||
|
||||
# Order ports and portgroups by ID, newest first.
|
||||
node_ports.sort(key=lambda port: port.id, reverse=True)
|
||||
node_portgroups.sort(key=lambda portgroup: portgroup.id, reverse=True)
|
||||
|
||||
portlikes = [base.Port.from_ironic_port(port) for port in node_ports]
|
||||
portgrouplikes = [base.Portgroup.from_ironic_portgroup(portgroup)
|
||||
for portgroup in node_portgroups]
|
||||
|
||||
for trait_action in network_trait.actions:
|
||||
match trait_action.action:
|
||||
case base.Actions.ATTACH_PORT:
|
||||
rendered_actions.extend(
|
||||
plan_attach_portlike(
|
||||
trait_action, node_uuid, portlikes,
|
||||
node_networks, 'port',
|
||||
lambda action_args:
|
||||
base.AttachPort(*action_args)))
|
||||
case base.Actions.ATTACH_PORTGROUP:
|
||||
rendered_actions.extend(
|
||||
plan_attach_portlike(
|
||||
trait_action, node_uuid, portgrouplikes,
|
||||
node_networks, 'portgroup',
|
||||
lambda action_args:
|
||||
base.AttachPortgroup(*action_args)))
|
||||
# TODO(clif): Support bond_ports and group_and_attach_ports
|
||||
case _:
|
||||
rendered_actions.append(
|
||||
base.NotImplementedAction(trait_action.action))
|
||||
|
||||
return rendered_actions
|
||||
|
||||
|
||||
def plan_attach_portlike(
|
||||
trait_action: base.NetworkTrait,
|
||||
node_uuid: str,
|
||||
node_portlikes: list[base.PrimordialPort],
|
||||
node_networks: list[base.Network],
|
||||
type_name: str,
|
||||
action_func: Callable[[base.NetworkTrait, str, str, str],
|
||||
base.RenderedAction]
|
||||
) -> list[base.RenderedAction]:
|
||||
actions = []
|
||||
for (portlike, network) in itertools.product(node_portlikes,
|
||||
node_networks):
|
||||
if trait_action.matches(portlike, network):
|
||||
actions.append(action_func((trait_action,
|
||||
node_uuid,
|
||||
portlike.uuid,
|
||||
network.id)))
|
||||
# No minimum count means match the first one.
|
||||
if trait_action.min_count is None:
|
||||
break
|
||||
if trait_action.max_count == len(actions):
|
||||
break
|
||||
|
||||
if len(actions) == 0:
|
||||
return [base.NoMatch(trait_action,
|
||||
node_uuid,
|
||||
_(f"No ({type_name}, network) pairs matched "
|
||||
"rule."))]
|
||||
|
||||
if (trait_action.min_count is not None
|
||||
and len(actions) < trait_action.min_count):
|
||||
return [base.NoMatch(trait_action,
|
||||
node_uuid,
|
||||
_(f"Not enough ({type_name}, network) pairs "
|
||||
"matched to meet minimum count threshold. "
|
||||
f"Matched {len(actions)} but min_count is "
|
||||
f"{trait_action.min_count}."))]
|
||||
|
||||
return actions
|
||||
317
ironic/tests/unit/common/trait_based_networking/test_plan.py
Normal file
317
ironic/tests/unit/common/trait_based_networking/test_plan.py
Normal file
@@ -0,0 +1,317 @@
|
||||
# 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 ironic.common.trait_based_networking.base as tbn_base
|
||||
import ironic.common.trait_based_networking.plan as tbn_plan
|
||||
|
||||
from ironic.tests import base
|
||||
import ironic.tests.unit.common.trait_based_networking.utils as utils
|
||||
|
||||
from ddt import data
|
||||
from ddt import ddt
|
||||
from ddt import unpack
|
||||
|
||||
|
||||
|
||||
def annotate(name, *args):
|
||||
class AnnotatedList(list):
|
||||
pass
|
||||
|
||||
al = AnnotatedList([*args])
|
||||
al.__name__ = name
|
||||
return al
|
||||
|
||||
|
||||
@ddt
|
||||
class TraitBasedNetworkingPlanningTestCase(base.TestCase):
|
||||
@data(
|
||||
annotate(
|
||||
"Match a port",
|
||||
tbn_base.TraitAction(
|
||||
"CUSTOM_TRAIT",
|
||||
tbn_base.Actions.ATTACH_PORT,
|
||||
tbn_base.FilterExpression.parse("port.vendor == 'clover'"),
|
||||
),
|
||||
"fake_node_uuid",
|
||||
[
|
||||
utils.FauxPortLikeObject(
|
||||
uuid="fake_port_uuid",
|
||||
vendor="clover",
|
||||
)
|
||||
],
|
||||
[tbn_base.Network("fake_net_id", "network_name", [])],
|
||||
[
|
||||
tbn_base.AttachPort(
|
||||
tbn_base.TraitAction(
|
||||
"CUSTOM_TRAIT",
|
||||
tbn_base.Actions.ATTACH_PORT,
|
||||
tbn_base.FilterExpression.parse(
|
||||
"port.vendor == 'clover'")
|
||||
),
|
||||
"fake_node_uuid",
|
||||
"fake_port_uuid",
|
||||
"fake_net_id")
|
||||
],
|
||||
'port'
|
||||
),
|
||||
annotate(
|
||||
"Match no ports",
|
||||
tbn_base.TraitAction(
|
||||
"CUSTOM_TRAIT",
|
||||
tbn_base.Actions.ATTACH_PORT,
|
||||
tbn_base.FilterExpression.parse(
|
||||
"port.vendor == 'cogwork'"),
|
||||
),
|
||||
"fake_node_uuid",
|
||||
[
|
||||
utils.FauxPortLikeObject(
|
||||
uuid="fake_port_uuid",
|
||||
vendor="clover",
|
||||
),
|
||||
utils.FauxPortLikeObject(
|
||||
uuid="fake_port_uuid2",
|
||||
vendor="clover",
|
||||
)
|
||||
],
|
||||
[tbn_base.Network("fake_net_id", "network_name", [])],
|
||||
[
|
||||
tbn_base.NoMatch(
|
||||
tbn_base.TraitAction(
|
||||
"CUSTOM_TRAIT",
|
||||
tbn_base.Actions.ATTACH_PORT,
|
||||
tbn_base.FilterExpression.parse(
|
||||
"port.vendor == 'cogwork'")
|
||||
),
|
||||
"fake_node_uuid",
|
||||
"No (port, network) pairs matched rule."
|
||||
)
|
||||
],
|
||||
'port'
|
||||
),
|
||||
annotate(
|
||||
"Match a specific port based on order",
|
||||
tbn_base.TraitAction(
|
||||
"CUSTOM_TRAIT",
|
||||
tbn_base.Actions.ATTACH_PORT,
|
||||
tbn_base.FilterExpression.parse(
|
||||
"port.vendor == 'cogwork'"),
|
||||
),
|
||||
"fake_node_uuid",
|
||||
[
|
||||
utils.FauxPortLikeObject(
|
||||
uuid="fake_port_uuid",
|
||||
vendor="clover",
|
||||
),
|
||||
utils.FauxPortLikeObject(
|
||||
uuid="fake_port_uuid3",
|
||||
vendor="cogwork",
|
||||
),
|
||||
utils.FauxPortLikeObject(
|
||||
uuid="fake_port_uuid2",
|
||||
vendor="cogwork",
|
||||
)
|
||||
],
|
||||
[tbn_base.Network("fake_net_id", "network_name", [])],
|
||||
[
|
||||
tbn_base.AttachPort(
|
||||
tbn_base.TraitAction(
|
||||
"CUSTOM_TRAIT",
|
||||
tbn_base.Actions.ATTACH_PORT,
|
||||
tbn_base.FilterExpression.parse(
|
||||
"port.vendor == 'cogwork'")
|
||||
),
|
||||
"fake_node_uuid",
|
||||
"fake_port_uuid3",
|
||||
"fake_net_id"
|
||||
)
|
||||
],
|
||||
'port'
|
||||
),
|
||||
annotate(
|
||||
"Match one portgroup",
|
||||
tbn_base.TraitAction(
|
||||
"CUSTOM_TRAIT",
|
||||
tbn_base.Actions.ATTACH_PORTGROUP,
|
||||
tbn_base.FilterExpression.parse(
|
||||
"port.physical_network == 'hypernet'"),
|
||||
),
|
||||
"fake_node_uuid",
|
||||
[
|
||||
utils.FauxPortLikeObject(
|
||||
uuid="fake_portgroup_uuid",
|
||||
vendor="clover",
|
||||
physical_network="hypernet",
|
||||
)
|
||||
],
|
||||
[tbn_base.Network("fake_net_id", "network_name", [])],
|
||||
[
|
||||
tbn_base.AttachPortgroup(
|
||||
tbn_base.TraitAction(
|
||||
"CUSTOM_TRAIT",
|
||||
tbn_base.Actions.ATTACH_PORTGROUP,
|
||||
tbn_base.FilterExpression.parse(
|
||||
"port.physical_network == 'hypernet'"),
|
||||
),
|
||||
"fake_node_uuid",
|
||||
"fake_portgroup_uuid",
|
||||
"fake_net_id")
|
||||
],
|
||||
'portgroup'
|
||||
),
|
||||
annotate(
|
||||
"Match no portgroups",
|
||||
tbn_base.TraitAction(
|
||||
"CUSTOM_TRAIT",
|
||||
tbn_base.Actions.ATTACH_PORTGROUP,
|
||||
tbn_base.FilterExpression.parse(
|
||||
"port.category == 'blue'"),
|
||||
),
|
||||
"fake_node_uuid",
|
||||
[
|
||||
utils.FauxPortLikeObject(
|
||||
uuid="fake_portgroup_uuid",
|
||||
category="green",
|
||||
),
|
||||
utils.FauxPortLikeObject(
|
||||
uuid="fake_portgroup_uuid2",
|
||||
category="red",
|
||||
)
|
||||
],
|
||||
[tbn_base.Network("fake_net_id", "network_name", [])],
|
||||
[
|
||||
tbn_base.NoMatch(
|
||||
tbn_base.TraitAction(
|
||||
"CUSTOM_TRAIT",
|
||||
tbn_base.Actions.ATTACH_PORTGROUP,
|
||||
tbn_base.FilterExpression.parse(
|
||||
"port.category == 'blue'")
|
||||
),
|
||||
"fake_node_uuid",
|
||||
"No (portgroup, network) pairs matched rule."
|
||||
)
|
||||
],
|
||||
'portgroup'
|
||||
),
|
||||
annotate(
|
||||
"Match a specific portgroup based on order",
|
||||
tbn_base.TraitAction(
|
||||
"CUSTOM_TRAIT",
|
||||
tbn_base.Actions.ATTACH_PORTGROUP,
|
||||
tbn_base.FilterExpression.parse(
|
||||
"port.category == 'red'"),
|
||||
),
|
||||
"fake_node_uuid",
|
||||
[
|
||||
utils.FauxPortLikeObject(
|
||||
uuid="fake_portgroup_uuid",
|
||||
vendor="clover",
|
||||
),
|
||||
utils.FauxPortLikeObject(
|
||||
uuid="fake_portgroup_uuid3",
|
||||
category="red",
|
||||
),
|
||||
utils.FauxPortLikeObject(
|
||||
uuid="fake_portgroup_uuid2",
|
||||
category="red",
|
||||
)
|
||||
],
|
||||
[tbn_base.Network("fake_net_id", "network_name", [])],
|
||||
[
|
||||
tbn_base.AttachPortgroup(
|
||||
tbn_base.TraitAction(
|
||||
"CUSTOM_TRAIT",
|
||||
tbn_base.Actions.ATTACH_PORTGROUP,
|
||||
tbn_base.FilterExpression.parse(
|
||||
"port.category == 'red'"),
|
||||
),
|
||||
"fake_node_uuid",
|
||||
"fake_portgroup_uuid3",
|
||||
"fake_net_id")
|
||||
],
|
||||
'portgroup'
|
||||
),
|
||||
# TODO(clif): Test min_count and max_count
|
||||
)
|
||||
@unpack
|
||||
def test_plan_attach_portlike(self,
|
||||
trait_action: tbn_base.TraitAction,
|
||||
node_uuid: str,
|
||||
node_portlikes: list[utils.FauxPortLikeObject],
|
||||
node_networks: list[tbn_base.Network],
|
||||
expected_actions: list[tbn_base.RenderedAction],
|
||||
type_name: str):
|
||||
action_funcs = {
|
||||
'port': lambda args: tbn_base.AttachPort(*args),
|
||||
'portgroup': lambda args: tbn_base.AttachPortgroup(*args)
|
||||
}
|
||||
|
||||
result_actions = tbn_plan.plan_attach_portlike(
|
||||
trait_action,
|
||||
node_uuid,
|
||||
node_portlikes,
|
||||
node_networks,
|
||||
type_name,
|
||||
action_funcs[type_name])
|
||||
self.assertEqual(expected_actions, result_actions)
|
||||
|
||||
@data(
|
||||
annotate("Attach one port",
|
||||
tbn_base.NetworkTrait(
|
||||
"CUSTOM_TRAIT",
|
||||
[
|
||||
tbn_base.TraitAction(
|
||||
"CUSTOM_TRAIT",
|
||||
tbn_base.Actions.ATTACH_PORT,
|
||||
tbn_base.FilterExpression.parse(
|
||||
"port.physical_network == 'hypernet'"),
|
||||
)
|
||||
]
|
||||
),
|
||||
"fake_node_uuid",
|
||||
[
|
||||
utils.FauxPortLikeObject(
|
||||
uuid="fake_port_uuid",
|
||||
vendor="clover",
|
||||
physical_network="hypernet",
|
||||
)
|
||||
],
|
||||
[],
|
||||
[tbn_base.Network("fake_net_id", "fake_net_name", [])],
|
||||
[
|
||||
tbn_base.AttachPort(
|
||||
tbn_base.TraitAction(
|
||||
"CUSTOM_TRAIT",
|
||||
tbn_base.Actions.ATTACH_PORT,
|
||||
tbn_base.FilterExpression.parse(
|
||||
"port.physical_network == 'hypernet'"),
|
||||
),
|
||||
"fake_node_uuid",
|
||||
"fake_port_uuid",
|
||||
"fake_net_id")
|
||||
],
|
||||
),
|
||||
)
|
||||
@unpack
|
||||
def test_plan_network(self,
|
||||
network_trait: tbn_base.NetworkTrait,
|
||||
node_uuid: str,
|
||||
node_ports: list[tbn_base.Port],
|
||||
node_portgroups: list[tbn_base.Portgroup],
|
||||
node_networks: list,
|
||||
expected_actions: list[tbn_base.RenderedAction]):
|
||||
result_actions = tbn_plan.plan_network(
|
||||
network_trait,
|
||||
node_uuid,
|
||||
node_ports,
|
||||
node_portgroups,
|
||||
node_networks)
|
||||
self.assertEqual(expected_actions, result_actions)
|
||||
Reference in New Issue
Block a user