From 55a9abfcea65f9e926b9852b7c26035a5b7341d4 Mon Sep 17 00:00:00 2001
From: grossmj <grossmj@gns3.net>
Date: Mon, 26 Aug 2019 16:48:03 +0700
Subject: [PATCH] Allow "none" for compute_id in templates.

---
 gns3server/controller/node.py                 |  4 +--
 gns3server/controller/project.py              |  6 +++-
 gns3server/controller/template.py             |  6 +++-
 gns3server/controller/template_manager.py     | 12 +++----
 gns3server/schemas/ethernet_hub_template.py   |  2 +-
 .../schemas/ethernet_switch_template.py       |  2 +-
 gns3server/schemas/template.py                |  2 +-
 gns3server/schemas/vpcs_template.py           |  2 +-
 tests/controller/test_project.py              | 31 +++++++++++++++++++
 9 files changed, 53 insertions(+), 14 deletions(-)

diff --git a/gns3server/controller/node.py b/gns3server/controller/node.py
index c50aad9f..a992fe42 100644
--- a/gns3server/controller/node.py
+++ b/gns3server/controller/node.py
@@ -650,14 +650,14 @@ class Node:
         elif self._node_type in ("ethernet_switch", "ethernet_hub"):
             # Basic node we don't want to have adapter number
             port_number = 0
-            for port in self._properties["ports_mapping"]:
+            for port in self._properties.get("ports_mapping", []):
                 self._ports.append(PortFactory(port["name"], 0, 0, port_number, "ethernet", short_name="e{}".format(port_number)))
                 port_number += 1
         elif self._node_type in ("vpcs", "traceng"):
             self._ports.append(PortFactory("Ethernet0", 0, 0, 0, "ethernet", short_name="e0"))
         elif self._node_type in ("cloud", "nat"):
             port_number = 0
-            for port in self._properties["ports_mapping"]:
+            for port in self._properties.get("ports_mapping", []):
                 self._ports.append(PortFactory(port["name"], 0, 0, port_number, "ethernet", short_name=port["name"]))
                 port_number += 1
         else:
diff --git a/gns3server/controller/project.py b/gns3server/controller/project.py
index c8bbd45e..ad058832 100644
--- a/gns3server/controller/project.py
+++ b/gns3server/controller/project.py
@@ -503,7 +503,11 @@ class Project:
         template["x"] = x
         template["y"] = y
         node_type = template.pop("template_type")
-        compute = self.controller.get_compute(template.pop("compute_id", compute_id))
+        if template["builtin"] is True:
+            # compute_id is selected by clients for builtin templates
+            compute = self.controller.get_compute(compute_id)
+        else:
+            compute = self.controller.get_compute(template.pop("compute_id", compute_id))
         name = template.pop("name")
         default_name_format = template.pop("default_name_format", "{name}-{0}")
         name = default_name_format.replace("{name}", name)
diff --git a/gns3server/controller/template.py b/gns3server/controller/template.py
index 9d2b6bd8..968acd97 100644
--- a/gns3server/controller/template.py
+++ b/gns3server/controller/template.py
@@ -203,7 +203,11 @@ class Template:
         settings.update({"template_id": self._id,
                          "builtin": self.builtin})
 
-        if not self.builtin:
+        if self.builtin:
+            # builin templates have compute_id set to None to tell clients
+            # to select a compute
+            settings["compute_id"] = None
+        else:
             settings["compute_id"] = self.compute_id
 
         return settings
diff --git a/gns3server/controller/template_manager.py b/gns3server/controller/template_manager.py
index 63239fde..1af13be6 100644
--- a/gns3server/controller/template_manager.py
+++ b/gns3server/controller/template_manager.py
@@ -60,13 +60,13 @@ class TemplateManager:
 
         # Add builtins
         builtins = []
-        builtins.append(Template(uuid.uuid3(uuid.NAMESPACE_DNS, "cloud"), {"template_type": "cloud", "name": "Cloud", "category": 2, "symbol": ":/symbols/cloud.svg"}, builtin=True))
-        builtins.append(Template(uuid.uuid3(uuid.NAMESPACE_DNS, "nat"), {"template_type": "nat", "name": "NAT", "category": 2, "symbol": ":/symbols/cloud.svg"}, builtin=True))
+        builtins.append(Template(uuid.uuid3(uuid.NAMESPACE_DNS, "cloud"), {"template_type": "cloud", "name": "Cloud", "default_name_format": "Cloud-{0}", "category": 2, "symbol": ":/symbols/cloud.svg"}, builtin=True))
+        builtins.append(Template(uuid.uuid3(uuid.NAMESPACE_DNS, "nat"), {"template_type": "nat", "name": "NAT", "default_name_format": "NAT-{0}", "category": 2, "symbol": ":/symbols/cloud.svg"}, builtin=True))
         builtins.append(Template(uuid.uuid3(uuid.NAMESPACE_DNS, "vpcs"), {"template_type": "vpcs", "name": "VPCS", "default_name_format": "PC-{0}", "category": 2, "symbol": ":/symbols/vpcs_guest.svg", "properties": {"base_script_file": "vpcs_base_config.txt"}}, builtin=True))
-        builtins.append(Template(uuid.uuid3(uuid.NAMESPACE_DNS, "ethernet_switch"), {"template_type": "ethernet_switch", "console_type": "none", "name": "Ethernet switch", "category": 1, "symbol": ":/symbols/ethernet_switch.svg"}, builtin=True))
-        builtins.append(Template(uuid.uuid3(uuid.NAMESPACE_DNS, "ethernet_hub"), {"template_type": "ethernet_hub", "name": "Ethernet hub", "category": 1, "symbol": ":/symbols/hub.svg"}, builtin=True))
-        builtins.append(Template(uuid.uuid3(uuid.NAMESPACE_DNS, "frame_relay_switch"), {"template_type": "frame_relay_switch", "name": "Frame Relay switch", "category": 1, "symbol": ":/symbols/frame_relay_switch.svg"}, builtin=True))
-        builtins.append(Template(uuid.uuid3(uuid.NAMESPACE_DNS, "atm_switch"), {"template_type": "atm_switch", "name": "ATM switch", "category": 1, "symbol": ":/symbols/atm_switch.svg"}, builtin=True))
+        builtins.append(Template(uuid.uuid3(uuid.NAMESPACE_DNS, "ethernet_switch"), {"template_type": "ethernet_switch", "console_type": "none", "name": "Ethernet switch", "default_name_format": "Switch-{0}", "category": 1, "symbol": ":/symbols/ethernet_switch.svg"}, builtin=True))
+        builtins.append(Template(uuid.uuid3(uuid.NAMESPACE_DNS, "ethernet_hub"), {"template_type": "ethernet_hub", "name": "Ethernet hub", "default_name_format": "Hub-{0}", "category": 1, "symbol": ":/symbols/hub.svg"}, builtin=True))
+        builtins.append(Template(uuid.uuid3(uuid.NAMESPACE_DNS, "frame_relay_switch"), {"template_type": "frame_relay_switch", "name": "Frame Relay switch", "default_name_format": "FRSW-{0}", "category": 1, "symbol": ":/symbols/frame_relay_switch.svg"}, builtin=True))
+        builtins.append(Template(uuid.uuid3(uuid.NAMESPACE_DNS, "atm_switch"), {"template_type": "atm_switch", "name": "ATM switch", "default_name_format": "ATMSW-{0}", "category": 1, "symbol": ":/symbols/atm_switch.svg"}, builtin=True))
 
         #FIXME: disable TraceNG
         #if sys.platform.startswith("win"):
diff --git a/gns3server/schemas/ethernet_hub_template.py b/gns3server/schemas/ethernet_hub_template.py
index f7a5edcd..221ed68b 100644
--- a/gns3server/schemas/ethernet_hub_template.py
+++ b/gns3server/schemas/ethernet_hub_template.py
@@ -68,7 +68,7 @@ ETHERNET_HUB_TEMPLATE_PROPERTIES = {
 
 ETHERNET_HUB_TEMPLATE_PROPERTIES.update(copy.deepcopy(BASE_TEMPLATE_PROPERTIES))
 ETHERNET_HUB_TEMPLATE_PROPERTIES["category"]["default"] = "switch"
-ETHERNET_HUB_TEMPLATE_PROPERTIES["default_name_format"]["default"] = "Hub{0}"
+ETHERNET_HUB_TEMPLATE_PROPERTIES["default_name_format"]["default"] = "Hub-{0}"
 ETHERNET_HUB_TEMPLATE_PROPERTIES["symbol"]["default"] = ":/symbols/hub.svg"
 
 ETHERNET_HUB_TEMPLATE_OBJECT_SCHEMA = {
diff --git a/gns3server/schemas/ethernet_switch_template.py b/gns3server/schemas/ethernet_switch_template.py
index e2473220..1bd2680f 100644
--- a/gns3server/schemas/ethernet_switch_template.py
+++ b/gns3server/schemas/ethernet_switch_template.py
@@ -115,7 +115,7 @@ ETHERNET_SWITCH_TEMPLATE_PROPERTIES = {
 
 ETHERNET_SWITCH_TEMPLATE_PROPERTIES.update(copy.deepcopy(BASE_TEMPLATE_PROPERTIES))
 ETHERNET_SWITCH_TEMPLATE_PROPERTIES["category"]["default"] = "switch"
-ETHERNET_SWITCH_TEMPLATE_PROPERTIES["default_name_format"]["default"] = "Switch{0}"
+ETHERNET_SWITCH_TEMPLATE_PROPERTIES["default_name_format"]["default"] = "Switch-{0}"
 ETHERNET_SWITCH_TEMPLATE_PROPERTIES["symbol"]["default"] = ":/symbols/ethernet_switch.svg"
 
 ETHERNET_SWITCH_TEMPLATE_OBJECT_SCHEMA = {
diff --git a/gns3server/schemas/template.py b/gns3server/schemas/template.py
index 2914911f..e9d4cc1e 100644
--- a/gns3server/schemas/template.py
+++ b/gns3server/schemas/template.py
@@ -37,7 +37,7 @@ BASE_TEMPLATE_PROPERTIES = {
     },
     "compute_id": {
         "description": "Compute identifier",
-        "type": "string"
+        "type": ["null", "string"]
     },
     "default_name_format": {
         "description": "Default name format",
diff --git a/gns3server/schemas/vpcs_template.py b/gns3server/schemas/vpcs_template.py
index e0726b24..e6582b6b 100644
--- a/gns3server/schemas/vpcs_template.py
+++ b/gns3server/schemas/vpcs_template.py
@@ -40,7 +40,7 @@ VPCS_TEMPLATE_PROPERTIES = {
 
 VPCS_TEMPLATE_PROPERTIES.update(copy.deepcopy(BASE_TEMPLATE_PROPERTIES))
 VPCS_TEMPLATE_PROPERTIES["category"]["default"] = "guest"
-VPCS_TEMPLATE_PROPERTIES["default_name_format"]["default"] = "PC{0}"
+VPCS_TEMPLATE_PROPERTIES["default_name_format"]["default"] = "PC-{0}"
 VPCS_TEMPLATE_PROPERTIES["symbol"]["default"] = ":/symbols/vpcs_guest.svg"
 
 VPCS_TEMPLATE_OBJECT_SCHEMA = {
diff --git a/tests/controller/test_project.py b/tests/controller/test_project.py
index 5fcee71e..4daca8a3 100644
--- a/tests/controller/test_project.py
+++ b/tests/controller/test_project.py
@@ -236,6 +236,37 @@ def test_add_node_from_template(async_run, controller):
     project.emit_notification.assert_any_call("node.created", node.__json__())
 
 
+def test_add_builtin_node_from_template(async_run, controller):
+    """
+    For a local server we send the project path
+    """
+    compute = MagicMock()
+    compute.id = "local"
+    project = Project(controller=controller, name="Test")
+    project.emit_notification = MagicMock()
+    template = Template(str(uuid.uuid4()), {
+        "name": "Builtin-switch",
+        "template_type": "ethernet_switch",
+    }, builtin=True)
+    controller.template_manager.templates[template.id] = template
+    template.__json__()
+    controller._computes["local"] = compute
+
+    response = MagicMock()
+    response.json = {"console": 2048}
+    compute.post = AsyncioMagicMock(return_value=response)
+
+    node = async_run(project.add_node_from_template(template.id, x=23, y=12, compute_id="local"))
+    compute.post.assert_any_call('/projects', data={
+        "name": project._name,
+        "project_id": project._id,
+        "path": project._path
+    })
+
+    assert compute in project._project_created_on_compute
+    project.emit_notification.assert_any_call("node.created", node.__json__())
+
+
 def test_delete_node(async_run, controller):
     """
     For a local server we send the project path