mirror of
https://github.com/GNS3/gns3-gui.git
synced 2026-05-17 00:46:01 +03:00
Add support for running IOU devices on cloud instances
This commit is contained in:
@@ -67,9 +67,7 @@ class ListInstancesThread(QThread):
|
||||
def run(self):
|
||||
try:
|
||||
instances = self._provider.list_instances()
|
||||
log.debug('Instance list:')
|
||||
for instance in instances:
|
||||
log.debug(' name={}, state={}'.format(instance.name, instance.state))
|
||||
log.debug('Instance list: {}'.format([(i.name, i.state) for i in instances]))
|
||||
self.instancesReady.emit(instances)
|
||||
except Exception as e:
|
||||
log.info('list_instances error: {}'.format(e))
|
||||
@@ -120,28 +118,42 @@ class StartGNS3ServerThread(QThread):
|
||||
# This is for testing without pushing to github
|
||||
# commands = '''
|
||||
# DEBIAN_FRONTEND=noninteractive dpkg --configure -a
|
||||
# DEBIAN_FRONTEND=noninteractive dpkg --add-architecture i386 # for iou
|
||||
# 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
|
||||
# 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
|
||||
# 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
|
||||
# ln -sf /usr/bin/dynamips /usr/local/bin/dynamips # for ios
|
||||
# wget 'https://github.com/GNS3/iouyap/releases/download/0.95/iouyap.tar.gz' # for iou
|
||||
# python -c 'import struct; open("/etc/hostid", "w").write(struct.pack("i", 00000000))' # set hostid for iou
|
||||
# hostname gns3-iouvm # set hostname for iou
|
||||
# tar xzf iouyap.tar.gz -C /usr/local/bin
|
||||
# killall python3 gns3server gns3dms
|
||||
# '''
|
||||
|
||||
commands = '''
|
||||
DEBIAN_FRONTEND=noninteractive dpkg --configure -a
|
||||
DEBIAN_FRONTEND=noninteractive dpkg --add-architecture i386 # for iou
|
||||
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
|
||||
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
|
||||
ln -sf /usr/bin/dynamips /usr/local/bin/dynamips # for ios
|
||||
wget 'https://github.com/GNS3/iouyap/releases/download/0.95/iouyap.tar.gz' # for iou
|
||||
tar xzf iouyap.tar.gz -C /usr/local/bin #
|
||||
python -c 'import struct; open("/etc/hostid", "w").write(struct.pack("i", 00000000))' # set hostid for iou
|
||||
hostname gns3-iouvm # set hostname for iou
|
||||
killall python3 gns3server gns3dms
|
||||
'''
|
||||
|
||||
@@ -334,19 +346,22 @@ class UploadFileThread(QThread):
|
||||
|
||||
completed = pyqtSignal()
|
||||
|
||||
def __init__(self, cloud_settings, router_settings):
|
||||
def __init__(self, cloud_settings, router_settings, upload_path):
|
||||
super().__init__()
|
||||
self._cloud_settings = cloud_settings
|
||||
self._router_settings = router_settings
|
||||
self._upload_path = upload_path
|
||||
|
||||
def run(self):
|
||||
disk_path = self._router_settings['path']
|
||||
filename = self._router_settings['image']
|
||||
# Eg: images/IOS/c3745.img
|
||||
upload_path = '{}/{}'.format(self._upload_path, filename)
|
||||
|
||||
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/IOS/{}'.format(filename))
|
||||
provider.upload_file(disk_path, upload_path)
|
||||
|
||||
self._cloud_settings['image'] = filename
|
||||
|
||||
|
||||
@@ -55,6 +55,9 @@ class CloudInstances(QtCore.QObject):
|
||||
def instances(self):
|
||||
return self._instances
|
||||
|
||||
def clear(self):
|
||||
self._instances.clear()
|
||||
|
||||
def add(self, topology_instance):
|
||||
self._instances.append(topology_instance)
|
||||
|
||||
|
||||
@@ -138,6 +138,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
||||
self._updateRecentFileActions()
|
||||
|
||||
self._cloud_provider = None
|
||||
CloudInstances.instance().clear()
|
||||
CloudInstances.instance().load()
|
||||
|
||||
# set the window icon
|
||||
|
||||
@@ -142,7 +142,8 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
|
||||
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 = UploadFileThread(MainWindow.instance().cloudSettings(), self._ios_routers[key],
|
||||
'images/IOS')
|
||||
upload_thread.completed.connect(self._imageUploadComplete)
|
||||
upload_thread.start()
|
||||
except Exception as e:
|
||||
|
||||
@@ -407,7 +407,12 @@ class IOU(Module):
|
||||
settings["nvram"] = self._iou_devices[iouimage]["nvram"]
|
||||
settings["ethernet_adapters"] = self._iou_devices[iouimage]["ethernet_adapters"]
|
||||
settings["serial_adapters"] = self._iou_devices[iouimage]["serial_adapters"]
|
||||
node.setup(iou_path, initial_settings=settings)
|
||||
|
||||
if node.server().isCloud():
|
||||
settings["cloud_path"] = "images/IOU"
|
||||
node.setup(self._iou_devices[iouimage]["image"], initial_settings=settings)
|
||||
else:
|
||||
node.setup(iou_path, initial_settings=settings)
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
|
||||
@@ -27,6 +27,7 @@ from gns3.qt import QtGui
|
||||
from gns3.node import Node
|
||||
from gns3.servers import Servers
|
||||
|
||||
from ....settings import ENABLE_CLOUD
|
||||
from ..ui.iou_device_wizard_ui import Ui_IOUDeviceWizard
|
||||
from .. import IOU
|
||||
|
||||
@@ -69,6 +70,10 @@ class IOUDeviceWizard(QtGui.QWizard, Ui_IOUDeviceWizard):
|
||||
else:
|
||||
self.uiIOUImageToolButton.setEnabled(False)
|
||||
|
||||
if not ENABLE_CLOUD:
|
||||
self.uiCloudRadioButton.hide()
|
||||
|
||||
|
||||
def _remoteServerToggledSlot(self, checked):
|
||||
"""
|
||||
Slot for when the remote server radio button is toggled.
|
||||
@@ -140,12 +145,6 @@ class IOUDeviceWizard(QtGui.QWizard, Ui_IOUDeviceWizard):
|
||||
Validates the server.
|
||||
"""
|
||||
|
||||
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()
|
||||
for iou_device in self._iou_devices.values():
|
||||
@@ -188,14 +187,17 @@ class IOUDeviceWizard(QtGui.QWizard, Ui_IOUDeviceWizard):
|
||||
|
||||
if IOU.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, "IOU device", "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, "IOU device", "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(),
|
||||
|
||||
@@ -133,6 +133,9 @@ class IOUDevice(Node):
|
||||
if console:
|
||||
params["console"] = self._settings["console"] = console
|
||||
|
||||
if "cloud_path" in initial_settings:
|
||||
params["cloud_path"] = self._settings["cloud_path"] = initial_settings.pop("cloud_path")
|
||||
|
||||
# other initial settings will be applied when the router has been created
|
||||
if initial_settings:
|
||||
self._inital_settings = initial_settings
|
||||
|
||||
@@ -29,6 +29,7 @@ from gns3.qt import QtCore, QtGui
|
||||
from gns3.main_window import MainWindow
|
||||
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
|
||||
from gns3.dialogs.configuration_dialog import ConfigurationDialog
|
||||
from gns3.cloud.utils import UploadFileThread
|
||||
|
||||
from .. import IOU
|
||||
from ..ui.iou_device_preferences_page_ui import Ui_IOUDevicePreferencesPageWidget
|
||||
@@ -110,6 +111,33 @@ class IOUDevicePreferencesPage(QtGui.QWidget, Ui_IOUDevicePreferencesPageWidget)
|
||||
self._items.append(item)
|
||||
self.uiIOUDevicesTreeWidget.setCurrentItem(item)
|
||||
|
||||
if new_device_settings["server"] == 'cloud':
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
log.debug(new_device_settings["image"])
|
||||
# Start uploading the image to cloud files
|
||||
|
||||
self._upload_image_progress_dialog = QtGui.QProgressDialog(
|
||||
"Uploading image file {}".format(new_device_settings['image']), "Cancel", 0, 0, parent=self)
|
||||
self._upload_image_progress_dialog.setWindowModality(QtCore.Qt.WindowModal)
|
||||
self._upload_image_progress_dialog.setWindowTitle("IOU image upload")
|
||||
self._upload_image_progress_dialog.show()
|
||||
try:
|
||||
upload_thread = UploadFileThread(MainWindow.instance().cloudSettings(), self._iou_devices[key],
|
||||
'images/IOU')
|
||||
upload_thread.completed.connect(self._imageUploadComplete)
|
||||
upload_thread.start()
|
||||
except Exception as e:
|
||||
self._upload_image_progress_dialog.reject()
|
||||
log.error(e)
|
||||
QtGui.QMessageBox.critical(self, "IOU image upload", "Error uploading IOU image: {}".format(e))
|
||||
|
||||
def _imageUploadComplete(self):
|
||||
if self._upload_image_progress_dialog.wasCanceled():
|
||||
return
|
||||
self._upload_image_progress_dialog.accept()
|
||||
|
||||
def _iouDeviceEditSlot(self):
|
||||
"""
|
||||
Edits an IOU device.
|
||||
|
||||
@@ -7,32 +7,79 @@ Right now it only connects to the first cloud server listed in the config
|
||||
file.
|
||||
"""
|
||||
|
||||
import getopt
|
||||
import os
|
||||
import sys
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
|
||||
if sys.platform.startswith('win') or sys.platform.startswith('darwin'):
|
||||
QtCore.QSettings.setDefaultFormat(QtCore.QSettings.IniFormat)
|
||||
SCRIPT_NAME = os.path.basename(__file__)
|
||||
|
||||
app = QtGui.QApplication([])
|
||||
app.setOrganizationName("GNS3")
|
||||
app.setOrganizationDomain("gns3.net")
|
||||
app.setApplicationName("GNS3")
|
||||
|
||||
settings = QtCore.QSettings()
|
||||
def parse_cmd_line(argv):
|
||||
"""
|
||||
Parse command line arguments
|
||||
|
||||
if not os.path.isfile(QtCore.QSettings().fileName()):
|
||||
print('Config file {} not found! Aborting...'.format(QtCore.QSettings().fileName()))
|
||||
sys.exit(1)
|
||||
argv: Passed in sys.argv
|
||||
"""
|
||||
|
||||
|
||||
usage = """
|
||||
USAGE: %s [-l] [-s <server_num>]
|
||||
|
||||
If no options are supplied a connection to server 1 will be opened.
|
||||
Options:
|
||||
|
||||
-h, --help Display this menu :)
|
||||
-l, --list List instances that are tracked
|
||||
-s, --server-num Connect to this server number (1-indexed)
|
||||
""" % (SCRIPT_NAME)
|
||||
|
||||
short_args = "hls:"
|
||||
long_args = ("help", "list", "server-num=")
|
||||
try:
|
||||
opts, extra_opts = getopt.getopt(argv[1:], short_args, long_args)
|
||||
except getopt.GetoptError as e:
|
||||
print("Unrecognized command line option or missing required argument: %s" %(e))
|
||||
print(usage)
|
||||
sys.exit(2)
|
||||
|
||||
cmd_line_option_list = {'action': 'ssh', 'server': '1'}
|
||||
|
||||
for opt, val in opts:
|
||||
if opt in ("-h", "--help"):
|
||||
print(usage)
|
||||
sys.exit(0)
|
||||
elif opt in ("-l", "--list"):
|
||||
cmd_line_option_list['action'] = 'list'
|
||||
elif opt in ("-s", "--server-num"):
|
||||
cmd_line_option_list['server'] = val
|
||||
|
||||
return cmd_line_option_list
|
||||
|
||||
|
||||
def setup():
|
||||
if sys.platform.startswith('win') or sys.platform.startswith('darwin'):
|
||||
QtCore.QSettings.setDefaultFormat(QtCore.QSettings.IniFormat)
|
||||
|
||||
app = QtGui.QApplication([])
|
||||
app.setOrganizationName("GNS3")
|
||||
app.setOrganizationDomain("gns3.net")
|
||||
app.setApplicationName("GNS3")
|
||||
|
||||
if not os.path.isfile(QtCore.QSettings().fileName()):
|
||||
print('Config file {} not found! Aborting...'.format(QtCore.QSettings().fileName()))
|
||||
sys.exit(1)
|
||||
|
||||
print('Config file: {}'.format(QtCore.QSettings().fileName()))
|
||||
|
||||
print('Reading config file {}...'.format(QtCore.QSettings().fileName()))
|
||||
|
||||
def read_cloud_settings():
|
||||
settings = QtCore.QSettings()
|
||||
settings.beginGroup("CloudInstances")
|
||||
|
||||
instances = []
|
||||
# Load the instances
|
||||
size = settings.beginReadArray("cloud_instance")
|
||||
for index in range(0, size):
|
||||
@@ -42,24 +89,43 @@ def read_cloud_settings():
|
||||
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
|
||||
raise Exception("Could not find any servers")
|
||||
|
||||
instances.append((name, host, private_key, public_key))
|
||||
|
||||
name, host, private_key, public_key = read_cloud_settings()
|
||||
if len(instances) == 0:
|
||||
raise Exception("Could not find any servers")
|
||||
|
||||
print('Instance name: {}'.format(name))
|
||||
print('Host ip: {}'.format(host))
|
||||
return instances
|
||||
|
||||
public_key_path = '/tmp/id_rsa.pub'
|
||||
open(public_key_path, 'w').write(public_key)
|
||||
private_key_path = '/tmp/id_rsa'
|
||||
open(private_key_path, 'w').write(private_key)
|
||||
cmd = 'chmod 0600 {}'.format(private_key_path)
|
||||
os.system(cmd)
|
||||
print('Per-instance ssh keys written to {}'.format(private_key_path))
|
||||
|
||||
cmd = 'ssh -i /tmp/id_rsa root@{}'.format(host)
|
||||
print(cmd)
|
||||
os.system(cmd)
|
||||
def main():
|
||||
options = parse_cmd_line(sys.argv)
|
||||
setup()
|
||||
instances = read_cloud_settings()
|
||||
|
||||
if options['action'] == 'ssh':
|
||||
name, host, private_key, public_key = instances[int(options['server'])-1]
|
||||
print('Instance name: {}'.format(name))
|
||||
print('Host ip: {}'.format(host))
|
||||
|
||||
public_key_path = '/tmp/id_rsa.pub'
|
||||
open(public_key_path, 'w').write(public_key)
|
||||
private_key_path = '/tmp/id_rsa'
|
||||
open(private_key_path, 'w').write(private_key)
|
||||
cmd = 'chmod 0600 {}'.format(private_key_path)
|
||||
os.system(cmd)
|
||||
print('Per-instance ssh keys written to {}'.format(private_key_path))
|
||||
|
||||
cmd = 'ssh -i /tmp/id_rsa root@{}'.format(host)
|
||||
print(cmd)
|
||||
os.system(cmd)
|
||||
elif options['action'] == 'list':
|
||||
print('ID Name')
|
||||
for idx, info in enumerate(instances):
|
||||
name, host, private_key, public_key = info
|
||||
print('{:2d} {}'.format(idx+1, name))
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user