Compare commits

..
56 Commits
Author SHA1 Message Date
Jeremy 701a1626f7 Bump version to 1.2.2 2015-01-15 17:44:09 -07:00
Jeremy e6e83ffa8a Update the VM name in VirtualBox for linked clones. 2015-01-14 16:48:32 -07:00
Jeremy 2bfe098866 Bump version to 1.2.2.dev2 2015-01-12 16:30:09 -07:00
Jeremy 2ef45781ad Checks connection failure to Qemu monitor. 2015-01-11 18:24:13 -07:00
Jeremy Grossmann 14cc6d414f Merge pull request #63 from Raizo62/master
Add snapshot named reset to linked cloned Vbox
2015-01-11 17:16:16 -07:00
Raizo62 cbbb89f69c Add snapshot named reset to linked cloned Vbox 2015-01-11 12:22:59 +00:00
Jeremy 29896151e7 Support spaces in the controller name of VirtualBox clones. 2015-01-06 15:31:50 -07:00
Jeremy 7f6f7022b0 Support to run VirtualBox as another user. 2015-01-05 16:18:33 -07:00
Jeremy Grossmann c77ebca036 Merge pull request #62 from shmygov/qemunio
Dynamically configure network connections of running QEMU VMs
2015-01-05 15:46:09 -07:00
Jeremy 881cc6f592 Fixes wrong IOS config paths. 2015-01-04 18:51:55 -07:00
Jeremy 5265818365 Support for IOURC file on the server side. 2015-01-04 15:59:00 -07:00
Jeremy d1e186317c Console switching from local/remote to remote/local while a VirtualBox VM is running. 2015-01-04 14:56:17 -07:00
Jeremy a6fd457cff Bump the maximum network adapters to 32 for Qemu. 2015-01-03 16:16:07 -07:00
Dmitry Shmygov 4eedc9c74c Dynamically configure network connections of running QEMU VMs (including new QEMU versions) 2015-01-03 05:04:52 +03:00
Dmitry Shmygov ccb4ccd612 Return QEMU monitor responses as Match Objects (more flexibility for future uses) 2014-12-29 01:59:45 +03:00
Dmitry Shmygov c775b05d33 Dynamically configure network connections of running QEMU VMs 2014-12-29 01:43:56 +03:00
Dmitry Shmygov 9cedb1cc99 Add sleep after write to QEMU monitor (to work with new QEMU versions) 2014-12-27 12:44:10 +03:00
Jeremy 1f615430ae Checks for broken symbolic links. 2014-12-24 17:19:42 -07:00
Jeremy 2de1a97076 Merge remote-tracking branch 'origin/master' 2014-12-24 15:47:07 -07:00
Jeremy 747c0ee7a3 More checks on minimum RAM for IOS routers and updates default values to match the latest IOS image requirements. 2014-12-24 15:46:59 -07:00
grossmj d9f44edcaf Fixes incompatibility for IOS startup-config and private-config paths created on Windows and loaded from a project on Linux/Mac OS X. 2014-12-23 15:29:27 -07:00
Jeremy Grossmann e9991affc3 Merge pull request #60 from shmygov/suspendqemu
QEMU VM suspend/resume and reload
2014-12-23 12:27:18 -07:00
Dmitry Shmygov 84511d7b39 QEMU VM suspend/resume and reload 2014-12-23 14:42:58 +03:00
Dmitry Shmygov 3590985c07 Add QEMU monitor port to control running QEMU VMs 2014-12-23 14:42:58 +03:00
Jeremy 7785c03eac Ignore Unicode errors when executing vboxmanage. 2014-12-19 15:47:12 -07:00
Jeremy 7f37f649da Catch OSError exception for subprocess calls. 2014-12-11 12:15:24 -07:00
Jeremy acb5103119 Merge remote-tracking branch 'origin/master' 2014-12-09 19:17:53 -07:00
Jeremy 823344bf44 Get Windows interface list from the registry if the COM service fails. 2014-12-09 19:17:25 -07:00
Jeremy Grossmann ecf7ebc26b Merge pull request #58 from dlintott/fix_gns3-gui_158
Remove extra line that was over-writing the auto Idle-PC value
2014-12-09 16:16:05 -07:00
Daniel Lintott 317a37147e Remove extra line that was over-writing the auto Idle-PC value
Fixes GNS3/gns3-gui#158
2014-12-09 19:39:22 +00:00
Jeremy Grossmann 0f238c5bb7 Merge pull request #54 from planctechnologies/dev
Qemu cloud improvements
2014-12-09 12:36:38 -07:00
Jeremy Grossmann 93934cf554 Merge pull request #55 from dlintott/fix_broken_test
Update test with correct hypervisor IP
2014-12-09 12:36:01 -07:00
Daniel Lintott 6a81390808 Update test with correct hypervisor IP 2014-12-09 13:49:45 +00:00
Jerry Seutter f1503ae655 Handle initrd and kernel paths in qemu 2014-12-08 20:14:55 -07:00
Jeremy 20592f72c0 Fixes UID bit test. 2014-12-08 14:54:06 -07:00
Jerry Seutter 00f49e337d Merge branch 'master' into dev 2014-12-08 14:22:31 -07:00
Jeremy fc04a94dce Bump version to 1.2.2.dev1 2014-12-05 13:53:30 -07:00
Jeremy ba357b0541 Bump version to 1.2.1 2014-12-04 12:49:40 -07:00
Jeremy f58c7960c9 Use bundled Qemu on Windows and OSX by default and checks if remote server are registered. 2014-12-04 12:25:49 -07:00
Jeremy 5a468888c8 Bump version to 1.2.1.dev2 2014-12-02 18:52:28 -07:00
Jeremy 8f53d51c05 Support for CPU throttling and process priority for Qemu. 2014-12-02 18:12:37 -07:00
Jeremy 1e01c85be9 Change search paths for Qemu on Windows. 2014-12-02 14:49:39 -07:00
grossmj fed02ee167 Adds default path for VBoxManage on Mac OS X. 2014-11-29 16:42:57 -07:00
grossmj 632134a02a Support for older Qemu versions like the 0.11.0 on Windows. 2014-11-29 14:11:51 -07:00
Jeremy 183a6aed44 Do not use universal_newlines in subprocess. 2014-11-26 15:07:15 -07:00
Jeremy d97ba11728 Fixes C7200 IO cards insert/remove issues and makes C7200-IO-FE the default. 2014-11-24 17:02:00 -07:00
Jerry Seutter 9ec1373d36 dms terminates instance if id matches 2014-11-24 16:49:01 -07:00
Jerry Seutter b220cffcf4 Updating dms to look for cloud_region instead of region 2014-11-24 16:40:55 -07:00
Jerry Seutter 7ef8dd794a Copy updated cloud/ module from gns3-gui 2014-11-24 16:24:57 -07:00
Jerry Seutter b9a4531544 Ensuring dead_time is an integer 2014-11-24 15:35:09 -07:00
Jerry Seutter cfdff773c4 deadtime -> dead_time 2014-11-24 15:08:58 -07:00
Jerry Seutter ef8c6b603c Add debugging to deadman process 2014-11-24 14:32:06 -07:00
Jeremy 4918675cd5 Fixes Qemu version detection. 2014-11-24 11:44:27 -07:00
Jeremy 6ef614103e Ignore inaccessible VirtualBox VMs. 2014-11-24 11:15:30 -07:00
Jeremy 09948a366f Use SubprocessError to catch Subprocess exceptions. 2014-11-22 17:45:04 -07:00
Jeremy 3bd88178a0 Bump to version 1.2.1.dev1 and fixes vboxmanage lookup on Windows. 2014-11-20 19:01:00 -07:00
30 changed files with 809 additions and 226 deletions

No files matched your search

+61 -9
View File
@@ -29,7 +29,7 @@ import logging
from io import StringIO, BytesIO
from libcloud.compute.base import NodeAuthSSHKey
from libcloud.storage.types import ContainerAlreadyExistsError, ContainerDoesNotExistError
from libcloud.storage.types import ContainerAlreadyExistsError, ContainerDoesNotExistError, ObjectDoesNotExistError
from .exceptions import ItemNotFound, KeyPairExists, MethodNotAllowed
from .exceptions import OverLimit, BadRequest, ServiceUnavailable
@@ -216,11 +216,11 @@ class BaseCloudCtrl(object):
return self.driver.list_key_pairs()
def upload_file(self, file_path, folder):
def upload_file(self, file_path, cloud_object_name):
"""
Uploads file to cloud storage (if it is not identical to a file already in cloud storage).
:param file_path: path to file to upload
:param folder: folder in cloud storage to save file in
:param cloud_object_name: name of file saved in cloud storage
:return: True if file was uploaded, False if it was skipped because it already existed and was identical
"""
try:
@@ -231,7 +231,6 @@ class BaseCloudCtrl(object):
with open(file_path, 'rb') as file:
local_file_hash = hashlib.md5(file.read()).hexdigest()
cloud_object_name = folder + '/' + os.path.basename(file_path)
cloud_hash_name = cloud_object_name + '.md5'
cloud_objects = [obj.name for obj in gns3_container.list_objects()]
@@ -254,23 +253,24 @@ class BaseCloudCtrl(object):
def list_projects(self):
"""
Lists projects in cloud storage
:return: List of (project name, object name in storage)
:return: Dictionary where project names are keys and values are names of objects in storage
"""
try:
gns3_container = self.storage_driver.get_container(self.GNS3_CONTAINER_NAME)
projects = [
(obj.name.replace('projects/', '').replace('.zip', ''), obj.name)
projects = {
obj.name.replace('projects/', '').replace('.zip', ''): obj.name
for obj in gns3_container.list_objects()
if obj.name.startswith('projects/') and obj.name[-4:] == '.zip'
]
}
return projects
except ContainerDoesNotExistError:
return []
def download_file(self, file_name, destination=None):
"""
Downloads file from cloud storage
Downloads file from cloud storage. If a file exists at destination, and it is identical to the file in cloud
storage, it is not downloaded.
:param file_name: name of file in cloud storage to download
:param destination: local path to save file to (if None, returns file contents as a file-like object)
:return: A file-like object if file contents are returned, or None if file is saved to filesystem
@@ -278,7 +278,22 @@ class BaseCloudCtrl(object):
gns3_container = self.storage_driver.get_container(self.GNS3_CONTAINER_NAME)
storage_object = gns3_container.get_object(file_name)
if destination is not None:
if os.path.isfile(destination):
# if a file exists at destination and its hash matches that of the
# file in cloud storage, don't download it
with open(destination, 'rb') as f:
local_file_hash = hashlib.md5(f.read()).hexdigest()
hash_object = gns3_container.get_object(file_name + '.md5')
cloud_object_hash = ''
for chunk in hash_object.as_stream():
cloud_object_hash += chunk.decode('utf8')
if local_file_hash == cloud_object_hash:
return
storage_object.download(destination)
else:
contents = b''
@@ -287,3 +302,40 @@ class BaseCloudCtrl(object):
contents += chunk
return BytesIO(contents)
def find_storage_image_names(self, images_to_find):
"""
Maps names of image files to their full name in cloud storage
:param images_to_find: list of image names to find
:return: A dictionary where keys are image names, and values are the corresponding names of
the files in cloud storage
"""
gns3_container = self.storage_driver.get_container(self.GNS3_CONTAINER_NAME)
images_in_storage = [obj.name for obj in gns3_container.list_objects() if obj.name.startswith('images/')]
images = {}
for image_name in images_to_find:
images_with_same_name =\
list(filter(lambda storage_image_name: storage_image_name.endswith(image_name), images_in_storage))
if len(images_with_same_name) == 1:
images[image_name] = images_with_same_name[0]
else:
raise Exception('Image does not exist in cloud storage or is duplicated')
return images
def delete_file(self, file_name):
gns3_container = self.storage_driver.get_container(self.GNS3_CONTAINER_NAME)
try:
object_to_delete = gns3_container.get_object(file_name)
object_to_delete.delete()
except ObjectDoesNotExistError:
pass
try:
hash_object = gns3_container.get_object(file_name + '.md5')
hash_object.delete()
except ObjectDoesNotExistError:
pass
+2 -54
View File
@@ -42,11 +42,9 @@ class RackspaceCtrl(BaseCloudCtrl):
""" Controller class for interacting with Rackspace API. """
def __init__(self, username, api_key, gns3_ias_url):
def __init__(self, username, api_key, *args, **kwargs):
super(RackspaceCtrl, self).__init__(username, api_key)
self.gns3_ias_url = gns3_ias_url
# set this up so it can be swapped out with a mock for testing
self.post_fn = requests.post
self.driver_cls = get_driver(Provider.RACKSPACE)
@@ -225,55 +223,6 @@ class RackspaceCtrl(BaseCloudCtrl):
self.region = region
return True
def _get_shared_images(self, username, region, gns3_version):
"""
Given a GNS3 version, ask gns3-ias to share compatible images
Response:
[{"created_at": "", "schema": "", "status": "", "member_id": "", "image_id": "", "updated_at": ""},]
or, if access was already asked
[{"image_id": "", "member_id": "", "status": "ALREADYREQUESTED"},]
"""
endpoint = self.gns3_ias_url+"/images/grant_access"
params = {
"user_id": username,
"user_region": region.upper(),
"gns3_version": gns3_version,
}
try:
response = requests.get(endpoint, params=params)
except requests.ConnectionError:
raise ApiError("Unable to connect to IAS")
status = response.status_code
if status == 200:
return response.json()
elif status == 404:
raise ItemNotFound()
else:
raise ApiError("IAS status code: %d" % status)
def list_images(self):
"""
Return a dictionary containing RackSpace server images
retrieved from gns3-ias server
"""
if not (self.tenant_id and self.region):
return {}
try:
shared_images = self._get_shared_images(self.tenant_id, self.region, __version__)
images = {}
for i in shared_images:
images[i['image_id']] = i['image_name']
return images
except ItemNotFound:
return {}
except ApiError as e:
log.error('Error while retrieving image list: %s' % e)
return {}
def get_image(self, image_id):
return self.driver.get_image(image_id)
@@ -290,12 +239,11 @@ def get_provider(cloud_settings):
username = cloud_settings['cloud_user_name']
apikey = cloud_settings['cloud_api_key']
region = cloud_settings['cloud_region']
ias_url = cloud_settings.get('gns3_ias_url', '')
except KeyError as e:
log.error("Unable to create cloud provider: {}".format(e))
return
provider = RackspaceCtrl(username, apikey, ias_url)
provider = RackspaceCtrl(username, apikey)
if not provider.authenticate():
log.error("Authentication failed for cloud provider")
+12 -11
View File
@@ -79,12 +79,12 @@ Options:
--instance_id ID of the Rackspace instance to terminate
--cloud_region Region of instance
--deadtime How long in seconds can the communication lose exist before we
--dead_time How long in seconds can the communication lose exist before we
shutdown this instance.
Default:
Example --deadtime=3600 (60 minutes)
Example --dead_time=3600 (60 minutes)
--check-interval Defaults to --deadtime, used for debugging
--check-interval Defaults to --dead_time, used for debugging
--init-wait Inital wait time, how long before we start pulling the file.
Default: 300 (5 min)
@@ -113,7 +113,7 @@ def parse_cmd_line(argv):
"cloud_api_key=",
"instance_id=",
"region=",
"deadtime=",
"dead_time=",
"init-wait=",
"check-interval=",
"file=",
@@ -133,7 +133,7 @@ def parse_cmd_line(argv):
cmd_line_option_list["cloud_api_key"] = None
cmd_line_option_list["instance_id"] = None
cmd_line_option_list["region"] = None
cmd_line_option_list["deadtime"] = 60 * 60 #minutes
cmd_line_option_list["dead_time"] = 60 * 60 #minutes
cmd_line_option_list["check-interval"] = None
cmd_line_option_list["init-wait"] = 5 * 60
cmd_line_option_list["file"] = None
@@ -150,6 +150,7 @@ def parse_cmd_line(argv):
get_gns3secrets(cmd_line_option_list)
cmd_line_option_list["dead_time"] = int(cmd_line_option_list["dead_time"])
for opt, val in opts:
if (opt in ("-h", "--help")):
@@ -167,8 +168,8 @@ def parse_cmd_line(argv):
cmd_line_option_list["instance_id"] = val
elif (opt in ("--region")):
cmd_line_option_list["region"] = val
elif (opt in ("--deadtime")):
cmd_line_option_list["deadtime"] = int(val)
elif (opt in ("--dead_time")):
cmd_line_option_list["dead_time"] = int(val)
elif (opt in ("--check-interval")):
cmd_line_option_list["check-interval"] = int(val)
elif (opt in ("--init-wait")):
@@ -183,7 +184,7 @@ def parse_cmd_line(argv):
if cmd_line_option_list["shutdown"] == False:
if cmd_line_option_list["check-interval"] is None:
cmd_line_option_list["check-interval"] = cmd_line_option_list["deadtime"] + 120
cmd_line_option_list["check-interval"] = cmd_line_option_list["dead_time"] + 120
if cmd_line_option_list["cloud_user_name"] is None:
print("You need to specify a username!!!!")
@@ -317,9 +318,9 @@ def monitor_loop(options):
delta = now - file_last_modified
log.debug("File last updated: %s seconds ago" % (delta.seconds))
if delta.seconds > options["deadtime"]:
log.warning("Deadtime exceeded, terminating instance ...")
#Terminate involes many layers of HTTP / API calls, lots of
if delta.seconds > options["dead_time"]:
log.warning("Dead time exceeded, terminating instance ...")
#Terminate involves many layers of HTTP / API calls, lots of
#different errors types could occur here.
try:
rksp = Rackspace(options)
+2 -2
View File
@@ -41,7 +41,7 @@ class Rackspace(object):
self.authenticated = False
self.hostname = socket.gethostname()
self.instance_id = options["instance_id"]
self.region = options["region"]
self.region = options["cloud_region"]
log.debug("Authenticating with Rackspace")
log.debug("My hostname: %s" % (self.hostname))
@@ -59,7 +59,7 @@ class Rackspace(object):
self.rksp.set_region(self.region)
for server in self.rksp.list_instances():
log.debug("Checking server: %s" % (server.name))
if server.name.lower() == self.hostname.lower() and server.id == self.instance_id:
if server.id == self.instance_id:
log.info("Found matching instance: %s" % (server.id))
log.info("Startup id: %s" % (self.instance_id))
return server
+38 -6
View File
@@ -35,16 +35,48 @@ def get_windows_interfaces():
"""
import win32com.client
import pywintypes
locator = win32com.client.Dispatch("WbemScripting.SWbemLocator")
service = locator.ConnectServer(".", "root\cimv2")
interfaces = []
# more info on Win32_NetworkAdapter: http://msdn.microsoft.com/en-us/library/aa394216%28v=vs.85%29.aspx
for adapter in service.InstancesOf("Win32_NetworkAdapter"):
if adapter.NetConnectionStatus == 2 or adapter.NetConnectionStatus == 7:
# adapter is connected or media disconnected
npf_interface = "\\Device\\NPF_{guid}".format(guid=adapter.GUID)
try:
# more info on Win32_NetworkAdapter: http://msdn.microsoft.com/en-us/library/aa394216%28v=vs.85%29.aspx
for adapter in service.InstancesOf("Win32_NetworkAdapter"):
if adapter.NetConnectionStatus == 2 or adapter.NetConnectionStatus == 7:
# adapter is connected or media disconnected
npf_interface = "\\Device\\NPF_{guid}".format(guid=adapter.GUID)
interfaces.append({"id": npf_interface,
"name": adapter.NetConnectionID})
except pywintypes.com_error:
log.warn("could not use the COM service to retrieve interface info, trying using the registry...")
return get_windows_interfaces_from_registry()
return interfaces
def get_windows_interfaces_from_registry():
import winreg
interfaces = []
try:
hkey = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkCards")
for index in range(winreg.QueryInfoKey(hkey)[0]):
network_card_id = winreg.EnumKey(hkey, index)
hkeycard = winreg.OpenKey(hkey, network_card_id)
guid, _ = winreg.QueryValueEx(hkeycard, "ServiceName")
connection = r"SYSTEM\CurrentControlSet\Control\Network\{4D36E972-E325-11CE-BFC1-08002BE10318}" + "\{}\Connection".format(guid)
hkeycon = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, connection)
name, _ = winreg.QueryValueEx(hkeycon, "Name")
npf_interface = "\\Device\\NPF_{guid}".format(guid=guid)
interfaces.append({"id": npf_interface,
"name": adapter.NetConnectionID})
"name": name})
winreg.CloseKey(hkeycon)
winreg.CloseKey(hkeycard)
winreg.CloseKey(hkey)
except OSError as e:
log.error("could not read registry information: {}".format(e))
return interfaces
+2 -2
View File
@@ -129,8 +129,8 @@ def has_privileged_access(executable):
if os.geteuid() == 0:
# we are root, so we should have privileged access.
return True
if os.stat(executable).st_mode & stat.S_ISVTX == stat.S_ISVTX:
# the executable has a sticky bit.
if os.stat(executable).st_mode & stat.S_ISUID or os.stat(executable).st_mode & stat.S_ISGID:
# the executable has set UID bit.
return True
# test if the executable has the CAP_NET_RAW capability (Linux only)
+1 -1
View File
@@ -83,6 +83,7 @@ class DeadMan(IModule):
cmd.append("--file")
cmd.append("%s" % (self._heartbeat_file))
cmd.append("--background")
cmd.append("--debug")
log.info("Deadman: Running command: %s"%(cmd))
process = subprocess.Popen(cmd, stderr=subprocess.STDOUT, shell=False)
@@ -94,7 +95,6 @@ class DeadMan(IModule):
"""
cmd = []
cmd.append("gns3dms")
cmd.append("-k")
log.info("Deadman: Running command: %s"%(cmd))
+23 -19
View File
@@ -16,7 +16,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import base64
import ntpath
import time
from gns3server.modules import IModule
@@ -73,8 +72,8 @@ import logging
log = logging.getLogger(__name__)
ADAPTER_MATRIX = {"C7200_IO_2FE": C7200_IO_2FE,
"C7200_IO_FE": C7200_IO_FE,
ADAPTER_MATRIX = {"C7200-IO-2FE": C7200_IO_2FE,
"C7200-IO-FE": C7200_IO_FE,
"C7200-IO-GE-E": C7200_IO_GE_E,
"NM-16ESW": NM_16ESW,
"NM-1E": NM_1E,
@@ -171,6 +170,8 @@ class VM(object):
if chassis:
router = PLATFORMS[platform](hypervisor, name, router_id, chassis=chassis)
elif platform == "c7200" and os.path.basename(image).lower().startswith("c7200p"):
router = PLATFORMS[platform](hypervisor, name, router_id, npe="npe-g2")
else:
router = PLATFORMS[platform](hypervisor, name, router_id)
router.ram = ram
@@ -412,36 +413,40 @@ class VM(object):
response = {}
try:
startup_config_path = os.path.join(router.hypervisor.working_dir, "configs", "i{}_startup-config.cfg".format(router.id))
private_config_path = os.path.join(router.hypervisor.working_dir, "configs", "i{}_private-config.cfg".format(router.id))
default_startup_config_path = os.path.join(router.hypervisor.working_dir, "configs", "i{}_startup-config.cfg".format(router.id))
default_private_config_path = os.path.join(router.hypervisor.working_dir, "configs", "i{}_private-config.cfg".format(router.id))
# a new startup-config has been pushed
if "startup_config_base64" in request:
# update the request with the new local startup-config path
request["startup_config"] = self.create_config_from_base64(request["startup_config_base64"], router, startup_config_path)
request["startup_config"] = self.create_config_from_base64(request["startup_config_base64"], router, default_startup_config_path)
# a new private-config has been pushed
if "private_config_base64" in request:
# update the request with the new local private-config path
request["private_config"] = self.create_config_from_base64(request["private_config_base64"], router, private_config_path)
request["private_config"] = self.create_config_from_base64(request["private_config_base64"], router, default_private_config_path)
if "startup_config" in request:
if os.path.isfile(request["startup_config"]) and request["startup_config"] != startup_config_path:
startup_config_path = request["startup_config"].replace("\\", '/')
if os.path.isfile(startup_config_path) and startup_config_path != default_startup_config_path:
# this is a local file set in the GUI
request["startup_config"] = self.create_config_from_file(request["startup_config"], router, startup_config_path)
router.set_config(request["startup_config"])
startup_config_path = self.create_config_from_file(startup_config_path, router, default_startup_config_path)
router.set_config(startup_config_path)
else:
router.set_config(request["startup_config"])
response["startup_config"] = request["startup_config"]
router.set_config(startup_config_path)
response["startup_config"] = startup_config_path
del request["startup_config"]
if "private_config" in request:
if os.path.isfile(request["private_config"]) and request["private_config"] != private_config_path:
private_config_path = request["private_config"].replace("\\", '/')
if os.path.isfile(private_config_path) and private_config_path != default_private_config_path:
# this is a local file set in the GUI
request["private_config"] = self.create_config_from_file(request["private_config"], router, private_config_path)
router.set_config(router.startup_config, request["private_config"])
private_config_path = self.create_config_from_file(private_config_path, router, default_private_config_path)
router.set_config(router.startup_config, private_config_path)
else:
router.set_config(router.startup_config, request["private_config"])
response["private_config"] = request["private_config"]
router.set_config(router.startup_config, private_config_path)
response["private_config"] = private_config_path
del request["private_config"]
except DynamipsError as e:
self.send_custom_error(str(e))
@@ -468,7 +473,7 @@ class VM(object):
except DynamipsError as e:
self.send_custom_error(str(e))
return
elif name.startswith("slot") and value == None:
elif name.startswith("slot") and value is None:
slot_id = int(name[-1])
if router.slots[slot_id]:
try:
@@ -760,7 +765,6 @@ class VM(object):
if was_auto_started:
router.stop()
validated_idlepc = "0x0"
response = {"id": router.id,
"logs": logs,
"idlepc": validated_idlepc}
@@ -474,8 +474,8 @@ class DynamipsHypervisor(object):
log.debug("sending {}".format(command))
self.socket.sendall(command.encode('utf-8'))
except OSError as e:
raise DynamipsError("Lost communication with {host}:{port} :{error}"
.format(host=self._host, port=self._port, error=e))
raise DynamipsError("Lost communication with {host}:{port} :{error}, Dynamips process running: {run}"
.format(host=self._host, port=self._port, error=e, run=self.is_running()))
# Now retrieve the result
data = []
@@ -485,16 +485,16 @@ class DynamipsHypervisor(object):
chunk = self.socket.recv(1024) # match to Dynamips' buffer size
buf += chunk.decode("utf-8")
except OSError as e:
raise DynamipsError("Communication timed out with {host}:{port} :{error}"
.format(host=self._host, port=self._port, error=e))
raise DynamipsError("Communication timed out with {host}:{port} :{error}, Dynamips process running: {run}"
.format(host=self._host, port=self._port, error=e, run=self.is_running()))
# If the buffer doesn't end in '\n' then we can't be done
try:
if buf[-1] != '\n':
continue
except IndexError:
raise DynamipsError("Could not communicate with {host}:{port}"
.format(host=self._host, port=self._port))
raise DynamipsError("Could not communicate with {host}:{port}, Dynamips process running: {run}"
.format(host=self._host, port=self._port, run=self.is_running()))
data += buf.split('\r\n')
if data[-1] == '':
@@ -502,8 +502,8 @@ class DynamipsHypervisor(object):
buf = ''
if len(data) == 0:
raise DynamipsError("no data returned from {host}:{port}"
.format(host=self._host, port=self._port))
raise DynamipsError("no data returned from {host}:{port}, Dynamips process running: {run}"
.format(host=self._host, port=self._port, run=self.is_running()))
# Does it contain an error code?
if self.error_re.search(data[-1]):
+1 -1
View File
@@ -212,7 +212,7 @@ class Hypervisor(DynamipsHypervisor):
cwd=self._working_dir)
log.info("Dynamips started PID={}".format(self._process.pid))
self._started = True
except OSError as e:
except (OSError, subprocess.SubprocessError) as e:
log.error("could not start Dynamips: {}".format(e))
raise DynamipsError("could not start Dynamips: {}".format(e))
+1 -1
View File
@@ -44,7 +44,7 @@ class C1700(Router):
Router.__init__(self, hypervisor, name, router_id, platform="c1700")
# Set default values for this platform
self._ram = 64
self._ram = 128
self._nvram = 32
self._disk0 = 0
self._disk1 = 0
+1 -1
View File
@@ -59,7 +59,7 @@ class C2600(Router):
Router.__init__(self, hypervisor, name, router_id, platform="c2600")
# Set default values for this platform
self._ram = 64
self._ram = 128
self._nvram = 128
self._disk0 = 0
self._disk1 = 0
+1 -1
View File
@@ -40,7 +40,7 @@ class C2691(Router):
Router.__init__(self, hypervisor, name, router_id, platform="c2691")
# Set default values for this platform
self._ram = 128
self._ram = 192
self._nvram = 112
self._disk0 = 16
self._disk1 = 0
+1 -1
View File
@@ -42,7 +42,7 @@ class C3600(Router):
Router.__init__(self, hypervisor, name, router_id, platform="c3600")
# Set default values for this platform
self._ram = 128
self._ram = 192
self._nvram = 128
self._disk0 = 0
self._disk1 = 0
+1 -1
View File
@@ -40,7 +40,7 @@ class C3745(Router):
Router.__init__(self, hypervisor, name, router_id, platform="c3745")
# Set default values for this platform
self._ram = 128
self._ram = 256
self._nvram = 304
self._disk0 = 16
self._disk1 = 0
+3 -3
View File
@@ -22,7 +22,7 @@ http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L294
from ..dynamips_error import DynamipsError
from .router import Router
from ..adapters.c7200_io_2fe import C7200_IO_2FE
from ..adapters.c7200_io_fe import C7200_IO_FE
from ..adapters.c7200_io_ge_e import C7200_IO_GE_E
import logging
@@ -43,7 +43,7 @@ class C7200(Router):
Router.__init__(self, hypervisor, name, router_id, platform="c7200")
# Set default values for this platform
self._ram = 256
self._ram = 512
self._nvram = 128
self._disk0 = 64
self._disk1 = 0
@@ -70,7 +70,7 @@ class C7200(Router):
if npe == "npe-g2":
self.slot_add_binding(0, C7200_IO_GE_E())
else:
self.slot_add_binding(0, C7200_IO_2FE())
self.slot_add_binding(0, C7200_IO_FE())
def defaults(self):
"""
+7 -4
View File
@@ -354,8 +354,11 @@ class Router(object):
self.resume()
elif status == "inactive":
if not os.path.isfile(self._image):
raise DynamipsError("IOS image '{}' is not accessible".format(self._image))
if not os.path.isfile(self._image) or not os.path.exists(self._image):
if os.path.islink(self._image):
raise DynamipsError("IOS image '{}' linked to '{}' is not accessible".format(self._image, os.path.realpath(self._image)))
else:
raise DynamipsError("IOS image '{}' is not accessible".format(self._image))
try:
with open(self._image, "rb") as f:
@@ -1324,7 +1327,7 @@ class Router(object):
adapter=current_adapter))
# Only c7200, c3600 and c3745 (NM-4T only) support new adapter while running
if self.is_running() and not (self._platform == 'c7200'
if self.is_running() and not ((self._platform == 'c7200' and not str(adapter).startswith('C7200'))
and not (self._platform == 'c3600' and self.chassis == '3660')
and not (self._platform == 'c3745' and adapter == 'NM-4T')):
raise DynamipsError("Adapter {adapter} cannot be added while router {name} is running".format(adapter=adapter,
@@ -1369,7 +1372,7 @@ class Router(object):
slot_id=slot_id))
# Only c7200, c3600 and c3745 (NM-4T only) support to remove adapter while running
if self.is_running() and not (self._platform == 'c7200'
if self.is_running() and not ((self._platform == 'c7200' and not str(adapter).startswith('C7200'))
and not (self._platform == 'c3600' and self.chassis == '3660')
and not (self._platform == 'c3745' and adapter == 'NM-4T')):
raise DynamipsError("Adapter {adapter} cannot be removed while router {name} is running".format(adapter=adapter,
+6 -1
View File
@@ -99,6 +99,7 @@ class IOU(IModule):
self._projects_dir = kwargs["projects_dir"]
self._tempdir = kwargs["temp_dir"]
self._working_dir = self._projects_dir
self._server_iourc_path = iou_config.get("iourc", "")
self._iourc = ""
# check every 5 seconds
@@ -473,7 +474,11 @@ class IOU(IModule):
try:
iou_instance.iouyap = self._iouyap
iou_instance.iourc = self._iourc
if self._iourc:
iou_instance.iourc = self._iourc
else:
# if there is no IOURC file pushed by the client then use the server IOURC file
iou_instance.iourc = self._server_iourc_path
iou_instance.start()
except IOUError as e:
self.send_custom_error(str(e))
+9 -6
View File
@@ -509,7 +509,7 @@ class IOUDevice(object):
cwd=self._working_dir)
log.info("iouyap started PID={}".format(self._iouyap_process.pid))
except OSError as e:
except (OSError, subprocess.SubprocessError) as e:
iouyap_stdout = self.read_iouyap_stdout()
log.error("could not start iouyap: {}\n{}".format(e, iouyap_stdout))
raise IOUError("Could not start iouyap: {}\n{}".format(e, iouyap_stdout))
@@ -521,7 +521,7 @@ class IOUDevice(object):
try:
output = subprocess.check_output(["ldd", self._path])
except (FileNotFoundError, subprocess.CalledProcessError) as e:
except (FileNotFoundError, subprocess.SubprocessError) as e:
log.warn("could not determine the shared library dependencies for {}: {}".format(self._path, e))
return
@@ -539,7 +539,10 @@ class IOUDevice(object):
if not self.is_running():
if not os.path.isfile(self._path) or not os.path.exists(self._path):
raise IOUError("IOU image '{}' is not accessible".format(self._path))
if os.path.islink(self._path):
raise IOUError("IOU image '{}' linked to '{}' is not accessible".format(self._path, os.path.realpath(self._path)))
else:
raise IOUError("IOU image '{}' is not accessible".format(self._path))
try:
with open(self._path, "rb") as f:
@@ -559,7 +562,7 @@ class IOUDevice(object):
self._library_check()
if not self._iourc or not os.path.isfile(self._iourc):
raise IOUError("A iourc file is necessary to start IOU")
raise IOUError("A valid iourc file is necessary to start IOU")
if not self._iouyap or not os.path.isfile(self._iouyap):
raise IOUError("iouyap is necessary to start IOU")
@@ -583,7 +586,7 @@ class IOUDevice(object):
self._started = True
except FileNotFoundError as e:
raise IOUError("could not start IOU: {}: 32-bit binary support is probably not installed".format(e))
except OSError as e:
except (OSError, subprocess.SubprocessError) as e:
iou_stdout = self.read_iou_stdout()
log.error("could not start IOU {}: {}\n{}".format(self._path, e, iou_stdout))
raise IOUError("could not start IOU {}: {}\n{}".format(self._path, e, iou_stdout))
@@ -761,7 +764,7 @@ class IOUDevice(object):
command.extend(["-l"])
else:
raise IOUError("layer 1 keepalive messages are not supported by {}".format(os.path.basename(self._path)))
except (OSError, subprocess.CalledProcessError) as e:
except (OSError, subprocess.SubprocessError) as e:
log.warn("could not determine if layer 1 keepalive messages are supported by {}: {}".format(os.path.basename(self._path), e))
def _build_command(self):
+32 -8
View File
@@ -67,11 +67,14 @@ class Qemu(IModule):
qemu_config = config.get_section_config(name.upper())
self._console_start_port_range = qemu_config.get("console_start_port_range", 5001)
self._console_end_port_range = qemu_config.get("console_end_port_range", 5500)
self._monitor_start_port_range = qemu_config.get("monitor_start_port_range", 5501)
self._monitor_end_port_range = qemu_config.get("monitor_end_port_range", 6000)
self._allocated_udp_ports = []
self._udp_start_port_range = qemu_config.get("udp_start_port_range", 40001)
self._udp_end_port_range = qemu_config.get("udp_end_port_range", 45500)
self._host = qemu_config.get("host", kwargs["host"])
self._console_host = qemu_config.get("console_host", kwargs["console_host"])
self._monitor_host = qemu_config.get("monitor_host", "127.0.0.1")
self._projects_dir = kwargs["projects_dir"]
self._tempdir = kwargs["temp_dir"]
self._working_dir = self._projects_dir
@@ -137,6 +140,8 @@ class Qemu(IModule):
- project_name
- console_start_port_range
- console_end_port_range
- monitor_start_port_range
- monitor_end_port_range
- udp_start_port_range
- udp_end_port_range
@@ -174,6 +179,10 @@ class Qemu(IModule):
self._console_start_port_range = request["console_start_port_range"]
self._console_end_port_range = request["console_end_port_range"]
if "monitor_start_port_range" in request and "monitor_end_port_range" in request:
self._monitor_start_port_range = request["monitor_start_port_range"]
self._monitor_end_port_range = request["monitor_end_port_range"]
if "udp_start_port_range" in request and "udp_end_port_range" in request:
self._udp_start_port_range = request["udp_start_port_range"]
self._udp_end_port_range = request["udp_end_port_range"]
@@ -191,6 +200,7 @@ class Qemu(IModule):
Optional request parameters:
- console (QEMU VM console port)
- monitor (QEMU VM monitor port)
Response parameters:
- id (QEMU VM instance identifier)
@@ -207,6 +217,7 @@ class Qemu(IModule):
name = request["name"]
qemu_path = request["qemu_path"]
console = request.get("console")
monitor = request.get("monitor")
qemu_id = request.get("qemu_id")
try:
@@ -218,7 +229,11 @@ class Qemu(IModule):
console,
self._console_host,
self._console_start_port_range,
self._console_end_port_range)
self._console_end_port_range,
monitor,
self._monitor_host,
self._monitor_start_port_range,
self._monitor_end_port_range)
except QemuError as e:
self.send_custom_error(str(e))
@@ -601,14 +616,14 @@ class Qemu(IModule):
if sys.platform.startswith("win"):
return ""
try:
output = subprocess.check_output([qemu_path, "--version"])
match = re.search("QEMU emulator version ([0-9a-z\-\.]+)", output.decode("utf-8"))
output = subprocess.check_output([qemu_path, "-version"])
match = re.search("version\s+([0-9a-z\-\.]+)", output.decode("utf-8"))
if match:
version = match.group(1)
return version
else:
raise QemuError("Could not determine the Qemu version for {}".format(qemu_path))
except (OSError, subprocess.CalledProcessError) as e:
except subprocess.SubprocessError as e:
raise QemuError("Error while looking for the Qemu version: {}".format(e))
@IModule.route("qemu.qemu_list")
@@ -625,7 +640,13 @@ class Qemu(IModule):
# look for Qemu binaries in the current working directory and $PATH
if sys.platform.startswith("win"):
# add specific Windows paths
paths.append(os.path.join(os.getcwd(), "qemu"))
if hasattr(sys, "frozen"):
# add any qemu dir in the same location as gns3server.exe to the list of paths
exec_dir = os.path.dirname(os.path.abspath(sys.executable))
for f in os.listdir(exec_dir):
if f.lower().startswith("qemu"):
paths.append(os.path.join(exec_dir, f))
if "PROGRAMFILES(X86)" in os.environ and os.path.exists(os.environ["PROGRAMFILES(X86)"]):
paths.append(os.path.join(os.environ["PROGRAMFILES(X86)"], "qemu"))
if "PROGRAMFILES" in os.environ and os.path.exists(os.environ["PROGRAMFILES"]):
@@ -633,15 +654,18 @@ class Qemu(IModule):
elif sys.platform.startswith("darwin"):
# add specific locations on Mac OS X regardless of what's in $PATH
paths.extend(["/usr/local/bin", "/opt/local/bin"])
if hasattr(sys, "frozen"):
paths.append(os.path.abspath(os.path.join(os.getcwd(), "../../../qemu/bin/")))
for path in paths:
try:
for f in os.listdir(path):
if (f.startswith("qemu-system") or f == "qemu" or f == "qemu.exe") and os.access(os.path.join(path, f), os.X_OK):
if (f.startswith("qemu-system") or f == "qemu" or f == "qemu.exe") and \
os.access(os.path.join(path, f), os.X_OK) and \
os.path.isfile(os.path.join(path, f)):
qemu_path = os.path.join(path, f)
version = self._get_qemu_version(qemu_path)
qemus.append({"path": qemu_path, "version": version})
except (OSError, QemuError) as e:
log.warn("Could not find QEMU version for {}: {}".format(path, e))
except OSError:
continue
response = {"qemus": qemus}
+402 -20
View File
@@ -19,12 +19,16 @@
QEMU VM instance.
"""
import sys
import os
import shutil
import random
import subprocess
import shlex
import ntpath
import telnetlib
import time
import re
from gns3server.config import Config
from gns3dms.cloud.rackspace_ctrl import get_provider
@@ -51,10 +55,15 @@ class QemuVM(object):
:param console_host: IP address to bind for console connections
:param console_start_port_range: TCP console port range start
:param console_end_port_range: TCP console port range end
:param monitor: TCP monitor port
:param monitor_host: IP address to bind for monitor connections
:param monitor_start_port_range: TCP monitor port range start
:param monitor_end_port_range: TCP monitor port range end
"""
_instances = []
_allocated_console_ports = []
_allocated_monitor_ports = []
def __init__(self,
name,
@@ -65,7 +74,11 @@ class QemuVM(object):
console=None,
console_host="0.0.0.0",
console_start_port_range=5001,
console_end_port_range=5500):
console_end_port_range=5500,
monitor=None,
monitor_host="0.0.0.0",
monitor_start_port_range=5501,
monitor_end_port_range=6000):
if not qemu_id:
self._id = 0
@@ -89,10 +102,14 @@ class QemuVM(object):
self._command = []
self._started = False
self._process = None
self._cpulimit_process = None
self._stdout_file = ""
self._console_host = console_host
self._console_start_port_range = console_start_port_range
self._console_end_port_range = console_end_port_range
self._monitor_host = monitor_host
self._monitor_start_port_range = monitor_start_port_range
self._monitor_end_port_range = monitor_end_port_range
self._cloud_path = None
# QEMU settings
@@ -102,11 +119,15 @@ class QemuVM(object):
self._options = ""
self._ram = 256
self._console = console
self._monitor = monitor
self._ethernet_adapters = []
self._adapter_type = "e1000"
self._initrd = ""
self._kernel_image = ""
self._kernel_command_line = ""
self._legacy_networking = False
self._cpu_throttling = 0 # means no CPU throttling
self._process_priority = "low"
working_dir_path = os.path.join(working_dir, "qemu", "vm-{}".format(self._id))
@@ -130,6 +151,20 @@ class QemuVM(object):
raise QemuError("Console port {} is already used by another QEMU VM".format(console))
self._allocated_console_ports.append(self._console)
if not self._monitor:
# allocate a monitor port
try:
self._monitor = find_unused_port(self._monitor_start_port_range,
self._monitor_end_port_range,
self._monitor_host,
ignore_ports=self._allocated_monitor_ports)
except Exception as e:
raise QemuError(e)
if self._monitor in self._allocated_monitor_ports:
raise QemuError("Monitor port {} is already used by another QEMU VM".format(monitor))
self._allocated_monitor_ports.append(self._monitor)
self.adapters = 1 # creates 1 adapter by default
log.info("QEMU VM {name} [id={id}] has been created".format(name=self._name,
id=self._id))
@@ -150,9 +185,14 @@ class QemuVM(object):
"adapters": self.adapters,
"adapter_type": self._adapter_type,
"console": self._console,
"monitor": self._monitor,
"initrd": self._initrd,
"kernel_image": self._kernel_image,
"kernel_command_line": self._kernel_command_line}
"kernel_command_line": self._kernel_command_line,
"legacy_networking": self._legacy_networking,
"cpu_throttling": self._cpu_throttling,
"process_priority": self._process_priority
}
return qemu_defaults
@@ -174,6 +214,7 @@ class QemuVM(object):
cls._instances.clear()
cls._allocated_console_ports.clear()
cls._allocated_monitor_ports.clear()
@property
def name(self):
@@ -258,6 +299,35 @@ class QemuVM(object):
id=self._id,
port=console))
@property
def monitor(self):
"""
Returns the TCP monitor port.
:returns: monitor port (integer)
"""
return self._monitor
@monitor.setter
def monitor(self, monitor):
"""
Sets the TCP monitor port.
:param monitor: monitor port (integer)
"""
if monitor in self._allocated_monitor_ports:
raise QemuError("Monitor port {} is already used by another QEMU VM".format(monitor))
self._allocated_monitor_ports.remove(self._monitor)
self._monitor = monitor
self._allocated_monitor_ports.append(self._monitor)
log.info("QEMU VM {name} [id={id}]: monitor port set to {port}".format(name=self._name,
id=self._id,
port=monitor))
def delete(self):
"""
Deletes this QEMU VM.
@@ -267,8 +337,11 @@ class QemuVM(object):
if self._id in self._instances:
self._instances.remove(self._id)
if self.console and self.console in self._allocated_console_ports:
self._allocated_console_ports.remove(self.console)
if self._console and self._console in self._allocated_console_ports:
self._allocated_console_ports.remove(self._console)
if self._monitor and self._monitor in self._allocated_monitor_ports:
self._allocated_monitor_ports.remove(self._monitor)
log.info("QEMU VM {name} [id={id}] has been deleted".format(name=self._name,
id=self._id))
@@ -282,8 +355,11 @@ class QemuVM(object):
if self._id in self._instances:
self._instances.remove(self._id)
if self.console:
self._allocated_console_ports.remove(self.console)
if self._console:
self._allocated_console_ports.remove(self._console)
if self._monitor:
self._allocated_monitor_ports.remove(self._monitor)
try:
shutil.rmtree(self._working_dir)
@@ -437,6 +513,80 @@ class QemuVM(object):
id=self._id,
adapter_type=adapter_type))
@property
def legacy_networking(self):
"""
Returns either QEMU legacy networking commands are used.
:returns: boolean
"""
return self._legacy_networking
@legacy_networking.setter
def legacy_networking(self, legacy_networking):
"""
Sets either QEMU legacy networking commands are used.
:param legacy_networking: boolean
"""
if legacy_networking:
log.info("QEMU VM {name} [id={id}] has enabled legacy networking".format(name=self._name, id=self._id))
else:
log.info("QEMU VM {name} [id={id}] has disabled legacy networking".format(name=self._name, id=self._id))
self._legacy_networking = legacy_networking
@property
def cpu_throttling(self):
"""
Returns the percentage of CPU allowed.
:returns: integer
"""
return self._cpu_throttling
@cpu_throttling.setter
def cpu_throttling(self, cpu_throttling):
"""
Sets the percentage of CPU allowed.
:param cpu_throttling: integer
"""
log.info("QEMU VM {name} [id={id}] has set the percentage of CPU allowed to {cpu}".format(name=self._name,
id=self._id,
cpu=cpu_throttling))
self._cpu_throttling = cpu_throttling
self._stop_cpulimit()
if cpu_throttling:
self._set_cpu_throttling()
@property
def process_priority(self):
"""
Returns the process priority.
:returns: string
"""
return self._process_priority
@process_priority.setter
def process_priority(self, process_priority):
"""
Sets the process priority.
:param process_priority: string
"""
log.info("QEMU VM {name} [id={id}] has set the process priority to {priority}".format(name=self._name,
id=self._id,
priority=process_priority))
self._process_priority = process_priority
@property
def ram(self):
"""
@@ -552,12 +702,96 @@ class QemuVM(object):
kernel_command_line=kernel_command_line))
self._kernel_command_line = kernel_command_line
def _set_process_priority(self):
"""
Changes the process priority
"""
if sys.platform.startswith("win"):
try:
import win32api
import win32con
import win32process
except ImportError:
log.error("pywin32 must be installed to change the priority class for QEMU VM {}".format(self._name))
else:
log.info("setting QEMU VM {} priority class to BELOW_NORMAL".format(self._name))
handle = win32api.OpenProcess(win32con.PROCESS_ALL_ACCESS, 0, self._process.pid)
if self._process_priority == "realtime":
priority = win32process.REALTIME_PRIORITY_CLASS
elif self._process_priority == "very high":
priority = win32process.HIGH_PRIORITY_CLASS
elif self._process_priority == "high":
priority = win32process.ABOVE_NORMAL_PRIORITY_CLASS
elif self._process_priority == "low":
priority = win32process.BELOW_NORMAL_PRIORITY_CLASS
elif self._process_priority == "very low":
priority = win32process.IDLE_PRIORITY_CLASS
else:
priority = win32process.NORMAL_PRIORITY_CLASS
win32process.SetPriorityClass(handle, priority)
else:
if self._process_priority == "realtime":
priority = -20
elif self._process_priority == "very high":
priority = -15
elif self._process_priority == "high":
priority = -5
elif self._process_priority == "low":
priority = 5
elif self._process_priority == "very low":
priority = 19
else:
priority = 0
try:
subprocess.call(['renice', '-n', str(priority), '-p', str(self._process.pid)])
except (OSError, subprocess.SubprocessError) as e:
log.error("could not change process priority for QEMU VM {}: {}".format(self._name, e))
def _stop_cpulimit(self):
"""
Stops the cpulimit process.
"""
if self._cpulimit_process and self._cpulimit_process.poll() is None:
self._cpulimit_process.kill()
try:
self._process.wait(3)
except subprocess.TimeoutExpired:
log.error("could not kill cpulimit process {}".format(self._cpulimit_process.pid))
def _set_cpu_throttling(self):
"""
Limits the CPU usage for current QEMU process.
"""
if not self.is_running():
return
try:
if sys.platform.startswith("win") and hasattr(sys, "frozen"):
cpulimit_exec = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), "cpulimit", "cpulimit.exe")
else:
cpulimit_exec = "cpulimit"
subprocess.Popen([cpulimit_exec, "--lazy", "--pid={}".format(self._process.pid), "--limit={}".format(self._cpu_throttling)], cwd=self._working_dir)
log.info("CPU throttled to {}%".format(self._cpu_throttling))
except FileNotFoundError:
raise QemuError("cpulimit could not be found, please install it or deactivate CPU throttling")
except (OSError, subprocess.SubprocessError) as e:
raise QemuError("Could not throttle CPU: {}".format(e))
def start(self):
"""
Starts this QEMU VM.
"""
if not self.is_running():
if self.is_running():
# resume the VM if it is paused
self.resume()
return
else:
if not os.path.isfile(self._qemu_path) or not os.path.exists(self._qemu_path):
found = False
@@ -600,6 +834,29 @@ class QemuVM(object):
log.debug("Download of {} complete.".format(src))
self.hdb_disk_image = dst
if self.initrd != "":
_, filename = ntpath.split(self.initrd)
src = '{}/{}'.format(self.cloud_path, filename)
dst = os.path.join(self.working_dir, filename)
if not os.path.isfile(dst):
cloud_settings = Config.instance().cloud_settings()
provider = get_provider(cloud_settings)
log.debug("Downloading file from {} to {}...".format(src, dst))
provider.download_file(src, dst)
log.debug("Download of {} complete.".format(src))
self.initrd = dst
if self.kernel_image != "":
_, filename = ntpath.split(self.kernel_image)
src = '{}/{}'.format(self.cloud_path, filename)
dst = os.path.join(self.working_dir, filename)
if not os.path.isfile(dst):
cloud_settings = Config.instance().cloud_settings()
provider = get_provider(cloud_settings)
log.debug("Downloading file from {} to {}...".format(src, dst))
provider.download_file(src, dst)
log.debug("Download of {} complete.".format(src))
self.kernel_image = dst
self._command = self._build_command()
try:
log.info("starting QEMU: {}".format(self._command))
@@ -612,11 +869,15 @@ class QemuVM(object):
cwd=self._working_dir)
log.info("QEMU VM instance {} started PID={}".format(self._id, self._process.pid))
self._started = True
except OSError as e:
except (OSError, subprocess.SubprocessError) as e:
stdout = self.read_stdout()
log.error("could not start QEMU {}: {}\n{}".format(self._qemu_path, e, stdout))
raise QemuError("could not start QEMU {}: {}\n{}".format(self._qemu_path, e, stdout))
self._set_process_priority()
if self._cpu_throttling:
self._set_cpu_throttling()
def stop(self):
"""
Stops this QEMU VM.
@@ -635,27 +896,88 @@ class QemuVM(object):
self._process.pid))
self._process = None
self._started = False
self._stop_cpulimit()
def _control_vm(self, command, expected=None, timeout=30):
"""
Executes a command with QEMU monitor when this VM is running.
:param command: QEMU monitor command (e.g. info status, stop etc.)
:param timeout: how long to wait for QEMU monitor
:returns: result of the command (Match object or None)
"""
result = None
if self.is_running() and self._monitor:
log.debug("Execute QEMU monitor command: {}".format(command))
try:
tn = telnetlib.Telnet(self._monitor_host, self._monitor, timeout=timeout)
except OSError as e:
log.warn("Could not connect to QEMU monitor: {}".format(e))
return result
try:
tn.write(command.encode('ascii') + b"\n")
time.sleep(0.1)
except OSError as e:
log.warn("Could not write to QEMU monitor: {}".format(e))
tn.close()
return result
if expected:
try:
ind, match, dat = tn.expect(list=expected, timeout=timeout)
if match:
result = match
except EOFError as e:
log.warn("Could not read from QEMU monitor: {}".format(e))
tn.close()
return result
def _get_vm_status(self):
"""
Returns this VM suspend status (running|paused)
:returns: status (string)
"""
result = None
match = self._control_vm("info status", [b"running", b"paused"])
if match:
result = match.group(0).decode('ascii')
return result
def suspend(self):
"""
Suspends this QEMU VM.
"""
pass
vm_status = self._get_vm_status()
if vm_status == "running":
self._control_vm("stop")
log.debug("QEMU VM has been suspended")
else:
log.info("QEMU VM is not running to be suspended, current status is {}".format(vm_status))
def reload(self):
"""
Reloads this QEMU VM.
"""
pass
self._control_vm("system_reset")
log.debug("QEMU VM has been reset")
def resume(self):
"""
Resumes this QEMU VM.
"""
pass
vm_status = self._get_vm_status()
if vm_status == "paused":
self._control_vm("cont")
log.debug("QEMU VM has been resumed")
else:
log.info("QEMU VM is not paused to be resumed, current status is {}".format(vm_status))
def port_add_nio_binding(self, adapter_id, nio):
"""
@@ -671,6 +993,25 @@ class QemuVM(object):
raise QemuError("Adapter {adapter_id} doesn't exist on QEMU VM {name}".format(name=self._name,
adapter_id=adapter_id))
if self.is_running():
# dynamically configure an UDP tunnel on the QEMU VM adapter
if nio and isinstance(nio, NIO_UDP):
if self._legacy_networking:
self._control_vm("host_net_remove {} gns3-{}".format(adapter_id, adapter_id))
self._control_vm("host_net_add udp vlan={},name=gns3-{},sport={},dport={},daddr={}".format(adapter_id,
adapter_id,
nio.lport,
nio.rport,
nio.rhost))
else:
self._control_vm("host_net_remove {} gns3-{}".format(adapter_id, adapter_id))
self._control_vm("host_net_add socket vlan={},name=gns3-{},udp={}:{},localaddr={}:{}".format(adapter_id,
adapter_id,
nio.rhost,
nio.rport,
self._host,
nio.lport))
adapter.add_nio(0, nio)
log.info("QEMU VM {name} [id={id}]: {nio} added to adapter {adapter_id}".format(name=self._name,
id=self._id,
@@ -692,6 +1033,11 @@ class QemuVM(object):
raise QemuError("Adapter {adapter_id} doesn't exist on QEMU VM {name}".format(name=self._name,
adapter_id=adapter_id))
if self.is_running():
# dynamically disable the QEMU VM adapter
self._control_vm("host_net_remove {} gns3-{}".format(adapter_id, adapter_id))
self._control_vm("host_net_add user vlan={},name=gns3-{}".format(adapter_id, adapter_id))
nio = adapter.get_nio(0)
adapter.remove_nio(0)
log.info("QEMU VM {name} [id={id}]: {nio} removed from adapter {adapter_id}".format(name=self._name,
@@ -752,6 +1098,13 @@ class QemuVM(object):
else:
return []
def _monitor_options(self):
if self._monitor:
return ["-monitor", "telnet:{}:{},server,nowait".format(self._monitor_host, self._monitor)]
else:
return []
def _disk_options(self):
options = []
@@ -769,6 +1122,11 @@ class QemuVM(object):
try:
if self._hda_disk_image:
if not os.path.isfile(self._hda_disk_image) or not os.path.exists(self._hda_disk_image):
if os.path.islink(self._hda_disk_image):
raise QemuError("hda disk image '{}' linked to '{}' is not accessible".format(self._hda_disk_image, os.path.realpath(self._hda_disk_image)))
else:
raise QemuError("hda disk image '{}' is not accessible".format(self._hda_disk_image))
hda_disk = os.path.join(self._working_dir, "hda_disk.qcow2")
if not os.path.exists(hda_disk):
retcode = subprocess.call([qemu_img_path, "create", "-o",
@@ -782,11 +1140,16 @@ class QemuVM(object):
retcode = subprocess.call([qemu_img_path, "create", "-f", "qcow2", hda_disk, "128M"])
log.info("{} returned with {}".format(qemu_img_path, retcode))
except OSError as e:
except (OSError, subprocess.SubprocessError) as e:
raise QemuError("Could not create disk image {}".format(e))
options.extend(["-hda", hda_disk])
if self._hdb_disk_image:
if not os.path.isfile(self._hdb_disk_image) or not os.path.exists(self._hdb_disk_image):
if os.path.islink(self._hdb_disk_image):
raise QemuError("hdb disk image '{}' linked to '{}' is not accessible".format(self._hdb_disk_image, os.path.realpath(self._hdb_disk_image)))
else:
raise QemuError("hdb disk image '{}' is not accessible".format(self._hdb_disk_image))
hdb_disk = os.path.join(self._working_dir, "hdb_disk.qcow2")
if not os.path.exists(hdb_disk):
try:
@@ -794,7 +1157,7 @@ class QemuVM(object):
"backing_file={}".format(self._hdb_disk_image),
"-f", "qcow2", hdb_disk])
log.info("{} returned with {}".format(qemu_img_path, retcode))
except OSError as e:
except (OSError, subprocess.SubprocessError) as e:
raise QemuError("Could not create disk image {}".format(e))
options.extend(["-hdb", hdb_disk])
@@ -804,8 +1167,18 @@ class QemuVM(object):
options = []
if self._initrd:
if not os.path.isfile(self._initrd) or not os.path.exists(self._initrd):
if os.path.islink(self._initrd):
raise QemuError("initrd file '{}' linked to '{}' is not accessible".format(self._initrd, os.path.realpath(self._initrd)))
else:
raise QemuError("initrd file '{}' is not accessible".format(self._initrd))
options.extend(["-initrd", self._initrd])
if self._kernel_image:
if not os.path.isfile(self._kernel_image) or not os.path.exists(self._kernel_image):
if os.path.islink(self._kernel_image):
raise QemuError("kernel image '{}' linked to '{}' is not accessible".format(self._kernel_image, os.path.realpath(self._kernel_image)))
else:
raise QemuError("kernel image '{}' is not accessible".format(self._kernel_image))
options.extend(["-kernel", self._kernel_image])
if self._kernel_command_line:
options.extend(["-append", self._kernel_command_line])
@@ -819,16 +1192,24 @@ class QemuVM(object):
for adapter in self._ethernet_adapters:
#TODO: let users specify a base mac address
mac = "00:00:ab:%02x:%02x:%02d" % (random.randint(0x00, 0xff), random.randint(0x00, 0xff), adapter_id)
network_options.extend(["-device", "{},mac={},netdev=gns3-{}".format(self._adapter_type, mac, adapter_id)])
network_options.extend(["-net", "nic,vlan={},macaddr={},model={}".format(adapter_id, mac, self._adapter_type)])
nio = adapter.get_nio(0)
if nio and isinstance(nio, NIO_UDP):
network_options.extend(["-netdev", "socket,id=gns3-{},udp={}:{},localaddr={}:{}".format(adapter_id,
nio.rhost,
nio.rport,
self._host,
nio.lport)])
if self._legacy_networking:
network_options.extend(["-net", "udp,vlan={},name=gns3-{},sport={},dport={},daddr={}".format(adapter_id,
adapter_id,
nio.lport,
nio.rport,
nio.rhost)])
else:
network_options.extend(["-net", "socket,vlan={},name=gns3-{},udp={}:{},localaddr={}:{}".format(adapter_id,
adapter_id,
nio.rhost,
nio.rport,
self._host,
nio.lport)])
else:
network_options.extend(["-netdev", "user,id=gns3-{}".format(adapter_id)])
network_options.extend(["-net", "user,vlan={},name=gns3-{}".format(adapter_id, adapter_id)])
adapter_id += 1
return network_options
@@ -845,6 +1226,7 @@ class QemuVM(object):
command.extend(self._disk_options())
command.extend(self._linux_boot_options())
command.extend(self._serial_options())
command.extend(self._monitor_options())
additional_options = self._options.strip()
if additional_options:
command.extend(shlex.split(additional_options))
+35 -4
View File
@@ -41,6 +41,12 @@ QEMU_CREATE_SCHEMA = {
"maximum": 65535,
"type": "integer"
},
"monitor": {
"description": "monitor TCP port",
"minimum": 1,
"maximum": 65535,
"type": "integer"
},
},
"additionalProperties": False,
"required": ["name", "qemu_path"],
@@ -95,7 +101,7 @@ QEMU_UPDATE_SCHEMA = {
"description": "number of adapters",
"type": "integer",
"minimum": 0,
"maximum": 8,
"maximum": 32,
},
"adapter_type": {
"description": "QEMU adapter type",
@@ -108,6 +114,12 @@ QEMU_UPDATE_SCHEMA = {
"maximum": 65535,
"type": "integer"
},
"monitor": {
"description": "monitor TCP port",
"minimum": 1,
"maximum": 65535,
"type": "integer"
},
"initrd": {
"description": "QEMU initrd path",
"type": "string",
@@ -124,8 +136,27 @@ QEMU_UPDATE_SCHEMA = {
"description": "Path to the image in the cloud object store",
"type": "string",
},
"legacy_networking": {
"description": "Use QEMU legagy networking commands (-net syntax)",
"type": "boolean",
},
"cpu_throttling": {
"description": "Percentage of CPU allowed for QEMU",
"minimum": 0,
"maximum": 800,
"type": "integer",
},
"process_priority": {
"description": "Process priority for QEMU",
"enum": ["realtime",
"very high",
"high",
"normal",
"low",
"very low"]
},
"options": {
"description": "additional QEMU options",
"description": "Additional QEMU options",
"type": "string",
},
},
@@ -350,7 +381,7 @@ QEMU_ADD_NIO_SCHEMA = {
"description": "Port number",
"type": "integer",
"minimum": 0,
"maximum": 8
"maximum": 32
},
"nio": {
"type": "object",
@@ -384,7 +415,7 @@ QEMU_DELETE_NIO_SCHEMA = {
"description": "Port number",
"type": "integer",
"minimum": 0,
"maximum": 8
"maximum": 32
},
},
"additionalProperties": False,
+34 -10
View File
@@ -62,8 +62,13 @@ class VirtualBox(IModule):
# get the vboxmanage location
self._vboxmanage_path = None
if sys.platform.startswith("win") and "VBOX_INSTALL_PATH" in os.environ:
self._vboxmanage_path = os.path.join(os.environ["VBOX_INSTALL_PATH"], "VBoxManage.exe")
if sys.platform.startswith("win"):
if "VBOX_INSTALL_PATH" in os.environ:
self._vboxmanage_path = os.path.join(os.environ["VBOX_INSTALL_PATH"], "VBoxManage.exe")
elif "VBOX_MSI_INSTALL_PATH" in os.environ:
self._vboxmanage_path = os.path.join(os.environ["VBOX_MSI_INSTALL_PATH"], "VBoxManage.exe")
elif sys.platform.startswith("darwin"):
self._vboxmanage_path = "/Applications/VirtualBox.app/Contents/MacOS/VBoxManage"
else:
config = Config.instance()
vbox_config = config.get_section_config(name.upper())
@@ -84,6 +89,8 @@ class VirtualBox(IModule):
elif not os.access(self._vboxmanage_path, os.X_OK):
log.warning("vboxmanage is not executable")
self._vbox_user = None
# a new process start when calling IModule
IModule.__init__(self, name, *args, **kwargs)
self._vbox_instances = {}
@@ -202,6 +209,9 @@ class VirtualBox(IModule):
if "vboxmanage_path" in request:
self._vboxmanage_path = request["vboxmanage_path"]
if "vbox_user" in request:
self._vbox_user = request["vbox_user"]
if "console_start_port_range" in request and "console_end_port_range" in request:
self._console_start_port_range = request["console_start_port_range"]
self._console_end_port_range = request["console_end_port_range"]
@@ -249,6 +259,7 @@ class VirtualBox(IModule):
raise VirtualBoxError("Could not find VBoxManage, is VirtualBox correctly installed?")
vbox_instance = VirtualBoxVM(self._vboxmanage_path,
self._vbox_user,
name,
vmname,
linked_clone,
@@ -706,7 +717,7 @@ class VirtualBox(IModule):
response = {"port_id": request["port_id"]}
self.send_response(response)
def _execute_vboxmanage(self, command):
def _execute_vboxmanage(self, user, command):
"""
Executes VBoxManage and return its result.
@@ -716,12 +727,14 @@ class VirtualBox(IModule):
"""
try:
result = subprocess.check_output(command, stderr=subprocess.STDOUT, universal_newlines=True, timeout=30)
except subprocess.CalledProcessError as e:
if not user.strip() or sys.platform.startswith("win"):
result = subprocess.check_output(command, stderr=subprocess.STDOUT, timeout=60)
else:
sudo_command = "sudo -i -u " + user.strip() + " " + " ".join(command)
result = subprocess.check_output(sudo_command, stderr=subprocess.STDOUT, shell=True, timeout=60)
except (OSError, subprocess.SubprocessError) as e:
raise VirtualBoxError("Could not execute VBoxManage {}".format(e))
except subprocess.TimeoutExpired:
raise VirtualBoxError("VBoxManage has timed out")
return result
return result.decode("utf-8", errors="ignore")
@IModule.route("virtualbox.vm_list")
def vm_list(self, request):
@@ -740,11 +753,16 @@ class VirtualBox(IModule):
else:
vboxmanage_path = self._vboxmanage_path
if request and "vbox_user" in request:
vbox_user = request["vbox_user"]
else:
vbox_user = self._vbox_user
if not vboxmanage_path or not os.path.exists(vboxmanage_path):
raise VirtualBoxError("Could not find VBoxManage, is VirtualBox correctly installed?")
command = [vboxmanage_path, "--nologo", "list", "vms"]
result = self._execute_vboxmanage(command)
result = self._execute_vboxmanage(vbox_user, command)
except VirtualBoxError as e:
self.send_custom_error(str(e))
return
@@ -753,7 +771,13 @@ class VirtualBox(IModule):
for line in result.splitlines():
vmname, uuid = line.rsplit(' ', 1)
vmname = vmname.strip('"')
extra_data = self._execute_vboxmanage([vboxmanage_path, "getextradata", vmname, "GNS3/Clone"]).strip()
if vmname == "<inaccessible>":
continue # ignore inaccessible VMs
try:
extra_data = self._execute_vboxmanage(vbox_user, [vboxmanage_path, "getextradata", vmname, "GNS3/Clone"]).strip()
except VirtualBoxError as e:
self.send_custom_error(str(e))
return
if not extra_data == "Value: yes":
vms.append(vmname)
+73 -45
View File
@@ -25,7 +25,6 @@ import re
import os
import subprocess
import tempfile
import shutil
import json
import socket
import time
@@ -64,6 +63,7 @@ class VirtualBoxVM(object):
def __init__(self,
vboxmanage_path,
vbox_user,
name,
vmname,
linked_clone,
@@ -95,6 +95,7 @@ class VirtualBoxVM(object):
self._working_dir = None
self._command = []
self._vboxmanage_path = vboxmanage_path
self._vbox_user = vbox_user
self._started = False
self._console_host = console_host
self._console_start_port_range = console_start_port_range
@@ -304,10 +305,10 @@ class VirtualBoxVM(object):
hdd_file = os.path.join(self._working_dir, self._vmname, "Snapshots", hdd_info["hdd"])
if os.path.exists(hdd_file):
log.debug("reattaching hdd {}".format(hdd_file))
self._storage_attach('--storagectl {} --port {} --device {} --type hdd --medium "{}"'.format(hdd_info["controller"],
hdd_info["port"],
hdd_info["device"],
hdd_file))
self._storage_attach('--storagectl "{}" --port {} --device {} --type hdd --medium "{}"'.format(hdd_info["controller"],
hdd_info["port"],
hdd_info["device"],
hdd_file))
def delete(self):
"""
@@ -327,13 +328,13 @@ class VirtualBoxVM(object):
hdd_files = self._get_all_hdd_files()
vm_info = self._get_vm_info()
for entry, value in vm_info.items():
match = re.search("^(\w+)\-(\d)\-(\d)$", entry)
match = re.search("^([\s\w]+)\-(\d)\-(\d)$", entry)
if match:
controller = match.group(1)
port = match.group(2)
device = match.group(3)
if value in hdd_files:
self._storage_attach("--storagectl {} --port {} --device {} --type hdd --medium none".format(controller, port, device))
self._storage_attach('--storagectl "{}" --port {} --device {} --type hdd --medium none'.format(controller, port, device))
hdd_table.append(
{
"hdd": os.path.basename(value),
@@ -428,8 +429,10 @@ class VirtualBoxVM(object):
if enable_remote_console:
log.info("VirtualBox VM {name} [id={id}] has enabled the console".format(name=self._name, id=self._id))
self._start_remote_console()
else:
log.info("VirtualBox VM {name} [id={id}] has disabled the console".format(name=self._name, id=self._id))
self._stop_remote_console()
self._enable_remote_console = enable_remote_console
@property
@@ -451,6 +454,8 @@ class VirtualBoxVM(object):
"""
log.info("VirtualBox VM {name} [id={id}] has set the VM name to {vmname}".format(name=self._name, id=self._id, vmname=vmname))
if self._linked_clone:
self._modify_vm('--name "{}"'.format(vmname))
self._vmname = vmname
@property
@@ -535,7 +540,7 @@ class VirtualBoxVM(object):
id=self._id,
adapter_type=adapter_type))
def _execute(self, subcommand, args, timeout=30):
def _execute(self, subcommand, args, timeout=60):
"""
Executes a command with VBoxManage.
@@ -549,18 +554,23 @@ class VirtualBoxVM(object):
command = [self._vboxmanage_path, "--nologo", subcommand]
command.extend(args)
log.debug("Execute vboxmanage command: {}".format(command))
user = self._vbox_user
try:
result = subprocess.check_output(command, stderr=subprocess.STDOUT, universal_newlines=True, timeout=timeout)
if not user.strip() or sys.platform.startswith("win") or sys.platform.startswith("darwin"):
result = subprocess.check_output(command, stderr=subprocess.STDOUT, timeout=timeout)
else:
sudo_command = "sudo -i -u " + user.strip() + " " + " ".join(command)
result = subprocess.check_output(sudo_command, stderr=subprocess.STDOUT, shell=True, timeout=timeout)
except subprocess.CalledProcessError as e:
if e.output:
# only the first line of the output is useful
virtualbox_error = e.output.splitlines()[0]
virtualbox_error = e.output.decode("utf-8").splitlines()[0]
raise VirtualBoxError("{}".format(virtualbox_error))
else:
raise VirtualBoxError("{}".format(e))
except subprocess.TimeoutExpired:
raise VirtualBoxError("VBoxManage has timed out")
return result.splitlines()
except (OSError, subprocess.SubprocessError) as e:
raise VirtualBoxError("Could not execute VBoxManage: {}".format(e))
return result.decode("utf-8", errors="ignore").splitlines()
def _get_vm_info(self):
"""
@@ -770,9 +780,56 @@ class VirtualBoxVM(object):
"--register"]
result = self._execute("clonevm", args)
log.debug("cloned VirtualBox VM: {}".format(result))
self._vmname = self._name
self._execute("setextradata", [self._vmname, "GNS3/Clone", "yes"])
log.debug("cloned VirtualBox VM: {}".format(result))
args = [self._name, "take", "reset"]
result = self._execute("snapshot", args)
log.debug("snapshot reset created: {}".format(result))
def _start_remote_console(self):
"""
Starts remote console support for this VM.
"""
# starts the Telnet to pipe thread
pipe_name = self._get_pipe_name()
if sys.platform.startswith('win'):
try:
self._serial_pipe = open(pipe_name, "a+b")
except OSError as e:
raise VirtualBoxError("Could not open the pipe {}: {}".format(pipe_name, e))
self._telnet_server_thread = TelnetServer(self._vmname, msvcrt.get_osfhandle(self._serial_pipe.fileno()), self._console_host, self._console)
self._telnet_server_thread.start()
else:
try:
self._serial_pipe = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self._serial_pipe.connect(pipe_name)
except OSError as e:
raise VirtualBoxError("Could not connect to the pipe {}: {}".format(pipe_name, e))
self._telnet_server_thread = TelnetServer(self._vmname, self._serial_pipe, self._console_host, self._console)
self._telnet_server_thread.start()
def _stop_remote_console(self):
"""
Stops remote console support for this VM.
"""
if self._telnet_server_thread:
self._telnet_server_thread.stop()
self._telnet_server_thread.join(timeout=3)
if self._telnet_server_thread.isAlive():
log.warn("Serial pire thread is still alive!")
self._telnet_server_thread = None
if self._serial_pipe:
if sys.platform.startswith('win'):
win32file.CloseHandle(msvcrt.get_osfhandle(self._serial_pipe.fileno()))
else:
self._serial_pipe.close()
self._serial_pipe = None
def start(self):
"""
@@ -805,43 +862,14 @@ class VirtualBoxVM(object):
self._execute("guestproperty", ["set", self._vmname, "ProjectDirInGNS3", self._working_dir])
if self._enable_remote_console:
# starts the Telnet to pipe thread
pipe_name = self._get_pipe_name()
if sys.platform.startswith('win'):
try:
self._serial_pipe = open(pipe_name, "a+b")
except OSError as e:
raise VirtualBoxError("Could not open the pipe {}: {}".format(pipe_name, e))
self._telnet_server_thread = TelnetServer(self._vmname, msvcrt.get_osfhandle(self._serial_pipe.fileno()), self._console_host, self._console)
self._telnet_server_thread.start()
else:
try:
self._serial_pipe = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self._serial_pipe.connect(pipe_name)
except OSError as e:
raise VirtualBoxError("Could not connect to the pipe {}: {}".format(pipe_name, e))
self._telnet_server_thread = TelnetServer(self._vmname, self._serial_pipe, self._console_host, self._console)
self._telnet_server_thread.start()
self._start_remote_console()
def stop(self):
"""
Stops this VirtualBox VM.
"""
if self._telnet_server_thread:
self._telnet_server_thread.stop()
self._telnet_server_thread.join(timeout=3)
if self._telnet_server_thread.isAlive():
log.warn("Serial pire thread is still alive!")
self._telnet_server_thread = None
if self._serial_pipe:
if sys.platform.startswith('win'):
win32file.CloseHandle(msvcrt.get_osfhandle(self._serial_pipe.fileno()))
else:
self._serial_pipe.close()
self._serial_pipe = None
self._stop_remote_console()
vm_state = self._get_vm_state()
if vm_state == "running" or vm_state == "paused" or vm_state == "stuck":
# power off the VM
+2 -2
View File
@@ -346,7 +346,7 @@ class VPCSDevice(object):
raise VPCSError("VPCS executable version must be >= 0.5b1")
else:
raise VPCSError("Could not determine the VPCS version for {}".format(self._path))
except (OSError, subprocess.CalledProcessError) as e:
except (OSError, subprocess.SubprocessError) as e:
raise VPCSError("Error while looking for the VPCS version: {}".format(e))
def start(self):
@@ -386,7 +386,7 @@ class VPCSDevice(object):
creationflags=flags)
log.info("VPCS instance {} started PID={}".format(self._id, self._process.pid))
self._started = True
except OSError as e:
except (OSError, subprocess.SubprocessError) as e:
vpcs_stdout = self.read_vpcs_stdout()
log.error("could not start VPCS {}: {}\n{}".format(self._path, e, vpcs_stdout))
raise VPCSError("could not start VPCS {}: {}\n{}".format(self._path, e, vpcs_stdout))
+1 -1
View File
@@ -184,7 +184,6 @@ class Server(object):
except KeyError:
log.info("Missing cloud.conf - disabling HTTP auth and SSL")
router = self._create_zmq_router()
# Add our JSON-RPC Websocket handler to Tornado
self.handlers.extend([(r"/", JSONRPCWebSocket, dict(zmq_router=router))])
@@ -208,6 +207,7 @@ class Server(object):
if parse_version(tornado.version) >= parse_version("3.1"):
kwargs["max_buffer_size"] = 524288000 # 500 MB file upload limit
tornado_app.listen(self._port, **kwargs)
except OSError as e:
if e.errno == errno.EADDRINUSE: # socket already in use
+1 -1
View File
@@ -44,7 +44,7 @@ import uuid
SCRIPT_NAME = os.path.basename(__file__)
#Is the full path when used as an import
# This is the full path when used as an import
SCRIPT_PATH = os.path.dirname(__file__)
if not SCRIPT_PATH:
+2 -2
View File
@@ -23,5 +23,5 @@
# or negative for a release candidate or beta (after the base version
# number has been incremented)
__version__ = "1.2"
__version_info__ = (1, 2, 0, 0)
__version__ = "1.2.2"
__version_info__ = (1, 2, 2, 0)
+46
View File
@@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from ws4py.client.threadedclient import WebSocketClient
class WSClient(WebSocketClient):
def opened(self):
print("Connection successful with {}:{}".format(self.host, self.port))
self.send('{"jsonrpc": 2.0, "method": "dynamips.settings", "params": {"path": "/usr/local/bin/dynamips", "allocate_hypervisor_per_device": true, "working_dir": "/tmp/gns3-1b4grwm3-files", "udp_end_port_range": 20000, "sparse_memory_support": true, "allocate_hypervisor_per_ios_image": true, "aux_start_port_range": 2501, "use_local_server": true, "hypervisor_end_port_range": 7700, "aux_end_port_range": 3000, "mmap_support": true, "console_start_port_range": 2001, "console_end_port_range": 2500, "hypervisor_start_port_range": 7200, "ghost_ios_support": true, "memory_usage_limit_per_hypervisor": 1024, "jit_sharing_support": false, "udp_start_port_range": 10001}}')
self.send('{"jsonrpc": 2.0, "method": "dynamips.vm.create", "id": "e8caf5be-de3d-40dd-80b9-ab6df8029570", "params": {"image": "/home/grossmj/GNS3/images/IOS/c3725-advipservicesk9-mz.124-15.T14.image", "name": "R1", "platform": "c3725", "ram": 256}}')
def closed(self, code, reason=None):
print("Closed down. Code: {} Reason: {}".format(code, reason))
def received_message(self, m):
print(m)
if len(m) == 175:
self.close(reason='Bye bye')
if __name__ == '__main__':
try:
ws = WSClient('ws://localhost:8000/', protocols=['http-only', 'chat'])
ws.connect()
ws.run_forever()
except KeyboardInterrupt:
ws.close()
+1 -1
View File
@@ -14,7 +14,7 @@ def test_port(hypervisor):
def test_host(hypervisor):
assert hypervisor.host == "127.0.0.1"
assert hypervisor.host == "0.0.0.0"
def test_working_dir(hypervisor):