diff --git a/gns3/cloud/utils.py b/gns3/cloud/utils.py index 036cc532..37aaa036 100644 --- a/gns3/cloud/utils.py +++ b/gns3/cloud/utils.py @@ -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 diff --git a/gns3/cloud_instances.py b/gns3/cloud_instances.py index e8dbd21f..c8629348 100644 --- a/gns3/cloud_instances.py +++ b/gns3/cloud_instances.py @@ -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) diff --git a/gns3/main_window.py b/gns3/main_window.py index 697722d6..63f23840 100644 --- a/gns3/main_window.py +++ b/gns3/main_window.py @@ -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 diff --git a/gns3/modules/dynamips/pages/ios_router_preferences_page.py b/gns3/modules/dynamips/pages/ios_router_preferences_page.py index 31601b29..7f860a0d 100644 --- a/gns3/modules/dynamips/pages/ios_router_preferences_page.py +++ b/gns3/modules/dynamips/pages/ios_router_preferences_page.py @@ -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: diff --git a/gns3/modules/iou/__init__.py b/gns3/modules/iou/__init__.py index 9098e733..ca2d168d 100644 --- a/gns3/modules/iou/__init__.py +++ b/gns3/modules/iou/__init__.py @@ -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): """ diff --git a/gns3/modules/iou/dialogs/iou_device_wizard.py b/gns3/modules/iou/dialogs/iou_device_wizard.py index 8a37f64e..04d7980d 100644 --- a/gns3/modules/iou/dialogs/iou_device_wizard.py +++ b/gns3/modules/iou/dialogs/iou_device_wizard.py @@ -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(), diff --git a/gns3/modules/iou/iou_device.py b/gns3/modules/iou/iou_device.py index 02eef818..b5bff44d 100644 --- a/gns3/modules/iou/iou_device.py +++ b/gns3/modules/iou/iou_device.py @@ -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 diff --git a/gns3/modules/iou/pages/iou_device_preferences_page.py b/gns3/modules/iou/pages/iou_device_preferences_page.py index 5c953ce9..69fbe65d 100644 --- a/gns3/modules/iou/pages/iou_device_preferences_page.py +++ b/gns3/modules/iou/pages/iou_device_preferences_page.py @@ -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. diff --git a/scripts/ssh_to_server.py b/scripts/ssh_to_server.py index dbf124a2..c810144c 100644 --- a/scripts/ssh_to_server.py +++ b/scripts/ssh_to_server.py @@ -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 ] + + 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()) \ No newline at end of file