From 36e539382c05b722f93dffbd99bc54c4bd8fdc44 Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 6 Sep 2014 00:51:43 -0600 Subject: [PATCH 01/11] Added support for cloud.conf file and startup script --- gns3server/start_server.py | 240 +++++++++++++++++++++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100644 gns3server/start_server.py diff --git a/gns3server/start_server.py b/gns3server/start_server.py new file mode 100644 index 00000000..b27f3af8 --- /dev/null +++ b/gns3server/start_server.py @@ -0,0 +1,240 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2013 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 . + +# __version__ is a human-readable version number. + +# __version_info__ is a four-tuple for programmatic comparison. The first +# three numbers are the components of the version number. The fourth +# is zero for an official release, positive for a development branch, +# or negative for a release candidate or beta (after the base version +# number has been incremented) + +""" +Startup script for GNS3 Server Cloud Instance +""" + +import os +import sys +import configparser +import getopt +import datetime +import signal +from logging.handlers import * +from os.path import expanduser +from gns3server.config import Config +import ast +import subprocess +import uuid + +SCRIPT_NAME = os.path.basename(__file__) + +#Is the full path when used as an import +SCRIPT_PATH = os.path.dirname(__file__) + +if not SCRIPT_PATH: + SCRIPT_PATH = os.path.join(os.path.dirname(os.path.abspath( + sys.argv[0]))) + + +LOG_NAME = "gns3-startup" +log = None + +usage = """ +USAGE: %s + +Options: + + -d, --debug Enable debugging + -v, --verbose Enable verbose logging + -h, --help Display this menu :) + + --data Python dict of data to be written to the config file: + " { 'gns3' : 'Is AWESOME' } " + +""" % (SCRIPT_NAME) + +# Parse cmd line options +def parse_cmd_line(argv): + """ + Parse command line arguments + + argv: Pass in cmd line arguments + """ + + short_args = "dvh" + long_args = ("debug", + "verbose", + "help", + "data=", + ) + 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 = {} + cmd_line_option_list["debug"] = False + cmd_line_option_list["verbose"] = True + cmd_line_option_list["data"] = None + + if sys.platform == "linux": + cmd_line_option_list['syslog'] = "/dev/log" + elif sys.platform == "osx": + cmd_line_option_list['syslog'] = "/var/run/syslog" + else: + cmd_line_option_list['syslog'] = ('localhost',514) + + for opt, val in opts: + if (opt in ("-h", "--help")): + print(usage) + sys.exit(0) + elif (opt in ("-d", "--debug")): + cmd_line_option_list["debug"] = True + elif (opt in ("-v", "--verbose")): + cmd_line_option_list["verbose"] = True + elif (opt in ("--data")): + cmd_line_option_list["data"] = ast.literal_eval(val) + + return cmd_line_option_list + + +def set_logging(cmd_options): + """ + Setup logging and format output for console and syslog + + Syslog is using the KERN facility + """ + log = logging.getLogger("%s" % (LOG_NAME)) + log_level = logging.INFO + log_level_console = logging.WARNING + + if cmd_options['verbose'] == True: + log_level_console = logging.INFO + + if cmd_options['debug'] == True: + log_level_console = logging.DEBUG + log_level = logging.DEBUG + + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + sys_formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s') + + console_log = logging.StreamHandler() + console_log.setLevel(log_level_console) + console_log.setFormatter(formatter) + + syslog_hndlr = SysLogHandler( + address=cmd_options['syslog'], + facility=SysLogHandler.LOG_KERN + ) + + syslog_hndlr.setFormatter(sys_formatter) + + log.setLevel(log_level) + log.addHandler(console_log) + log.addHandler(syslog_hndlr) + + return log + +def _generate_certs(): + cmd = [] + cmd.append("%s/cert_utils/create_cert.sh" % (SCRIPT_PATH)) + + log.debug("Generating certs ...") + output_raw = subprocess.check_output(cmd, shell=False, + stderr=subprocess.STDOUT) + + output_str = output_raw.decode("utf-8") + output = output_str.strip().split("\n") + log.debug(output) + return (output[-2], output[-1]) + +def _start_gns3server(): + cmd = [] + cmd.append("gns3server") + + log.debug("Starting gns3server ...") + subprocess.Popen(cmd, shell=False) + + +def main(): + + global log + options = parse_cmd_line(sys.argv) + log = set_logging(options) + + def _shutdown(signalnum=None, frame=None): + """ + Handles the SIGINT and SIGTERM event, inside of main so it has access to + the log vars. + """ + + log.info("Received shutdown signal") + sys.exit(0) + + + # Setup signal to catch Control-C / SIGINT and SIGTERM + signal.signal(signal.SIGINT, _shutdown) + signal.signal(signal.SIGTERM, _shutdown) + + client_data = {} + + config = Config.instance() + cfg = config.list_cloud_config_file() + cfg_path = os.path.dirname(cfg) + + try: + os.makedirs(cfg_path) + except FileExistsError: + pass + + (server_key, server_crt ) = _generate_certs() + + cloud_config = configparser.ConfigParser() + cloud_config['CLOUD_SERVER'] = {} + + if options['data']: + cloud_config['CLOUD_SERVER'] = options['data'] + + cloud_config['CLOUD_SERVER']['SSL_KEY'] = server_key + cloud_config['CLOUD_SERVER']['SSL_CRT'] = server_crt + cloud_config['CLOUD_SERVER']['SSL_ENABLED'] = 'yes' + cloud_config['CLOUD_SERVER']['WEB_USERNAME'] = str(uuid.uuid4()).upper()[0:8] + cloud_config['CLOUD_SERVER']['WEB_PASSWORD'] = str(uuid.uuid4()).upper()[0:8] + + with open(cfg, 'w') as cloud_config_file: + cloud_config.write(cloud_config_file) + + cloud_config_file.close() + + _start_gns3server() + + with open(server_crt, 'r') as cert_file: + cert_data = cert_file.readlines() + + cert_file.close() + + client_data['SSL_CRT_FILE'] = server_crt + client_data['SSL_CRT'] = cert_data + + print(client_data) + + +if __name__ == "__main__": + result = main() + sys.exit(result) From ef492d4690c518395ddb90c7cbfaff03e22f2616 Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 6 Sep 2014 20:46:06 -0600 Subject: [PATCH 02/11] Update gns3dms to support cloud.conf --- gns3dms/main.py | 34 +++++++++++++++++--------- gns3server/cert_utils/create_cert.sh | 5 +++- gns3server/config.py | 7 +++++- gns3server/modules/__init__.py | 3 ++- gns3server/modules/deadman/__init__.py | 12 ++++++--- gns3server/server.py | 32 ++++++++---------------- requirements.txt | 1 + 7 files changed, 54 insertions(+), 40 deletions(-) diff --git a/gns3dms/main.py b/gns3dms/main.py index bad64a44..e8d245d6 100644 --- a/gns3dms/main.py +++ b/gns3dms/main.py @@ -24,7 +24,7 @@ # number has been incremented) """ -Monitors communication with the GNS3 client via tmp file. Will terminate the instance if +Monitors communication with the GNS3 client via tmp file. Will terminate the instance if communication is lost. """ @@ -62,7 +62,7 @@ sys.path.append(EXTRA_LIB) import daemon -my_daemon = None +my_daemon = None usage = """ USAGE: %s @@ -73,14 +73,14 @@ Options: -v, --verbose Enable verbose logging -h, --help Display this menu :) - --cloud_api_key Rackspace API key + --cloud_api_key Rackspace API key --cloud_user_name --instance_id ID of the Rackspace instance to terminate - - --deadtime How long in seconds can the communication lose exist before we - shutdown this instance. - Default: + + --deadtime How long in seconds can the communication lose exist before we + shutdown this instance. + Default: Example --deadtime=3600 (60 minutes) --check-interval Defaults to --deadtime, used for debugging @@ -146,7 +146,7 @@ def parse_cmd_line(argv): cmd_line_option_list['syslog'] = ('localhost',514) - get_gns3secrets(cmd_line_option_list) + get_gns3config(cmd_line_option_list) for opt, val in opts: if (opt in ("-h", "--help")): @@ -202,7 +202,7 @@ def parse_cmd_line(argv): return cmd_line_option_list -def get_gns3secrets(cmd_line_option_list): +def get_gns3config(cmd_line_option_list): """ Load cloud credentials from .gns3secrets """ @@ -225,6 +225,15 @@ def get_gns3secrets(cmd_line_option_list): except configparser.NoSectionError: pass + cloud_config_file = "%s/.config/GNS3/cloud.conf" + if os.path.isfile(cloud_config_file) + config.read(cloud_config_file) + + try: + for key, value in config.items("CLOUD_SERVER"): + cmd_line_option_list[key] = value.strip() + except configparser.NoSectionError: + pass def set_logging(cmd_options): """ @@ -256,7 +265,7 @@ def set_logging(cmd_options): ) syslog_hndlr.setFormatter(sys_formatter) - + log.setLevel(log_level) log.addHandler(console_log) log.addHandler(syslog_hndlr) @@ -308,7 +317,7 @@ def monitor_loop(options): if delta.seconds > options["deadtime"]: log.warning("Deadtime exceeded, terminating instance ...") - #Terminate involes many layers of HTTP / API calls, lots of + #Terminate involes many layers of HTTP / API calls, lots of #different errors types could occur here. try: rksp = Rackspace(options) @@ -341,7 +350,8 @@ def main(): log.info("Received shutdown signal") options["shutdown"] = True - + sys.exit(0) + pid_file = "%s/.gns3ias.pid" % (expanduser("~")) if options["shutdown"]: diff --git a/gns3server/cert_utils/create_cert.sh b/gns3server/cert_utils/create_cert.sh index 57427088..5b2c8e28 100755 --- a/gns3server/cert_utils/create_cert.sh +++ b/gns3server/cert_utils/create_cert.sh @@ -68,7 +68,7 @@ emailAddress=gns3cert@gns3.com " # Generate the server private key -openssl genrsa -aes256 -out $DST_DIR/$DOMAIN.key -passout env:PASSPHRASE 2048 +openssl genrsa -aes256 -out $DOMAIN.key -passout env:PASSPHRASE 2048 fail_if_error $? #openssl rsa -outform der -in $DOMAIN.pem -out $DOMAIN.key -passin env:PASSPHRASE @@ -93,4 +93,7 @@ fail_if_error $? openssl x509 -req -days 3650 -in $DOMAIN.csr -signkey $DOMAIN.key -out $DOMAIN.crt fail_if_error $? +echo "${DST_DIR}${DOMAIN}.key" +echo "${DST_DIR}${DOMAIN}.crt" + cd $OLD_DIR \ No newline at end of file diff --git a/gns3server/config.py b/gns3server/config.py index cd2d07a1..caa9c0d4 100644 --- a/gns3server/config.py +++ b/gns3server/config.py @@ -62,16 +62,21 @@ class Config(object): # 5: server.conf in the current working directory home = os.path.expanduser("~") + self._cloud_config = os.path.join(home, ".config", appname, "cloud.conf") filename = "server.conf" self._files = [os.path.join(home, ".config", appname, filename), os.path.join(home, ".config", appname + ".conf"), os.path.join("/etc/xdg", appname, filename), os.path.join("/etc/xdg", appname + ".conf"), - filename] + filename, + self._cloud_config] self._config = configparser.ConfigParser() self.read_config() + def list_cloud_config_file(self): + return self._cloud_config + def read_config(self): """ Read the configuration files. diff --git a/gns3server/modules/__init__.py b/gns3server/modules/__init__.py index 5bd4c110..f38af25b 100644 --- a/gns3server/modules/__init__.py +++ b/gns3server/modules/__init__.py @@ -20,8 +20,9 @@ from .base import IModule from .dynamips import Dynamips from .vpcs import VPCS from .virtualbox import VirtualBox +from .deadman import DeadMan -MODULES = [Dynamips, VPCS, VirtualBox] +MODULES = [Dynamips, VPCS, VirtualBox, DeadMan] if sys.platform.startswith("linux"): # IOU runs only on Linux diff --git a/gns3server/modules/deadman/__init__.py b/gns3server/modules/deadman/__init__.py index ab81e971..288d5b2a 100644 --- a/gns3server/modules/deadman/__init__.py +++ b/gns3server/modules/deadman/__init__.py @@ -30,7 +30,7 @@ from gns3server.config import Config import logging log = logging.getLogger(__name__) -class DeadMan(): +class DeadMan(IModule): """ DeadMan module. @@ -51,10 +51,12 @@ class DeadMan(): self._heartbeat_file = "%s/heartbeat_file_for_gnsdms" % ( self._tempdir) + self.cloud_config = Config.instance().get_section_config("CLOUD_SERVER") + self._heartbeat_file = self.cloud_config["heartbeat_file"] + if 'heartbeat_file' in kwargs: self._heartbeat_file = kwargs['heartbeat_file'] - self._deadman_process = None self.start() @@ -63,8 +65,12 @@ class DeadMan(): Start a subprocess and return the object """ - cmd = [] + #gnsserver gets configuration options from cloud.conf. This is where + #the client adds specific cloud information. + #gns3dms also reads in cloud.conf. That is why we don't need to specific + #all the command line arguments here. + cmd = [] cmd.append("gns3dms") cmd.append("--file %s" % (self._heartbeat_file)) cmd.append("--background") diff --git a/gns3server/server.py b/gns3server/server.py index 275123ad..49223790 100644 --- a/gns3server/server.py +++ b/gns3server/server.py @@ -141,35 +141,20 @@ class Server(object): instance.start() # starts the new process - def _get_cert_info(self): - """ - Finds the cert and key file needed for SSL - """ - - home = expanduser("~") - ssl_dir = "%s/.conf/GNS3Certs/" % (home) - log.debug("Looking for SSL certs in: %s" % (ssl_dir)) - - keyfile = "%s/gns3server.localdomain.com.key" % (ssl_dir) - certfile = "%s/gns3server.localdomain.com.crt" % (ssl_dir) - - if os.path.isfile(keyfile) and os.path.isfile(certfile): - return { "certfile" : certfile, - "keyfile" : keyfile, - } - def run(self): """ Starts the Tornado web server and ZeroMQ server. """ # FIXME: debug mode! + cloud_config = Config.instance().get_section_config("CLOUD_SERVER") + settings = { "debug":True, "cookie_secret": base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes), "login_url": "/login", - "required_user" : "test123", - "required_pass" : "test456", + "required_user" : cloud_config['WEB_USERNAME'], + "required_pass" : cloud_config['WEB_PASSWORD'], } router = self._create_zmq_router() @@ -191,11 +176,14 @@ class Server(object): zmq.zmq_version())) kwargs = {"address": self._host} - ssl_options = self._get_cert_info() + if cloud_config["SSL_ENABLED"] == "yes": + ssl_options = { + "certfile" : cloud_config["SSL_CRT"], + "keyfile" : cloud_config["SSL_KEY"], + } - if ssl_options: log.info("Certs found - starting in SSL mode") - kwargs['ssl_options'] = ssl_options + kwargs["ssl_options"] = ssl_options if parse_version(tornado.version) >= parse_version("3.1"): kwargs["max_buffer_size"] = 524288000 # 500 MB file upload limit diff --git a/requirements.txt b/requirements.txt index 2cf31cd5..3e267f9a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,5 @@ jsonschema pycurl python-dateutil apache-libcloud +requests From f876a862c41c16757dda56189eff895eda2e8c84 Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 6 Sep 2014 21:13:09 -0600 Subject: [PATCH 03/11] GNS3 server will now create the heardbeat file durining initialization --- gns3dms/main.py | 7 ++++--- gns3server/modules/deadman/__init__.py | 12 +++++------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/gns3dms/main.py b/gns3dms/main.py index e8d245d6..94e412d7 100644 --- a/gns3dms/main.py +++ b/gns3dms/main.py @@ -145,7 +145,6 @@ def parse_cmd_line(argv): else: cmd_line_option_list['syslog'] = ('localhost',514) - get_gns3config(cmd_line_option_list) for opt, val in opts: @@ -225,8 +224,10 @@ def get_gns3config(cmd_line_option_list): except configparser.NoSectionError: pass - cloud_config_file = "%s/.config/GNS3/cloud.conf" - if os.path.isfile(cloud_config_file) + cloud_config_file = "%s/.config/GNS3/cloud.conf" % ( + os.path.expanduser("~/")) + + if os.path.isfile(cloud_config_file): config.read(cloud_config_file) try: diff --git a/gns3server/modules/deadman/__init__.py b/gns3server/modules/deadman/__init__.py index 288d5b2a..86f97363 100644 --- a/gns3server/modules/deadman/__init__.py +++ b/gns3server/modules/deadman/__init__.py @@ -51,13 +51,11 @@ class DeadMan(IModule): self._heartbeat_file = "%s/heartbeat_file_for_gnsdms" % ( self._tempdir) - self.cloud_config = Config.instance().get_section_config("CLOUD_SERVER") - self._heartbeat_file = self.cloud_config["heartbeat_file"] - if 'heartbeat_file' in kwargs: self._heartbeat_file = kwargs['heartbeat_file'] self._deadman_process = None + self.heartbeat() self.start() def _start_deadman_process(self): @@ -72,11 +70,12 @@ class DeadMan(IModule): cmd = [] cmd.append("gns3dms") - cmd.append("--file %s" % (self._heartbeat_file)) + cmd.append("--file") + cmd.append("%s" % (self._heartbeat_file)) cmd.append("--background") log.debug("Deadman: Running %s"%(cmd)) - process = subprocess.Popen(cmd, shell=False) + process = subprocess.Popen(cmd, stderr=subprocess.STDOUT, shell=False) return process def _stop_deadman_process(self): @@ -143,7 +142,7 @@ class DeadMan(IModule): now = time.time() with open(self._heartbeat_file, 'w') as heartbeat_file: - heartbeat_file.write(now) + heartbeat_file.write(str(now)) heartbeat_file.close() log.debug("Deadman: heartbeat_file updated: %s %s" % ( @@ -151,5 +150,4 @@ class DeadMan(IModule): now, )) - self.start() \ No newline at end of file From 6421367259f44d15ed7741ca4dee9fc6d51e2989 Mon Sep 17 00:00:00 2001 From: Jerry Seutter Date: Mon, 8 Sep 2014 15:35:22 +0000 Subject: [PATCH 04/11] Importing changeset from gns3dms repo --- gns3dms/main.py | 30 ++++++------- gns3dms/modules/daemon.py | 71 ++++++++++++++++++------------ gns3dms/modules/rackspace_cloud.py | 22 ++++----- 3 files changed, 70 insertions(+), 53 deletions(-) diff --git a/gns3dms/main.py b/gns3dms/main.py index 94e412d7..50c012db 100644 --- a/gns3dms/main.py +++ b/gns3dms/main.py @@ -77,6 +77,7 @@ Options: --cloud_user_name --instance_id ID of the Rackspace instance to terminate + --region Region of instance --deadtime How long in seconds can the communication lose exist before we shutdown this instance. @@ -111,6 +112,7 @@ def parse_cmd_line(argv): "cloud_user_name=", "cloud_api_key=", "instance_id=", + "region=", "deadtime=", "init-wait=", "check-interval=", @@ -130,6 +132,7 @@ def parse_cmd_line(argv): cmd_line_option_list["cloud_user_name"] = None cmd_line_option_list["cloud_api_key"] = None cmd_line_option_list["instance_id"] = None + cmd_line_option_list["region"] = None cmd_line_option_list["deadtime"] = 60 * 60 #minutes cmd_line_option_list["check-interval"] = None cmd_line_option_list["init-wait"] = 5 * 60 @@ -145,7 +148,8 @@ def parse_cmd_line(argv): else: cmd_line_option_list['syslog'] = ('localhost',514) - get_gns3config(cmd_line_option_list) + + get_gns3secrets(cmd_line_option_list) for opt, val in opts: if (opt in ("-h", "--help")): @@ -161,6 +165,8 @@ def parse_cmd_line(argv): cmd_line_option_list["cloud_api_key"] = val elif (opt in ("--instance_id")): cmd_line_option_list["instance_id"] = val + elif (opt in ("--region")): + cmd_line_option_list["region"] = val elif (opt in ("--deadtime")): cmd_line_option_list["deadtime"] = int(val) elif (opt in ("--check-interval")): @@ -199,9 +205,15 @@ def parse_cmd_line(argv): print(usage) sys.exit(2) + if cmd_line_option_list["region"] is None: + print("You need to specify a region") + print(usage) + sys.exit(2) + + return cmd_line_option_list -def get_gns3config(cmd_line_option_list): +def get_gns3secrets(cmd_line_option_list): """ Load cloud credentials from .gns3secrets """ @@ -224,17 +236,6 @@ def get_gns3config(cmd_line_option_list): except configparser.NoSectionError: pass - cloud_config_file = "%s/.config/GNS3/cloud.conf" % ( - os.path.expanduser("~/")) - - if os.path.isfile(cloud_config_file): - config.read(cloud_config_file) - - try: - for key, value in config.items("CLOUD_SERVER"): - cmd_line_option_list[key] = value.strip() - except configparser.NoSectionError: - pass def set_logging(cmd_options): """ @@ -351,9 +352,8 @@ def main(): log.info("Received shutdown signal") options["shutdown"] = True - sys.exit(0) - pid_file = "%s/.gns3ias.pid" % (expanduser("~")) + pid_file = "%s/.gns3dms.pid" % (expanduser("~")) if options["shutdown"]: send_shutdown(pid_file) diff --git a/gns3dms/modules/daemon.py b/gns3dms/modules/daemon.py index d10d8d2e..c7245335 100644 --- a/gns3dms/modules/daemon.py +++ b/gns3dms/modules/daemon.py @@ -7,38 +7,38 @@ class daemon: Usage: subclass the daemon class and override the run() method.""" - def __init__(self, pidfile, options): + def __init__(self, pidfile, options): self.pidfile = pidfile self.options = options - + def daemonize(self): """Deamonize class. UNIX double fork mechanism.""" - try: - pid = os.fork() + try: + pid = os.fork() if pid > 0: # exit first parent - sys.exit(0) - except OSError as err: + sys.exit(0) + except OSError as err: sys.stderr.write('fork #1 failed: {0}\n'.format(err)) sys.exit(1) - + # decouple from parent environment - os.chdir('/') - os.setsid() - os.umask(0) - + os.chdir('/') + os.setsid() + os.umask(0) + # do second fork - try: - pid = os.fork() + try: + pid = os.fork() if pid > 0: # exit from second parent - sys.exit(0) - except OSError as err: + sys.exit(0) + except OSError as err: sys.stderr.write('fork #2 failed: {0}\n'.format(err)) - sys.exit(1) - + sys.exit(1) + # redirect standard file descriptors sys.stdout.flush() sys.stderr.flush() @@ -49,17 +49,26 @@ class daemon: os.dup2(si.fileno(), sys.stdin.fileno()) os.dup2(so.fileno(), sys.stdout.fileno()) os.dup2(se.fileno(), sys.stderr.fileno()) - + # write pidfile atexit.register(self.delpid) pid = str(os.getpid()) with open(self.pidfile,'w+') as f: f.write(pid + '\n') - + def delpid(self): os.remove(self.pidfile) + def check_pid(self, pid): + """ Check For the existence of a unix pid. """ + try: + os.kill(pid, 0) + except OSError: + return False + else: + return True + def start(self): """Start the daemon.""" @@ -70,13 +79,19 @@ class daemon: pid = int(pf.read().strip()) except IOError: pid = None - + if pid: - message = "pidfile {0} already exist. " + \ - "Daemon already running?\n" + pid_exist = self.check_pid(pid) + + if pid_exist: + message = "Already running: %s\n" % (pid) + sys.stderr.write(message) + sys.exit(1) + else: + message = "pidfile {0} already exist. " + \ + "but process is dead\n" sys.stderr.write(message.format(self.pidfile)) - sys.exit(1) - + # Start the daemon self.daemonize() self.run() @@ -90,14 +105,14 @@ class daemon: pid = int(pf.read().strip()) except IOError: pid = None - + if not pid: message = "pidfile {0} does not exist. " + \ "Daemon not running?\n" sys.stderr.write(message.format(self.pidfile)) return # not an error in a restart - # Try killing the daemon process + # Try killing the daemon process try: while 1: os.kill(pid, signal.SIGTERM) @@ -118,6 +133,6 @@ class daemon: def run(self): """You should override this method when you subclass Daemon. - - It will be called after the process has been daemonized by + + It will be called after the process has been daemonized by start() or restart().""" diff --git a/gns3dms/modules/rackspace_cloud.py b/gns3dms/modules/rackspace_cloud.py index 4b1d6c0f..00059047 100644 --- a/gns3dms/modules/rackspace_cloud.py +++ b/gns3dms/modules/rackspace_cloud.py @@ -41,6 +41,7 @@ class Rackspace(object): self.authenticated = False self.hostname = socket.gethostname() self.instance_id = options["instance_id"] + self.region = options["region"] log.debug("Authenticating with Rackspace") log.debug("My hostname: %s" % (self.hostname)) @@ -51,16 +52,17 @@ class Rackspace(object): if self.authenticated == False: log.critical("Not authenticated against rackspace!!!!") - for region_dict in self.rksp.list_regions(): - region_k, region_v = region_dict.popitem() - log.debug("Checking region: %s" % (region_k)) - self.rksp.set_region(region_v) - for server in self.rksp.list_instances(): - log.debug("Checking server: %s" % (server.name)) - if server.name.lower() == self.hostname.lower() and server.id == self.instance_id: - log.info("Found matching instance: %s" % (server.id)) - log.info("Startup id: %s" % (self.instance_id)) - return server + for region in self.rksp.list_regions(): + log.debug("Rackspace regions: %s" % (region)) + + log.debug("Checking region: %s" % (self.region)) + self.rksp.set_region(self.region) + for server in self.rksp.list_instances(): + log.debug("Checking server: %s" % (server.name)) + if server.name.lower() == self.hostname.lower() and server.id == self.instance_id: + log.info("Found matching instance: %s" % (server.id)) + log.info("Startup id: %s" % (self.instance_id)) + return server def terminate(self): server = self._find_my_instance() From 17e4b51d18583a4ed8124c4cc1214ea26a48b68d Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 8 Sep 2014 20:45:36 -0600 Subject: [PATCH 05/11] Testing out dummy config --- gns3server/server.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/gns3server/server.py b/gns3server/server.py index 49223790..85365e56 100644 --- a/gns3server/server.py +++ b/gns3server/server.py @@ -140,6 +140,17 @@ class Server(object): JSONRPCWebSocket.register_destination(destination, instance.name) instance.start() # starts the new process + def _dummy_cloud_config(self): + + config = configparser.ConfigParser() + config["CLOUD_SERVER"] = { + "WEB_AUTH_ENABLED" : "no", + "WEB_USERNAME" : "", + "WEB_PASSWORD" : "", + "SSL_ENABLED" : "no", + } + + return config["CLOUD_SERVER"] def run(self): """ @@ -147,7 +158,10 @@ class Server(object): """ # FIXME: debug mode! - cloud_config = Config.instance().get_section_config("CLOUD_SERVER") + try: + cloud_config = Config.instance().get_section_config("CLOUD_SERVER") + except KeyError: + cloud_config = self._dummy_cloud_config() settings = { "debug":True, From 4fa87005bc7066c42ad4c94d15e0faa63162ac7e Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 8 Sep 2014 21:51:56 -0600 Subject: [PATCH 06/11] Enabled HTTP Auth, SSL and DMS disabling based on cloud.conf availability --- gns3server/handlers/auth_handler.py | 6 +++ gns3server/modules/deadman/__init__.py | 19 ++++++++-- gns3server/server.py | 52 +++++++++++++------------- 3 files changed, 47 insertions(+), 30 deletions(-) diff --git a/gns3server/handlers/auth_handler.py b/gns3server/handlers/auth_handler.py index f136ab02..3d8cf331 100644 --- a/gns3server/handlers/auth_handler.py +++ b/gns3server/handlers/auth_handler.py @@ -29,6 +29,9 @@ log = logging.getLogger(__name__) class GNS3BaseHandler(tornado.web.RequestHandler): def get_current_user(self): + if 'required_user' not in self.settings: + return "FakeUser" + user = self.get_secure_cookie("user") if not user: return None @@ -38,6 +41,9 @@ class GNS3BaseHandler(tornado.web.RequestHandler): class GNS3WebSocketBaseHandler(tornado.websocket.WebSocketHandler): def get_current_user(self): + if 'required_user' not in self.settings: + return "FakeUser" + user = self.get_secure_cookie("user") if not user: return None diff --git a/gns3server/modules/deadman/__init__.py b/gns3server/modules/deadman/__init__.py index 86f97363..6fd30baa 100644 --- a/gns3server/modules/deadman/__init__.py +++ b/gns3server/modules/deadman/__init__.py @@ -54,6 +54,16 @@ class DeadMan(IModule): if 'heartbeat_file' in kwargs: self._heartbeat_file = kwargs['heartbeat_file'] + self._is_enabled = False + try: + cloud_config = Config.instance().get_section_config("CLOUD_SERVER") + instance_id = cloud_config["instance_id"] + cloud_user_name = cloud_config["cloud_user_name"] + cloud_api_key = cloud_config["cloud_api_key"] + self._is_enabled = True + except KeyError: + log.critical("Missing cloud.conf - disabling Deadman Switch") + self._deadman_process = None self.heartbeat() self.start() @@ -73,7 +83,7 @@ class DeadMan(IModule): cmd.append("--file") cmd.append("%s" % (self._heartbeat_file)) cmd.append("--background") - log.debug("Deadman: Running %s"%(cmd)) + log.debug("Deadman: Running command: %s"%(cmd)) process = subprocess.Popen(cmd, stderr=subprocess.STDOUT, shell=False) return process @@ -87,7 +97,7 @@ class DeadMan(IModule): cmd.append("gns3dms") cmd.append("-k") - log.debug("Deadman: Running %s"%(cmd)) + log.debug("Deadman: Running command: %s"%(cmd)) process = subprocess.Popen(cmd, shell=False) return process @@ -116,8 +126,9 @@ class DeadMan(IModule): Start the deadman process on the server """ - self._deadman_process = self._start_deadman_process() - log.debug("Deadman: Process is starting") + if self._is_enabled: + self._deadman_process = self._start_deadman_process() + log.debug("Deadman: Process is starting") @IModule.route("deadman.reset") def reset(self, request=None): diff --git a/gns3server/server.py b/gns3server/server.py index 85365e56..3f8b41bc 100644 --- a/gns3server/server.py +++ b/gns3server/server.py @@ -140,37 +140,43 @@ class Server(object): JSONRPCWebSocket.register_destination(destination, instance.name) instance.start() # starts the new process - def _dummy_cloud_config(self): - - config = configparser.ConfigParser() - config["CLOUD_SERVER"] = { - "WEB_AUTH_ENABLED" : "no", - "WEB_USERNAME" : "", - "WEB_PASSWORD" : "", - "SSL_ENABLED" : "no", - } - - return config["CLOUD_SERVER"] def run(self): """ Starts the Tornado web server and ZeroMQ server. """ - # FIXME: debug mode! - try: - cloud_config = Config.instance().get_section_config("CLOUD_SERVER") - except KeyError: - cloud_config = self._dummy_cloud_config() - settings = { "debug":True, "cookie_secret": base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes), "login_url": "/login", - "required_user" : cloud_config['WEB_USERNAME'], - "required_pass" : cloud_config['WEB_PASSWORD'], } + ssl_options = {} + + try: + cloud_config = Config.instance().get_section_config("CLOUD_SERVER") + + cloud_settings = { + + "required_user" : cloud_config['WEB_USERNAME'], + "required_pass" : cloud_config['WEB_PASSWORD'], + } + + settings.update(cloud_settings) + + if cloud_config["SSL_ENABLED"] == "yes": + ssl_options = { + "certfile" : cloud_config["SSL_CRT"], + "keyfile" : cloud_config["SSL_KEY"], + } + + log.info("Certs found - starting in SSL mode") + + except KeyError: + log.info("Missing cloud.conf - disabling HTTP auth and SSL") + + router = self._create_zmq_router() # Add our JSON-RPC Websocket handler to Tornado self.handlers.extend([(r"/", JSONRPCWebSocket, dict(zmq_router=router))]) @@ -190,13 +196,7 @@ class Server(object): zmq.zmq_version())) kwargs = {"address": self._host} - if cloud_config["SSL_ENABLED"] == "yes": - ssl_options = { - "certfile" : cloud_config["SSL_CRT"], - "keyfile" : cloud_config["SSL_KEY"], - } - - log.info("Certs found - starting in SSL mode") + if ssl_options: kwargs["ssl_options"] = ssl_options if parse_version(tornado.version) >= parse_version("3.1"): From 6c6c9200e46cfc529f1cbc533f3651601eaca9cf Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 8 Sep 2014 22:07:33 -0600 Subject: [PATCH 07/11] Add CN support to cert as command line arg --- gns3server/cert_utils/create_cert.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gns3server/cert_utils/create_cert.sh b/gns3server/cert_utils/create_cert.sh index 5b2c8e28..92f6edfb 100755 --- a/gns3server/cert_utils/create_cert.sh +++ b/gns3server/cert_utils/create_cert.sh @@ -62,7 +62,7 @@ C=CA ST=Alberta O=GNS3 localityName=Calgary -commonName=gns3server.localdomain.com +commonName=$DOMAIN organizationalUnitName=GNS3Server emailAddress=gns3cert@gns3.com " From a0e2fe551ac35d0df85f96740134baa10a65a2f9 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 15 Sep 2014 21:25:09 -0600 Subject: [PATCH 08/11] Added web user and password to start_server output --- gns3server/start_server.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gns3server/start_server.py b/gns3server/start_server.py index b27f3af8..099c3966 100644 --- a/gns3server/start_server.py +++ b/gns3server/start_server.py @@ -231,6 +231,8 @@ def main(): client_data['SSL_CRT_FILE'] = server_crt client_data['SSL_CRT'] = cert_data + client_data['WEB_USERNAME'] = cloud_config['CLOUD_SERVER']['WEB_USERNAME'] + client_data['WEB_PASSWORD'] = cloud_config['CLOUD_SERVER']['WEB_PASSWORD'] print(client_data) From b132c901c97e23fb7d26acdfdb489f78b6db5d1b Mon Sep 17 00:00:00 2001 From: Jerry Seutter Date: Thu, 18 Sep 2014 20:39:12 +0000 Subject: [PATCH 09/11] Disabling auth from version string --- gns3server/handlers/version_handler.py | 1 - gns3server/server.py | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/gns3server/handlers/version_handler.py b/gns3server/handlers/version_handler.py index 3b338bd2..ec83d4ce 100644 --- a/gns3server/handlers/version_handler.py +++ b/gns3server/handlers/version_handler.py @@ -22,7 +22,6 @@ from ..version import __version__ class VersionHandler(GNS3BaseHandler): - @tornado.web.authenticated def get(self): response = {'version': __version__} self.write(response) diff --git a/gns3server/server.py b/gns3server/server.py index 3f8b41bc..42f8ec88 100644 --- a/gns3server/server.py +++ b/gns3server/server.py @@ -172,9 +172,8 @@ class Server(object): } log.info("Certs found - starting in SSL mode") - except KeyError: - log.info("Missing cloud.conf - disabling HTTP auth and SSL") + log.info("Missing cloud.conf - disabling HTTP auth and SSL") router = self._create_zmq_router() From cf59240befb7f0395cd5b555ec28a3c4059ebb17 Mon Sep 17 00:00:00 2001 From: Jerry Seutter Date: Sun, 21 Sep 2014 21:41:51 -0600 Subject: [PATCH 10/11] Bugfixes with cloud server communication --- cloud-image/script_template | 4 +--- gns3dms/main.py | 6 +++--- gns3server/modules/deadman/__init__.py | 4 ++-- gns3server/start_server.py | 4 ++-- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/cloud-image/script_template b/cloud-image/script_template index e7c5ad56..c59fbbe6 100644 --- a/cloud-image/script_template +++ b/cloud-image/script_template @@ -11,9 +11,7 @@ mkdir -p /opt/gns3 pushd /opt/gns3 git clone --branch ${git_branch} ${git_url} cd gns3-server -pip3 install tornado -pip3 install pyzmq -pip3 install jsonschema +pip3 install -r dev-requirements.txt python3 ./setup.py install ${rc_local} diff --git a/gns3dms/main.py b/gns3dms/main.py index 50c012db..1e86cbac 100644 --- a/gns3dms/main.py +++ b/gns3dms/main.py @@ -219,19 +219,19 @@ def get_gns3secrets(cmd_line_option_list): """ gns3secret_paths = [ - os.path.expanduser("~/"), + os.path.join(os.path.expanduser("~"), '.config', 'GNS3'), SCRIPT_PATH, ] config = configparser.ConfigParser() for gns3secret_path in gns3secret_paths: - gns3secret_file = "%s/.gns3secrets.conf" % (gns3secret_path) + gns3secret_file = "%s/cloud.conf" % (gns3secret_path) if os.path.isfile(gns3secret_file): config.read(gns3secret_file) try: - for key, value in config.items("Cloud"): + for key, value in config.items("CLOUD_SERVER"): cmd_line_option_list[key] = value.strip() except configparser.NoSectionError: pass diff --git a/gns3server/modules/deadman/__init__.py b/gns3server/modules/deadman/__init__.py index 6fd30baa..c5619c96 100644 --- a/gns3server/modules/deadman/__init__.py +++ b/gns3server/modules/deadman/__init__.py @@ -83,7 +83,7 @@ class DeadMan(IModule): cmd.append("--file") cmd.append("%s" % (self._heartbeat_file)) cmd.append("--background") - log.debug("Deadman: Running command: %s"%(cmd)) + log.info("Deadman: Running command: %s"%(cmd)) process = subprocess.Popen(cmd, stderr=subprocess.STDOUT, shell=False) return process @@ -97,7 +97,7 @@ class DeadMan(IModule): cmd.append("gns3dms") cmd.append("-k") - log.debug("Deadman: Running command: %s"%(cmd)) + log.info("Deadman: Running command: %s"%(cmd)) process = subprocess.Popen(cmd, shell=False) return process diff --git a/gns3server/start_server.py b/gns3server/start_server.py index 099c3966..279ccbc3 100644 --- a/gns3server/start_server.py +++ b/gns3server/start_server.py @@ -168,7 +168,7 @@ def _start_gns3server(): cmd = [] cmd.append("gns3server") - log.debug("Starting gns3server ...") + log.info("Starting gns3server ...") subprocess.Popen(cmd, shell=False) @@ -203,7 +203,7 @@ def main(): except FileExistsError: pass - (server_key, server_crt ) = _generate_certs() + (server_key, server_crt) = _generate_certs() cloud_config = configparser.ConfigParser() cloud_config['CLOUD_SERVER'] = {} From 3b465890b6e6d4f95622ff3661f7b821b2168c4a Mon Sep 17 00:00:00 2001 From: Jerry Seutter Date: Mon, 22 Sep 2014 09:10:30 -0600 Subject: [PATCH 11/11] Increase sleep to work around Rackspace slowness --- cloud-image/create_image.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cloud-image/create_image.py b/cloud-image/create_image.py index d980a2ba..b7b1fec1 100644 --- a/cloud-image/create_image.py +++ b/cloud-image/create_image.py @@ -101,8 +101,9 @@ def main(): startup_script) passwd = uuid.uuid4().hex instance.change_password(passwd) - # wait for the password change to be processed - sleep(POLL_SEC) + # wait for the password change to be processed. Continuing while + # a password change is processing will cause image creation to fail. + sleep(POLL_SEC*6) env.host_string = str(instance.accessIPv4) env.user = "root"