diff --git a/gns3server/api/routes/compute/cloud_nodes.py b/gns3server/api/routes/compute/cloud_nodes.py index c220ea1c2..3c62dbb54 100644 --- a/gns3server/api/routes/compute/cloud_nodes.py +++ b/gns3server/api/routes/compute/cloud_nodes.py @@ -176,6 +176,7 @@ async def update_cloud_nio( """ nio = node.get_nio(port_number) + nio.filters.clear() if nio_data.filters: nio.filters = nio_data.filters await node.update_nio(port_number, nio) diff --git a/gns3server/api/routes/compute/docker_nodes.py b/gns3server/api/routes/compute/docker_nodes.py index bf1bb072c..978eff344 100644 --- a/gns3server/api/routes/compute/docker_nodes.py +++ b/gns3server/api/routes/compute/docker_nodes.py @@ -289,6 +289,7 @@ async def update_docker_node_nio( """ nio = node.get_nio(adapter_number) + nio.filters.clear() if nio_data.filters: nio.filters = nio_data.filters await node.adapter_update_nio_binding(adapter_number, nio) diff --git a/gns3server/api/routes/compute/dynamips_nodes.py b/gns3server/api/routes/compute/dynamips_nodes.py index 50ee7b45f..08eebb0d7 100644 --- a/gns3server/api/routes/compute/dynamips_nodes.py +++ b/gns3server/api/routes/compute/dynamips_nodes.py @@ -232,6 +232,7 @@ async def update_nio( """ nio = node.get_nio(adapter_number, port_number) + nio.filters.clear() if nio_data.filters: nio.filters = nio_data.filters await node.slot_update_nio_binding(adapter_number, port_number, nio) diff --git a/gns3server/api/routes/compute/iou_nodes.py b/gns3server/api/routes/compute/iou_nodes.py index f3b5b8fe8..4453c9bff 100644 --- a/gns3server/api/routes/compute/iou_nodes.py +++ b/gns3server/api/routes/compute/iou_nodes.py @@ -250,6 +250,7 @@ async def update_iou_node_nio( """ nio = node.get_nio(adapter_number, port_number) + nio.filters.clear() if nio_data.filters: nio.filters = nio_data.filters await node.adapter_update_nio_binding(adapter_number, port_number, nio) diff --git a/gns3server/api/routes/compute/nat_nodes.py b/gns3server/api/routes/compute/nat_nodes.py index 06788f6a8..03283c9b0 100644 --- a/gns3server/api/routes/compute/nat_nodes.py +++ b/gns3server/api/routes/compute/nat_nodes.py @@ -171,6 +171,7 @@ async def update_nat_node_nio( """ nio = node.get_nio(port_number) + nio.filters.clear() if nio_data.filters: nio.filters = nio_data.filters await node.update_nio(port_number, nio) diff --git a/gns3server/api/routes/compute/qemu_nodes.py b/gns3server/api/routes/compute/qemu_nodes.py index 1689fabe9..e8be9b003 100644 --- a/gns3server/api/routes/compute/qemu_nodes.py +++ b/gns3server/api/routes/compute/qemu_nodes.py @@ -304,10 +304,10 @@ async def update_qemu_node_nio( """ nio = node.get_nio(adapter_number) + nio.filters.clear() if nio_data.filters: nio.filters = nio_data.filters - if nio_data.suspend: - nio.suspend = nio_data.suspend + nio.suspend = nio_data.suspend await node.adapter_update_nio_binding(adapter_number, nio) return nio.asdict() diff --git a/gns3server/api/routes/compute/virtualbox_nodes.py b/gns3server/api/routes/compute/virtualbox_nodes.py index f676025e6..957f1635f 100644 --- a/gns3server/api/routes/compute/virtualbox_nodes.py +++ b/gns3server/api/routes/compute/virtualbox_nodes.py @@ -268,10 +268,10 @@ async def update_virtualbox_node_nio( """ nio = node.get_nio(adapter_number) + nio.filters.clear() if nio_data.filters: nio.filters = nio_data.filters - if nio_data.suspend: - nio.suspend = nio_data.suspend + nio.suspend = nio_data.suspend await node.adapter_update_nio_binding(adapter_number, nio) return nio.asdict() diff --git a/gns3server/api/routes/compute/vmware_nodes.py b/gns3server/api/routes/compute/vmware_nodes.py index 30f79902c..aecce0289 100644 --- a/gns3server/api/routes/compute/vmware_nodes.py +++ b/gns3server/api/routes/compute/vmware_nodes.py @@ -233,6 +233,7 @@ async def update_vmware_node_nio( """ nio = node.get_nio(adapter_number) + nio.filters.clear() if nio_data.filters: nio.filters = nio_data.filters await node.adapter_update_nio_binding(adapter_number, nio) diff --git a/gns3server/api/routes/compute/vpcs_nodes.py b/gns3server/api/routes/compute/vpcs_nodes.py index bd5181b67..545421d2e 100644 --- a/gns3server/api/routes/compute/vpcs_nodes.py +++ b/gns3server/api/routes/compute/vpcs_nodes.py @@ -235,6 +235,7 @@ async def update_vpcs_node_nio( """ nio = node.get_nio(port_number) + nio.filters.clear() if nio_data.filters: nio.filters = nio_data.filters await node.port_update_nio_binding(port_number, nio) diff --git a/gns3server/api/routes/controller/projects.py b/gns3server/api/routes/controller/projects.py index 37c7e35b4..99861230c 100644 --- a/gns3server/api/routes/controller/projects.py +++ b/gns3server/api/routes/controller/projects.py @@ -113,6 +113,7 @@ async def get_projects( ) async def create_project( project_data: schemas.ProjectCreate, + current_user: schemas.User = Depends(get_current_active_user), ) -> schemas.Project: """ Create a new project. @@ -121,7 +122,9 @@ async def create_project( """ controller = Controller.instance() - project = await controller.add_project(**jsonable_encoder(project_data, exclude_unset=True)) + project_dict = jsonable_encoder(project_data, exclude_unset=True) + project_dict["created_by"] = current_user.username + project = await controller.add_project(**project_dict) return project.asdict() diff --git a/gns3server/compute/nios/nio_udp.py b/gns3server/compute/nios/nio_udp.py index 32fe16b86..b7736a39e 100644 --- a/gns3server/compute/nios/nio_udp.py +++ b/gns3server/compute/nios/nio_udp.py @@ -78,5 +78,7 @@ class NIOUDP(NIO): "type": "nio_udp", "lport": self._lport, "rport": self._rport, - "rhost": self._rhost + "rhost": self._rhost, + "suspend": self._suspended, + "filters": self._filters } diff --git a/gns3server/compute/qemu/qemu_vm.py b/gns3server/compute/qemu/qemu_vm.py index f1e145696..cc666f030 100644 --- a/gns3server/compute/qemu/qemu_vm.py +++ b/gns3server/compute/qemu/qemu_vm.py @@ -2438,31 +2438,13 @@ class QemuVM(BaseNode): ) # we do not want any user networking back-end if no adapter is connected. # Each 32 PCI device we need to add a PCI bridge with max 9 bridges - # Count PCI devices: 4 default + disks (virtio/sata/nvme/scsi) + network adapters - pci_disk_devices = 0 - for drive in ["a", "b", "c", "d"]: - # config disk replaces hdd when enabled - if drive == "d" and self._create_config_disk: - continue - if getattr(self, "_hd{}_disk_image".format(drive)): - interface = getattr(self, "_hd{}_disk_interface".format(drive)) - # virtio, sata, nvme, and scsi each consume 1 PCI slot; ide and none don't - if interface not in ("none", "ide"): - pci_disk_devices += 1 - # config disk (replaces hdd) also consumes a PCI slot if created - if self._create_config_disk and self.config_disk_image: - interface = self.hdd_disk_interface if self.hdd_disk_interface != "none" else self.hda_disk_interface - if interface not in ("none", "ide"): - pci_disk_devices += 1 - - pci_devices = 4 + pci_disk_devices + len(self._ethernet_adapters) - pci_bridges = math.floor(pci_devices / 32) + # Reserve 32 devices on root pci_bridge, + # since the number of devices used by templates may differ significantly + # and pci_bridges also consume IDs. + # Move network devices to their own bridge + pci_devices_reserved = 32 pci_bridges_created = 0 - if pci_bridges >= 1: - if self._qemu_version and parse_version(self._qemu_version) < parse_version("2.4.0"): - raise QemuError("Qemu version 2.4 or later is required to run this VM with a large number of network adapters") - - pci_device_id = 4 + pci_bridges + pci_disk_devices # Bridges and disks consume PCI ports + pci_device_id = pci_devices_reserved for adapter_number, adapter in enumerate(self._ethernet_adapters): mac = int_to_macaddress(macaddress_to_int(self._mac_address) + adapter_number) diff --git a/gns3server/controller/project.py b/gns3server/controller/project.py index 6423cdeef..e73602d54 100644 --- a/gns3server/controller/project.py +++ b/gns3server/controller/project.py @@ -97,6 +97,7 @@ class Project: show_interface_labels=False, variables=None, supplier=None, + created_by=None, ): self._controller = controller @@ -117,6 +118,7 @@ class Project: self._show_interface_labels = show_interface_labels self._variables = variables self._supplier = supplier + self._created_by = created_by self._loading = False self._closing = False @@ -380,6 +382,21 @@ class Project: """ self._supplier = supplier + @property + def created_by(self): + """ + Username of the user who created the project + :return: str or None + """ + return self._created_by + + @created_by.setter + def created_by(self, created_by): + """ + Setter for the username of the user who created the project + """ + self._created_by = created_by + @property def auto_start(self): """ @@ -1327,6 +1344,7 @@ class Project: "show_interface_labels": self._show_interface_labels, "supplier": self._supplier, "variables": self._variables, + "created_by": self._created_by, } def __repr__(self): diff --git a/gns3server/controller/topology.py b/gns3server/controller/topology.py index 417848fd1..22dfc56ce 100644 --- a/gns3server/controller/topology.py +++ b/gns3server/controller/topology.py @@ -87,6 +87,7 @@ def project_to_topology(project): "show_interface_labels": project.show_interface_labels, "variables": project.variables, "supplier": project.supplier, + "created_by": project.created_by, "topology": {"nodes": [], "links": [], "computes": [], "drawings": []}, "type": "topology", "revision": GNS3_FILE_FORMAT_REVISION, diff --git a/gns3server/schemas/compute/nios.py b/gns3server/schemas/compute/nios.py index 686c85327..cf69f9e30 100644 --- a/gns3server/schemas/compute/nios.py +++ b/gns3server/schemas/compute/nios.py @@ -34,7 +34,7 @@ class UDPNIO(BaseModel): lport: int = Field(..., gt=0, le=65535, description="Local port") rhost: str = Field(..., description="Remote host") rport: int = Field(..., gt=0, le=65535, description="Remote port") - suspend: Optional[int] = Field(None, description="Suspend the NIO") + suspend: Optional[bool] = Field(None, description="Suspend the NIO") filters: Optional[dict] = Field(None, description="Packet filters") diff --git a/gns3server/schemas/controller/projects.py b/gns3server/schemas/controller/projects.py index 4d98e7c5b..711eabb6b 100644 --- a/gns3server/schemas/controller/projects.py +++ b/gns3server/schemas/controller/projects.py @@ -96,6 +96,7 @@ class Project(ProjectBase): name: Optional[str] = None status: Optional[ProjectStatus] = None filename: Optional[str] = None + created_by: Optional[str] = Field(None, description="Username of the user who created the project") class ProjectFile(BaseModel): diff --git a/gns3server/schemas/controller/topology.py b/gns3server/schemas/controller/topology.py index d62930eea..1b9dcfddc 100644 --- a/gns3server/schemas/controller/topology.py +++ b/gns3server/schemas/controller/topology.py @@ -66,6 +66,7 @@ class Topology(BaseModel): show_interface_labels: Optional[bool] = Field(None, description="Show interface labels on the drawing area") supplier: Optional[Supplier] = Field(None, description="Supplier of the project") variables: Optional[List[Variable]] = Field(None, description="Variables required to run the project") + created_by: Optional[str] = Field(None, description="Username of the user who created the project") def main(): diff --git a/tests/api/routes/compute/test_cloud_nodes.py b/tests/api/routes/compute/test_cloud_nodes.py index 18014eb92..7a3b6bbac 100644 --- a/tests/api/routes/compute/test_cloud_nodes.py +++ b/tests/api/routes/compute/test_cloud_nodes.py @@ -97,19 +97,25 @@ class TestCloudNodesRoutes: vm: dict ) -> None: - params = {"type": "nio_udp", - "lport": 4242, - "rport": 4343, - "rhost": "127.0.0.1"} + params = { + "type": "nio_udp", + "lport": 4242, + "rport": 4343, + "rhost": "127.0.0.1", + "filters": {"packet_loss": 10} + } - url = app.url_path_for("compute:create_cloud_nio", - project_id=vm["project_id"], - node_id=vm["node_id"], - adapter_number="0", - port_number="0") - await compute_client.post(url, json=params) + url = app.url_path_for( + "compute:create_cloud_nio", + project_id=vm["project_id"], + node_id=vm["node_id"], + adapter_number="0", + port_number="0") + response = await compute_client.post(url, json=params) + assert response.status_code == status.HTTP_201_CREATED + assert response.json()["filters"] == {"packet_loss": 10} + params["filters"].clear() - params["filters"] = {} url = app.url_path_for("compute:create_cloud_nio", project_id=vm["project_id"], node_id=vm["node_id"], @@ -118,6 +124,7 @@ class TestCloudNodesRoutes: response = await compute_client.put(url, json=params) assert response.status_code == status.HTTP_201_CREATED assert response.json()["type"] == "nio_udp" + assert response.json()["filters"] == {} async def test_cloud_delete_nio( diff --git a/tests/api/routes/compute/test_docker_nodes.py b/tests/api/routes/compute/test_docker_nodes.py index b36c29656..ebbba442f 100644 --- a/tests/api/routes/compute/test_docker_nodes.py +++ b/tests/api/routes/compute/test_docker_nodes.py @@ -218,7 +218,8 @@ class TestDockerNodesRoutes: "type": "nio_udp", "lport": 4242, "rport": 4343, - "rhost": "127.0.0.1" + "rhost": "127.0.0.1", + "filters": {"packet_loss": 10} } url = app.url_path_for("compute:create_docker_node_nio", @@ -228,6 +229,8 @@ class TestDockerNodesRoutes: port_number="0") response = await compute_client.post(url, json=params) assert response.status_code == status.HTTP_201_CREATED + assert response.json()["filters"] == {"packet_loss": 10} + params["filters"] = {} url = app.url_path_for("compute:update_docker_node_nio", project_id=vm["project_id"], @@ -237,7 +240,8 @@ class TestDockerNodesRoutes: with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.adapter_update_nio_binding"): response = await compute_client.put(url, json=params) assert response.status_code == status.HTTP_201_CREATED - + assert response.json()["type"] == "nio_udp" + assert response.json()["filters"] == {} async def test_docker_delete_nio(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None: diff --git a/tests/api/routes/compute/test_iou_nodes.py b/tests/api/routes/compute/test_iou_nodes.py index 8343ba330..b0de85b57 100644 --- a/tests/api/routes/compute/test_iou_nodes.py +++ b/tests/api/routes/compute/test_iou_nodes.py @@ -297,10 +297,13 @@ class TestIOUNodesRoutes: async def test_iou_nio_update_udp(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None: - params = {"type": "nio_udp", - "lport": 4242, - "rport": 4343, - "rhost": "127.0.0.1"} + params = { + "type": "nio_udp", + "lport": 4242, + "rport": 4343, + "rhost": "127.0.0.1", + "filters": {"packet_loss": 10} + } url = app.url_path_for("compute:create_iou_node_nio", project_id=vm["project_id"], @@ -308,8 +311,10 @@ class TestIOUNodesRoutes: adapter_number="1", port_number="0") - await compute_client.post(url, json=params) - params["filters"] = {} + response = await compute_client.post(url, json=params) + assert response.status_code == status.HTTP_201_CREATED + assert response.json()["filters"] == {"packet_loss": 10} + params["filters"].clear() url = app.url_path_for("compute:update_iou_node_nio", project_id=vm["project_id"], @@ -319,6 +324,7 @@ class TestIOUNodesRoutes: response = await compute_client.put(url, json=params) assert response.status_code == status.HTTP_201_CREATED assert response.json()["type"] == "nio_udp" + assert response.json()["filters"] == {} async def test_iou_nio_create_ethernet( diff --git a/tests/api/routes/compute/test_nat_nodes.py b/tests/api/routes/compute/test_nat_nodes.py index 636a538ad..fea31fa01 100644 --- a/tests/api/routes/compute/test_nat_nodes.py +++ b/tests/api/routes/compute/test_nat_nodes.py @@ -104,7 +104,8 @@ class TestNATNodesRoutes: "type": "nio_udp", "lport": 4242, "rport": 4343, - "rhost": "127.0.0.1" + "rhost": "127.0.0.1", + "filters": {"packet_loss": 10} } url = app.url_path_for("compute:create_nat_node_nio", @@ -113,8 +114,10 @@ class TestNATNodesRoutes: adapter_number="0", port_number="0") - await compute_client.post(url, json=params) - params["filters"] = {} + response = await compute_client.post(url, json=params) + assert response.status_code == status.HTTP_201_CREATED + assert response.json()["filters"] == {"packet_loss": 10} + params["filters"].clear() url = app.url_path_for("compute:update_nat_node_nio", project_id=vm["project_id"], @@ -124,7 +127,7 @@ class TestNATNodesRoutes: response = await compute_client.put(url, json=params) assert response.status_code == status.HTTP_201_CREATED assert response.json()["type"] == "nio_udp" - + assert response.json()["filters"] == {} async def test_nat_delete_nio(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None: diff --git a/tests/api/routes/compute/test_qemu_nodes.py b/tests/api/routes/compute/test_qemu_nodes.py index 464b5ddf8..6edcbec3a 100644 --- a/tests/api/routes/compute/test_qemu_nodes.py +++ b/tests/api/routes/compute/test_qemu_nodes.py @@ -344,7 +344,8 @@ class TestQemuNodesRoutes: "type": "nio_udp", "lport": 4242, "rport": 4343, - "rhost": "127.0.0.1" + "rhost": "127.0.0.1", + "filters": {"packet_loss": 10} } await compute_client.put( @@ -360,9 +361,11 @@ class TestQemuNodesRoutes: port_number="0" ) - await compute_client.post(url, json=params) - params["filters"] = {} - + response = await compute_client.post(url, json=params) + assert response.status_code == status.HTTP_201_CREATED + assert response.json()["filters"] == {"packet_loss": 10} + params["filters"].clear() + url = app.url_path_for( "compute:update_qemu_node_nio", project_id=qemu_vm["project_id"], @@ -373,6 +376,7 @@ class TestQemuNodesRoutes: response = await compute_client.put(url, json=params) assert response.status_code == status.HTTP_201_CREATED assert response.json()["type"] == "nio_udp" + assert response.json()["filters"] == {} async def test_qemu_delete_nio(self, app: FastAPI, compute_client: AsyncClient, qemu_vm: dict) -> None: diff --git a/tests/api/routes/compute/test_vpcs_nodes.py b/tests/api/routes/compute/test_vpcs_nodes.py index 0bf987817..294ab7df8 100644 --- a/tests/api/routes/compute/test_vpcs_nodes.py +++ b/tests/api/routes/compute/test_vpcs_nodes.py @@ -128,7 +128,8 @@ class TestVPCSNodesRoutes: "type": "nio_udp", "lport": 4242, "rport": 4343, - "rhost": "127.0.0.1" + "rhost": "127.0.0.1", + "filters": {"packet_loss": 10} } url = app.url_path_for("compute:create_vpcs_node_nio", @@ -140,8 +141,9 @@ class TestVPCSNodesRoutes: with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.add_ubridge_udp_connection"): response = await compute_client.post(url, json=params) assert response.status_code == status.HTTP_201_CREATED - - params["filters"] = {} + assert response.json()["filters"] == {"packet_loss": 10} + params["filters"].clear() + url = app.url_path_for("compute:update_vpcs_node_nio", project_id=vm["project_id"], node_id=vm["node_id"], @@ -150,7 +152,7 @@ class TestVPCSNodesRoutes: response = await compute_client.put(url, json=params) assert response.status_code == status.HTTP_201_CREATED assert response.json()["type"] == "nio_udp" - + assert response.json()["filters"] == {} async def test_vpcs_delete_nio(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None: diff --git a/tests/controller/test_project.py b/tests/controller/test_project.py index 3743e1d87..f4ba50ad8 100644 --- a/tests/controller/test_project.py +++ b/tests/controller/test_project.py @@ -81,10 +81,21 @@ async def test_json(): "grid_size": 75, "drawing_grid_size": 25, "supplier": None, - "variables": None + "variables": None, + "created_by": None } +@pytest.mark.asyncio +async def test_created_by(): + + with patch('gns3server.controller.project.Project.emit_controller_notification'): + p = Project(name="Test", created_by="admin") + + assert p.created_by == "admin" + assert p.asdict()["created_by"] == "admin" + + @pytest.mark.asyncio async def test_update(controller): diff --git a/tests/controller/test_topology.py b/tests/controller/test_topology.py index 3cf554dc8..9e1f72f82 100644 --- a/tests/controller/test_topology.py +++ b/tests/controller/test_topology.py @@ -59,7 +59,8 @@ async def test_project_to_topology_empty(tmpdir): "type": "topology", "supplier": None, "variables": None, - "version": __version__ + "version": __version__, + "created_by": None }