Documentation, comments and small changes
This commit is contained in:
+75
-10
@@ -32,6 +32,8 @@ from topology_parser import (
|
||||
)
|
||||
|
||||
|
||||
# ========================== Execution plan model ==========================
|
||||
|
||||
class TopologyPlanError(ValueError):
|
||||
"""Raised when a validated topology cannot be lowered into a plan."""
|
||||
|
||||
@@ -182,6 +184,12 @@ class PlannedService:
|
||||
class ExecutionPlan:
|
||||
"""Contain the complete, fully resolved execution plan.
|
||||
|
||||
Ordered tuples of frozen operation objects capture the result of planning.
|
||||
They carry concrete names and configuration into the renderer without
|
||||
exposing the mutable allocation context or the source model objects.
|
||||
The renderer emits the operations in lifecycle order: construction,
|
||||
configuration, and service startup.
|
||||
|
||||
Args:
|
||||
nodes: Node creation operations in declaration order.
|
||||
links: Link creation operations in declaration order.
|
||||
@@ -201,21 +209,45 @@ class ExecutionPlan:
|
||||
services: Tuple[PlannedService, ...]
|
||||
|
||||
|
||||
# ======================== Mutable planning context ========================
|
||||
|
||||
@dataclass
|
||||
class _PlanningContext:
|
||||
"""Hold mutable state used while lowering a topology.
|
||||
|
||||
_build_context() initializes node lookup, reservations, free pools, and
|
||||
counters from the validated topology.
|
||||
During _plan_links(), allocation helpers consume pool entries, advance
|
||||
counters, and record interface mappings.
|
||||
Interface, node-setting, and service planning then read those mappings.
|
||||
|
||||
A logical interface is identified by (node_name, logical_name), since tags
|
||||
need only be unique within their owning node.
|
||||
interface_names and interface_models acquire the same keys on allocation:
|
||||
one supplies the backend name, and the other supplies its configuration.
|
||||
A reservation alone does not create an entry in either mapping.
|
||||
|
||||
Args:
|
||||
nodes: Nodes indexed by logical name.
|
||||
reserved_interfaces: Explicitly referenced logical interfaces per node.
|
||||
free_interfaces: Remaining declared interfaces available to AUTO.
|
||||
dynamic_interface_counters: Synthetic logical-name counters for nodes
|
||||
whose interface sets were omitted.
|
||||
physical_interface_counters: Concrete eth-index counters per node.
|
||||
interface_names: Mapping from resolved logical interfaces to concrete
|
||||
Linux/Mininet interface names.
|
||||
interface_models: Mapping from resolved logical interfaces to their
|
||||
source Interface objects.
|
||||
nodes: Source nodes indexed by name, safe after uniqueness validation.
|
||||
reserved_interfaces: All explicit link references per node, used to
|
||||
construct free pools and retained unchanged during allocation.
|
||||
free_interfaces: Ordered lists of unreserved declared tags for fixed
|
||||
pools; AUTO removes the first entry.
|
||||
An empty list means exhaustion; dynamic nodes have no entry.
|
||||
dynamic_interface_counters: Next synthetic logical-tag indices, such
|
||||
as auto0; advanced only for nodes with omitted interface sets.
|
||||
physical_interface_counters: Next concrete eth-index per node;
|
||||
advanced for every allocation, including explicit selections.
|
||||
interface_names: Allocated (node, logical tag) pairs mapped to concrete
|
||||
Linux/Mininet names, in interface-configuration order.
|
||||
interface_models: The same allocated keys mapped to source Interface
|
||||
objects or default models created for dynamic interfaces.
|
||||
|
||||
Example:
|
||||
A fixed pool declared as p1, p2, uplink with an explicit uplink
|
||||
reference starts with free_interfaces containing [p1, p2].
|
||||
If the first endpoint for that node uses AUTO, it consumes p1 and
|
||||
receives <node>-eth0, recording both mappings for (node, p1).
|
||||
"""
|
||||
|
||||
nodes: Dict[str, Node]
|
||||
@@ -227,9 +259,15 @@ class _PlanningContext:
|
||||
interface_models: Dict[Tuple[str, str], Interface]
|
||||
|
||||
|
||||
# ========================= Context initialization =========================
|
||||
|
||||
def _build_context(topology: Topology) -> _PlanningContext:
|
||||
"""Create planning state and reserve all explicit link interfaces.
|
||||
|
||||
Scan every link before constructing the free-interface pools so an AUTO
|
||||
endpoint cannot consume an interface explicitly referenced by a later link.
|
||||
Preserve declaration order in each pool for deterministic allocation.
|
||||
|
||||
Args:
|
||||
topology: Validated topology to lower.
|
||||
|
||||
@@ -267,6 +305,8 @@ def _build_context(topology: Topology) -> _PlanningContext:
|
||||
)
|
||||
|
||||
|
||||
# ========================= Node and link planning =========================
|
||||
|
||||
def _plan_nodes(topology: Topology) -> Tuple[PlannedNode, ...]:
|
||||
"""Lower topology nodes into concrete node creation operations.
|
||||
|
||||
@@ -296,6 +336,11 @@ def _allocate_logical_interface(
|
||||
) -> Tuple[str, Interface]:
|
||||
"""Resolve one link endpoint to a concrete logical interface.
|
||||
|
||||
Explicit tags look up their declared models without consuming a free pool.
|
||||
AUTO consumes the first available tag for a fixed pool, or creates a
|
||||
synthetic tag and an unconfigured model for a dynamic pool.
|
||||
Concrete backend names are assigned separately by physical allocation.
|
||||
|
||||
Args:
|
||||
node: Node that owns the endpoint.
|
||||
selector: Explicit interface tag or AUTO selector.
|
||||
@@ -373,6 +418,7 @@ def _allocate_physical_interface(
|
||||
context.physical_interface_counters[node_name] = index + 1
|
||||
|
||||
interface_name = "%s-eth%d" % (node_name, index)
|
||||
# These mappings let later stages recover both the name and configuration.
|
||||
context.interface_names[key] = interface_name
|
||||
context.interface_models[key] = interface
|
||||
|
||||
@@ -385,6 +431,11 @@ def _plan_links(
|
||||
) -> Tuple[PlannedLink, ...]:
|
||||
"""Resolve all link endpoints and concrete interface names.
|
||||
|
||||
Traverse links and their endpoints in source order, resolving each logical
|
||||
selection before assigning its backend name.
|
||||
Allocation records mappings in the shared context for subsequent interface,
|
||||
node-setting, and service planning, so traversal order determines naming.
|
||||
|
||||
Args:
|
||||
topology: Validated topology to lower.
|
||||
context: Mutable planning context.
|
||||
@@ -436,6 +487,8 @@ def _plan_links(
|
||||
return tuple(planned_links)
|
||||
|
||||
|
||||
# ==================== Interface and node configuration ====================
|
||||
|
||||
def _interface_requires_materialization(interface: Interface) -> bool:
|
||||
"""Return whether an unused declared interface carries configuration.
|
||||
|
||||
@@ -462,6 +515,12 @@ def _plan_interfaces(
|
||||
) -> Tuple[PlannedInterface, ...]:
|
||||
"""Create interface-configuration operations for materialized interfaces.
|
||||
|
||||
First reject configured declarations absent from the allocation mappings:
|
||||
Mininet creates these interfaces through links, so there would be no
|
||||
concrete interface on which to apply their configuration.
|
||||
Empty unused declarations need no operation and may remain unallocated.
|
||||
Then pair each allocated name with its model in allocation order.
|
||||
|
||||
Args:
|
||||
topology: Validated topology to lower.
|
||||
context: Planning context containing resolved interface mappings.
|
||||
@@ -581,6 +640,8 @@ def _plan_node_settings(
|
||||
return tuple(planned)
|
||||
|
||||
|
||||
# ============================ Service planning ============================
|
||||
|
||||
def _resolve_service_logical_interface(
|
||||
service: Service,
|
||||
node: Node,
|
||||
@@ -694,6 +755,8 @@ def _plan_services(
|
||||
return tuple(planned)
|
||||
|
||||
|
||||
# ============================ Public interface ============================
|
||||
|
||||
def plan(topology: Topology) -> ExecutionPlan:
|
||||
"""Lower a validated topology into a fully resolved execution plan.
|
||||
|
||||
@@ -730,6 +793,8 @@ def plan(topology: Topology) -> ExecutionPlan:
|
||||
)
|
||||
|
||||
|
||||
# =============================== Self-test ===============================
|
||||
|
||||
def _self_test() -> None:
|
||||
"""Run a smoke test against the public plan() interface."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user