mirror of
https://github.com/GNS3/gns3-server
synced 2024-11-24 17:28:08 +00:00
Merge pull request #7 from planctechnologies/gns-110
Support launching devices from cloud file images
This commit is contained in:
commit
bf0b6ee534
@ -22,13 +22,24 @@ Base class for interacting with Cloud APIs to create and manage cloud
|
|||||||
instances.
|
instances.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
from collections import namedtuple
|
||||||
|
import hashlib
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
from io import StringIO, BytesIO
|
||||||
|
|
||||||
from libcloud.compute.base import NodeAuthSSHKey
|
from libcloud.compute.base import NodeAuthSSHKey
|
||||||
|
from libcloud.storage.types import ContainerAlreadyExistsError, ContainerDoesNotExistError
|
||||||
|
|
||||||
from .exceptions import ItemNotFound, KeyPairExists, MethodNotAllowed
|
from .exceptions import ItemNotFound, KeyPairExists, MethodNotAllowed
|
||||||
from .exceptions import OverLimit, BadRequest, ServiceUnavailable
|
from .exceptions import OverLimit, BadRequest, ServiceUnavailable
|
||||||
from .exceptions import Unauthorized, ApiError
|
from .exceptions import Unauthorized, ApiError
|
||||||
|
|
||||||
|
|
||||||
|
KeyPair = namedtuple("KeyPair", ['name'], verbose=False)
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def parse_exception(exception):
|
def parse_exception(exception):
|
||||||
"""
|
"""
|
||||||
Parse the exception to separate the HTTP status code from the text.
|
Parse the exception to separate the HTTP status code from the text.
|
||||||
@ -67,6 +78,8 @@ class BaseCloudCtrl(object):
|
|||||||
503: ServiceUnavailable
|
503: ServiceUnavailable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GNS3_CONTAINER_NAME = 'GNS3'
|
||||||
|
|
||||||
def __init__(self, username, api_key):
|
def __init__(self, username, api_key):
|
||||||
self.username = username
|
self.username = username
|
||||||
self.api_key = api_key
|
self.api_key = api_key
|
||||||
@ -89,23 +102,37 @@ class BaseCloudCtrl(object):
|
|||||||
|
|
||||||
return self.driver.list_sizes()
|
return self.driver.list_sizes()
|
||||||
|
|
||||||
def create_instance(self, name, size, image, keypair):
|
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.
|
Create a new instance with the supplied attributes.
|
||||||
|
|
||||||
Return a Node object.
|
Return a Node object.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
auth_key = NodeAuthSSHKey(keypair.public_key)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return self.driver.create_node(
|
image = self.get_image(image_id)
|
||||||
name=name,
|
if image is None:
|
||||||
size=size,
|
raise ItemNotFound("Image not found")
|
||||||
image=image,
|
|
||||||
auth=auth_key
|
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:
|
except Exception as e:
|
||||||
status, error_text = parse_exception(e)
|
status, error_text = parse_exception(e)
|
||||||
@ -113,7 +140,8 @@ class BaseCloudCtrl(object):
|
|||||||
if status:
|
if status:
|
||||||
self._handle_exception(status, error_text)
|
self._handle_exception(status, error_text)
|
||||||
else:
|
else:
|
||||||
raise e
|
log.error("create_instance method raised an exception: {}".format(e))
|
||||||
|
log.error('image id {}'.format(image))
|
||||||
|
|
||||||
def delete_instance(self, instance):
|
def delete_instance(self, instance):
|
||||||
""" Delete the specified instance. Returns True or False. """
|
""" Delete the specified instance. Returns True or False. """
|
||||||
@ -142,7 +170,11 @@ class BaseCloudCtrl(object):
|
|||||||
def list_instances(self):
|
def list_instances(self):
|
||||||
""" Return a list of instances in the current region. """
|
""" Return a list of instances in the current region. """
|
||||||
|
|
||||||
return self.driver.list_nodes()
|
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):
|
def create_key_pair(self, name):
|
||||||
""" Create and return a new Key Pair. """
|
""" Create and return a new Key Pair. """
|
||||||
@ -173,7 +205,85 @@ class BaseCloudCtrl(object):
|
|||||||
else:
|
else:
|
||||||
raise e
|
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):
|
def list_key_pairs(self):
|
||||||
""" Return a list of Key Pairs. """
|
""" Return a list of Key Pairs. """
|
||||||
|
|
||||||
return self.driver.list_key_pairs()
|
return self.driver.list_key_pairs()
|
||||||
|
|
||||||
|
def upload_file(self, file_path, folder):
|
||||||
|
"""
|
||||||
|
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 folder: folder in cloud storage to save file in
|
||||||
|
: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_object_name = folder + '/' + os.path.basename(file_path)
|
||||||
|
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: List of (project name, object name 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
|
||||||
|
: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:
|
||||||
|
storage_object.download(destination)
|
||||||
|
else:
|
||||||
|
contents = b''
|
||||||
|
|
||||||
|
for chunk in storage_object.as_stream():
|
||||||
|
contents += chunk
|
||||||
|
|
||||||
|
return BytesIO(contents)
|
||||||
|
@ -23,31 +23,37 @@ import requests
|
|||||||
from libcloud.compute.drivers.rackspace import ENDPOINT_ARGS_MAP
|
from libcloud.compute.drivers.rackspace import ENDPOINT_ARGS_MAP
|
||||||
from libcloud.compute.providers import get_driver
|
from libcloud.compute.providers import get_driver
|
||||||
from libcloud.compute.types import Provider
|
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 .exceptions import ItemNotFound, ApiError
|
||||||
from ..version import __version__
|
from ..version import __version__
|
||||||
|
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
RACKSPACE_REGIONS = [{ENDPOINT_ARGS_MAP[k]['region']: k} for k in
|
RACKSPACE_REGIONS = [{ENDPOINT_ARGS_MAP[k]['region']: k} for k in
|
||||||
ENDPOINT_ARGS_MAP]
|
ENDPOINT_ARGS_MAP]
|
||||||
|
|
||||||
GNS3IAS_URL = 'http://localhost:8888' # TODO find a place for this value
|
|
||||||
|
|
||||||
|
|
||||||
class RackspaceCtrl(BaseCloudCtrl):
|
class RackspaceCtrl(BaseCloudCtrl):
|
||||||
|
|
||||||
""" Controller class for interacting with Rackspace API. """
|
""" Controller class for interacting with Rackspace API. """
|
||||||
|
|
||||||
def __init__(self, username, api_key):
|
def __init__(self, username, api_key, gns3_ias_url):
|
||||||
super(RackspaceCtrl, self).__init__(username, api_key)
|
super(RackspaceCtrl, self).__init__(username, api_key)
|
||||||
|
|
||||||
|
self.gns3_ias_url = gns3_ias_url
|
||||||
|
|
||||||
# set this up so it can be swapped out with a mock for testing
|
# set this up so it can be swapped out with a mock for testing
|
||||||
self.post_fn = requests.post
|
self.post_fn = requests.post
|
||||||
self.driver_cls = get_driver(Provider.RACKSPACE)
|
self.driver_cls = get_driver(Provider.RACKSPACE)
|
||||||
|
self.storage_driver_cls = get_storage_driver(StorageProvider.CLOUDFILES)
|
||||||
|
|
||||||
self.driver = None
|
self.driver = None
|
||||||
|
self.storage_driver = None
|
||||||
self.region = None
|
self.region = None
|
||||||
self.instances = {}
|
self.instances = {}
|
||||||
|
|
||||||
@ -57,6 +63,26 @@ class RackspaceCtrl(BaseCloudCtrl):
|
|||||||
|
|
||||||
self.regions = []
|
self.regions = []
|
||||||
self.token = None
|
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):
|
def authenticate(self):
|
||||||
"""
|
"""
|
||||||
@ -100,6 +126,7 @@ class RackspaceCtrl(BaseCloudCtrl):
|
|||||||
self.authenticated = True
|
self.authenticated = True
|
||||||
user_regions = self._parse_endpoints(api_data)
|
user_regions = self._parse_endpoints(api_data)
|
||||||
self.regions = self._make_region_list(user_regions)
|
self.regions = self._make_region_list(user_regions)
|
||||||
|
self.tenant_id = self._parse_tenant_id(api_data)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.regions = []
|
self.regions = []
|
||||||
@ -114,6 +141,11 @@ class RackspaceCtrl(BaseCloudCtrl):
|
|||||||
|
|
||||||
return self.regions
|
return self.regions
|
||||||
|
|
||||||
|
def list_flavors(self):
|
||||||
|
""" Return the dictionary containing flavors id and names """
|
||||||
|
|
||||||
|
return self._flavors
|
||||||
|
|
||||||
def _parse_endpoints(self, api_data):
|
def _parse_endpoints(self, api_data):
|
||||||
"""
|
"""
|
||||||
Parse the JSON-encoded data returned by the Identity Service API.
|
Parse the JSON-encoded data returned by the Identity Service API.
|
||||||
@ -144,6 +176,17 @@ class RackspaceCtrl(BaseCloudCtrl):
|
|||||||
|
|
||||||
return token
|
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):
|
def _make_region_list(self, region_codes):
|
||||||
"""
|
"""
|
||||||
Make a list of regions for use in the GUI.
|
Make a list of regions for use in the GUI.
|
||||||
@ -173,6 +216,8 @@ class RackspaceCtrl(BaseCloudCtrl):
|
|||||||
try:
|
try:
|
||||||
self.driver = self.driver_cls(self.username, self.api_key,
|
self.driver = self.driver_cls(self.username, self.api_key,
|
||||||
region=region)
|
region=region)
|
||||||
|
self.storage_driver = self.storage_driver_cls(self.username, self.api_key,
|
||||||
|
region=region)
|
||||||
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
@ -189,14 +234,19 @@ class RackspaceCtrl(BaseCloudCtrl):
|
|||||||
or, if access was already asked
|
or, if access was already asked
|
||||||
[{"image_id": "", "member_id": "", "status": "ALREADYREQUESTED"},]
|
[{"image_id": "", "member_id": "", "status": "ALREADYREQUESTED"},]
|
||||||
"""
|
"""
|
||||||
endpoint = GNS3IAS_URL+"/images/grant_access"
|
endpoint = self.gns3_ias_url+"/images/grant_access"
|
||||||
params = {
|
params = {
|
||||||
"user_id": username,
|
"user_id": username,
|
||||||
"user_region": region,
|
"user_region": region.upper(),
|
||||||
"gns3_version": gns3_version,
|
"gns3_version": gns3_version,
|
||||||
}
|
}
|
||||||
response = requests.get(endpoint, params=params)
|
try:
|
||||||
|
response = requests.get(endpoint, params=params)
|
||||||
|
except requests.ConnectionError:
|
||||||
|
raise ApiError("Unable to connect to IAS")
|
||||||
|
|
||||||
status = response.status_code
|
status = response.status_code
|
||||||
|
|
||||||
if status == 200:
|
if status == 200:
|
||||||
return response.json()
|
return response.json()
|
||||||
elif status == 404:
|
elif status == 404:
|
||||||
@ -209,17 +259,53 @@ class RackspaceCtrl(BaseCloudCtrl):
|
|||||||
Return a dictionary containing RackSpace server images
|
Return a dictionary containing RackSpace server images
|
||||||
retrieved from gns3-ias server
|
retrieved from gns3-ias server
|
||||||
"""
|
"""
|
||||||
if not (self.username and self.region):
|
if not (self.tenant_id and self.region):
|
||||||
return []
|
return {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = self._get_shared_images(self.username, self.region, __version__)
|
shared_images = self._get_shared_images(self.tenant_id, self.region, __version__)
|
||||||
shared_images = json.loads(response)
|
|
||||||
images = {}
|
images = {}
|
||||||
for i in shared_images:
|
for i in shared_images:
|
||||||
images[i['image_id']] = i['image_name']
|
images[i['image_id']] = i['image_name']
|
||||||
return images
|
return images
|
||||||
except ItemNotFound:
|
except ItemNotFound:
|
||||||
return []
|
return {}
|
||||||
except ApiError as e:
|
except ApiError as e:
|
||||||
log.error('Error while retrieving image list: %s' % e)
|
log.error('Error while retrieving image list: %s' % e)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
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']
|
||||||
|
ias_url = cloud_settings.get('gns3_ias_url', '')
|
||||||
|
except KeyError as e:
|
||||||
|
log.error("Unable to create cloud provider: {}".format(e))
|
||||||
|
return
|
||||||
|
|
||||||
|
provider = RackspaceCtrl(username, apikey, ias_url)
|
||||||
|
|
||||||
|
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
|
||||||
|
@ -77,7 +77,7 @@ Options:
|
|||||||
--cloud_user_name
|
--cloud_user_name
|
||||||
|
|
||||||
--instance_id ID of the Rackspace instance to terminate
|
--instance_id ID of the Rackspace instance to terminate
|
||||||
--region Region of instance
|
--cloud_region Region of instance
|
||||||
|
|
||||||
--deadtime How long in seconds can the communication lose exist before we
|
--deadtime How long in seconds can the communication lose exist before we
|
||||||
shutdown this instance.
|
shutdown this instance.
|
||||||
@ -205,8 +205,8 @@ def parse_cmd_line(argv):
|
|||||||
print(usage)
|
print(usage)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
if cmd_line_option_list["region"] is None:
|
if cmd_line_option_list["cloud_region"] is None:
|
||||||
print("You need to specify a region")
|
print("You need to specify a cloud_region")
|
||||||
print(usage)
|
print(usage)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
|
@ -26,6 +26,8 @@ import configparser
|
|||||||
import logging
|
import logging
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
CLOUD_SERVER = 'CLOUD_SERVER'
|
||||||
|
|
||||||
|
|
||||||
class Config(object):
|
class Config(object):
|
||||||
"""
|
"""
|
||||||
@ -62,20 +64,30 @@ class Config(object):
|
|||||||
# 5: server.conf in the current working directory
|
# 5: server.conf in the current working directory
|
||||||
|
|
||||||
home = os.path.expanduser("~")
|
home = os.path.expanduser("~")
|
||||||
self._cloud_config = os.path.join(home, ".config", appname, "cloud.conf")
|
self._cloud_file = os.path.join(home, ".config", appname, "cloud.conf")
|
||||||
filename = "server.conf"
|
filename = "server.conf"
|
||||||
self._files = [os.path.join(home, ".config", appname, filename),
|
self._files = [os.path.join(home, ".config", appname, filename),
|
||||||
os.path.join(home, ".config", appname + ".conf"),
|
os.path.join(home, ".config", appname + ".conf"),
|
||||||
os.path.join("/etc/xdg", appname, filename),
|
os.path.join("/etc/xdg", appname, filename),
|
||||||
os.path.join("/etc/xdg", appname + ".conf"),
|
os.path.join("/etc/xdg", appname + ".conf"),
|
||||||
filename,
|
filename,
|
||||||
self._cloud_config]
|
self._cloud_file]
|
||||||
|
|
||||||
self._config = configparser.ConfigParser()
|
self._config = configparser.ConfigParser()
|
||||||
self.read_config()
|
self.read_config()
|
||||||
|
self._cloud_config = configparser.ConfigParser()
|
||||||
|
self.read_cloud_config()
|
||||||
|
|
||||||
def list_cloud_config_file(self):
|
def list_cloud_config_file(self):
|
||||||
return self._cloud_config
|
return self._cloud_file
|
||||||
|
|
||||||
|
def read_cloud_config(self):
|
||||||
|
parsed_file = self._cloud_config.read(self._cloud_file)
|
||||||
|
if not self._cloud_config.has_section(CLOUD_SERVER):
|
||||||
|
self._cloud_config.add_section(CLOUD_SERVER)
|
||||||
|
|
||||||
|
def cloud_settings(self):
|
||||||
|
return self._cloud_config[CLOUD_SERVER]
|
||||||
|
|
||||||
def read_config(self):
|
def read_config(self):
|
||||||
"""
|
"""
|
||||||
|
@ -78,11 +78,15 @@ class LoginHandler(tornado.web.RequestHandler):
|
|||||||
self.set_secure_cookie("user", "None")
|
self.set_secure_cookie("user", "None")
|
||||||
auth_status = "failure"
|
auth_status = "failure"
|
||||||
|
|
||||||
log.info("Authentication attempt %s: %s" %(auth_status, user))
|
log.info("Authentication attempt {}: {}, {}".format(auth_status, user, password))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
redirect_to = self.get_secure_cookie("login_success_redirect_to")
|
redirect_to = self.get_secure_cookie("login_success_redirect_to")
|
||||||
except tornado.web.MissingArgumentError:
|
except tornado.web.MissingArgumentError:
|
||||||
redirect_to = "/"
|
redirect_to = "/"
|
||||||
|
|
||||||
self.redirect(redirect_to)
|
if redirect_to is None:
|
||||||
|
self.write({'result': auth_status})
|
||||||
|
else:
|
||||||
|
log.info('Redirecting to {}'.format(redirect_to))
|
||||||
|
self.redirect(redirect_to)
|
@ -61,6 +61,7 @@ class IModule(multiprocessing.Process):
|
|||||||
self._current_destination = None
|
self._current_destination = None
|
||||||
self._current_call_id = None
|
self._current_call_id = None
|
||||||
self._stopping = False
|
self._stopping = False
|
||||||
|
self._cloud_settings = config.cloud_settings()
|
||||||
|
|
||||||
def _setup(self):
|
def _setup(self):
|
||||||
"""
|
"""
|
||||||
@ -177,7 +178,6 @@ class IModule(multiprocessing.Process):
|
|||||||
|
|
||||||
# add session to the response
|
# add session to the response
|
||||||
response = [self._current_session, jsonrpc_response]
|
response = [self._current_session, jsonrpc_response]
|
||||||
log.debug("ZeroMQ client ({}) sending: {}".format(self.name, response))
|
|
||||||
self._stream.send_json(response)
|
self._stream.send_json(response)
|
||||||
|
|
||||||
def send_param_error(self):
|
def send_param_error(self):
|
||||||
|
@ -19,6 +19,7 @@ import os
|
|||||||
import base64
|
import base64
|
||||||
import time
|
import time
|
||||||
from gns3server.modules import IModule
|
from gns3server.modules import IModule
|
||||||
|
from gns3dms.cloud.rackspace_ctrl import get_provider
|
||||||
from ..dynamips_error import DynamipsError
|
from ..dynamips_error import DynamipsError
|
||||||
|
|
||||||
from ..nodes.c1700 import C1700
|
from ..nodes.c1700 import C1700
|
||||||
@ -140,12 +141,22 @@ class VM(object):
|
|||||||
chassis = request.get("chassis")
|
chassis = request.get("chassis")
|
||||||
router_id = request.get("router_id")
|
router_id = request.get("router_id")
|
||||||
|
|
||||||
|
# Locate the image
|
||||||
updated_image_path = os.path.join(self.images_directory, image)
|
updated_image_path = os.path.join(self.images_directory, image)
|
||||||
if os.path.isfile(updated_image_path):
|
if os.path.isfile(updated_image_path):
|
||||||
image = updated_image_path
|
image = updated_image_path
|
||||||
|
else:
|
||||||
|
if not os.path.exists(self.images_directory):
|
||||||
|
os.mkdir(self.images_directory)
|
||||||
|
if request.get("cloud_path", None):
|
||||||
|
# Download the image from cloud files
|
||||||
|
cloud_path = request.get("cloud_path")
|
||||||
|
full_cloud_path = "/".join((cloud_path, image))
|
||||||
|
|
||||||
|
provider = get_provider(self._cloud_settings)
|
||||||
|
provider.download_file(full_cloud_path, updated_image_path)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
if platform not in PLATFORMS:
|
if platform not in PLATFORMS:
|
||||||
raise DynamipsError("Unknown router platform: {}".format(platform))
|
raise DynamipsError("Unknown router platform: {}".format(platform))
|
||||||
|
|
||||||
|
@ -67,6 +67,10 @@ VM_CREATE_SCHEMA = {
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"pattern": "^([0-9a-fA-F]{4}\\.){2}[0-9a-fA-F]{4}$"
|
"pattern": "^([0-9a-fA-F]{4}\\.){2}[0-9a-fA-F]{4}$"
|
||||||
|
},
|
||||||
|
"cloud_path": {
|
||||||
|
"description": "Path to the image in the cloud object store",
|
||||||
|
"type": "string",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": False,
|
"additionalProperties": False,
|
||||||
|
@ -61,6 +61,7 @@ USAGE: %s
|
|||||||
Options:
|
Options:
|
||||||
|
|
||||||
-d, --debug Enable debugging
|
-d, --debug Enable debugging
|
||||||
|
-i --ip The ip address of the server, for cert generation
|
||||||
-v, --verbose Enable verbose logging
|
-v, --verbose Enable verbose logging
|
||||||
-h, --help Display this menu :)
|
-h, --help Display this menu :)
|
||||||
|
|
||||||
@ -79,6 +80,7 @@ def parse_cmd_line(argv):
|
|||||||
|
|
||||||
short_args = "dvh"
|
short_args = "dvh"
|
||||||
long_args = ("debug",
|
long_args = ("debug",
|
||||||
|
"ip=",
|
||||||
"verbose",
|
"verbose",
|
||||||
"help",
|
"help",
|
||||||
"data=",
|
"data=",
|
||||||
@ -105,6 +107,8 @@ def parse_cmd_line(argv):
|
|||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
elif opt in ("-d", "--debug"):
|
elif opt in ("-d", "--debug"):
|
||||||
cmd_line_option_list["debug"] = True
|
cmd_line_option_list["debug"] = True
|
||||||
|
elif opt in ("--ip",):
|
||||||
|
cmd_line_option_list["ip"] = val
|
||||||
elif opt in ("-v", "--verbose"):
|
elif opt in ("-v", "--verbose"):
|
||||||
cmd_line_option_list["verbose"] = True
|
cmd_line_option_list["verbose"] = True
|
||||||
elif opt in ("--data",):
|
elif opt in ("--data",):
|
||||||
@ -151,7 +155,7 @@ def set_logging(cmd_options):
|
|||||||
return log
|
return log
|
||||||
|
|
||||||
|
|
||||||
def _generate_certs():
|
def _generate_certs(options):
|
||||||
"""
|
"""
|
||||||
Generate a self-signed certificate for SSL-enabling the WebSocket
|
Generate a self-signed certificate for SSL-enabling the WebSocket
|
||||||
connection. The certificate is sent back to the client so it can
|
connection. The certificate is sent back to the client so it can
|
||||||
@ -159,7 +163,7 @@ def _generate_certs():
|
|||||||
|
|
||||||
:return: A 2-tuple of strings containing (server_key, server_cert)
|
:return: A 2-tuple of strings containing (server_key, server_cert)
|
||||||
"""
|
"""
|
||||||
cmd = ["{}/cert_utils/create_cert.sh".format(SCRIPT_PATH)]
|
cmd = ["{}/cert_utils/create_cert.sh".format(SCRIPT_PATH), options['ip']]
|
||||||
log.debug("Generating certs with cmd: {}".format(' '.join(cmd)))
|
log.debug("Generating certs with cmd: {}".format(' '.join(cmd)))
|
||||||
output_raw = subprocess.check_output(cmd, shell=False,
|
output_raw = subprocess.check_output(cmd, shell=False,
|
||||||
stderr=subprocess.STDOUT)
|
stderr=subprocess.STDOUT)
|
||||||
@ -176,9 +180,9 @@ def _start_gns3server():
|
|||||||
|
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
cmd = ['gns3server', '--quiet']
|
cmd = 'gns3server --quiet > /tmp/gns3.log 2>&1 &'
|
||||||
log.info("Starting gns3server with cmd {}".format(cmd))
|
log.info("Starting gns3server with cmd {}".format(cmd))
|
||||||
subprocess.Popen(cmd, shell=False)
|
os.system(cmd)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@ -211,7 +215,7 @@ def main():
|
|||||||
except FileExistsError:
|
except FileExistsError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
(server_key, server_crt) = _generate_certs()
|
(server_key, server_crt) = _generate_certs(options)
|
||||||
|
|
||||||
cloud_config = configparser.ConfigParser()
|
cloud_config = configparser.ConfigParser()
|
||||||
cloud_config['CLOUD_SERVER'] = {}
|
cloud_config['CLOUD_SERVER'] = {}
|
||||||
@ -221,15 +225,13 @@ def main():
|
|||||||
|
|
||||||
cloud_config['CLOUD_SERVER']['SSL_KEY'] = server_key
|
cloud_config['CLOUD_SERVER']['SSL_KEY'] = server_key
|
||||||
cloud_config['CLOUD_SERVER']['SSL_CRT'] = server_crt
|
cloud_config['CLOUD_SERVER']['SSL_CRT'] = server_crt
|
||||||
cloud_config['CLOUD_SERVER']['SSL_ENABLED'] = 'yes'
|
cloud_config['CLOUD_SERVER']['SSL_ENABLED'] = 'no'
|
||||||
cloud_config['CLOUD_SERVER']['WEB_USERNAME'] = str(uuid.uuid4()).upper()[0:8]
|
cloud_config['CLOUD_SERVER']['WEB_USERNAME'] = str(uuid.uuid4()).upper()[0:8]
|
||||||
cloud_config['CLOUD_SERVER']['WEB_PASSWORD'] = str(uuid.uuid4()).upper()[0:8]
|
cloud_config['CLOUD_SERVER']['WEB_PASSWORD'] = str(uuid.uuid4()).upper()[0:8]
|
||||||
|
|
||||||
with open(cfg, 'w') as cloud_config_file:
|
with open(cfg, 'w') as cloud_config_file:
|
||||||
cloud_config.write(cloud_config_file)
|
cloud_config.write(cloud_config_file)
|
||||||
|
|
||||||
cloud_config_file.close()
|
|
||||||
|
|
||||||
_start_gns3server()
|
_start_gns3server()
|
||||||
|
|
||||||
with open(server_crt, 'r') as cert_file:
|
with open(server_crt, 'r') as cert_file:
|
||||||
|
Loading…
Reference in New Issue
Block a user