mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-12 18:05:54 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
976154fd0b | ||
|
|
dea68bcb28 | ||
|
|
1fb4d191c9 | ||
|
|
167a0b8435 | ||
|
|
5842487cd0 | ||
|
|
942d07c8ce | ||
|
|
3fc64bced2 | ||
|
|
373113545f | ||
|
|
5bee927481 | ||
|
|
4f61443b20 | ||
|
|
4673424da7 | ||
|
|
40261ec99c | ||
|
|
3e4423f663 | ||
|
|
88e8c36d1c | ||
|
|
537122daba | ||
|
|
fd22cd8361 | ||
|
|
71c3bda0a5 | ||
|
|
53d60bc71a | ||
|
|
ce7ac0505a | ||
|
|
bf316f35cc | ||
|
|
29089f1884 | ||
|
|
889ea14973 | ||
|
|
2e05290206 | ||
|
|
39ddc4e8c1 | ||
|
|
a279bfe1d9 | ||
|
|
35e045a22e | ||
|
|
58b998600e | ||
|
|
119a2a3b66 | ||
|
|
ee2104ba35 | ||
|
|
425a05ecd8 | ||
|
|
cd9cb3eb6a | ||
|
|
5617ddc4cc | ||
|
|
e9c2a76f43 | ||
|
|
65a05791ce | ||
|
|
a9eae691f6 | ||
|
|
0a4a01fed0 | ||
|
|
2938b97bd4 | ||
|
|
f83e878d89 | ||
|
|
8225bad76b | ||
|
|
ba4128f57b | ||
|
|
bc3016eec5 | ||
|
|
a8c03f754b | ||
|
|
c2d313f5a7 | ||
|
|
48678506f7 | ||
|
|
7ce17cc48f |
No files matched your search
@@ -1,5 +1,8 @@
|
||||
*.py[cod]
|
||||
|
||||
#py.test
|
||||
.cache
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
|
||||
@@ -10,8 +10,5 @@ install:
|
||||
- pip install coveralls
|
||||
script:
|
||||
- py.test -v -s tests --cov gns3server --cov-report term-missing
|
||||
notifications:
|
||||
slack:
|
||||
secure: gsbGgtNbJ0ElyPawA9DNunVgVD4SHmB/vDeQBMnmnw3g8Z01iNiiFLaaiUo/Vr65Yd0KMujw7ocC5aiSsgPWF3dEC57ntogWcMF2tf9YlbHdY+90HlIs7k7/xf3vRdrE+rNy24svvU7zaxYCL9gO70PQqIT6nI2Dgy4qQFiwr2s=
|
||||
after_success:
|
||||
- coveralls
|
||||
@@ -1,5 +1,36 @@
|
||||
# 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
|
||||
* Fixes issue with packet capture on VMware VMs. Fixes #396.
|
||||
* Fixes concurrency issue when closing multiple VMware linked clone VMs. Fixes #410.
|
||||
* Fixes "can only use tap interfaces that both already exist and are up". Fixes #399.
|
||||
* Send machine stats via the notification stream
|
||||
* Check for /dev/kvm instead of kvm-ok
|
||||
* Show a warning when starting ASA8
|
||||
* Fix error when setting Qemu VM boot to 'cd' (HDD or CD/DVD-ROM)
|
||||
* Fixed the VMware default VM location on Windows, so that it doesn't assume the "Documents" folder is within the %USERPROFILE% folder, and also support Windows Server's folder (which is "My Virtual Machines" instead of "Virtual Machines").
|
||||
* Improve dynamips startup_config dump
|
||||
* Dump environnement to server debug log
|
||||
* Fix usage of qemu 0.10 on Windows
|
||||
* Show hostname when the hostname is missing in the iourc.txt
|
||||
|
||||
## 1.4.0 12/01/2016
|
||||
* Release 1.4.0
|
||||
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
sphinx==1.2.3
|
||||
pytest==2.8.2
|
||||
pep8==1.5.7
|
||||
pytest-timeout
|
||||
pytest-capturelog
|
||||
pytest-cov
|
||||
pytest-capturelog==0.7
|
||||
pytest-cov==2.2.0
|
||||
pytest-timeout==0.5
|
||||
@@ -1,8 +0,0 @@
|
||||
Docker
|
||||
---------------------
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
:maxdepth: 2
|
||||
|
||||
docker/*
|
||||
@@ -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>✔</td> <td>string</td> <td>Project UUID</td> </tr>
|
||||
<tr><td>vm_id</td> <td>✔</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
|
||||
|
||||
-40
@@ -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>✔</td> <td>string</td> <td>Project UUID</td> </tr>
|
||||
<tr><td>vm_id</td> <td>✔</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>✔</td> <td>string</td> <td>Project UUID</td> </tr>
|
||||
<tr><td>vm_id</td> <td>✔</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>✔</td> <td>string</td> <td>Project UUID</td> </tr>
|
||||
<tr><td>vm_id</td> <td>✔</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>✔</td> <td>string</td> <td>Project UUID</td> </tr>
|
||||
<tr><td>vm_id</td> <td>✔</td> <td>string</td> <td>Docker container instance UUID</td> </tr>
|
||||
</table>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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(
|
||||
|
||||
@@ -19,6 +19,7 @@ import aiohttp
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import psutil
|
||||
|
||||
from ...web.route import Route
|
||||
from ...schemas.project import PROJECT_OBJECT_SCHEMA, PROJECT_CREATE_SCHEMA, PROJECT_UPDATE_SCHEMA, PROJECT_FILE_LIST_SCHEMA, PROJECT_LIST_SCHEMA
|
||||
@@ -205,7 +206,7 @@ class ProjectHandler:
|
||||
queue = project.get_listen_queue()
|
||||
ProjectHandler._notifications_listening.setdefault(project.id, 0)
|
||||
ProjectHandler._notifications_listening[project.id] += 1
|
||||
response.write("{\"action\": \"ping\"}\n".encode("utf-8"))
|
||||
response.write("{}\n".format(json.dumps(ProjectHandler._getPingMessage())).encode("utf-8"))
|
||||
while True:
|
||||
try:
|
||||
(action, msg) = yield from asyncio.wait_for(queue.get(), 5)
|
||||
@@ -218,11 +219,26 @@ class ProjectHandler:
|
||||
except asyncio.futures.CancelledError as e:
|
||||
break
|
||||
except asyncio.futures.TimeoutError:
|
||||
response.write("{\"action\": \"ping\"}\n".encode("utf-8"))
|
||||
response.write("{}\n".format(json.dumps(ProjectHandler._getPingMessage())).encode("utf-8"))
|
||||
project.stop_listen_queue(queue)
|
||||
if project.id in ProjectHandler._notifications_listening:
|
||||
ProjectHandler._notifications_listening[project.id] -= 1
|
||||
|
||||
@classmethod
|
||||
def _getPingMessage(cls):
|
||||
"""
|
||||
The ping message is regulary send to the client to
|
||||
keep the connection open. We send with it some informations
|
||||
about server load.
|
||||
|
||||
:returns: hash
|
||||
"""
|
||||
stats = {}
|
||||
# Non blocking call in order to get cpu usage. First call will return 0
|
||||
stats["cpu_usage_percent"] = psutil.cpu_percent(interval=None)
|
||||
stats["memory_usage_percent"] = psutil.virtual_memory().percent
|
||||
return {"action": "ping", "event": stats}
|
||||
|
||||
@classmethod
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/files",
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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'])
|
||||
@@ -458,9 +458,10 @@ class Dynamips(BaseManager):
|
||||
nio = NIOLinuxEthernet(node.hypervisor, ethernet_device)
|
||||
elif nio_settings["type"] == "nio_tap":
|
||||
tap_device = nio_settings["tap_device"]
|
||||
if not is_interface_up(tap_device):
|
||||
raise aiohttp.web.HTTPConflict(text="TAP interface {} is down".format(tap_device))
|
||||
nio = NIOTAP(node.hypervisor, tap_device)
|
||||
if not is_interface_up(tap_device):
|
||||
# test after the TAP interface has been created (if it doesn't exist yet)
|
||||
raise aiohttp.web.HTTPConflict(text="TAP interface {} is down".format(tap_device))
|
||||
elif nio_settings["type"] == "nio_unix":
|
||||
local_file = nio_settings["local_file"]
|
||||
remote_file = nio_settings["remote_file"]
|
||||
@@ -602,6 +603,10 @@ class Dynamips(BaseManager):
|
||||
elif startup_config_content:
|
||||
startup_config_path = self._create_config(vm, default_startup_config_path, startup_config_content)
|
||||
yield from vm.set_configs(startup_config_path)
|
||||
# An empty startup config crash dynamips
|
||||
else:
|
||||
startup_config_path = self._create_config(vm, default_startup_config_path, "!\n")
|
||||
yield from vm.set_configs(startup_config_path)
|
||||
|
||||
private_config_path = settings.get("private_config")
|
||||
private_config_content = settings.get("private_config_content")
|
||||
@@ -705,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:
|
||||
@@ -719,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':
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -281,6 +281,7 @@ class Router(BaseVM):
|
||||
yield from self._hypervisor.send('vm stop "{name}"'.format(name=self._name))
|
||||
self.status = "stopped"
|
||||
log.info('Router "{name}" [{id}] has been stopped'.format(name=self._name, id=self._id))
|
||||
yield from self.save_configs()
|
||||
|
||||
@asyncio.coroutine
|
||||
def reload(self):
|
||||
@@ -352,7 +353,6 @@ class Router(BaseVM):
|
||||
if self._hypervisor and not self._hypervisor.devices:
|
||||
try:
|
||||
yield from self.stop()
|
||||
yield from self.save_configs()
|
||||
yield from self._hypervisor.send('vm delete "{}"'.format(self._name))
|
||||
except DynamipsError:
|
||||
pass
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
@@ -400,12 +400,12 @@ class IOUVM(BaseVM):
|
||||
raise IOUError("License section not found in iourc file {}".format(self.iourc_path))
|
||||
hostname = socket.gethostname()
|
||||
if hostname not in config["license"]:
|
||||
raise IOUError("Hostname key not found in iourc file {}".format(self.iourc_path))
|
||||
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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -49,21 +49,17 @@ class Qemu(BaseManager):
|
||||
"""
|
||||
kvm = []
|
||||
|
||||
try:
|
||||
process = yield from asyncio.create_subprocess_exec("kvm-ok")
|
||||
yield from process.wait()
|
||||
except OSError:
|
||||
if not os.path.exists("/dev/kvm"):
|
||||
return kvm
|
||||
|
||||
if process.returncode == 0:
|
||||
arch = platform.machine()
|
||||
if arch == "x86_64":
|
||||
kvm.append("x86_64")
|
||||
kvm.append("i386")
|
||||
elif arch == "i386":
|
||||
kvm.append("i386")
|
||||
else:
|
||||
kvm.append(platform.machine())
|
||||
arch = platform.machine()
|
||||
if arch == "x86_64":
|
||||
kvm.append("x86_64")
|
||||
kvm.append("i386")
|
||||
elif arch == "i386":
|
||||
kvm.append("i386")
|
||||
else:
|
||||
kvm.append(platform.machine())
|
||||
return kvm
|
||||
|
||||
@staticmethod
|
||||
@@ -121,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)):
|
||||
|
||||
@@ -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
|
||||
@@ -80,10 +79,12 @@ class QemuVM(BaseVM):
|
||||
try:
|
||||
self.qemu_path = qemu_path
|
||||
except QemuError as e:
|
||||
# If the binary is not found for topologies 1.4 and later
|
||||
# search via the platform otherwise use the binary name
|
||||
if platform:
|
||||
self.platform = platform
|
||||
else:
|
||||
raise e
|
||||
self.qemu_path = os.path.basename(qemu_path)
|
||||
else:
|
||||
self.platform = platform
|
||||
|
||||
@@ -157,7 +158,11 @@ class QemuVM(BaseVM):
|
||||
else:
|
||||
qemu_bin = os.path.basename(qemu_path)
|
||||
qemu_bin = re.sub(r'(w)?\.(exe|EXE)$', '', qemu_bin)
|
||||
self._platform = re.sub(r'^qemu-system-(.*)$', r'\1', qemu_bin, re.IGNORECASE)
|
||||
# Old version of GNS3 provide a binary named qemu.exe
|
||||
if qemu_bin == "qemu":
|
||||
self._platform = "i386"
|
||||
else:
|
||||
self._platform = re.sub(r'^qemu-system-(.*)$', r'\1', qemu_bin, re.IGNORECASE)
|
||||
if self._platform.split(".")[0] not in QEMU_PLATFORMS:
|
||||
raise QemuError("Platform {} is unknown".format(self._platform))
|
||||
log.info('QEMU VM "{name}" [{id}] has set the QEMU path to {qemu_path}'.format(name=self._name,
|
||||
@@ -690,6 +695,8 @@ class QemuVM(BaseVM):
|
||||
log.info('QEMU VM "{name}" [{id}] has set the QEMU initrd path to {initrd}'.format(name=self._name,
|
||||
id=self._id,
|
||||
initrd=initrd))
|
||||
if "asa" in initrd:
|
||||
self.project.emit("log.warning", {"message": "Warning ASA 8 is not officialy supported by GNS3 and Cisco, we recommend to use ASAv. Depending of your hardware this could not work or you could be limited to one instance."})
|
||||
self._initrd = initrd
|
||||
|
||||
@property
|
||||
@@ -859,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)
|
||||
@@ -1076,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,
|
||||
@@ -1494,7 +1502,6 @@ class QemuVM(BaseVM):
|
||||
answer[field] = getattr(self, field)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
answer["hda_disk_image"] = self.manager.get_relative_image_path(self._hda_disk_image)
|
||||
answer["hda_disk_image_md5sum"] = md5sum(self._hda_disk_image)
|
||||
answer["hdb_disk_image"] = self.manager.get_relative_image_path(self._hdb_disk_image)
|
||||
|
||||
@@ -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,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__)
|
||||
|
||||
@@ -48,6 +49,7 @@ class VMware(BaseManager):
|
||||
|
||||
super().__init__()
|
||||
self._execute_lock = asyncio.Lock()
|
||||
self._vmware_inventory_lock = asyncio.Lock()
|
||||
self._vmrun_path = None
|
||||
self._vmnets = []
|
||||
self._vmnet_start_range = 2
|
||||
@@ -95,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:
|
||||
@@ -191,9 +183,11 @@ class VMware(BaseManager):
|
||||
if int(version) < 6:
|
||||
raise VMwareError("Using VMware Player requires version 6 or above")
|
||||
if version is None:
|
||||
log.warning("Could not find VMware version")
|
||||
log.warning("Could not find VMware version. Output of VMware: {}".format(output))
|
||||
raise VMwareError("Could not find VMware version. Output of VMware: {}".format(output))
|
||||
except (OSError, subprocess.SubprocessError) as e:
|
||||
log.error("Error while looking for the VMware version: {}".format(e))
|
||||
raise VMwareError("Error while looking for the VMware version: {}".format(e))
|
||||
|
||||
@staticmethod
|
||||
def _get_vmnet_interfaces_registry():
|
||||
@@ -355,6 +349,64 @@ 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):
|
||||
"""
|
||||
Removes a linked clone from the VMware inventory file.
|
||||
|
||||
:param vmx_path: path of the linked clone VMX file
|
||||
"""
|
||||
|
||||
with (yield from self._vmware_inventory_lock):
|
||||
inventory_path = self.get_vmware_inventory_path()
|
||||
if os.path.exists(inventory_path):
|
||||
try:
|
||||
inventory_pairs = self.parse_vmware_file(inventory_path)
|
||||
except OSError as e:
|
||||
log.warning('Could not read VMware inventory file "{}": {}'.format(inventory_path, e))
|
||||
return
|
||||
|
||||
vmlist_entry = None
|
||||
for name, value in inventory_pairs.items():
|
||||
if value == vmx_path:
|
||||
vmlist_entry = name.split(".", 1)[0]
|
||||
break
|
||||
|
||||
if vmlist_entry is not None:
|
||||
for name in inventory_pairs.copy().keys():
|
||||
if name.startswith(vmlist_entry):
|
||||
del inventory_pairs[name]
|
||||
|
||||
try:
|
||||
self.write_vmware_file(inventory_path, inventory_pairs)
|
||||
except OSError as e:
|
||||
raise VMwareError('Could not write VMware inventory file "{}": {}'.format(inventory_path, e))
|
||||
|
||||
@staticmethod
|
||||
def parse_vmware_file(path):
|
||||
"""
|
||||
@@ -540,7 +592,13 @@ class VMware(BaseManager):
|
||||
"""
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
return os.path.expandvars(r"%USERPROFILE%\Documents\Virtual Machines")
|
||||
from win32com.shell import shell, shellcon
|
||||
documents_folder = shell.SHGetSpecialFolderPath(None, shellcon.CSIDL_PERSONAL)
|
||||
windows_type = sys.getwindowsversion().product_type
|
||||
if windows_type == 2 or windows_type == 3:
|
||||
return '{}\My Virtual Machines'.format(documents_folder)
|
||||
else:
|
||||
return '{}\Virtual Machines'.format(documents_folder)
|
||||
elif sys.platform.startswith("darwin"):
|
||||
return os.path.expanduser("~/Documents/Virtual Machines.localized")
|
||||
else:
|
||||
|
||||
@@ -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"
|
||||
@@ -375,6 +376,8 @@ class VMwareVM(BaseVM):
|
||||
vnet = "ethernet{}.vnet".format(adapter_number)
|
||||
if vnet not in self._vmx_pairs:
|
||||
raise VMwareError("vnet {} not in VMX file".format(vnet))
|
||||
if not self._ubridge_hypervisor:
|
||||
raise VMwareError("Cannot start the packet capture: uBridge is not running")
|
||||
yield from self._ubridge_hypervisor.send('bridge start_capture {name} "{output_file}"'.format(name=vnet,
|
||||
output_file=output_file))
|
||||
|
||||
@@ -389,6 +392,8 @@ class VMwareVM(BaseVM):
|
||||
vnet = "ethernet{}.vnet".format(adapter_number)
|
||||
if vnet not in self._vmx_pairs:
|
||||
raise VMwareError("vnet {} not in VMX file".format(vnet))
|
||||
if not self._ubridge_hypervisor:
|
||||
raise VMwareError("Cannot stop the packet capture: uBridge is not running")
|
||||
yield from self._ubridge_hypervisor.send("bridge stop_capture {name}".format(name=vnet))
|
||||
|
||||
def check_hw_virtualization(self):
|
||||
@@ -560,31 +565,7 @@ class VMwareVM(BaseVM):
|
||||
pass
|
||||
|
||||
if self._linked_clone:
|
||||
# clean the VMware inventory path from this linked clone
|
||||
inventory_path = self.manager.get_vmware_inventory_path()
|
||||
inventory_pairs = {}
|
||||
if os.path.exists(inventory_path):
|
||||
try:
|
||||
inventory_pairs = self.manager.parse_vmware_file(inventory_path)
|
||||
except OSError as e:
|
||||
log.warning('Could not read VMware inventory file "{}": {}'.format(inventory_path, e))
|
||||
return
|
||||
|
||||
vmlist_entry = None
|
||||
for name, value in inventory_pairs.items():
|
||||
if value == self._vmx_path:
|
||||
vmlist_entry = name.split(".", 1)[0]
|
||||
break
|
||||
|
||||
if vmlist_entry is not None:
|
||||
for name in inventory_pairs.keys():
|
||||
if name.startswith(vmlist_entry):
|
||||
del inventory_pairs[name]
|
||||
|
||||
try:
|
||||
self.manager.write_vmware_file(inventory_path, inventory_pairs)
|
||||
except OSError as e:
|
||||
raise VMwareError('Could not write VMware inventory file "{}": {}'.format(inventory_path, e))
|
||||
yield from self.manager.remove_from_vmware_inventory(self._vmx_path)
|
||||
|
||||
log.info("VirtualBox VM '{name}' [{id}] closed".format(name=self.name, id=self.id))
|
||||
self._closed = True
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -296,7 +296,7 @@ QEMU_UPDATE_SCHEMA = {
|
||||
},
|
||||
"boot_priority": {
|
||||
"description": "QEMU boot priority",
|
||||
"enum": ["c", "d"]
|
||||
"enum": ["c", "d", "n", "cn", "cd"]
|
||||
},
|
||||
"ram": {
|
||||
"description": "amount of RAM in MB",
|
||||
@@ -468,7 +468,7 @@ QEMU_OBJECT_SCHEMA = {
|
||||
},
|
||||
"boot_priority": {
|
||||
"description": "QEMU boot priority",
|
||||
"enum": ["c", "d"]
|
||||
"enum": ["c", "d", "n", "cn", "cd"]
|
||||
},
|
||||
"vm_directory": {
|
||||
"decription": "Path to the VM working directory",
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -226,6 +226,9 @@ class Server:
|
||||
# Asyncio will raise error if coroutine is not called
|
||||
self._loop.set_debug(True)
|
||||
|
||||
for key, val in os.environ.items():
|
||||
log.debug("ENV %s=%s", key, val)
|
||||
|
||||
app = aiohttp.web.Application()
|
||||
for method, route, handler in Route.get_routes():
|
||||
log.debug("Adding route: {} {}".format(method, route))
|
||||
|
||||
@@ -23,5 +23,5 @@
|
||||
# or negative for a release candidate or beta (after the base version
|
||||
# number has been incremented)
|
||||
|
||||
__version__ = "1.4.0"
|
||||
__version_info__ = (1, 4, 0, 0)
|
||||
__version__ = "1.4.2"
|
||||
__version_info__ = (1, 4, 2, 0)
|
||||
@@ -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
|
||||
@@ -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
-2
@@ -147,7 +147,9 @@ def run_around_tests(monkeypatch, port_manager):
|
||||
port_manager._instance = port_manager
|
||||
config = Config.instance()
|
||||
config.clear()
|
||||
config.set("Server", "project_directory", tmppath)
|
||||
os.makedirs(os.path.join(tmppath, 'projects'))
|
||||
config.set("Server", "project_directory", os.path.join(tmppath, 'projects'))
|
||||
config.set("Server", "images_path", os.path.join(tmppath, 'images'))
|
||||
config.set("Server", "auth", False)
|
||||
|
||||
# Prevent executions of the VM if we forgot to mock something
|
||||
@@ -158,7 +160,7 @@ def run_around_tests(monkeypatch, port_manager):
|
||||
# Force turn off KVM because it's not available on CI
|
||||
config.set("Qemu", "enable_kvm", False)
|
||||
|
||||
monkeypatch.setattr("gns3server.modules.project.Project._get_default_project_directory", lambda *args: tmppath)
|
||||
monkeypatch.setattr("gns3server.modules.project.Project._get_default_project_directory", lambda *args: os.path.join(tmppath, 'projects'))
|
||||
|
||||
# Force sys.platform to the original value. Because it seem not be restore correctly at each tests
|
||||
sys.platform = sys.original_platform
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -207,9 +207,9 @@ def test_notification(server, project, loop):
|
||||
@asyncio.coroutine
|
||||
def go(future):
|
||||
response = yield from aiohttp.request("GET", server.get_url("/projects/{project_id}/notifications".format(project_id=project.id), 1))
|
||||
response.body = yield from response.content.read(19)
|
||||
response.body = yield from response.content.read(200)
|
||||
project.emit("vm.created", {"a": "b"})
|
||||
response.body += yield from response.content.read(47)
|
||||
response.body += yield from response.content.read(50)
|
||||
response.close()
|
||||
future.set_result(response)
|
||||
|
||||
@@ -217,7 +217,9 @@ def test_notification(server, project, loop):
|
||||
asyncio.async(go(future))
|
||||
response = loop.run_until_complete(future)
|
||||
assert response.status == 200
|
||||
assert response.body == b'{"action": "ping"}\n{"action": "vm.created", "event": {"a": "b"}}\n'
|
||||
assert b'"action": "ping"' in response.body
|
||||
assert b'"cpu_usage_percent"' in response.body
|
||||
assert b'{"action": "vm.created", "event": {"a": "b"}}\n' in response.body
|
||||
|
||||
|
||||
def test_notification_invalid_id(server):
|
||||
|
||||
@@ -23,7 +23,6 @@ from tests.utils import asyncio_patch
|
||||
from unittest.mock import patch
|
||||
from gns3server.config import Config
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fake_qemu_bin():
|
||||
|
||||
@@ -40,7 +39,10 @@ def fake_qemu_bin():
|
||||
@pytest.fixture
|
||||
def fake_qemu_vm(tmpdir):
|
||||
|
||||
bin_path = os.path.join(str(tmpdir / "linux.img"))
|
||||
img_dir = Config.instance().get_section_config("Server").get("images_path")
|
||||
img_dir = os.path.join(img_dir, "QEMU")
|
||||
os.makedirs(img_dir)
|
||||
bin_path = os.path.join(img_dir, "linux载.img")
|
||||
with open(bin_path, "w+") as f:
|
||||
f.write("1")
|
||||
os.chmod(bin_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
|
||||
@@ -86,15 +88,17 @@ def test_qemu_create_platform(server, project, base_params, fake_qemu_bin):
|
||||
def test_qemu_create_with_params(server, project, base_params, fake_qemu_vm):
|
||||
params = base_params
|
||||
params["ram"] = 1024
|
||||
params["hda_disk_image"] = "linux.img"
|
||||
params["hda_disk_image"] = "linux载.img"
|
||||
|
||||
response = server.post("/projects/{project_id}/qemu/vms".format(project_id=project.id), params, example=True)
|
||||
|
||||
assert response.status == 201
|
||||
assert response.route == "/projects/{project_id}/qemu/vms"
|
||||
assert response.json["name"] == "PC TEST 1"
|
||||
assert response.json["project_id"] == project.id
|
||||
assert response.json["ram"] == 1024
|
||||
assert response.json["hda_disk_image"] == "linux.img"
|
||||
assert response.json["hda_disk_image"] == "linux载.img"
|
||||
assert response.json["hda_disk_image_md5sum"] == "c4ca4238a0b923820dcc509a6f75849b"
|
||||
|
||||
|
||||
def test_qemu_get(server, project, vm):
|
||||
@@ -110,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):
|
||||
@@ -220,10 +225,9 @@ def test_qemu_list_binaries_filter(server, vm):
|
||||
|
||||
def test_vms(server, tmpdir, fake_qemu_vm):
|
||||
|
||||
with patch("gns3server.modules.Qemu.get_images_directory", return_value=str(tmpdir), example=True):
|
||||
response = server.get("/qemu/vms")
|
||||
response = server.get("/qemu/vms")
|
||||
assert response.status == 200
|
||||
assert response.json == [{"filename": "linux.img", "path": "linux.img"}]
|
||||
assert response.json == [{"filename": "linux载.img", "path": "linux载.img"}]
|
||||
|
||||
|
||||
def test_upload_vm(server, tmpdir):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
@@ -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"])))
|
||||
|
||||
@@ -177,19 +178,15 @@ def test_create_image_exist(loop, tmpdir, fake_qemu_img_binary):
|
||||
assert not process.called
|
||||
|
||||
|
||||
def test_get_kvm_archs_no_kvm(loop):
|
||||
with asyncio_patch("asyncio.create_subprocess_exec", side_effect=FileNotFoundError('kvm-ok')):
|
||||
archs = loop.run_until_complete(asyncio.async(Qemu.get_kvm_archs()))
|
||||
assert archs == []
|
||||
|
||||
|
||||
def test_get_kvm_archs_kvm_ok(loop):
|
||||
|
||||
process = MagicMock()
|
||||
with asyncio_patch("asyncio.create_subprocess_exec", return_value=process):
|
||||
process.returncode = 0
|
||||
with patch("os.path.exists", return_value=True):
|
||||
archs = loop.run_until_complete(asyncio.async(Qemu.get_kvm_archs()))
|
||||
if platform.machine() == 'x86_64':
|
||||
assert archs == ['x86_64', 'i386']
|
||||
else:
|
||||
assert archs == platform.machine()
|
||||
|
||||
with patch("os.path.exists", return_value=False):
|
||||
archs = loop.run_until_complete(asyncio.async(Qemu.get_kvm_archs()))
|
||||
assert archs == []
|
||||
@@ -85,6 +85,22 @@ def test_vm(project, manager, fake_qemu_binary):
|
||||
assert vm.id == "00010203-0405-0607-0809-0a0b0c0d0e0f"
|
||||
|
||||
|
||||
def test_vm_invalid_qemu_with_platform(project, manager, fake_qemu_binary):
|
||||
|
||||
vm = QemuVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager, qemu_path="/usr/fake/bin/qemu-system-64", platform="x86_64")
|
||||
|
||||
assert vm.qemu_path == fake_qemu_binary
|
||||
assert vm.platform == "x86_64"
|
||||
|
||||
|
||||
def test_vm_invalid_qemu_without_platform(project, manager, fake_qemu_binary):
|
||||
|
||||
vm = QemuVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager, qemu_path="/usr/fake/bin/qemu-system-x86_64")
|
||||
|
||||
assert vm.qemu_path == fake_qemu_binary
|
||||
assert vm.platform == "x86_64"
|
||||
|
||||
|
||||
def test_is_running(vm, running_subprocess_mock):
|
||||
|
||||
vm._process = None
|
||||
@@ -96,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):
|
||||
@@ -258,6 +275,18 @@ def test_set_qemu_path_windows(vm, tmpdir):
|
||||
assert vm.platform == "x86_64"
|
||||
|
||||
|
||||
def test_set_qemu_path_old_windows(vm, tmpdir):
|
||||
|
||||
bin_path = os.path.join(os.environ["PATH"], "qemu.exe")
|
||||
open(bin_path, "w+").close()
|
||||
os.chmod(bin_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
|
||||
|
||||
vm.qemu_path = bin_path
|
||||
|
||||
assert vm.qemu_path == bin_path
|
||||
assert vm.platform == "i386"
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows")
|
||||
def test_set_qemu_path_kvm_binary(vm, tmpdir, fake_qemu_binary):
|
||||
|
||||
@@ -447,6 +476,30 @@ def test_hdd_disk_image(vm, tmpdir):
|
||||
assert vm.hdd_disk_image == force_unix_path(str(tmpdir / "QEMU" / "test"))
|
||||
|
||||
|
||||
def test_initrd(vm, tmpdir):
|
||||
|
||||
vm.manager.config.set("Server", "images_path", str(tmpdir))
|
||||
|
||||
with patch("gns3server.modules.project.Project.emit") as mock:
|
||||
vm.initrd = str(tmpdir / "test")
|
||||
assert vm.initrd == force_unix_path(str(tmpdir / "test"))
|
||||
vm.initrd = "test"
|
||||
assert vm.initrd == force_unix_path(str(tmpdir / "QEMU" / "test"))
|
||||
assert not mock.called
|
||||
|
||||
|
||||
def test_initrd_asa(vm, tmpdir):
|
||||
|
||||
vm.manager.config.set("Server", "images_path", str(tmpdir))
|
||||
|
||||
with patch("gns3server.modules.project.Project.emit") as mock:
|
||||
vm.initrd = str(tmpdir / "asa842-initrd.gz")
|
||||
assert vm.initrd == force_unix_path(str(tmpdir / "asa842-initrd.gz"))
|
||||
vm.initrd = "asa842-initrd.gz"
|
||||
assert vm.initrd == force_unix_path(str(tmpdir / "QEMU" / "asa842-initrd.gz"))
|
||||
assert mock.called
|
||||
|
||||
|
||||
def test_options(linux_platform, vm):
|
||||
vm.kvm = False
|
||||
vm.options = "-usb"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -21,13 +21,13 @@ from gns3server.utils.images import md5sum, remove_checksum
|
||||
|
||||
|
||||
def test_md5sum(tmpdir):
|
||||
fake_img = str(tmpdir / 'hello')
|
||||
fake_img = str(tmpdir / 'hello载')
|
||||
|
||||
with open(fake_img, 'w+') as f:
|
||||
f.write('hello')
|
||||
|
||||
assert md5sum(fake_img) == '5d41402abc4b2a76b9719d911017c592'
|
||||
with open(str(tmpdir / 'hello.md5sum')) as f:
|
||||
with open(str(tmpdir / 'hello载.md5sum')) as f:
|
||||
assert f.read() == '5d41402abc4b2a76b9719d911017c592'
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user