From 83c1ada63eecdaea4fc798277f38daa7a07518d7 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Thu, 5 Mar 2015 17:15:16 +0100 Subject: [PATCH] Drop unused cloud code, this cleanup the dependencies --- gns3dms/__init__.py | 26 -- gns3dms/cloud/__init__.py | 0 gns3dms/cloud/base_cloud_ctrl.py | 340 ------------------------ gns3dms/cloud/exceptions.py | 67 ----- gns3dms/cloud/rackspace_ctrl.py | 259 ------------------- gns3dms/main.py | 402 ----------------------------- gns3dms/modules/__init__.py | 24 -- gns3dms/modules/daemon.py | 144 ----------- gns3dms/modules/rackspace_cloud.py | 73 ------ gns3dms/version.py | 27 -- requirements.txt | 2 - setup.py | 2 - 12 files changed, 1366 deletions(-) delete mode 100644 gns3dms/__init__.py delete mode 100644 gns3dms/cloud/__init__.py delete mode 100644 gns3dms/cloud/base_cloud_ctrl.py delete mode 100644 gns3dms/cloud/exceptions.py delete mode 100644 gns3dms/cloud/rackspace_ctrl.py delete mode 100644 gns3dms/main.py delete mode 100644 gns3dms/modules/__init__.py delete mode 100644 gns3dms/modules/daemon.py delete mode 100644 gns3dms/modules/rackspace_cloud.py delete mode 100644 gns3dms/version.py diff --git a/gns3dms/__init__.py b/gns3dms/__init__.py deleted file mode 100644 index cf426f79..00000000 --- a/gns3dms/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- 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) - -from .version import __version__ diff --git a/gns3dms/cloud/__init__.py b/gns3dms/cloud/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/gns3dms/cloud/base_cloud_ctrl.py b/gns3dms/cloud/base_cloud_ctrl.py deleted file mode 100644 index 0ad74af1..00000000 --- a/gns3dms/cloud/base_cloud_ctrl.py +++ /dev/null @@ -1,340 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2014 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 . - -""" -Base cloud controller class. - -Base class for interacting with Cloud APIs to create and manage cloud -instances. - -""" -from collections import namedtuple -import hashlib -import os -import logging -from io import StringIO, BytesIO - -from libcloud.compute.base import NodeAuthSSHKey -from libcloud.storage.types import ContainerAlreadyExistsError, ContainerDoesNotExistError, ObjectDoesNotExistError - -from .exceptions import ItemNotFound, KeyPairExists, MethodNotAllowed -from .exceptions import OverLimit, BadRequest, ServiceUnavailable -from .exceptions import Unauthorized, ApiError - - -KeyPair = namedtuple("KeyPair", ['name'], verbose=False) -log = logging.getLogger(__name__) - - -def parse_exception(exception): - """ - Parse the exception to separate the HTTP status code from the text. - - Libcloud raises many exceptions of the form: - Exception(" ") - - in lieu of raising specific incident-based exceptions. - - """ - - e_str = str(exception) - - try: - status = int(e_str[0:3]) - error_text = e_str[3:] - - except ValueError: - status = None - error_text = e_str - - return status, error_text - - -class BaseCloudCtrl(object): - - """ Base class for interacting with a cloud provider API. """ - - http_status_to_exception = { - 400: BadRequest, - 401: Unauthorized, - 404: ItemNotFound, - 405: MethodNotAllowed, - 413: OverLimit, - 500: ApiError, - 503: ServiceUnavailable - } - - GNS3_CONTAINER_NAME = 'GNS3' - - def __init__(self, username, api_key): - self.username = username - self.api_key = api_key - - def _handle_exception(self, status, error_text, response_overrides=None): - """ Raise an exception based on the HTTP status. """ - - if response_overrides: - if status in response_overrides: - raise response_overrides[status](error_text) - - raise self.http_status_to_exception[status](error_text) - - def authenticate(self): - """ Validate cloud account credentials. Return boolean. """ - raise NotImplementedError - - def list_sizes(self): - """ Return a list of NodeSize objects. """ - - return self.driver.list_sizes() - - def list_flavors(self): - """ Return an iterable of flavors """ - - raise NotImplementedError - - def create_instance(self, name, size_id, image_id, keypair): - """ - Create a new instance with the supplied attributes. - - Return a Node object. - - """ - try: - image = self.get_image(image_id) - if image is None: - raise ItemNotFound("Image not found") - - size = self.driver.ex_get_size(size_id) - - args = { - "name": name, - "size": size, - "image": image, - } - - if keypair is not None: - auth_key = NodeAuthSSHKey(keypair.public_key) - args["auth"] = auth_key - args["ex_keyname"] = name - - return self.driver.create_node(**args) - - except Exception as e: - status, error_text = parse_exception(e) - - if status: - self._handle_exception(status, error_text) - else: - log.error("create_instance method raised an exception: {}".format(e)) - log.error('image id {}'.format(image)) - - def delete_instance(self, instance): - """ Delete the specified instance. Returns True or False. """ - - try: - return self.driver.destroy_node(instance) - - except Exception as e: - - status, error_text = parse_exception(e) - - if status: - self._handle_exception(status, error_text) - else: - raise e - - def get_instance(self, instance): - """ Return a Node object representing the requested instance. """ - - for i in self.driver.list_nodes(): - if i.id == instance.id: - return i - - raise ItemNotFound("Instance not found") - - def list_instances(self): - """ Return a list of instances in the current region. """ - - try: - return self.driver.list_nodes() - except Exception as e: - log.error("list_instances returned an error: {}".format(e)) - - def create_key_pair(self, name): - """ Create and return a new Key Pair. """ - - response_overrides = { - 409: KeyPairExists - } - try: - return self.driver.create_key_pair(name) - - except Exception as e: - status, error_text = parse_exception(e) - if status: - self._handle_exception(status, error_text, response_overrides) - else: - raise e - - def delete_key_pair(self, keypair): - """ Delete the keypair. Returns True or False. """ - - try: - return self.driver.delete_key_pair(keypair) - - except Exception as e: - status, error_text = parse_exception(e) - if status: - self._handle_exception(status, error_text) - else: - raise e - - def delete_key_pair_by_name(self, keypair_name): - """ Utility method to incapsulate boilerplate code """ - - kp = KeyPair(name=keypair_name) - return self.delete_key_pair(kp) - - def list_key_pairs(self): - """ Return a list of Key Pairs. """ - - return self.driver.list_key_pairs() - - def upload_file(self, file_path, cloud_object_name): - """ - Uploads file to cloud storage (if it is not identical to a file already in cloud storage). - :param file_path: path to file to upload - :param cloud_object_name: name of file saved in cloud storage - :return: True if file was uploaded, False if it was skipped because it already existed and was identical - """ - try: - gns3_container = self.storage_driver.create_container(self.GNS3_CONTAINER_NAME) - except ContainerAlreadyExistsError: - gns3_container = self.storage_driver.get_container(self.GNS3_CONTAINER_NAME) - - with open(file_path, 'rb') as file: - local_file_hash = hashlib.md5(file.read()).hexdigest() - - cloud_hash_name = cloud_object_name + '.md5' - cloud_objects = [obj.name for obj in gns3_container.list_objects()] - - # if the file and its hash are in object storage, and the local and storage file hashes match - # do not upload the file, otherwise upload it - if cloud_object_name in cloud_objects and cloud_hash_name in cloud_objects: - hash_object = gns3_container.get_object(cloud_hash_name) - cloud_object_hash = '' - for chunk in hash_object.as_stream(): - cloud_object_hash += chunk.decode('utf8') - - if cloud_object_hash == local_file_hash: - return False - - file.seek(0) - self.storage_driver.upload_object_via_stream(file, gns3_container, cloud_object_name) - self.storage_driver.upload_object_via_stream(StringIO(local_file_hash), gns3_container, cloud_hash_name) - return True - - def list_projects(self): - """ - Lists projects in cloud storage - :return: Dictionary where project names are keys and values are names of objects in storage - """ - - try: - gns3_container = self.storage_driver.get_container(self.GNS3_CONTAINER_NAME) - projects = { - obj.name.replace('projects/', '').replace('.zip', ''): obj.name - for obj in gns3_container.list_objects() - if obj.name.startswith('projects/') and obj.name[-4:] == '.zip' - } - return projects - except ContainerDoesNotExistError: - return [] - - def download_file(self, file_name, destination=None): - """ - Downloads file from cloud storage. If a file exists at destination, and it is identical to the file in cloud - storage, it is not downloaded. - :param file_name: name of file in cloud storage to download - :param destination: local path to save file to (if None, returns file contents as a file-like object) - :return: A file-like object if file contents are returned, or None if file is saved to filesystem - """ - - gns3_container = self.storage_driver.get_container(self.GNS3_CONTAINER_NAME) - storage_object = gns3_container.get_object(file_name) - - if destination is not None: - if os.path.isfile(destination): - # if a file exists at destination and its hash matches that of the - # file in cloud storage, don't download it - with open(destination, 'rb') as f: - local_file_hash = hashlib.md5(f.read()).hexdigest() - - hash_object = gns3_container.get_object(file_name + '.md5') - cloud_object_hash = '' - for chunk in hash_object.as_stream(): - cloud_object_hash += chunk.decode('utf8') - - if local_file_hash == cloud_object_hash: - return - - storage_object.download(destination) - else: - contents = b'' - - for chunk in storage_object.as_stream(): - contents += chunk - - return BytesIO(contents) - - def find_storage_image_names(self, images_to_find): - """ - Maps names of image files to their full name in cloud storage - :param images_to_find: list of image names to find - :return: A dictionary where keys are image names, and values are the corresponding names of - the files in cloud storage - """ - gns3_container = self.storage_driver.get_container(self.GNS3_CONTAINER_NAME) - images_in_storage = [obj.name for obj in gns3_container.list_objects() if obj.name.startswith('images/')] - - images = {} - for image_name in images_to_find: - images_with_same_name =\ - list(filter(lambda storage_image_name: storage_image_name.endswith(image_name), images_in_storage)) - - if len(images_with_same_name) == 1: - images[image_name] = images_with_same_name[0] - else: - raise Exception('Image does not exist in cloud storage or is duplicated') - - return images - - def delete_file(self, file_name): - gns3_container = self.storage_driver.get_container(self.GNS3_CONTAINER_NAME) - - try: - object_to_delete = gns3_container.get_object(file_name) - object_to_delete.delete() - except ObjectDoesNotExistError: - pass - - try: - hash_object = gns3_container.get_object(file_name + '.md5') - hash_object.delete() - except ObjectDoesNotExistError: - pass diff --git a/gns3dms/cloud/exceptions.py b/gns3dms/cloud/exceptions.py deleted file mode 100644 index 65d65f9f..00000000 --- a/gns3dms/cloud/exceptions.py +++ /dev/null @@ -1,67 +0,0 @@ -""" Exception classes for CloudCtrl classes. """ - - -class ApiError(Exception): - - """ Raised when the server returns 500 Compute Error. """ - pass - - -class BadRequest(Exception): - - """ Raised when the server returns 400 Bad Request. """ - pass - - -class ComputeFault(Exception): - - """ Raised when the server returns 400|500 Compute Fault. """ - pass - - -class Forbidden(Exception): - - """ Raised when the server returns 403 Forbidden. """ - pass - - -class ItemNotFound(Exception): - - """ Raised when the server returns 404 Not Found. """ - pass - - -class KeyPairExists(Exception): - - """ Raised when the server returns 409 Conflict Key pair exists. """ - pass - - -class MethodNotAllowed(Exception): - - """ Raised when the server returns 405 Method Not Allowed. """ - pass - - -class OverLimit(Exception): - - """ Raised when the server returns 413 Over Limit. """ - pass - - -class ServerCapacityUnavailable(Exception): - - """ Raised when the server returns 503 Server Capacity Uavailable. """ - pass - - -class ServiceUnavailable(Exception): - - """ Raised when the server returns 503 Service Unavailable. """ - pass - - -class Unauthorized(Exception): - - """ Raised when the server returns 401 Unauthorized. """ - pass diff --git a/gns3dms/cloud/rackspace_ctrl.py b/gns3dms/cloud/rackspace_ctrl.py deleted file mode 100644 index aee7f46d..00000000 --- a/gns3dms/cloud/rackspace_ctrl.py +++ /dev/null @@ -1,259 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2014 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 . - -""" Interacts with Rackspace API to create and manage cloud instances. """ - -from .base_cloud_ctrl import BaseCloudCtrl -import json -import requests -from libcloud.compute.drivers.rackspace import ENDPOINT_ARGS_MAP -from libcloud.compute.providers import get_driver -from libcloud.compute.types import Provider -from libcloud.storage.providers import get_driver as get_storage_driver -from libcloud.storage.types import Provider as StorageProvider - -from .exceptions import ItemNotFound, ApiError -from ..version import __version__ - -from collections import OrderedDict - -import logging -log = logging.getLogger(__name__) - -RACKSPACE_REGIONS = [{ENDPOINT_ARGS_MAP[k]['region']: k} for k in - ENDPOINT_ARGS_MAP] - - -class RackspaceCtrl(BaseCloudCtrl): - - """ Controller class for interacting with Rackspace API. """ - - def __init__(self, username, api_key, *args, **kwargs): - super(RackspaceCtrl, self).__init__(username, api_key) - - # set this up so it can be swapped out with a mock for testing - self.post_fn = requests.post - self.driver_cls = get_driver(Provider.RACKSPACE) - self.storage_driver_cls = get_storage_driver(StorageProvider.CLOUDFILES) - - self.driver = None - self.storage_driver = None - self.region = None - self.instances = {} - - self.authenticated = False - self.identity_ep = \ - "https://identity.api.rackspacecloud.com/v2.0/tokens" - - self.regions = [] - self.token = None - self.tenant_id = None - self.flavor_ep = "https://dfw.servers.api.rackspacecloud.com/v2/{username}/flavors" - self._flavors = OrderedDict([ - ('2', '512MB, 1 VCPU'), - ('3', '1GB, 1 VCPU'), - ('4', '2GB, 2 VCPUs'), - ('5', '4GB, 2 VCPUs'), - ('6', '8GB, 4 VCPUs'), - ('7', '15GB, 6 VCPUs'), - ('8', '30GB, 8 VCPUs'), - ('performance1-1', '1GB Performance, 1 VCPU'), - ('performance1-2', '2GB Performance, 2 VCPUs'), - ('performance1-4', '4GB Performance, 4 VCPUs'), - ('performance1-8', '8GB Performance, 8 VCPUs'), - ('performance2-15', '15GB Performance, 4 VCPUs'), - ('performance2-30', '30GB Performance, 8 VCPUs'), - ('performance2-60', '60GB Performance, 16 VCPUs'), - ('performance2-90', '90GB Performance, 24 VCPUs'), - ('performance2-120', '120GB Performance, 32 VCPUs',) - ]) - - def authenticate(self): - """ - Submit username and api key to API service. - - If authentication is successful, set self.regions and self.token. - Return boolean. - - """ - - self.authenticated = False - - if len(self.username) < 1: - return False - - if len(self.api_key) < 1: - return False - - data = json.dumps({ - "auth": { - "RAX-KSKEY:apiKeyCredentials": { - "username": self.username, - "apiKey": self.api_key - } - } - }) - - headers = { - 'Content-type': 'application/json', - 'Accept': 'application/json' - } - - response = self.post_fn(self.identity_ep, data=data, headers=headers) - - if response.status_code == 200: - - api_data = response.json() - self.token = self._parse_token(api_data) - - if self.token: - self.authenticated = True - user_regions = self._parse_endpoints(api_data) - self.regions = self._make_region_list(user_regions) - self.tenant_id = self._parse_tenant_id(api_data) - - else: - self.regions = [] - self.token = None - - response.connection.close() - - return self.authenticated - - def list_regions(self): - """ Return a list the regions available to the user. """ - - return self.regions - - def list_flavors(self): - """ Return the dictionary containing flavors id and names """ - - return self._flavors - - def _parse_endpoints(self, api_data): - """ - Parse the JSON-encoded data returned by the Identity Service API. - - Return a list of regions available for Compute v2. - - """ - - region_codes = [] - - for ep_type in api_data['access']['serviceCatalog']: - if ep_type['name'] == "cloudServersOpenStack" \ - and ep_type['type'] == "compute": - - for ep in ep_type['endpoints']: - if ep['versionId'] == "2": - region_codes.append(ep['region']) - - return region_codes - - def _parse_token(self, api_data): - """ Parse the token from the JSON-encoded data returned by the API. """ - - try: - token = api_data['access']['token']['id'] - except KeyError: - return None - - return token - - def _parse_tenant_id(self, api_data): - """ """ - try: - roles = api_data['access']['user']['roles'] - for role in roles: - if 'tenantId' in role and role['name'] == 'compute:default': - return role['tenantId'] - return None - except KeyError: - return None - - def _make_region_list(self, region_codes): - """ - Make a list of regions for use in the GUI. - - Returns a list of key-value pairs in the form: - : - eg, - [ - {'DFW': 'dfw'} - {'ORD': 'ord'}, - ... - ] - - """ - - region_list = [] - - for ep in ENDPOINT_ARGS_MAP: - if ENDPOINT_ARGS_MAP[ep]['region'] in region_codes: - region_list.append({ENDPOINT_ARGS_MAP[ep]['region']: ep}) - - return region_list - - def set_region(self, region): - """ Set self.region and self.driver. Returns True or False. """ - - try: - self.driver = self.driver_cls(self.username, self.api_key, - region=region) - self.storage_driver = self.storage_driver_cls(self.username, self.api_key, - region=region) - - except ValueError: - return False - - self.region = region - return True - - def get_image(self, image_id): - return self.driver.get_image(image_id) - - -def get_provider(cloud_settings): - """ - Utility function to retrieve a cloud provider instance already authenticated and with the - region set - - :param cloud_settings: cloud settings dictionary - :return: a provider instance or None on errors - """ - try: - username = cloud_settings['cloud_user_name'] - apikey = cloud_settings['cloud_api_key'] - region = cloud_settings['cloud_region'] - except KeyError as e: - log.error("Unable to create cloud provider: {}".format(e)) - return - - provider = RackspaceCtrl(username, apikey) - - if not provider.authenticate(): - log.error("Authentication failed for cloud provider") - return - - if not region: - region = provider.list_regions().values()[0] - - if not provider.set_region(region): - log.error("Unable to set cloud provider region") - return - - return provider diff --git a/gns3dms/main.py b/gns3dms/main.py deleted file mode 100644 index d7d0fc30..00000000 --- a/gns3dms/main.py +++ /dev/null @@ -1,402 +0,0 @@ -# -*- 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) - -""" -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 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"] is 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']: - log_level_console = logging.INFO - - if cmd_options['debug']: - 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'] is 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"]) is False: - log.critical("File does not exist!!!") - sys.exit(1) - - test_acess = _get_file_age(options["file"]) - if not isinstance(test_acess, 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) diff --git a/gns3dms/modules/__init__.py b/gns3dms/modules/__init__.py deleted file mode 100644 index 0950e877..00000000 --- a/gns3dms/modules/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- 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) diff --git a/gns3dms/modules/daemon.py b/gns3dms/modules/daemon.py deleted file mode 100644 index cfc5539f..00000000 --- a/gns3dms/modules/daemon.py +++ /dev/null @@ -1,144 +0,0 @@ -"""Generic linux daemon base class for python 3.x.""" - -import sys -import os -import time -import atexit -import signal - - -class daemon: - - """A generic daemon class. - - Usage: subclass the daemon class and override the run() method.""" - - def __init__(self, pidfile, options): - self.pidfile = pidfile - self.options = options - - def daemonize(self): - """Deamonize class. UNIX double fork mechanism.""" - - try: - pid = os.fork() - if pid > 0: - # exit first parent - 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) - - # do second fork - try: - pid = os.fork() - if pid > 0: - - # exit from second parent - sys.exit(0) - except OSError as err: - sys.stderr.write('fork #2 failed: {0}\n'.format(err)) - sys.exit(1) - - # redirect standard file descriptors - sys.stdout.flush() - sys.stderr.flush() - si = open(os.devnull, 'r') - so = open(os.devnull, 'a+') - se = open(os.devnull, 'a+') - - 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.""" - - # Check for a pidfile to see if the daemon already runs - try: - with open(self.pidfile, 'r') as pf: - - pid = int(pf.read().strip()) - except IOError: - pid = None - - if pid: - 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)) - - # Start the daemon - self.daemonize() - self.run() - - def stop(self): - """Stop the daemon.""" - - # Get the pid from the pidfile - try: - with open(self.pidfile, 'r') as pf: - 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: - while True: - os.kill(pid, signal.SIGTERM) - time.sleep(0.1) - except OSError as err: - e = str(err.args) - if e.find("No such process") > 0: - if os.path.exists(self.pidfile): - os.remove(self.pidfile) - else: - print(str(err.args)) - sys.exit(1) - - def restart(self): - """Restart the daemon.""" - self.stop() - self.start() - - def run(self): - """You should override this method when you subclass Daemon. - - 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 deleted file mode 100644 index 14c0128f..00000000 --- a/gns3dms/modules/rackspace_cloud.py +++ /dev/null @@ -1,73 +0,0 @@ -# -*- 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) - -import os -import sys -import json -import logging -import socket - -from gns3dms.cloud.rackspace_ctrl import RackspaceCtrl - - -LOG_NAME = "gns3dms.rksp" -log = logging.getLogger("%s" % (LOG_NAME)) - - -class Rackspace(object): - - def __init__(self, options): - self.username = options["cloud_user_name"] - self.apikey = options["cloud_api_key"] - self.authenticated = False - self.hostname = socket.gethostname() - self.instance_id = options["instance_id"] - self.region = options["cloud_region"] - - log.debug("Authenticating with Rackspace") - log.debug("My hostname: %s" % (self.hostname)) - self.rksp = RackspaceCtrl(self.username, self.apikey) - self.authenticated = self.rksp.authenticate() - - def _find_my_instance(self): - if self.authenticated is not False: - log.critical("Not authenticated against rackspace!!!!") - - 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.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() - log.warning("Sending termination") - self.rksp.delete_instance(server) diff --git a/gns3dms/version.py b/gns3dms/version.py deleted file mode 100644 index 545a0060..00000000 --- a/gns3dms/version.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- 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) - -__version__ = "0.1" -__version_info__ = (0, 0, 1, -99) diff --git a/requirements.txt b/requirements.txt index db4f67fd..c12c2071 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,6 @@ netifaces==0.10.4 jsonschema==2.4.0 python-dateutil==2.3 -apache-libcloud==0.16.0 -requests==2.5.0 aiohttp==0.14.4 Jinja2==2.7.3 raven==5.2.0 diff --git a/setup.py b/setup.py index 4b716076..37fd5902 100644 --- a/setup.py +++ b/setup.py @@ -36,8 +36,6 @@ class PyTest(TestCommand): dependencies = ["aiohttp==0.14.4", "jsonschema==2.4.0", - "apache-libcloud==0.16.0", - "requests==2.5.0", "Jinja2==2.7.3", "raven==5.2.0"]