mirror of
https://github.com/GNS3/gns3-gui.git
synced 2026-05-17 00:46:01 +03:00
IOS devices can be deployed on cloud instances.
This commit is contained in:
@@ -8,7 +8,6 @@ import tempfile
|
||||
import time
|
||||
import zipfile
|
||||
|
||||
from PyQt4 import QtCore
|
||||
from PyQt4.QtCore import QThread
|
||||
from PyQt4.QtCore import pyqtSignal
|
||||
|
||||
@@ -117,35 +116,34 @@ class StartGNS3ServerThread(QThread):
|
||||
"""
|
||||
gns3server_started = pyqtSignal(str, str, str)
|
||||
|
||||
# # Note: The htop package is for troubleshooting. It can safely be removed.
|
||||
# Note: The htop package is for troubleshooting. It can safely be removed.
|
||||
commands = '''
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -y update
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -y install htop
|
||||
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
|
||||
mkdir -p /opt/gns3
|
||||
tar xzf /tmp/gns3-server.tgz -C /opt/gns3
|
||||
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
|
||||
killall python3 gns3server gns3dms
|
||||
'''
|
||||
|
||||
# commands = '''
|
||||
# echo 'hello world'
|
||||
# DEBIAN_FRONTEND=noninteractive dpkg --configure -a
|
||||
# DEBIAN_FRONTEND=noninteractive apt-get -y update
|
||||
# DEBIAN_FRONTEND=noninteractive apt-get -y install htop
|
||||
# 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
|
||||
# mkdir -p /opt/gns3
|
||||
# tar xzf /tmp/gns3-server.tgz -C /opt/gns3
|
||||
# cd /opt/gns3; git clone https://github.com/planctechnologies/gns3-server.git
|
||||
# cd /opt/gns3/gns3-server; git checkout gns-110
|
||||
# cd /opt/gns3/gns3-server; pip3 install -r dev-requirements.txt
|
||||
# cd /opt/gns3/gns3-server; python3 ./setup.py install
|
||||
# killall gns3server gns3dms
|
||||
# '''
|
||||
|
||||
commands = '''
|
||||
echo 'hello world'
|
||||
DEBIAN_FRONTEND=noninteractive dpkg --configure -a
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -y update
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -y install htop
|
||||
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
|
||||
mkdir -p /opt/gns3
|
||||
cd /opt/gns3; git clone https://github.com/planctechnologies/gns3-server.git
|
||||
cd /opt/gns3/gns3-server; git checkout dev
|
||||
cd /opt/gns3/gns3-server; pip3 install -r dev-requirements.txt
|
||||
cd /opt/gns3/gns3-server; python3 ./setup.py install
|
||||
killall gns3server gns3dms
|
||||
'''
|
||||
|
||||
def __init__(self, parent, host, private_key_string, server_id, username, api_key, region, dead_time):
|
||||
super(QThread, self).__init__(parent)
|
||||
self._host = host
|
||||
@@ -186,10 +184,6 @@ killall gns3server gns3dms
|
||||
def run(self):
|
||||
# Uncomment this at the same time as the commands above to test without having to push
|
||||
# changes to github.
|
||||
# os.system('rm -rf /tmp/gns3-server')
|
||||
# os.system('cp -a /Users/jseutter/projects/gns3-server-newinstancerework /tmp/gns3-server')
|
||||
# os.system('cd /tmp; tar czf /tmp/gns3-server.tgz gns3-server')
|
||||
# os.system('scp /tmp/gns3-server.tgz root@{}:/tmp/'.format(self._host))
|
||||
|
||||
# We might be attempting a connection before the instance is fully booted, so retry
|
||||
# when the ssh connection fails.
|
||||
@@ -201,6 +195,13 @@ killall gns3server gns3dms
|
||||
continue
|
||||
ssh_connected = True
|
||||
|
||||
os.system('rm -rf /tmp/gns3-server')
|
||||
os.system('cp -a /Users/jseutter/projects/gns3-server /tmp/gns3-server')
|
||||
os.system('cd /tmp; tar czf /tmp/gns3-server.tgz gns3-server')
|
||||
sftp = client.open_sftp()
|
||||
sftp.put('/tmp/gns3-server.tgz', '/tmp/gns3-server.tgz')
|
||||
sftp.close()
|
||||
|
||||
for cmd in [l for l in self.commands.splitlines() if l.strip()]:
|
||||
self.exec_command(client, cmd)
|
||||
|
||||
@@ -208,11 +209,11 @@ killall gns3server gns3dms
|
||||
'instance_id': self._server_id,
|
||||
'cloud_user_name': self._username,
|
||||
'cloud_api_key': self._api_key,
|
||||
'region': self._region,
|
||||
'cloud_region': self._region,
|
||||
'dead_time': self._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 --data="{}"'.format(data)
|
||||
start_cmd = '/usr/bin/python3 /opt/gns3/gns3-server/gns3server/start_server.py -d -v --ip={} --data="{}" 2>/tmp/gns3-stderr.log'.format(self._host, data)
|
||||
stdout, stderr = self.exec_command(client, start_cmd, wait_time=15)
|
||||
response = stdout.decode('utf-8')
|
||||
self.gns3server_started.emit(str(self._server_id), str(self._host), str(response))
|
||||
@@ -225,13 +226,15 @@ class WSConnectThread(QThread):
|
||||
"""
|
||||
established = pyqtSignal(str)
|
||||
|
||||
def __init__(self, parent, provider, server_id, host, port, ca_file):
|
||||
def __init__(self, parent, provider, server_id, host, port, ca_file, auth_user, auth_password):
|
||||
super(QThread, self).__init__(parent)
|
||||
self._provider = provider
|
||||
self._server_id = server_id
|
||||
self._host = host
|
||||
self._port = port
|
||||
self._ca_file = ca_file
|
||||
self._auth_user = auth_user
|
||||
self._auth_password = auth_password
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
@@ -240,7 +243,7 @@ class WSConnectThread(QThread):
|
||||
|
||||
log.debug('WSConnectThread.run() begin')
|
||||
servers = Servers.instance()
|
||||
server = servers.getCloudServer(self._host, self._port, self._ca_file)
|
||||
server = servers.getCloudServer(self._host, self._port, self._ca_file, self._auth_user, self._auth_password)
|
||||
log.debug('after getCloudServer call. {}'.format(server))
|
||||
self.established.emit(str(self._server_id))
|
||||
|
||||
@@ -253,42 +256,23 @@ class UploadProjectThread(QThread):
|
||||
"""
|
||||
Zip and Upload project to the cloud
|
||||
"""
|
||||
|
||||
# signals to update the progress dialog.
|
||||
error = QtCore.pyqtSignal(str, bool)
|
||||
completed = QtCore.pyqtSignal()
|
||||
update = QtCore.pyqtSignal(int)
|
||||
|
||||
def __init__(self, project_settings, cloud_settings):
|
||||
super().__init__()
|
||||
self.project_settings = project_settings
|
||||
self.cloud_settings = cloud_settings
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
log.info("Exporting project to cloud")
|
||||
self.update.emit(0)
|
||||
log.info("Exporting project to cloud")
|
||||
zipped_project_file = self.zip_project_dir()
|
||||
|
||||
zipped_project_file = self.zip_project_dir()
|
||||
provider = get_provider(self.cloud_settings)
|
||||
provider.upload_file(zipped_project_file, 'projects')
|
||||
|
||||
self.update.emit(10) # update progress to 10%
|
||||
topology = Topology.instance()
|
||||
images = set([node.settings()["image"] for node in topology.nodes() if 'image' in node.settings()])
|
||||
|
||||
provider = get_provider(self.cloud_settings)
|
||||
provider.upload_file(zipped_project_file, 'projects')
|
||||
|
||||
self.update.emit(20) # update progress to 20%
|
||||
|
||||
topology = Topology.instance()
|
||||
images = set([node.settings()["image"] for node in topology.nodes() if 'image' in node.settings()])
|
||||
|
||||
for i, image in enumerate(images):
|
||||
provider.upload_file(image, 'images')
|
||||
self.update.emit(20 + (float(i) / len(images) * 80))
|
||||
|
||||
self.completed.emit()
|
||||
except Exception as e:
|
||||
log.exception("Error exporting project to cloud")
|
||||
self.error.emit("Error exporting project {}".format(str(e)), True)
|
||||
for image in images:
|
||||
provider.upload_file(image, 'images')
|
||||
|
||||
def zip_project_dir(self):
|
||||
"""
|
||||
@@ -305,19 +289,34 @@ class UploadProjectThread(QThread):
|
||||
zip_file.write(root, os.path.relpath(root, relroot))
|
||||
for file in files:
|
||||
filename = os.path.join(root, file)
|
||||
if os.path.isfile(filename) and not self._should_exclude(filename): # regular files only
|
||||
if os.path.isfile(filename): # regular files only
|
||||
arcname = os.path.join(os.path.relpath(root, relroot), file)
|
||||
zip_file.write(filename, arcname)
|
||||
|
||||
return output_filename
|
||||
|
||||
def _should_exclude(self, filename):
|
||||
"""
|
||||
Returns True if file should be excluded from zip of project files
|
||||
:param filename:
|
||||
:return: True if file should be excluded from zip, False otherwise
|
||||
"""
|
||||
return filename.endswith('.ghost')
|
||||
class UploadFileThread(QThread):
|
||||
"""
|
||||
Upload an file to cloud files
|
||||
"""
|
||||
def __init__(self, cloud_settings, router_settings):
|
||||
super().__init__()
|
||||
self._cloud_settings = cloud_settings
|
||||
self._router_settings = router_settings
|
||||
self.completed_callback = None
|
||||
|
||||
def stop(self):
|
||||
self.quit()
|
||||
|
||||
def run(self):
|
||||
disk_path = self._router_settings['path']
|
||||
filename = self._router_settings['image']
|
||||
|
||||
log.debug('Uploading image {}'.format(disk_path))
|
||||
log.debug('Cloud filename: {}'.format(filename))
|
||||
provider = get_provider(self._cloud_settings)
|
||||
provider.upload_file(disk_path, 'images')
|
||||
|
||||
self._cloud_settings['image'] = filename
|
||||
|
||||
log.debug('Uploading image completed')
|
||||
if self.completed_callback:
|
||||
self.completed_callback()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import ast
|
||||
import logging
|
||||
import os
|
||||
from PyQt4.QtGui import QWidget
|
||||
from PyQt4.QtGui import QIcon
|
||||
from PyQt4.QtGui import QMenu
|
||||
@@ -311,12 +312,23 @@ class CloudInspectorView(QWidget, Ui_CloudInspectorView):
|
||||
password = data['WEB_PASSWORD']
|
||||
|
||||
ssl_cert = ''.join(data['SSL_CRT'])
|
||||
# TODO: Store the cert file in an appropriate spot
|
||||
ca_file = '/tmp/cloud_server_{}.crt'.format(host_ip)
|
||||
open(ca_file, 'w').write(ssl_cert)
|
||||
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)
|
||||
|
||||
log.debug('Cloud server gns3server started.')
|
||||
wss_thread = WSConnectThread(self, self._provider, id, host_ip, port, ca_file)
|
||||
wss_thread = WSConnectThread(self, self._provider, id, host_ip, port, ca_file, username, password)
|
||||
wss_thread.established.connect(self._wss_connected_slot)
|
||||
wss_thread.start()
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
Graphical view on the scene where items are drawn.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import pickle
|
||||
|
||||
@@ -51,6 +52,8 @@ from .items.rectangle_item import RectangleItem
|
||||
from .items.ellipse_item import EllipseItem
|
||||
from .items.image_item import ImageItem
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GraphicsView(QtGui.QGraphicsView):
|
||||
"""
|
||||
@@ -1095,6 +1098,7 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
"""
|
||||
|
||||
try:
|
||||
log.debug('In createNode')
|
||||
node_module = None
|
||||
for module in MODULES:
|
||||
instance = module.instance()
|
||||
@@ -1108,11 +1112,30 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
|
||||
if node_data["server"] == "local":
|
||||
server = Servers.instance().localServer()
|
||||
elif node_data["server"] == "cloud":
|
||||
server = Servers.instance().anyCloudServer()
|
||||
else:
|
||||
host, port = node_data["server"].rsplit(":", 1)
|
||||
server = Servers.instance().getRemoteServer(host, port)
|
||||
if not server.connected() and ConnectToServer(self, server) is False:
|
||||
return
|
||||
# if self._topology.resourcesType == "cloud":
|
||||
# use_cloud = True
|
||||
# else:
|
||||
# use_cloud = False
|
||||
# log.debug("use_cloud is set to {}".format(use_cloud))
|
||||
#
|
||||
# server = node_module.allocateServer(node_class, use_cloud)
|
||||
# if not server.connected():
|
||||
# # connect to server in a non-blocking way.
|
||||
# self._thread = WaitForConnectionThread(server.host, server.port)
|
||||
# progress_dialog = ProgressDialog(self._thread,
|
||||
# "Server",
|
||||
# "Connecting to server {} on port {}...".format(server.host, server.port),
|
||||
# "Cancel", busy=True, parent=self)
|
||||
# progress_dialog.show()
|
||||
# if progress_dialog.exec_() is False:
|
||||
# return
|
||||
|
||||
node = node_module.createNode(node_class, server)
|
||||
node.error_signal.connect(self._main_window.uiConsoleTextEdit.writeError)
|
||||
|
||||
@@ -1617,7 +1617,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
||||
index = 0
|
||||
for instance in instances:
|
||||
settings.setArrayIndex(index)
|
||||
for name in instance._fields:
|
||||
for name in instance.fields():
|
||||
log.debug('{}={}'.format(name, getattr(instance, name)))
|
||||
settings.setValue(name, getattr(instance, name))
|
||||
index += 1
|
||||
@@ -1638,7 +1638,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
||||
for index in range(0, size):
|
||||
settings.setArrayIndex(index)
|
||||
info = {}
|
||||
for name in TopologyInstance._fields:
|
||||
for name in TopologyInstance.fields():
|
||||
log.debug('{}={}'.format(name, settings.value(name, "")))
|
||||
info[name] = settings.value(name, "")
|
||||
topology.addInstance(**info)
|
||||
|
||||
@@ -387,6 +387,34 @@ class Dynamips(Module):
|
||||
params.update({"project_name": project_name})
|
||||
server.send_notification("dynamips.settings", params)
|
||||
|
||||
def allocateServer(self, node_class, use_cloud=False):
|
||||
"""
|
||||
Allocates a server.
|
||||
|
||||
:param node_class: Node object
|
||||
|
||||
:returns: allocated server (WebSocketClient instance)
|
||||
"""
|
||||
|
||||
# allocate a server for the node
|
||||
servers = Servers.instance()
|
||||
|
||||
if use_cloud:
|
||||
from ...topology import Topology
|
||||
topology = Topology.instance()
|
||||
top_instance = topology.anyInstance()
|
||||
server = servers.getCloudServer(top_instance.host, top_instance.port, top_instance.ssl_ca_file)
|
||||
else:
|
||||
if self._settings["use_local_server"]:
|
||||
# use the local server
|
||||
server = servers.localServer()
|
||||
else:
|
||||
# pick up a remote server (round-robin method)
|
||||
server = next(iter(servers))
|
||||
if not server:
|
||||
raise ModuleError("No remote server is configured")
|
||||
return server
|
||||
|
||||
def createNode(self, node_class, server):
|
||||
"""
|
||||
Creates a new node.
|
||||
@@ -468,7 +496,10 @@ class Dynamips(Module):
|
||||
if wic in ios_router:
|
||||
settings[wic] = ios_router[wic]
|
||||
|
||||
node.setup(ios_router["path"], ios_router["ram"], initial_settings=settings)
|
||||
if node.server().isCloud():
|
||||
node.setup(ios_router["image"], ios_router["ram"], initial_settings=settings)
|
||||
else:
|
||||
node.setup(ios_router["path"], ios_router["ram"], initial_settings=settings)
|
||||
else:
|
||||
node.setup()
|
||||
|
||||
|
||||
@@ -305,12 +305,12 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
|
||||
Validates the IOS name.
|
||||
"""
|
||||
|
||||
if self.currentPage() == self.uiServerWizardPage:
|
||||
|
||||
#FIXME: prevent users to use "cloud"
|
||||
if self.uiCloudRadioButton.isChecked():
|
||||
QtGui.QMessageBox.critical(self, "Cloud", "Sorry not implemented yet!")
|
||||
return False
|
||||
# if self.currentPage() == self.uiServerWizardPage:
|
||||
#
|
||||
# #FIXME: prevent users to use "cloud"
|
||||
# if self.uiCloudRadioButton.isChecked():
|
||||
# QtGui.QMessageBox.critical(self, "Cloud", "Sorry not implemented yet!")
|
||||
# return False
|
||||
|
||||
# if self.currentPage() == self.uiNameImageWizardPage:
|
||||
# name = self.uiNameLineEdit.text()
|
||||
@@ -347,14 +347,17 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
|
||||
path = self.uiIOSImageLineEdit.text()
|
||||
if Dynamips.instance().settings()["use_local_server"] or self.uiLocalRadioButton.isChecked():
|
||||
server = "local"
|
||||
elif self.uiLoadBalanceCheckBox.isChecked():
|
||||
server = next(iter(Servers.instance()))
|
||||
if not server:
|
||||
QtGui.QMessageBox.critical(self, "IOS router", "No remote server available!")
|
||||
return
|
||||
server = "{}:{}".format(server.host, server.port)
|
||||
else:
|
||||
server = self.uiRemoteServersComboBox.currentText()
|
||||
elif self.uiRemoteRadioButton.isChecked():
|
||||
if self.uiLoadBalanceCheckBox.isChecked():
|
||||
server = next(iter(Servers.instance()))
|
||||
if not server:
|
||||
QtGui.QMessageBox.critical(self, "IOS router", "No remote server available!")
|
||||
return
|
||||
server = "{}:{}".format(server.host, server.port)
|
||||
else:
|
||||
server = self.uiRemoteServersComboBox.currentText()
|
||||
else: # Cloud is selected
|
||||
server = "cloud"
|
||||
|
||||
settings = {
|
||||
"name": self.uiNameLineEdit.text(),
|
||||
|
||||
@@ -263,6 +263,9 @@ class Router(Node):
|
||||
if "chassis" in initial_settings:
|
||||
params["chassis"] = self._settings["chassis"] = initial_settings.pop("chassis")
|
||||
|
||||
#TODO: make this only send when dealing with cloud
|
||||
params["cloud_path"] = "images/IOS"
|
||||
|
||||
# other initial settings will be applied when the router has been created
|
||||
if initial_settings:
|
||||
self._inital_settings = initial_settings
|
||||
|
||||
@@ -32,6 +32,7 @@ from gns3.main_window import MainWindow
|
||||
from gns3.utils.progress_dialog import ProgressDialog
|
||||
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
|
||||
from gns3.dialogs.configuration_dialog import ConfigurationDialog
|
||||
from gns3.cloud.utils import UploadFileThread
|
||||
|
||||
from ..utils.decompress_ios import isIOSCompressed
|
||||
from ..utils.decompress_ios_thread import DecompressIOSThread
|
||||
@@ -127,6 +128,26 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
|
||||
"system_id": "FTX0945W0MY",
|
||||
"server": ios_settings["server"]}
|
||||
|
||||
if ios_settings["server"] == 'cloud':
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
log.debug(ios_settings["image"])
|
||||
# Start uploading the image to cloud files
|
||||
|
||||
self._upload_image_progress_dialog = QtGui.QProgressDialog("Uploading image file {}".format(ios_settings['image']), "Cancel", 0, 0, parent=self)
|
||||
self._upload_image_progress_dialog.setWindowModality(QtCore.Qt.WindowModal)
|
||||
self._upload_image_progress_dialog.setWindowTitle("IOS image upload")
|
||||
self._upload_image_progress_dialog.show()
|
||||
try:
|
||||
upload_thread = UploadFileThread(MainWindow.instance().cloudSettings(), self._ios_routers[key])
|
||||
upload_thread.completed_callback = self._imageUploadComplete
|
||||
upload_thread.start()
|
||||
except Exception as e:
|
||||
self._upload_image_progress_dialog.reject()
|
||||
log.error(e)
|
||||
QtGui.QMessageBox.critical(self, "IOS image upload", "Error uploading IOS image: {}".format(e))
|
||||
|
||||
if ios_settings["platform"] == "c7200":
|
||||
self._ios_routers[key]["midplane"] = "vxr"
|
||||
self._ios_routers[key]["npe"] = "npe-400"
|
||||
@@ -151,6 +172,11 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
|
||||
self._items.append(item)
|
||||
self.uiIOSRoutersTreeWidget.setCurrentItem(item)
|
||||
|
||||
def _imageUploadComplete(self):
|
||||
if self._upload_image_progress_dialog.wasCanceled():
|
||||
return
|
||||
self._upload_image_progress_dialog.accept()
|
||||
|
||||
def _iosRouterEditSlot(self):
|
||||
"""
|
||||
Edits an IOS router.
|
||||
|
||||
@@ -250,7 +250,7 @@ class Servers(QtCore.QObject):
|
||||
def getRemoteServer(self, host, port):
|
||||
|
||||
for server in self._remote_servers.values():
|
||||
if server.host == host and server.port == port:
|
||||
if server.host == host and int(server.port) == int(port):
|
||||
return server
|
||||
|
||||
return self._addRemoteServer(host, port)
|
||||
@@ -291,7 +291,7 @@ class Servers(QtCore.QObject):
|
||||
|
||||
return self._remote_servers
|
||||
|
||||
def getCloudServer(self, host, port, ca_file):
|
||||
def getCloudServer(self, host, port, ca_file, auth_user, auth_password):
|
||||
"""
|
||||
Return a websocket connection to the cloud server, creating one if none exists.
|
||||
|
||||
@@ -302,13 +302,13 @@ class Servers(QtCore.QObject):
|
||||
"""
|
||||
|
||||
for server in self._remote_servers.values():
|
||||
if server.host == host and server.port == port:
|
||||
if server.host == host and int(server.port) == int(port):
|
||||
return server
|
||||
|
||||
heartbeat_freq = self._settings.value("heartbeat_freq", DEFAULT_HEARTBEAT_FREQ)
|
||||
return self._addCloudServer(host, port, ca_file, heartbeat_freq)
|
||||
|
||||
def _addCloudServer(self, host, port, ca_file, heartbeat_freq):
|
||||
return self._addCloudServer(host, port, ca_file, auth_user, auth_password, heartbeat_freq)
|
||||
|
||||
def _addCloudServer(self, host, port, ca_file, auth_user, auth_password, heartbeat_freq):
|
||||
"""
|
||||
Create a websocket connection to the specified cloud server
|
||||
|
||||
@@ -323,12 +323,20 @@ class Servers(QtCore.QObject):
|
||||
url = "wss://{host}:{port}".format(host=host, port=port)
|
||||
log.debug('Starting SecureWebSocketClient url={}'.format(url))
|
||||
log.debug('Starting SecureWebSocketClient ca_file={}'.format(ca_file))
|
||||
server = SecureWebSocketClient(url, ca_file)
|
||||
server = SecureWebSocketClient(url)
|
||||
server.setSecureOptions(ca_file, auth_user, auth_password)
|
||||
server.setCloud(True)
|
||||
server.enableHeartbeatsAt(heartbeat_freq)
|
||||
self._cloud_servers[host] = server
|
||||
log.info("new remote server connection {} registered".format(url))
|
||||
return server
|
||||
|
||||
def anyCloudServer(self):
|
||||
# Return the first server for now
|
||||
for key, value in self._cloud_servers.items():
|
||||
return value
|
||||
return None
|
||||
|
||||
def __iter__(self):
|
||||
"""
|
||||
Creates a round-robin system to pick up a remote server.
|
||||
|
||||
@@ -40,9 +40,43 @@ from pkg_resources import parse_version
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
TopologyInstance = namedtuple("TopologyInstance",
|
||||
["name", "id", "size_id", "image_id", "private_key", "public_key"],
|
||||
verbose=False)
|
||||
|
||||
# TopologyInstance = namedtuple("TopologyInstance",
|
||||
# ["name", "id", "size_id", "image_id", "private_key", "public_key",
|
||||
# "host", "port", "ssl_ca", "ssl_ca_file"],
|
||||
# verbose=False)
|
||||
|
||||
class TopologyInstance:
|
||||
def __init__(self, name, id, size_id, image_id, private_key, public_key,
|
||||
host=None, port=None, ssl_ca=None, ssl_ca_file=None):
|
||||
# host, port, ssl_ca and ssl_ca_file are not known when the instance is created.
|
||||
# They will typically be set at a later point in time.
|
||||
self.name = name
|
||||
self.id = id
|
||||
self.size_id = size_id
|
||||
self.image_id = image_id
|
||||
self.public_key = public_key
|
||||
self.private_key = private_key
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.ssl_ca = ssl_ca
|
||||
self.ssl_ca_file = ssl_ca_file
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return ["name", "id", "size_id", "image_id", "private_key", "public_key",
|
||||
"host", "port", "ssl_ca", "ssl_ca_file"]
|
||||
|
||||
def set_later_attributes(self, host, port, ssl_ca, ssl_ca_file):
|
||||
"""
|
||||
Set attributes that are not known at the time of cloud instance creation.
|
||||
"""
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.ssl_ca = ssl_ca
|
||||
self.ssl_ca_file = ssl_ca_file
|
||||
|
||||
|
||||
|
||||
|
||||
class Topology(object):
|
||||
@@ -203,13 +237,16 @@ class Topology(object):
|
||||
if image in self._images:
|
||||
self._images.remove(image)
|
||||
|
||||
def addInstance(self, name, id, size_id, image_id, private_key, public_key):
|
||||
def addInstance(self, name, id, size_id, image_id, private_key, public_key,
|
||||
host=None, port=None, ssl_ca=None, ssl_ca_file=None):
|
||||
"""
|
||||
Add an instance to this cloud topology
|
||||
"""
|
||||
|
||||
i = TopologyInstance(name=name, id=id, size_id=size_id, image_id=image_id,
|
||||
private_key=private_key, public_key=public_key)
|
||||
private_key=private_key, public_key=public_key, host=host,
|
||||
port=port, ssl_ca=ssl_ca, ssl_ca_file=ssl_ca_file)
|
||||
|
||||
self._instances.append(i)
|
||||
|
||||
def removeInstance(self, id):
|
||||
@@ -236,6 +273,10 @@ class Topology(object):
|
||||
if instance.id == id:
|
||||
return instance
|
||||
|
||||
def anyInstance(self):
|
||||
# For now, just return the first instance
|
||||
return self._instances[0]
|
||||
|
||||
def nodes(self):
|
||||
"""
|
||||
Returns all the nodes in this topology.
|
||||
|
||||
@@ -56,6 +56,7 @@ class WebSocketClient(WebSocketBaseClient):
|
||||
self.callbacks = {}
|
||||
self._connected = False
|
||||
self._local = False
|
||||
self._cloud = False
|
||||
self._version = ""
|
||||
self._fd_notifier = None
|
||||
self._heartbeat_timer = None
|
||||
@@ -108,6 +109,12 @@ class WebSocketClient(WebSocketBaseClient):
|
||||
|
||||
return self._local
|
||||
|
||||
def setCloud(self, value):
|
||||
self._cloud = value
|
||||
|
||||
def isCloud(self):
|
||||
return self._cloud
|
||||
|
||||
def opened(self):
|
||||
"""
|
||||
Called when the connection with the server is successful.
|
||||
@@ -141,7 +148,7 @@ class WebSocketClient(WebSocketBaseClient):
|
||||
except OSError:
|
||||
raise
|
||||
except Exception as e:
|
||||
log.error("could to connect {}: {}".format(self.url, e))
|
||||
log.error("could not to connect {}: {}".format(self.url, e))
|
||||
raise OSError("Websocket exception {}: {}".format(type(e), e))
|
||||
|
||||
def check_server_version(self):
|
||||
@@ -353,28 +360,57 @@ class WebSocketClient(WebSocketBaseClient):
|
||||
|
||||
|
||||
class SecureWebSocketClient(WebSocketClient):
|
||||
def connect(self, ca_file=''):
|
||||
def __init__(self, url, protocols=None, extensions=None,
|
||||
heartbeat_freq=None, ssl_options=None, headers=None):
|
||||
|
||||
self.use_auth = True
|
||||
self.use_ssl = True
|
||||
self.use_ssl = False
|
||||
|
||||
self.ca_file = ca_file
|
||||
self.login_url = "https://{host}:{port}/login".format(host=self.host, port=self.port)
|
||||
self.version_url = "https://{host}:{port}/version".format(host=self.host, port=self.port)
|
||||
self.websocket_url = "wss://{host}:{port}".format(host=self.host, port=self.port)
|
||||
self.auth_user = 'test123'
|
||||
self.auth_password = 'test456'
|
||||
# The url has to be set before the constructor is called
|
||||
scheme, rest = url.split(':', 1)
|
||||
if self.use_ssl:
|
||||
url = "wss:{}".format(rest)
|
||||
else:
|
||||
url = "ws:{}".format(rest)
|
||||
|
||||
WebSocketClient.__init__(self, url,
|
||||
protocols,
|
||||
extensions,
|
||||
heartbeat_freq,
|
||||
ssl_options,
|
||||
headers)
|
||||
|
||||
|
||||
def setSecureOptions(self, ca_file, auth_user, auth_password):
|
||||
self._ca_file = ca_file
|
||||
self._auth_user = auth_user
|
||||
self._auth_password = auth_password
|
||||
|
||||
def connect(self):
|
||||
log.debug('In SecureWebSocketClient.connect()')
|
||||
|
||||
import ssl
|
||||
import socket
|
||||
|
||||
if self.use_ssl:
|
||||
self.login_url = "https://{host}:{port}/login".format(host=self.host, port=self.port)
|
||||
self.version_url = "https://{host}:{port}/version".format(host=self.host, port=self.port)
|
||||
self.ssl_options = {'ca_certs': self._ca_file}
|
||||
context = ssl._create_stdlib_context(cert_reqs=ssl.CERT_REQUIRED, cafile=self._ca_file)
|
||||
self.https_handler = urllib.request.HTTPSHandler(context=context, check_hostname=False)
|
||||
else:
|
||||
self.login_url = "http://{host}:{port}/login".format(host=self.host, port=self.port)
|
||||
self.version_url = "http://{host}:{port}/version".format(host=self.host, port=self.port)
|
||||
self.ssl_options = {}
|
||||
self.https_handler = urllib.request.HTTPHandler()
|
||||
|
||||
self.ssl_options = {'ca_certs': self.ca_file}
|
||||
self.https_handler = urllib.request.HTTPSHandler(check_hostname=False)
|
||||
self.cookie_processor = urllib.request.HTTPCookieProcessor()
|
||||
self.opener = urllib.request.build_opener(self.https_handler, self.cookie_processor)
|
||||
|
||||
self.check_server_version()
|
||||
|
||||
data = urllib.parse.urlencode({'name': self.auth_user, 'password': self.auth_password}).encode('utf-8')
|
||||
urllib.request.install_opener(self.opener)
|
||||
f = urllib.request.urlopen(self.login_url, data, cafile=self.ca_file)
|
||||
log.debug(self.cookie_processor.cookiejar)
|
||||
data = urllib.parse.urlencode({'name': self._auth_user, 'password': self._auth_password}).encode('utf-8')
|
||||
f = self.opener.open(self.login_url, data, socket._GLOBAL_DEFAULT_TIMEOUT)
|
||||
log.debug('login result: {}'.format(f.read()))
|
||||
|
||||
self._connect()
|
||||
log.debug(self.sock)
|
||||
|
||||
47
scripts/ssh_to_server.py
Normal file
47
scripts/ssh_to_server.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import os
|
||||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
QtCore.QSettings.setDefaultFormat(QtCore.QSettings.IniFormat)
|
||||
|
||||
app = QtGui.QApplication([])
|
||||
app.setOrganizationName("GNS3")
|
||||
app.setOrganizationDomain("gns3.net")
|
||||
app.setApplicationName("GNS3")
|
||||
|
||||
settings = QtCore.QSettings()
|
||||
|
||||
print('Reading config from {}'.format(QtCore.QSettings().fileName()))
|
||||
|
||||
def read_cloud_settings():
|
||||
settings = QtCore.QSettings()
|
||||
settings.beginGroup("CloudInstances")
|
||||
|
||||
# Load the instances
|
||||
size = settings.beginReadArray("cloud_instance")
|
||||
for index in range(0, size):
|
||||
settings.setArrayIndex(index)
|
||||
name = settings.value('name')
|
||||
host = settings.value('host')
|
||||
private_key = settings.value('private_key')
|
||||
public_key = settings.value('public_key')
|
||||
|
||||
# For now, just use the first system.
|
||||
return name, host, private_key, public_key
|
||||
|
||||
|
||||
|
||||
name, host, private_key, public_key = read_cloud_settings()
|
||||
|
||||
print(name)
|
||||
print(host)
|
||||
print(private_key)
|
||||
print(public_key)
|
||||
|
||||
|
||||
open('/tmp/id_rsa.pub', 'w').write(public_key)
|
||||
open('/tmp/id_rsa', 'w').write(private_key)
|
||||
os.system('chmod 0600 /tmp/id_rsa')
|
||||
|
||||
cmd = 'ssh -i /tmp/id_rsa root@{}'.format(host)
|
||||
os.system(cmd)
|
||||
|
||||
Reference in New Issue
Block a user