diff --git a/gns3server/handlers/__init__.py b/gns3server/handlers/__init__.py index 9f824c16..8d2587c9 100644 --- a/gns3server/handlers/__init__.py +++ b/gns3server/handlers/__init__.py @@ -16,4 +16,3 @@ from gns3server.handlers.api import * from gns3server.handlers.upload_handler import UploadHandler - diff --git a/gns3server/handlers/api/dynamips_vm_handler.py b/gns3server/handlers/api/dynamips_vm_handler.py index c2855a29..79e7c4c0 100644 --- a/gns3server/handlers/api/dynamips_vm_handler.py +++ b/gns3server/handlers/api/dynamips_vm_handler.py @@ -30,6 +30,7 @@ from ...schemas.dynamips_vm import VM_CONFIGS_SCHEMA from ...modules.dynamips import Dynamips from ...modules.project_manager import ProjectManager + class DynamipsVMHandler: """ @@ -387,4 +388,3 @@ class DynamipsVMHandler: idlepc = yield from dynamips_manager.auto_idlepc(vm) response.set_status(200) response.json({"idlepc": idlepc}) - diff --git a/gns3server/handlers/upload_handler.py b/gns3server/handlers/upload_handler.py index 7bc6a5b7..2f5d9fd4 100644 --- a/gns3server/handlers/upload_handler.py +++ b/gns3server/handlers/upload_handler.py @@ -15,6 +15,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import os +import stat + +from ..config import Config from ..web.route import Route from ..schemas.version import VERSION_SCHEMA from ..version import __version__ @@ -30,5 +34,38 @@ class UploadHandler: api_version=None ) def index(request, response): - response.template("upload.html") + files = [] + for filename in os.listdir(UploadHandler.image_directory()): + if os.path.isfile(os.path.join(UploadHandler.image_directory(), filename)): + if filename[0] != ".": + files.append(filename) + response.template("upload.html", files=files, image_path=UploadHandler.image_directory()) + @classmethod + @Route.post( + r"/upload", + description="Manage upload of GNS3 images", + api_version=None + ) + def upload(request, response): + data = yield from request.post() + + destination_path = os.path.join(UploadHandler.image_directory(), data["file"].filename) + + try: + os.makedirs(UploadHandler.image_directory(), exist_ok=True) + with open(destination_path, "wb+") as f: + f.write(data["file"].file.read()) + print(destination_path) + st = os.stat(destination_path) + os.chmod(destination_path, st.st_mode | stat.S_IXUSR) + except OSError as e: + response.html("Could not upload file: {}".format(e)) + response.set_status(500) + return + response.redirect("/upload") + + @staticmethod + def image_directory(): + server_config = Config.instance().get_section_config("Server") + return os.path.expanduser(server_config.get("image_directory", "~/GNS3/images")) diff --git a/gns3server/modules/dynamips/__init__.py b/gns3server/modules/dynamips/__init__.py index 5c388f14..d8f70ab6 100644 --- a/gns3server/modules/dynamips/__init__.py +++ b/gns3server/modules/dynamips/__init__.py @@ -357,10 +357,10 @@ class Dynamips(BaseManager): raise DynamipsError("Could not create an UDP connection to {}:{}: {}".format(rhost, rport, e)) # check if we have an allocated NIO UDP auto #nio = node.hypervisor.get_nio_udp_auto(lport) - #if not nio: + # if not nio: # otherwise create an NIO UDP nio = NIOUDP(node.hypervisor, lport, rhost, rport) - #else: + # else: # nio.connect(rhost, rport) elif nio_settings["type"] == "nio_generic_ethernet": ethernet_device = nio_settings["ethernet_device"] @@ -462,9 +462,9 @@ class Dynamips(BaseManager): slot_id = int(name[-1]) adapter_name = value adapter = ADAPTER_MATRIX[adapter_name]() - if vm.slots[slot_id] and type(vm.slots[slot_id]) != type(adapter): + if vm.slots[slot_id] and not isinstance(vm.slots[slot_id], type(adapter)): yield from vm.slot_remove_binding(slot_id) - if type(vm.slots[slot_id]) != type(adapter): + if not isinstance(vm.slots[slot_id], type(adapter)): yield from vm.slot_add_binding(slot_id, adapter) elif name.startswith("slot") and value is None: slot_id = int(name[-1]) @@ -474,9 +474,9 @@ class Dynamips(BaseManager): wic_slot_id = int(name[-1]) wic_name = value wic = WIC_MATRIX[wic_name]() - if vm.slots[0].wics[wic_slot_id] and type(vm.slots[0].wics[wic_slot_id]) != type(wic): + if vm.slots[0].wics[wic_slot_id] and not isinstance(vm.slots[0].wics[wic_slot_id], type(wic)): yield from vm.uninstall_wic(wic_slot_id) - if type(vm.slots[0].wics[wic_slot_id]) != type(wic): + if not isinstance(vm.slots[0].wics[wic_slot_id], type(wic)): yield from vm.install_wic(wic_slot_id, wic) elif name.startswith("wic") and value is None: wic_slot_id = int(name[-1]) diff --git a/gns3server/modules/dynamips/nodes/atm_switch.py b/gns3server/modules/dynamips/nodes/atm_switch.py index 32867883..5c620221 100644 --- a/gns3server/modules/dynamips/nodes/atm_switch.py +++ b/gns3server/modules/dynamips/nodes/atm_switch.py @@ -31,6 +31,7 @@ log = logging.getLogger(__name__) class ATMSwitch(Device): + """ Dynamips ATM switch. @@ -79,7 +80,6 @@ class ATMSwitch(Device): new_name=new_name)) self._name = new_name - @property def nios(self): """ diff --git a/gns3server/modules/dynamips/nodes/bridge.py b/gns3server/modules/dynamips/nodes/bridge.py index 5f056101..174fbb86 100644 --- a/gns3server/modules/dynamips/nodes/bridge.py +++ b/gns3server/modules/dynamips/nodes/bridge.py @@ -25,6 +25,7 @@ from .device import Device class Bridge(Device): + """ Dynamips bridge. diff --git a/gns3server/modules/dynamips/nodes/device.py b/gns3server/modules/dynamips/nodes/device.py index cbf755bd..fb5b3595 100644 --- a/gns3server/modules/dynamips/nodes/device.py +++ b/gns3server/modules/dynamips/nodes/device.py @@ -17,6 +17,7 @@ class Device: + """ Base device for switches and hubs diff --git a/gns3server/modules/dynamips/nodes/ethernet_hub.py b/gns3server/modules/dynamips/nodes/ethernet_hub.py index 33c523ab..39a891ab 100644 --- a/gns3server/modules/dynamips/nodes/ethernet_hub.py +++ b/gns3server/modules/dynamips/nodes/ethernet_hub.py @@ -29,6 +29,7 @@ log = logging.getLogger(__name__) class EthernetHub(Bridge): + """ Dynamips Ethernet hub (based on Bridge) diff --git a/gns3server/modules/dynamips/nodes/ethernet_switch.py b/gns3server/modules/dynamips/nodes/ethernet_switch.py index b0e425b8..7a8d8abe 100644 --- a/gns3server/modules/dynamips/nodes/ethernet_switch.py +++ b/gns3server/modules/dynamips/nodes/ethernet_switch.py @@ -31,6 +31,7 @@ log = logging.getLogger(__name__) class EthernetSwitch(Device): + """ Dynamips Ethernet switch. diff --git a/gns3server/modules/dynamips/nodes/frame_relay_switch.py b/gns3server/modules/dynamips/nodes/frame_relay_switch.py index 16572871..74fdda1c 100644 --- a/gns3server/modules/dynamips/nodes/frame_relay_switch.py +++ b/gns3server/modules/dynamips/nodes/frame_relay_switch.py @@ -30,6 +30,7 @@ log = logging.getLogger(__name__) class FrameRelaySwitch(Device): + """ Dynamips Frame Relay switch. diff --git a/gns3server/schemas/dynamips_device.py b/gns3server/schemas/dynamips_device.py index 28ba32be..52a9ba53 100644 --- a/gns3server/schemas/dynamips_device.py +++ b/gns3server/schemas/dynamips_device.py @@ -63,7 +63,7 @@ DEVICE_UPDATE_SCHEMA = { "vlan": {"description": "VLAN number", "type": "integer", "minimum": 1 - }, + }, }, "required": ["port", "type", "vlan"], "additionalProperties": False @@ -108,7 +108,7 @@ DEVICE_OBJECT_SCHEMA = { "vlan": {"description": "VLAN number", "type": "integer", "minimum": 1 - }, + }, }, "required": ["port", "type", "vlan"], "additionalProperties": False @@ -306,7 +306,7 @@ DEVICE_NIO_SCHEMA = { "vlan": {"description": "VLAN number", "type": "integer", "minimum": 1 - }, + }, }, "required": ["type", "vlan"], "additionalProperties": False diff --git a/gns3server/templates/upload.html b/gns3server/templates/upload.html index c912e49d..e9ba8b0b 100644 --- a/gns3server/templates/upload.html +++ b/gns3server/templates/upload.html @@ -7,10 +7,10 @@
- {%if items%} -

Files on {{host}}

- {%for item in items%} -

{{path}}/{{item}}

+ {%if files%} +

Files on {{gns3_host}}

+ {%for file in files%} +

{{image_path}}/{{file}}

{%endfor%} {%endif%} {% endblock %} diff --git a/gns3server/web/response.py b/gns3server/web/response.py index 9bd453c6..42112fa9 100644 --- a/gns3server/web/response.py +++ b/gns3server/web/response.py @@ -46,7 +46,7 @@ class Response(aiohttp.web.Response): log.debug("%s", request.json) log.info("Response: %d %s", self.status, self.reason) log.debug(dict(self.headers)) - if hasattr(self, 'body') and self.body is not None: + if hasattr(self, 'body') and self.body is not None and self.headers["CONTENT-TYPE"] == "application/json": log.debug(json.loads(self.body.decode('utf-8'))) return super().start(request) @@ -90,3 +90,11 @@ class Response(aiohttp.web.Response): log.error("Invalid output query. JSON schema error: {}".format(e.message)) raise aiohttp.web.HTTPBadRequest(text="{}".format(e)) self.body = json.dumps(answer, indent=4, sort_keys=True).encode('utf-8') + + def redirect(self, url): + """ + Redirect to url + + :params url: Redirection URL + """ + raise aiohttp.web.HTTPFound(url) diff --git a/gns3server/web/route.py b/gns3server/web/route.py index 4ec4d2b8..93b10eed 100644 --- a/gns3server/web/route.py +++ b/gns3server/web/route.py @@ -82,6 +82,8 @@ class Route(object): output_schema = kw.get("output", {}) input_schema = kw.get("input", {}) api_version = kw.get("api_version", 1) + + # If it's a JSON api endpoint just register the endpoint an do nothing if api_version is None: cls._path = path else: @@ -107,6 +109,14 @@ class Route(object): @asyncio.coroutine def control_schema(request): # This block is executed at each method call + + # Non API call + if api_version is None: + response = Response(route=route, output_schema=output_schema) + yield from func(request, response) + return response + + # API call try: request = yield from parse_request(request, input_schema) response = Response(route=route, output_schema=output_schema) diff --git a/tests/handlers/api/base.py b/tests/handlers/api/base.py index d0f38878..d7c7f690 100644 --- a/tests/handlers/api/base.py +++ b/tests/handlers/api/base.py @@ -49,7 +49,7 @@ class Query: return "http://{}:{}{}".format(self._host, self._port, path) return "http://{}:{}/v{}{}".format(self._host, self._port, version, path) - def _fetch(self, method, path, body=None, api_version = 1, **kwargs): + def _fetch(self, method, path, body=None, api_version=1, **kwargs): """Fetch an url, parse the JSON and return response Options: diff --git a/tests/handlers/test_upload.py b/tests/handlers/test_upload.py index d53b6302..65869b4f 100644 --- a/tests/handlers/test_upload.py +++ b/tests/handlers/test_upload.py @@ -15,17 +15,33 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -""" -This test suite check /version endpoint -It's also used for unittest the HTTP implementation. -""" + +import aiohttp +import os +from unittest.mock import patch from gns3server.version import __version__ -def test_version_index_upload(server): +def test_index_upload(server): response = server.get('/upload', api_version=None) assert response.status == 200 html = response.html assert "GNS3 Server" in html assert "Select & Upload" in html + + +def test_upload(server, tmpdir): + + with open(str(tmpdir / "test"), "w+") as f: + f.write("TEST") + body = aiohttp.FormData() + body.add_field("file", open(str(tmpdir / "test"), "rb"), content_type="application/iou", filename="test2") + + with patch("gns3server.config.Config.get_section_config", return_value={"image_directory": str(tmpdir)}): + response = server.post('/upload', api_version=None, body=body, raw=True) + + with open(str(tmpdir / "test2")) as f: + assert f.read() == "TEST" + + assert "test2" in response.body.decode("utf-8")