mirror of
https://github.com/GNS3/gns3-server
synced 2024-11-28 03:08:14 +00:00
Drop unused cloud code, this cleanup the dependencies
This commit is contained in:
parent
3407ba802e
commit
83c1ada63e
@ -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 <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)
|
||||
|
||||
from .version import __version__
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
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("<http status code> <http error> <reponse body>")
|
||||
|
||||
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
|
@ -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
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
""" 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:
|
||||
<API's Region Name>: <libcloud's Region Name>
|
||||
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
|
402
gns3dms/main.py
402
gns3dms/main.py
@ -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 <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"] 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)
|
@ -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 <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)
|
@ -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()."""
|
@ -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 <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)
|
||||
|
||||
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)
|
@ -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 <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)
|
||||
|
||||
__version__ = "0.1"
|
||||
__version_info__ = (0, 0, 1, -99)
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user