Add support for running IOU devices on cloud instances

This commit is contained in:
Jerry Seutter
2014-11-06 16:07:40 -07:00
parent 9f3d831d4c
commit 10dfd59203
9 changed files with 175 additions and 51 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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:

View File

@@ -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):
"""

View File

@@ -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(),

View File

@@ -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

View File

@@ -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.

View File

@@ -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())