Compare commits

...
18 Commits
Author SHA1 Message Date
Julien Duponchelle 976154fd0b 1.4.2 2016-02-17 19:38:34 +01:00
Julien Duponchelle dea68bcb28 Fix missing format in IOU export 2016-02-11 09:15:48 +01:00
Julien Duponchelle 1fb4d191c9 Fix number of arguments to the UDP errors on VBOX 2016-02-11 09:02:38 +01:00
grossmj 167a0b8435 Add verification when UDP tunnel is created in a VirtualBox VM. Ref #899. 2016-02-10 19:08:34 -07:00
grossmj 5842487cd0 Fixes VMware linked clone cleanup bug. Fixes #420. 2016-02-10 11:26:40 -07:00
Julien Duponchelle 942d07c8ce Removed docker support from 1.4 (drop unused code) 2016-02-08 13:40:14 +01:00
Julien Duponchelle 3fc64bced2 Fix a crash if you create a file named IOS in the image dir
Fix #419
2016-02-05 10:29:27 +01:00
Julien Duponchelle 373113545f Code cleanup 2016-02-05 10:07:13 +01:00
Julien Duponchelle 5bee927481 Disallow creating project with " in the path
It's not supported by dynamips.

Fix https://github.com/GNS3/gns3-gui/issues/987
2016-02-04 11:47:48 +01:00
grossmj 4f61443b20 Always look at the registry to find vmrun.exe on Windows. 2016-02-03 19:08:41 -07:00
grossmj 4673424da7 Check for VMware VIX library version. Fixes #413. 2016-02-03 18:15:33 -07:00
grossmj 40261ec99c Fixes VDE not working #345. 2016-02-03 16:26:15 -07:00
Jeremy Grossmann 3e4423f663 Merge pull request #418 from GNS3/no_spice
Do not list qemu binary with -spice in the name
2016-02-03 13:45:03 -08:00
Jeremy Grossmann 88e8c36d1c Merge pull request #416 from GNS3/command_line
Send command line used to start the VM to client
2016-02-03 11:10:37 -08:00
Julien Duponchelle 537122daba Do not list qemu binary with -spice in the name
It's create confusion and you can enable spice with the -spice
options.

Fix https://github.com/GNS3/gns3-gui/issues/981
2016-02-03 16:40:39 +01:00
Julien Duponchelle fd22cd8361 Send command line used to start the VM to client
Add a command_line attribute to the VM object with
the command line used to start the VM.

Now /start return the object in order to get
this new attribute. And the HTTP status code
is 200 instead of 204 because 204 disallow body.

Support:
* Qemu
* Dynamips
* IOU

Ref https://github.com/GNS3/gns3-gui/issues/513
2016-02-02 18:25:17 +01:00
Julien Duponchelle 71c3bda0a5 Fix crash if you have a { in your user name
Fix #414
2016-02-02 10:05:08 +01:00
Julien Duponchelle 53d60bc71a 1.4.2dev1 2016-02-01 17:48:10 +01:00
47 changed files with 182 additions and 1221 deletions

No files matched your search

+15
View File
@@ -1,5 +1,20 @@
# Change Log
## 1.4.2 17/02/2016
* Fix missing format in IOU export
* Fix number of arguments to the UDP errors on VBOX
* Add verification when UDP tunnel is created in a VirtualBox VM. Ref #899.
* Fixes VMware linked clone cleanup bug. Fixes #420.
* Removed docker support from 1.4 (drop unused code)
* Fix a crash if you create a file named IOS in the image dir
* Disallow creating project with " in the path
* Always look at the registry to find vmrun.exe on Windows.
* Check for VMware VIX library version. Fixes #413.
* Fixes VDE not working #345.
* Do not list qemu binary with -spice in the name
* Send command line used to start the VM to client
* Fix crash if you have a { in your user name
## 1.4.1 01/02/2016
* VMware raise error if version is not found
* For topologies before 1.4 manage qemu missing
-8
View File
@@ -1,8 +0,0 @@
Docker
---------------------
.. toctree::
:glob:
:maxdepth: 2
docker/*
-13
View File
@@ -1,13 +0,0 @@
/v1/docker/images
----------------------------------------------------------------------------------------------------------------------
.. contents::
GET /v1/docker/images
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Get all available Docker images
Response status codes
**********************
- **200**: Success
@@ -1,49 +0,0 @@
/v1/projects/{project_id}/docker/images
----------------------------------------------------------------------------------------------------------------------
.. contents::
POST /v1/projects/**{project_id}**/docker/images
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Create a new Docker container
Parameters
**********
- **project_id**: UUID for the project
Response status codes
**********************
- **400**: Invalid request
- **201**: Instance created
- **409**: Conflict
Input
*******
.. raw:: html
<table>
<tr> <th>Name</th> <th>Mandatory</th> <th>Type</th> <th>Description</th> </tr>
<tr><td>adapter_type</td> <td> </td> <td>string</td> <td>Docker adapter type</td> </tr>
<tr><td>adapters</td> <td> </td> <td>integer</td> <td>number of adapters</td> </tr>
<tr><td>console</td> <td> </td> <td>string</td> <td>console name</td> </tr>
<tr><td>imagename</td> <td> </td> <td>string</td> <td>Docker image name</td> </tr>
<tr><td>name</td> <td> </td> <td>string</td> <td>Docker container name</td> </tr>
<tr><td>startcmd</td> <td> </td> <td>string</td> <td>Docker CMD entry</td> </tr>
<tr><td>vm_id</td> <td> </td> <td></td> <td>Docker VM instance identifier</td> </tr>
</table>
Output
*******
.. raw:: html
<table>
<tr> <th>Name</th> <th>Mandatory</th> <th>Type</th> <th>Description</th> </tr>
<tr><td>adapter_type</td> <td> </td> <td>string</td> <td>Docker adapter type</td> </tr>
<tr><td>adapters</td> <td> </td> <td>integer</td> <td>number of adapters</td> </tr>
<tr><td>cid</td> <td> </td> <td>string</td> <td>Docker container ID</td> </tr>
<tr><td>image</td> <td> </td> <td>string</td> <td>Docker image name</td> </tr>
<tr><td>name</td> <td> </td> <td>string</td> <td>Docker container name</td> </tr>
<tr><td>project_id</td> <td>&#10004;</td> <td>string</td> <td>Project UUID</td> </tr>
<tr><td>vm_id</td> <td>&#10004;</td> <td>string</td> <td>Docker container instance UUID</td> </tr>
</table>
@@ -1,20 +0,0 @@
/v1/projects/{project_id}/docker/images/{id}
----------------------------------------------------------------------------------------------------------------------
.. contents::
DELETE /v1/projects/**{project_id}**/docker/images/**{id}**
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Delete a Docker container
Parameters
**********
- **id**: ID for the container
- **project_id**: UUID for the project
Response status codes
**********************
- **400**: Invalid request
- **404**: Instance doesn't exist
- **204**: Instance deleted
@@ -1,40 +0,0 @@
/v1/projects/{project_id}/docker/images/{id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio
----------------------------------------------------------------------------------------------------------------------
.. contents::
POST /v1/projects/**{project_id}**/docker/images/**{id}**/adapters/**{adapter_number:\d+}**/ports/**{port_number:\d+}**/nio
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Add a NIO to a Docker container
Parameters
**********
- **id**: ID of the container
- **adapter_number**: Adapter where the nio should be added
- **project_id**: UUID for the project
- **port_number**: Port on the adapter
Response status codes
**********************
- **400**: Invalid request
- **201**: NIO created
- **404**: Instance doesn't exist
DELETE /v1/projects/**{project_id}**/docker/images/**{id}**/adapters/**{adapter_number:\d+}**/ports/**{port_number:\d+}**/nio
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Remove a NIO from a Docker container
Parameters
**********
- **id**: ID of the container
- **adapter_number**: Adapter where the nio should be added
- **project_id**: UUID for the project
- **port_number**: Port on the adapter
Response status codes
**********************
- **400**: Invalid request
- **404**: Instance doesn't exist
- **204**: NIO deleted
@@ -1,50 +0,0 @@
/v1/projects/{project_id}/docker/images/{id}/reload
----------------------------------------------------------------------------------------------------------------------
.. contents::
POST /v1/projects/**{project_id}**/docker/images/**{id}**/reload
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Restart a Docker container
Parameters
**********
- **id**: ID of the container
- **project_id**: UUID of the project
Response status codes
**********************
- **400**: Invalid request
- **404**: Instance doesn't exist
- **204**: Instance restarted
Input
*******
.. raw:: html
<table>
<tr> <th>Name</th> <th>Mandatory</th> <th>Type</th> <th>Description</th> </tr>
<tr><td>adapter_type</td> <td> </td> <td>string</td> <td>Docker adapter type</td> </tr>
<tr><td>adapters</td> <td> </td> <td>integer</td> <td>number of adapters</td> </tr>
<tr><td>console</td> <td> </td> <td>string</td> <td>console name</td> </tr>
<tr><td>imagename</td> <td> </td> <td>string</td> <td>Docker image name</td> </tr>
<tr><td>name</td> <td> </td> <td>string</td> <td>Docker container name</td> </tr>
<tr><td>startcmd</td> <td> </td> <td>string</td> <td>Docker CMD entry</td> </tr>
<tr><td>vm_id</td> <td> </td> <td></td> <td>Docker VM instance identifier</td> </tr>
</table>
Output
*******
.. raw:: html
<table>
<tr> <th>Name</th> <th>Mandatory</th> <th>Type</th> <th>Description</th> </tr>
<tr><td>adapter_type</td> <td> </td> <td>string</td> <td>Docker adapter type</td> </tr>
<tr><td>adapters</td> <td> </td> <td>integer</td> <td>number of adapters</td> </tr>
<tr><td>cid</td> <td> </td> <td>string</td> <td>Docker container ID</td> </tr>
<tr><td>image</td> <td> </td> <td>string</td> <td>Docker image name</td> </tr>
<tr><td>name</td> <td> </td> <td>string</td> <td>Docker container name</td> </tr>
<tr><td>project_id</td> <td>&#10004;</td> <td>string</td> <td>Project UUID</td> </tr>
<tr><td>vm_id</td> <td>&#10004;</td> <td>string</td> <td>Docker container instance UUID</td> </tr>
</table>
@@ -1,50 +0,0 @@
/v1/projects/{project_id}/docker/images/{id}/start
----------------------------------------------------------------------------------------------------------------------
.. contents::
POST /v1/projects/**{project_id}**/docker/images/**{id}**/start
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Start a Docker container
Parameters
**********
- **id**: ID of the container
- **project_id**: UUID of the project
Response status codes
**********************
- **400**: Invalid request
- **404**: Instance doesn't exist
- **204**: Instance started
Input
*******
.. raw:: html
<table>
<tr> <th>Name</th> <th>Mandatory</th> <th>Type</th> <th>Description</th> </tr>
<tr><td>adapter_type</td> <td> </td> <td>string</td> <td>Docker adapter type</td> </tr>
<tr><td>adapters</td> <td> </td> <td>integer</td> <td>number of adapters</td> </tr>
<tr><td>console</td> <td> </td> <td>string</td> <td>console name</td> </tr>
<tr><td>imagename</td> <td> </td> <td>string</td> <td>Docker image name</td> </tr>
<tr><td>name</td> <td> </td> <td>string</td> <td>Docker container name</td> </tr>
<tr><td>startcmd</td> <td> </td> <td>string</td> <td>Docker CMD entry</td> </tr>
<tr><td>vm_id</td> <td> </td> <td></td> <td>Docker VM instance identifier</td> </tr>
</table>
Output
*******
.. raw:: html
<table>
<tr> <th>Name</th> <th>Mandatory</th> <th>Type</th> <th>Description</th> </tr>
<tr><td>adapter_type</td> <td> </td> <td>string</td> <td>Docker adapter type</td> </tr>
<tr><td>adapters</td> <td> </td> <td>integer</td> <td>number of adapters</td> </tr>
<tr><td>cid</td> <td> </td> <td>string</td> <td>Docker container ID</td> </tr>
<tr><td>image</td> <td> </td> <td>string</td> <td>Docker image name</td> </tr>
<tr><td>name</td> <td> </td> <td>string</td> <td>Docker container name</td> </tr>
<tr><td>project_id</td> <td>&#10004;</td> <td>string</td> <td>Project UUID</td> </tr>
<tr><td>vm_id</td> <td>&#10004;</td> <td>string</td> <td>Docker container instance UUID</td> </tr>
</table>
@@ -1,50 +0,0 @@
/v1/projects/{project_id}/docker/images/{id}/stop
----------------------------------------------------------------------------------------------------------------------
.. contents::
POST /v1/projects/**{project_id}**/docker/images/**{id}**/stop
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Stop a Docker container
Parameters
**********
- **id**: ID of the container
- **project_id**: UUID of the project
Response status codes
**********************
- **400**: Invalid request
- **404**: Instance doesn't exist
- **204**: Instance stopped
Input
*******
.. raw:: html
<table>
<tr> <th>Name</th> <th>Mandatory</th> <th>Type</th> <th>Description</th> </tr>
<tr><td>adapter_type</td> <td> </td> <td>string</td> <td>Docker adapter type</td> </tr>
<tr><td>adapters</td> <td> </td> <td>integer</td> <td>number of adapters</td> </tr>
<tr><td>console</td> <td> </td> <td>string</td> <td>console name</td> </tr>
<tr><td>imagename</td> <td> </td> <td>string</td> <td>Docker image name</td> </tr>
<tr><td>name</td> <td> </td> <td>string</td> <td>Docker container name</td> </tr>
<tr><td>startcmd</td> <td> </td> <td>string</td> <td>Docker CMD entry</td> </tr>
<tr><td>vm_id</td> <td> </td> <td></td> <td>Docker VM instance identifier</td> </tr>
</table>
Output
*******
.. raw:: html
<table>
<tr> <th>Name</th> <th>Mandatory</th> <th>Type</th> <th>Description</th> </tr>
<tr><td>adapter_type</td> <td> </td> <td>string</td> <td>Docker adapter type</td> </tr>
<tr><td>adapters</td> <td> </td> <td>integer</td> <td>number of adapters</td> </tr>
<tr><td>cid</td> <td> </td> <td>string</td> <td>Docker container ID</td> </tr>
<tr><td>image</td> <td> </td> <td>string</td> <td>Docker image name</td> </tr>
<tr><td>name</td> <td> </td> <td>string</td> <td>Docker container name</td> </tr>
<tr><td>project_id</td> <td>&#10004;</td> <td>string</td> <td>Project UUID</td> </tr>
<tr><td>vm_id</td> <td>&#10004;</td> <td>string</td> <td>Docker container instance UUID</td> </tr>
</table>
@@ -1,50 +0,0 @@
/v1/projects/{project_id}/docker/images/{id}/suspend
----------------------------------------------------------------------------------------------------------------------
.. contents::
POST /v1/projects/**{project_id}**/docker/images/**{id}**/suspend
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Pause a Docker container
Parameters
**********
- **id**: ID of the container
- **project_id**: UUID of the project
Response status codes
**********************
- **400**: Invalid request
- **404**: Instance doesn't exist
- **204**: Instance paused
Input
*******
.. raw:: html
<table>
<tr> <th>Name</th> <th>Mandatory</th> <th>Type</th> <th>Description</th> </tr>
<tr><td>adapter_type</td> <td> </td> <td>string</td> <td>Docker adapter type</td> </tr>
<tr><td>adapters</td> <td> </td> <td>integer</td> <td>number of adapters</td> </tr>
<tr><td>console</td> <td> </td> <td>string</td> <td>console name</td> </tr>
<tr><td>imagename</td> <td> </td> <td>string</td> <td>Docker image name</td> </tr>
<tr><td>name</td> <td> </td> <td>string</td> <td>Docker container name</td> </tr>
<tr><td>startcmd</td> <td> </td> <td>string</td> <td>Docker CMD entry</td> </tr>
<tr><td>vm_id</td> <td> </td> <td></td> <td>Docker VM instance identifier</td> </tr>
</table>
Output
*******
.. raw:: html
<table>
<tr> <th>Name</th> <th>Mandatory</th> <th>Type</th> <th>Description</th> </tr>
<tr><td>adapter_type</td> <td> </td> <td>string</td> <td>Docker adapter type</td> </tr>
<tr><td>adapters</td> <td> </td> <td>integer</td> <td>number of adapters</td> </tr>
<tr><td>cid</td> <td> </td> <td>string</td> <td>Docker container ID</td> </tr>
<tr><td>image</td> <td> </td> <td>string</td> <td>Docker image name</td> </tr>
<tr><td>name</td> <td> </td> <td>string</td> <td>Docker container name</td> </tr>
<tr><td>project_id</td> <td>&#10004;</td> <td>string</td> <td>Project UUID</td> </tr>
<tr><td>vm_id</td> <td>&#10004;</td> <td>string</td> <td>Docker container instance UUID</td> </tr>
</table>
-1
View File
@@ -24,7 +24,6 @@ from gns3server.handlers.api.dynamips_device_handler import DynamipsDeviceHandle
from gns3server.handlers.api.dynamips_vm_handler import DynamipsVMHandler
from gns3server.handlers.api.qemu_handler import QEMUHandler
from gns3server.handlers.api.virtualbox_handler import VirtualBoxHandler
from gns3server.handlers.api.docker_handler import DockerHandler
from gns3server.handlers.api.vpcs_handler import VPCSHandler
from gns3server.handlers.api.vmware_handler import VMwareHandler
from gns3server.handlers.api.config_handler import ConfigHandler
-247
View File
@@ -1,247 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from aiohttp.web import HTTPConflict
from ...web.route import Route
from ...modules.docker import Docker
from ...schemas.docker import (
DOCKER_CREATE_SCHEMA, DOCKER_UPDATE_SCHEMA, DOCKER_CAPTURE_SCHEMA,
DOCKER_OBJECT_SCHEMA
)
from ...schemas.nio import NIO_SCHEMA
class DockerHandler:
"""API entry points for Docker."""
@classmethod
@Route.get(
r"/docker/images",
status_codes={
200: "Success",
},
description="Get all available Docker images")
def show(request, response):
docker_manager = Docker.instance()
images = yield from docker_manager.list_images()
response.json(images)
@classmethod
@Route.post(
r"/projects/{project_id}/docker/images",
parameters={
"project_id": "UUID for the project"
},
status_codes={
201: "Instance created",
400: "Invalid request",
409: "Conflict"
},
description="Create a new Docker container",
input=DOCKER_CREATE_SCHEMA,
output=DOCKER_OBJECT_SCHEMA)
def create(request, response):
docker_manager = Docker.instance()
container = yield from docker_manager.create_vm(
request.json.pop("name"),
request.match_info["project_id"],
request.json.get("id"),
image=request.json.pop("imagename"),
startcmd=request.json.get("startcmd")
)
# FIXME: DO WE NEED THIS?
for name, value in request.json.items():
if name != "_vm_id":
if hasattr(container, name) and getattr(container, name) != value:
setattr(container, name, value)
response.set_status(201)
response.json(container)
@classmethod
@Route.post(
r"/projects/{project_id}/docker/images/{id}/start",
parameters={
"project_id": "UUID of the project",
"id": "ID of the container"
},
status_codes={
204: "Instance started",
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Start a Docker container",
input=DOCKER_CREATE_SCHEMA,
output=DOCKER_OBJECT_SCHEMA)
def start(request, response):
docker_manager = Docker.instance()
container = docker_manager.get_container(
request.match_info["id"],
project_id=request.match_info["project_id"])
yield from container.start()
response.set_status(204)
@classmethod
@Route.post(
r"/projects/{project_id}/docker/images/{id}/stop",
parameters={
"project_id": "UUID of the project",
"id": "ID of the container"
},
status_codes={
204: "Instance stopped",
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Stop a Docker container",
input=DOCKER_CREATE_SCHEMA,
output=DOCKER_OBJECT_SCHEMA)
def stop(request, response):
docker_manager = Docker.instance()
container = docker_manager.get_container(
request.match_info["id"],
project_id=request.match_info["project_id"])
yield from container.stop()
response.set_status(204)
@classmethod
@Route.post(
r"/projects/{project_id}/docker/images/{id}/reload",
parameters={
"project_id": "UUID of the project",
"id": "ID of the container"
},
status_codes={
204: "Instance restarted",
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Restart a Docker container",
input=DOCKER_CREATE_SCHEMA,
output=DOCKER_OBJECT_SCHEMA)
def reload(request, response):
docker_manager = Docker.instance()
container = docker_manager.get_container(
request.match_info["id"],
project_id=request.match_info["project_id"])
yield from container.restart()
response.set_status(204)
@classmethod
@Route.delete(
r"/projects/{project_id}/docker/images/{id}",
parameters={
"id": "ID for the container",
"project_id": "UUID for the project"
},
status_codes={
204: "Instance deleted",
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Delete a Docker container")
def delete(request, response):
docker_manager = Docker.instance()
container = docker_manager.get_container(
request.match_info["id"],
project_id=request.match_info["project_id"])
yield from container.remove()
response.set_status(204)
@classmethod
@Route.post(
r"/projects/{project_id}/docker/images/{id}/suspend",
parameters={
"project_id": "UUID of the project",
"id": "ID of the container"
},
status_codes={
204: "Instance paused",
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Pause a Docker container",
input=DOCKER_CREATE_SCHEMA,
output=DOCKER_OBJECT_SCHEMA)
def suspend(request, response):
docker_manager = Docker.instance()
container = docker_manager.get_container(
request.match_info["id"],
project_id=request.match_info["project_id"])
yield from container.pause()
response.set_status(204)
@Route.post(
r"/projects/{project_id}/docker/images/{id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
parameters={
"project_id": "UUID for the project",
"id": "ID of the container",
"adapter_number": "Adapter where the nio should be added",
"port_number": "Port on the adapter"
},
status_codes={
201: "NIO created",
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Add a NIO to a Docker container",
input=NIO_SCHEMA,
output=NIO_SCHEMA)
def create_nio(request, response):
docker_manager = Docker.instance()
container = docker_manager.get_container(
request.match_info["id"],
project_id=request.match_info["project_id"])
nio_type = request.json["type"]
if nio_type not in ("nio_udp"):
raise HTTPConflict(
text="NIO of type {} is not supported".format(nio_type))
nio = docker_manager.create_nio(
int(request.match_info["adapter_number"]), request.json)
adapter = container._ethernet_adapters[
int(request.match_info["adapter_number"])
]
container.adapter_add_nio_binding(
int(request.match_info["adapter_number"]), nio)
response.set_status(201)
response.json(nio)
@classmethod
@Route.delete(
r"/projects/{project_id}/docker/images/{id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
parameters={
"project_id": "UUID for the project",
"id": "ID of the container",
"adapter_number": "Adapter where the nio should be added",
"port_number": "Port on the adapter"
},
status_codes={
204: "NIO deleted",
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Remove a NIO from a Docker container")
def delete_nio(request, response):
docker_manager = Docker.instance()
container = docker_manager.get_container(
request.match_info["id"],
project_id=request.match_info["project_id"])
yield from container.adapter_remove_nio_binding(
int(request.match_info["adapter_number"]))
response.set_status(204)
+3 -2
View File
@@ -148,11 +148,12 @@ class IOUHandler:
"vm_id": "UUID for the instance"
},
status_codes={
204: "Instance started",
200: "Instance started",
400: "Invalid request",
404: "Instance doesn't exist"
},
input=IOU_START_SCHEMA,
output=IOU_OBJECT_SCHEMA,
description="Start a IOU instance")
def start(request, response):
@@ -166,7 +167,7 @@ class IOUHandler:
print(vm.iourc_path)
yield from vm.start()
response.set_status(204)
response.json(vm)
@classmethod
@Route.post(
+4 -3
View File
@@ -146,11 +146,12 @@ class QEMUHandler:
"vm_id": "UUID for the instance"
},
status_codes={
204: "Instance started",
200: "Instance started",
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Start a Qemu VM instance")
description="Start a Qemu VM instance",
output=QEMU_OBJECT_SCHEMA)
def start(request, response):
qemu_manager = Qemu.instance()
@@ -161,7 +162,7 @@ class QEMUHandler:
if pm.check_hardware_virtualization(vm) is False:
raise HTTPConflict(text="Cannot start VM with KVM enabled because hardware virtualization (VT-x/AMD-V) is already used by another software like VMware or VirtualBox")
yield from vm.start()
response.set_status(204)
response.json(vm)
@classmethod
@Route.post(
+3 -2
View File
@@ -130,13 +130,14 @@ class VPCSHandler:
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Start a VPCS instance")
description="Start a VPCS instance",
output=VPCS_OBJECT_SCHEMA)
def start(request, response):
vpcs_manager = VPCS.instance()
vm = vpcs_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
yield from vm.start()
response.set_status(204)
response.json(vm)
@classmethod
@Route.post(
+12
View File
@@ -58,6 +58,7 @@ class BaseVM:
self._hw_virtualization = False
self._ubridge_hypervisor = None
self._vm_status = "stopped"
self._command_line = ""
if self._console is not None:
if console_type == "vnc":
@@ -94,6 +95,17 @@ class BaseVM:
self._vm_status = status
self._project.emit("vm.{}".format(status), self)
@property
def command_line(self):
"""Return command used to start the VM"""
return self._command_line
@command_line.setter
def command_line(self, command_line):
self._command_line = command_line
@property
def project(self):
"""
-119
View File
@@ -1,119 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Docker server module.
"""
import asyncio
import logging
import aiohttp
import docker
from requests.exceptions import ConnectionError
log = logging.getLogger(__name__)
from ..base_manager import BaseManager
from ..project_manager import ProjectManager
from .docker_vm import Container
from .docker_error import DockerError
class Docker(BaseManager):
_VM_CLASS = Container
def __init__(self):
super().__init__()
# FIXME: make configurable and start docker before trying
self._server_url = 'unix://var/run/docker.sock'
self._client = docker.Client(base_url=self._server_url)
self._execute_lock = asyncio.Lock()
@property
def server_url(self):
"""Returns the Docker server url.
:returns: url
:rtype: string
"""
return self._server_url
@server_url.setter
def server_url(self, value):
self._server_url = value
self._client = docker.Client(base_url=value)
@asyncio.coroutine
def execute(self, command, kwargs, timeout=60):
command = getattr(self._client, command)
log.debug("Executing Docker with command: {}".format(command))
try:
result = command(**kwargs)
except Exception as error:
raise DockerError("Docker has returned an error: {}".format(error))
return result
@asyncio.coroutine
def list_images(self):
"""Gets Docker image list.
:returns: list of dicts
:rtype: list
"""
images = []
try:
for image in self._client.images():
for tag in image['RepoTags']:
images.append({'imagename': tag})
return images
except ConnectionError as error:
raise DockerError(
"""Docker couldn't list images and returned an error: {}
Is the Docker service running?""".format(error))
@asyncio.coroutine
def list_containers(self):
"""Gets Docker container list.
:returns: list of dicts
:rtype: list
"""
return self._client.containers()
def get_container(self, cid, project_id=None):
"""Returns a Docker container.
:param id: Docker container identifier
:param project_id: Project identifier
:returns: Docker container
"""
if project_id:
project = ProjectManager.instance().get_project(project_id)
if cid not in self._vms:
raise aiohttp.web.HTTPNotFound(
text="Docker container with ID {} doesn't exist".format(cid))
container = self._vms[cid]
if project_id:
if container.project.id != project.id:
raise aiohttp.web.HTTPNotFound(
text="Project ID {} doesn't belong to container {}".format(
project_id, container.name))
return container
-26
View File
@@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Custom exceptions for the Docker module.
"""
from ..vm_error import VMError
class DockerError(VMError):
pass
-403
View File
@@ -1,403 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Docker container instance.
"""
import asyncio
import shutil
import psutil
from docker.utils import create_host_config
from gns3server.ubridge.hypervisor import Hypervisor
from pkg_resources import parse_version
from .docker_error import DockerError
from ..base_vm import BaseVM
from ..adapters.ethernet_adapter import EthernetAdapter
from ..nios.nio_udp import NIOUDP
import logging
log = logging.getLogger(__name__)
class Container(BaseVM):
"""Docker container implementation.
:param name: Docker container name
:param vm_id: Docker VM identifier
:param project: Project instance
:param manager: Manager instance
:param image: Docker image
"""
def __init__(self, name, vm_id, project, manager, image, startcmd=None):
self._name = name
self._id = vm_id
self._project = project
self._manager = manager
self._image = image
self._startcmd = startcmd
self._veths = []
self._ethernet_adapters = []
self._ubridge_hypervisor = None
self._temporary_directory = None
self._hw_virtualization = False
log.debug(
"{module}: {name} [{image}] initialized.".format(
module=self.manager.module_name,
name=self.name,
image=self._image))
def __json__(self):
return {
"name": self._name,
"vm_id": self._id,
"cid": self._cid,
"project_id": self._project.id,
"image": self._image,
}
@property
def veths(self):
"""Returns Docker host veth interfaces."""
return self._veths
@asyncio.coroutine
def _get_container_state(self):
"""Returns the container state (e.g. running, paused etc.)
:returns: state
:rtype: str
"""
try:
result = yield from self.manager.execute(
"inspect_container", {"container": self._cid})
result_dict = {state.lower(): value for state, value in result["State"].items()}
for state, value in result_dict.items():
if value is True:
# a container can be both paused and running
if state == "paused":
return "paused"
if state == "running":
if "paused" in result_dict and result_dict["paused"] is True:
return "paused"
return state.lower()
return 'exited'
except Exception as err:
raise DockerError("Could not get container state for {0}: ".format(
self._name), str(err))
@asyncio.coroutine
def create(self):
"""Creates the Docker container."""
params = {
"name": self._name,
"image": self._image,
"network_disabled": True,
"host_config": create_host_config(
privileged=True, cap_add=['ALL'])
}
if self._startcmd:
params.update({'command': self._startcmd})
result = yield from self.manager.execute("create_container", params)
self._cid = result['Id']
log.info("Docker container '{name}' [{id}] created".format(
name=self._name, id=self._id))
return True
@property
def ubridge_path(self):
"""Returns the uBridge executable path.
:returns: path to uBridge
"""
path = self._manager.config.get_section_config("Server").get(
"ubridge_path", "ubridge")
if path == "ubridge":
path = shutil.which("ubridge")
return path
@asyncio.coroutine
def _start_ubridge(self):
"""Starts uBridge (handles connections to and from this Docker VM)."""
server_config = self._manager.config.get_section_config("Server")
server_host = server_config.get("host")
self._ubridge_hypervisor = Hypervisor(
self._project, self.ubridge_path, self.working_dir, server_host)
log.info("Starting new uBridge hypervisor {}:{}".format(
self._ubridge_hypervisor.host, self._ubridge_hypervisor.port))
yield from self._ubridge_hypervisor.start()
log.info("Hypervisor {}:{} has successfully started".format(
self._ubridge_hypervisor.host, self._ubridge_hypervisor.port))
yield from self._ubridge_hypervisor.connect()
if parse_version(
self._ubridge_hypervisor.version) < parse_version('0.9.1'):
raise DockerError(
"uBridge version must be >= 0.9.1, detected version is {}".format(
self._ubridge_hypervisor.version))
@asyncio.coroutine
def start(self):
"""Starts this Docker container."""
state = yield from self._get_container_state()
if state == "paused":
yield from self.unpause()
else:
result = yield from self.manager.execute(
"start", {"container": self._cid})
yield from self._start_ubridge()
for adapter_number in range(0, self.adapters):
nio = self._ethernet_adapters[adapter_number].get_nio(0)
if nio:
yield from self._add_ubridge_connection(nio, adapter_number)
log.info("Docker container '{name}' [{image}] started".format(
name=self._name, image=self._image))
def is_running(self):
"""Checks if the container is running.
:returns: True or False
:rtype: bool
"""
state = yield from self._get_container_state()
if state == "running":
return True
return False
@asyncio.coroutine
def restart(self):
"""Restarts this Docker container."""
result = yield from self.manager.execute(
"restart", {"container": self._cid})
log.info("Docker container '{name}' [{image}] restarted".format(
name=self._name, image=self._image))
@asyncio.coroutine
def stop(self):
"""Stops this Docker container."""
if self._ubridge_hypervisor and self._ubridge_hypervisor.is_running():
yield from self._ubridge_hypervisor.stop()
state = yield from self._get_container_state()
if state == "paused":
yield from self.unpause()
result = yield from self.manager.execute(
"kill", {"container": self._cid})
log.info("Docker container '{name}' [{image}] stopped".format(
name=self._name, image=self._image))
@asyncio.coroutine
def pause(self):
"""Pauses this Docker container."""
result = yield from self.manager.execute(
"pause", {"container": self._cid})
log.info("Docker container '{name}' [{image}] paused".format(
name=self._name, image=self._image))
@asyncio.coroutine
def unpause(self):
"""Unpauses this Docker container."""
result = yield from self.manager.execute(
"unpause", {"container": self._cid})
state = yield from self._get_container_state()
log.info("Docker container '{name}' [{image}] unpaused".format(
name=self._name, image=self._image))
@asyncio.coroutine
def remove(self):
"""Removes this Docker container."""
state = yield from self._get_container_state()
if state == "paused":
yield from self.unpause()
if state == "running":
yield from self.stop()
result = yield from self.manager.execute(
"remove_container", {"container": self._cid, "force": True})
log.info("Docker container '{name}' [{image}] removed".format(
name=self._name, image=self._image))
@asyncio.coroutine
def close(self):
"""Closes this Docker container."""
log.debug("Docker container '{name}' [{id}] is closing".format(
name=self.name, id=self._cid))
for adapter in self._ethernet_adapters.values():
if adapter is not None:
for nio in adapter.ports.values():
if nio and isinstance(nio, NIOUDP):
self.manager.port_manager.release_udp_port(
nio.lport, self._project)
yield from self.remove()
log.info("Docker container '{name}' [{id}] closed".format(
name=self.name, id=self._cid))
self._closed = True
def _add_ubridge_connection(self, nio, adapter_number):
"""
Creates a connection in uBridge.
:param nio: NIO instance
:param adapter_number: adapter number
"""
try:
adapter = self._ethernet_adapters[adapter_number]
except IndexError:
raise DockerError(
"Adapter {adapter_number} doesn't exist on Docker container '{name}'".format(
name=self.name, adapter_number=adapter_number))
if nio and isinstance(nio, NIOUDP):
for index in range(128):
if "gns3-veth{}ext".format(index) not in psutil.net_if_addrs():
adapter.ifc = "eth{}".format(str(index))
adapter.host_ifc = "gns3-veth{}ext".format(str(index))
adapter.guest_ifc = "gns3-veth{}int".format(str(index))
break
if not hasattr(adapter, "ifc"):
raise DockerError(
"Adapter {adapter_number} couldn't allocate interface on Docker container '{name}'".format(
name=self.name, adapter_number=adapter_number))
yield from self._ubridge_hypervisor.send(
'docker create_veth {hostif} {guestif}'.format(
guestif=adapter.guest_ifc, hostif=adapter.host_ifc))
self._veths.append(adapter.host_ifc)
namespace = yield from self.get_namespace()
yield from self._ubridge_hypervisor.send(
'docker move_to_ns {ifc} {ns}'.format(
ifc=adapter.guest_ifc, ns=namespace))
yield from self._ubridge_hypervisor.send(
'bridge create bridge{}'.format(adapter_number))
yield from self._ubridge_hypervisor.send(
'bridge add_nio_linux_raw bridge{adapter} {ifc}'.format(
ifc=adapter.host_ifc, adapter=adapter_number))
if isinstance(nio, NIOUDP):
yield from self._ubridge_hypervisor.send(
'bridge add_nio_udp bridge{adapter} {lport} {rhost} {rport}'.format(
adapter=adapter_number, lport=nio.lport, rhost=nio.rhost,
rport=nio.rport))
if nio.capturing:
yield from self._ubridge_hypervisor.send(
'bridge start_capture bridge{adapter} "{pcap_file}"'.format(
adapter=adapter_number, pcap_file=nio.pcap_output_file))
yield from self._ubridge_hypervisor.send(
'bridge start bridge{adapter}'.format(adapter=adapter_number))
def _delete_ubridge_connection(self, adapter_number):
"""Deletes a connection in uBridge.
:param adapter_number: adapter number
"""
yield from self._ubridge_hypervisor.send("bridge delete bridge{name}".format(
name=adapter_number))
adapter = self._ethernet_adapters[adapter_number]
yield from self._ubridge_hypervisor.send("docker delete_veth {name}".format(
name=adapter.host_ifc))
def adapter_add_nio_binding(self, adapter_number, nio):
"""Adds an adapter NIO binding.
:param adapter_number: adapter number
:param nio: NIO instance to add to the slot/port
"""
try:
adapter = self._ethernet_adapters[adapter_number]
except IndexError:
raise DockerError(
"Adapter {adapter_number} doesn't exist on Docker container '{name}'".format(
name=self.name, adapter_number=adapter_number))
adapter.add_nio(0, nio)
log.info(
"Docker container '{name}' [{id}]: {nio} added to adapter {adapter_number}".format(
name=self.name,
id=self._id,
nio=nio,
adapter_number=adapter_number))
def adapter_remove_nio_binding(self, adapter_number):
"""
Removes an adapter NIO binding.
:param adapter_number: adapter number
:returns: NIO instance
"""
try:
adapter = self._ethernet_adapters[adapter_number]
except IndexError:
raise DockerError(
"Adapter {adapter_number} doesn't exist on Docker VM '{name}'".format(
name=self.name, adapter_number=adapter_number))
adapter.remove_nio(0)
try:
yield from self._delete_ubridge_connection(adapter_number)
except:
pass
log.info(
"Docker VM '{name}' [{id}]: {nio} removed from adapter {adapter_number}".format(
name=self.name, id=self.id, nio=adapter.host_ifc,
adapter_number=adapter_number))
@property
def adapters(self):
"""Returns the number of Ethernet adapters for this Docker VM.
:returns: number of adapters
:rtype: int
"""
return len(self._ethernet_adapters)
@adapters.setter
def adapters(self, adapters):
"""Sets the number of Ethernet adapters for this Docker container.
:param adapters: number of adapters
"""
self._ethernet_adapters.clear()
for adapter_number in range(0, adapters):
self._ethernet_adapters.append(EthernetAdapter())
log.info(
'Docker container "{name}" [{id}]: number of Ethernet adapters changed to {adapters}'.format(
name=self._name,
id=self._id,
adapters=adapters))
def get_namespace(self):
result = yield from self.manager.execute(
"inspect_container", {"container": self._cid})
return int(result['State']['Pid'])
+4 -3
View File
@@ -710,10 +710,12 @@ class Dynamips(BaseManager):
"""
image_dir = self.get_images_directory()
if not os.path.exists(image_dir):
return []
try:
files = os.listdir(image_dir)
except FileNotFoundError:
return []
except OSError as e:
raise DynamipsError("Can not list {}: {}".format(image_dir, str(e)))
files.sort()
images = []
for filename in files:
@@ -724,7 +726,6 @@ class Dynamips(BaseManager):
# read the first 7 bytes of the file.
elf_header_start = f.read(7)
except OSError as e:
print(e)
continue
# valid IOS images must start with the ELF magic number, be 32-bit, big endian and have an ELF version of 1
if elf_header_start == b'\x7fELF\x01\x02\x01':
+3 -3
View File
@@ -48,9 +48,9 @@ class NIOVDE(NIO):
@asyncio.coroutine
def create(self):
self._hypervisor.send("nio create_vde {name} {control} {local}".format(name=self._name,
control=self._control_file,
local=self._local_file))
yield from self._hypervisor.send("nio create_vde {name} {control} {local}".format(name=self._name,
control=self._control_file,
local=self._local_file))
log.info("NIO VDE {name} created with control={control}, local={local}".format(name=self._name,
control=self._control_file,
+1 -1
View File
@@ -367,7 +367,7 @@ class Router(BaseVM):
files += glob.glob(os.path.join(glob.escape(project_dir), "{}_i{}_flash[0-1]".format(self.platform, self.dynamips_id)))
files += glob.glob(os.path.join(glob.escape(project_dir), "{}_i{}_rom".format(self.platform, self.dynamips_id)))
files += glob.glob(os.path.join(glob.escape(project_dir), "{}_i{}_bootflash".format(self.platform, self.dynamips_id)))
files += glob.glob(os.path.join(glob.escape(project_dir), "{}_i{}_ssa").format(self.platform, self.dynamips_id))
files += glob.glob(os.path.join(glob.escape(project_dir), "{}_i{}_ssa".format(self.platform, self.dynamips_id)))
for file in files:
try:
log.debug("Deleting file {}".format(file))
+13 -12
View File
@@ -72,7 +72,6 @@ class IOUVM(BaseVM):
super().__init__(name, vm_id, project, manager, console=console)
self._command = []
self._iouyap_process = None
self._iou_process = None
self._iou_stdout_file = ""
@@ -220,7 +219,8 @@ class IOUVM(BaseVM):
"startup_config": self.relative_startup_config_file,
"private_config": self.relative_private_config_file,
"iourc_path": self.iourc_path,
"use_default_iou_values": self._use_default_iou_values}
"use_default_iou_values": self._use_default_iou_values,
"command_line": self.command_line}
# return the relative path if the IOU image is in the images_path directory
iou_vm_info["path"] = self.manager.get_relative_image_path(self.path)
@@ -403,9 +403,9 @@ class IOUVM(BaseVM):
raise IOUError("Hostname \"{}\" not found in iourc file {}".format(hostname, self.iourc_path))
user_ioukey = config["license"][hostname]
if user_ioukey[-1:] != ';':
raise IOUError("IOU key not ending with ; in iourc file".format(self.iourc_path))
raise IOUError("IOU key not ending with ; in iourc file {}".format(self.iourc_path))
if len(user_ioukey) != 17:
raise IOUError("IOU key length is not 16 characters in iourc file".format(self.iourc_path))
raise IOUError("IOU key length is not 16 characters in iourc file {}".format(self.iourc_path))
user_ioukey = user_ioukey[:16]
# We can't test this because it's mean distributing a valid licence key
@@ -502,13 +502,14 @@ class IOUVM(BaseVM):
if "IOURC" not in os.environ:
env["IOURC"] = iourc_path
self._command = yield from self._build_command()
command = yield from self._build_command()
try:
log.info("Starting IOU: {}".format(self._command))
log.info("Starting IOU: {}".format(command))
self._iou_stdout_file = os.path.join(self.working_dir, "iou.log")
log.info("Logging to {}".format(self._iou_stdout_file))
with open(self._iou_stdout_file, "w", encoding="utf-8") as fd:
self._iou_process = yield from asyncio.create_subprocess_exec(*self._command,
self.command_line = ' '.join(command)
self._iou_process = yield from asyncio.create_subprocess_exec(*command,
stdout=fd,
stderr=subprocess.STDOUT,
cwd=self.working_dir,
@@ -1188,7 +1189,7 @@ class IOUVM(BaseVM):
try:
startup_config_content, private_config_content = nvram_export(nvram_content)
except ValueError as e:
log.warning("Could not export configs from nvram file".format(nvram_file, e))
log.warning("Could not export configs from nvram file {}: {}".format(nvram_file, e))
return None, None
return startup_config_content, private_config_content
@@ -1254,10 +1255,10 @@ class IOUVM(BaseVM):
nio.startPacketCapture(output_file, data_link_type)
log.info('IOU "{name}" [{id}]: starting packet capture on {adapter_number}/{port_number} to {output_file}'.format(name=self._name,
id=self._id,
adapter_number=adapter_number,
port_number=port_number,
output_file=output_file))
id=self._id,
adapter_number=adapter_number,
port_number=port_number,
output_file=output_file))
if self.is_iouyap_running():
self._update_iouyap_config()
+4 -6
View File
@@ -142,16 +142,14 @@ class PortManager:
if end_port < start_port:
raise HTTPConflict(text="Invalid port range {}-{}".format(start_port, end_port))
last_exception = None
for port in range(start_port, end_port + 1):
if port in ignore_ports:
continue
last_exception
try:
PortManager._check_port(host, port, socket_type)
return port
PortManager._check_port(host, port, socket_type)
return port
except OSError as e:
last_exception = e
if port + 1 == end_port:
@@ -163,6 +161,7 @@ class PortManager:
end_port,
host,
last_exception))
@staticmethod
def _check_port(host, port, socket_type):
"""
@@ -182,7 +181,6 @@ class PortManager:
s.bind(sa) # the port is available if bind is a success
return True
def get_free_tcp_port(self, project, port_range_start=None, port_range_end=None):
"""
Get an available TCP port and reserve it
@@ -291,7 +289,7 @@ class PortManager:
"""
if port in self._used_udp_ports:
raise HTTPConflict(text="UDP port {} already in use on host".format(port, self._console_host))
raise HTTPConflict(text="UDP port {} already in use on host {}".format(port, self._console_host))
if port < self._udp_port_range[0] or port > self._udp_port_range[1]:
raise HTTPConflict(text="UDP port {} is outside the range {}-{}".format(port, self._udp_port_range[0], self._udp_port_range[1]))
self._used_udp_ports.add(port)
+4 -1
View File
@@ -141,7 +141,10 @@ class Project:
if hasattr(self, "_path"):
if path != self._path and self.is_local() is False:
raise aiohttp.web.HTTPForbidden(text="You are not allowed to modify the project directory location")
raise aiohttp.web.HTTPForbidden(text="You are not allowed to modify the project directory path")
if '"' in path:
raise aiohttp.web.HTTPForbidden(text="You are not allowed to use \" in the project directory path. It's not supported by Dynamips.")
self._path = path
self._update_temporary_file()
+2
View File
@@ -117,6 +117,8 @@ class Qemu(BaseManager):
for path in Qemu.paths_list():
try:
for f in os.listdir(path):
if f.endswith("-spice"):
continue
if (f.startswith("qemu-system") or f.startswith("qemu-kvm") or f == "qemu" or f == "qemu.exe") and \
os.access(os.path.join(path, f), os.X_OK) and \
os.path.isfile(os.path.join(path, f)):
+22 -22
View File
@@ -68,7 +68,6 @@ class QemuVM(BaseVM):
self._host = server_config.get("host", "127.0.0.1")
self._monitor_host = server_config.get("monitor_host", "127.0.0.1")
self._linked_clone = linked_clone
self._command = []
self._process = None
self._cpulimit_process = None
self._monitor = None
@@ -867,15 +866,16 @@ class QemuVM(BaseVM):
# check if there is enough RAM to run
self.check_available_ram(self.ram)
self._command = yield from self._build_command()
command_string = " ".join(shlex.quote(s) for s in self._command)
command = yield from self._build_command()
command_string = " ".join(shlex.quote(s) for s in command)
try:
log.info("Starting QEMU with: {}".format(command_string))
self._stdout_file = os.path.join(self.working_dir, "qemu.log")
log.info("logging to {}".format(self._stdout_file))
with open(self._stdout_file, "w", encoding="utf-8") as fd:
fd.write("Start QEMU with {}\n\nExecution log:\n".format(command_string))
self._process = yield from asyncio.create_subprocess_exec(*self._command,
self.command_line = ' '.join(command)
self._process = yield from asyncio.create_subprocess_exec(*command,
stdout=fd,
stderr=subprocess.STDOUT,
cwd=self.working_dir)
@@ -1084,24 +1084,24 @@ class QemuVM(BaseVM):
raise QemuError("Sorry, adding a link to a started Qemu VM is not supported.")
# FIXME: does the code below work? very undocumented feature...
# dynamically configure an UDP tunnel on the QEMU VM adapter
if nio and isinstance(nio, NIOUDP):
if self._legacy_networking:
yield from self._control_vm("host_net_remove {} gns3-{}".format(adapter_number, adapter_number))
yield from self._control_vm("host_net_add udp vlan={},name=gns3-{},sport={},dport={},daddr={}".format(adapter_number,
adapter_number,
nio.lport,
nio.rport,
nio.rhost))
else:
# Apparently there is a bug in Qemu...
# netdev_add [user|tap|socket|hubport|netmap],id=str[,prop=value][,...] -- add host network device
# netdev_del id -- remove host network device
yield from self._control_vm("netdev_del gns3-{}".format(adapter_number))
yield from self._control_vm("netdev_add socket,id=gns3-{},udp={}:{},localaddr={}:{}".format(adapter_number,
nio.rhost,
nio.rport,
self._host,
nio.lport))
# if nio and isinstance(nio, NIOUDP):
# if self._legacy_networking:
# yield from self._control_vm("host_net_remove {} gns3-{}".format(adapter_number, adapter_number))
# yield from self._control_vm("host_net_add udp vlan={},name=gns3-{},sport={},dport={},daddr={}".format(adapter_number,
# adapter_number,
# nio.lport,
# nio.rport,
# nio.rhost))
# else:
# # Apparently there is a bug in Qemu...
# # netdev_add [user|tap|socket|hubport|netmap],id=str[,prop=value][,...] -- add host network device
# # netdev_del id -- remove host network device
# yield from self._control_vm("netdev_del gns3-{}".format(adapter_number))
# yield from self._control_vm("netdev_add socket,id=gns3-{},udp={}:{},localaddr={}:{}".format(adapter_number,
# nio.rhost,
# nio.rport,
# self._host,
# nio.lport))
adapter.add_nio(0, nio)
log.info('QEMU VM "{name}" [{id}]: {nio} added to adapter {adapter_number}'.format(name=self._name,
@@ -879,6 +879,14 @@ class VirtualBoxVM(BaseVM):
yield from self._control_vm("nicproperty{} dest={}".format(adapter_number + 1, nio.rhost))
yield from self._control_vm("nicproperty{} dport={}".format(adapter_number + 1, nio.rport))
yield from self._control_vm("setlinkstate{} on".format(adapter_number + 1))
# check if the UDP tunnel has been correctly set
vm_info = yield from self._get_vm_info()
generic_driver_number = "generic{}".format(adapter_number + 1)
if generic_driver_number not in vm_info and vm_info[generic_driver_number] != "UDPTunnel":
log.warning("UDP tunnel has not been set on nic: {}".format(adapter_number + 1))
self.project.emit("log.warning", {"message": "UDP tunnel has not been set on nic: {}".format(adapter_number + 1)})
elif isinstance(nio, NIONAT):
yield from self._control_vm("nic{} nat".format(adapter_number + 1))
yield from self._control_vm("setlinkstate{} on".format(adapter_number + 1))
+31 -15
View File
@@ -31,6 +31,7 @@ import codecs
from collections import OrderedDict
from gns3server.utils.interfaces import interfaces
from gns3server.utils.asyncio import subprocess_check_output
from pkg_resources import parse_version
log = logging.getLogger(__name__)
@@ -96,21 +97,11 @@ class VMware(BaseManager):
if sys.platform.startswith("win"):
vmrun_path = shutil.which("vmrun")
if vmrun_path is None:
# look for vmrun.exe in default VMware Workstation directory
vmrun_ws = os.path.expandvars(r"%PROGRAMFILES(X86)%\VMware\VMware Workstation\vmrun.exe")
if os.path.exists(vmrun_ws):
vmrun_path = vmrun_ws
else:
# look for vmrun.exe using the directory listed in the registry
vmrun_path = self._find_vmrun_registry(r"SOFTWARE\Wow6432Node\VMware, Inc.\VMware Workstation")
# look for vmrun.exe using the VMware Workstation directory listed in the registry
vmrun_path = self._find_vmrun_registry(r"SOFTWARE\Wow6432Node\VMware, Inc.\VMware Workstation")
if vmrun_path is None:
# look for vmrun.exe in default VMware VIX directory
vmrun_vix = os.path.expandvars(r"%PROGRAMFILES(X86)%\VMware\VMware VIX\vmrun.exe")
if os.path.exists(vmrun_vix):
vmrun_path = vmrun_vix
else:
# look for vmrun.exe using the directory listed in the registry
vmrun_path = self._find_vmrun_registry(r"SOFTWARE\Wow6432Node\VMware, Inc.\VMware VIX")
# look for vmrun.exe using the VIX directory listed in the registry
vmrun_path = self._find_vmrun_registry(r"SOFTWARE\Wow6432Node\VMware, Inc.\VMware VIX")
elif sys.platform.startswith("darwin"):
vmrun_path = "/Applications/VMware Fusion.app/Contents/Library/vmrun"
else:
@@ -358,6 +349,31 @@ class VMware(BaseManager):
return stdout_data.decode("utf-8", errors="ignore").splitlines()
@asyncio.coroutine
def check_vmrun_version(self):
with (yield from self._execute_lock):
vmrun_path = self.vmrun_path
if not vmrun_path:
vmrun_path = self.find_vmrun()
try:
output = yield from subprocess_check_output(vmrun_path)
match = re.search("vmrun version ([0-9\.]+)", output)
version = None
if match:
version = match.group(1)
log.debug("VMware vmrun version {} detected".format(version))
if parse_version(version) < parse_version("1.13"):
# VMware VIX library version must be at least >= 1.13
raise VMwareError("VMware vmrun executable version must be >= version 1.13")
if version is None:
log.warning("Could not find VMware vmrun version. Output: {}".format(output))
raise VMwareError("Could not find VMware vmrun version. Output: {}".format(output))
except (OSError, subprocess.SubprocessError) as e:
log.error("Error while looking for the VMware vmrun version: {}".format(e))
raise VMwareError("Error while looking for the VMware vmrun version: {}".format(e))
@asyncio.coroutine
def remove_from_vmware_inventory(self, vmx_path):
"""
@@ -382,7 +398,7 @@ class VMware(BaseManager):
break
if vmlist_entry is not None:
for name in inventory_pairs.keys():
for name in inventory_pairs.copy().keys():
if name.startswith(vmlist_entry):
del inventory_pairs[name]
+1
View File
@@ -142,6 +142,7 @@ class VMwareVM(BaseVM):
Creates this VM and handle linked clones.
"""
yield from self.manager.check_vmrun_version()
if self._linked_clone and not os.path.exists(os.path.join(self.working_dir, os.path.basename(self._vmx_path))):
# create the base snapshot for linked clones
base_snapshot_name = "GNS3 Linked Base for clones"
+6 -5
View File
@@ -59,7 +59,6 @@ class VPCSVM(BaseVM):
def __init__(self, name, vm_id, project, manager, console=None, startup_script=None):
super().__init__(name, vm_id, project, manager, console=console)
self._command = []
self._process = None
self._vpcs_stdout_file = ""
self._vpcs_version = None
@@ -115,7 +114,8 @@ class VPCSVM(BaseVM):
"console": self._console,
"project_id": self.project.id,
"startup_script": self.startup_script,
"startup_script_path": self.relative_startup_script}
"startup_script_path": self.relative_startup_script,
"command_line": self.command_line}
@property
def relative_startup_script(self):
@@ -223,16 +223,17 @@ class VPCSVM(BaseVM):
if not self._ethernet_adapter.get_nio(0):
raise VPCSError("This VPCS instance must be connected in order to start")
self._command = self._build_command()
command = self._build_command()
try:
log.info("Starting VPCS: {}".format(self._command))
log.info("Starting VPCS: {}".format(command))
self._vpcs_stdout_file = os.path.join(self.working_dir, "vpcs.log")
log.info("Logging to {}".format(self._vpcs_stdout_file))
flags = 0
if sys.platform.startswith("win32"):
flags = subprocess.CREATE_NEW_PROCESS_GROUP
with open(self._vpcs_stdout_file, "w", encoding="utf-8") as fd:
self._process = yield from asyncio.create_subprocess_exec(*self._command,
self.command_line = ' '.join(command)
self._process = yield from asyncio.create_subprocess_exec(*command,
stdout=fd,
stderr=subprocess.STDOUT,
cwd=self.working_dir,
+6 -1
View File
@@ -254,11 +254,16 @@ IOU_OBJECT_SCHEMA = {
"iourc_path": {
"description": "Path of the iourc file used by remote servers",
"type": ["string", "null"]
},
"command_line": {
"description": "Last command line used by GNS3 to start QEMU",
"type": "string"
}
},
"additionalProperties": False,
"required": ["name", "vm_id", "console", "project_id", "path", "md5sum", "serial_adapters", "ethernet_adapters",
"ram", "nvram", "l1_keepalives", "startup_config", "private_config", "use_default_iou_values"]
"ram", "nvram", "l1_keepalives", "startup_config", "private_config", "use_default_iou_values",
"command_line"]
}
IOU_CAPTURE_SCHEMA = {
+6 -1
View File
@@ -558,6 +558,10 @@ QEMU_OBJECT_SCHEMA = {
"description": "Additional QEMU options",
"type": "string",
},
"command_line": {
"description": "Last command line used by GNS3 to start QEMU",
"type": "string"
}
},
"additionalProperties": False,
"required": ["vm_id",
@@ -598,7 +602,8 @@ QEMU_OBJECT_SCHEMA = {
"cpu_throttling",
"process_priority",
"options",
"vm_directory"]
"vm_directory",
"command_line"]
}
QEMU_BINARY_FILTER_SCHEMA = {
+5 -1
View File
@@ -121,7 +121,11 @@ VPCS_OBJECT_SCHEMA = {
"description": "Path of the VPCS startup script relative to project directory",
"type": ["string", "null"]
},
"command_line": {
"description": "Last command line used by GNS3 to start QEMU",
"type": "string"
}
},
"additionalProperties": False,
"required": ["name", "vm_id", "status", "console", "project_id", "startup_script_path"]
"required": ["name", "vm_id", "status", "console", "project_id", "startup_script_path", "command_line"]
}
+2 -2
View File
@@ -23,5 +23,5 @@
# or negative for a release candidate or beta (after the base version
# number has been incremented)
__version__ = "1.4.1"
__version_info__ = (1, 4, 1, 0)
__version__ = "1.4.2"
__version_info__ = (1, 4, 2, 0)
-1
View File
@@ -2,5 +2,4 @@ jsonschema>=2.4.0
aiohttp==0.17.4
Jinja2>=2.7.3
raven>=5.2.0
docker-py==1.4.0
psutil>=3.0.0
-1
View File
@@ -43,7 +43,6 @@ dependencies = [
"aiohttp>=0.15.1",
"Jinja2>=2.7.3",
"raven>=5.2.0",
"docker-py>=1.4.0",
"psutil>=3.0.0"
]
+4 -4
View File
@@ -61,20 +61,20 @@ class Query:
body = json.dumps(body)
@asyncio.coroutine
def go(future):
def go_request(future):
response = yield from aiohttp.request(method, self.get_url(path, api_version), data=body)
future.set_result(response)
future = asyncio.Future()
asyncio.async(go(future))
asyncio.async(go_request(future))
self._loop.run_until_complete(future)
response = future.result()
@asyncio.coroutine
def go(future, response):
def go_read(future, response):
response = yield from response.read()
future.set_result(response)
future = asyncio.Future()
asyncio.async(go(future, response))
asyncio.async(go_read(future, response))
self._loop.run_until_complete(future)
response.body = future.result()
x_route = response.headers.get('X-Route', None)
+3 -2
View File
@@ -139,7 +139,8 @@ def test_iou_start(server, vm):
with asyncio_patch("gns3server.modules.iou.iou_vm.IOUVM.start", return_value=True) as mock:
response = server.post("/projects/{project_id}/iou/vms/{vm_id}/start".format(project_id=vm["project_id"], vm_id=vm["vm_id"]))
assert mock.called
assert response.status == 204
assert response.status == 200
assert response.json["name"] == "PC TEST 1"
def test_iou_start_with_iourc(server, vm, tmpdir):
@@ -148,7 +149,7 @@ def test_iou_start_with_iourc(server, vm, tmpdir):
with asyncio_patch("gns3server.modules.iou.iou_vm.IOUVM.start", return_value=True) as mock:
response = server.post("/projects/{project_id}/iou/vms/{vm_id}/start".format(project_id=vm["project_id"], vm_id=vm["vm_id"]), body=body, example=True)
assert mock.called
assert response.status == 204
assert response.status == 200
response = server.get("/projects/{project_id}/iou/vms/{vm_id}".format(project_id=vm["project_id"], vm_id=vm["vm_id"]))
assert response.status == 200
+2 -1
View File
@@ -114,7 +114,8 @@ def test_qemu_start(server, vm):
with asyncio_patch("gns3server.modules.qemu.qemu_vm.QemuVM.start", return_value=True) as mock:
response = server.post("/projects/{project_id}/qemu/vms/{vm_id}/start".format(project_id=vm["project_id"], vm_id=vm["vm_id"]), example=True)
assert mock.called
assert response.status == 204
assert response.status == 200
assert response.json["name"] == "PC TEST 1"
def test_qemu_stop(server, vm):
+2 -1
View File
@@ -102,7 +102,8 @@ def test_vpcs_start(server, vm):
with asyncio_patch("gns3server.modules.vpcs.vpcs_vm.VPCSVM.start", return_value=True) as mock:
response = server.post("/projects/{project_id}/vpcs/vms/{vm_id}/start".format(project_id=vm["project_id"], vm_id=vm["vm_id"]), example=True)
assert mock.called
assert response.status == 204
assert response.status == 200
assert response.json["name"] == "PC TEST 1"
def test_vpcs_stop(server, vm):
+2 -2
View File
@@ -112,11 +112,11 @@ def test_start(loop, vm, monkeypatch):
with asyncio_patch("gns3server.modules.iou.iou_vm.IOUVM._check_iou_licence", return_value=True):
with asyncio_patch("gns3server.modules.iou.iou_vm.IOUVM._start_ioucon", return_value=True):
with asyncio_patch("gns3server.modules.iou.iou_vm.IOUVM._start_iouyap", return_value=True):
with asyncio_patch("asyncio.create_subprocess_exec", return_value=mock_process):
with asyncio_patch("asyncio.create_subprocess_exec", return_value=mock_process) as mock_exec:
mock_process.returncode = None
loop.run_until_complete(asyncio.async(vm.start()))
assert vm.is_running()
assert vm.command_line == ' '.join(mock_exec.call_args[0])
def test_start_with_iourc(loop, vm, monkeypatch, tmpdir):
+2 -1
View File
@@ -50,7 +50,7 @@ def test_get_qemu_version(loop):
def test_binary_list(loop):
files_to_create = ["qemu-system-x86", "qemu-system-x42", "qemu-kvm", "hello"]
files_to_create = ["qemu-system-x86", "qemu-system-x42", "qemu-kvm", "hello", "qemu-system-x86_64-spice"]
for file_to_create in files_to_create:
path = os.path.join(os.environ["PATH"], file_to_create)
@@ -70,6 +70,7 @@ def test_binary_list(loop):
assert {"path": os.path.join(os.environ["PATH"], "qemu-kvm"), "version": version} in qemus
assert {"path": os.path.join(os.environ["PATH"], "qemu-system-x42"), "version": version} in qemus
assert {"path": os.path.join(os.environ["PATH"], "hello"), "version": version} not in qemus
assert {"path": os.path.join(os.environ["PATH"], "qemu-system-x86_64-spice"), "version": version} not in qemus
qemus = loop.run_until_complete(asyncio.async(Qemu.binary_list(["x86"])))
+2 -1
View File
@@ -112,9 +112,10 @@ def test_is_running(vm, running_subprocess_mock):
def test_start(loop, vm, running_subprocess_mock):
with asyncio_patch("asyncio.create_subprocess_exec", return_value=running_subprocess_mock):
with asyncio_patch("asyncio.create_subprocess_exec", return_value=running_subprocess_mock) as mock:
loop.run_until_complete(asyncio.async(vm.start()))
assert vm.is_running()
assert vm.command_line == ' '.join(mock.call_args[0])
def test_stop(loop, vm, running_subprocess_mock):
+2 -1
View File
@@ -42,7 +42,7 @@ def test_reserve_tcp_port_outside_range():
assert mock_emit.call_args[0][0] == "log.warning"
def test_reserve_tcp_port_already_used():
def test_reserve_tcp_port_already_used_by_another_program():
"""
This test simulate a scenario where the port is already taken
by another programm on the server
@@ -65,6 +65,7 @@ def test_reserve_tcp_port_already_used():
assert port != 2001
assert mock_emit.call_args[0][0] == "log.warning"
def test_reserve_tcp_port_already_used():
"""
This test simulate a scenario where the port is already taken
+7
View File
@@ -102,6 +102,13 @@ def test_changing_path_not_allowed(tmpdir):
p.path = str(tmpdir)
def test_changing_path_with_quote_not_allowed(tmpdir):
with patch("gns3server.modules.project.Project.is_local", return_value=True):
with pytest.raises(aiohttp.web.HTTPForbidden):
p = Project()
p.path = str(tmpdir / "project\"53")
def test_json(tmpdir):
p = Project()
assert p.__json__() == {"name": p.name, "location": p.location, "path": p.path, "project_id": p.id, "temporary": False}
+1
View File
@@ -107,6 +107,7 @@ def test_start(loop, vm):
'-t',
'127.0.0.1')
assert vm.is_running()
assert vm.command_line == ' '.join(mock_exec.call_args[0])
(action, event) = queue.get_nowait()
assert action == "vm.started"
assert event == vm