diff --git a/gns3server/controller/node.py b/gns3server/controller/node.py index 2046c7cd0..6135456d1 100644 --- a/gns3server/controller/node.py +++ b/gns3server/controller/node.py @@ -57,6 +57,7 @@ class Node: "ports", "category", "console_auto_start", + "netmiko_device_type", ] def __init__(self, project, compute, name, node_id=None, node_type=None, template_id=None, **kwargs): @@ -112,6 +113,7 @@ class Node: self._port_segment_size = 0 self._first_port_name = None self._console_auto_start = False + self._netmiko_device_type = None # This properties will be recomputed ignore_properties = ("width", "height", "hover_symbol") @@ -212,6 +214,14 @@ class Node: def console_auto_start(self, val): self._console_auto_start = val + @property + def netmiko_device_type(self): + return self._netmiko_device_type + + @netmiko_device_type.setter + def netmiko_device_type(self, val): + self._netmiko_device_type = val + @property def properties(self): return self._properties @@ -833,6 +843,7 @@ class Node: "console": self._console, "console_type": self._console_type, "console_auto_start": self._console_auto_start, + "netmiko_device_type": self._netmiko_device_type, "aux": self._aux, "aux_type": self._aux_type, "properties": self._properties, diff --git a/gns3server/schemas/controller/nodes.py b/gns3server/schemas/controller/nodes.py index 840bfdce6..14bb81c9e 100644 --- a/gns3server/schemas/controller/nodes.py +++ b/gns3server/schemas/controller/nodes.py @@ -116,6 +116,11 @@ class NodeBase(BaseModel): console_auto_start: Optional[bool] = Field( False, description="Automatically start the console when the node has started" ) + netmiko_device_type: Optional[str] = Field( + None, + description="Device type for Netmiko-based automation tools, overrides the template value", + pattern=r"^[a-z0-9_]+$", + ) aux: Optional[int] = Field(None, gt=0, le=65535, description="Auxiliary console TCP port") aux_type: Optional[ConsoleType] = None properties: Optional[dict] = Field(default_factory=dict, description="Properties specific to an emulator") diff --git a/tests/controller/test_node.py b/tests/controller/test_node.py index 27a02d4d7..2b9b9659a 100644 --- a/tests/controller/test_node.py +++ b/tests/controller/test_node.py @@ -142,6 +142,7 @@ def test_json(node, compute): "tags": [], "custom_adapters": [], "console_auto_start": False, + "netmiko_device_type": None, "ports": [ { "adapter_number": 0, @@ -179,6 +180,7 @@ def test_json(node, compute): "custom_adapters": [], "tags": [], "console_auto_start": False, + "netmiko_device_type": None, } @@ -383,6 +385,36 @@ async def test_update_only_controller(node, compute): assert not node._project.emit_notification.called +@pytest.mark.asyncio +async def test_update_netmiko_device_type(node, compute): + """ + netmiko_device_type is a controller-only property: updating it must not + call the compute and must be visible in the node json. + """ + + compute.put = AsyncioMagicMock() + node._project.emit_notification = AsyncioMagicMock() + node._project.dump = MagicMock() + + await node.update(netmiko_device_type="cisco_ios_telnet") + assert not compute.put.called + assert node.netmiko_device_type == "cisco_ios_telnet" + assert node.asdict()["netmiko_device_type"] == "cisco_ios_telnet" + + +def test_netmiko_device_type_from_template_kwargs(compute, project): + """ + A node created with the template properties as kwargs inherits + netmiko_device_type without sending it to the compute. + """ + + node = Node(project, compute, "test", node_type="vpcs", netmiko_device_type="nokia_srl") + assert node.netmiko_device_type == "nokia_srl" + # controller-only: must not leak into the compute properties + assert "netmiko_device_type" not in node.properties + assert node.asdict(topology_dump=True)["netmiko_device_type"] == "nokia_srl" + + @pytest.mark.asyncio async def test_update_no_changes(node, compute): """