gns3-server/gns3dms/main.py
2014-11-24 15:35:09 -07:00

403 lines
12 KiB
Python

# -*- 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 <http://www.gnu.org/licenses/>.
# __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)
"""
Monitors communication with the GNS3 client via tmp file. Will terminate the instance if
communication is lost.
"""
import os
import sys
import time
import getopt
import datetime
import logging
import signal
import configparser
from logging.handlers import *
from os.path import expanduser
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])))
EXTRA_LIB = "%s/modules" % (SCRIPT_PATH)
sys.path.append(EXTRA_LIB)
from . import cloud
from rackspace_cloud import Rackspace
LOG_NAME = "gns3dms"
log = None
sys.path.append(EXTRA_LIB)
import daemon
my_daemon = None
usage = """
USAGE: %s
Options:
-d, --debug Enable debugging
-v, --verbose Enable verbose logging
-h, --help Display this menu :)
--cloud_api_key <api_key> Rackspace API key
--cloud_user_name
--instance_id ID of the Rackspace instance to terminate
--cloud_region Region of instance
--dead_time How long in seconds can the communication lose exist before we
shutdown this instance.
Default:
Example --dead_time=3600 (60 minutes)
--check-interval Defaults to --dead_time, used for debugging
--init-wait Inital wait time, how long before we start pulling the file.
Default: 300 (5 min)
Example --init-wait=300
--file The file we monitor for updates
-k Kill previous instance running in background
--background Run in background
""" % (SCRIPT_NAME)
# Parse cmd line options
def parse_cmd_line(argv):
"""
Parse command line arguments
argv: Pass in cmd line arguments
"""
short_args = "dvhk"
long_args = ("debug",
"verbose",
"help",
"cloud_user_name=",
"cloud_api_key=",
"instance_id=",
"region=",
"dead_time=",
"init-wait=",
"check-interval=",
"file=",
"background",
)
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["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["dead_time"] = 60 * 60 #minutes
cmd_line_option_list["check-interval"] = None
cmd_line_option_list["init-wait"] = 5 * 60
cmd_line_option_list["file"] = None
cmd_line_option_list["shutdown"] = False
cmd_line_option_list["daemon"] = False
cmd_line_option_list['starttime'] = datetime.datetime.now()
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)
get_gns3secrets(cmd_line_option_list)
cmd_line_option_list["dead_time"] = int(cmd_line_option_list["dead_time"])
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 ("--cloud_user_name")):
cmd_line_option_list["cloud_user_name"] = val
elif (opt in ("--cloud_api_key")):
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 ("--dead_time")):
cmd_line_option_list["dead_time"] = int(val)
elif (opt in ("--check-interval")):
cmd_line_option_list["check-interval"] = int(val)
elif (opt in ("--init-wait")):
cmd_line_option_list["init-wait"] = int(val)
elif (opt in ("--file")):
cmd_line_option_list["file"] = val
elif (opt in ("-k")):
cmd_line_option_list["shutdown"] = True
elif (opt in ("--background")):
cmd_line_option_list["daemon"] = True
if cmd_line_option_list["shutdown"] == False:
if cmd_line_option_list["check-interval"] is None:
cmd_line_option_list["check-interval"] = cmd_line_option_list["dead_time"] + 120
if cmd_line_option_list["cloud_user_name"] is None:
print("You need to specify a username!!!!")
print(usage)
sys.exit(2)
if cmd_line_option_list["cloud_api_key"] is None:
print("You need to specify an apikey!!!!")
print(usage)
sys.exit(2)
if cmd_line_option_list["file"] is None:
print("You need to specify a file to watch!!!!")
print(usage)
sys.exit(2)
if cmd_line_option_list["instance_id"] is None:
print("You need to specify an instance_id")
print(usage)
sys.exit(2)
if cmd_line_option_list["cloud_region"] is None:
print("You need to specify a cloud_region")
print(usage)
sys.exit(2)
return cmd_line_option_list
def get_gns3secrets(cmd_line_option_list):
"""
Load cloud credentials from .gns3secrets
"""
gns3secret_paths = [
os.path.join(os.path.expanduser("~"), '.config', 'GNS3'),
SCRIPT_PATH,
]
config = configparser.ConfigParser()
for gns3secret_path in gns3secret_paths:
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_SERVER"):
cmd_line_option_list[key] = value.strip()
except configparser.NoSectionError:
pass
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 send_shutdown(pid_file):
"""
Sends the daemon process a kill signal
"""
try:
with open(pid_file, 'r') as pidf:
pid = int(pidf.readline().strip())
pidf.close()
os.kill(pid, 15)
except:
log.info("No running instance found!!!")
log.info("Missing PID file: %s" % (pid_file))
def _get_file_age(filename):
return datetime.datetime.fromtimestamp(
os.path.getmtime(filename)
)
def monitor_loop(options):
"""
Checks the options["file"] modification time against an interval. If the
modification time is too old we terminate the instance.
"""
log.debug("Waiting for init-wait to pass: %s" % (options["init-wait"]))
time.sleep(options["init-wait"])
log.info("Starting monitor_loop")
terminate_attempts = 0
while options['shutdown'] == False:
log.debug("In monitor_loop for : %s" % (
datetime.datetime.now() - options['starttime'])
)
file_last_modified = _get_file_age(options["file"])
now = datetime.datetime.now()
delta = now - file_last_modified
log.debug("File last updated: %s seconds ago" % (delta.seconds))
if delta.seconds > options["dead_time"]:
log.warning("Dead time exceeded, terminating instance ...")
#Terminate involves many layers of HTTP / API calls, lots of
#different errors types could occur here.
try:
rksp = Rackspace(options)
rksp.terminate()
except Exception as e:
log.critical("Exception during terminate: %s" % (e))
terminate_attempts+=1
log.warning("Termination sent, attempt: %s" % (terminate_attempts))
time.sleep(600)
else:
time.sleep(options["check-interval"])
log.info("Leaving monitor_loop")
log.info("Shutting down")
def main():
global log
global my_daemon
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")
options["shutdown"] = True
pid_file = "%s/.gns3dms.pid" % (expanduser("~"))
if options["shutdown"]:
send_shutdown(pid_file)
sys.exit(0)
if options["daemon"]:
my_daemon = MyDaemon(pid_file, options)
# Setup signal to catch Control-C / SIGINT and SIGTERM
signal.signal(signal.SIGINT, _shutdown)
signal.signal(signal.SIGTERM, _shutdown)
log.info("Starting ...")
log.debug("Using settings:")
for key, value in iter(sorted(options.items())):
log.debug("%s : %s" % (key, value))
log.debug("Checking file ....")
if os.path.isfile(options["file"]) == False:
log.critical("File does not exist!!!")
sys.exit(1)
test_acess = _get_file_age(options["file"])
if type(test_acess) is not datetime.datetime:
log.critical("Can't get file modification time!!!")
sys.exit(1)
if my_daemon:
my_daemon.start()
else:
monitor_loop(options)
class MyDaemon(daemon.daemon):
def run(self):
monitor_loop(self.options)
if __name__ == "__main__":
result = main()
sys.exit(result)