Compare commits

...
102 Commits
Author SHA1 Message Date
ziajka eafe27e684 Release v2.1.6 2018-05-22 14:11:48 +02:00
ziajka c52342907a Merge pull request #1332 from GNS3/global-vars
Docker `ExtraHosts`, global variables for project and supplier support, Fixes: #2482
2018-05-15 09:23:54 +02:00
ziajka 5a0e21f58d Locks down async-timeout<3.0.0 for P3.4 support; Fixes: #1331 2018-05-11 13:37:04 +02:00
ziajka f3c6faed75 Removed unnecessary line 2018-05-09 15:53:50 +02:00
ziajka 7b3d5ae5e3 Create/update project on compute when variables changes 2018-05-09 15:29:35 +02:00
ziajka 83445214be Support for nested global variables 2018-05-09 11:25:55 +02:00
ziajka 0bcc657bf1 Don't clean logo images when applied to the project 2018-05-08 16:22:35 +02:00
ziajka f2700ed445 Support of supplier and variables in topology 2018-05-08 10:07:42 +02:00
ziajka e4436096c2 Fix tests 2018-05-07 15:18:19 +02:00
ziajka 132c7c8f5b Fix tests 2018-05-07 12:55:32 +02:00
ziajka e267f8a8b8 Project global variables 2018-05-04 14:34:44 +02:00
grossmj 202e7362a5 Add command information when uBridge has an error. Ref #1289 2018-04-28 18:48:52 +07:00
grossmj 305fe2e817 Handle asyncio timeouts. Ref #1307. 2018-04-28 17:42:02 +07:00
grossmj a56d5b453f Fix project export tests. 2018-04-28 16:46:47 +07:00
grossmj 3b94484914 Fix bug with export project. Ref #1187 #1307. 2018-04-28 16:38:52 +07:00
grossmj 50a922f83e Offload slow file operations to threads for snapshots and project "save as". Ref #1187 #1307. 2018-04-28 16:01:43 +07:00
ziajka 80958e8a6d support based on init.sh, Ref: #2482 2018-04-27 14:33:07 +02:00
grossmj 20294e284c Fix exception from send_signal() on Windows. 2018-04-27 17:00:28 +07:00
ziajka 43081152ef Add support of ExtraHosts for Docker, Ref. gns3-gui#2482 2018-04-25 16:03:01 +02:00
ziajka 747814f083 Development on v2.1.6 2018-04-18 11:41:30 +02:00
ziajka 2e9c5590a8 Release v2.1.5 2018-04-18 11:29:02 +02:00
grossmj cfd0216554 Disable TraceNG for version 2.1.5 2018-04-18 17:19:44 +08:00
grossmj 9ecb058343 Fix tests. 2018-04-18 16:13:59 +08:00
grossmj 013c74f6d8 Sync appliance files. 2018-04-18 16:12:30 +08:00
grossmj fff2e128eb Set the first byte to 0C when generating a random MAC address for a Qemu VM. Ref #1267. 2018-04-18 16:06:51 +08:00
grossmj 5690d350bd Update appliance files. 2018-04-17 12:58:37 +08:00
grossmj 1fea086f13 Do not use VMnet0 when allocating VMnet adapters. 2018-04-17 11:47:25 +08:00
grossmj dd9c18b9f8 Use SO_REUSEADDR before calling bind() where missing. Fixes #1289. 2018-04-16 16:36:36 +08:00
grossmj 5c4f6dd3dc Do not fail a Dynamips project conversion if a file being used. 2018-04-16 15:45:43 +08:00
grossmj a12c5a95b0 Catch exceptions when using AsyncioTelnetServer. Fixes #1321. 2018-04-16 15:30:06 +08:00
grossmj 025441f027 Add default destination setting for traceng + some checks. 2018-04-16 15:03:22 +08:00
grossmj 801547983a Fix tests. 2018-04-13 17:04:19 +08:00
grossmj 2de182eff4 Grid size support for projects. 2018-04-13 16:54:57 +08:00
grossmj 9adfb1820d Remove 'include INSTALL' from MANIFEST. 2018-04-13 14:17:18 +08:00
grossmj 297bbd91ec Fix traceng tests. 2018-03-30 12:10:57 +07:00
grossmj 47e5dfabd8 Merge branch 'traceng' into 2.1 2018-03-29 15:17:41 +07:00
grossmj 898f180310 Fix issue with start all. 2018-03-29 13:32:56 +07:00
grossmj b61bd2dc67 Merge remote-tracking branch 'origin/traceng' into traceng 2018-03-29 13:29:56 +07:00
grossmj b7780ba7cc Check for valid IP address and prevent to run on non-Windows platforms. 2018-03-29 13:26:43 +07:00
grossmj c053a2f350 Enable UDP tunnel option and use ICMP probing by default. 2018-03-28 04:26:40 -07:00
grossmj 05ec14e888 Use the configured IP address to trace. 2018-03-27 03:43:50 -07:00
grossmj d08c08617c Support for source and destination for traceNG. 2018-03-27 16:58:49 +07:00
grossmj 8e695c8af1 Have TraceNG start without needing cmd.exe 2018-03-27 01:19:24 -07:00
grossmj a4626a2b79 Custom icons and small fixes for TraceNG integration. 2018-03-14 16:56:37 +07:00
grossmj 397c1322b4 Base support for TraceNG. 2018-03-12 17:57:13 +07:00
ziajka 148baaf465 Back to development on v2.1.5dev1 2018-03-12 11:27:02 +01:00
ziajka aaac2a2d93 Fix issue with temporary travis deploy issues 2018-03-12 09:43:57 +01:00
ziajka e54a94240d Re-release v2.1.4 2018-03-12 09:40:42 +01:00
ziajka 4ca216b181 Development on v2.1.5dev1 2018-03-12 09:26:07 +01:00
ziajka e417ee8fed Release v2.1.4 2018-03-12 09:19:18 +01:00
grossmj 3201a996ed Add Juniper JunOS space appliance. 2018-03-11 23:02:43 +07:00
grossmj 1df03d052f Sync checkpoint gaia appliance template. 2018-03-09 15:41:17 +07:00
grossmj da31358a65 Sync appliance templates. 2018-03-09 15:26:40 +07:00
grossmj 0bd7736460 Make sure we use an IPv4 address in the remote install script. 2018-03-08 21:09:00 +07:00
grossmj 38f458db7c Delete old pcap file when starting a new packet capture. 2018-03-08 16:00:05 +07:00
grossmj 233e41d006 Fix export project test. 2018-03-08 00:39:08 +07:00
grossmj 22db13d2bf Fix bug preventing to export portable projects with IOU images. 2018-03-08 00:13:35 +07:00
grossmj 0fde1cf54a Ignore invalid BPF filters. Ref #1290. 2018-03-07 21:37:15 +07:00
grossmj 00e781826b Different approach to handle no data returned by uBridge hypervisors. Fixes #1289. 2018-03-07 19:42:19 +07:00
grossmj 7c14e21f4a Different approach to handle no data returned by Dynamips or uBridge hypervisors. Fixes #1289. 2018-03-07 19:33:19 +07:00
grossmj a3a0657502 Do not raise exception if Dynamips or uBridge hypervisor don't return data and are still running. Fixes #1289 2018-03-07 19:11:34 +07:00
grossmj 3ca4f72b99 Fix Dynamips private config not loaded into nvram when starting a router. Fixes #1313. 2018-03-07 18:18:51 +07:00
grossmj d911e82dc4 Merge remote-tracking branch 'origin/2.1' into 2.1 2018-03-07 16:42:12 +07:00
grossmj c93d0d8d12 Make sure we don't try to read when opening a file in binary more. Fixes #1301. 2018-03-07 16:39:04 +07:00
ziajka 53a229f8e9 Fix never reached condition 2018-03-05 11:16:03 +01:00
ziajka 419797dd92 Merge pull request #1312 from GNS3/controller-compat
Compatybility with controller, default_symbol and hover_symbol, Fixes…
2018-03-02 13:49:32 +01:00
ziajka 05b6be2e42 Compatybility with controller, default_symbol and hover_symbol, Fixes: #2444 2018-03-02 13:48:27 +01:00
ziajka c714c9c8de Merge pull request #1310 from GNS3/fix-1297
Filter snapshots directory during the snapshot, Fixes: #1297
2018-02-28 16:40:18 +01:00
ziajka 9be76d98a2 Filter snapshots directory during the snapshot, Fixes: #1297 2018-02-28 16:38:32 +01:00
ziajka 9e73ca6620 Merge pull request #1309 from GNS3/fix-2420
Handle docker env with last empty line, Fixes: #2420
2018-02-27 16:41:24 +01:00
ziajka e79e27a73f Handle docker env with last empty line, Fixes: #2420 2018-02-27 16:33:55 +01:00
ziajka 7beae4e451 Merge pull request #1308 from GNS3/pywin32-dep
Pywin32 instead of pypiwin32, Ref. #1276
2018-02-27 09:27:54 +01:00
Jeremy Grossmann a0f2dd374e Require uBridge version 0.9.14 on Linux 2018-02-26 23:18:44 +07:00
ziajka b1657996e3 Pywin32 instead of pypiwin32, Ref. #1276 2018-02-26 16:25:31 +01:00
Jeremy Grossmann e83ed3b4b5 Merge pull request #1295 from ehlers/fix_cpuload
Implement a minimum interval between psutil calls. Fixes #2262
2018-02-26 13:08:10 +06:30
ziajka 2111f549c9 Merge pull request #1302 from nabilbendafi/master
Fix missing 'locales' package in base image
2018-02-21 10:14:01 +01:00
Nabil BENDAFI 3a15839272 Fix missing 'locales' package in base image
`/bin/sh: 1: locale-gen: not found`
2018-02-20 16:43:22 +01:00
Bernhard Ehlers 3a09bd43dc Implement a minimum interval between psutil calls. Fixes #2262 2018-02-13 11:31:04 +01:00
grossmj 17422bc461 Fix appliance loading test (missing category). 2018-02-06 16:55:40 +08:00
grossmj 935b8981ea Fix appliance loading test. 2018-02-06 16:15:34 +08:00
grossmj fff593b358 Fix error when appliance template is broken (missing fields). Fixes #1287. 2018-02-06 16:07:23 +08:00
grossmj abbe3d3a31 Fix "Change of linked base VM doesn't work with templates migrated from 2.0" 2018-02-05 16:42:29 +08:00
grossmj 52b690b683 Fix "Unable to override non-custom VMware adapter". 2018-02-04 22:08:41 +08:00
grossmj 654aa18d09 Fix get_port test. 2018-02-02 22:14:50 +08:00
grossmj 519df3ab35 Let a project be opened when a port cannot be found (can happens if a project is corrupted). 2018-02-02 22:05:51 +08:00
grossmj 4ba523a0d7 Add an error message when Docker container is not ready to be started. Ref #1281. 2018-02-02 16:29:08 +08:00
grossmj 07d2d7b340 Update documentation. 2018-01-31 23:37:02 +07:00
grossmj e5a4afaeb1 Sync appliance files. 2018-01-31 19:05:03 +07:00
grossmj 3802a3d70b Fix more IOU tests. 2018-01-31 16:34:41 +07:00
grossmj 0ee87c9110 Fix IOU tests. 2018-01-31 16:12:55 +07:00
grossmj e5c76750b1 Fix issue when running multiple project containing IOU nodes on the same server. Ref #1239. 2018-01-31 15:51:29 +07:00
grossmj 1045364adc Set first byte to 52 when generating a random MAC address for a Qemu VM. Ref #1267. 2018-01-30 18:26:38 +07:00
grossmj 008baad86a Update link state and save project when a link is suspended or filters are added/removed (without node properties set). 2018-01-30 17:39:33 +07:00
ziajka 4f2a50894b Merge pull request #1277 from GNS3/av-pywin32
Fix AppVeyor crashes with pypiwin32-222 Ref. #1276
2018-01-30 08:52:35 +01:00
ziajka 889aa3ba94 More generic dependency for pypiwin32, Ref. #1276 2018-01-29 15:26:45 +01:00
ziajka cbb15c7a84 Check if locking version of pypiwin32==220 helps, Ref. #1276 2018-01-29 15:16:38 +01:00
ziajka 8268e76a86 Check if passes tests on 2.1 2018-01-29 15:09:03 +01:00
ziajka daf058a306 Merge tag 'v2.1.3' 2018-01-24 10:09:31 +01:00
ziajka f7d82059d5 Development on v2.1.4dev1 2018-01-19 07:18:00 +01:00
ziajka b792be00f9 Merge tag 'v2.1.2' 2018-01-18 08:29:40 +01:00
grossmj 840aa39457 Merge remote-tracking branch 'origin/2.1' 2017-11-16 15:04:33 +07:00
grossmj fb2e10d3c1 Merge remote-tracking branch 'remotes/origin/master' 2017-11-16 14:59:57 +07:00
122 changed files with 3988 additions and 816 deletions

No files matched your search

+1
View File
@@ -39,6 +39,7 @@ nosetests.xml
.project
.pydevproject
.settings
.vscode
# Pycharm
.idea
+2
View File
@@ -16,6 +16,8 @@ before_deploy:
deploy:
provider: pypi
edge:
branch: v1.8.45
user: noplay
password:
secure: Fa66zp8ML4oSGwzkUMZi07MIYfO3tbS5gHFUaLN2mk2MBknhCjDYexmFJqT//sC/+xqv6sSJE6rz1EPoy/THbxj8R96ZgIyiUZIbDCbzgdy92d7J/eusrDoNdpApBLke8NqQqtFETb3addMZZNofQ3IDANFD2m2jY+KECU8z8NI=
+64
View File
@@ -1,5 +1,69 @@
# Change Log
## 2.1.6 22/05/2018
* Locks down async-timeout<3.0.0 for P3.4 support; Fixes: #1331
* Create/update project on compute when variables changes
* Support for nested global variables
* Don't clean logo images when applied to the project
* Support of supplier and variables in topology
* Project global variables
* Add command information when uBridge has an error. Ref #1289
* Handle asyncio timeouts. Ref #1307.
* Fix bug with export project. Ref #1187 #1307.
* Offload slow file operations to threads for snapshots and project "save as". Ref #1187 #1307.
* support based on init.sh, Ref: #2482
* Fix exception from send_signal() on Windows.
* Add support of ExtraHosts for Docker, Ref. gns3-gui#2482
## 2.1.5 18/04/2018
* Set the first byte to 0C when generating a random MAC address for a Qemu VM. Ref #1267.
* Update appliance files.
* Do not use VMnet0 when allocating VMnet adapters.
* Use SO_REUSEADDR before calling bind() where missing. Fixes #1289.
* Do not fail a Dynamips project conversion if a file being used.
* Catch exceptions when using AsyncioTelnetServer. Fixes #1321.
* Grid size support for projects.
* Remove 'include INSTALL' from MANIFEST.
* Fix issue with start all.
* Check for valid IP address and prevent to run on non-Windows platforms.
* Enable UDP tunnel option and use ICMP probing by default.
* Use the configured IP address to trace.
* Have TraceNG start without needing cmd.exe
## 2.1.4 12/03/2018
* Add Juniper JunOS space appliance.
* Sync checkpoint gaia appliance template.
* Sync appliance templates.
* Make sure we use an IPv4 address in the remote install script.
* Delete old pcap file when starting a new packet capture.
* Fix bug preventing to export portable projects with IOU images.
* Ignore invalid BPF filters. Ref #1290.
* Different approach to handle no data returned by uBridge hypervisors. Fixes #1289.
* Do not raise exception if Dynamips or uBridge hypervisor don't return data and are still running. Fixes #1289
* Fix Dynamips private config not loaded into nvram when starting a router. Fixes #1313.
* Make sure we don't try to read when opening a file in binary more. Fixes #1301.
* Compatybility with controller, default_symbol and hover_symbol, Fixes: #2444
* Filter snapshots directory during the snapshot, Fixes: #1297
* Handle docker env with last empty line, Fixes: #2420
* Require uBridge version 0.9.14 on Linux
* Pywin32 instead of pypiwin32, Ref. #1276
* Fix missing 'locales' package in base image
* Implement a minimum interval between psutil calls. Fixes #2262
* Fix error when appliance template is broken (missing fields). Fixes #1287.
* Fix "Change of linked base VM doesn't work with templates migrated from 2.0"
* Fix "Unable to override non-custom VMware adapter".
* Let a project be opened when a port cannot be found (can happens if a project is corrupted).
* Add an error message when Docker container is not ready to be started. Ref #1281.
* Update documentation.
* Sync appliance files.
* Fix issue when running multiple project containing IOU nodes on the same server. Ref #1239.
* Set first byte to 52 when generating a random MAC address for a Qemu VM. Ref #1267.
* Update link state and save project when a link is suspended or filters are added/removed (without node properties set).
* More generic dependency for pypiwin32, Ref. #1276
## 2.1.3 19/01/2018
* Update appliance files.
+3 -1
View File
@@ -5,7 +5,6 @@ FROM ubuntu:16.04
ENV DEBIAN_FRONTEND noninteractive
# Set the locale
RUN locale-gen en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8
@@ -13,6 +12,7 @@ ENV LC_ALL en_US.UTF-8
RUN apt-get update && apt-get install -y software-properties-common
RUN add-apt-repository ppa:gns3/ppa
RUN apt-get update && apt-get install -y \
locales \
python3-pip \
python3-dev \
qemu-system-x86 \
@@ -21,6 +21,8 @@ RUN apt-get update && apt-get install -y \
libvirt-bin \
x11vnc
RUN locale-gen en_US.UTF-8
# Install uninstall to install dependencies
RUN apt-get install -y vpcs ubridge
-1
View File
@@ -1,6 +1,5 @@
include README.rst
include AUTHORS
include INSTALL
include LICENSE
include MANIFEST.in
include tox.ini
+53 -49
View File
@@ -1,30 +1,34 @@
Sample session using curl
=========================
Sample sessions using curl
==========================
You need to read the :doc:`glossary`, and :doc:`general` before.
Read the :doc:`glossary`, and :doc:`general` pages first.
Full endpoints list is available: :doc:`endpoints`
A list of all endpoints is available in :doc:`endpoints`
.. warning::
Beware the output of this sample is truncated in order
to simplify the understanding. Please read the
documentation for the exact output.
Note that the output of the samples can be truncated in
order to simplify their understanding. Please read the
documentation for the exact output meaning.
You can check the server version with a simple curl command:
Server version
###############
Check the server version with a simple curl command:
.. code-block:: shell-session
# curl "http://localhost:3080/v2/version"
{
"version": "2.0.0dev1"
"local": false,
"version": "2.1.4"
}
List computes
##############
We will list the computes node where we can run our nodes:
List all the compute servers:
.. code-block:: shell-session
@@ -34,20 +38,20 @@ We will list the computes node where we can run our nodes:
"compute_id": "local",
"connected": true,
"host": "127.0.0.1",
"name": "Local",
"name": "local",
"port": 3080,
"protocol": "http",
"user": "admin"
}
]
In this sample we have only one compute where we can run our nodes. This compute as a special id: local. This
mean it's the local server embed in the GNS3 controller.
There is only one compute server where nodes can be run in this example.
This compute as a special id: local, this is the local server which is embedded in the GNS3 controller.
Create project
###############
Create a project
#################
The next step is to create a project.
The next step is to create a project:
.. code-block:: shell-session
@@ -60,7 +64,7 @@ The next step is to create a project.
Create nodes
#############
With this project id we can now create two VPCS Node.
Using the project id, it is now possible to create two VPCS nodes:
.. code-block:: shell-session
@@ -87,15 +91,14 @@ With this project id we can now create two VPCS Node.
"node_id": "83892a4d-aea0-4350-8b3e-d0af3713da74",
"node_type": "vpcs",
"project_id": "b8c070f7-f34c-4b7b-ba6f-be3d26ed073f",
"properties": {},
"status": "stopped"
}
The properties dictionnary contains all setting specific to a node type (dynamips, docker, vpcs...)
Link nodes
###########
Now we need to link the two VPCS by connecting their port 0 together.
The two VPCS nodes can be linked together using their port number 0 (VPCS has only one network adapter with one port):
.. code-block:: shell-session
@@ -123,7 +126,7 @@ Now we need to link the two VPCS by connecting their port 0 together.
Start nodes
###########
Now we can start the two nodes.
Start the two nodes:
.. code-block:: shell-session
@@ -133,8 +136,8 @@ Now we can start the two nodes.
Connect to nodes
#################
Everything should be started now. You can connect via telnet to the different Node.
The port is the field console in the create Node request.
Use a Telnet client to connect to the nodes once they have been started.
The port number can be found in the output when the nodes have been created above.
.. code-block:: shell-session
@@ -200,7 +203,7 @@ The port is the field console in the create Node request.
Stop nodes
##########
And we stop the two nodes.
Stop the two nodes:
.. code-block:: shell-session
@@ -208,40 +211,41 @@ And we stop the two nodes.
# curl -X POST "http://localhost:3080/v2/projects/b8c070f7-f34c-4b7b-ba6f-be3d26ed073f/nodes/83892a4d-aea0-4350-8b3e-d0af3713da74/stop" -d "{}"
Add a visual element
######################
Add visual elements
####################
When you want add visual elements to the topology like rectangle, circle, images you can just send a raw SVG.
This will display a red square in the middle of your topologies:
Visual elements like rectangle, ellipses or images in the form of raw SVG can be added to a project.
This will display a red square in the middle of your canvas:
.. code-block:: shell-session
# curl -X POST "http://localhost:3080/v2/projects/b8c070f7-f34c-4b7b-ba6f-be3d26ed073f/drawings" -d '{"x":0, "y": 12, "svg": "<svg width=\"50\" height=\"50\"><rect width=\"50\" height=\"50\" style=\"fill: #ff0000\"></rect></svg>"}'
Tips: you can embed png/jpg... by using a base64 encoding in the SVG.
Tip: embed PNG, JPEG etc. images using base64 encoding in the SVG.
Add filter to the link
######################
Add a packet filter
####################
Filter allow you to add error on a link.
Packet filters allow to filter packet on a given link. Here to drop a packet every 5 packets:
.. code-block:: shell-session
curl -X PUT "http://localhost:3080/v2/projects/b8c070f7-f34c-4b7b-ba6f-be3d26ed073f/links/007f2177-6790-4e1b-ac28-41fa226b2a06" -d '{"filters": {"frequency_drop": [5]}}'
Creation of nodes
#################
Node creation
##############
Their is two way of adding nodes. Manual by passing all the information require for a Node.
There are two ways to add nodes.
Or by using an appliance. The appliance is a node model saved in your server.
1. Manually by passing all the information required to create a new node.
2. Using an appliance template stored on your server.
Using an appliance
------------------
Using an appliance template
---------------------------
First you need to list the available appliances
List all the available appliance templates:
.. code-block:: shell-session
@@ -268,15 +272,15 @@ First you need to list the available appliances
}
]
Now you can use the appliance and put it at a specific position
Use the appliance template and add coordinates to select where the node will be put on the canvas:
.. code-block:: shell-session
# curl -X POST http://localhost:3080/v2/projects/b8c070f7-f34c-4b7b-ba6f-be3d26ed073f -d '{"x": 12, "y": 42}'
# curl -X POST http://localhost:3080/v2/projects/b8c070f7-f34c-4b7b-ba6f-be3d26ed073f/appliances/9cd59d5a-c70f-4454-8313-6a9e81a8278f -d '{"x": 12, "y": 42}'
Manual creation of a Qemu node
-------------------------------
------------------------------
.. code-block:: shell-session
@@ -360,7 +364,7 @@ Manual creation of a Qemu node
}
Manual creation of a dynamips node
Manual creation of a Dynamips node
-----------------------------------
.. code-block:: shell-session
@@ -486,7 +490,7 @@ Manual creation of a dynamips node
Notifications
#############
You can see notification about the changes via the notification feed:
Notifications can be seen by connection to the notification feed:
.. code-block:: shell-session
@@ -494,14 +498,14 @@ You can see notification about the changes via the notification feed:
{"action": "ping", "event": {"compute_id": "local", "cpu_usage_percent": 35.7, "memory_usage_percent": 80.7}}
{"action": "node.updated", "event": {"command_line": "/usr/local/bin/vpcs -p 5001 -m 1 -i 1 -F -R -s 10001 -c 10000 -t 127.0.0.1", "compute_id": "local", "console": 5001, "console_host": "127.0.0.1", "console_type": "telnet", "name": "VPCS 2", "node_id": "83892a4d-aea0-4350-8b3e-d0af3713da74", "node_type": "vpcs", "project_id": "b8c070f7-f34c-4b7b-ba6f-be3d26ed073f", "properties": {"startup_script": null, "startup_script_path": null}, "status": "started"}}
A websocket version is also available on http://localhost:3080/v2/projects/b8c070f7-f34c-4b7b-ba6f-be3d26ed073f/notifications/ws
A Websocket notification stream is also available on http://localhost:3080/v2/projects/b8c070f7-f34c-4b7b-ba6f-be3d26ed073f/notifications/ws
Read :doc:`notifications` for more informations
Read :doc:`notifications` for more information.
How to found the endpoints?
Where to find the endpoints?
###########################
Full endpoints list is available: :doc:`endpoints`
A list of all endpoints is available: :doc:`endpoints`
If you start the server with **--debug** you can see all the requests made by the client and by the controller to the computes nodes.
Tip: requests made by a client and by a controller to the computes nodes can been seen if the server is started with the **--debug** parameter.
+4 -7
View File
@@ -4,27 +4,25 @@ Development
Code convention
===============
You should respect all the PEP8 convention except the
rule about max line length.
Respect all the PEP8 convention except the max line length rule.
Source code
===========
Source code is available on github under GPL V3 licence:
Source code is available on Github under the GPL V3 licence:
https://github.com/GNS3/
The GNS3 server: https://github.com/GNS3/gns3-server
The Qt GUI: https://github.com/GNS3/gns3-gui
The GNS3 user interface: https://github.com/GNS3/gns3-gui
Documentation
==============
In the gns3-server project.
The documentation can be found in the gns3-server project.
Build doc
----------
In the project root folder:
.. code-block:: bash
@@ -41,4 +39,3 @@ Run tests
.. code-block:: bash
py.test -v
+18 -16
View File
@@ -1,21 +1,22 @@
Endpoints
------------
GNS3 expose two type of endpoints:
GNS3 exposes two type of endpoints:
* Controller
* Compute
* Controller endpoints
* Compute endpoints
Controller API Endpoints
~~~~~~~~~~~~~~~~~~~~~~~~
Controller endpoints
~~~~~~~~~~~~~~~~~~~~~
The controller manage all the running topologies. The controller
has knowledge of everything on in GNS3. If you want to create and
manage a topology it's here. The controller will call the compute API
when needed.
The controller manages everything, it is the central decision point
and has a complete view of your network topologies, what nodes run on
which compute server, the links between them etc.
In a standard GNS3 installation you have one controller and one or many
computes.
This is the high level API which can be used by users to manually control
the GNS3 backend. The controller will call the compute endpoints when needed.
A standard GNS3 setup is to have one controller and one or many computes.
.. toctree::
:glob:
@@ -24,14 +25,15 @@ computes.
api/v2/controller/*
Compute API Endpoints
~~~~~~~~~~~~~~~~~~~~~~~~~~
Compute Endpoints
~~~~~~~~~~~~~~~~~~
The compute is the GNS3 process running on a server and controlling
the VM process.
A compute is the GNS3 process running on a host. It controls emulators in order to run nodes
(e.g. VMware VMs with VMware Workstation, IOS routers with Dynamips etc.)
.. WARNING::
Consider this endpoints as a private API used by the controller.
These endpoints should be considered low level and private.
They should only be used by the controller or for debugging purposes.
.. toctree::
:glob:
+24 -24
View File
@@ -1,11 +1,11 @@
GNS3 file formats
=================
The GNS3 files
===============
The .gns3
##########
.gns3 files
############
It's the topology file of GNS3 this file is a JSON with all
the informations about what is inside the topology.
GNS3 project files in JSON file format with all
the information necessary to save a project.
A minimal version:
@@ -30,34 +30,34 @@ The revision is the version of file format:
* 4: GNS3 1.5
* 3: GNS3 1.4
* 2: GNS3 1.3
* 1: GNS3 1.0, 1.1, 1.2 (Not mentionned in the topology file)
* 1: GNS3 1.0, 1.1, 1.2 (Not mentioned in the file)
And the full JSON schema:
The full JSON schema can be found there:
.. literalinclude:: gns3_file.json
The .net
#########
It's topologies made for GNS3 0.8
.net files
###########
Topology files made for GNS3 <= version 1.0. Not supported.
The .gns3p or .gns3project
###########################
It's a zipped version of the .gns3 and all files require for
a topology. The images could be included inside but are optionnals.
The zip could be a ZIP64 if the project is too big for standard
zip file.
The .gns3a or .gns3appliance
.gns3p or .gns3project files
#############################
This file contains details on how to import an appliance in GNS3.
This this a zipped version of a.gns3 file and includes all the required files to easily share a project.
The binary images can optionally be included.
A JSON schema is available here:
The zip can be a ZIP64 if the project is too big for standard zip file.
.gns3a or .gns3appliance files
##############################
These files contain everything needed to create a new appliance template in GNS3.
A JSON schema is available there:
https://github.com/GNS3/gns3-registry/blob/master/schemas/appliance.json
And samples here:
And samples there:
https://github.com/GNS3/gns3-registry/tree/master/appliances
+30 -40
View File
@@ -1,29 +1,27 @@
General
################
#######
Architecture
============
GNS3 is splitted in four part:
GNS3 can be divided in four part:
* the GUI (project gns3-gui, gns3-web)
* the controller (project gns3-server)
* the compute (project gns3-server)
* the emulators (qemu, iou, dynamips...)
* the user interface or GUI (gns3-gui or gns3-web projects)
* the controller (gns3-server project)
* the compute (part of the gns3-server project)
* the emulators (Qemu, Dynamips, VirtualBox...)
The controller pilot everything it's the part that manage the state
of a project, save it on disk. Only one controller exists.
The controller pilots everything, it manages the state
of each project. Only one controller should run.
The GUI displays a topology representing a project on a canvas and allow to
perform actions on given project, sending API requests to the controller.
The GUI display the topology. The GUI has only direct contact with
the controller.
The compute controls emulators to run nodes. A compute that is on
the same server as the controller is the same process.
The compute are where emulator are executed. If the compute is on
the same server as the controller, they are in the same process.
For each node of the topology will start an emulator instance.
The compute usually starts an emulator instance for each node.
A small schema::
@@ -42,19 +40,18 @@ A small schema::
+--------+
If you want to pilot GNS3 you need to use the controller API.
Use the controller API to work with the GNS3 backend
Communications
===============
==============
All the communication are done over HTTP using JSON.
All communication are done over HTTP using the JSON format.
Errors
======
In case of error a standard HTTP error is raise and you got a
JSON like that
A standard HTTP error is sent in case of an error:
.. code-block:: json
@@ -63,10 +60,6 @@ JSON like that
"message": "Conflict"
}
409 error could be display to the user. They are normal behavior
they are used to warn user about something he should change and
they are not an internal software error.
Limitations
============
@@ -74,37 +67,34 @@ Limitations
Concurrency
------------
A node can't process multiple request in the same time. But you can make
multiple request on multiple node. It's transparent for the client
when the first request on a Node start a lock is acquire for this node id
and released for the next request at the end. You can safely send all
the requests in the same time and let the server manage an efficent concurrency.
We think it can be a little slower for some operations, but it's remove a big
complexity for the client due to the fact only some command on some node can be
concurrent.
A node cannot processes multiple requests at the same time. However,
multiple requests on multiple nodes can be executed concurrently.
This should be transparent for clients since internal locks are used inside the server,
so it is safe to send multiple requests at the same time and let the server
manage the concurrency.
Authentication
-----------------
--------------
You can use HTTP basic auth to protect the access to the API. And run
the API over HTTPS.
HTTP basic authentication can be used to prevent unauthorized API requests.
It is recommended to set up a VPN if the communication between clients and the server must be encrypted.
Notifications
=============
You can receive notification from the server if you listen the HTTP stream /notifications or the websocket.
Notifications can be received from the server by listening to a HTTP stream or via a Websocket.
Read :doc:`notifications` for more informations
Read :doc:`notifications` for more information
Previous versions
=================
API version 1
-------------
Shipped with GNS3 1.3, 1.4 and 1.5.
This API doesn't support the controller system and save used a commit system instead of live save.
Shipped with GNS3 1.3, 1.4 and 1.5.
This API doesn't support the controller architecture.
+18 -18
View File
@@ -4,44 +4,41 @@ Glossary
Topology
--------
The place where you have all things (node, drawing, link...)
Contains everything to represent a virtual network (nodes, visual elements, links...)
Node
-----
----
A Virtual Machine (Dynamips, IOU, Qemu, VPCS...), a cloud, a builtin device (switch, hub...)
A Virtual Machine (Dynamips, IOU, Qemu, VPCS...) or builtin node (cloud, switch, hub...) that run on a compute.
Appliance
---------
A model for a node. When you drag an appliance to the topology a node is created.
A model for a node used to create a node. When you drag an appliance to the topology a node is created.
Appliance template
------------------
A file (.gns3a) use for creating new node model.
A file (.gns3a) used to create a new node.
Drawing
--------
-------
Drawing are visual element not used by the network emulation. Like
text, images, rectangle... They are pure SVG elements.
A Drawing is a visual element like annotations, images, rectangles etc. There are pure SVG elements.
Adapter
-------
The physical network interface. The adapter can contain multiple ports.
A physical network interface, like a PCI card. The adapter can contain multiple ports.
Port
----
A port is an opening on network adapter that cable plug into.
A port is an opening on a network adapter where can be plugged into.
For example a VM can have a serial and an ethernet adapter plugged in.
The ethernet adapter can have 4 ports.
For example a VM can have a serial and an Ethernet adapter.
The Ethernet adapter itself can have 4 ports.
Controller
----------
@@ -50,20 +47,23 @@ The central server managing everything in GNS3. A GNS3 controller
will manage multiple GNS3 compute node.
Compute
----------
-------
The process running on each server with GNS3. The GNS3 compute node
is controlled by the GNS3 controller.
Symbol
------
Symbol are the icon used for nodes.
A symbol is an icon used to represent a node on a scene.
Scene
-----
The drawing area
A scene is the drawing area or canvas.
Filter
------
Packet filter this allow to add latency or packet drop.
Packet filter, for instance to add latency on a link or drop packets
+6 -10
View File
@@ -2,17 +2,13 @@ Welcome to API documentation!
======================================
.. WARNING::
This documentation are for developers for user documentation go
to https://gns3.com/
The API is not stable, feel free to post comments on our website
https://gns3.com/
This documentation is intended for developers. The user documentation is
available on https://gns3.com/
This documentation cover the GNS3 API and ressources for GNS3 developers.
This documentation describe the GNS3 API and provide information for GNS3 developers.
If you want a quick demo on how to use the API read: :doc:`curl`
For a quick demo on how to use the API read: :doc:`curl`
API
----
@@ -26,8 +22,8 @@ API
position
endpoints
GNS3 developements
------------------
GNS3 development
----------------
.. toctree::
development
file_format
+27 -31
View File
@@ -1,17 +1,17 @@
Notifications
=============
You can receive notification from the controller allowing you to update your local data.
Notifications can be received from the controller, they can be used to update your local data.
Notifications endpoints
***********************
Notification endpoints
**********************
You can listen the HTTP stream /notifications or the websocket.
Listen to the HTTP stream endpoint or to the Websocket endpoint.
* :doc:`api/v2/controller/project/projectsprojectidnotifications`
* :doc:`api/v2/controller/project/projectsprojectidnotificationsws`
We recommend using the websocket.
It is recommended to use the Websocket endpoint.
Available notifications
***********************
@@ -21,7 +21,7 @@ Available notifications
ping
----
Keep the connection between client and controller.
Keep-alive between client and controller. Also used to receive the current CPU and memory usage.
.. literalinclude:: api/notifications/ping.json
@@ -29,7 +29,7 @@ Keep the connection between client and controller.
compute.created
----------------
Compute has been created.
A compute has been created.
.. literalinclude:: api/notifications/compute.created.json
@@ -37,9 +37,7 @@ Compute has been created.
compute.updated
----------------
Compute has been updated. You will receive a lot of this
event because it's include change of CPU and memory usage
on the compute node.
A compute has been updated.
.. literalinclude:: api/notifications/compute.updated.json
@@ -47,7 +45,7 @@ on the compute node.
compute.deleted
---------------
Compute has been deleted.
A compute has been deleted.
.. literalinclude:: api/notifications/compute.deleted.json
@@ -55,7 +53,7 @@ Compute has been deleted.
node.created
------------
Node has been created.
A node has been created.
.. literalinclude:: api/notifications/node.created.json
@@ -63,7 +61,7 @@ Node has been created.
node.updated
------------
Node has been updated.
A node has been updated.
.. literalinclude:: api/notifications/node.updated.json
@@ -71,7 +69,7 @@ Node has been updated.
node.deleted
------------
Node has been deleted.
A node has been deleted.
.. literalinclude:: api/notifications/node.deleted.json
@@ -79,8 +77,8 @@ Node has been deleted.
link.created
------------
Link has been created. Note that a link when created
is not yet connected to both part.
A link has been created. Note that a link is not connected
to any node when it is created.
.. literalinclude:: api/notifications/link.created.json
@@ -88,7 +86,7 @@ is not yet connected to both part.
link.updated
------------
Link has been updated.
A link has been updated.
.. literalinclude:: api/notifications/link.updated.json
@@ -96,7 +94,7 @@ Link has been updated.
link.deleted
------------
Link has been deleted.
A link has been deleted.
.. literalinclude:: api/notifications/link.deleted.json
@@ -104,7 +102,7 @@ Link has been deleted.
drawing.created
---------------
Drawing has been created.
A drawing has been created.
.. literalinclude:: api/notifications/drawing.created.json
@@ -112,8 +110,8 @@ Drawing has been created.
drawing.updated
---------------
Drawing has been updated. To reduce data transfert if the
svg field has not change the field is not included.
A drawing has been updated. The svg field is only included if it
has changed in order to reduce data transfer.
.. literalinclude:: api/notifications/drawing.updated.json
@@ -121,7 +119,7 @@ svg field has not change the field is not included.
drawing.deleted
---------------
Drawing has been deleted.
A drawing has been deleted.
.. literalinclude:: api/notifications/drawing.deleted.json
@@ -129,7 +127,7 @@ Drawing has been deleted.
project.updated
---------------
Project has been updated.
A project has been updated.
.. literalinclude:: api/notifications/project.updated.json
@@ -137,7 +135,7 @@ Project has been updated.
project.closed
---------------
Project has been closed.
A project has been closed.
.. literalinclude:: api/notifications/project.closed.json
@@ -145,14 +143,14 @@ Project has been closed.
snapshot.restored
--------------------------
Snapshot has been restored
A snapshot has been restored
.. literalinclude:: api/notifications/project.snapshot_restored.json
log.error
---------
Send an error to the user
Sends an error
.. literalinclude:: api/notifications/log.error.json
@@ -160,7 +158,7 @@ Send an error to the user
log.warning
------------
Send a warning to the user
Sends a warning
.. literalinclude:: api/notifications/log.warning.json
@@ -168,7 +166,7 @@ Send a warning to the user
log.info
---------
Send an information to the user
Sends an information
.. literalinclude:: api/notifications/log.info.json
@@ -176,8 +174,6 @@ Send an information to the user
settings.updated
-----------------
GUI settings updated. Will be removed in a later release.
GUI settings have been updated. Will be removed in a later release.
.. literalinclude:: api/notifications/settings.updated.json
+3 -3
View File
@@ -1,7 +1,7 @@
Positions
=========
In a the project object you have properties scene_height and scene_width this define the
size of the drawing area as px.
A project object contains the scene_height and scene_width properties. This defines the
size of the drawing area in px.
The position of the node are relative to this with 0,0 as center of the area.
The position of the nodes are relative with 0,0 as center of the area.
+1
View File
@@ -9,6 +9,7 @@
"product_url": "https://www.centos.org/download/",
"registry_version": 5,
"status": "stable",
"availability": "free",
"maintainer": "GNS3 Team",
"maintainer_email": "developers@gns3.net",
"usage": "Username: osboxes.org\nPassword: osboxes.org",
+9 -8
View File
@@ -6,15 +6,16 @@
"vendor_url": "https://www.checkpoint.com",
"documentation_url": "http://downloads.checkpoint.com/dc/download.htm?ID=26770",
"product_name": "Gaia",
"registry_version": 3,
"registry_version": 4,
"status": "experimental",
"maintainer": "GNS3 Team",
"maintainer_email": "developers@gns3.net",
"usage": "At boot choose the install on disk options. You need to open quickly the terminal after launching the appliance if you want to see the menu. You need a web browser in order to finalize the installation. You can use the firefox appliance for this.",
"qemu": {
"cpus": 2,
"adapter_type": "e1000",
"adapters": 8,
"ram": 2048,
"ram": 4096,
"arch": "x86_64",
"console_type": "telnet",
"boot_priority": "dc",
@@ -44,33 +45,33 @@
"download_url": "https://supportcenter.checkpoint.com/supportcenter/portal?eventSubmit_doGoviewsolutiondetails=&solutionid=sk104859"
},
{
"filename": "empty8G.qcow2",
"filename": "empty100G.qcow2",
"version": "1.0",
"md5sum": "f1d2c25b6990f99bd05b433ab603bdb4",
"md5sum": "1e6409a4523ada212dea2ebc50e50a65",
"filesize": 197120,
"download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/",
"direct_download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/empty8G.qcow2/download"
"direct_download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/empty100G.qcow2/download"
}
],
"versions": [
{
"name": "80.10",
"images": {
"hda_disk_image": "empty8G.qcow2",
"hda_disk_image": "empty100G.qcow2",
"cdrom_image": "Check_Point_R80.10_T421_Gaia.iso"
}
},
{
"name": "77.30",
"images": {
"hda_disk_image": "empty8G.qcow2",
"hda_disk_image": "empty100G.qcow2",
"cdrom_image": "Check_Point_R77.30_T204_Install_and_Upgrade.Gaia.iso"
}
},
{
"name": "77.20",
"images": {
"hda_disk_image": "empty8G.qcow2",
"hda_disk_image": "empty100G.qcow2",
"cdrom_image": "Check_Point_R77.20_T124_Install.Gaia.iso"
}
}
+2 -2
View File
@@ -22,14 +22,14 @@
"images": [
{
"filename": "c7200-adventerprisek9-mz.124-24.T5.image",
"version": "124-25.T5",
"version": "124-24.T5",
"md5sum": "6b89d0d804e1f2bb5b8bda66b5692047",
"filesize": 102345240
}
],
"versions": [
{
"name": "124-25.T5",
"name": "124-24.T5",
"idlepc": "0x606df838",
"images": {
"image": "c7200-adventerprisek9-mz.124-24.T5.image"
+18 -5
View File
@@ -25,23 +25,30 @@
{
"filename": "csr1000v-universalk9.16.07.01-serial.qcow2",
"version": "16.7.1",
"md5sum": "13adbfc2586d06c9802b9805168c0c44",
"filesize": 882769920,
"download_url": "https://software.cisco.com/download/release.html?mdfid=284364978&flowid=39582&softwareid=282046477&release=Fuji-16.7.1&relind=AVAILABLE&rellifecycle=ED&reltype=latest"
"md5sum": "bad9000d4ae8317bbc99a34a8cdd2eb4",
"filesize": 884539392,
"download_url": "https://software.cisco.com/download/release.html?mdfid=284364978&flowid=39582&softwareid=282046477&release=Fuji-16.7.1"
},
{
"filename": "csr1000v-universalk9.16.06.02-serial.qcow2",
"version": "16.6.2",
"md5sum": "11e393b31ab9d1ace8e5f7551c491ba2",
"filesize": 1570242560,
"download_url": "https://software.cisco.com/download/release.html?mdfid=284364978&flowid=39582&softwareid=282046477&release=Everest-16.6.2"
},
{
"filename": "csr1000v-universalk9.16.06.01-serial.qcow2",
"version": "16.6.1",
"md5sum": "909e74446d3ff0b82c14327c0058fdc2",
"filesize": 1566179328,
"download_url": "https://software.cisco.com/download/release.html?mdfid=284364978&flowid=39582&softwareid=282046477&release=Denali-16.3.5&relind=AVAILABLE&rellifecycle=ED&reltype=latest"
"download_url": "https://software.cisco.com/download/release.html?mdfid=284364978&flowid=39582&softwareid=282046477&release=Everest-16.6.1"
},
{
"filename": "csr1000v-universalk9.16.05.02-serial.qcow2",
"version": "16.5.2",
"md5sum": "59a84da28d59ee75176aa05ecde7f72a",
"filesize": 1322385408,
"download_url": "https://software.cisco.com/download/release.html?mdfid=284364978&flowid=39582&softwareid=282046477&release=Denali-16.3.5&relind=AVAILABLE&rellifecycle=ED&reltype=latest"
"download_url": "https://software.cisco.com/download/release.html?mdfid=284364978&flowid=39582&softwareid=282046477&release=Everest-16.5.2"
},
{
"filename": "csr1000v-universalk9.16.5.1b-serial.qcow2",
@@ -93,6 +100,12 @@
"hda_disk_image": "csr1000v-universalk9.16.07.01-serial.qcow2"
}
},
{
"name": "16.6.2",
"images": {
"hda_disk_image": "csr1000v-universalk9.16.06.02-serial.qcow2"
}
},
{
"name": "16.6.1",
"images": {
+1
View File
@@ -9,6 +9,7 @@
"product_url": "http://www.cisco.com/c/en/us/td/docs/security/firepower/quick_start/kvm/fmcv-kvm-qsg.html",
"registry_version": 4,
"status": "experimental",
"availability": "service-contract",
"maintainer": "Community",
"maintainer_email":"",
"usage": "BE PATIENT\nOn first boot FMCv generates about 6GB of data. This can take 30 minutes or more. Plan on a long wait after the following line in the boot up:\n\n usbcore: registered new interface driver usb-storage\n\nInitial IP address: 192.168.45.45.\n\nDefault username/password: admin/Admin123.",
+1
View File
@@ -9,6 +9,7 @@
"product_url": "http://www.cisco.com/c/en/us/td/docs/security/firepower/quick_start/kvm/ftdv-kvm-qsg.html",
"registry_version": 4,
"status": "experimental",
"availability": "service-contract",
"maintainer": "Community",
"maintainer_email": "",
"usage": "Default username/password: admin/Admin123.",
+1
View File
@@ -9,6 +9,7 @@
"product_url": "http://www.cisco.com/c/en/us/support/security/ngips-virtual-appliance/tsd-products-support-series-home.html",
"registry_version": 4,
"status": "experimental",
"availability": "service-contract",
"maintainer": "Community",
"maintainer_email": "",
"usage": "Default username/password: admin/Admin123.",
+14
View File
@@ -25,6 +25,13 @@
"kvm": "require"
},
"images": [
{
"filename": "nxosv-final.7.0.3.I7.3.qcow2",
"version": "7.0.3.I7.3",
"md5sum": "9d7a20367bf681a239f14097bbce470a",
"filesize": 983629824,
"download_url": "https://software.cisco.com/download/"
},
{
"filename": "nxosv-final.7.0.3.I7.2.qcow2",
"version": "7.0.3.I7.2",
@@ -71,6 +78,13 @@
}
],
"versions": [
{
"name": "7.0.3.I7.3",
"images": {
"bios_image": "OVMF-20160813.fd",
"hda_disk_image": "nxosv-final.7.0.3.I7.3.qcow2"
}
},
{
"name": "7.0.3.I7.2",
"images": {
@@ -26,6 +26,13 @@
"options": "-smp 2 -cpu host"
},
"images": [
{
"filename": "NSVPX-KVM-12.0-56.20_nc_32.qcow2",
"version": "12.0-56.20",
"md5sum": "0ea1c23e3b8eb8451037d46ee472cfa6",
"filesize": 739704832,
"download_url": "https://www.citrix.com/lp/try/netscaler-vpx-express.html"
},
{
"filename": "NSVPX-KVM-11.1-47.14_nc.raw",
"version": "11.1-47.14 F",
@@ -42,6 +49,12 @@
}
],
"versions": [
{
"name": "12.0-56.20",
"images": {
"hda_disk_image": "NSVPX-KVM-12.0-56.20_nc_32.qcow2"
}
},
{
"name": "11.1-47.14 F",
"images": {
+14
View File
@@ -22,6 +22,13 @@
"kvm": "require"
},
"images": [
{
"filename": "ClearOS-7.4-DVD-x86_64.iso",
"version": "7.4",
"md5sum": "826da592f9cd4b59f5fc996ff2d569f1",
"filesize": 1029701632,
"download_url": "https://www.clearos.com/clearfoundation/software/clearos-downloads"
},
{
"filename": "ClearOS-7.3-DVD-x86_64.iso",
"version": "7.3",
@@ -46,6 +53,13 @@
}
],
"versions": [
{
"name": "7.4",
"images": {
"hda_disk_image": "empty30G.qcow2",
"cdrom_image": "ClearOS-7.4-DVD-x86_64.iso"
}
},
{
"name": "7.3",
"images": {
+15
View File
@@ -21,6 +21,15 @@
"kvm": "allow"
},
"images": [
{
"filename": "coreos_production_qemu_image.1632.2.1.img",
"version": "1632.2.1",
"md5sum": "facd05ca85eb87e2dc6aefd6779f6806",
"filesize": 885719040,
"download_url": "http://stable.release.core-os.net/amd64-usr/1632.2.1/",
"direct_download_url": "http://stable.release.core-os.net/amd64-usr/1632.2.1/coreos_production_qemu_image.img.bz2",
"compression": "bzip2"
},
{
"filename": "coreos_production_qemu_image.1576.4.0.img",
"version": "1576.4.0",
@@ -149,6 +158,12 @@
}
],
"versions": [
{
"name": "1632.2.1",
"images": {
"hda_disk_image": "coreos_production_qemu_image.1576.4.0.img"
}
},
{
"name": "1576.4.0",
"images": {
+14
View File
@@ -23,6 +23,14 @@
"kvm": "require"
},
"images": [
{
"filename": "cumulus-linux-3.5.2-vx-amd64.qcow2",
"version": "3.5.2",
"md5sum": "87d1d8b297e5ebd77924669dfb7e4c9f",
"filesize": 996605952,
"download_url": "https://cumulusnetworks.com/cumulus-vx/download/",
"direct_download_url": "http://cumulusfiles.s3.amazonaws.com/cumulus-linux-3.5.0-vx-amd64.qcow2"
},
{
"filename": "cumulus-linux-3.5.0-vx-amd64.qcow2",
"version": "3.5.0",
@@ -133,6 +141,12 @@
}
],
"versions": [
{
"name": "3.5.2",
"images": {
"hda_disk_image": "cumulus-linux-3.5.2-vx-amd64.qcow2"
}
},
{
"name": "3.5.0",
"images": {
+14
View File
@@ -27,6 +27,13 @@
"options": "-smp 2 -cpu host"
},
"images": [
{
"filename": "BIGIP-13.1.0.2.0.0.6.qcow2",
"version": "13.1.0 HF2",
"md5sum": "d29eb861d8906fc36f88d9861a0055f4",
"filesize": 4363649024,
"download_url": "https://downloads.f5.com/esd/serveDownload.jsp?path=/big-ip/big-ip_v13.x/13.1.0/english/13.1.0.2_virtual-edition/&sw=BIG-IP&pro=big-ip_v13.x&ver=13.1.0&container=13.1.0.2_Virtual-Edition&file=BIGIP-13.1.0.2.0.0.6.ALL.qcow2.zip"
},
{
"filename": "BIGIP-13.1.0.1.0.0.8.qcow2",
"version": "13.1.0 HF1",
@@ -114,6 +121,13 @@
}
],
"versions": [
{
"name": "13.1.0 HF2",
"images": {
"hda_disk_image": "BIGIP-13.1.0.2.0.0.6.qcow2",
"hdb_disk_image": "empty100G.qcow2"
}
},
{
"name": "13.1.0 HF1",
"images": {
+14
View File
@@ -34,6 +34,13 @@
"filesize": 30998528,
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
},
{
"filename": "FAD_KVM-V400-build0989-FORTINET.out.kvm-boot.qcow2",
"version": "4.8.4",
"md5sum": "c1926d5979ef24d9d14d3394c0bb832b",
"filesize": 72810496,
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
},
{
"filename": "FAD_KVM-V400-build0983-FORTINET.out.kvm-boot.qcow2",
"version": "4.8.3",
@@ -148,6 +155,13 @@
}
],
"versions": [
{
"name": "4.8.4",
"images": {
"hda_disk_image": "FAD_KVM-V400-build0989-FORTINET.out.kvm-boot.qcow2",
"hdb_disk_image": "FAD_KVM-v400-FORTINET.out.kvm-data.qcow2"
}
},
{
"name": "4.8.3",
"images": {
+14
View File
@@ -54,6 +54,13 @@
"filesize": 38760448,
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
},
{
"filename": "FGT_VM64_KVM-v5-build1183-FORTINET.out.kvm.qcow2",
"version": "5.4.8",
"md5sum": "c1eb02996a0919c934785d5f48df9507",
"filesize": 38608896,
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
},
{
"filename": "FGT_VM64_KVM-v5-build6446-FORTINET.out.kvm.qcow2",
"version": "5.4.7",
@@ -204,6 +211,13 @@
"hdb_disk_image": "empty30G.qcow2"
}
},
{
"name": "5.4.8",
"images": {
"hda_disk_image": "FGT_VM64_KVM-v5-build1183-FORTINET.out.kvm.qcow2",
"hdb_disk_image": "empty30G.qcow2"
}
},
{
"name": "5.4.7",
"images": {
+14
View File
@@ -27,6 +27,13 @@
"options": "-smp 2"
},
"images": [
{
"filename": "FSA_KVM-v200-build0329-FORTINET.out.kvm.qcow2",
"version": "2.5.1",
"md5sum": "782ba56a644d78da59b89f4ac91bd319",
"filesize": 114491904,
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
},
{
"filename": "FSA_KVM-v200-build0261-FORTINET.out.kvm.qcow2",
"version": "2.4.1",
@@ -71,6 +78,13 @@
}
],
"versions": [
{
"name": "2.5.1",
"images": {
"hda_disk_image": "FSA_KVM-v200-build0329-FORTINET.out.kvm.qcow2",
"hdb_disk_image": "FSA_v200-datadrive.qcow2"
}
},
{
"name": "2.4.1",
"images": {
+20 -4
View File
@@ -24,6 +24,14 @@
"kvm": "require"
},
"images": [
{
"filename": "FreeNAS-11.1-U1.iso",
"version": "11.1 U1",
"md5sum": "ccbd9990a5878d35c6bc0cc6eea34b16",
"filesize": 626601984,
"download_url": "http://www.freenas.org/download/",
"direct_download_url": "http://download.freenas.org/11/11.1-RELEASE/x64/FreeNAS-11.1-RELEASE.iso"
},
{
"filename": "FreeNAS-11.1-RELEASE.iso",
"version": "11.1",
@@ -34,7 +42,7 @@
},
{
"filename": "FreeNAS-11.0-U4.iso",
"version": "11.0-U4",
"version": "11.0 U4",
"md5sum": "4c210f1a6510d1fa95257d81ef569ff8",
"filesize": 567312384,
"download_url": "http://www.freenas.org/download/",
@@ -42,7 +50,7 @@
},
{
"filename": "FreeNAS-9.10.1-U4.iso",
"version": "9.10",
"version": "9.10 U4",
"md5sum": "b4fb14513dcbb4eb4c5596c5911ca9cc",
"filesize": 533098496,
"download_url": "http://www.freenas.org/download/",
@@ -58,6 +66,14 @@
}
],
"versions": [
{
"name": "11.1 U1",
"images": {
"hda_disk_image": "empty30G.qcow2",
"hdb_disk_image": "empty30G.qcow2",
"cdrom_image": "FreeNAS-11.1-U1.iso"
}
},
{
"name": "11.1",
"images": {
@@ -67,7 +83,7 @@
}
},
{
"name": "11.0",
"name": "11.0 U4",
"images": {
"hda_disk_image": "empty30G.qcow2",
"hdb_disk_image": "empty30G.qcow2",
@@ -75,7 +91,7 @@
}
},
{
"name": "9.10",
"name": "9.10 U4",
"images": {
"hda_disk_image": "empty30G.qcow2",
"hdb_disk_image": "empty30G.qcow2",
+15
View File
@@ -24,6 +24,15 @@
"kvm": "allow"
},
"images": [
{
"filename": "ipfire-2.19.1gb-ext4-scon.x86_64-full-core117.img",
"version": "2.19.117",
"md5sum": "657673d88b94ed7d22332aebe817bc86",
"filesize": 1063256064,
"download_url": "http://www.ipfire.org/download",
"direct_download_url": "https://downloads.ipfire.org/releases/ipfire-2.x/2.19-core117/ipfire-2.19.1gb-ext4-scon.x86_64-full-core117.img.gz",
"compression": "gzip"
},
{
"filename": "ipfire-2.19.1gb-ext4-scon.x86_64-full-core116.img",
"version": "2.19.116",
@@ -53,6 +62,12 @@
}
],
"versions": [
{
"name": "2.19.117",
"images": {
"hda_disk_image": "ipfire-2.19.1gb-ext4-scon.x86_64-full-core117.img"
}
},
{
"name": "2.19.116",
"images": {
@@ -0,0 +1,43 @@
{
"name": "Junos Space",
"category": "guest",
"description": "Junos Space Network Management Platform works with Juniper's management applications to simplify and automate management of Juniper's switching, routing, and security devices. As part of a complete solution, the platform provides broad fault, configuration, accounting, performance, and security management (FCAPS) capability, same day support for new devices and Junos OS releases, a task-specific user interface, and northbound APIs for integration with existing network management systems (NMS) or operations/business support systems (OSS/BSS).\n\nThe platform helps network operators at enterprises and service providers scale operations, reduce complexity, and enable new applications and services to be brought to market quickly, through multilayered network abstractions, operator-centric automation schemes, and a simple point-and-click UI.",
"vendor_name": "Juniper",
"vendor_url": "https://www.juniper.net/us/en/",
"documentation_url": "http://www.juniper.net/techpubs/",
"product_name": "Junos Space",
"product_url": "https://www.juniper.net/us/en/dm/free-vqfx-trial/",
"registry_version": 3,
"status": "stable",
"maintainer": "GNS3 Team",
"maintainer_email": "developers@gns3.net",
"symbol": "juniper-vqfx.svg",
"usage": "16 GB RAM is the bare minimum; you should use 32/64 GB in production deplyments.\nDefault credentials:\n- CLI: admin / abc123\n- WebUI: super / juniper123",
"port_name_format": "em{0}",
"qemu": {
"adapter_type": "e1000",
"adapters": 4,
"ram": 16384,
"arch": "x86_64",
"console_type": "telnet",
"kvm": "require",
"options": "-smp 4 -nographic"
},
"images": [
{
"filename": "space-17.2R1.4.qcow2",
"version": "17.2R1.4",
"md5sum": "4124fa756c3a78be0619e876b8ee687e",
"filesize": 5150474240,
"download_url": "https://www.juniper.net/support/downloads/?p=space#sw"
}
],
"versions": [
{
"name": "17.2R1.4",
"images": {
"hda_disk_image": "space-17.2R1.4.qcow2"
}
}
]
}
+1
View File
@@ -12,6 +12,7 @@
"maintainer": "GNS3 Team",
"maintainer_email": "developers@gns3.net",
"usage": "Initial username is root, no password.",
"first_port_name": "fxp0",
"port_name_format": "ge-0/0/{0}",
"qemu": {
"adapter_type": "e1000",
+39
View File
@@ -26,6 +26,33 @@
"options": "-nographic"
},
"images": [
{
"filename": "chr-6.42.img",
"version": "6.42",
"md5sum": "279bb518497b40f41c8585128916a2fb",
"filesize": 134217728,
"download_url": "http://www.mikrotik.com/download",
"direct_download_url": "https://download2.mikrotik.com/routeros/6.42/chr-6.42.img.zip",
"compression": "zip"
},
{
"filename": "chr-6.41.4.img",
"version": "6.41.4",
"md5sum": "63b555b2b7f0d78b79edb92f7e7d2ed7",
"filesize": 134217728,
"download_url": "http://www.mikrotik.com/download",
"direct_download_url": "https://download2.mikrotik.com/routeros/6.41.4/chr-6.41.4.img.zip",
"compression": "zip"
},
{
"filename": "chr-6.40.7.img",
"version": "6.40.7",
"md5sum": "424b897d631c4cac4324ca310e81b494",
"filesize": 134217728,
"download_url": "http://www.mikrotik.com/download",
"direct_download_url": "https://download2.mikrotik.com/routeros/6.40.7/chr-6.40.7.img.zip",
"compression": "zip"
},
{
"filename": "chr-6.40.5.img",
"version": "6.40.5",
@@ -235,6 +262,18 @@
}
],
"versions": [
{
"name": "6.41.4",
"images": {
"hda_disk_image": "chr-6.41.4.img"
}
},
{
"name": "6.40.7",
"images": {
"hda_disk_image": "chr-6.40.7.img"
}
},
{
"name": "6.40.5",
"images": {
+1
View File
@@ -9,6 +9,7 @@
"product_url": "https://www.opensuse.org/#Leap",
"registry_version": 4,
"status": "stable",
"availability": "free",
"maintainer": "GNS3 Team",
"maintainer_email": "developers@gns3.net",
"usage": "Username: osboxes\nPassword: osboxes.org\n\nroot password: osboxes.org",
@@ -22,6 +22,15 @@
"kvm": "require"
},
"images": [
{
"filename": "PacketFenceZEN_USB-7.4.0.img",
"version": "7.4.0",
"md5sum": "83951211540f16dd5813c26955c52429",
"filesize": 3221225472,
"download_url": "https://packetfence.org/download.html#/zen",
"direct_download_url": "https://sourceforge.net/projects/packetfence/files/PacketFence%20ZEN/7.4.0/PacketFenceZEN_USB-7.4.0.tar.bz2/download",
"compression": "bzip2"
},
{
"filename": "PacketFenceZEN_USB-7.3.0.img",
"version": "7.3.0",
@@ -96,6 +105,12 @@
}
],
"versions": [
{
"name": "7.4.0",
"images": {
"hda_disk_image": "PacketFenceZEN_USB-7.4.0.img"
}
},
{
"name": "7.3.0",
"images": {
+61 -23
View File
@@ -27,17 +27,31 @@
},
"images": [
{
"filename": "PA-VM-ESX-6.1.0-disk1.vmdk",
"version": "6.1.0 (ESX)",
"md5sum": "64b1e81cd54008318235832ea6d71424",
"filesize": 2959736832,
"filename": "PA-VM-ESX-8.1.0-disk1.vmdk",
"version": "8.1.0",
"md5sum": "49af8e8225c2e90414bde0be15eaf421",
"filesize": 2281454080,
"download_url": "https://support.paloaltonetworks.com/Updates/SoftwareUpdates/"
},
{
"filename": "PA-VM-KVM-7.1.0.qcow2",
"version": "7.1.0",
"md5sum": "da300253709740068927408239c2e321",
"filesize": 1858797568,
"filename": "PA-VM-KVM-8.1.0.qcow2",
"version": "8.1.0",
"md5sum": "459558515b965b2e43fde2842abbae66",
"filesize": 2260467712,
"download_url": "https://support.paloaltonetworks.com/Updates/SoftwareUpdates/"
},
{
"filename": "PA-VM-ESX-8.0.0-disk1.vmdk",
"version": "8.0.0",
"md5sum": "a505fb1dbcc855ecf98630fd5d329f9a",
"filesize": 2002713088,
"download_url": "https://support.paloaltonetworks.com/Updates/SoftwareUpdates/"
},
{
"filename": "PA-VM-KVM-8.0.0.qcow2",
"version": "8.0.0",
"md5sum": "b6a1ddc8552aff87f05f9c0d4cb54dc3",
"filesize": 1987444736,
"download_url": "https://support.paloaltonetworks.com/Updates/SoftwareUpdates/"
},
{
@@ -48,31 +62,37 @@
"download_url": "https://support.paloaltonetworks.com/Updates/SoftwareUpdates/"
},
{
"filename": "PA-VM-KVM-8.0.0.qcow2",
"version": "8.0.0",
"md5sum": "b6a1ddc8552aff87f05f9c0d4cb54dc3",
"filesize": 1987444736,
"filename": "PA-VM-KVM-7.1.0.qcow2",
"version": "7.1.0",
"md5sum": "da300253709740068927408239c2e321",
"filesize": 1858797568,
"download_url": "https://support.paloaltonetworks.com/Updates/SoftwareUpdates/"
}
},
{
"filename": "PA-VM-ESX-6.1.0-disk1.vmdk",
"version": "6.1.0 (ESX)",
"md5sum": "64b1e81cd54008318235832ea6d71424",
"filesize": 2959736832,
"download_url": "https://support.paloaltonetworks.com/Updates/SoftwareUpdates/"
}
],
"versions": [
{
"name": "6.1.0 (ESX)",
"name": "8.1.0 (ESX)",
"images": {
"hda_disk_image": "PA-VM-ESX-6.1.0-disk1.vmdk"
"hda_disk_image": "PA-VM-ESX-8.1.0-disk1.vmdk"
}
},
},
{
"name": "7.1.0",
"name": "8.1.0",
"images": {
"hda_disk_image": "PA-VM-KVM-7.1.0.qcow2"
"hda_disk_image": "PA-VM-KVM-8.1.0.qcow2"
}
},
},
{
"name": "7.1.0 (ESX)",
"name": "8.0.0 (ESX)",
"images": {
"hda_disk_image": "PA-VM-ESX-7.1.0-disk1.vmdk"
"hda_disk_image": "PA-VM-ESX-8.0.0-disk1.vmdk2"
}
},
{
@@ -80,6 +100,24 @@
"images": {
"hda_disk_image": "PA-VM-KVM-8.0.0.qcow2"
}
}
},
{
"name": "7.1.0 (ESX)",
"images": {
"hda_disk_image": "PA-VM-ESX-7.1.0-disk1.vmdk"
}
},
{
"name": "7.1.0",
"images": {
"hda_disk_image": "PA-VM-KVM-7.1.0.qcow2"
}
},
{
"name": "6.1.0 (ESX)",
"images": {
"hda_disk_image": "PA-VM-ESX-6.1.0-disk1.vmdk"
}
}
]
}
+80
View File
@@ -0,0 +1,80 @@
{
"name": "Ubuntu Cloud Guest",
"category": "guest",
"description": "The term 'Ubuntu Cloud Guest' refers to the Official Ubuntu images that are available at http://cloud-images.ubuntu.com . These images are built by Canonical. They are then registered on EC2, and compressed tarfiles are made also available for download. For using those images on a public cloud such as Amazon EC2, you simply choose an image and launch it. To use those images on a private cloud, or to run the image on a local hypervisor (such as KVM) you would need to download those images and either publish them to your private cloud, or launch them directly on a hypervisor. The following sections explain in more details how to perform each of those actions",
"vendor_name": "Canonical Inc.",
"vendor_url": "https://www.ubuntu.com",
"documentation_url": "https://help.ubuntu.com/community/UEC/Images",
"product_name": "Ubuntu Cloud Guest",
"product_url": "https://www.ubuntu.com/cloud",
"registry_version": 3,
"status": "stable",
"maintainer": "GNS3 Team",
"maintainer_email": "developers@gns3.net",
"usage": "Username: ubuntu\nPassword: ubuntu",
"port_name_format": "Ethernet{0}",
"qemu": {
"adapter_type": "virtio-net-pci",
"adapters": 1,
"ram": 1024,
"hda_disk_interface": "virtio",
"arch": "x86_64",
"console_type": "telnet",
"boot_priority": "c",
"kvm": "require",
"options": "-nographic"
},
"images": [
{
"filename": "ubuntu-17.10-server-cloudimg-amd64.img",
"version": "17.10",
"md5sum": "331b44f2b05858c251b3ea92c8b65152",
"filesize": 320405504,
"download_url": "https://cloud-images.ubuntu.com/releases/17.10/release-20180404/ubuntu-17.10-server-cloudimg-amd64.img"
},
{
"filename": "ubuntu-16.04-server-cloudimg-amd64-disk1.img",
"version": "16.04",
"md5sum": "22c124ba65ea096cdef8b0a197dd613a",
"filesize": 290193408,
"download_url": "https://cloud-images.ubuntu.com/releases/16.04/release-20180405/ubuntu-16.04-server-cloudimg-amd64-disk1.img"
},
{
"filename": "ubuntu-14.04-server-cloudimg-amd64-disk1.img",
"version": "14.04",
"md5sum": "d11b89321d41d0eeddcacf73bf0d2262",
"filesize": 262668800,
"download_url": "https://cloud-images.ubuntu.com/releases/14.04/release-20180404/ubuntu-14.04-server-cloudimg-amd64-disk1.img"
},
{
"filename": "ubuntu-cloud-init-data.iso",
"version": "1.0",
"md5sum": "328469100156ae8dbf262daa319c27ff",
"filesize": 131072,
"download_url": "https://github.com/asenci/gns3-ubuntu-cloud-init-data/raw/master/ubuntu-cloud-init-data.iso"
}
],
"versions": [
{
"name": "17.10",
"images": {
"hda_disk_image": "ubuntu-17.10-server-cloudimg-amd64.img",
"cdrom_image": "ubuntu-cloud-init-data.iso"
}
},
{
"name": "16.04 (LTS)",
"images": {
"hda_disk_image": "ubuntu-16.04-server-cloudimg-amd64-disk1.img",
"cdrom_image": "ubuntu-cloud-init-data.iso"
}
},
{
"name": "14.04 (LTS)",
"images": {
"hda_disk_image": "ubuntu-14.04-server-cloudimg-amd64-disk1.img",
"cdrom_image": "ubuntu-cloud-init-data.iso"
}
}
]
}
@@ -1,5 +1,5 @@
{
"name": "Ubuntu",
"name": "Ubuntu Docker Guest",
"category": "guest",
"description": "Ubuntu is a Debian-based Linux operating system, with Unity as its default desktop environment. It is based on free software and named after the Southern African philosophy of ubuntu (literally, \"human-ness\"), which often is translated as \"humanity towards others\" or \"the belief in a universal bond of sharing that connects all humanity\".",
"vendor_name": "Canonical",
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name": "Ubuntu",
"name": "Ubuntu Desktop Guest",
"category": "guest",
"description": "Ubuntu is a full-featured Linux operating system which is based on Debian distribution and freely available with both community and professional support, it comes with Unity as its default desktop environment. There are other flavors of Ubuntu available with other desktops as default like Ubuntu Gnome, Lubuntu, Xubuntu, and so on. A tightly-integrated selection of excellent applications is included, and an incredible variety of add-on software is just a few clicks away. A default installation of Ubuntu contains a wide range of software that includes LibreOffice, Firefox, Empathy, Transmission, etc.",
"vendor_name": "Canonical Inc.",
+14
View File
@@ -24,6 +24,13 @@
"kvm": "allow"
},
"images": [
{
"filename": "untangle_1320_x64.iso",
"version": "13.2.0",
"md5sum": "0ce2293acec0f37f1339e703653727f8",
"filesize": 768000000,
"download_url": "https://www.untangle.com/get-untangle/"
},
{
"filename": "untangle_1310_x64.iso",
"version": "13.1.0",
@@ -90,6 +97,13 @@
}
],
"versions": [
{
"name": "13.2.0",
"images": {
"hda_disk_image": "empty30G.qcow2",
"cdrom_image": "untangle_1320_x64.iso"
}
},
{
"name": "13.1.0",
"images": {
+7
View File
@@ -80,6 +80,13 @@
"cdrom_image": "vyos-1.2.0-beta1-amd64.iso"
}
},
{
"name": "1.1.8",
"images": {
"hda_disk_image": "empty8G.qcow2",
"cdrom_image": "vyos-1.1.8-amd64.iso"
}
},
{
"name": "1.1.7",
"images": {
+14
View File
@@ -9,6 +9,7 @@
"product_url": "https://www.microsoft.com/en-us/windows",
"registry_version": 4,
"status": "stable",
"availability": "free-to-try",
"maintainer": "GNS3 Team",
"maintainer_email": "developers@gns3.net",
"usage": "These virtual machines expire after 90 days; i.e. you have to re-create them in your project after this time but you don't have to re-import the appliance.\n\nDefault credentials: IEUser / Passw0rd!",
@@ -25,6 +26,13 @@
"kvm": "require"
},
"images": [
{
"filename": "MSEdge-Win10-VMWare-disk1.vmdk",
"version": "10 w/ Edge",
"md5sum": "fef74c69e1949480d4e2095324a169af",
"filesize": 5636608512,
"download_url": "https://developer.microsoft.com/en-us/microsoft-edge/tools/vms/"
},
{
"filename": "MSEdge_-_Win10_preview.vmdk",
"version": "10 w/ Edge",
@@ -71,6 +79,12 @@
"versions": [
{
"name": "10 w/ Edge",
"images": {
"hda_disk_image": "MSEdge-Win10-VMWare-disk1.vmdk"
}
},
{
"name": "10 w/ Edge (Preview)",
"images": {
"hda_disk_image": "MSEdge_-_Win10_preview.vmdk"
}
+3 -1
View File
@@ -9,6 +9,7 @@
"product_url": "https://www.microsoft.com/en-us/windows",
"registry_version": 4,
"status": "stable",
"availability": "free-to-try",
"maintainer": "GNS3 Team",
"maintainer_email": "developers@gns3.net",
"symbol": "microsoft.svg",
@@ -21,7 +22,8 @@
"arch": "x86_64",
"console_type": "vnc",
"boot_priority": "c",
"kvm": "require"
"kvm": "require",
"options": "-usbdevice tablet"
},
"images": [
{
+5 -6
View File
@@ -24,15 +24,14 @@ from .virtualbox import VirtualBox
from .dynamips import Dynamips
from .qemu import Qemu
from .vmware import VMware
from .traceng import TraceNG
MODULES = [Builtin, VPCS, VirtualBox, Dynamips, Qemu, VMware]
MODULES = [Builtin, VPCS, VirtualBox, Dynamips, Qemu, VMware, TraceNG]
if sys.platform.startswith("linux") or hasattr(sys, "_called_from_test") or os.environ.get("PYTEST_BUILD_DOCUMENTATION") == "1":
from .docker import Docker
MODULES.append(Docker)
# IOU runs only on Linux but testsuite work on UNIX platform
# IOU & Docker only runs on Linux but test suite works on UNIX platform
if not sys.platform.startswith("win"):
from .docker import Docker
MODULES.append(Docker)
from .iou import IOU
MODULES.append(IOU)
+1 -1
View File
@@ -549,7 +549,7 @@ class BaseManager:
# We store the file under his final name only when the upload is finished
tmp_path = path + ".tmp"
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(tmp_path, 'wb+') as f:
with open(tmp_path, 'wb') as f:
while True:
packet = yield from stream.read(4096)
if not packet:
+17 -6
View File
@@ -25,6 +25,7 @@ import asyncio
import tempfile
import psutil
import platform
import re
from gns3server.utils.interfaces import interfaces
from ..compute.port_manager import PortManager
@@ -345,6 +346,7 @@ class BaseNode:
remaining_trial -= 1
yield from AsyncioTelnetServer.write_client_intro(writer, echo=True)
server = AsyncioTelnetServer(reader=reader, writer=writer, binary=True, echo=True)
# warning: this will raise OSError exception if there is a problem...
self._wrapper_telnet_server = yield from asyncio.start_server(server.run, self._manager.port_manager.console_host, self.console)
@property
@@ -512,7 +514,7 @@ class BaseNode:
try:
yield from self._ubridge_hypervisor.send(command)
except UbridgeError as e:
raise UbridgeError("{}: {}".format(e, self._ubridge_hypervisor.read_stdout()))
raise UbridgeError("Error while sending command '{}': {}: {}".format(command, e, self._ubridge_hypervisor.read_stdout()))
@locked_coroutine
def _start_ubridge(self):
@@ -598,15 +600,24 @@ class BaseNode:
@asyncio.coroutine
def _ubridge_apply_filters(self, bridge_name, filters):
"""
Apply filter like rate limiting
Apply packet filters
:param bridge_name: bridge name in uBridge
:param filters: Array of filter dictionnary
:param filters: Array of filter dictionary
"""
yield from self._ubridge_send('bridge reset_packet_filters ' + bridge_name)
for filter in self._build_filter_list(filters):
cmd = 'bridge add_packet_filter {} {}'.format(bridge_name, filter)
yield from self._ubridge_send(cmd)
for packet_filter in self._build_filter_list(filters):
cmd = 'bridge add_packet_filter {} {}'.format(bridge_name, packet_filter)
try:
yield from self._ubridge_send(cmd)
except UbridgeError as e:
match = re.search("Cannot compile filter '(.*)': syntax error", str(e))
if match:
message = "Warning: ignoring BPF packet filter '{}' due to syntax error".format(self.name, match.group(1))
log.warning(message)
self.project.emit("log.warning", {"message": message})
else:
raise
def _build_filter_list(self, filters):
"""
+6 -2
View File
@@ -99,7 +99,7 @@ class Docker(BaseManager):
:param method: HTTP method
:param path: Endpoint in API
:param data: Dictionnary with the body. Will be transformed to a JSON
:param data: Dictionary with the body. Will be transformed to a JSON
:param params: Parameters added as a query arg
"""
@@ -198,7 +198,7 @@ class Docker(BaseManager):
pass
if progress_callback:
progress_callback("Pull {} from docker hub".format(image))
progress_callback("Pulling '{}' from docker hub".format(image))
response = yield from self.http_query("POST", "images/create", params={"fromImage": image}, timeout=None)
# The pull api will stream status via an HTTP JSON stream
content = ""
@@ -206,6 +206,10 @@ class Docker(BaseManager):
try:
chunk = yield from response.content.read(1024)
except aiohttp.ServerDisconnectedError:
log.error("Disconnected from server while pulling Docker image '{}' from docker hub".format(image))
break
except asyncio.TimeoutError:
log.error("Timeout while pulling Docker image '{}' from docker hub".format(image))
break
if not chunk:
break
+63 -8
View File
@@ -61,11 +61,12 @@ class DockerVM(BaseNode):
:param console_resolution: Resolution of the VNC display
:param console_http_port: Port to redirect HTTP queries
:param console_http_path: Url part with the path of the web interface
:param extra_hosts: Hosts which will be written into /etc/hosts into docker conainer
"""
def __init__(self, name, node_id, project, manager, image, console=None, aux=None, start_command=None,
adapters=None, environment=None, console_type="telnet", console_resolution="1024x768",
console_http_port=80, console_http_path="/"):
console_http_port=80, console_http_path="/", extra_hosts=None):
super().__init__(name, node_id, project, manager, console=console, aux=aux, allocate_aux=True, console_type=console_type)
@@ -84,6 +85,8 @@ class DockerVM(BaseNode):
self._console_http_path = console_http_path
self._console_http_port = console_http_port
self._console_websocket = None
self._extra_hosts = extra_hosts
self._volumes = []
# Keep a list of created bridge
self._bridges = set()
@@ -114,7 +117,8 @@ class DockerVM(BaseNode):
"start_command": self.start_command,
"status": self.status,
"environment": self.environment,
"node_directory": self.working_path
"node_directory": self.working_path,
"extra_hosts": self.extra_hosts
}
def _get_free_display_port(self):
@@ -178,6 +182,14 @@ class DockerVM(BaseNode):
def environment(self, command):
self._environment = command
@property
def extra_hosts(self):
return self._extra_hosts
@extra_hosts.setter
def extra_hosts(self, extra_hosts):
self._extra_hosts = extra_hosts
@asyncio.coroutine
def _get_container_state(self):
"""Returns the container state (e.g. running, paused etc.)
@@ -288,7 +300,7 @@ class DockerVM(BaseNode):
"HostConfig": {
"CapAdd": ["ALL"],
"Privileged": True,
"Binds": self._mount_binds(image_infos)
"Binds": self._mount_binds(image_infos),
},
"Volumes": {},
"Env": ["container=docker"], # Systemd compliant: https://github.com/GNS3/gns3-server/issues/573
@@ -313,11 +325,20 @@ class DockerVM(BaseNode):
# Give the information to the container the list of volume path mounted
params["Env"].append("GNS3_VOLUMES={}".format(":".join(self._volumes)))
variables = self.project.variables
if not variables:
variables = []
for var in variables:
formatted = self._format_env(variables, var.get('value', ''))
params["Env"].append("{}={}".format(var["name"], formatted))
if self._environment:
for e in self._environment.split("\n"):
for e in self._environment.strip().split("\n"):
e = e.strip()
if not e.startswith("GNS3_"):
params["Env"].append(e)
formatted = self._format_env(variables, e)
params["Env"].append(formatted)
if self._console_type == "vnc":
yield from self._start_vnc()
@@ -325,12 +346,36 @@ class DockerVM(BaseNode):
params["Env"].append("DISPLAY=:{}".format(self._display))
params["HostConfig"]["Binds"].append("/tmp/.X11-unix/:/tmp/.X11-unix/")
if self._extra_hosts:
extra_hosts = self._format_extra_hosts(self._extra_hosts)
if extra_hosts:
params["Env"].append("GNS3_EXTRA_HOSTS={}".format(extra_hosts))
result = yield from self.manager.query("POST", "containers/create", data=params)
self._cid = result['Id']
log.info("Docker container '{name}' [{id}] created".format(
name=self._name, id=self._id))
return True
def _format_env(self, variables, env):
for variable in variables:
env = env.replace('${' + variable["name"] + '}', variable.get("value", ""))
return env
def _format_extra_hosts(self, extra_hosts):
lines = [h.strip() for h in self._extra_hosts.split("\n") if h.strip() != ""]
hosts = []
try:
for host in lines:
hostname, ip = host.split(":")
hostname = hostname.strip()
ip = ip.strip()
if hostname and ip:
hosts.append((hostname, ip))
except ValueError:
raise DockerError("Can't apply `ExtraHosts`, wrong format: {}".format(extra_hosts))
return "\n".join(["{}\t{}".format(h[1], h[0]) for h in hosts])
@asyncio.coroutine
def update(self):
"""
@@ -352,7 +397,11 @@ class DockerVM(BaseNode):
def start(self):
"""Starts this Docker container."""
state = yield from self._get_container_state()
try:
state = yield from self._get_container_state()
except DockerHttp404Error:
raise DockerError("Docker container '{name}' with ID {cid} does not exist or is not ready yet. Please try again in a few seconds.".format(name=self.name,
cid=self._cid))
if state == "paused":
yield from self.unpause()
elif state == "running":
@@ -408,7 +457,10 @@ class DockerVM(BaseNode):
stderr=asyncio.subprocess.STDOUT,
stdin=asyncio.subprocess.PIPE)
server = AsyncioTelnetServer(reader=process.stdout, writer=process.stdin, binary=True, echo=True)
self._telnet_servers.append((yield from asyncio.start_server(server.run, self._manager.port_manager.console_host, self.aux)))
try:
self._telnet_servers.append((yield from asyncio.start_server(server.run, self._manager.port_manager.console_host, self.aux)))
except OSError as e:
raise DockerError("Could not start Telnet server on socket {}:{}: {}".format(self._manager.port_manager.console_host, self.aux, e))
log.debug("Docker container '%s' started listen for auxilary telnet on %d", self.name, self.aux)
@asyncio.coroutine
@@ -504,7 +556,10 @@ class DockerVM(BaseNode):
input_stream = InputStream()
telnet = AsyncioTelnetServer(reader=output_stream, writer=input_stream, echo=True)
self._telnet_servers.append((yield from asyncio.start_server(telnet.run, self._manager.port_manager.console_host, self.console)))
try:
self._telnet_servers.append((yield from asyncio.start_server(telnet.run, self._manager.port_manager.console_host, self.console)))
except OSError as e:
raise DockerError("Could not start Telnet server on socket {}:{}: {}".format(self._manager.port_manager.console_host, self.console, e))
self._console_websocket = yield from self.manager.websocket_query("containers/{}/attach/ws?stream=1&stdin=1&stdout=1&stderr=1".format(self._cid))
input_stream.ws = self._console_websocket
@@ -60,6 +60,14 @@ ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
__EOF__
# imitate docker's `ExtraHosts` behaviour
sed -i '/GNS3_EXTRA_HOSTS_START/,/GNS3_EXTRA_HOSTS_END/d' /etc/hosts
[ -n "$GNS3_EXTRA_HOSTS" ] && cat >> /etc/hosts << __EOF__
# GNS3_EXTRA_HOSTS_START
$GNS3_EXTRA_HOSTS
# GNS3_EXTRA_HOSTS_END
__EOF__
# configure loopback interface
ip link set dev lo up
+1 -1
View File
@@ -547,7 +547,7 @@ class Dynamips(BaseManager):
content = content.replace('%h', vm.name)
f.write(content.encode("utf-8"))
except OSError as e:
raise DynamipsError("Could not create config file {}: {}".format(path, e))
raise DynamipsError("Could not create config file '{}': {}".format(path, e))
return os.path.join("configs", os.path.basename(path))
@@ -260,6 +260,8 @@ class DynamipsHypervisor:
# Now retrieve the result
data = []
buf = ''
retries = 0
max_retries = 10
while True:
try:
try:
@@ -276,8 +278,14 @@ class DynamipsHypervisor:
log.warning("Connection reset received while reading Dynamips response: {}".format(e))
continue
if not chunk:
raise DynamipsError("No data returned from {host}:{port}, Dynamips process running: {run}"
.format(host=self._host, port=self._port, run=self.is_running()))
if retries > max_retries:
raise DynamipsError("No data returned from {host}:{port}, Dynamips process running: {run}"
.format(host=self._host, port=self._port, run=self.is_running()))
else:
retries += 1
yield from asyncio.sleep(0.1)
continue
retries = 0
buf += chunk.decode("utf-8", errors="ignore")
except OSError as e:
raise DynamipsError("Could not read response for '{command}' from {host}:{port}: {error}, process running: {run}"
@@ -167,8 +167,10 @@ class EthernetSwitch(Device):
self._telnet_shell = EthernetSwitchConsole(self)
self._telnet_shell.prompt = self._name + '> '
self._telnet = create_telnet_shell(self._telnet_shell)
self._telnet_server = (yield from asyncio.start_server(self._telnet.run, self._manager.port_manager.console_host, self.console))
try:
self._telnet_server = (yield from asyncio.start_server(self._telnet.run, self._manager.port_manager.console_host, self.console))
except OSError as e:
self.project.emit("log.warning", {"message": "Could not start Telnet server on socket {}:{}: {}".format(self._manager.port_manager.console_host, self.console, e)})
self._hypervisor.devices.append(self)
@asyncio.coroutine
+8 -3
View File
@@ -131,14 +131,16 @@ class Router(BaseNode):
try:
shutil.move(path, dst)
except OSError as e:
raise DynamipsError("Can't move {}: {}".format(path, str(e)))
log.error("Can't move {}: {}".format(path, str(e)))
continue
for path in glob.glob(os.path.join(glob.escape(dynamips_dir), "*_i{}_*".format(dynamips_id))):
dst = os.path.join(self._working_directory, os.path.basename(path))
if not os.path.exists(dst):
try:
shutil.move(path, dst)
except OSError as e:
raise DynamipsError("Can't move {}: {}".format(path, str(e)))
log.error("Can't move {}: {}".format(path, str(e)))
continue
def __json__(self):
@@ -283,12 +285,15 @@ class Router(BaseNode):
if not self._ghost_flag:
self.check_available_ram(self.ram)
# config paths are relative to the working directory configured on Dynamips hypervisor
startup_config_path = os.path.join("configs", "i{}_startup-config.cfg".format(self._dynamips_id))
private_config_path = os.path.join("configs", "i{}_private-config.cfg".format(self._dynamips_id))
if not os.path.exists(private_config_path) or not os.path.getsize(private_config_path):
if not os.path.exists(os.path.join(self._working_directory, private_config_path)) or \
not os.path.getsize(os.path.join(self._working_directory, private_config_path)):
# an empty private-config can prevent a router to boot.
private_config_path = ''
yield from self._hypervisor.send('vm set_config "{name}" "{startup}" "{private}"'.format(
name=self._name,
startup=startup_config_path,
+8 -34
View File
@@ -25,6 +25,7 @@ import asyncio
from ..base_manager import BaseManager
from .iou_error import IOUError
from .iou_vm import IOUVM
from .utils.application_id import get_next_application_id
import logging
log = logging.getLogger(__name__)
@@ -38,8 +39,7 @@ class IOU(BaseManager):
def __init__(self):
super().__init__()
self._free_application_ids = list(range(1, 512))
self._used_application_ids = {}
self._iou_id_lock = asyncio.Lock()
@asyncio.coroutine
def create_node(self, *args, **kwargs):
@@ -49,40 +49,14 @@ class IOU(BaseManager):
:returns: IOUVM instance
"""
node = yield from super().create_node(*args, **kwargs)
try:
self._used_application_ids[node.id] = self._free_application_ids.pop(0)
except IndexError:
raise IOUError("Cannot create a new IOU VM (limit of 512 VMs reached on this host)")
with (yield from self._iou_id_lock):
# wait for a node to be completely created before adding a new one
# this is important otherwise we allocate the same application ID
# when creating multiple IOU node at the same time
application_id = get_next_application_id(self.nodes)
node = yield from super().create_node(*args, application_id=application_id, **kwargs)
return node
@asyncio.coroutine
def close_node(self, node_id, *args, **kwargs):
"""
Closes an IOU VM.
:returns: IOUVM instance
"""
node = self.get_node(node_id)
if node_id in self._used_application_ids:
i = self._used_application_ids[node_id]
self._free_application_ids.insert(0, i)
del self._used_application_ids[node_id]
yield from super().close_node(node_id, *args, **kwargs)
return node
def get_application_id(self, node_id):
"""
Get an unique application identifier for IOU.
:param node_id: Node identifier
:returns: IOU application identifier
"""
return self._used_application_ids.get(node_id, 1)
@staticmethod
def get_legacy_vm_workdir(legacy_vm_id, name):
"""
+10 -8
View File
@@ -65,7 +65,7 @@ class IOUVM(BaseNode):
:param console: TCP console port
"""
def __init__(self, name, node_id, project, manager, path=None, console=None):
def __init__(self, name, node_id, project, manager, application_id=None, path=None, console=None):
super().__init__(name, node_id, project, manager, console=console)
@@ -86,7 +86,7 @@ class IOUVM(BaseNode):
self._startup_config = ""
self._private_config = ""
self._ram = 256 # Megabytes
self._application_id = None
self._application_id = application_id
self._l1_keepalives = False # used to overcome the always-up Ethernet interfaces (not supported by all IOSes).
def _config(self):
@@ -348,14 +348,14 @@ class IOUVM(BaseNode):
# reload
path = os.path.join(os.path.expanduser("~/"), ".iourc")
try:
with open(path, "wb+") as f:
with open(path, "wb") as f:
f.write(value.encode("utf-8"))
except OSError as e:
raise IOUError("Could not write the iourc file {}: {}".format(path, e))
path = os.path.join(self.temporary_directory, "iourc")
try:
with open(path, "wb+") as f:
with open(path, "wb") as f:
f.write(value.encode("utf-8"))
except OSError as e:
raise IOUError("Could not write the iourc file {}: {}".format(path, e))
@@ -541,7 +541,11 @@ class IOUVM(BaseNode):
raise IOUError("Could not start IOU {}: {}\n{}".format(self._path, e, iou_stdout))
server = AsyncioTelnetServer(reader=self._iou_process.stdout, writer=self._iou_process.stdin, binary=True, echo=True)
self._telnet_server = yield from asyncio.start_server(server.run, self._manager.port_manager.console_host, self.console)
try:
self._telnet_server = yield from asyncio.start_server(server.run, self._manager.port_manager.console_host, self.console)
except OSError as e:
yield from self.stop()
raise IOUError("Could not start Telnet server on socket {}:{}: {}".format(self._manager.port_manager.console_host, self.console, e))
# configure networking support
yield from self._networking()
@@ -1141,9 +1145,7 @@ class IOUVM(BaseNode):
:returns: integer between 1 and 512
"""
if self._application_id is None:
#FIXME: is this necessary? application ID is allocated by controller and should not be None
return self._manager.get_application_id(self.id)
return self._application_id
@application_id.setter
@@ -24,13 +24,14 @@ log = logging.getLogger(__name__)
def get_next_application_id(nodes):
"""
Calculates free application_id from given nodes
:param nodes:
:raises IOUError when exceeds number
:return: integer first free id
"""
used = set([n.properties.get('application_id') for n in nodes if n.node_type == 'iou'])
used = set([n.application_id for n in nodes])
pool = set(range(1, 512))
try:
return (pool - used).pop()
except KeyError:
raise IOUError("Cannot create a new IOU VM (limit of 512 VMs reached)")
raise IOUError("Cannot create a new IOU VM (limit of 512 VMs on one host reached)")
+24 -3
View File
@@ -25,13 +25,13 @@ import zipfile
import json
from uuid import UUID, uuid4
from .port_manager import PortManager
from .notification_manager import NotificationManager
from ..config import Config
from ..utils.asyncio import wait_run_in_executor
from ..utils.path import check_path_allowed, get_default_project_directory
import logging
log = logging.getLogger(__name__)
@@ -46,7 +46,7 @@ class Project:
:param path: path of the project. (None use the standard directory)
"""
def __init__(self, name=None, project_id=None, path=None):
def __init__(self, name=None, project_id=None, path=None, variables=None):
self._name = name
if project_id:
@@ -61,6 +61,7 @@ class Project:
self._nodes = set()
self._used_tcp_ports = set()
self._used_udp_ports = set()
self._variables = variables
if path is None:
location = get_default_project_directory()
@@ -83,7 +84,8 @@ class Project:
return {
"name": self._name,
"project_id": self._id
"project_id": self._id,
"variables": self._variables
}
def _config(self):
@@ -131,6 +133,14 @@ class Project:
return self._nodes
@property
def variables(self):
return self._variables
@variables.setter
def variables(self, variables):
self._variables = variables
def record_tcp_port(self, port):
"""
Associate a reserved TCP port number with this project.
@@ -287,6 +297,17 @@ class Project:
yield from node.delete()
self._nodes.remove(node)
@asyncio.coroutine
def update(self, variables=None, **kwargs):
original_variables = self.variables
self.variables = variables
# we need to update docker nodes when variables changes
if original_variables != variables:
for node in self.nodes:
if hasattr(node, 'update'):
yield from node.update()
@asyncio.coroutine
def close(self):
"""
+4 -3
View File
@@ -16,6 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import aiohttp
import asyncio
import psutil
import platform
from .project import Project
@@ -95,16 +96,16 @@ class ProjectManager:
log.warning(message)
project.emit("log.warning", {"message": message})
def create_project(self, name=None, project_id=None, path=None):
def create_project(self, name=None, project_id=None, path=None, variables=None):
"""
Create a project and keep a references to it in project manager.
See documentation of Project for arguments
"""
if project_id is not None and project_id in self._projects:
return self._projects[project_id]
project = Project(name=name, project_id=project_id, path=path)
project = Project(name=name, project_id=project_id,
path=path, variables=variables)
self._check_available_disk_space(project)
self._projects[project.id] = project
return project
+2 -1
View File
@@ -533,7 +533,7 @@ class QemuVM(BaseNode):
if not mac_address:
# use the node UUID to generate a random MAC address
self._mac_address = "00:%s:%s:%s:%s:00" % (self.project.id[-4:-2], self.project.id[-2:], self.id[-4:-2], self.id[-2:])
self._mac_address = "0c:%s:%s:%s:%s:00" % (self.project.id[-4:-2], self.project.id[-2:], self.id[-4:-2], self.id[-2:])
else:
self._mac_address = mac_address
@@ -894,6 +894,7 @@ class QemuVM(BaseNode):
af, socktype, proto, _, sa = res
# let the OS find an unused port for the Qemu monitor
with socket.socket(af, socktype, proto) as sock:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(sa)
self._monitor = sock.getsockname()[1]
except OSError as e:
+45
View File
@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2018 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/>.
"""
TraceNG server module.
"""
import asyncio
from ..base_manager import BaseManager
from .traceng_error import TraceNGError
from .traceng_vm import TraceNGVM
class TraceNG(BaseManager):
_NODE_CLASS = TraceNGVM
def __init__(self):
super().__init__()
@asyncio.coroutine
def create_node(self, *args, **kwargs):
"""
Creates a new TraceNG VM.
:returns: TraceNGVM instance
"""
return (yield from super().create_node(*args, **kwargs))
@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2018 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 TraceNG module.
"""
from ..error import NodeError
class TraceNGError(NodeError):
pass
+458
View File
@@ -0,0 +1,458 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2018 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/>.
"""
TraceNG VM management in order to run a TraceNG VM.
"""
import sys
import os
import socket
import subprocess
import asyncio
import shutil
import ipaddress
from gns3server.utils.asyncio import wait_for_process_termination
from gns3server.utils.asyncio import monitor_process
from .traceng_error import TraceNGError
from ..adapters.ethernet_adapter import EthernetAdapter
from ..nios.nio_udp import NIOUDP
from ..base_node import BaseNode
import logging
log = logging.getLogger(__name__)
class TraceNGVM(BaseNode):
module_name = 'traceng'
"""
TraceNG VM implementation.
:param name: TraceNG VM name
:param node_id: Node identifier
:param project: Project instance
:param manager: Manager instance
:param console: TCP console port
"""
def __init__(self, name, node_id, project, manager, console=None, console_type="none"):
super().__init__(name, node_id, project, manager, console=console, console_type=console_type)
self._process = None
self._started = False
self._ip_address = None
self._default_destination = None
self._destination = None
self._local_udp_tunnel = None
self._ethernet_adapter = EthernetAdapter() # one adapter with 1 Ethernet interface
@property
def ethernet_adapter(self):
return self._ethernet_adapter
@asyncio.coroutine
def close(self):
"""
Closes this TraceNG VM.
"""
if not (yield from super().close()):
return False
nio = self._ethernet_adapter.get_nio(0)
if isinstance(nio, NIOUDP):
self.manager.port_manager.release_udp_port(nio.lport, self._project)
if self._local_udp_tunnel:
self.manager.port_manager.release_udp_port(self._local_udp_tunnel[0].lport, self._project)
self.manager.port_manager.release_udp_port(self._local_udp_tunnel[1].lport, self._project)
self._local_udp_tunnel = None
yield from self._stop_ubridge()
if self.is_running():
self._terminate_process()
return True
@asyncio.coroutine
def _check_requirements(self):
"""
Check if TraceNG is available.
"""
path = self._traceng_path()
if not path:
raise TraceNGError("No path to a TraceNG executable has been set")
# This raise an error if ubridge is not available
self.ubridge_path
if not os.path.isfile(path):
raise TraceNGError("TraceNG program '{}' is not accessible".format(path))
if not os.access(path, os.X_OK):
raise TraceNGError("TraceNG program '{}' is not executable".format(path))
def __json__(self):
return {"name": self.name,
"ip_address": self.ip_address,
"default_destination": self._default_destination,
"node_id": self.id,
"node_directory": self.working_path,
"status": self.status,
"console": self._console,
"console_type": "none",
"project_id": self.project.id,
"command_line": self.command_line}
def _traceng_path(self):
"""
Returns the TraceNG executable path.
:returns: path to TraceNG
"""
search_path = self._manager.config.get_section_config("TraceNG").get("traceng_path", "traceng")
path = shutil.which(search_path)
# shutil.which return None if the path doesn't exists
if not path:
return search_path
return path
@property
def ip_address(self):
"""
Returns the IP address for this node.
:returns: IP address
"""
return self._ip_address
@ip_address.setter
def ip_address(self, ip_address):
"""
Sets the IP address of this node.
:param ip_address: IP address
"""
try:
if ip_address:
ipaddress.IPv4Address(ip_address)
except ipaddress.AddressValueError:
raise TraceNGError("Invalid IP address: {}\n".format(ip_address))
self._ip_address = ip_address
log.info("{module}: {name} [{id}] set IP address to {ip_address}".format(module=self.manager.module_name,
name=self.name,
id=self.id,
ip_address=ip_address))
@property
def default_destination(self):
"""
Returns the default destination IP/host for this node.
:returns: destination IP/host
"""
return self._default_destination
@default_destination.setter
def default_destination(self, destination):
"""
Sets the destination IP/host for this node.
:param destination: destination IP/host
"""
self._default_destination = destination
log.info("{module}: {name} [{id}] set default destination to {destination}".format(module=self.manager.module_name,
name=self.name,
id=self.id,
destination=destination))
@asyncio.coroutine
def start(self, destination=None):
"""
Starts the TraceNG process.
"""
if not sys.platform.startswith("win"):
raise TraceNGError("Sorry, TraceNG can only run on Windows")
yield from self._check_requirements()
if not self.is_running():
nio = self._ethernet_adapter.get_nio(0)
command = self._build_command(destination)
yield from self._stop_ubridge() # make use we start with a fresh uBridge instance
try:
log.info("Starting TraceNG: {}".format(command))
flags = 0
if hasattr(subprocess, "CREATE_NEW_CONSOLE"):
flags = subprocess.CREATE_NEW_CONSOLE
self.command_line = ' '.join(command)
self._process = yield from asyncio.create_subprocess_exec(*command,
cwd=self.working_dir,
creationflags=flags)
monitor_process(self._process, self._termination_callback)
yield from self._start_ubridge()
if nio:
yield from self.add_ubridge_udp_connection("TraceNG-{}".format(self._id), self._local_udp_tunnel[1], nio)
log.info("TraceNG instance {} started PID={}".format(self.name, self._process.pid))
self._started = True
self.status = "started"
except (OSError, subprocess.SubprocessError) as e:
log.error("Could not start TraceNG {}: {}\n".format(self._traceng_path(), e))
raise TraceNGError("Could not start TraceNG {}: {}\n".format(self._traceng_path(), e))
def _termination_callback(self, returncode):
"""
Called when the process has stopped.
:param returncode: Process returncode
"""
if self._started:
log.info("TraceNG process has stopped, return code: %d", returncode)
self._started = False
self.status = "stopped"
self._process = None
if returncode != 0:
self.project.emit("log.error", {"message": "TraceNG process has stopped, return code: {}\n".format(returncode)})
@asyncio.coroutine
def stop(self):
"""
Stops the TraceNG process.
"""
yield from self._stop_ubridge()
if self.is_running():
self._terminate_process()
if self._process.returncode is None:
try:
yield from wait_for_process_termination(self._process, timeout=3)
except asyncio.TimeoutError:
if self._process.returncode is None:
try:
self._process.kill()
except OSError as e:
log.error("Cannot stop the TraceNG process: {}".format(e))
if self._process.returncode is None:
log.warning('TraceNG VM "{}" with PID={} is still running'.format(self._name, self._process.pid))
self._process = None
self._started = False
yield from super().stop()
@asyncio.coroutine
def reload(self):
"""
Reloads the TraceNG process (stop & start).
"""
yield from self.stop()
yield from self.start(self._destination)
def _terminate_process(self):
"""
Terminate the process if running
"""
log.info("Stopping TraceNG instance {} PID={}".format(self.name, self._process.pid))
#if sys.platform.startswith("win32"):
# self._process.send_signal(signal.CTRL_BREAK_EVENT)
#else:
try:
self._process.terminate()
# Sometime the process may already be dead when we garbage collect
except ProcessLookupError:
pass
def is_running(self):
"""
Checks if the TraceNG process is running
:returns: True or False
"""
if self._process and self._process.returncode is None:
return True
return False
@asyncio.coroutine
def port_add_nio_binding(self, port_number, nio):
"""
Adds a port NIO binding.
:param port_number: port number
:param nio: NIO instance to add to the slot/port
"""
if not self._ethernet_adapter.port_exists(port_number):
raise TraceNGError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter,
port_number=port_number))
if self.is_running():
yield from self.add_ubridge_udp_connection("TraceNG-{}".format(self._id), self._local_udp_tunnel[1], nio)
self._ethernet_adapter.add_nio(port_number, nio)
log.info('TraceNG "{name}" [{id}]: {nio} added to port {port_number}'.format(name=self._name,
id=self.id,
nio=nio,
port_number=port_number))
return nio
@asyncio.coroutine
def port_update_nio_binding(self, port_number, nio):
if not self._ethernet_adapter.port_exists(port_number):
raise TraceNGError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter,
port_number=port_number))
if self.is_running():
yield from self.update_ubridge_udp_connection("TraceNG-{}".format(self._id), self._local_udp_tunnel[1], nio)
@asyncio.coroutine
def port_remove_nio_binding(self, port_number):
"""
Removes a port NIO binding.
:param port_number: port number
:returns: NIO instance
"""
if not self._ethernet_adapter.port_exists(port_number):
raise TraceNGError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter,
port_number=port_number))
if self.is_running():
yield from self._ubridge_send("bridge delete {name}".format(name="TraceNG-{}".format(self._id)))
nio = self._ethernet_adapter.get_nio(port_number)
if isinstance(nio, NIOUDP):
self.manager.port_manager.release_udp_port(nio.lport, self._project)
self._ethernet_adapter.remove_nio(port_number)
log.info('TraceNG "{name}" [{id}]: {nio} removed from port {port_number}'.format(name=self._name,
id=self.id,
nio=nio,
port_number=port_number))
return nio
@asyncio.coroutine
def start_capture(self, port_number, output_file):
"""
Starts a packet capture.
:param port_number: port number
:param output_file: PCAP destination file for the capture
"""
if not self._ethernet_adapter.port_exists(port_number):
raise TraceNGError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter,
port_number=port_number))
nio = self._ethernet_adapter.get_nio(0)
if not nio:
raise TraceNGError("Port {} is not connected".format(port_number))
if nio.capturing:
raise TraceNGError("Packet capture is already activated on port {port_number}".format(port_number=port_number))
nio.startPacketCapture(output_file)
if self.ubridge:
yield from self._ubridge_send('bridge start_capture {name} "{output_file}"'.format(name="TraceNG-{}".format(self._id),
output_file=output_file))
log.info("TraceNG '{name}' [{id}]: starting packet capture on port {port_number}".format(name=self.name,
id=self.id,
port_number=port_number))
@asyncio.coroutine
def stop_capture(self, port_number):
"""
Stops a packet capture.
:param port_number: port number
"""
if not self._ethernet_adapter.port_exists(port_number):
raise TraceNGError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter,
port_number=port_number))
nio = self._ethernet_adapter.get_nio(0)
if not nio:
raise TraceNGError("Port {} is not connected".format(port_number))
nio.stopPacketCapture()
if self.ubridge:
yield from self._ubridge_send('bridge stop_capture {name}'.format(name="TraceNG-{}".format(self._id)))
log.info("TraceNG '{name}' [{id}]: stopping packet capture on port {port_number}".format(name=self.name,
id=self.id,
port_number=port_number))
def _build_command(self, destination):
"""
Command to start the TraceNG process.
(to be passed to subprocess.Popen())
"""
if not destination:
# use the default destination if no specific destination provided
destination = self.default_destination
if not destination:
raise TraceNGError("Please provide a host or IP address to trace")
if not self.ip_address:
raise TraceNGError("Please configure an IP address for this TraceNG node")
if self.ip_address == destination:
raise TraceNGError("Destination cannot be the same as the IP address")
self._destination = destination
command = [self._traceng_path()]
# use the local UDP tunnel to uBridge instead
if not self._local_udp_tunnel:
self._local_udp_tunnel = self._create_local_udp_tunnel()
nio = self._local_udp_tunnel[0]
if nio and isinstance(nio, NIOUDP):
# UDP tunnel
command.extend(["-u"]) # enable UDP tunnel
command.extend(["-c", str(nio.lport)]) # source UDP port
command.extend(["-v", str(nio.rport)]) # destination UDP port
try:
command.extend(["-b", socket.gethostbyname(nio.rhost)]) # destination host, we need to resolve the hostname because TraceNG doesn't support it
except socket.gaierror as e:
raise TraceNGError("Can't resolve hostname {}: {}".format(nio.rhost, e))
command.extend(["-s", "ICMP"]) # Use ICMP probe type by default
command.extend(["-f", self._ip_address]) # source IP address to trace from
command.extend([destination]) # host or IP to trace
return command
@@ -942,7 +942,10 @@ class VirtualBoxVM(BaseNode):
writer=self._remote_pipe,
binary=True,
echo=True)
self._telnet_server = yield from asyncio.start_server(server.run, self._manager.port_manager.console_host, self.console)
try:
self._telnet_server = yield from asyncio.start_server(server.run, self._manager.port_manager.console_host, self.console)
except OSError as e:
self.project.emit("log.warning", {"message": "Could not start Telnet server on socket {}:{}: {}".format(self._manager.port_manager.console_host, self.console, e)})
@asyncio.coroutine
def _stop_remote_console(self):
+6 -6
View File
@@ -253,7 +253,7 @@ class VMware(BaseManager):
if winreg.QueryInfoKey(hkeyvmnet)[1]:
# the vmnet has not been configure if the key has no values
vmnet = vmnet.replace("vm", "VM")
if vmnet not in ("VMnet1", "VMnet8"):
if vmnet not in ("VMnet0", "VMnet1", "VMnet8"):
vmnet_interfaces.append(vmnet)
winreg.CloseKey(hkeyvmnet)
winreg.CloseKey(hkey)
@@ -278,7 +278,7 @@ class VMware(BaseManager):
match = re.search("VNET_([0-9]+)_VIRTUAL_ADAPTER", line)
if match:
vmnet = "vmnet{}".format(match.group(1))
if vmnet not in ("vmnet1", "vmnet8"):
if vmnet not in ("vmnet0", "vmnet1", "vmnet8"):
vmnet_interfaces.append(vmnet)
except OSError as e:
raise VMwareError("Cannot open {}: {}".format(vmware_networking_file, e))
@@ -297,11 +297,11 @@ class VMware(BaseManager):
match = re.search("(VMnet[0-9]+)", windows_name)
if match:
vmnet = match.group(1)
if vmnet not in ("VMnet1", "VMnet8"):
if vmnet not in ("VMnet0", "VMnet1", "VMnet8"):
vmnet_interfaces.append(vmnet)
elif interface["name"].startswith("vmnet"):
vmnet = interface["name"]
if vmnet not in ("vmnet1", "vmnet8"):
if vmnet not in ("vmnet0", "vmnet1", "vmnet8"):
vmnet_interfaces.append(interface["name"])
return vmnet_interfaces
@@ -396,12 +396,12 @@ class VMware(BaseManager):
try:
stdout_data, _ = yield from asyncio.wait_for(process.communicate(), timeout=timeout)
except asyncio.TimeoutError:
raise VMwareError("vmrun has timed out after {} seconds!\nTry to run {} in a terminal to see more informations.\n\nMake sure GNS3 and VMware run under the same user and whitelist vmrun.exe in your antivirus.".format(timeout, command_string))
raise VMwareError("vmrun has timed out after {} seconds!\nTry to run {} in a terminal to see more details.\n\nMake sure GNS3 and VMware run under the same user and whitelist vmrun.exe in your antivirus.".format(timeout, command_string))
if process.returncode:
# vmrun print errors on stdout
vmrun_error = stdout_data.decode("utf-8", errors="ignore")
raise VMwareError("vmrun has returned an error: {}\nTry to run {} in a terminal to see more informations.\nAnd make sure GNS3 and VMware run under the same user.".format(vmrun_error, command_string))
raise VMwareError("vmrun has returned an error: {}\nTry to run {} in a terminal to see more details.\nAnd make sure GNS3 and VMware run under the same user.".format(vmrun_error, command_string))
return stdout_data.decode("utf-8", errors="ignore").splitlines()
+15 -11
View File
@@ -24,7 +24,6 @@ import os
import asyncio
import tempfile
from gns3server.utils.interfaces import interfaces
from gns3server.utils.asyncio.telnet_server import AsyncioTelnetServer
from gns3server.utils.asyncio.serial import asyncio_open_serial
from gns3server.utils.asyncio import locked_coroutine
@@ -277,6 +276,7 @@ class VMwareVM(BaseNode):
continue
self._vmx_pairs["ethernet{}.connectiontype".format(adapter_number)] = "custom"
# make sure we have a vmnet per adapter if we use uBridge
allocate_vmnet = False
@@ -285,7 +285,7 @@ class VMwareVM(BaseNode):
if vnet in self._vmx_pairs:
vmnet = os.path.basename(self._vmx_pairs[vnet])
if self.manager.is_managed_vmnet(vmnet) or vmnet in ("vmnet0", "vmnet1", "vmnet8"):
# vmnet already managed, try to allocate a new one
# vmnet already managed or a special vmnet, try to allocate a new one
allocate_vmnet = True
else:
# otherwise allocate a new one
@@ -299,7 +299,7 @@ class VMwareVM(BaseNode):
self._vmnets.clear()
raise
# mark the vmnet managed by us
# mark the vmnet as managed by us
if vmnet not in self._vmnets:
self._vmnets.append(vmnet)
self._vmx_pairs["ethernet{}.vnet".format(adapter_number)] = vmnet
@@ -739,17 +739,18 @@ class VMwareVM(BaseNode):
if self._get_vmx_setting("ethernet{}.present".format(adapter_number), "TRUE"):
# check for the connection type
connection_type = "ethernet{}.connectiontype".format(adapter_number)
if connection_type in self._vmx_pairs and self._vmx_pairs[connection_type] in ("nat", "bridged", "hostonly"):
if not self._use_any_adapter:
raise VMwareError("Attachment '{attachment}' is already configured on network adapter {adapter_number}. "
"Please remove it or allow VMware VM '{name}' to use any adapter.".format(attachment=self._vmx_pairs[connection_type],
adapter_number=adapter_number,
name=self.name))
elif self.is_running():
if not self._use_any_adapter and connection_type in self._vmx_pairs and self._vmx_pairs[connection_type] in ("nat", "bridged", "hostonly"):
if (yield from self.is_running()):
raise VMwareError("Attachment '{attachment}' is configured on network adapter {adapter_number}. "
"Please stop VMware VM '{name}' to link to this adapter and allow GNS3 to change the attachment type.".format(attachment=self._vmx_pairs[connection_type],
adapter_number=adapter_number,
name=self.name))
else:
raise VMwareError("Attachment '{attachment}' is already configured on network adapter {adapter_number}. "
"Please remove it or allow VMware VM '{name}' to use any adapter.".format(attachment=self._vmx_pairs[connection_type],
adapter_number=adapter_number,
name=self.name))
adapter.add_nio(0, nio)
if self._started and self._ubridge_hypervisor:
@@ -848,7 +849,10 @@ class VMwareVM(BaseNode):
writer=self._remote_pipe,
binary=True,
echo=True)
self._telnet_server = yield from asyncio.start_server(server.run, self._manager.port_manager.console_host, self.console)
try:
self._telnet_server = yield from asyncio.start_server(server.run, self._manager.port_manager.console_host, self.console)
except OSError as e:
self.project.emit("log.warning", {"message": "Could not start Telnet server on socket {}:{}: {}".format(self._manager.port_manager.console_host, self.console, e)})
@asyncio.coroutine
def _stop_remote_console(self):
+4 -1
View File
@@ -313,7 +313,10 @@ class VPCSVM(BaseNode):
log.info("Stopping VPCS instance {} PID={}".format(self.name, self._process.pid))
if sys.platform.startswith("win32"):
self._process.send_signal(signal.CTRL_BREAK_EVENT)
try:
self._process.send_signal(signal.CTRL_BREAK_EVENT)
except OSError:
pass
else:
try:
self._process.terminate()
+28 -6
View File
@@ -82,7 +82,8 @@ class Controller:
if appliance.status != 'broken':
self._appliance_templates[appliance.id] = appliance
except (ValueError, OSError, KeyError) as e:
log.warning("Can't load %s: %s", path, str(e))
log.warning("Cannot load appliance template file '%s': %s", path, str(e))
continue
self._appliances = {}
vms = []
@@ -116,19 +117,36 @@ class Controller:
for vm in self._settings.get("VPCS", {}).get("nodes", []):
vm["node_type"] = "vpcs"
vms.append(vm)
for vm in self._settings.get("TraceNG", {}).get("nodes", []):
vm["node_type"] = "traceng"
vms.append(vm)
for vm in vms:
# remove deprecated properties
for prop in vm.copy():
if prop in ["enable_remote_console", "use_ubridge"]:
del vm[prop]
# remove deprecated default_symbol and hover_symbol
# and set symbol if not present
deprecated = ["default_symbol", "hover_symbol"]
if len([prop for prop in vm.keys() if prop in deprecated]) > 0:
if "default_symbol" in vm.keys():
del vm["default_symbol"]
if "hover_symbol" in vm.keys():
del vm["hover_symbol"]
if "symbol" not in vm.keys():
vm["symbol"] = ":/symbols/computer.svg"
vm.setdefault("appliance_id", str(uuid.uuid4()))
try:
appliance = Appliance(vm["appliance_id"], vm)
appliance.__json__() # Check if loaded without error
self._appliances[appliance.id] = appliance
except KeyError as e:
# appliance data is not complete (missing name or type)
log.warning("Could not load appliance template {} ('{}'): {}".format(vm["appliance_id"], vm.get("name", "unknown"), e))
log.warning("Cannot load appliance template {} ('{}'): missing key {}".format(vm["appliance_id"], vm.get("name", "unknown"), e))
continue
# Add builtins
@@ -140,6 +158,10 @@ class Controller:
builtins.append(Appliance(uuid.uuid3(uuid.NAMESPACE_DNS, "ethernet_hub"), {"node_type": "ethernet_hub", "name": "Ethernet hub", "category": 1, "symbol": ":/symbols/hub.svg"}, builtin=True))
builtins.append(Appliance(uuid.uuid3(uuid.NAMESPACE_DNS, "frame_relay_switch"), {"node_type": "frame_relay_switch", "name": "Frame Relay switch", "category": 1, "symbol": ":/symbols/frame_relay_switch.svg"}, builtin=True))
builtins.append(Appliance(uuid.uuid3(uuid.NAMESPACE_DNS, "atm_switch"), {"node_type": "atm_switch", "name": "ATM switch", "category": 1, "symbol": ":/symbols/atm_switch.svg"}, builtin=True))
#FIXME: disable TraceNG
#if sys.platform.startswith("win"):
# builtins.append(Appliance(uuid.uuid3(uuid.NAMESPACE_DNS, "traceng"), {"node_type": "traceng", "name": "TraceNG", "default_name_format": "TraceNG-{0}", "category": 2, "symbol": ":/symbols/traceng.svg", "properties": {}}, builtin=True))
for b in builtins:
self._appliances[b.id] = b
@@ -172,13 +194,13 @@ class Controller:
user=server_config.get("user", ""),
password=server_config.get("password", ""),
force=True)
except aiohttp.web_exceptions.HTTPConflict as e:
except aiohttp.web.HTTPConflict as e:
log.fatal("Can't access to the local server, make sure anything else is not running on the same port")
sys.exit(1)
for c in computes:
try:
yield from self.add_compute(**c)
except (aiohttp.web_exceptions.HTTPConflict, KeyError):
except (aiohttp.web.HTTPConflict, KeyError):
pass # Skip not available servers at loading
yield from self.load_projects()
try:
@@ -206,7 +228,7 @@ class Controller:
try:
yield from compute.close()
# We don't care if a compute is down at this step
except (ComputeError, aiohttp.web_exceptions.HTTPError, OSError):
except (ComputeError, aiohttp.web.HTTPError, OSError):
pass
yield from self.gns3vm.exit_vm()
self._computes = {}
@@ -286,7 +308,7 @@ class Controller:
if file.endswith(".gns3"):
try:
yield from self.load_project(os.path.join(project_dir, file), load=False)
except (aiohttp.web_exceptions.HTTPConflict, NotImplementedError):
except (aiohttp.web.HTTPConflict, NotImplementedError):
pass # Skip not compatible projects
except OSError as e:
log.error(str(e))
+3 -1
View File
@@ -44,7 +44,9 @@ class Appliance:
# Version of the gui before 2.1 use linked_base
# and the server linked_clone
if "linked_base" in self._data:
self._data["linked_clone"] = self._data.pop("linked_base")
linked_base = self._data.pop("linked_base")
if "linked_clone" not in self._data:
self._data["linked_clone"] = linked_base
if data["node_type"] == "iou" and "image" in data:
del self._data["image"]
self._builtin = builtin
-1
View File
@@ -460,7 +460,6 @@ class Compute:
msg = json.loads(response.data)
action = msg.pop("action")
event = msg.pop("event")
if action == "ping":
self._cpu_usage_percent = event["cpu_usage_percent"]
self._memory_usage_percent = event["memory_usage_percent"]
+1 -1
View File
@@ -118,7 +118,7 @@ class Drawing:
file_path = os.path.join(self._project.pictures_directory, filename)
if not os.path.exists(file_path):
with open(file_path, "wb+") as f:
with open(file_path, "wb") as f:
f.write(data)
value = filename
+80 -76
View File
@@ -31,116 +31,126 @@ log = logging.getLogger(__name__)
@asyncio.coroutine
def export_project(project, temporary_dir, include_images=False, keep_compute_id=False, allow_all_nodes=False):
"""
Export the project as zip. It's a ZipStream object.
The file will be read chunk by chunk when you iterate on
the zip.
Export a project to a zip file.
It will ignore some files like snapshots and
The file will be read chunk by chunk when you iterate over the zip stream.
Some files like snapshots and packet captures are ignored.
:param temporary_dir: A temporary dir where to store intermediate data
:param keep_compute_id: If false replace all compute id by local it's the standard behavior for .gns3project to make them portable
:param allow_all_nodes: Allow all nodes type to be include in the zip even if not portable default False
:param include images: save OS images to the zip file
:param keep_compute_id: If false replace all compute id by local (standard behavior for .gns3project to make it portable)
:param allow_all_nodes: Allow all nodes type to be include in the zip even if not portable
:returns: ZipStream object
"""
# To avoid issue with data not saved we disallow the export of a running topologie
# To avoid issue with data not saved we disallow the export of a running project
if project.is_running():
raise aiohttp.web.HTTPConflict(text="Running topology could not be exported")
raise aiohttp.web.HTTPConflict(text="Project must be stopped in order to export it")
# Make sure we save the project
project.dump()
z = zipstream.ZipFile(allowZip64=True)
zstream = zipstream.ZipFile(allowZip64=True)
if not os.path.exists(project._path):
raise aiohttp.web.HTTPNotFound(text="The project doesn't exist at location {}".format(project._path))
raise aiohttp.web.HTTPNotFound(text="Project could not be found at '{}'".format(project._path))
# First we process the .gns3 in order to be sure we don't have an error
for file in os.listdir(project._path):
if file.endswith(".gns3"):
images = yield from _export_project_file(project, os.path.join(project._path, file),
z, include_images, keep_compute_id, allow_all_nodes, temporary_dir)
yield from _patch_project_file(project, os.path.join(project._path, file), zstream, include_images, keep_compute_id, allow_all_nodes, temporary_dir)
# Export the local files
for root, dirs, files in os.walk(project._path, topdown=True):
files = [f for f in files if not _filter_files(os.path.join(root, f))]
files = [f for f in files if _is_exportable(os.path.join(root, f))]
for file in files:
path = os.path.join(root, file)
# Try open the file
# check if we can export the file
try:
open(path).close()
except OSError as e:
msg = "Could not export file {}: {}".format(path, e)
log.warn(msg)
msg = "Could not export file '{}': {}".format(path, e)
log.warning(msg)
project.controller.notification.emit("log.warning", {"message": msg})
continue
# ignore the .gns3 file
if file.endswith(".gns3"):
pass
else:
z.write(path, os.path.relpath(path, project._path), compress_type=zipfile.ZIP_DEFLATED)
continue
zstream.write(path, os.path.relpath(path, project._path), compress_type=zipfile.ZIP_DEFLATED)
# Export files from remote computes
downloaded_files = set()
for compute in project.computes:
if compute.id != "local":
compute_files = yield from compute.list_files(project)
for compute_file in compute_files:
if not _filter_files(compute_file["path"]):
if _is_exportable(compute_file["path"]):
(fd, temp_path) = tempfile.mkstemp(dir=temporary_dir)
f = open(fd, "wb", closefd=True)
response = yield from compute.download_file(project, compute_file["path"])
while True:
data = yield from response.content.read(512)
try:
data = yield from response.content.read(1024)
except asyncio.TimeoutError:
raise aiohttp.web.HTTPRequestTimeout(text="Timeout when downloading file '{}' from remote compute server {}:{}".format(compute_file["path"], compute.host, compute.port))
if not data:
break
f.write(data)
response.close()
f.close()
z.write(temp_path, arcname=compute_file["path"], compress_type=zipfile.ZIP_DEFLATED)
zstream.write(temp_path, arcname=compute_file["path"], compress_type=zipfile.ZIP_DEFLATED)
downloaded_files.add(compute_file['path'])
return z
return zstream
def _filter_files(path):
def _is_exportable(path):
"""
:returns: True if file should not be included in the final archive
"""
s = os.path.normpath(path).split(os.path.sep)
# do not export snapshots
if path.endswith("snapshots"):
return True
return False
# do not export directories of snapshots
if "{sep}snapshots{sep}".format(sep=os.path.sep) in path:
return False
try:
# do not export captures and other temporary directory
s = os.path.normpath(path).split(os.path.sep)
i = s.index("project-files")
if s[i + 1] in ("tmp", "captures", "snapshots"):
return True
return False
except (ValueError, IndexError):
pass
file_name = os.path.basename(path)
# Ignore log files and OS noises
if file_name.endswith('_log.txt') or file_name.endswith('.log') or file_name == '.DS_Store':
return True
return False
# do not export log files and OS noise
filename = os.path.basename(path)
if filename.endswith('_log.txt') or filename.endswith('.log') or filename == '.DS_Store':
return False
return True
@asyncio.coroutine
def _export_project_file(project, path, z, include_images, keep_compute_id, allow_all_nodes, temporary_dir):
def _patch_project_file(project, path, zstream, include_images, keep_compute_id, allow_all_nodes, temporary_dir):
"""
Take a project file (.gns3) and patch it for the export
Patch a project file (.gns3) to export a project.
The .gns3 file is renamed to project.gns3
We rename the .gns3 project.gns3 to avoid the task to the client to guess the file name
:param path: Path of the .gns3
:param path: path of the .gns3 file
"""
# Image file that we need to include in the exported archive
# image files that we need to include in the exported archive
images = []
with open(path) as f:
topology = json.load(f)
try:
with open(path) as f:
topology = json.load(f)
except (OSError, ValueError) as e:
raise aiohttp.web.HTTPConflict(text="Project file '{}' cannot be read: {}".format(path, e))
if "topology" in topology:
if "nodes" in topology["topology"]:
@@ -148,18 +158,21 @@ def _export_project_file(project, path, z, include_images, keep_compute_id, allo
compute_id = node.get('compute_id', 'local')
if node["node_type"] == "virtualbox" and node.get("properties", {}).get("linked_clone"):
raise aiohttp.web.HTTPConflict(text="Topology with a linked {} clone could not be exported. Use qemu instead.".format(node["node_type"]))
raise aiohttp.web.HTTPConflict(text="Projects with a linked {} clone node cannot not be exported. Please use Qemu instead.".format(node["node_type"]))
if not allow_all_nodes and node["node_type"] in ["virtualbox", "vmware", "cloud"]:
raise aiohttp.web.HTTPConflict(text="Topology with a {} could not be exported".format(node["node_type"]))
raise aiohttp.web.HTTPConflict(text="Projects with a {} node cannot be exported".format(node["node_type"]))
if not keep_compute_id:
node["compute_id"] = "local" # To make project portable all node by default run on local
if "properties" in node and node["node_type"] != "docker":
for prop, value in node["properties"].items():
if not prop.endswith("image"):
continue
if node["node_type"] == "iou":
if not prop == "path":
continue
elif not prop.endswith("image"):
continue
if value is None or value.strip() == '':
continue
@@ -179,79 +192,71 @@ def _export_project_file(project, path, z, include_images, keep_compute_id, allo
local_images = set([i['image'] for i in images if i['compute_id'] == 'local'])
for image in local_images:
_export_local_images(project, image, z)
_export_local_image(image, zstream)
remote_images = set([
(i['compute_id'], i['image_type'], i['image'])
for i in images if i['compute_id'] != 'local'])
for compute_id, image_type, image in remote_images:
yield from _export_remote_images(project, compute_id, image_type, image, z, temporary_dir)
z.writestr("project.gns3", json.dumps(topology).encode())
yield from _export_remote_images(project, compute_id, image_type, image, zstream, temporary_dir)
zstream.writestr("project.gns3", json.dumps(topology).encode())
return images
def _export_local_images(project, image, z):
def _export_local_image(image, zstream):
"""
Take a project file (.gns3) and export images to the zip
Exports a local image to the zip file.
:param image: Image path
:param z: Zipfile instance for the export
:param image: image path
:param zstream: Zipfile instance for the export
"""
from ..compute import MODULES
for module in MODULES:
try:
img_directory = module.instance().get_images_directory()
images_directory = module.instance().get_images_directory()
except NotImplementedError:
# Some modules don't have images
continue
directory = os.path.split(img_directory)[-1:][0]
directory = os.path.split(images_directory)[-1:][0]
if os.path.exists(image):
path = image
else:
path = os.path.join(img_directory, image)
path = os.path.join(images_directory, image)
if os.path.exists(path):
arcname = os.path.join("images", directory, os.path.basename(image))
z.write(path, arcname)
zstream.write(path, arcname)
return
@asyncio.coroutine
def _export_remote_images(project, compute_id, image_type, image, project_zipfile, temporary_dir):
"""
Export specific image from remote compute
:param project:
:param compute_id:
:param image_type:
:param image:
:param project_zipfile:
:return:
Export specific image from remote compute.
"""
log.info("Obtaining image `{}` from `{}`".format(image, compute_id))
log.info("Downloading image '{}' from compute server '{}'".format(image, compute_id))
try:
compute = [compute for compute in project.computes if compute.id == compute_id][0]
except IndexError:
raise aiohttp.web.HTTPConflict(
text="Cannot export image from `{}` compute. Compute doesn't exist.".format(compute_id))
raise aiohttp.web.HTTPConflict(text="Cannot export image from '{}' compute. Compute doesn't exist.".format(compute_id))
(fd, temp_path) = tempfile.mkstemp(dir=temporary_dir)
f = open(fd, "wb", closefd=True)
response = yield from compute.download_image(image_type, image)
if response.status != 200:
raise aiohttp.web.HTTPConflict(
text="Cannot export image from `{}` compute. Compute sent `{}` status.".format(
compute_id, response.status))
raise aiohttp.web.HTTPConflict(text="Cannot export image from '{}' compute. Compute returned status code {}.".format(compute_id, response.status))
while True:
data = yield from response.content.read(512)
try:
data = yield from response.content.read(1024)
except asyncio.TimeoutError:
raise aiohttp.web.HTTPRequestTimeout(text="Timeout when downloading image '{}' from remote compute server {}:{}".format(image, compute.host, compute.port))
if not data:
break
f.write(data)
@@ -260,4 +265,3 @@ def _export_remote_images(project, compute_id, image_type, image, project_zipfil
arcname = os.path.join("images", image_type, image)
log.info("Saved {}".format(arcname))
project_zipfile.write(temp_path, arcname=arcname, compress_type=zipfile.ZIP_DEFLATED)
@@ -191,6 +191,7 @@ class VirtualBoxGNS3VM(BaseGNS3VM):
try:
# get a random port on localhost
with socket.socket() as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((ip_address, 0))
api_port = s.getsockname()[1]
except OSError as e:
+111 -106
View File
@@ -26,7 +26,7 @@ import aiohttp
import itertools
from .topology import load_topology
from ..utils.asyncio import wait_run_in_executor
"""
Handle the import of project from a .gns3project
@@ -38,7 +38,7 @@ def import_project(controller, project_id, stream, location=None, name=None, kee
"""
Import a project contain in a zip file
You need to handle OSerror exceptions
You need to handle OSError exceptions
:param controller: GNS3 Controller
:param project_id: ID of the project to import
@@ -46,6 +46,7 @@ def import_project(controller, project_id, stream, location=None, name=None, kee
:param location: Directory for the project if None put in the default directory
:param name: Wanted project name, generate one from the .gns3 if None
:param keep_compute_id: If true do not touch the compute id
:returns: Project
"""
@@ -53,115 +54,118 @@ def import_project(controller, project_id, stream, location=None, name=None, kee
raise aiohttp.web.HTTPConflict(text="The destination path should not contain .gns3")
try:
with zipfile.ZipFile(stream) as myzip:
try:
topology = json.loads(myzip.read("project.gns3").decode())
# We import the project on top of an existing project (snapshots)
if topology["project_id"] == project_id:
project_name = topology["name"]
else:
# If the project name is already used we generate a new one
if name:
project_name = controller.get_free_project_name(name)
else:
project_name = controller.get_free_project_name(topology["name"])
except KeyError:
raise aiohttp.web.HTTPConflict(text="Can't import topology the .gns3 is corrupted or missing")
if location:
path = location
else:
projects_path = controller.projects_directory()
path = os.path.join(projects_path, project_id)
try:
os.makedirs(path, exist_ok=True)
except UnicodeEncodeError as e:
raise aiohttp.web.HTTPConflict(text="The project name contain non supported or invalid characters")
myzip.extractall(path)
topology = load_topology(os.path.join(path, "project.gns3"))
topology["name"] = project_name
# To avoid unexpected behavior (project start without manual operations just after import)
topology["auto_start"] = False
topology["auto_open"] = False
topology["auto_close"] = True
# Generate a new node id
node_old_to_new = {}
for node in topology["topology"]["nodes"]:
if "node_id" in node:
node_old_to_new[node["node_id"]] = str(uuid.uuid4())
_move_node_file(path, node["node_id"], node_old_to_new[node["node_id"]])
node["node_id"] = node_old_to_new[node["node_id"]]
else:
node["node_id"] = str(uuid.uuid4())
# Update link to use new id
for link in topology["topology"]["links"]:
link["link_id"] = str(uuid.uuid4())
for node in link["nodes"]:
node["node_id"] = node_old_to_new[node["node_id"]]
# Generate new drawings id
for drawing in topology["topology"]["drawings"]:
drawing["drawing_id"] = str(uuid.uuid4())
# Modify the compute id of the node depending of compute capacity
if not keep_compute_id:
# For some VM type we move them to the GNS3 VM if possible
# unless it's a linux host without GNS3 VM
if not sys.platform.startswith("linux") or controller.has_compute("vm"):
for node in topology["topology"]["nodes"]:
if node["node_type"] in ("docker", "qemu", "iou", "nat"):
node["compute_id"] = "vm"
else:
# Round-robin through available compute resources.
compute_nodes = itertools.cycle(controller.computes)
for node in topology["topology"]["nodes"]:
node["compute_id"] = next(compute_nodes)
compute_created = set()
for node in topology["topology"]["nodes"]:
if node["compute_id"] != "local":
# Project created on the remote GNS3 VM?
if node["compute_id"] not in compute_created:
compute = controller.get_compute(node["compute_id"])
yield from compute.post("/projects", data={
"name": project_name,
"project_id": project_id,
})
compute_created.add(node["compute_id"])
yield from _move_files_to_compute(compute, project_id, path, os.path.join("project-files", node["node_type"], node["node_id"]))
# And we dump the updated.gns3
dot_gns3_path = os.path.join(path, project_name + ".gns3")
# We change the project_id to avoid erasing the project
topology["project_id"] = project_id
with open(dot_gns3_path, "w+") as f:
json.dump(topology, f, indent=4)
os.remove(os.path.join(path, "project.gns3"))
if os.path.exists(os.path.join(path, "images")):
_import_images(controller, path)
project = yield from controller.load_project(dot_gns3_path, load=False)
return project
with zipfile.ZipFile(stream) as zip_file:
project_file = zip_file.read("project.gns3").decode()
except zipfile.BadZipFile:
raise aiohttp.web.HTTPConflict(text="Can't import topology the file is corrupted or not a GNS3 project (invalid zip)")
raise aiohttp.web.HTTPConflict(text="Cannot import project, not a GNS3 project (invalid zip)")
except KeyError:
raise aiohttp.web.HTTPConflict(text="Cannot import project, project.gns3 file could not be found")
try:
topology = json.loads(project_file)
# We import the project on top of an existing project (snapshots)
if topology["project_id"] == project_id:
project_name = topology["name"]
else:
# If the project name is already used we generate a new one
if name:
project_name = controller.get_free_project_name(name)
else:
project_name = controller.get_free_project_name(topology["name"])
except (ValueError, KeyError):
raise aiohttp.web.HTTPConflict(text="Cannot import project, the project.gns3 file is corrupted")
if location:
path = location
else:
projects_path = controller.projects_directory()
path = os.path.join(projects_path, project_id)
try:
os.makedirs(path, exist_ok=True)
except UnicodeEncodeError:
raise aiohttp.web.HTTPConflict(text="The project name contain non supported or invalid characters")
try:
with zipfile.ZipFile(stream) as zip_file:
yield from wait_run_in_executor(zip_file.extractall, path)
except zipfile.BadZipFile:
raise aiohttp.web.HTTPConflict(text="Cannot extract files from GNS3 project (invalid zip)")
topology = load_topology(os.path.join(path, "project.gns3"))
topology["name"] = project_name
# To avoid unexpected behavior (project start without manual operations just after import)
topology["auto_start"] = False
topology["auto_open"] = False
topology["auto_close"] = True
# Generate a new node id
node_old_to_new = {}
for node in topology["topology"]["nodes"]:
if "node_id" in node:
node_old_to_new[node["node_id"]] = str(uuid.uuid4())
_move_node_file(path, node["node_id"], node_old_to_new[node["node_id"]])
node["node_id"] = node_old_to_new[node["node_id"]]
else:
node["node_id"] = str(uuid.uuid4())
# Update link to use new id
for link in topology["topology"]["links"]:
link["link_id"] = str(uuid.uuid4())
for node in link["nodes"]:
node["node_id"] = node_old_to_new[node["node_id"]]
# Generate new drawings id
for drawing in topology["topology"]["drawings"]:
drawing["drawing_id"] = str(uuid.uuid4())
# Modify the compute id of the node depending of compute capacity
if not keep_compute_id:
# For some VM type we move them to the GNS3 VM if possible
# unless it's a linux host without GNS3 VM
if not sys.platform.startswith("linux") or controller.has_compute("vm"):
for node in topology["topology"]["nodes"]:
if node["node_type"] in ("docker", "qemu", "iou", "nat"):
node["compute_id"] = "vm"
else:
# Round-robin through available compute resources.
compute_nodes = itertools.cycle(controller.computes)
for node in topology["topology"]["nodes"]:
node["compute_id"] = next(compute_nodes)
compute_created = set()
for node in topology["topology"]["nodes"]:
if node["compute_id"] != "local":
# Project created on the remote GNS3 VM?
if node["compute_id"] not in compute_created:
compute = controller.get_compute(node["compute_id"])
yield from compute.post("/projects", data={"name": project_name, "project_id": project_id,})
compute_created.add(node["compute_id"])
yield from _move_files_to_compute(compute, project_id, path, os.path.join("project-files", node["node_type"], node["node_id"]))
# And we dump the updated.gns3
dot_gns3_path = os.path.join(path, project_name + ".gns3")
# We change the project_id to avoid erasing the project
topology["project_id"] = project_id
with open(dot_gns3_path, "w+") as f:
json.dump(topology, f, indent=4)
os.remove(os.path.join(path, "project.gns3"))
if os.path.exists(os.path.join(path, "images")):
_import_images(controller, path)
project = yield from controller.load_project(dot_gns3_path, load=False)
return project
def _move_node_file(path, old_id, new_id):
"""
Move the files from a node when changing his id
Move a file from a node when changing its id
:param path: Path of the project
:param old_id: ID before change
:param new_id: New node UUID
"""
root = os.path.join(path, "project-files")
if os.path.exists(root):
for dirname in os.listdir(root):
@@ -175,8 +179,9 @@ def _move_node_file(path, old_id, new_id):
@asyncio.coroutine
def _move_files_to_compute(compute, project_id, directory, files_path):
"""
Move the files to a remote compute
Move files to a remote compute
"""
location = os.path.join(directory, files_path)
if os.path.exists(location):
for (dirpath, dirnames, filenames) in os.walk(location):
@@ -184,7 +189,7 @@ def _move_files_to_compute(compute, project_id, directory, files_path):
path = os.path.join(dirpath, filename)
dst = os.path.relpath(path, directory)
yield from _upload_file(compute, project_id, path, dst)
shutil.rmtree(os.path.join(directory, files_path))
yield from wait_run_in_executor(shutil.rmtree, os.path.join(directory, files_path))
@asyncio.coroutine
@@ -195,6 +200,7 @@ def _upload_file(compute, project_id, file_path, path):
:param file_path: File path on the controller file system
:param path: File path on the remote system relative to project directory
"""
path = "/projects/{}/files/{}".format(project_id, path.replace("\\", "/"))
with open(file_path, "rb") as f:
yield from compute.http_query("POST", path, f, timeout=None)
@@ -202,11 +208,10 @@ def _upload_file(compute, project_id, file_path, path):
def _import_images(controller, path):
"""
Copy images to the images directory or delete them if they
already exists.
Copy images to the images directory or delete them if they already exists.
"""
image_dir = controller.images_path()
image_dir = controller.images_path()
root = os.path.join(path, "images")
for (dirpath, dirnames, filenames) in os.walk(root):
for filename in filenames:
+28 -10
View File
@@ -171,12 +171,16 @@ class Link:
self._filters = new_filters
if self._created:
yield from self.update()
self._project.controller.notification.emit("link.updated", self.__json__())
self._project.dump()
@asyncio.coroutine
def update_suspend(self, value):
if value != self._suspend:
self._suspend = value
yield from self.update()
self._project.controller.notification.emit("link.updated", self.__json__())
self._project.dump()
@property
def created(self):
@@ -194,6 +198,8 @@ class Link:
"""
port = node.get_port(adapter_number, port_number)
if port is None:
raise aiohttp.web.HTTPNotFound(text="Port {}/{} for {} not found".format(adapter_number, port_number, node.name))
if port.link is not None:
raise aiohttp.web.HTTPConflict(text="Port is already used")
@@ -209,6 +215,8 @@ class Link:
# Check if user is not connecting serial => ethernet
other_port = other_node["node"].get_port(other_node["adapter_number"], other_node["port_number"])
if other_port is None:
raise aiohttp.web.HTTPNotFound(text="Port {}/{} for {} not found".format(other_node["adapter_number"], other_node["port_number"], other_node["node"].name))
if port.link_type != other_port.link_type:
raise aiohttp.web.HTTPConflict(text="It's not allowed to connect a {} to a {}".format(other_port.link_type, port.link_type))
@@ -297,6 +305,12 @@ class Link:
Dump a pcap file on disk
"""
if os.path.exists(self.capture_file_path):
try:
os.remove(self.capture_file_path)
except OSError as e:
raise aiohttp.web.HTTPConflict(text="Could not delete old capture file '{}': {}".format(self.capture_file_path, e))
try:
stream_content = yield from self.read_pcap_from_source()
except aiohttp.web.HTTPException as e:
@@ -307,16 +321,19 @@ class Link:
self._project.controller.notification.emit("link.updated", self.__json__())
with stream_content as stream:
with open(self.capture_file_path, "wb+") as f:
while self._capturing:
# We read 1 bytes by 1 otherwise the remaining data is not read if the traffic stops
data = yield from stream.read(1)
if data:
f.write(data)
# Flush to disk otherwise the live is not really live
f.flush()
else:
break
try:
with open(self.capture_file_path, "wb") as f:
while self._capturing:
# We read 1 bytes by 1 otherwise the remaining data is not read if the traffic stops
data = yield from stream.read(1)
if data:
f.write(data)
# Flush to disk otherwise the live is not really live
f.flush()
else:
break
except OSError as e:
raise aiohttp.web.HTTPConflict(text="Could not write capture file '{}': {}".format(self.capture_file_path, e))
@asyncio.coroutine
def stop_capture(self):
@@ -397,6 +414,7 @@ class Link:
"""
for node in self._nodes:
if node["node"].node_type in ('vpcs',
'traceng',
'vmware',
'dynamips',
'qemu',
+6 -6
View File
@@ -121,7 +121,7 @@ class Node:
return self.node_type not in (
"qemu", "docker", "dynamips",
"vpcs", "vmware", "virtualbox",
"iou")
"iou", "traceng")
@property
def id(self):
@@ -454,7 +454,7 @@ class Node:
yield from self.delete()
@asyncio.coroutine
def start(self):
def start(self, data=None):
"""
Start a node
"""
@@ -467,7 +467,7 @@ class Node:
raise aiohttp.web.HTTPConflict(text="IOU licence is not configured")
yield from self.post("/start", timeout=240, data={"iourc_content": licence})
else:
yield from self.post("/start", timeout=240)
yield from self.post("/start", data=data, timeout=240)
except asyncio.TimeoutError:
raise aiohttp.web.HTTPRequestTimeout(text="Timeout when starting {}".format(self._name))
@@ -574,12 +574,12 @@ class Node:
def get_port(self, adapter_number, port_number):
"""
Return the port for this adapter_number and port_number
or raise an HTTPNotFound
or returns None if the port is not found
"""
for port in self.ports:
if port.adapter_number == adapter_number and port.port_number == port_number:
return port
raise aiohttp.web.HTTPNotFound(text="Port {}/{} for {} not found".format(adapter_number, port_number, self.name))
return None
def _list_ports(self):
"""
@@ -622,7 +622,7 @@ class Node:
for port in self._properties["ports_mapping"]:
self._ports.append(PortFactory(port["name"], 0, 0, port_number, "ethernet", short_name="e{}".format(port_number)))
port_number += 1
elif self._node_type in ("vpcs"):
elif self._node_type in ("vpcs", "traceng"):
self._ports.append(PortFactory("Ethernet0", 0, 0, 0, "ethernet", short_name="e0"))
elif self._node_type in ("cloud", "nat"):
port_number = 0
+115 -53
View File
@@ -37,9 +37,9 @@ from ..config import Config
from ..utils.path import check_path_allowed, get_default_project_directory
from ..utils.asyncio.pool import Pool
from ..utils.asyncio import locked_coroutine
from ..utils.asyncio import wait_run_in_executor
from .export_project import export_project
from .import_project import import_project
from ..compute.iou.utils.application_id import get_next_application_id
import logging
log = logging.getLogger(__name__)
@@ -69,7 +69,7 @@ class Project:
def __init__(self, name=None, project_id=None, path=None, controller=None, status="opened",
filename=None, auto_start=False, auto_open=False, auto_close=True,
scene_height=1000, scene_width=2000, zoom=100, show_layers=False, snap_to_grid=False, show_grid=False,
show_interface_labels=False):
grid_size=0, show_interface_labels=False, variables=None, supplier=None):
self._controller = controller
assert name is not None
@@ -84,9 +84,12 @@ class Project:
self._show_layers = show_layers
self._snap_to_grid = snap_to_grid
self._show_grid = show_grid
self._grid_size = grid_size
self._show_interface_labels = show_interface_labels
self._variables = variables
self._supplier = supplier
self._loading = False
self._add_node_lock = asyncio.Lock()
# Disallow overwrite of existing project
if project_id is None and path is not None:
@@ -134,6 +137,15 @@ class Project:
self.controller.notification.emit("project.updated", self.__json__())
self.dump()
# update on computes
for compute in list(self._project_created_on_compute):
yield from compute.put(
"/projects/{}".format(self._id), {
"variables": self.variables
}
)
def reset(self):
"""
Called when open/close a project. Cleanup internal stuff
@@ -237,6 +249,21 @@ class Project:
"""
self._show_grid = show_grid
@property
def grid_size(self):
"""
Grid size
:return: integer
"""
return self._grid_size
@grid_size.setter
def grid_size(self, grid_size):
"""
Setter for grid size
"""
self._grid_size = grid_size
@property
def show_interface_labels(self):
"""
@@ -252,6 +279,36 @@ class Project:
"""
self._show_interface_labels = show_interface_labels
@property
def variables(self):
"""
Variables applied to the project
:return: list
"""
return self._variables
@variables.setter
def variables(self, variables):
"""
Setter for variables applied to the project
"""
self._variables = variables
@property
def supplier(self):
"""
Supplier of the project
:return: dict
"""
return self._supplier
@supplier.setter
def supplier(self, supplier):
"""
Setter for supplier of the project
"""
self._supplier = supplier
@property
def auto_start(self):
"""
@@ -439,34 +496,29 @@ class Project:
if node_id in self._nodes:
return self._nodes[node_id]
with (yield from self._add_node_lock):
# wait for a node to be completely created before adding a new one
# this is important otherwise we allocate the same application ID
# when creating multiple IOU node at the same time
if node_type == "iou" and 'application_id' not in kwargs.keys():
kwargs['application_id'] = get_next_application_id(self._nodes.values())
node = Node(self, compute, name, node_id=node_id, node_type=node_type, **kwargs)
if compute not in self._project_created_on_compute:
# For a local server we send the project path
if compute.id == "local":
yield from compute.post("/projects", data={
"name": self._name,
"project_id": self._id,
"path": self._path,
"variables": self._variables
})
else:
yield from compute.post("/projects", data={
"name": self._name,
"project_id": self._id,
"variables": self._variables
})
node = Node(self, compute, name, node_id=node_id, node_type=node_type, **kwargs)
if compute not in self._project_created_on_compute:
# For a local server we send the project path
if compute.id == "local":
yield from compute.post("/projects", data={
"name": self._name,
"project_id": self._id,
"path": self._path
})
else:
yield from compute.post("/projects", data={
"name": self._name,
"project_id": self._id,
})
self._project_created_on_compute.add(compute)
yield from node.create()
self._nodes[node.id] = node
self.controller.notification.emit("node.created", node.__json__())
if dump:
self.dump()
self._project_created_on_compute.add(compute)
yield from node.create()
self._nodes[node.id] = node
self.controller.notification.emit("node.created", node.__json__())
if dump:
self.dump()
return node
@locked_coroutine
@@ -624,24 +676,10 @@ class Project:
:param name: Name of the snapshot
"""
if name in [snap.name for snap in self.snapshots.values()]:
raise aiohttp.web_exceptions.HTTPConflict(text="The snapshot {} already exist".format(name))
if name in [snap.name for snap in self._snapshots.values()]:
raise aiohttp.web.HTTPConflict(text="The snapshot name {} already exists".format(name))
snapshot = Snapshot(self, name=name)
try:
if os.path.exists(snapshot.path):
raise aiohttp.web_exceptions.HTTPConflict(text="The snapshot {} already exist".format(name))
os.makedirs(os.path.join(self.path, "snapshots"), exist_ok=True)
with tempfile.TemporaryDirectory() as tmpdir:
zipstream = yield from export_project(self, tmpdir, keep_compute_id=True, allow_all_nodes=True)
with open(snapshot.path, "wb+") as f:
for data in zipstream:
f.write(data)
except OSError as e:
raise aiohttp.web.HTTPInternalServerError(text="Could not create project directory: {}".format(e))
yield from snapshot.create()
self._snapshots[snapshot.id] = snapshot
return snapshot
@@ -682,6 +720,15 @@ class Project:
except KeyError:
pass
# don't remove supplier's logo
if self.supplier:
try:
logo = self.supplier['logo']
pictures.remove(logo)
except KeyError:
pass
for pict in pictures:
os.remove(os.path.join(self.pictures_directory, pict))
except OSError as e:
@@ -766,6 +813,7 @@ class Project:
"show_layers",
"snap_to_grid",
"show_grid",
"grid_size",
"show_interface_labels"
]
@@ -792,8 +840,11 @@ class Project:
for node_link in link_data["nodes"]:
node = self.get_node(node_link["node_id"])
port = node.get_port(node_link["adapter_number"], node_link["port_number"])
if port is None:
log.warning("Port {}/{} for {} not found".format(node_link["adapter_number"], node_link["port_number"], node.name))
continue
if port.link is not None:
# the node port is already attached to another link
log.warning("Port {}/{} is already connected to link ID {}".format(node_link["adapter_number"], node_link["port_number"], port.link.id))
continue
yield from link.add_node(node, node_link["adapter_number"], node_link["port_number"], label=node_link.get("label"), dump=False)
if len(link.nodes) != 2:
@@ -843,6 +894,15 @@ class Project:
while self._loading:
yield from asyncio.sleep(0.5)
def _create_duplicate_project_file(self, path, zipstream):
"""
Creates the project file (to be run in its own thread)
"""
with open(path, "wb") as f:
for data in zipstream:
f.write(data)
@asyncio.coroutine
def duplicate(self, name=None, location=None):
"""
@@ -864,10 +924,9 @@ class Project:
try:
with tempfile.TemporaryDirectory() as tmpdir:
zipstream = yield from export_project(self, tmpdir, keep_compute_id=True, allow_all_nodes=True)
with open(os.path.join(tmpdir, "project.gns3p"), "wb+") as f:
for data in zipstream:
f.write(data)
with open(os.path.join(tmpdir, "project.gns3p"), "rb") as f:
project_path = os.path.join(tmpdir, "project.gns3p")
yield from wait_run_in_executor(self._create_duplicate_project_file, project_path, zipstream)
with open(project_path, "rb") as f:
project = yield from import_project(self._controller, str(uuid.uuid4()), f, location=location, name=name, keep_compute_id=True)
except (OSError, UnicodeEncodeError) as e:
raise aiohttp.web.HTTPConflict(text="Can not duplicate project: {}".format(str(e)))
@@ -997,7 +1056,10 @@ class Project:
"show_layers": self._show_layers,
"snap_to_grid": self._snap_to_grid,
"show_grid": self._show_grid,
"show_interface_labels": self._show_interface_labels
"grid_size": self._grid_size,
"show_interface_labels": self._show_interface_labels,
"supplier": self._supplier,
"variables": self._variables
}
def __repr__(self):
+42 -6
View File
@@ -19,11 +19,13 @@
import os
import uuid
import shutil
import tempfile
import asyncio
import aiohttp.web
from datetime import datetime, timezone
from ..utils.asyncio import wait_run_in_executor
from .export_project import export_project
from .import_project import import_project
@@ -71,6 +73,37 @@ class Snapshot:
def created_at(self):
return int(self._created_at)
def _create_snapshot_file(self, zipstream):
"""
Creates the snapshot file (to be run in its own thread)
"""
with open(self.path, "wb") as f:
for data in zipstream:
f.write(data)
@asyncio.coroutine
def create(self):
"""
Create the snapshot
"""
if os.path.exists(self.path):
raise aiohttp.web.HTTPConflict(text="The snapshot file '{}' already exists".format(self.name))
snapshot_directory = os.path.join(self._project.path, "snapshots")
try:
os.makedirs(snapshot_directory, exist_ok=True)
except OSError as e:
raise aiohttp.web.HTTPInternalServerError(text="Could not create the snapshot directory '{}': {}".format(snapshot_directory, e))
try:
with tempfile.TemporaryDirectory() as tmpdir:
zipstream = yield from export_project(self._project, tmpdir, keep_compute_id=True, allow_all_nodes=True)
yield from wait_run_in_executor(self._create_snapshot_file, zipstream)
except OSError as e:
raise aiohttp.web.HTTPConflict(text="Could not create snapshot file '{}': {}".format(self.path, e))
@asyncio.coroutine
def restore(self):
"""
@@ -78,18 +111,21 @@ class Snapshot:
"""
yield from self._project.delete_on_computes()
# We don't send close notif to clients because the close / open dance is purely internal
# We don't send close notification to clients because the close / open dance is purely internal
yield from self._project.close(ignore_notification=True)
self._project.controller.notification.emit("snapshot.restored", self.__json__())
try:
if os.path.exists(os.path.join(self._project.path, "project-files")):
shutil.rmtree(os.path.join(self._project.path, "project-files"))
# delete the current project files
project_files_path = os.path.join(self._project.path, "project-files")
if os.path.exists(project_files_path):
yield from wait_run_in_executor(shutil.rmtree, project_files_path)
with open(self._path, "rb") as f:
project = yield from import_project(self._project.controller, self._project.id, f, location=self._project.path)
except (OSError, PermissionError) as e:
raise aiohttp.web.HTTPConflict(text=str(e))
yield from project.open()
return project
self._project.controller.notification.emit("snapshot.restored", self.__json__())
return self._project
def __json__(self):
return {
+3
View File
@@ -83,7 +83,10 @@ def project_to_topology(project):
"show_layers": project.show_layers,
"snap_to_grid": project.snap_to_grid,
"show_grid": project.show_grid,
"grid_size": project.grid_size,
"show_interface_labels": project.show_interface_labels,
"variables": project.variables,
"supplier": project.supplier,
"topology": {
"nodes": [],
"links": [],
+1 -1
View File
@@ -57,7 +57,7 @@ class CrashReport:
Report crash to a third party service
"""
DSN = "sync+https://9bd029d7f92b48178b01868465532d6e:9f4a6a513bd1452fbfd1771ae2ca8b66@sentry.io/38482"
DSN = "sync+https://b0dcf456eaa24e97b46d789ecf927d41:f2541a589b1b492fb79f8bee5b9d799d@sentry.io/38482"
if hasattr(sys, "frozen"):
cacert = get_resource("cacert.pem")
if cacert is not None and os.path.isfile(cacert):
+2 -1
View File
@@ -34,9 +34,10 @@ from .ethernet_hub_handler import EthernetHubHandler
from .ethernet_switch_handler import EthernetSwitchHandler
from .frame_relay_switch_handler import FrameRelaySwitchHandler
from .atm_switch_handler import ATMSwitchHandler
from .traceng_handler import TraceNGHandler
if sys.platform.startswith("linux") or hasattr(sys, "_called_from_test") or os.environ.get("PYTEST_BUILD_DOCUMENTATION") == "1":
# IOU runs only on Linux but test suite works on UNIX platform
# IOU & Docker only runs on Linux but test suite works on UNIX platform
if not sys.platform.startswith("win"):
from .iou_handler import IOUHandler
from .docker_handler import DockerHandler
@@ -60,7 +60,8 @@ class DockerHandler:
console_resolution=request.json.get("console_resolution", "1024x768"),
console_http_port=request.json.get("console_http_port", 80),
console_http_path=request.json.get("console_http_path", "/"),
aux=request.json.get("aux"))
aux=request.json.get("aux"),
extra_hosts=request.json.get("extra_hosts"))
for name, value in request.json.items():
if name != "node_id":
if hasattr(container, name) and getattr(container, name) != value:
@@ -312,7 +313,7 @@ class DockerHandler:
props = [
"name", "console", "aux", "console_type", "console_resolution",
"console_http_port", "console_http_path", "start_command",
"environment", "adapters"
"environment", "adapters", "extra_hosts"
]
changed = False
@@ -65,6 +65,8 @@ class IOUHandler:
for name, value in request.json.items():
if hasattr(vm, name) and getattr(vm, name) != value:
if name == "application_id":
continue # we must ignore this to avoid overwriting the application_id allocated by the IOU manager
if name == "startup_config_content" and (vm.startup_config_content and len(vm.startup_config_content) > 0):
continue
if name == "private_config_content" and (vm.private_config_content and len(vm.private_config_content) > 0):
@@ -116,6 +118,8 @@ class IOUHandler:
for name, value in request.json.items():
if hasattr(vm, name) and getattr(vm, name) != value:
if name == "application_id":
continue # we must ignore this to avoid overwriting the application_id allocated by the IOU manager
setattr(vm, name, value)
if vm.use_default_iou_values:
@@ -19,12 +19,12 @@ import aiohttp
import asyncio
import json
import os
import psutil
import tempfile
from gns3server.web.route import Route
from gns3server.compute.project_manager import ProjectManager
from gns3server.compute import MODULES
from gns3server.utils.ping_stats import PingStats
from gns3server.schemas.project import (
PROJECT_OBJECT_SCHEMA,
@@ -73,11 +73,31 @@ class ProjectHandler:
p = pm.create_project(
name=request.json.get("name"),
path=request.json.get("path"),
project_id=request.json.get("project_id")
project_id=request.json.get("project_id"),
variables=request.json.get("variables", None)
)
response.set_status(201)
response.json(p)
@Route.put(
r"/projects/{project_id}",
description="Update the project on the server",
status_codes={
201: "Project updated",
403: "Forbidden to update a project"
},
output=PROJECT_OBJECT_SCHEMA,
input=PROJECT_UPDATE_SCHEMA)
def update_project(request, response):
pm = ProjectManager.instance()
project = pm.get_project(request.match_info["project_id"])
yield from project.update(
variables=request.json.get("variables", None)
)
response.set_status(200)
response.json(project)
@Route.get(
r"/projects/{project_id}",
description="Get project information",
@@ -186,11 +206,7 @@ class ProjectHandler:
: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}
return {"action": "ping", "event": PingStats.get()}
@Route.get(
r"/projects/{project_id}/files",
@@ -323,10 +339,13 @@ class ProjectHandler:
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, 'wb+') as f:
while True:
packet = yield from request.content.read(512)
if not packet:
try:
chunk = yield from request.content.read(1024)
except asyncio.TimeoutError:
raise aiohttp.web.HTTPRequestTimeout(text="Timeout when writing to file '{}'".format(path))
if not chunk:
break
f.write(packet)
f.write(chunk)
except FileNotFoundError:
raise aiohttp.web.HTTPNotFound()
@@ -384,10 +403,10 @@ class ProjectHandler:
try:
with tempfile.SpooledTemporaryFile(max_size=10000) as temp:
while True:
packet = yield from request.content.read(512)
if not packet:
chunk = yield from request.content.read(1024)
if not chunk:
break
temp.write(packet)
temp.write(chunk)
project.import_zip(temp, gns3vm=bool(int(request.GET.get("gns3vm", "1"))))
except OSError as e:
raise aiohttp.web.HTTPInternalServerError(text="Could not import the project: {}".format(e))
@@ -0,0 +1,341 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2018 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/>.
import os
from aiohttp.web import HTTPConflict
from gns3server.web.route import Route
from gns3server.schemas.nio import NIO_SCHEMA
from gns3server.schemas.node import NODE_CAPTURE_SCHEMA
from gns3server.compute.traceng import TraceNG
from gns3server.schemas.traceng import (
TRACENG_CREATE_SCHEMA,
TRACENG_UPDATE_SCHEMA,
TRACENG_START_SCHEMA,
TRACENG_OBJECT_SCHEMA
)
class TraceNGHandler:
"""
API entry points for TraceNG.
"""
@Route.post(
r"/projects/{project_id}/traceng/nodes",
parameters={
"project_id": "Project UUID"
},
status_codes={
201: "Instance created",
400: "Invalid request",
409: "Conflict"
},
description="Create a new TraceNG instance",
input=TRACENG_CREATE_SCHEMA,
output=TRACENG_OBJECT_SCHEMA)
def create(request, response):
traceng = TraceNG.instance()
vm = yield from traceng.create_node(request.json["name"],
request.match_info["project_id"],
request.json.get("node_id"),
console=request.json.get("console"))
vm.ip_address = request.json.get("ip_address", "")
vm.default_destination = request.json.get("default_destination", "")
response.set_status(201)
response.json(vm)
@Route.get(
r"/projects/{project_id}/traceng/nodes/{node_id}",
parameters={
"project_id": "Project UUID",
"node_id": "Node UUID"
},
status_codes={
200: "Success",
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Get a TraceNG instance",
output=TRACENG_OBJECT_SCHEMA)
def show(request, response):
traceng_manager = TraceNG.instance()
vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
response.json(vm)
@Route.put(
r"/projects/{project_id}/traceng/nodes/{node_id}",
parameters={
"project_id": "Project UUID",
"node_id": "Node UUID"
},
status_codes={
200: "Instance updated",
400: "Invalid request",
404: "Instance doesn't exist",
409: "Conflict"
},
description="Update a TraceNG instance",
input=TRACENG_UPDATE_SCHEMA,
output=TRACENG_OBJECT_SCHEMA)
def update(request, response):
traceng_manager = TraceNG.instance()
vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
vm.name = request.json.get("name", vm.name)
vm.ip_address = request.json.get("ip_address", vm.ip_address)
vm.default_destination = request.json.get("default_destination", vm.default_destination)
vm.updated()
response.json(vm)
@Route.delete(
r"/projects/{project_id}/traceng/nodes/{node_id}",
parameters={
"project_id": "Project UUID",
"node_id": "Node UUID"
},
status_codes={
204: "Instance deleted",
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Delete a TraceNG instance")
def delete(request, response):
yield from TraceNG.instance().delete_node(request.match_info["node_id"])
response.set_status(204)
@Route.post(
r"/projects/{project_id}/traceng/nodes/{node_id}/duplicate",
parameters={
"project_id": "Project UUID",
"node_id": "Node UUID"
},
status_codes={
201: "Instance duplicated",
404: "Instance doesn't exist"
},
description="Duplicate a TraceNG instance")
def duplicate(request, response):
new_node = yield from TraceNG.instance().duplicate_node(
request.match_info["node_id"],
request.json["destination_node_id"]
)
response.set_status(201)
response.json(new_node)
@Route.post(
r"/projects/{project_id}/traceng/nodes/{node_id}/start",
parameters={
"project_id": "Project UUID",
"node_id": "Node UUID"
},
status_codes={
204: "Instance started",
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Start a TraceNG instance",
input=TRACENG_START_SCHEMA,
output=TRACENG_OBJECT_SCHEMA)
def start(request, response):
traceng_manager = TraceNG.instance()
vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
yield from vm.start(request.get("destination"))
response.json(vm)
@Route.post(
r"/projects/{project_id}/traceng/nodes/{node_id}/stop",
parameters={
"project_id": "Project UUID",
"node_id": "Node UUID"
},
status_codes={
204: "Instance stopped",
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Stop a TraceNG instance")
def stop(request, response):
traceng_manager = TraceNG.instance()
vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
yield from vm.stop()
response.set_status(204)
@Route.post(
r"/projects/{project_id}/traceng/nodes/{node_id}/suspend",
parameters={
"project_id": "Project UUID",
"node_id": "Node UUID"
},
status_codes={
204: "Instance suspended",
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Suspend a TraceNG instance (does nothing)")
def suspend(request, response):
traceng_manager = TraceNG.instance()
traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
response.set_status(204)
@Route.post(
r"/projects/{project_id}/traceng/nodes/{node_id}/reload",
parameters={
"project_id": "Project UUID",
"node_id": "Node UUID",
},
status_codes={
204: "Instance reloaded",
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Reload a TraceNG instance")
def reload(request, response):
traceng_manager = TraceNG.instance()
vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
yield from vm.reload()
response.set_status(204)
@Route.post(
r"/projects/{project_id}/traceng/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
parameters={
"project_id": "Project UUID",
"node_id": "Node UUID",
"adapter_number": "Network adapter where the nio is located",
"port_number": "Port where the nio should be added"
},
status_codes={
201: "NIO created",
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Add a NIO to a TraceNG instance",
input=NIO_SCHEMA,
output=NIO_SCHEMA)
def create_nio(request, response):
traceng_manager = TraceNG.instance()
vm = traceng_manager.get_node(request.match_info["node_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 = traceng_manager.create_nio(request.json)
yield from vm.port_add_nio_binding(int(request.match_info["port_number"]), nio)
response.set_status(201)
response.json(nio)
@Route.put(
r"/projects/{project_id}/traceng/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
parameters={
"project_id": "Project UUID",
"node_id": "Node UUID",
"adapter_number": "Network adapter where the nio is located",
"port_number": "Port from where the nio should be updated"
},
status_codes={
201: "NIO updated",
400: "Invalid request",
404: "Instance doesn't exist"
},
input=NIO_SCHEMA,
output=NIO_SCHEMA,
description="Update a NIO from a TraceNG instance")
def update_nio(request, response):
traceng_manager = TraceNG.instance()
vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
nio = vm.ethernet_adapter.get_nio(int(request.match_info["port_number"]))
if "filters" in request.json and nio:
nio.filters = request.json["filters"]
yield from vm.port_update_nio_binding(int(request.match_info["port_number"]), nio)
response.set_status(201)
response.json(request.json)
@Route.delete(
r"/projects/{project_id}/traceng/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
parameters={
"project_id": "Project UUID",
"node_id": "Node UUID",
"adapter_number": "Network adapter where the nio is located",
"port_number": "Port from where the nio should be removed"
},
status_codes={
204: "NIO deleted",
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Remove a NIO from a TraceNG instance")
def delete_nio(request, response):
traceng_manager = TraceNG.instance()
vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
yield from vm.port_remove_nio_binding(int(request.match_info["port_number"]))
response.set_status(204)
@Route.post(
r"/projects/{project_id}/traceng/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/start_capture",
parameters={
"project_id": "Project UUID",
"node_id": "Node UUID",
"adapter_number": "Adapter to start a packet capture",
"port_number": "Port on the adapter"
},
status_codes={
200: "Capture started",
400: "Invalid request",
404: "Instance doesn't exist",
},
description="Start a packet capture on a TraceNG instance",
input=NODE_CAPTURE_SCHEMA)
def start_capture(request, response):
traceng_manager = TraceNG.instance()
vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
port_number = int(request.match_info["port_number"])
pcap_file_path = os.path.join(vm.project.capture_working_directory(), request.json["capture_file_name"])
yield from vm.start_capture(port_number, pcap_file_path)
response.json({"pcap_file_path": pcap_file_path})
@Route.post(
r"/projects/{project_id}/traceng/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/stop_capture",
parameters={
"project_id": "Project UUID",
"node_id": "Node UUID",
"adapter_number": "Adapter to stop a packet capture",
"port_number": "Port on the adapter"
},
status_codes={
204: "Capture stopped",
400: "Invalid request",
404: "Instance doesn't exist",
},
description="Stop a packet capture on a TraceNG instance")
def stop_capture(request, response):
traceng_manager = TraceNG.instance()
vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
port_number = int(request.match_info["port_number"])
yield from vm.stop_capture(port_number)
response.set_status(204)
@@ -73,7 +73,7 @@ class LinkHandler:
node.get("adapter_number", 0),
node.get("port_number", 0),
label=node.get("label"))
except aiohttp.web_exceptions.HTTPException as e:
except aiohttp.web.HTTPException as e:
yield from project.delete_link(link.id)
raise e
response.set_status(201)
@@ -223,7 +223,7 @@ class NodeHandler:
project = yield from Controller.instance().get_loaded_project(request.match_info["project_id"])
node = project.get_node(request.match_info["node_id"])
yield from node.start()
yield from node.start(data=request.json)
response.json(node)
response.set_status(201)
@@ -409,8 +409,6 @@ class NodeHandler:
node_type = node.node_type
path = "/project-files/{}/{}/{}".format(node_type, node.id, path)
data = yield from request.content.read()
data = yield from request.content.read() #FIXME: are we handling timeout or large files correctly?
yield from node.compute.http_query("POST", "/projects/{project_id}/files{path}".format(project_id=project.id, path=path), data=data, timeout=None, raw=True)
response.set_status(201)
@@ -335,10 +335,10 @@ class ProjectHandler:
try:
with tempfile.SpooledTemporaryFile(max_size=10000) as temp:
while True:
packet = yield from request.content.read(512)
if not packet:
chunk = yield from request.content.read(1024)
if not chunk:
break
temp.write(packet)
temp.write(chunk)
project = yield from import_project(controller, request.match_info["project_id"], temp, location=path, name=name)
except OSError as e:
raise aiohttp.web.HTTPInternalServerError(text="Could not import the project: {}".format(e))
@@ -448,10 +448,13 @@ class ProjectHandler:
try:
with open(path, 'wb+') as f:
while True:
packet = yield from request.content.read(512)
if not packet:
try:
chunk = yield from request.content.read(1024)
except asyncio.TimeoutError:
raise aiohttp.web.HTTPRequestTimeout(text="Timeout when writing to file '{}'".format(path))
if not chunk:
break
f.write(packet)
f.write(chunk)
except FileNotFoundError:
raise aiohttp.web.HTTPNotFound()
except PermissionError:
@@ -133,7 +133,7 @@ class ServerHandler:
@Route.post(
r"/debug",
description="Dump debug informations to disk (debug directory in config directory). Work only for local server",
description="Dump debug information to disk (debug directory in config directory). Work only for local server",
status_codes={
201: "Writed"
})
@@ -16,6 +16,9 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import aiohttp
import asyncio
from gns3server.web.route import Route
from gns3server.controller import Controller
@@ -62,12 +65,18 @@ class SymbolHandler:
def upload(request, response):
controller = Controller.instance()
path = os.path.join(controller.symbols.symbols_path(), os.path.basename(request.match_info["symbol_id"]))
with open(path, 'wb+') as f:
while True:
packet = yield from request.content.read(512)
if not packet:
break
f.write(packet)
try:
with open(path, 'wb') as f:
while True:
try:
chunk = yield from request.content.read(1024)
except asyncio.TimeoutError:
raise aiohttp.web.HTTPRequestTimeout(text="Timeout when writing to symbol '{}'".format(path))
if not chunk:
break
f.write(chunk)
except OSError as e:
raise aiohttp.web.HTTPConflict(text="Could not write symbol file '{}': {}".format(path, e))
# Reset the symbol list
controller.symbols.list()
response.set_status(204)
+4 -13
View File
@@ -17,7 +17,8 @@
import asyncio
import json
import psutil
from gns3server.utils.ping_stats import PingStats
class NotificationQueue(asyncio.Queue):
@@ -38,24 +39,14 @@ class NotificationQueue(asyncio.Queue):
# At first get we return a ping so the client immediately receives data
if self._first:
self._first = False
return ("ping", self._getPing(), {})
return ("ping", PingStats.get(), {})
try:
(action, msg, kwargs) = yield from asyncio.wait_for(super().get(), timeout)
except asyncio.futures.TimeoutError:
return ("ping", self._getPing(), {})
return ("ping", PingStats.get(), {})
return (action, msg, kwargs)
def _getPing(self):
"""
Return the content of the ping notification
"""
msg = {}
# Non blocking call in order to get cpu usage. First call will return 0
msg["cpu_usage_percent"] = psutil.cpu_percent(interval=None)
msg["memory_usage_percent"] = psutil.virtual_memory().percent
return msg
@asyncio.coroutine
def get_json(self, timeout):
"""
+2 -2
View File
@@ -160,7 +160,7 @@ def pid_lock(path):
with open(path) as f:
try:
pid = int(f.read())
os.kill(pid, 0) # If the proces is not running kill return an error
os.kill(pid, 0) # kill returns an error if the process is not running
except (OSError, SystemError, ValueError):
pid = None
except OSError as e:
@@ -183,7 +183,7 @@ def kill_ghosts():
"""
Kill process from previous GNS3 session
"""
detect_process = ["vpcs", "ubridge", "dynamips"]
detect_process = ["vpcs", "traceng", "ubridge", "dynamips"]
for proc in psutil.process_iter():
try:
name = proc.name().lower().split(".")[0]
+10
View File
@@ -87,6 +87,11 @@ DOCKER_CREATE_SCHEMA = {
"type": ["string", "null"],
"minLength": 0,
},
"extra_hosts": {
"description": "Docker extra hosts (added to /etc/hosts)",
"type": ["string", "null"],
"minLength": 0,
},
"container_id": {
"description": "Docker container ID Read only",
"type": "string",
@@ -184,6 +189,11 @@ DOCKER_OBJECT_SCHEMA = {
"type": ["string", "null"],
"minLength": 0,
},
"extra_hosts": {
"description": "Docker extra hosts (added to /etc/hosts)",
"type": ["string", "null"],
"minLength": 0,
},
"node_directory": {
"description": "Path to the node working directory Read only",
"type": "string"
+2 -1
View File
@@ -30,6 +30,7 @@ NODE_TYPE_SCHEMA = {
"docker",
"dynamips",
"vpcs",
"traceng",
"virtualbox",
"vmware",
"iou",
@@ -144,7 +145,7 @@ NODE_OBJECT_SCHEMA = {
},
"console_type": {
"description": "Console type",
"enum": ["vnc", "telnet", "http", "https", "spice", None]
"enum": ["vnc", "telnet", "http", "https", "spice", "none", None]
},
"properties": {
"description": "Properties specific to an emulator",
+55 -3
View File
@@ -15,6 +15,40 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
SUPPLIER_OBJECT_SCHEMA = {
"type": ["object", "null"],
"description": "Supplier of the project",
"properties": {
"logo": {
"type": "string",
"description": "Path to the project supplier logo"
},
"url": {
"type": "string",
"description": "URL to the project supplier site"
}
}
}
VARIABLES_OBJECT_SCHEMA = {
"type": ["array", "null"],
"description": "Variables required to run the project",
"items": {
"properties": {
"name": {
"type": "string",
"description": "Variable name"
},
"value": {
"type": "string",
"description": "Variable value"
}
},
"required": ["name"]
}
}
PROJECT_CREATE_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
@@ -66,10 +100,16 @@ PROJECT_CREATE_SCHEMA = {
"type": "boolean",
"description": "Show the grid on the drawing area"
},
"grid_size": {
"type": "integer",
"description": "Grid size for the drawing area"
},
"show_interface_labels": {
"type": "boolean",
"description": "Show interface labels on the drawing area"
}
},
"supplier": SUPPLIER_OBJECT_SCHEMA,
"variables": VARIABLES_OBJECT_SCHEMA
},
"additionalProperties": False,
"required": ["name"]
@@ -125,10 +165,16 @@ PROJECT_UPDATE_SCHEMA = {
"type": "boolean",
"description": "Show the grid on the drawing area"
},
"grid_size": {
"type": "integer",
"description": "Grid size for the drawing area"
},
"show_interface_labels": {
"type": "boolean",
"description": "Show interface labels on the drawing area"
}
},
"supplier": SUPPLIER_OBJECT_SCHEMA,
"variables": VARIABLES_OBJECT_SCHEMA
},
"additionalProperties": False,
}
@@ -200,10 +246,16 @@ PROJECT_OBJECT_SCHEMA = {
"type": "boolean",
"description": "Show the grid on the drawing area"
},
"grid_size": {
"type": "integer",
"description": "Grid size for the drawing area"
},
"show_interface_labels": {
"type": "boolean",
"description": "Show interface labels on the drawing area"
}
},
"supplier": SUPPLIER_OBJECT_SCHEMA,
"variables": VARIABLES_OBJECT_SCHEMA
},
"additionalProperties": False,
"required": ["project_id"]
+8
View File
@@ -23,6 +23,8 @@ from gns3server.schemas.compute import COMPUTE_OBJECT_SCHEMA
from gns3server.schemas.drawing import DRAWING_OBJECT_SCHEMA
from gns3server.schemas.link import LINK_OBJECT_SCHEMA
from gns3server.schemas.node import NODE_OBJECT_SCHEMA
from gns3server.schemas.project import VARIABLES_OBJECT_SCHEMA
from gns3server.schemas.project import SUPPLIER_OBJECT_SCHEMA
TOPOLOGY_SCHEMA = {
@@ -89,10 +91,16 @@ TOPOLOGY_SCHEMA = {
"type": "boolean",
"description": "Show the grid on the drawing area"
},
"grid_size": {
"type": "integer",
"description": "Grid size for the drawing area"
},
"show_interface_labels": {
"type": "boolean",
"description": "Show interface labels on the drawing area"
},
"supplier": SUPPLIER_OBJECT_SCHEMA,
"variables": VARIABLES_OBJECT_SCHEMA,
"topology": {
"description": "The topology content",
"type": "object",
+162
View File
@@ -0,0 +1,162 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2018 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/>.
TRACENG_CREATE_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation to create a new TraceNG instance",
"type": "object",
"properties": {
"name": {
"description": "TraceNG VM name",
"type": "string",
"minLength": 1,
},
"node_id": {
"description": "Node UUID",
"oneOf": [
{"type": "string",
"minLength": 36,
"maxLength": 36,
"pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"}
]
},
"console": {
"description": "Console TCP port",
"minimum": 1,
"maximum": 65535,
"type": ["integer", "null"]
},
"console_type": {
"description": "Console type",
"enum": ["none"]
},
"ip_address": {
"description": "Source IP address for tracing",
"type": ["string"]
},
"default_destination": {
"description": "Default destination IP address or hostname for tracing",
"type": ["string"]
}
},
"additionalProperties": False,
"required": ["name"]
}
TRACENG_UPDATE_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation to update a TraceNG instance",
"type": "object",
"properties": {
"name": {
"description": "TraceNG VM name",
"type": ["string", "null"],
"minLength": 1,
},
"console": {
"description": "Console TCP port",
"minimum": 1,
"maximum": 65535,
"type": ["integer", "null"]
},
"console_type": {
"description": "Console type",
"enum": ["none"]
},
"ip_address": {
"description": "Source IP address for tracing",
"type": ["string"]
},
"default_destination": {
"description": "Default destination IP address or hostname for tracing",
"type": ["string"]
}
},
"additionalProperties": False,
}
TRACENG_START_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation to start a TraceNG instance",
"type": "object",
"properties": {
"destination": {
"description": "Host or IP address to trace",
"type": ["string"]
}
},
}
TRACENG_OBJECT_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "TraceNG instance",
"type": "object",
"properties": {
"name": {
"description": "TraceNG VM name",
"type": "string",
"minLength": 1,
},
"node_id": {
"description": "Node UUID",
"type": "string",
"minLength": 36,
"maxLength": 36,
"pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"
},
"node_directory": {
"description": "Path to the VM working directory",
"type": "string"
},
"status": {
"description": "VM status",
"enum": ["started", "stopped", "suspended"]
},
"console": {
"description": "Console TCP port",
"minimum": 1,
"maximum": 65535,
"type": ["integer", "null"]
},
"console_type": {
"description": "Console type",
"enum": ["none"]
},
"project_id": {
"description": "Project UUID",
"type": "string",
"minLength": 36,
"maxLength": 36,
"pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"
},
"command_line": {
"description": "Last command line used by GNS3 to start TraceNG",
"type": "string"
},
"ip_address": {
"description": "Source IP address for tracing",
"type": ["string"]
},
"default_destination": {
"description": "Default destination IP address or hostname for tracing",
"type": ["string"]
}
},
"additionalProperties": False,
"required": ["name", "node_id", "status", "console", "console_type", "project_id", "command_line", "ip_address", "default_destination"]
}
+610
View File
@@ -0,0 +1,610 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
height="59.253815"
id="Andysvg"
inkscape:version="0.91 r13725"
sodipodi:docname="traceng.svg"
sodipodi:version="0.32"
version="1.0"
width="65.414536"
x="0.00000000"
y="0.00000000"
inkscape:output_extension="org.inkscape.output.svg.inkscape">
<metadata
id="metadata3">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:title />
<dc:description />
<dc:subject>
<rdf:Bag>
<rdf:li>hash</rdf:li>
<rdf:li />
<rdf:li>hardware</rdf:li>
<rdf:li>computer</rdf:li>
</rdf:Bag>
</dc:subject>
<dc:publisher>
<cc:Agent
rdf:about="http://www.openclipart.org">
<dc:title>Andy Fitzsimon</dc:title>
</cc:Agent>
</dc:publisher>
<dc:creator>
<cc:Agent>
<dc:title>Andy Fitzsimon</dc:title>
</cc:Agent>
</dc:creator>
<dc:rights>
<cc:Agent>
<dc:title>Andy Fitzsimon</dc:title>
</cc:Agent>
</dc:rights>
<dc:date />
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<cc:license
rdf:resource="http://web.resource.org/cc/PublicDomain" />
<dc:language>en</dc:language>
</cc:Work>
<cc:License
rdf:about="http://web.resource.org/cc/PublicDomain">
<cc:permits
rdf:resource="http://web.resource.org/cc/Reproduction" />
<cc:permits
rdf:resource="http://web.resource.org/cc/Distribution" />
<cc:permits
rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
</cc:License>
</rdf:RDF>
</metadata>
<defs
id="defs3">
<linearGradient
id="linearGradient1806">
<stop
id="stop1807"
offset="0.0000000"
style="stop-color:#000000;stop-opacity:0.35051546;" />
<stop
id="stop3276"
offset="0.64999998"
style="stop-color:#000000;stop-opacity:0.13402061;" />
<stop
id="stop1808"
offset="1.0000000"
style="stop-color:#000000;stop-opacity:0.0000000;" />
</linearGradient>
<radialGradient
cx="42.007256"
cy="39.007645"
fx="42.280807"
fy="39.410465"
id="radialGradient1977"
r="11.574221"
xlink:href="#linearGradient1806"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient893">
<stop
id="stop895"
offset="0"
style="stop-color:#000;stop-opacity:1;" />
<stop
id="stop896"
offset="1"
style="stop-color:#fff;stop-opacity:1;" />
</linearGradient>
<linearGradient
id="linearGradient1317">
<stop
id="stop1318"
offset="0.00000000"
style="stop-color:#000000;stop-opacity:0.52892560;" />
<stop
id="stop1320"
offset="0.50000000"
style="stop-color:#000000;stop-opacity:0.17355372;" />
<stop
id="stop1319"
offset="1.0000000"
style="stop-color:#000000;stop-opacity:0.00000000;" />
</linearGradient>
<linearGradient
id="linearGradient1133">
<stop
id="stop1134"
offset="0.00000000"
style="stop-color:#8bb7df;stop-opacity:1.0000000;" />
<stop
id="stop1136"
offset="0.76209301"
style="stop-color:#2a6092;stop-opacity:1.0000000;" />
<stop
id="stop1135"
offset="1.0000000"
style="stop-color:#375e82;stop-opacity:1.0000000;" />
</linearGradient>
<linearGradient
id="linearGradient1098">
<stop
id="stop1099"
offset="0.00000000"
style="stop-color:#ffffff;stop-opacity:1.0000000;" />
<stop
id="stop1101"
offset="0.50000000"
style="stop-color:#ffffff;stop-opacity:0.22314049;" />
<stop
id="stop1102"
offset="0.59930235"
style="stop-color:#ffffff;stop-opacity:0.00000000;" />
<stop
id="stop1100"
offset="1.0000000"
style="stop-color:#ffffff;stop-opacity:0.60330576;" />
</linearGradient>
<linearGradient
id="linearGradient902">
<stop
id="stop903"
offset="0.00000000"
style="stop-color:#000000;stop-opacity:0.00000000;" />
<stop
id="stop904"
offset="1.0000000"
style="stop-color:#000000;stop-opacity:0.22000000;" />
</linearGradient>
<linearGradient
id="linearGradient892">
<stop
id="stop893"
offset="0.00000000"
style="stop-color:#ffffff;stop-opacity:0.00000000;" />
<stop
id="stop894"
offset="1"
style="stop-color:#fff;stop-opacity:1;" />
</linearGradient>
<linearGradient
id="linearGradient888">
<stop
id="stop889"
offset="0.0000000"
style="stop-color:#626262;stop-opacity:1.0000000;" />
<stop
id="stop890"
offset="1"
style="stop-color:#fff;stop-opacity:1;" />
</linearGradient>
<linearGradient
id="linearGradient891"
x1="92.437965"
x2="27.674332"
xlink:href="#linearGradient888"
y1="-3.9104078"
y2="91.076988"
gradientTransform="matrix(0.8184166,0,0,0.530237,-9.8827678,-14.589608)"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient901"
xlink:href="#linearGradient1806"
gradientTransform="matrix(0.8517959,0,0,0.5754549,70.875335,-17.351492)"
x1="9.4921856"
y1="22.862282"
x2="41.719688"
y2="22.862282"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient905"
x1="-77.726181"
x2="95.64444"
xlink:href="#linearGradient888"
y1="208.43991"
y2="11.699047"
gradientTransform="matrix(0.7314635,0,0,0.5932693,-9.8827694,-14.589616)"
gradientUnits="userSpaceOnUse" />
<radialGradient
cx="60.004654"
cy="56.485935"
fx="72.10788"
fy="39.288475"
id="radialGradient1132"
r="68.589226"
xlink:href="#linearGradient1133"
gradientTransform="matrix(0.8184166,0,0,0.530237,-9.8827678,-14.589608)"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient1138"
x1="-249.72067"
x2="-268.25406"
xlink:href="#linearGradient1806"
y1="375.922"
y2="75.912529"
gradientTransform="scale(1.087146,0.91984)"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient1140"
x1="45.685757"
x2="41.96706"
xlink:href="#linearGradient888"
y1="110.4447"
y2="232.24953"
gradientTransform="matrix(1.2743811,0,0,0.3405213,-9.8827694,-14.589616)"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient1141"
x1="0"
x2="0.92957747"
xlink:href="#linearGradient888"
y1="3.3012049"
y2="-0.45783132" />
<linearGradient
id="linearGradient1144"
x1="31.449743"
x2="31.617281"
xlink:href="#linearGradient892"
y1="203.49899"
y2="251.21892"
gradientTransform="matrix(1.4044089,0,0,0.3089952,-9.8827694,-14.589616)"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient1146"
x1="39.810947"
x2="17.87653"
xlink:href="#linearGradient892"
y1="90.197021"
y2="113.71949"
gradientTransform="matrix(0.8811179,0,0,0.4925045,-9.8827694,-14.589616)"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient1148"
x1="39.690613"
x2="70.224304"
xlink:href="#linearGradient892"
y1="49.507656"
y2="20.481863"
gradientTransform="scale(1.329144,0.752364)"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient1150"
x1="35.190361"
x2="8.3460579"
xlink:href="#linearGradient892"
y1="76.277557"
y2="105.42543"
gradientTransform="scale(1.328386,0.752793)"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient1156"
x1="25.220816"
x2="25.220816"
xlink:href="#linearGradient888"
y1="178.48862"
y2="234.26866"
gradientTransform="matrix(1.6156202,0,0,0.2685999,-9.8827678,-14.589608)"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient1157"
x1="51.46093"
x2="-16.224497"
xlink:href="#linearGradient888"
y1="269.85831"
y2="176.28694"
gradientTransform="matrix(1.6156202,0,0,0.2685999,-9.8827678,-14.589608)"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient1166"
x1="111.49758"
x2="107.04918"
xlink:href="#linearGradient1317"
y1="131.25249"
y2="148.78619"
gradientTransform="matrix(0.562951,0,0,0.378005,1.021178,-9.426161)"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient1167"
x1="141.60217"
x2="88.447014"
xlink:href="#linearGradient888"
y1="228.39311"
y2="133.5471"
gradientTransform="matrix(0.544555,0,0,0.390775,1.021178,-9.426161)"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient1169"
x1="146.69923"
x2="74.533691"
xlink:href="#linearGradient893"
y1="224.57898"
y2="81.4776"
gradientTransform="matrix(0.546024,0,0,0.389723,1.021178,-9.426161)"
gradientUnits="userSpaceOnUse" />
<linearGradient
gradientTransform="scale(0.998371,1.001632)"
id="linearGradient1170"
x1="0.47284532"
x2="0.48655096"
xlink:href="#linearGradient902"
y1="-0.016295359"
y2="1.8378206" />
<linearGradient
id="linearGradient1171"
x1="101.10657"
x2="95.100159"
xlink:href="#linearGradient902"
y1="177.77768"
y2="173.03152"
gradientTransform="matrix(0.62565,0,0,0.340123,1.021178,-9.426161)"
gradientUnits="userSpaceOnUse" />
<radialGradient
cx="0.47887325"
cy="0.53333336"
fx="0.47535211"
fy="0.26666668"
id="radialGradient1315"
r="0.41197181"
xlink:href="#linearGradient1317" />
<radialGradient
cx="0.5"
cy="0.50000006"
fx="0.50352114"
fy="0.18269235"
id="radialGradient1316"
r="0.34964636"
xlink:href="#linearGradient1317" />
<linearGradient
id="linearGradient1404"
x1="88.755692"
x2="88.996956"
xlink:href="#linearGradient892"
y1="169.09755"
y2="182.99155"
gradientTransform="matrix(0.629979,0,0,0.337786,1.021178,-9.426161)"
gradientUnits="userSpaceOnUse" />
<linearGradient
gradientTransform="scale(0.997825,1.00218)"
id="linearGradient1505"
x1="0.47157744"
x2="0.48548824"
xlink:href="#linearGradient902"
y1="-0.02485317"
y2="1.8570156" />
<linearGradient
gradientTransform="scale(0.995847,1.00417)"
id="linearGradient1506"
x1="0.4704251"
x2="0.48481107"
xlink:href="#linearGradient902"
y1="-0.04365262"
y2="1.9025002" />
<linearGradient
gradientTransform="scale(0.997153,1.002855)"
id="linearGradient2740"
x1="0.47041038"
x2="0.48453596"
xlink:href="#linearGradient902"
y1="-0.033741195"
y2="1.8771822" />
<linearGradient
id="linearGradient4283"
x1="-0.77314812"
x2="0.99074072"
xlink:href="#linearGradient893"
y1="2.0837989"
y2="-0.033519555" />
<linearGradient
id="linearGradient4284"
x1="-2.3960868e-17"
x2="0.92957747"
xlink:href="#linearGradient893"
y1="3.3012049"
y2="-0.45783132" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient1806"
id="linearGradient1948"
gradientUnits="userSpaceOnUse"
gradientTransform="scale(1.087146,0.91984)"
x1="-249.72067"
y1="375.922"
x2="-268.25406"
y2="75.912529" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient1806"
id="linearGradient1950"
gradientUnits="userSpaceOnUse"
gradientTransform="scale(1.087146,0.91984)"
x1="-249.72067"
y1="375.922"
x2="-268.25406"
y2="75.912529" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient1806"
id="linearGradient1952"
gradientUnits="userSpaceOnUse"
gradientTransform="scale(1.087146,0.91984)"
x1="-249.72067"
y1="375.922"
x2="-268.25406"
y2="75.912529" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient892"
id="linearGradient2625"
gradientUnits="userSpaceOnUse"
gradientTransform="scale(1.329144,0.752364)"
x1="39.690613"
y1="49.507656"
x2="70.224304"
y2="20.481863" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient892"
id="linearGradient2627"
gradientUnits="userSpaceOnUse"
gradientTransform="scale(1.328386,0.752793)"
x1="35.190361"
y1="76.277557"
x2="8.3460579"
y2="105.42543" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient888"
id="linearGradient2633"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.3870648,0,0,0.2694829,71.57581,-24.087318)"
x1="92.437965"
y1="-3.9104078"
x2="27.674332"
y2="91.076988" />
</defs>
<sodipodi:namedview
bordercolor="#666666"
borderopacity="1.0"
id="base"
inkscape:cx="26.459162"
inkscape:cy="28.090626"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:window-height="1405"
inkscape:window-width="2280"
inkscape:window-x="696"
inkscape:window-y="149"
inkscape:zoom="16"
pagecolor="#ffffff"
showborder="true"
inkscape:current-layer="Andysvg"
showgrid="false"
inkscape:window-maximized="0" />
<rect
height="5.4088969"
id="rect1155"
style="fill:url(#linearGradient1156);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient1157);stroke-width:0.95344429pt"
width="38.405891"
x="12.350429"
y="36.575089" />
<path
d="M 0.19287398,54.885213 C -0.67386281,56.46268 1.5579198,59.253939 3.3933636,59.253939 L 62.069027,59.253939 C 63.628857,59.253939 65.976201,57.281205 65.269522,55.925378 L 58.655165,43.235277 C 58.145696,42.257819 57.005954,41.779036 55.881408,41.779036 L 8.9408812,41.779036 C 8.0135624,41.779036 7.0317541,42.438291 6.5938539,43.235277 L 0.19287398,54.885213 z "
id="path1139"
sodipodi:nodetypes="czzzzzzzz"
style="fill:url(#linearGradient1140);fill-opacity:1;fill-rule:evenodd;stroke-width:1.44734821pt" />
<rect
height="39.110481"
id="rect1137"
rx="3.6272225"
ry="3.536587"
style="fill:url(#linearGradient905);fill-opacity:1;fill-rule:evenodd;stroke-width:1.62826681"
width="48.220726"
x="7.4732022"
y="-0.010164791" />
<rect
height="24.74659"
id="rect1131"
style="fill:#ff9955;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient891);stroke-width:0.96503658pt"
width="38.813015"
x="12.390426"
y="6.027596" />
<path
d="M 8.5906358,19.782157 L 8.3772707,34.032521 C 8.3432557,36.304242 9.6712558,37.75887 12.004493,37.777143 L 38.568566,37.985175 L 41.128957,32.160207 L 11.471076,31.952177 L 11.257713,19.678141 L 8.5906358,19.782157 z "
id="path1145"
sodipodi:nodetypes="czzccccc"
style="fill:url(#linearGradient1146);fill-opacity:1;fill-rule:evenodd;stroke-width:1pt" />
<path
d="M 2.7205883,54.675124 C 2.1113822,55.866786 2.9089235,57.975349 4.9701318,57.975349 L 60.179981,57.975349 C 61.276346,57.975349 62.926231,56.48511 62.429524,55.460899 L 57.78047,45.874547 C 57.422369,45.136152 56.621274,44.774474 55.830865,44.774474 L 8.8693353,44.774468 C 8.2175462,44.774468 7.5274604,45.272486 7.2196723,45.874547 L 2.7205883,54.675124 z "
id="path1143"
sodipodi:nodetypes="czzzzzzzz"
style="fill:url(#linearGradient1144);fill-opacity:1;fill-rule:evenodd;stroke-width:1pt" />
<ellipse
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="path4212"
cx="21.5625"
cy="11.253814"
rx="3.3125"
ry="3.375" />
<ellipse
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="path4212-3"
cx="41.1875"
cy="11.128816"
rx="3.3125"
ry="3.375" />
<ellipse
style="opacity:1;fill:#ff9955;fill-opacity:1;stroke:#ffffff;stroke-width:1.85415959;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="path4212-3-6"
cx="30.46245"
cy="25.978863"
rx="2.3854203"
ry="2.4479203" />
<path
inkscape:connector-curvature="0"
style="fill:#ffffff;stroke:#ffffff;stroke-width:0.38147008;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 37.747841,25.720332 c 2.423268,-1.218576 3.927625,-3.912271 3.812948,-7.621135 l 1.642658,1.638437 1.08515,-1.049406 -3.462731,-3.556066 -3.963528,3.384577 1.033228,0.870639 1.703184,-1.46973 c 0.452288,3.542567 -0.666814,5.13406 -2.97277,6.29367 -1.596753,0.802958 -2.928888,1.433178 -4.2106,-0.23115 l -0.510936,0.914481 c 0.714057,0.927235 0.370391,1.816393 1.288764,2.104435 0.947222,0.297085 3.631209,-0.814386 4.554633,-1.278752 z"
id="path3"
sodipodi:nodetypes="scccccccsccss" />
<g
transform="matrix(1.3039536,0,0,1.3039536,-225.75033,-238.74618)"
id="g5" />
<g
transform="matrix(1.3039536,0,0,1.3039536,-225.75033,-238.74618)"
id="g7" />
<g
transform="matrix(1.3039536,0,0,1.3039536,-225.75033,-238.74618)"
id="g9" />
<g
transform="matrix(1.3039536,0,0,1.3039536,-225.75033,-238.74618)"
id="g11" />
<g
transform="matrix(1.3039536,0,0,1.3039536,-225.75033,-238.74618)"
id="g13" />
<g
transform="matrix(1.3039536,0,0,1.3039536,-225.75033,-238.74618)"
id="g15" />
<g
transform="matrix(1.3039536,0,0,1.3039536,-225.75033,-238.74618)"
id="g17" />
<g
transform="matrix(1.3039536,0,0,1.3039536,-225.75033,-238.74618)"
id="g19" />
<g
transform="matrix(1.3039536,0,0,1.3039536,-225.75033,-238.74618)"
id="g21" />
<g
transform="matrix(1.3039536,0,0,1.3039536,-225.75033,-238.74618)"
id="g23" />
<g
transform="matrix(1.3039536,0,0,1.3039536,-225.75033,-238.74618)"
id="g25" />
<g
transform="matrix(1.3039536,0,0,1.3039536,-225.75033,-238.74618)"
id="g27" />
<g
transform="matrix(1.3039536,0,0,1.3039536,-225.75033,-238.74618)"
id="g29" />
<g
transform="matrix(1.3039536,0,0,1.3039536,-225.75033,-238.74618)"
id="g31" />
<g
transform="matrix(1.3039536,0,0,1.3039536,-225.75033,-238.74618)"
id="g33" />
<path
inkscape:connector-curvature="0"
style="fill:#ffffff;stroke:#ffffff;stroke-width:0.38147008;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 27.747543,11.203969 c 2.374543,1.310993 3.774049,4.060623 3.516628,7.762319 l 1.704535,-1.573962 1.043932,1.090417 -3.597106,3.420077 -3.830247,-3.534702 1.06599,-0.830203 1.645321,1.534229 c 0.588378,-3.522521 -0.468606,-5.15593 -2.728194,-6.403483 -1.564647,-0.863854 -2.871524,-1.544907 -4.216378,0.06883 l -0.475341,-0.933479 c 0.749236,-0.899048 0.440066,-1.800781 1.368851,-2.053243 0.95796,-0.2603868 3.597153,0.953621 4.502009,1.453203 z"
id="path3-7"
sodipodi:nodetypes="scccccccsccss" />
</svg>

After

Width:  |  Height:  |  Size: 20 KiB

+9 -2
View File
@@ -62,6 +62,7 @@ class Hypervisor(UBridgeHypervisor):
af, socktype, proto, _, sa = res
# let the OS find an unused port for the uBridge hypervisor
with socket.socket(af, socktype, proto) as sock:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(sa)
port = sock.getsockname()[1]
break
@@ -138,8 +139,14 @@ class Hypervisor(UBridgeHypervisor):
match = re.search("ubridge version ([0-9a-z\.]+)", output)
if match:
self._version = match.group(1)
if parse_version(self._version) < parse_version("0.9.12"):
raise UbridgeError("uBridge executable version must be >= 0.9.12")
if sys.platform.startswith("win") or sys.platform.startswith("darwin"):
minimum_required_version = "0.9.12"
else:
# uBridge version 0.9.14 is required for packet filters
# to work for IOU nodes.
minimum_required_version = "0.9.14"
if parse_version(self._version) < parse_version(minimum_required_version):
raise UbridgeError("uBridge executable version must be >= {}".format(minimum_required_version))
else:
raise UbridgeError("Could not determine uBridge version for {}".format(self._path))
except (OSError, subprocess.SubprocessError) as e:
Loaded 100 of 122 files, more files were not shown because too many files have changed in this diff. Show more