From 8d75f150577b94bcdbdc483d0ddd8ed8803316e4 Mon Sep 17 00:00:00 2001 From: Leon Lux Date: Mon, 11 Nov 2024 11:57:54 +0100 Subject: [PATCH 01/19] update .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d6c4127..b55f750 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ clab-* **/__pycache__ *.pyc .venv/* -*.tar** \ No newline at end of file +*.tar** +.idea/ \ No newline at end of file From 2d35510b696e1afd2ae9c3f9ad72c36c98f5effd Mon Sep 17 00:00:00 2001 From: Leon Lux Date: Mon, 11 Nov 2024 12:15:34 +0100 Subject: [PATCH 02/19] add new topology package --- topology/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 topology/__init__.py diff --git a/topology/__init__.py b/topology/__init__.py new file mode 100644 index 0000000..e69de29 From dd9fd701ace45c5464d2ce808812fcc226bc0ffe Mon Sep 17 00:00:00 2001 From: Leon Lux Date: Mon, 11 Nov 2024 12:16:02 +0100 Subject: [PATCH 03/19] add link dataclass for representing links between nodes --- topology/link.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 topology/link.py diff --git a/topology/link.py b/topology/link.py new file mode 100644 index 0000000..345847e --- /dev/null +++ b/topology/link.py @@ -0,0 +1,8 @@ +from dataclasses import dataclass + +@dataclass +class Link: + name_from: str + name_to: str + interface_from: str + interface_to: str \ No newline at end of file From 63f91d91bb8cea707442afccccf4925ea424f562 Mon Sep 17 00:00:00 2001 From: Leon Lux Date: Mon, 11 Nov 2024 12:16:16 +0100 Subject: [PATCH 04/19] add node representing a node in a topology --- topology/node.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 topology/node.py diff --git a/topology/node.py b/topology/node.py new file mode 100644 index 0000000..4d8529e --- /dev/null +++ b/topology/node.py @@ -0,0 +1,9 @@ +from dataclasses import dataclass +from typing import List +from .link import Link + +@dataclass +class Node: + name: str + kind: str + links: List[Link] From 0a806f8ac44a6663fa95f4985e332f9c020d094a Mon Sep 17 00:00:00 2001 From: Leon Lux Date: Mon, 11 Nov 2024 13:08:03 +0100 Subject: [PATCH 05/19] add topology class as abstraction for topologies --- topology/topology.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 topology/topology.py diff --git a/topology/topology.py b/topology/topology.py new file mode 100644 index 0000000..a490909 --- /dev/null +++ b/topology/topology.py @@ -0,0 +1,29 @@ +from .link import Link +from .node import Node +from typing import List + +class Topology: + def __init__(self): + self.nodes: List[Node] = [] + self.links: List[Link] = [] + +class TopologyBuilder: + def __init__(self) -> None: + self._topo = Topology() + + def add_node(self, name: str, kind: str) -> None: + self._topo.nodes.append(Node(name, kind, [])) + + def add_link(self, node_from: str, node_to: str, interface_from: str, interface_to: str) -> None: + # assert + assert any(node.name == node_from for node in self._topo.nodes) + assert any(node.name == node_to for node in self._topo.nodes) + self._topo.links.append(Link(node_from, node_to, interface_from, interface_to)) + + def clear(self) -> None: + self._topo.nodes = [] + self._topo.links = [] + + def build(self) -> Topology: + return self._topo + From 64309b7fc3246d2bb2eda5a0d29efdd32c1e2b15 Mon Sep 17 00:00:00 2001 From: Leon Lux Date: Mon, 11 Nov 2024 13:14:38 +0100 Subject: [PATCH 06/19] export topology types with __all__ --- topology/__init__.py | 5 +++++ topology/link.py | 2 ++ topology/node.py | 2 ++ topology/topology.py | 2 ++ 4 files changed, 11 insertions(+) diff --git a/topology/__init__.py b/topology/__init__.py index e69de29..0eef68c 100644 --- a/topology/__init__.py +++ b/topology/__init__.py @@ -0,0 +1,5 @@ +from .link import * # noqa: F403, F401 +from .node import * # noqa: F403, F401 +from .topology import * # noqa: F403, F401 + +__all__ = topology.__all__ + node.__all__ + link.__all__ \ No newline at end of file diff --git a/topology/link.py b/topology/link.py index 345847e..96d0b90 100644 --- a/topology/link.py +++ b/topology/link.py @@ -1,5 +1,7 @@ from dataclasses import dataclass +__all__ = ['Link'] + @dataclass class Link: name_from: str diff --git a/topology/node.py b/topology/node.py index 4d8529e..1318dd8 100644 --- a/topology/node.py +++ b/topology/node.py @@ -2,6 +2,8 @@ from typing import List from .link import Link +__all__ = ['Node'] + @dataclass class Node: name: str diff --git a/topology/topology.py b/topology/topology.py index a490909..7bc8e6b 100644 --- a/topology/topology.py +++ b/topology/topology.py @@ -2,6 +2,8 @@ from .node import Node from typing import List +__all__ = ['Topology', 'TopologyBuilder'] + class Topology: def __init__(self): self.nodes: List[Node] = [] From 12374f95a784731381c2ed1c4fc662f5eae8c906 Mon Sep 17 00:00:00 2001 From: Leon Lux Date: Mon, 11 Nov 2024 13:42:44 +0100 Subject: [PATCH 07/19] add tests for topology buider --- tests/topology/__init__.py | 0 tests/topology/test_topology.py | 42 +++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 tests/topology/__init__.py create mode 100644 tests/topology/test_topology.py diff --git a/tests/topology/__init__.py b/tests/topology/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topology/test_topology.py b/tests/topology/test_topology.py new file mode 100644 index 0000000..ef82dc0 --- /dev/null +++ b/tests/topology/test_topology.py @@ -0,0 +1,42 @@ +import unittest +from topology import * + +class TopologyTests(unittest.TestCase): + + def setUp(self): + self.b = TopologyBuilder() + + def test_topology_builder(self): + self.b = TopologyBuilder() + self.b.add_node('srl1', 'nokia_srlinux') + self.b.add_node('srl2', 'nokia_srlinux') + self.b.add_link('srl1', 'srl2', 'e1-1', 'e1-1') + + topo = self.b.build() + self.assertEqual(len(topo.nodes), 2) + self.assertEqual(len(topo.links), 1) + + self.assertEqual(topo.nodes[0].name, 'srl1') + self.assertEqual(topo.nodes[1].name, 'srl2') + self.assertEqual(topo.links[0].name_from, 'srl1') + self.assertEqual(topo.links[0].name_to, 'srl2') + self.assertEqual(topo.links[0].interface_to, 'e1-1') + self.assertEqual(topo.links[0].interface_from, 'e1-1') + + + def test_topology_faulty_link(self): + self.b.add_node('srl1', 'nokia_srlinux') + with self.assertRaises(AssertionError): + self.b.add_link('srl1', 'srl2', 'e1-1', 'e1-1') + + + def test_topology_clear(self): + self.b.add_node('srl1', 'nokia_srlinux') + self.b.add_node('srl2', 'nokia_srlinux') + self.b.add_link('srl1', 'srl2', 'e1-1', 'e1-1') + self.b.clear() + topo = self.b.build() + + self.assertEqual(len(topo.nodes), 0) + self.assertEqual(len(topo.links), 0) + From e3d31134bac3944052de576e7ba2eec571e3cb36 Mon Sep 17 00:00:00 2001 From: Leon Lux Date: Mon, 11 Nov 2024 13:43:01 +0100 Subject: [PATCH 08/19] remove sample tests --- tests/example/__init__.py | 0 tests/example/test_example.py | 2 -- 2 files changed, 2 deletions(-) delete mode 100644 tests/example/__init__.py delete mode 100644 tests/example/test_example.py diff --git a/tests/example/__init__.py b/tests/example/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/example/test_example.py b/tests/example/test_example.py deleted file mode 100644 index 813df60..0000000 --- a/tests/example/test_example.py +++ /dev/null @@ -1,2 +0,0 @@ -def test_example(): - assert True From f131ad3e58c0a99c54bbfe58c4ed39cddd401eef Mon Sep 17 00:00:00 2001 From: Leon Lux Date: Mon, 11 Nov 2024 13:54:46 +0100 Subject: [PATCH 09/19] make flake8 and black happy --- config/cli.py | 2 +- config/settings.py | 28 +++++++++++++++++++-------- tests/topology/test_topology.py | 34 ++++++++++++++++----------------- topology/__init__.py | 12 ++++++++---- topology/link.py | 5 +++-- topology/node.py | 3 ++- topology/topology.py | 17 +++++++++++++---- 7 files changed, 63 insertions(+), 38 deletions(-) diff --git a/config/cli.py b/config/cli.py index d3a3aae..6290057 100644 --- a/config/cli.py +++ b/config/cli.py @@ -1,7 +1,7 @@ import argparse -__all__ = ['ArgParser'] +__all__ = ["ArgParser"] class ArgParser: diff --git a/config/settings.py b/config/settings.py index 68cbbc9..5670fa3 100644 --- a/config/settings.py +++ b/config/settings.py @@ -13,14 +13,26 @@ import yaml -__all__ = ['TopologyType', 'TopologyAdjustment', 'KafkaSettings', - 'RabbitSettings', 'TopologyAdjustmentAdd', - 'TopologyAdjustmentRemove', 'TopologyAdjustmentRemoveLink', - 'TopologyAdjustmentAddLink', 'InterfaceSettings', - 'InterfaceCredentials', 'RealnetSettings', - 'ControllerSettings', 'SiblingSettings', - 'BuilderSettings', 'read_config', 'validate_config', - 'AppSettings', 'Settings'] +__all__ = [ + "TopologyType", + "TopologyAdjustment", + "KafkaSettings", + "RabbitSettings", + "TopologyAdjustmentAdd", + "TopologyAdjustmentRemove", + "TopologyAdjustmentRemoveLink", + "TopologyAdjustmentAddLink", + "InterfaceSettings", + "InterfaceCredentials", + "RealnetSettings", + "ControllerSettings", + "SiblingSettings", + "BuilderSettings", + "read_config", + "validate_config", + "AppSettings", + "Settings", +] class TopologyType(BaseModel): diff --git a/tests/topology/test_topology.py b/tests/topology/test_topology.py index ef82dc0..daa7d74 100644 --- a/tests/topology/test_topology.py +++ b/tests/topology/test_topology.py @@ -1,5 +1,6 @@ import unittest -from topology import * +from topology import TopologyBuilder + class TopologyTests(unittest.TestCase): @@ -8,35 +9,32 @@ def setUp(self): def test_topology_builder(self): self.b = TopologyBuilder() - self.b.add_node('srl1', 'nokia_srlinux') - self.b.add_node('srl2', 'nokia_srlinux') - self.b.add_link('srl1', 'srl2', 'e1-1', 'e1-1') + self.b.add_node("srl1", "nokia_srlinux") + self.b.add_node("srl2", "nokia_srlinux") + self.b.add_link("srl1", "srl2", "e1-1", "e1-1") topo = self.b.build() self.assertEqual(len(topo.nodes), 2) self.assertEqual(len(topo.links), 1) - self.assertEqual(topo.nodes[0].name, 'srl1') - self.assertEqual(topo.nodes[1].name, 'srl2') - self.assertEqual(topo.links[0].name_from, 'srl1') - self.assertEqual(topo.links[0].name_to, 'srl2') - self.assertEqual(topo.links[0].interface_to, 'e1-1') - self.assertEqual(topo.links[0].interface_from, 'e1-1') - + self.assertEqual(topo.nodes[0].name, "srl1") + self.assertEqual(topo.nodes[1].name, "srl2") + self.assertEqual(topo.links[0].name_from, "srl1") + self.assertEqual(topo.links[0].name_to, "srl2") + self.assertEqual(topo.links[0].interface_to, "e1-1") + self.assertEqual(topo.links[0].interface_from, "e1-1") def test_topology_faulty_link(self): - self.b.add_node('srl1', 'nokia_srlinux') + self.b.add_node("srl1", "nokia_srlinux") with self.assertRaises(AssertionError): - self.b.add_link('srl1', 'srl2', 'e1-1', 'e1-1') - + self.b.add_link("srl1", "srl2", "e1-1", "e1-1") def test_topology_clear(self): - self.b.add_node('srl1', 'nokia_srlinux') - self.b.add_node('srl2', 'nokia_srlinux') - self.b.add_link('srl1', 'srl2', 'e1-1', 'e1-1') + self.b.add_node("srl1", "nokia_srlinux") + self.b.add_node("srl2", "nokia_srlinux") + self.b.add_link("srl1", "srl2", "e1-1", "e1-1") self.b.clear() topo = self.b.build() self.assertEqual(len(topo.nodes), 0) self.assertEqual(len(topo.links), 0) - diff --git a/topology/__init__.py b/topology/__init__.py index 0eef68c..f555fa7 100644 --- a/topology/__init__.py +++ b/topology/__init__.py @@ -1,5 +1,9 @@ -from .link import * # noqa: F403, F401 -from .node import * # noqa: F403, F401 -from .topology import * # noqa: F403, F401 +from .link import * # noqa: F403, F401 +from .node import * # noqa: F403, F401 +from .topology import * # noqa: F403, F401 -__all__ = topology.__all__ + node.__all__ + link.__all__ \ No newline at end of file +import link +import topology +import node + +__all__ = topology.__all__ + node.__all__ + link.__all__ diff --git a/topology/link.py b/topology/link.py index 96d0b90..62efba0 100644 --- a/topology/link.py +++ b/topology/link.py @@ -1,10 +1,11 @@ from dataclasses import dataclass -__all__ = ['Link'] +__all__ = ["Link"] + @dataclass class Link: name_from: str name_to: str interface_from: str - interface_to: str \ No newline at end of file + interface_to: str diff --git a/topology/node.py b/topology/node.py index 1318dd8..3de65fd 100644 --- a/topology/node.py +++ b/topology/node.py @@ -2,7 +2,8 @@ from typing import List from .link import Link -__all__ = ['Node'] +__all__ = ["Node"] + @dataclass class Node: diff --git a/topology/topology.py b/topology/topology.py index 7bc8e6b..62dea56 100644 --- a/topology/topology.py +++ b/topology/topology.py @@ -2,13 +2,15 @@ from .node import Node from typing import List -__all__ = ['Topology', 'TopologyBuilder'] +__all__ = ["Topology", "TopologyBuilder"] + class Topology: def __init__(self): self.nodes: List[Node] = [] self.links: List[Link] = [] + class TopologyBuilder: def __init__(self) -> None: self._topo = Topology() @@ -16,11 +18,19 @@ def __init__(self) -> None: def add_node(self, name: str, kind: str) -> None: self._topo.nodes.append(Node(name, kind, [])) - def add_link(self, node_from: str, node_to: str, interface_from: str, interface_to: str) -> None: + def add_link( + self, + node_from: str, + node_to: str, + interface_from: str, + interface_to: str, + ) -> None: # assert assert any(node.name == node_from for node in self._topo.nodes) assert any(node.name == node_to for node in self._topo.nodes) - self._topo.links.append(Link(node_from, node_to, interface_from, interface_to)) + self._topo.links.append( + Link(node_from, node_to, interface_from, interface_to) + ) def clear(self) -> None: self._topo.nodes = [] @@ -28,4 +38,3 @@ def clear(self) -> None: def build(self) -> Topology: return self._topo - From c5a7ded512dbb93ef11bbb9cb9d26e7c6c66da34 Mon Sep 17 00:00:00 2001 From: Leon Lux Date: Mon, 11 Nov 2024 13:59:45 +0100 Subject: [PATCH 10/19] fix import path for tests --- tests/topology/test_topology.py | 2 +- topology/__init__.py | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/tests/topology/test_topology.py b/tests/topology/test_topology.py index daa7d74..046565c 100644 --- a/tests/topology/test_topology.py +++ b/tests/topology/test_topology.py @@ -1,5 +1,5 @@ import unittest -from topology import TopologyBuilder +from ...topology import TopologyBuilder class TopologyTests(unittest.TestCase): diff --git a/topology/__init__.py b/topology/__init__.py index f555fa7..75fe97f 100644 --- a/topology/__init__.py +++ b/topology/__init__.py @@ -1,9 +1,3 @@ from .link import * # noqa: F403, F401 from .node import * # noqa: F403, F401 from .topology import * # noqa: F403, F401 - -import link -import topology -import node - -__all__ = topology.__all__ + node.__all__ + link.__all__ From da267a525bf6bb50584fbf6ef9d70b687779677d Mon Sep 17 00:00:00 2001 From: Leon Lux Date: Fri, 22 Nov 2024 14:17:48 +0100 Subject: [PATCH 11/19] add builder static method to topology --- topology/topology.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/topology/topology.py b/topology/topology.py index 62dea56..18f63e0 100644 --- a/topology/topology.py +++ b/topology/topology.py @@ -10,6 +10,11 @@ def __init__(self): self.nodes: List[Node] = [] self.links: List[Link] = [] + @staticmethod + def builder(): + return TopologyBuilder() + + class TopologyBuilder: def __init__(self) -> None: From 250a1ed5d3024a50c8607fded22e0ab2d30bb7de Mon Sep 17 00:00:00 2001 From: Leon Lux Date: Fri, 22 Nov 2024 14:19:19 +0100 Subject: [PATCH 12/19] add name field to topology --- topology/topology.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/topology/topology.py b/topology/topology.py index 18f63e0..fd5b614 100644 --- a/topology/topology.py +++ b/topology/topology.py @@ -7,6 +7,7 @@ class Topology: def __init__(self): + self.name = '' self.nodes: List[Node] = [] self.links: List[Link] = [] @@ -37,7 +38,11 @@ def add_link( Link(node_from, node_to, interface_from, interface_to) ) + def name(self, name: str) -> None: + self._topo.name = name + def clear(self) -> None: + self._topo.name = '' self._topo.nodes = [] self._topo.links = [] From 29d51f4164c107499a51caddf1ef9141183b489c Mon Sep 17 00:00:00 2001 From: Leon Lux Date: Fri, 22 Nov 2024 14:28:39 +0100 Subject: [PATCH 13/19] add new builder interface --- builders/builder2.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 builders/builder2.py diff --git a/builders/builder2.py b/builders/builder2.py new file mode 100644 index 0000000..7b9cbdc --- /dev/null +++ b/builders/builder2.py @@ -0,0 +1,14 @@ +from abc import ABC, abstractmethod +from topology import Topology + +class TopologyBuilder(ABC): + def __init__(self, topology: Topology): + self.topology = topology + + @abstractmethod + def build_topology(self): + pass + + @abstractmethod + def destroy_topology(self): + pass \ No newline at end of file From 712341aad8c4cf3418b0c3938bd42d8ab59195de Mon Sep 17 00:00:00 2001 From: Leon Lux Date: Fri, 22 Nov 2024 15:49:54 +0100 Subject: [PATCH 14/19] fix formatting --- builders/builder2.py | 3 ++- topology/topology.py | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/builders/builder2.py b/builders/builder2.py index 7b9cbdc..88565f7 100644 --- a/builders/builder2.py +++ b/builders/builder2.py @@ -1,6 +1,7 @@ from abc import ABC, abstractmethod from topology import Topology + class TopologyBuilder(ABC): def __init__(self, topology: Topology): self.topology = topology @@ -11,4 +12,4 @@ def build_topology(self): @abstractmethod def destroy_topology(self): - pass \ No newline at end of file + pass diff --git a/topology/topology.py b/topology/topology.py index fd5b614..87f5749 100644 --- a/topology/topology.py +++ b/topology/topology.py @@ -16,7 +16,6 @@ def builder(): return TopologyBuilder() - class TopologyBuilder: def __init__(self) -> None: self._topo = Topology() From e561d905c97600a8823fa117342f66a2a7309d31 Mon Sep 17 00:00:00 2001 From: Leon Lux Date: Fri, 22 Nov 2024 15:50:16 +0100 Subject: [PATCH 15/19] add clab builder that can turn a topology into a clab yaml file --- builders/clab_builder.py | 46 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 builders/clab_builder.py diff --git a/builders/clab_builder.py b/builders/clab_builder.py new file mode 100644 index 0000000..1405103 --- /dev/null +++ b/builders/clab_builder.py @@ -0,0 +1,46 @@ +from builders.builder2 import TopologyBuilder +from topology import Topology +import yaml + + +class TopologyDumper: + def __init__(self, topology: Topology): + self.topology = topology + + def __to_dict(self): + return { + 'name': self.topology.name, + 'topology': { + 'nodes': { + node.name: { + 'kind': node.kind, + 'image': f"{node.kind}:latest", + } + for node in self.topology.nodes + }, + 'links': [ + { + 'endpoints': [ + f"{link.name_from}:{link.interface_from}", + f"{link.name_to}:{link.interface_to}", + ] + } + for link in self.topology.links + ] + } + } + + def dump(self) -> str: + data = self.__to_dict() + return yaml.safe_dump(data, default_flow_style=False, sort_keys=False) + + +class ClabBuilder(TopologyBuilder): + def __init__(self, topology: Topology): + super().__init__(topology) + + def build_topology(self): + pass + + def destroy_topology(self): + pass From 874b7796a307b3d424013ef0b0698aa34e26875f Mon Sep 17 00:00:00 2001 From: Leon Lux Date: Tue, 3 Dec 2024 21:45:33 +0100 Subject: [PATCH 16/19] add implementation to build topo with clab --- builders/clab_builder.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/builders/clab_builder.py b/builders/clab_builder.py index 1405103..6eea298 100644 --- a/builders/clab_builder.py +++ b/builders/clab_builder.py @@ -1,6 +1,7 @@ from builders.builder2 import TopologyBuilder from topology import Topology import yaml +import subprocess class TopologyDumper: @@ -40,7 +41,20 @@ def __init__(self, topology: Topology): super().__init__(topology) def build_topology(self): - pass + topology_spec = TopologyDumper(self.topology).dump() + try: + proc = subprocess.Popen( + f"clab deploy --topo -", + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + stdout, stderr = proc.communicate(topology_spec) + if proc.returncode != 0: + raise RuntimeError(f"Topology deployment failed with code {proc.returncode}: {stderr}") + except FileNotFoundError: + raise RuntimeError(f"Containerlab is required to use the clab builder.") def destroy_topology(self): pass From 6554de807d820ea7aa24fa1d5b561baa5fb67245 Mon Sep 17 00:00:00 2001 From: Leon Lux Date: Tue, 3 Dec 2024 21:47:57 +0100 Subject: [PATCH 17/19] add logging to build_topology --- builders/clab_builder.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/builders/clab_builder.py b/builders/clab_builder.py index 6eea298..03a9bc7 100644 --- a/builders/clab_builder.py +++ b/builders/clab_builder.py @@ -2,6 +2,7 @@ from topology import Topology import yaml import subprocess +import logging class TopologyDumper: @@ -39,9 +40,11 @@ def dump(self) -> str: class ClabBuilder(TopologyBuilder): def __init__(self, topology: Topology): super().__init__(topology) + self.logger = logging.getLogger(__name__) def build_topology(self): topology_spec = TopologyDumper(self.topology).dump() + self.logger.info(f"Attempting to build topology {self.topology.name} with Containerlab...") try: proc = subprocess.Popen( f"clab deploy --topo -", @@ -52,8 +55,11 @@ def build_topology(self): ) stdout, stderr = proc.communicate(topology_spec) if proc.returncode != 0: + self.logger.info(f"Error creating topology {self.topology.name}: {stderr}") raise RuntimeError(f"Topology deployment failed with code {proc.returncode}: {stderr}") + self.logger.info(f"Successfully built topology {self.topology.name}") except FileNotFoundError: + self.logger.info(f"Containerlab not installed. Aborting topology creation") raise RuntimeError(f"Containerlab is required to use the clab builder.") def destroy_topology(self): From c1cfa9445e8d37476906f6e6c5a53b4182a64480 Mon Sep 17 00:00:00 2001 From: Leon Lux Date: Tue, 3 Dec 2024 21:52:32 +0100 Subject: [PATCH 18/19] implement containerlab destruction --- builders/clab_builder.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/builders/clab_builder.py b/builders/clab_builder.py index 03a9bc7..08feff4 100644 --- a/builders/clab_builder.py +++ b/builders/clab_builder.py @@ -57,10 +57,30 @@ def build_topology(self): if proc.returncode != 0: self.logger.info(f"Error creating topology {self.topology.name}: {stderr}") raise RuntimeError(f"Topology deployment failed with code {proc.returncode}: {stderr}") + self.logger.info(f"Successfully built topology {self.topology.name}") + except FileNotFoundError: self.logger.info(f"Containerlab not installed. Aborting topology creation") raise RuntimeError(f"Containerlab is required to use the clab builder.") def destroy_topology(self): - pass + topology_spec = TopologyDumper(self.topology).dump() + self.logger.info(f"Attempting to destroy Containerlab topology {self.topology.name}...") + try: + proc = subprocess.Popen( + f"clab destroy --cleanup --topo -", + stdin = subprocess.PIPE, + stdout = subprocess.PIPE, + stderr = subprocess.PIPE, + text = True + ) + stdout, stderr = proc.communicate(topology_spec) + if proc.returncode != 0: + self.logger.info(f"Error destroying topology {self.topology.name}: {stderr}") + raise RuntimeError(f"Topology destruction failed with code {proc.returncode}: {stderr}") + + self.logger.info(f"Successfully removed topology {self.topology.name}") + + except FileNotFoundError: + self.logger.info(f"Containerlab not installed. Aborting topology destruction") From e70252fd0f1b4a8c844d5ede91f9100ac8729f62 Mon Sep 17 00:00:00 2001 From: Leon Lux Date: Tue, 3 Dec 2024 21:55:29 +0100 Subject: [PATCH 19/19] formatting and codestyle --- builders/clab_builder.py | 78 ++++++++++++++++++++++++++-------------- topology/topology.py | 4 +-- 2 files changed, 53 insertions(+), 29 deletions(-) diff --git a/builders/clab_builder.py b/builders/clab_builder.py index 08feff4..fba5510 100644 --- a/builders/clab_builder.py +++ b/builders/clab_builder.py @@ -11,25 +11,25 @@ def __init__(self, topology: Topology): def __to_dict(self): return { - 'name': self.topology.name, - 'topology': { - 'nodes': { + "name": self.topology.name, + "topology": { + "nodes": { node.name: { - 'kind': node.kind, - 'image': f"{node.kind}:latest", + "kind": node.kind, + "image": f"{node.kind}:latest", } for node in self.topology.nodes }, - 'links': [ + "links": [ { - 'endpoints': [ + "endpoints": [ f"{link.name_from}:{link.interface_from}", f"{link.name_to}:{link.interface_to}", ] } for link in self.topology.links - ] - } + ], + }, } def dump(self) -> str: @@ -44,43 +44,67 @@ def __init__(self, topology: Topology): def build_topology(self): topology_spec = TopologyDumper(self.topology).dump() - self.logger.info(f"Attempting to build topology {self.topology.name} with Containerlab...") + self.logger.info( + f"Building topology {self.topology.name} with Containerlab..." + ) try: proc = subprocess.Popen( - f"clab deploy --topo -", + "clab deploy --topo -", stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - text=True + text=True, ) stdout, stderr = proc.communicate(topology_spec) if proc.returncode != 0: - self.logger.info(f"Error creating topology {self.topology.name}: {stderr}") - raise RuntimeError(f"Topology deployment failed with code {proc.returncode}: {stderr}") + self.logger.info( + f"Error creating topology {self.topology.name}: {stderr}" + ) + raise RuntimeError( + f"Topology deployment failed. " + f"Code: {proc.returncode}: {stderr}" + ) - self.logger.info(f"Successfully built topology {self.topology.name}") + self.logger.info( + f"Successfully built topology {self.topology.name}" + ) except FileNotFoundError: - self.logger.info(f"Containerlab not installed. Aborting topology creation") - raise RuntimeError(f"Containerlab is required to use the clab builder.") + self.logger.info( + "Containerlab not installed. Aborting topology creation" + ) + raise RuntimeError( + "Containerlab is required to use the clab builder." + ) def destroy_topology(self): topology_spec = TopologyDumper(self.topology).dump() - self.logger.info(f"Attempting to destroy Containerlab topology {self.topology.name}...") + self.logger.info( + f"Destroying Containerlab topology {self.topology.name}..." + ) try: proc = subprocess.Popen( - f"clab destroy --cleanup --topo -", - stdin = subprocess.PIPE, - stdout = subprocess.PIPE, - stderr = subprocess.PIPE, - text = True + "clab destroy --cleanup --topo -", + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, ) stdout, stderr = proc.communicate(topology_spec) if proc.returncode != 0: - self.logger.info(f"Error destroying topology {self.topology.name}: {stderr}") - raise RuntimeError(f"Topology destruction failed with code {proc.returncode}: {stderr}") + self.logger.info( + f"Error destroying topology {self.topology.name}: {stderr}" + ) + raise RuntimeError( + f"Topology destruction failed. " + f"Code: {proc.returncode}: {stderr}" + ) - self.logger.info(f"Successfully removed topology {self.topology.name}") + self.logger.info( + f"Successfully removed topology {self.topology.name}" + ) except FileNotFoundError: - self.logger.info(f"Containerlab not installed. Aborting topology destruction") + self.logger.info( + "Containerlab not installed. Aborting topology destruction" + ) diff --git a/topology/topology.py b/topology/topology.py index 87f5749..0298f90 100644 --- a/topology/topology.py +++ b/topology/topology.py @@ -7,7 +7,7 @@ class Topology: def __init__(self): - self.name = '' + self.name = "" self.nodes: List[Node] = [] self.links: List[Link] = [] @@ -41,7 +41,7 @@ def name(self, name: str) -> None: self._topo.name = name def clear(self) -> None: - self._topo.name = '' + self._topo.name = "" self._topo.nodes = [] self._topo.links = []