Merge pull request: Move local project to cloud #168

This commit is contained in:
Jeremy
2014-12-15 17:00:23 -07:00
parent 868e9a322e
commit 2b2e45ca45
14 changed files with 575 additions and 187 deletions

View File

@@ -170,10 +170,7 @@ class BaseCloudCtrl(object):
def list_instances(self):
""" Return a list of instances in the current region. """
try:
return self.driver.list_nodes()
except Exception as e:
log.error("list_instances returned an error: {}".format(e))
return self.driver.list_nodes()
def create_key_pair(self, name):

View File

@@ -46,7 +46,10 @@ def ssh_client(host, key_string):
client.connect(hostname=host, username="root", pkey=key)
yield client
except socket_error as e:
log.error("SSH connection error to {}: {}".format(host, e))
log.debug("SSH connection socket error to {}: {}".format(host, e))
yield None
except Exception as e:
log.debug("SSH connection error to {}: {}".format(host, e))
yield None
finally:
client.close()
@@ -60,7 +63,7 @@ class ListInstancesThread(QtCore.QThread):
instancesReady = QtCore.pyqtSignal(object)
def __init__(self, parent, provider):
super(QtCore.QThread, self).__init__(parent)
super().__init__(parent)
self._provider = provider
def run(self):
@@ -79,7 +82,7 @@ class CreateInstanceThread(QtCore.QThread):
instanceCreated = QtCore.pyqtSignal(object, object)
def __init__(self, parent, provider, name, flavor_id, image_id):
super(QtCore.QThread, self).__init__(parent)
super().__init__(parent)
self._provider = provider
self._name = name
self._flavor_id = flavor_id
@@ -109,7 +112,7 @@ class DeleteInstanceThread(QtCore.QThread):
instanceDeleted = QtCore.pyqtSignal(object)
def __init__(self, parent, provider, instance):
super(QtCore.QThread, self).__init__(parent)
super().__init__(parent)
self._provider = provider
self._instance = instance
@@ -171,7 +174,7 @@ killall python3 gns3server gns3dms
'''
def __init__(self, parent, host, private_key_string, server_id, username, api_key, region, dead_time):
super(QtCore.QThread, self).__init__(parent)
super().__init__(parent)
self._host = host
self._private_key_string = private_key_string
self._server_id = server_id
@@ -250,7 +253,7 @@ class WSConnectThread(QtCore.QThread):
def __init__(self, parent, provider, server_id, host, port, ca_file,
auth_user, auth_password, ssh_pkey, instance_id):
super(QtCore.QThread, self).__init__(parent)
super().__init__(parent)
self._provider = provider
self._server_id = server_id
self._host = host
@@ -290,7 +293,7 @@ class UploadProjectThread(QtCore.QThread):
update = QtCore.pyqtSignal(int)
def __init__(self, parent, cloud_settings, project_path, images_path):
super(QtCore.QThread, self).__init__(parent)
super().__init__(parent)
self.cloud_settings = cloud_settings
self.project_path = project_path
self.images_path = images_path
@@ -356,26 +359,42 @@ class UploadProjectThread(QtCore.QThread):
class UploadFilesThread(QtCore.QThread):
"""
Upload multiple files to cloud files
Uploads files to cloud files
uploads - A list of 2-tuples of (local_src_path, remote_dst_path)
:param cloud_settings:
:param files_to_upload: list of tuples of (file path, file name to save in cloud)
"""
error = QtCore.pyqtSignal(str, bool)
completed = QtCore.pyqtSignal()
update = QtCore.pyqtSignal(int)
def __init__(self, parent, cloud_settings, uploads):
super(QtCore.QThread, self).__init__(parent)
def __init__(self, parent, cloud_settings, files_to_upload):
super().__init__(parent)
self._cloud_settings = cloud_settings
self._uploads = uploads
self._files_to_upload = files_to_upload
def run(self):
for src, dst in self._uploads:
log.debug('Upload from {} to {}'.format(src, dst))
provider = get_provider(self._cloud_settings)
provider.upload_file(src, dst)
log.debug('Upload image completed')
self.update.emit(0)
try:
for i, file_to_upload in enumerate(self._files_to_upload):
provider = get_provider(self._cloud_settings)
log.debug('Uploading image {} to cloud as {}'.format(file_to_upload[0], file_to_upload[1]))
provider.upload_file(file_to_upload[0], file_to_upload[1])
self.update.emit((i+1) * 100 / len(self._files_to_upload))
log.debug('Uploading image completed')
except Exception as e:
log.exception("Error uploading images to cloud")
self.error.emit("Error uploading images: {}".format(str(e)), True)
self.completed.emit()
def stop(self):
self.quit()
class DownloadProjectThread(QtCore.QThread):
"""
@@ -388,7 +407,7 @@ class DownloadProjectThread(QtCore.QThread):
update = QtCore.pyqtSignal(int)
def __init__(self, parent, cloud_project_file_name, project_dest_path, images_dest_path, cloud_settings):
super(QtCore.QThread, self).__init__(parent)
super().__init__(parent)
self.project_name = cloud_project_file_name
self.project_dest_path = project_dest_path
self.images_dest_path = images_dest_path
@@ -445,7 +464,7 @@ class DeleteProjectThread(QtCore.QThread):
update = QtCore.pyqtSignal(int)
def __init__(self, parent, project_file_name, cloud_settings):
super(QtCore.QThread, self).__init__(parent)
super().__init__(parent)
self.project_file_name = project_file_name
self.cloud_settings = cloud_settings

253
gns3/cloud_builder.py Normal file
View File

@@ -0,0 +1,253 @@
# -*- 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 PyQt4.QtCore import pyqtSignal
from PyQt4.QtCore import QThread
import ast
import logging
import os
import time
from .cloud.utils import ssh_client
from .cloud.exceptions import KeyPairExists
from .servers import Servers
from .topology import Topology
log = logging.getLogger(__name__)
class CloudBuilder(QThread):
"""
"""
# Notify with progress amount and instance_id
progressUpdate = pyqtSignal(object, str)
# Notify with current state and instance_id
stateChange = pyqtSignal(object, str)
# Notify when instance is ready with instance_id
buildComplete = pyqtSignal(str)
# Notify when the instance has been created with instance and keypair
instanceCreated = pyqtSignal(object, object)
# Notify when the public ip is available with ip and instance_id
instanceHasIP = pyqtSignal(str, str)
# Notify when instance id exists with builder and instance_id
instanceIdExists = pyqtSignal(object, str)
def __init__(self, parent, cloud_provider, ca_dir):
super(QThread, self).__init__(parent)
# Store our parent so it can be passed to threads we spawn.
self._parent = parent
self._provider = cloud_provider
self._ca_dir = ca_dir
self._start_at_create = False
self._start_at_setup = False
self._instance = None
def startAtCreate(self, instance_name, flavor_id, image_id):
self._start_at_create = True
self._instance_name = instance_name
self._flavor_id = flavor_id
self._image_id = image_id
def startAtSetup(self, instance, keypair):
self._start_at_setup = True
self._instance = instance
self._key_pair = keypair
def run(self):
try:
log.debug('CloudBuilder.run')
if self._start_at_create:
log.debug('CloudBuilder._start_at_create')
self._createInstance(self._provider, self._instance_name, self._flavor_id,
self._image_id)
log.debug('got here 3')
if self._start_at_setup:
log.debug('CloudBuilder start at setup')
self._instanceCreated(self._instance, self._key_pair)
except Exception:
log.exception("CloudBuilder trapped an exception:")
log.error('CloudBuilder stopped in error state.')
def _createInstance(self, provider, name, flavor_id, image_id):
log.debug("Creating cloud keypair with name {}".format(name))
key_pair = None
while key_pair is None:
try:
key_pair = provider.create_key_pair(name)
except KeyPairExists:
log.debug("Deleting old key pair with name {}.".format(name))
self._provider.delete_key_pair_by_name(name)
except Exception as e:
log.debug("create_key_pair exception {}".format(e))
log.debug("Creating cloud server with name {}".format(name))
instance = None
while instance is None:
try:
instance = self._provider.create_instance(name, flavor_id, image_id, key_pair)
except Exception as e:
log.debug("create_instance exception {}".format(e))
log.debug("Cloud server {} created".format(name))
self._instanceCreated(instance, key_pair)
def _instanceCreated(self, instance, key_pair):
log.debug('CloudBuilder._instanceCreated {}'.format(instance.id))
self._instance = instance
self._instance_id = instance.id
self._key_pair = key_pair
self.instanceIdExists.emit(self, instance.id)
self.instanceCreated.emit(instance, key_pair)
self._waitForPublicIP()
def _waitForPublicIP(self):
public_ip = None
while public_ip is None:
time.sleep(10)
try:
instance = self._provider.get_instance(self._instance)
# Look for public ip address
for ip in instance.public_ips:
# Don't use the ipv6 address
if ':' not in ip:
public_ip = ip
break
except Exception as e:
log.debug('list_instances error: {}'.format(e))
# updated info, keep it.
self._instance = instance
self._public_ip = public_ip
self.instanceHasIP.emit(self._public_ip, self._instance.id)
time.sleep(60)
self._startGNS3Server(1800)
def _startGNS3Server(self, dead_time):
commands = '''
DEBIAN_FRONTEND=noninteractive dpkg --configure -a
DEBIAN_FRONTEND=noninteractive dpkg --add-architecture i386
DEBIAN_FRONTEND=noninteractive apt-get -y update
DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confnew" --force-yes -fuy dist-upgrade
DEBIAN_FRONTEND=noninteractive apt-get -y install git python3-setuptools python3-netifaces python3-pip python3-zmq dynamips qemu-system
DEBIAN_FRONTEND=noninteractive apt-get -y install libc6:i386 libstdc++6:i386 libssl1.0.0:i386
ln -s /lib/i386-linux-gnu/libcrypto.so.1.0.0 /lib/i386-linux-gnu/libcrypto.so.4
mkdir -p /opt/gns3
cd /opt/gns3; git clone https://github.com/planctechnologies/gns3-server.git
cd /opt/gns3/gns3-server; git checkout dev; git pull
cd /opt/gns3/gns3-server; pip3 install -r dev-requirements.txt
cd /opt/gns3/gns3-server; python3 ./setup.py install
ln -sf /usr/bin/dynamips /usr/local/bin/dynamips
wget 'https://github.com/GNS3/iouyap/releases/download/0.95/iouyap.tar.gz'
tar xzf iouyap.tar.gz -C /usr/local/bin
python -c 'import struct; open("/etc/hostid", "w").write(struct.pack("i", 00000000))'
hostname gns3-iouvm # set hostname for iou
wget 'http://downloads.sourceforge.net/project/vpcs/0.6/vpcs_0.6_Linux64'
cp vpcs_0.6_Linux64 /usr/local/bin/vpcs
chmod a+x /usr/local/bin/vpcs
killall python3 gns3server gns3dms
'''
def exec_command(client, cmd, wait_time=-1):
cmd += '; exit $?'
stdout_data = b''
stderr_data = b''
log.debug('cmd: {}'.format(cmd))
# Send the command (non-blocking)
stdin, stdout, stderr = client.exec_command(cmd)
# Wait for the command to terminate
wait = int(wait_time)
while not stdout.channel.exit_status_ready() and wait != 0:
time.sleep(1)
wait -= 1
stdout_data = stdout.read()
stderr_data = stderr.read()
log.debug('exit status: {}'.format(stdout.channel.exit_status))
log.debug('stdout: {}'.format(stdout_data.decode('utf-8')))
log.debug('stderr: {}'.format(stderr_data.decode('utf-8')))
return stdout_data, stderr_data
# We might be attempting a connection before the instance is fully booted, so retry
# when the ssh connection fails.
ssh_connected = False
response = None
while not ssh_connected:
with ssh_client(self._public_ip, self._key_pair.private_key) as client:
if client is None:
time.sleep(1)
continue
ssh_connected = True
for cmd in [l for l in commands.splitlines() if l.strip()]:
exec_command(client, cmd)
data = {
'instance_id': self._instance_id,
'cloud_user_name': self._provider.username,
'cloud_api_key': self._provider.api_key,
'cloud_region': self._provider.region,
'dead_time': dead_time,
}
# TODO: Properly escape the data portion of the command line
start_cmd = '/usr/bin/python3 /opt/gns3/gns3-server/gns3server/start_server.py -d -v --ip={} --data="{}" 2>/tmp/gns3-stderr.log'.format(self._public_ip, data)
stdout, stderr = exec_command(client, start_cmd, wait_time=15)
response = stdout.decode('utf-8')
log.debug(response)
data = ast.literal_eval(response)
# TODO: have the server return the port it is running on
port = 8000
username = data['WEB_USERNAME']
password = data['WEB_PASSWORD']
ssl_cert = ''.join(data['SSL_CRT'])
ca_filename = 'cloud_server_{}.crt'.format(self._public_ip)
ca_dir = self._ca_dir
ca_file = os.path.join(ca_dir, ca_filename)
try:
os.makedirs(ca_dir)
except FileExistsError:
pass
with open(ca_file, 'wb') as ca_fh:
ca_fh.write(ssl_cert.encode('utf-8'))
topology = Topology.instance()
top_instance = topology.getInstance(self._instance_id)
top_instance.set_later_attributes(self._public_ip, port, ssl_cert, ca_file)
servers = Servers.instance()
server = servers.getCloudServer(self._public_ip, port, ca_file, username, password,
self._key_pair.private_key, self._instance_id)
servers.save()
log.debug('Cloud server gns3server started.')
self.buildComplete.emit(self._instance_id)

View File

@@ -1,17 +1,19 @@
# -*- coding: utf-8 -*-
import ast
from collections import namedtuple
import logging
import os
from .qt import QtCore, QtGui
from .cloud.utils import (ListInstancesThread, CreateInstanceThread, DeleteInstanceThread,
StartGNS3ServerThread, WSConnectThread)
from libcloud.compute.types import NodeState
from .qt import QtCore, QtGui
from .cloud.utils import (ListInstancesThread, DeleteInstanceThread)
from .topology import Topology
from .servers import Servers
# this widget was promoted on Creator, must use absolute imports
from gns3.ui.cloud_inspector_view_ui import Ui_CloudInspectorView
from gns3.cloud_builder import CloudBuilder
from gns3.cloud_instances import CloudInstances
log = logging.getLogger(__name__)
@@ -196,8 +198,8 @@ class CloudInspectorView(QtGui.QWidget, Ui_CloudInspectorView):
# map flavor ids to combobox indexes
self.flavor_index_id = []
# TODO: Delete me
self._running = {}
# A dictionary of {image_id, CloudBuilder}
self._builders = {}
def _get_flavor_index(self, flavor_id):
try:
@@ -205,17 +207,17 @@ class CloudInspectorView(QtGui.QWidget, Ui_CloudInspectorView):
except ValueError:
return -1
def load(self, main_win, instances):
def load(self, main_win, instance_ids):
"""
Fill the model data layer with instances retrieved through libcloud
Fill the model data layer with instance info loaded from the topology file
"""
self._main_window = main_win
self._provider = main_win.cloudProvider
self._settings = main_win.cloudSettings()
log.info('CloudInspectorView.load')
for i in instances:
self._project_instances_id.append(i["id"])
for instance_id in instance_ids:
self._project_instances_id.append(instance_id)
update_thread = ListInstancesThread(self, self._provider)
update_thread.instancesReady.connect(self._update_model)
@@ -310,95 +312,39 @@ class CloudInspectorView(QtGui.QWidget, Ui_CloudInspectorView):
update_thread.instancesReady.connect(self._update_model)
update_thread.start()
def _gns3server_started_slot(self, id, host_ip, start_response):
def _instanceBuilt(self, id):
"""
This slot is called when the StartGNS3ServerThread succesfully started
the server.
:param id: the id of the instance
:param host_ip: the host ip of the instance
:param start_response: the output of the server start script on the remote host
This slot is called when instance has finished building.
"""
# instance state transition: GNS3SERVER_STARTING --> GNS3SERVER_STARTED
instance = self._model.getInstanceById(id)
instance.state = RunningInstanceState.GNS3SERVER_STARTED
self._model.updateInstanceFields(instance, ['state'])
data = ast.literal_eval(start_response)
# TODO: have the server return the port it is running on
port = 8000
username = data['WEB_USERNAME']
password = data['WEB_PASSWORD']
ssl_cert = ''.join(data['SSL_CRT'])
ca_filename = 'cloud_server_{}.crt'.format(host_ip)
# TODO: Move this directory into projectSettings.
ca_dir = os.path.join(self._main_window.projectSettings()["project_files_dir"], "keys")
ca_file = os.path.join(ca_dir, ca_filename)
try:
os.makedirs(ca_dir)
except FileExistsError:
pass
with open(ca_file, 'wb') as ca_fh:
ca_fh.write(ssl_cert.encode('utf-8'))
topology = Topology.instance()
top_instance = topology.getInstance(id)
top_instance.set_later_attributes(host_ip, port, ssl_cert, ca_file)
ssh_pkey = top_instance.private_key
log.debug('Cloud server gns3server started.')
wss_thread = WSConnectThread(self, self._provider, id, host_ip, port, ca_file,
username, password, ssh_pkey, id)
wss_thread.established.connect(self._wss_connected_slot)
wss_thread.start()
def _wss_connected_slot(self, id):
"""
This slot is called when the WSConnectThread successfully connected to
the websocket on the remote host
"""
# instance state transition: GNS3SERVER_STARTED --> WS_CONNECTED
instance = self._model.getInstanceById(id)
instance.state = RunningInstanceState.WS_CONNECTED
self._model.updateInstanceFields(instance, ['state'])
def _get_public_ip(self, ip_list):
"""
Pick the ipv4 address from the list of ip addresses that the instance
has.
"""
for ip in ip_list:
log.debug('Cloud server ip {}'.format(ip))
# Don't use the ipv6 address
if ':' not in ip:
log.debug('Chose {} as public ip'.format(ip))
return ip
return None
def _update_model(self, instances):
if not instances:
return
# Filter instances to only those in the current project
project_instances = [i for i in instances if i.id in self._project_instances_id]
# populate underlying model if this is the first call
if self._model.rowCount() == 0 and len(instances) > 0:
self._populate_model(instances)
if self._model.rowCount() == 0 and len(project_instances) > 0:
self._populate_model(project_instances)
self._rebuild_instances(project_instances)
instance_manager = CloudInstances.instance()
instance_manager.update_instances(instances)
# filter instances to only those in the current project
project_instances = [i for i in instances if i.id in self._project_instances_id]
# cleanup removed instances
# Clean up removed instances
real = set(i.id for i in project_instances)
current = set(self._model.instanceIds)
for i in current.difference(real):
self._model.removeInstanceById(i)
self.uiInstancesTableView.resizeColumnsToContents()
# Update instance status
for i in project_instances:
# get the customized instance state from self._model
model_instance = self._model.getInstanceById(i.id)
@@ -407,31 +353,12 @@ class CloudInspectorView(QtGui.QWidget, Ui_CloudInspectorView):
if i.state != RunningInstanceState.RUNNING:
self._model.updateInstanceFields(i, ['state'])
# start gns3server if needed
if i.state == RunningInstanceState.RUNNING and (
model_instance.state >= RunningInstanceState.RUNNING):
# instance state transition: RUNNING --> GNS3SERVER_STARTING
model_instance.state = RunningInstanceState.GNS3SERVER_STARTING
self._model.updateInstanceFields(model_instance, ['state'])
# start GNS3 server and deadman switch
public_ip = self._get_public_ip(i.public_ips)
instance_manager.update_host_for_instance(i.id, public_ip)
topology_instance = instance_manager.get_instance(i.id)
ssh_thread = StartGNS3ServerThread(
self, public_ip, topology_instance.private_key, i.id,
self._provider.username, self._provider.api_key, self._provider.region,
1800)
ssh_thread.gns3server_started.connect(self._gns3server_started_slot)
ssh_thread.start()
def _populate_model(self, instances):
log.info('CloudInspectorView._populate_model')
self._model.flavors = self._provider.list_flavors()
# filter instances for current project
project_instances = [i for i in instances if i.id in self._project_instances_id]
for i in project_instances:
self._model.addInstance(i)
for inst in instances:
self._model.addInstance(inst)
self.uiInstancesTableView.resizeColumnsToContents()
def _create_new_instance(self):
@@ -445,10 +372,44 @@ class CloudInspectorView(QtGui.QWidget, Ui_CloudInspectorView):
"then wait for the instance to appear in the inspector.")
if ok:
if not name.endswith("-gns3"):
name += "-gns3"
self.createInstance(name, flavor_id, image_id)
create_thread = CreateInstanceThread(self, self._provider, name, flavor_id, image_id)
create_thread.instanceCreated.connect(self._main_window.add_instance_to_project)
create_thread.instanceCreated.connect(CloudInstances.instance().add_instance)
create_thread.start()
def createInstance(self, instance_name, flavor_id, image_id):
if not instance_name.endswith("-gns3"):
instance_name += "-gns3"
# TODO: Add a keys_dir to projectSettings
ca_dir = os.path.join(self._main_window.projectSettings()["project_files_dir"], "keys")
builder = CloudBuilder(self, self._provider, ca_dir)
builder.startAtCreate(instance_name, flavor_id, image_id)
builder.instanceCreated.connect(self._main_window.add_instance_to_project)
builder.instanceCreated.connect(CloudInstances.instance().add_instance)
builder.instanceIdExists.connect(self._associateBuilderWithInstance)
builder.instanceHasIP.connect(CloudInstances.instance().update_host_for_instance)
builder.buildComplete.connect(self._instanceBuilt)
builder.start()
return builder
def _associateBuilderWithInstance(self, builder, instance_id):
self._builders[instance_id] = builder
def _rebuild_instances(self, instances):
# TODO: Add a keys_dir to projectSettings
ca_dir = os.path.join(self._main_window.projectSettings()["project_files_dir"], "keys")
for instance in instances:
log.debug('CloudInspectorView._rebuild_instances {}'.format(instance.name))
builder = CloudBuilder(self, self._provider, ca_dir)
cloud_instance = CloudInstances.instance().get_instance(instance.id)
public_key = cloud_instance.public_key
private_key = cloud_instance.private_key
# Fake a KeyPair object because we don't store it.
keypair = namedtuple('KeyPair', ['private_key', 'public_key'])(private_key, public_key)
builder.startAtSetup(instance, keypair)
builder.instanceCreated.connect(self._main_window.add_instance_to_project)
builder.instanceCreated.connect(CloudInstances.instance().add_instance)
builder.instanceIdExists.connect(self._associateBuilderWithInstance)
builder.instanceHasIP.connect(CloudInstances.instance().update_host_for_instance)
builder.buildComplete.connect(self._instanceBuilt)
builder.start()
return builder

View File

@@ -64,29 +64,38 @@ class CloudInstances(QtCore.QObject):
def add_instance(self, instance, keypair):
if instance is None:
return
ti = TopologyInstance(instance.name, instance.id, instance.extra['flavorId'],
instance.extra['imageId'], keypair.private_key, keypair.public_key)
self._instances.append(ti)
self.save()
existing = self.get_instance(instance.id)
if existing is None:
ti = TopologyInstance(instance.name, instance.id, instance.extra['flavorId'],
instance.extra['imageId'], keypair.private_key, keypair.public_key)
self._instances.append(ti)
self.save()
def update_instances(self, instances):
"""
Compare with the existing list of instances to purge instances that no
longer exist.
"""
save_needed = False
# Look for instances that have been deleted
for static in self._instances:
for stored in self._instances:
found = False
for dynamic in instances:
if static.id == dynamic.id:
if stored.id == dynamic.id:
found = True
break
if not found:
self._instances.remove(static)
self._instances.remove(stored)
save_needed = True
if save_needed:
self.save()
def update_host_for_instance(self, instance_id, host):
def update_host_for_instance(self, host, instance_id):
"""
Update the public IP for the instance.
"""
for instance in self.instances:
if instance.id == instance_id:
if instance.host != host:

View File

@@ -31,6 +31,8 @@ import json
import glob
import logging
import functools
import ast
import posixpath
from pkg_resources import parse_version
@@ -58,7 +60,7 @@ from .items.shape_item import ShapeItem
from .items.image_item import ImageItem
from .items.note_item import NoteItem
from .topology import Topology, TopologyInstance
from .cloud.utils import UploadProjectThread
from .cloud.utils import UploadProjectThread, UploadFilesThread, ssh_client
from .cloud.rackspace_ctrl import get_provider
from .cloud.exceptions import KeyPairExists
from .cloud_instances import CloudInstances
@@ -274,7 +276,9 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
self.uiSaveProjectAction.triggered.connect(self._saveProjectActionSlot)
self.uiSaveProjectAsAction.triggered.connect(self._saveProjectAsActionSlot)
self.uiExportProjectAction.triggered.connect(self._exportProjectActionSlot)
#self.uiImportProjectAction.triggered.connect(self._importProjectActionSlot)
self.uiImportProjectAction.triggered.connect(self._importProjectActionSlot)
self.uiMoveLocalProjectToCloudAction.triggered.connect(self._moveLocalProjectToCloudActionSlot)
self.uiMoveCloudProjectToLocalAction.triggered.connect(self._moveCloudProjectToLocalActionSlot)
self.uiImportExportConfigsAction.triggered.connect(self._importExportConfigsActionSlot)
self.uiScreenshotAction.triggered.connect(self._screenshotActionSlot)
self.uiSnapshotAction.triggered.connect(self._snapshotActionSlot)
@@ -376,7 +380,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
def _createNewProject(self, new_project_settings):
"""
Crates a new project.
Creates a new project.
:param new_project_settings: project settings (dict)
"""
@@ -1580,7 +1584,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
return
project_instances = json_topology["topology"]["instances"]
self.CloudInspectorView.load(self, project_instances)
self.CloudInspectorView.load(self, [i["id"] for i in project_instances])
def add_instance_to_project(self, instance, keypair):
"""
@@ -1640,9 +1644,13 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
if self._temporary_project:
# do nothing if project is temporary
QtGui.QMessageBox.critical(
self, "Export project server",
"Cannot export temporary projects, please save current project first.")
self,
"Backup project",
"Cannot backup temporary projects, please save current project first."
)
return
if self.checkForUnsavedChanges():
self.saveProject(self._project_settings["project_path"])
upload_thread = UploadProjectThread(
self,
@@ -1650,7 +1658,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
self._project_settings['project_path'],
self._settings['images_path']
)
progress_dialog = ProgressDialog(upload_thread, "Exporting Project", "Uploading project files...", "Cancel",
progress_dialog = ProgressDialog(upload_thread, "Backing Up Project", "Uploading project files...", "Cancel",
parent=self)
progress_dialog.show()
progress_dialog.exec_()
@@ -1666,6 +1674,117 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
dialog.show()
dialog.exec_()
def _moveLocalProjectToCloudActionSlot(self):
if self._temporary_project:
# do nothing if project is temporary
QtGui.QMessageBox.critical(
self,
"Move project to Cloud",
"Cannot move temporary projects, please save current project first.")
return
if self._project_settings["project_type"] == "cloud":
# do nothing if project is already a cloud project
QtGui.QMessageBox.critical(
self,
"Move project to Cloud",
"This project is already a Cloud Project")
return
if not self.checkForUnsavedChanges():
# do nothing if project is already a cloud project
QtGui.QMessageBox.critical(
self,
"Unsaved changes",
"There are unsaved changes. Please save the project first.")
return
# Upload images to cloud storage
topology = Topology.instance()
images = set([
(
node.settings()['image'],
'images/' + os.path.relpath(node.settings()['image'], self._settings["images_path"])
)
for node in topology.nodes() if 'image' in node.settings()
])
log.debug('uploading images ' + str(images) + ' to cloud')
upload_thread = UploadFilesThread(self, self._cloud_settings, images)
upload_images_progress_dialog = ProgressDialog(upload_thread, "Uploading images", "Uploading image files...",
"Cancel", parent=self)
upload_images_progress_dialog.show()
upload_images_progress_dialog.exec_()
progress_dialog = QtGui.QProgressDialog("Moving project to cloud", "Cancel", 0, 100, self)
progress_dialog.show()
def buildComplete(server_id):
progress_dialog.setValue(80)
log.debug("websocket connected, server_id=" + str(server_id))
instance = topology.getInstance(server_id)
# copy nvram, config, and disk files to server
with ssh_client(instance.host, instance.private_key) as client:
log.debug('copying device files to cloud instance')
sftp = client.open_sftp()
def should_copy(filename):
return not filename.endswith('.ghost')
project_files_dir = os.path.join(
os.path.dirname(self._project_settings['project_path']),
os.path.basename(os.path.dirname(self._project_settings['project_path'])) + '-files'
)
dest_project_path = posixpath.join(
'/root/GNS3/projects',
os.path.basename(os.path.dirname(self._project_settings['project_path']))
)
for root, dirs, files in os.walk(project_files_dir):
directory = posixpath.normpath(posixpath.join(
dest_project_path,
os.path.relpath(root, project_files_dir).replace('\\', '/')
))
sftp.mkdir(directory)
sftp.chdir(directory)
for file in files:
local_filepath = os.path.join(root, file)
if os.path.isfile(local_filepath) and should_copy(file):
log.debug('copying file ' + local_filepath)
sftp.put(local_filepath, file)
log.debug('copied file successfully')
sftp.close()
self._project_settings["project_type"] = "cloud"
server = Servers.instance().anyCloudServer()
for node in topology.nodes():
node._server = server
self.saveProject(self._project_settings["project_path"])
topology.reset()
self.loadProject(self._project_settings["project_path"])
progress_dialog.accept()
instances = CloudInstances.instance().instances
for instance in instances:
topology.addInstance2(instance)
self.CloudInspectorView.load(self, [i.id for i in topology.instances()])
# Create a new instance. At some point we could reuse an existing instance.
builder = self.CloudInspectorView.createInstance(
self._project_settings["project_name"],
self.cloudSettings()['default_flavor'],
self.cloudSettings()['default_image']
)
builder.buildComplete.connect(buildComplete)
def _moveCloudProjectToLocalActionSlot(self):
#TODO implement moving cloud project to local
print("move cloud project to local")
def _cloud_instance_selected(self, instance_id):
"""
Clear selection, then select all the nodes on the graphics view

View File

@@ -488,11 +488,7 @@ class Dynamips(Module):
if wic in ios_router:
settings[wic] = ios_router[wic]
if node.server().isCloud():
settings["cloud_path"] = "images/IOS"
node.setup(ios_router["image"], ios_router["ram"], initial_settings=settings)
else:
node.setup(ios_router["path"], ios_router["ram"], initial_settings=settings)
node.setup(ios_router["path"], ios_router["ram"], initial_settings=settings)
else:
node.setup()

View File

@@ -250,6 +250,10 @@ class Router(Node):
"ram": ram,
"image": image}
if self.server().isCloud():
initial_settings["cloud_path"] = "images/IOS"
params["image"] = os.path.basename(params["image"])
if router_id:
params["router_id"] = router_id

View File

@@ -128,10 +128,15 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
self._upload_image_progress_dialog.setWindowTitle("IOS image upload")
self._upload_image_progress_dialog.show()
try:
src = self._ios_routers[key]['path']
# Eg: images/IOS/c3745.img
dst = 'images/IOS/{}'.format(self._ios_routers[key]['image'])
upload_thread = UploadFilesThread(self, MainWindow.instance().cloudSettings(), [(src, dst)])
upload_thread = UploadFilesThread(
self,
cloud_settings=MainWindow.instance().cloudSettings(),
files_to_upload=[(
self._ios_routers[key]["path"],
'images/' + os.path.relpath(self._ios_routers[key]["path"],
self._main_window.settings()["images_path"])
)]
)
upload_thread.completed.connect(self._imageUploadComplete)
upload_thread.start()
except Exception as e:

View File

@@ -267,7 +267,6 @@ class Topology(object):
:param id: the instance id
:return: a TopologyInstance object
"""
for instance in self._instances:
if instance.id == id:
return instance
@@ -516,6 +515,8 @@ class Topology(object):
for topology_server in servers:
if "local" in topology_server and topology_server["local"]:
self._servers[topology_server["id"]] = server_manager.localServer()
elif "cloud" in topology_server and topology_server["cloud"]:
self._servers[topology_server["id"]] = server_manager.anyCloudServer()
else:
host = topology_server["host"]
port = topology_server["port"]

View File

@@ -85,8 +85,11 @@ background-none;
<addaction name="uiOpenProjectAction"/>
<addaction name="uiSaveProjectAction"/>
<addaction name="uiSaveProjectAsAction"/>
<addaction name="uiImportProjectAction"/>
<addaction name="uiExportProjectAction"/>
<addaction name="uiImportProjectAction"/>
<addaction name="separator"/>
<addaction name="uiMoveLocalProjectToCloudAction"/>
<addaction name="uiMoveCloudProjectToLocalAction"/>
<addaction name="separator"/>
<addaction name="uiImportExportConfigsAction"/>
<addaction name="uiScreenshotAction"/>
@@ -1109,12 +1112,22 @@ background-none;
</action>
<action name="uiExportProjectAction">
<property name="text">
<string>Export project</string>
<string>Backup project to cloud</string>
</property>
</action>
<action name="uiImportProjectAction">
<property name="text">
<string>Import project</string>
<string>Restore backup from cloud</string>
</property>
</action>
<action name="uiMoveLocalProjectToCloudAction">
<property name="text">
<string>Move local project to cloud</string>
</property>
</action>
<action name="uiMoveCloudProjectToLocalAction">
<property name="text">
<string>Move cloud project to local</string>
</property>
</action>
<action name="uiDarkStyleAction">

View File

@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/main_window.ui'
# Form implementation generated from reading ui file 'gns3/ui/main_window.ui'
#
# Created: Thu Dec 11 23:26:53 2014
# Created: Wed Dec 10 15:30:27 2014
# by: PyQt4 UI code generator 4.10.4
#
# WARNING! All changes made in this file will be lost!
@@ -112,7 +112,7 @@ class Ui_MainWindow(object):
sizePolicy.setHeightForWidth(self.uiNodesView.sizePolicy().hasHeightForWidth())
self.uiNodesView.setSizePolicy(sizePolicy)
self.uiNodesView.setDragEnabled(False)
self.uiNodesView.setIconSize(QtCore.QSize(32, 32))
self.uiNodesView.setIconSize(QtCore.QSize(24, 24))
self.uiNodesView.setRootIsDecorated(False)
self.uiNodesView.setObjectName(_fromUtf8("uiNodesView"))
self.uiNodesView.header().setVisible(False)
@@ -217,22 +217,22 @@ class Ui_MainWindow(object):
self.uiOnlineHelpAction.setObjectName(_fromUtf8("uiOnlineHelpAction"))
self.uiScreenshotAction = QtGui.QAction(MainWindow)
icon4 = QtGui.QIcon()
icon4.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/camera-photo.svg")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon4.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/camera-photo-hover.svg")), QtGui.QIcon.Active, QtGui.QIcon.Off)
icon4.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/camera-photo.svg")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.uiScreenshotAction.setIcon(icon4)
self.uiScreenshotAction.setObjectName(_fromUtf8("uiScreenshotAction"))
self.uiStartAllAction = QtGui.QAction(MainWindow)
self.uiStartAllAction.setEnabled(True)
icon5 = QtGui.QIcon()
icon5.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/start.svg")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon5.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/start-hover.svg")), QtGui.QIcon.Active, QtGui.QIcon.Off)
icon5.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/start.svg")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.uiStartAllAction.setIcon(icon5)
self.uiStartAllAction.setObjectName(_fromUtf8("uiStartAllAction"))
self.uiStopAllAction = QtGui.QAction(MainWindow)
self.uiStopAllAction.setEnabled(True)
icon6 = QtGui.QIcon()
icon6.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/stop.svg")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon6.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/stop-hover.svg")), QtGui.QIcon.Active, QtGui.QIcon.Off)
icon6.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/stop.svg")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.uiStopAllAction.setIcon(icon6)
self.uiStopAllAction.setObjectName(_fromUtf8("uiStopAllAction"))
self.uiShowNamesAction = QtGui.QAction(MainWindow)
@@ -252,14 +252,14 @@ class Ui_MainWindow(object):
self.uiAboutQtAction.setObjectName(_fromUtf8("uiAboutQtAction"))
self.uiZoomInAction = QtGui.QAction(MainWindow)
icon9 = QtGui.QIcon()
icon9.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/zoom-in.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon9.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/zoom-in-hover.png")), QtGui.QIcon.Active, QtGui.QIcon.Off)
icon9.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/zoom-in.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.uiZoomInAction.setIcon(icon9)
self.uiZoomInAction.setObjectName(_fromUtf8("uiZoomInAction"))
self.uiZoomOutAction = QtGui.QAction(MainWindow)
icon10 = QtGui.QIcon()
icon10.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/zoom-out.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon10.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/zoom-out-hover.png")), QtGui.QIcon.Active, QtGui.QIcon.Off)
icon10.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/zoom-out.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.uiZoomOutAction.setIcon(icon10)
self.uiZoomOutAction.setObjectName(_fromUtf8("uiZoomOutAction"))
self.uiZoomResetAction = QtGui.QAction(MainWindow)
@@ -275,8 +275,8 @@ class Ui_MainWindow(object):
self.uiPreferencesAction.setObjectName(_fromUtf8("uiPreferencesAction"))
self.uiSuspendAllAction = QtGui.QAction(MainWindow)
icon12 = QtGui.QIcon()
icon12.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/pause.svg")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon12.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/pause-hover.svg")), QtGui.QIcon.Active, QtGui.QIcon.Off)
icon12.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/pause.svg")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.uiSuspendAllAction.setIcon(icon12)
self.uiSuspendAllAction.setObjectName(_fromUtf8("uiSuspendAllAction"))
self.uiAddNoteAction = QtGui.QAction(MainWindow)
@@ -304,15 +304,15 @@ class Ui_MainWindow(object):
self.uiDrawRectangleAction = QtGui.QAction(MainWindow)
self.uiDrawRectangleAction.setCheckable(True)
icon17 = QtGui.QIcon()
icon17.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/rectangle.svg")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon17.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/rectangle-hover.svg")), QtGui.QIcon.Active, QtGui.QIcon.Off)
icon17.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/rectangle.svg")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.uiDrawRectangleAction.setIcon(icon17)
self.uiDrawRectangleAction.setObjectName(_fromUtf8("uiDrawRectangleAction"))
self.uiDrawEllipseAction = QtGui.QAction(MainWindow)
self.uiDrawEllipseAction.setCheckable(True)
icon18 = QtGui.QIcon()
icon18.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/ellipse.svg")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon18.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/ellipse-hover.svg")), QtGui.QIcon.Active, QtGui.QIcon.Off)
icon18.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/ellipse.svg")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.uiDrawEllipseAction.setIcon(icon18)
self.uiDrawEllipseAction.setObjectName(_fromUtf8("uiDrawEllipseAction"))
self.uiShowPortNamesAction = QtGui.QAction(MainWindow)
@@ -358,41 +358,41 @@ class Ui_MainWindow(object):
self.uiDefaultStyleAction.setObjectName(_fromUtf8("uiDefaultStyleAction"))
self.uiBrowseRoutersAction = QtGui.QAction(MainWindow)
icon24 = QtGui.QIcon()
icon24.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/router.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon24.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/router-hover.png")), QtGui.QIcon.Active, QtGui.QIcon.Off)
icon24.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/router.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.uiBrowseRoutersAction.setIcon(icon24)
self.uiBrowseRoutersAction.setObjectName(_fromUtf8("uiBrowseRoutersAction"))
self.uiBrowseSwitchesAction = QtGui.QAction(MainWindow)
icon25 = QtGui.QIcon()
icon25.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/switch.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon25.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/switch-hover.png")), QtGui.QIcon.Active, QtGui.QIcon.Off)
icon25.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/switch.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.uiBrowseSwitchesAction.setIcon(icon25)
self.uiBrowseSwitchesAction.setObjectName(_fromUtf8("uiBrowseSwitchesAction"))
self.uiBrowseEndDevicesAction = QtGui.QAction(MainWindow)
icon26 = QtGui.QIcon()
icon26.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/PC.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon26.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/PC-hover.png")), QtGui.QIcon.Active, QtGui.QIcon.Off)
icon26.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/PC.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.uiBrowseEndDevicesAction.setIcon(icon26)
self.uiBrowseEndDevicesAction.setObjectName(_fromUtf8("uiBrowseEndDevicesAction"))
self.uiBrowseSecurityDevicesAction = QtGui.QAction(MainWindow)
icon27 = QtGui.QIcon()
icon27.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/firewall.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon27.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/firewall-hover.png")), QtGui.QIcon.Active, QtGui.QIcon.Off)
icon27.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/firewall.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.uiBrowseSecurityDevicesAction.setIcon(icon27)
self.uiBrowseSecurityDevicesAction.setObjectName(_fromUtf8("uiBrowseSecurityDevicesAction"))
self.uiBrowseAllDevicesAction = QtGui.QAction(MainWindow)
icon28 = QtGui.QIcon()
icon28.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/browse-all-icons.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon28.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/browse-all-icons-hover.png")), QtGui.QIcon.Active, QtGui.QIcon.Off)
icon28.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/browse-all-icons.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.uiBrowseAllDevicesAction.setIcon(icon28)
self.uiBrowseAllDevicesAction.setObjectName(_fromUtf8("uiBrowseAllDevicesAction"))
self.uiAddLinkAction = QtGui.QAction(MainWindow)
self.uiAddLinkAction.setCheckable(True)
icon29 = QtGui.QIcon()
icon29.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/connection-new.svg")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon29.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/cancel-connection.svg")), QtGui.QIcon.Normal, QtGui.QIcon.On)
icon29.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/connection-new-hover.svg")), QtGui.QIcon.Active, QtGui.QIcon.Off)
icon29.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/cancel-connection.svg")), QtGui.QIcon.Active, QtGui.QIcon.On)
icon29.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/connection-new-hover.svg")), QtGui.QIcon.Active, QtGui.QIcon.Off)
icon29.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/cancel-connection.svg")), QtGui.QIcon.Normal, QtGui.QIcon.On)
icon29.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/connection-new.svg")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.uiAddLinkAction.setIcon(icon29)
self.uiAddLinkAction.setObjectName(_fromUtf8("uiAddLinkAction"))
self.uiGettingStartedAction = QtGui.QAction(MainWindow)
@@ -405,6 +405,10 @@ class Ui_MainWindow(object):
self.uiExportProjectAction.setObjectName(_fromUtf8("uiExportProjectAction"))
self.uiImportProjectAction = QtGui.QAction(MainWindow)
self.uiImportProjectAction.setObjectName(_fromUtf8("uiImportProjectAction"))
self.uiMoveLocalProjectToCloudAction = QtGui.QAction(MainWindow)
self.uiMoveLocalProjectToCloudAction.setObjectName(_fromUtf8("uiMoveLocalProjectToCloudAction"))
self.uiMoveCloudProjectToLocalAction = QtGui.QAction(MainWindow)
self.uiMoveCloudProjectToLocalAction.setObjectName(_fromUtf8("uiMoveCloudProjectToLocalAction"))
self.uiDarkStyleAction = QtGui.QAction(MainWindow)
self.uiDarkStyleAction.setObjectName(_fromUtf8("uiDarkStyleAction"))
self.uiActionFullscreen = QtGui.QAction(MainWindow)
@@ -417,8 +421,11 @@ class Ui_MainWindow(object):
self.uiFileMenu.addAction(self.uiOpenProjectAction)
self.uiFileMenu.addAction(self.uiSaveProjectAction)
self.uiFileMenu.addAction(self.uiSaveProjectAsAction)
self.uiFileMenu.addAction(self.uiImportProjectAction)
self.uiFileMenu.addAction(self.uiExportProjectAction)
self.uiFileMenu.addAction(self.uiImportProjectAction)
self.uiFileMenu.addSeparator()
self.uiFileMenu.addAction(self.uiMoveLocalProjectToCloudAction)
self.uiFileMenu.addAction(self.uiMoveCloudProjectToLocalAction)
self.uiFileMenu.addSeparator()
self.uiFileMenu.addAction(self.uiImportExportConfigsAction)
self.uiFileMenu.addAction(self.uiScreenshotAction)
@@ -647,15 +654,17 @@ class Ui_MainWindow(object):
self.uiGettingStartedAction.setToolTip(_translate("MainWindow", "Show GNS3 news", None))
self.uiLabInstructionsAction.setText(_translate("MainWindow", "Lab instructions", None))
self.uiFitInViewAction.setText(_translate("MainWindow", "Fit in view", None))
self.uiExportProjectAction.setText(_translate("MainWindow", "Export project", None))
self.uiImportProjectAction.setText(_translate("MainWindow", "Import project", None))
self.uiExportProjectAction.setText(_translate("MainWindow", "Backup project to cloud", None))
self.uiImportProjectAction.setText(_translate("MainWindow", "Restore backup from cloud", None))
self.uiMoveLocalProjectToCloudAction.setText(_translate("MainWindow", "Move local project to cloud", None))
self.uiMoveCloudProjectToLocalAction.setText(_translate("MainWindow", "Move cloud project to local", None))
self.uiDarkStyleAction.setText(_translate("MainWindow", "Dark Style", None))
self.uiActionFullscreen.setText(_translate("MainWindow", "Fullscreen", None))
self.uiActionFullscreen.setShortcut(_translate("MainWindow", "Ctrl+F", None))
from ..cloud_inspector_view import CloudInspectorView
from ..console_view import ConsoleView
from ..topology_summary_view import TopologySummaryView
from ..nodes_view import NodesView
from ..graphics_view import GraphicsView
from ..topology_summary_view import TopologySummaryView
from ..cloud_inspector_view import CloudInspectorView
from ..console_view import ConsoleView
from . import resources_rc

View File

@@ -355,7 +355,8 @@ class WebSocketClient(WebSocketBaseClient):
return {"id": self._id,
"host": self.host,
"port": self.port,
"local": self._local}
"local": self._local,
"cloud": self._cloud}
def _heartbeat(self):
self.send_notification("deadman.heartbeat")

View File

@@ -88,8 +88,9 @@ def read_cloud_settings():
host = settings.value('host')
private_key = settings.value('private_key')
public_key = settings.value('public_key')
uid = settings.value('id')
instances.append((name, host, private_key, public_key))
instances.append((name, host, private_key, public_key, uid))
if len(instances) == 0:
raise Exception("Could not find any servers")
@@ -103,7 +104,7 @@ def main():
instances = read_cloud_settings()
if options['action'] == 'ssh':
name, host, private_key, public_key = instances[int(options['server'])-1]
name, host, private_key, public_key, uid = instances[int(options['server'])-1]
print('Instance name: {}'.format(name))
print('Host ip: {}'.format(host))
@@ -119,13 +120,13 @@ def main():
print(cmd)
os.system(cmd)
elif options['action'] == 'list':
print('ID Name')
print('ID Name IP UID')
for idx, info in enumerate(instances):
name, host, private_key, public_key = info
print('{:2d} {}'.format(idx+1, name))
name, host, private_key, public_key, uid = info
print('{:2d} {} {} {}'.format(idx+1, name, host, uid))
return 0
if __name__ == "__main__":
sys.exit(main())
sys.exit(main())