Merge branch 'master' of github.com:NOPping/gstack into refactor
diff --git a/.coveragerc b/.coveragerc
index c35bfbc..ac30c60 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -6,6 +6,7 @@
*__main__*
gstack/models/*.py
gstack/configure.py
+ gstack/core.py
[report]
exclude_lines =
diff --git a/alembic.ini b/alembic.ini
new file mode 100644
index 0000000..f34bc98
--- /dev/null
+++ b/alembic.ini
@@ -0,0 +1,59 @@
+# A generic, single database configuration.
+
+[alembic]
+# path to migration scripts
+script_location = migrations
+
+# template used to generate migration files
+# file_template = %%(rev)s_%%(slug)s
+
+# max length of characters to apply to the
+# "slug" field
+#truncate_slug_length = 40
+
+# set to 'true' to run the environment during
+# the 'revision' command, regardless of autogenerate
+# revision_environment = false
+
+# set to 'true' to allow .pyc and .pyo files without
+# a source .py file to be detected as revisions in the
+# versions/ directory
+# sourceless = false
+
+sqlalchemy.url = driver://user:pass@localhost/dbname
+
+
+# Logging configuration
+[loggers]
+keys = root,sqlalchemy,alembic
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = WARN
+handlers = console
+qualname =
+
+[logger_sqlalchemy]
+level = WARN
+handlers =
+qualname = sqlalchemy.engine
+
+[logger_alembic]
+level = INFO
+handlers =
+qualname = alembic
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(levelname)-5.5s [%(name)s] %(message)s
+datefmt = %H:%M:%S
diff --git a/gstack/__init__.py b/gstack/__init__.py
index acc141d..276ebbf 100644
--- a/gstack/__init__.py
+++ b/gstack/__init__.py
@@ -21,7 +21,7 @@
import sys
from flask import Flask
-from flask.ext.sqlalchemy import SQLAlchemy
+from gstack.core import db
def _load_config_file():
@@ -29,34 +29,42 @@
os.path.expanduser('~'),
'.gstack/gstack.conf'
)
-
if not os.path.exists(config_file):
sys.exit('No configuration found, please run gstack-configure')
return config_file
+def _load_database():
+ database_file = os.path.join(
+ os.path.expanduser('~'),
+ '.gstack/gstack.sqlite'
+ )
+
+ if not os.path.exists(database_file):
+ sys.exit('No database found, please run gstack-configure')
+
+ return 'sqlite:///' + database_file
+
+
def configure_app(settings=None):
app.config['DATA'] = os.path.abspath(os.path.dirname(__file__)) + '/data'
+ db.init_app(app)
+
if settings:
app.config.from_object(settings)
else:
config_file = _load_config_file()
+ database_uri = _load_database()
app.config.from_pyfile(config_file)
-
- app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + \
- os.path.join(app.config['DATA'], 'app.db')
+ app.config['SQLALCHEMY_DATABASE_URI'] = database_uri
app = Flask(__name__)
-db = SQLAlchemy(app)
publickey_storage = {}
from gstack.controllers import *
-
basedir = os.path.abspath(os.path.dirname(__file__))
-
-db.create_all()
diff --git a/gstack/configure.py b/gstack/configure.py
index 436dbd0..e4d042b 100644
--- a/gstack/configure.py
+++ b/gstack/configure.py
@@ -19,10 +19,14 @@
import os
+from alembic import command
+from alembic.config import Config as AlembicConfig
+
def main():
config_folder = _create_config_folder()
_create_config_file(config_folder)
+ _create_database()
def _create_config_folder():
@@ -69,3 +73,13 @@
config_file.write('CLOUDSTACK_PATH = \'%s\'\n' % cloudstack_path)
config_file.close()
+
+
+def _create_database():
+ directory = os.path.join(os.path.dirname(__file__), '../migrations')
+ config = AlembicConfig(os.path.join(
+ directory,
+ 'alembic.ini'
+ ))
+ config.set_main_option('script_location', directory)
+ command.upgrade(config, 'head', sql=False, tag=None)
diff --git a/gstack/controllers/__init__.py b/gstack/controllers/__init__.py
index 47e309d..97f7847 100644
--- a/gstack/controllers/__init__.py
+++ b/gstack/controllers/__init__.py
@@ -20,5 +20,118 @@
import os
import glob
-__all__ = [os.path.basename(
- f)[:-3] for f in glob.glob(os.path.dirname(__file__) + '/*.py')]
+from gstack import helpers
+from flask import request
+from gstack.services import requester
+from gstack.controllers import errors
+
+__all__ = [os.path.basename(f)[:-3] for f in glob.glob(os.path.dirname(__file__) + '/*.py')]
+
+
+def filter_by_name(data, name):
+ for item in data:
+ if item['name'] == name:
+ return item
+ elif 'displayname' in item and item['displayname'] == name:
+ return item
+
+ return None
+
+
+def _get_items(authorization, args=None):
+ args['listAll'] = 'true'
+
+ response = requester.make_request(
+ args['command'],
+ args,
+ authorization.client_id,
+ authorization.client_secret
+ )
+ response = response[response.keys()[0]]
+
+ return response
+
+
+def get_item_with_name(authorization, name, args, type):
+ response = _get_items(
+ authorization=authorization,
+ args=args
+ )
+
+ if 'count' in response:
+ response = filter_by_name(
+ data=response[type],
+ name=name
+ )
+ return response
+ else:
+ return None
+
+
+def get_item_with_name_or_error(authorization, name, args, type, func_route, to_cloudstack, **kwargs):
+ cloudstack_item = get_item_with_name(authorization, name, args, type)
+
+ if cloudstack_item:
+ return helpers.create_response(to_cloudstack(
+ cloudstack_response=cloudstack_item, **kwargs
+ ))
+ else:
+ return errors.resource_not_found(func_route)
+
+
+def _get_requested_items(authorization, args, type, to_cloudstack, **kwargs):
+ name = None
+ filter = helpers.get_filter(request.args)
+
+ if 'name' in filter:
+ name = filter['name']
+
+ items = []
+
+ if name:
+ cloudstack_item = get_item_with_name(
+ authorization=authorization,
+ name=name,
+ args=args,
+ type=type
+ )
+ if cloudstack_item:
+ items.append(
+ to_cloudstack(
+ cloudstack_response=cloudstack_item, **kwargs
+ )
+ )
+ else:
+ cloudstack_items = _get_items(authorization=authorization, args=args)
+ if cloudstack_items:
+ for cloudstack_item in cloudstack_items[type]:
+ items.append(
+ to_cloudstack(
+ cloudstack_response=cloudstack_item, **kwargs)
+ )
+
+ return items
+
+
+def describe_items_aggregated(authorization, args, type, gce_type, to_cloudstack, **kwargs):
+ from gstack.controllers import zones
+ items = {}
+
+ zone_list = zones.get_zone_names(authorization=authorization)
+
+ for zone in zone_list:
+ kwargs['zone'] = zone
+ zone_items = _get_requested_items(authorization, args, type, to_cloudstack, **kwargs)
+ items['zone/' + zone] = {}
+ if zone_items:
+ items['zone/' + zone][gce_type] = zone_items
+ else:
+ items['zone/' + zone] = errors.no_results_found(zone)
+
+ return items
+
+
+def describe_items(authorization, args, type, to_cloudstack, **kwargs):
+ items = _get_requested_items(authorization, args, type, to_cloudstack, **kwargs)
+
+ return items
diff --git a/gstack/controllers/disks.py b/gstack/controllers/disks.py
index 0743a48..c0bbbb0 100644
--- a/gstack/controllers/disks.py
+++ b/gstack/controllers/disks.py
@@ -20,40 +20,8 @@
import urllib
from flask import request, url_for
from gstack import app, authentication
-from gstack.services import requester
-from gstack.controllers import zones, helper, errors
-
-
-def _get_disks(authorization, args=None):
- command = 'listVolumes'
- if not args:
- args = {}
- cloudstack_response = requester.make_request(
- command,
- args,
- authorization.client_id,
- authorization.client_secret
- )
-
- return cloudstack_response
-
-
-def get_disk_by_name(authorization, disk):
- disk_list = _get_disks(
- authorization=authorization,
- args={
- 'keyword': disk
- }
- )
-
- if disk_list['listvolumesresponse']:
- response = helper.filter_by_name(
- data=disk_list['listvolumesresponse']['volume'],
- name=disk
- )
- return response
- else:
- return None
+from gstack import helpers
+from gstack import controllers
def _cloudstack_volume_to_gce(cloudstack_response, projectid, zone):
@@ -66,17 +34,13 @@
response['description'] = cloudstack_response['name']
response['sizeGb'] = cloudstack_response['size']
- response['selfLink'] = urllib.unquote_plus(helper.get_root_url() + url_for(
+ response['selfLink'] = urllib.unquote_plus(helpers.get_root_url() + url_for(
'getmachinetype',
projectid=projectid,
machinetype=cloudstack_response['name'],
zone=zone
))
-
- if not zone:
- response['zone'] = cloudstack_response['zonename']
- else:
- response['zone'] = zone
+ response['zone'] = zone
return response
@@ -84,44 +48,11 @@
@app.route('/compute/v1/projects/<projectid>/aggregated/disks', methods=['GET'])
@authentication.required
def aggregatedlistdisks(projectid, authorization):
- disk_list = _get_disks(authorization=authorization)
- zone_list = zones.get_zone_names(authorization=authorization)
-
- disk = None
- filter = helper.get_filter(request.args)
-
- if 'name' in filter:
- disk = filter['name']
-
- items = {}
-
- for zone in zone_list:
- zone_disks = []
- if disk:
- disk = get_disk_by_name(
- authorization=authorization,
- disk=disk
- )
- if disk:
- zone_disks.append(
- _cloudstack_volume_to_gce(
- cloudstack_response=disk,
- projectid=projectid,
- zone=zone,
- )
- )
-
- elif disk_list['listvolumesresponse']:
- for disk in disk_list['listvolumesresponse']['volume']:
- zone_disks.append(
- _cloudstack_volume_to_gce(
- cloudstack_response=disk,
- projectid=projectid,
- zone=zone,
- )
- )
- items['zone/' + zone] = {}
- items['zone/' + zone]['disks'] = zone_disks
+ args = {'command': 'listVolumes'}
+ kwargs = {'projectid': projectid}
+ items = controllers.describe_items_aggregated(
+ authorization, args, 'volume', 'disk',
+ _cloudstack_volume_to_gce, **kwargs)
populated_response = {
'kind': 'compute#diskAggregatedList',
@@ -130,49 +61,17 @@
'items': items
}
- return helper.create_response(data=populated_response)
+ return helpers.create_response(data=populated_response)
@app.route('/compute/v1/projects/<projectid>/zones/<zone>/disks', methods=['GET'])
@authentication.required
def listdisks(projectid, authorization, zone):
- disk = None
- filter = helper.get_filter(request.args)
-
- if 'name' in filter:
- disk = filter['name']
-
- items = []
-
- if disk:
- disk_list = _get_disks(
- authorization=authorization,
- args={'keyword': disk}
- )
- if disk_list['listvolumesresponse']:
- disk = helper.filter_by_name(
- data=disk_list['listvolumesresponse']['volume'],
- name=disk
- )
- if disk:
- items.append(
- _cloudstack_volume_to_gce(
- cloudstack_response=disk,
- projectid=projectid,
- zone=zone
- )
- )
- else:
- disk_list = _get_disks(authorization=authorization)
- if disk_list['listvolumesresponse']:
- for disk in disk_list['listvolumesresponse']['volume']:
- items.append(
- _cloudstack_volume_to_gce(
- cloudstack_response=disk,
- projectid=projectid,
- zone=zone
- )
- )
+ args = {'command': 'listVolumes'}
+ kwargs = {'projectid': projectid, 'zone': zone}
+ items = controllers.describe_items(
+ authorization, args, 'volume',
+ _cloudstack_volume_to_gce, **kwargs)
populated_response = {
'kind': 'compute#imageList',
@@ -181,30 +80,15 @@
'items': items
}
- return helper.create_response(data=populated_response)
+ return helpers.create_response(data=populated_response)
@app.route('/compute/v1/projects/<projectid>/zones/<zone>/disks/<disk>', methods=['GET'])
@authentication.required
def getdisk(projectid, authorization, zone, disk):
- response = get_disk_by_name(
- authorization=authorization,
- disk=disk
- )
-
- if response:
- return helper.create_response(
- data=_cloudstack_volume_to_gce(
- cloudstack_response=response,
- projectid=projectid,
- zone=zone
- )
- )
- else:
- func_route = url_for(
- 'getdisk',
- projectid=projectid,
- zone=zone,
- disk=disk
- )
- return errors.resource_not_found(func_route)
+ func_route = url_for('getdisk', projectid=projectid, zone=zone, disk=disk)
+ args = {'command': 'listVolumes'}
+ kwargs = {'projectid': projectid, 'zone': zone}
+ return controllers.get_item_with_name_or_error(
+ authorization, disk, args, 'volume', func_route,
+ _cloudstack_volume_to_gce, **kwargs)
diff --git a/gstack/controllers/errors.py b/gstack/controllers/errors.py
index 4ddbb57..9805ca9 100644
--- a/gstack/controllers/errors.py
+++ b/gstack/controllers/errors.py
@@ -19,17 +19,13 @@
import urllib
from gstack import app
-from flask import jsonify, Response
-
-
-@app.errorhandler(404)
-def not_found(e):
- return Response('Not Found', status=404, mimetype='text/html')
+from gstack import helpers
+from flask import Response
@app.errorhandler(401)
def unauthorized(e):
- res = jsonify({
+ res = {
'error': {
'errors': [
{
@@ -43,14 +39,13 @@
},
'code': 401,
'message': 'Login Required',
- })
+ }
- res.status_code = 401
- return res
+ return helpers.create_errored_response(res, 401)
def resource_not_found(func_url):
- res = jsonify({
+ res = {
'error': {
'errors': [
{
@@ -62,16 +57,16 @@
'code': 404,
'message': 'The resource \'' + urllib.unquote_plus(func_url) + '\' was not found'
}
- })
- res.status_code = 404
- return res
+ }
+
+ return helpers.create_errored_response(res, 404)
def no_results_found(scope):
return ({
"warning": {
"code": "NO_RESULTS_ON_PAGE",
- "message": "There are no results for scope" + scope + " on this page.",
+ "message": "There are no results for scope " + scope + " on this page.",
"data": [{
"key": "scope",
"value": scope
diff --git a/gstack/controllers/firewalls.py b/gstack/controllers/firewalls.py
index 34fb736..b2264ac 100755
--- a/gstack/controllers/firewalls.py
+++ b/gstack/controllers/firewalls.py
@@ -19,15 +19,17 @@
from gstack import app
from gstack import authentication
+from gstack import controllers
+from gstack import helpers
from gstack.services import requester
from gstack.controllers import errors
from flask import jsonify, request, url_for
import json
-def _cloudstack_securitygroup_to_gce(response_item):
- if 'ingressrule' in response_item:
- rules = response_item['ingressrule']
+def _cloudstack_securitygroup_to_gce(cloudstack_response):
+ if 'ingressrule' in cloudstack_response:
+ rules = cloudstack_response['ingressrule']
allowed = []
sourceranges = []
for rule in rules:
@@ -44,16 +46,16 @@
return ({
"kind": "compute#firewall",
"selfLink": '',
- "id": response_item['id'],
+ "id": cloudstack_response['id'],
"creationTimestamp": '',
- "name": response_item['name'],
- "description": response_item['description'],
+ "name": cloudstack_response['name'],
+ "description": cloudstack_response['description'],
"network": '',
"sourceRanges": sourceranges,
"sourceTags": [
''
],
- "targetTags": response_item['tags'],
+ "targetTags": cloudstack_response['tags'],
"allowed": allowed
})
@@ -61,32 +63,10 @@
@app.route('/compute/v1/projects/<projectid>/global/firewalls', methods=['GET'])
@authentication.required
def listsecuritygroups(projectid, authorization):
- command = 'listSecurityGroups'
- args = {}
- cloudstack_response = requester.make_request(
- command,
- args,
- authorization.client_id,
- authorization.client_secret
- )
-
- cloudstack_response = cloudstack_response
-
- app.logger.debug(
- 'Processing request for aggregated list Firewalls\n'
- 'Project: ' + projectid + '\n' +
- json.dumps(cloudstack_response, indent=4, separators=(',', ': '))
- )
-
- firewalls = []
- if cloudstack_response['listsecuritygroupsresponse']:
- res = cloudstack_response['listsecuritygroupsresponse']
- for response_item in res['securitygroup']:
- firewalls.append(response_item)
-
- items = []
- for fw in firewalls:
- items.append(_cloudstack_securitygroup_to_gce(fw))
+ args = {'command': 'listSecurityGroups'}
+ items = controllers.describe_items(
+ authorization, args, 'securitygroup',
+ _cloudstack_securitygroup_to_gce, **{})
populated_response = {
'kind': 'compute#firewallList',
@@ -95,9 +75,7 @@
'items': items
}
- res = jsonify(populated_response)
- res.status_code = 200
- return res
+ return helpers.create_response(data=populated_response)
@app.route('/compute/v1/projects/<projectid>/global/firewalls/<firewall>', methods=['GET'])
@@ -158,7 +136,6 @@
json.dumps(cloudstack_response, indent=4, separators=(',', ': '))
)
- # return Global Operations
populated_response = {}
res = jsonify(populated_response)
diff --git a/gstack/controllers/helper.py b/gstack/controllers/helper.py
deleted file mode 100644
index 05830ce..0000000
--- a/gstack/controllers/helper.py
+++ /dev/null
@@ -1,51 +0,0 @@
-#!/usr/bin/env python
-# encoding: utf-8
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import urllib
-from gstack import app
-from flask import jsonify
-
-
-def create_response(data):
- res = jsonify(data)
- res.status_code = 200
-
- return res
-
-
-def get_filter(data):
- filter = {}
-
- if 'filter' in data:
- filter = urllib.unquote_plus(data['filter'])
- filter = dict(filter.split(' eq ') for values in filter)
-
- return filter
-
-
-def filter_by_name(data, name):
- for item in data:
- if item['name'] == name:
- return item
- return None
-
-
-def get_root_url():
- return 'https://' + \
- app.config['GSTACK_BIND_ADDRESS'] + ':' + app.config['GSTACK_PORT']
diff --git a/gstack/controllers/images.py b/gstack/controllers/images.py
index 25c400a..c31af2b 100755
--- a/gstack/controllers/images.py
+++ b/gstack/controllers/images.py
@@ -19,49 +19,20 @@
import urllib
from gstack import app, authentication
-from gstack.services import requester
-from gstack.controllers import helper, errors
+from gstack import helpers
+from gstack import controllers
from flask import request, url_for
-def _get_templates(authorization, args=None):
- command = 'listTemplates'
- if not args:
- args = {}
-
- if 'templatefilter' not in args:
- args['templatefilter'] = 'executable'
-
- cloudstack_response = requester.make_request(
- command,
- args,
- authorization.client_id,
- authorization.client_secret
- )
- return cloudstack_response
-
-
def get_template_by_name(authorization, image):
- image_list = _get_templates(
- authorization=authorization,
- args={
- 'keyword': image
- }
- )
-
- if image_list['listtemplatesresponse']:
- response = helper.filter_by_name(
- data=image_list['listtemplatesresponse']['template'],
- name=image
- )
- return response
- else:
- return None
+ args = {'templatefilter': 'executable', 'command': 'listTemplates'}
+ return controllers.get_item_with_name(authorization, image, args, 'template')
def _create_populated_image_response(projectid, images=None):
if not images:
images = []
+
populated_response = {
'kind': 'compute#imageList',
'selfLink': request.base_url,
@@ -71,25 +42,15 @@
return populated_response
-def _cloudstack_template_to_gce(cloudstack_response, selfLink=None):
- translate_image_status = {
- 'True': 'Ready',
- 'False': 'Failed'
- }
-
+def _cloudstack_template_to_gce(cloudstack_response):
response = {}
response['kind'] = 'compute#image'
response['id'] = cloudstack_response['id']
response['creationTimestamp'] = cloudstack_response['created']
response['name'] = cloudstack_response['name']
response['description'] = cloudstack_response['displaytext']
- response['status'] = translate_image_status[
- str(cloudstack_response['isready'])]
-
- if selfLink:
- response['selfLink'] = urllib.unquote_plus(selfLink)
- else:
- response['selfLink'] = urllib.unquote_plus(request.base_url)
+ response['status'] = cloudstack_response['isready']
+ response['selfLink'] = urllib.unquote_plus(request.base_url) + '/' + response['name']
return response
@@ -98,46 +59,33 @@
@authentication.required
def listnocentoscloudimages(authorization):
populated_response = _create_populated_image_response('centos-cloud')
- return helper.create_response(data=populated_response)
+ return helpers.create_response(data=populated_response)
@app.route('/compute/v1/projects/debian-cloud/global/images', methods=['GET'])
@authentication.required
def listnodebiancloudimages(authorization):
populated_response = _create_populated_image_response('debian-cloud')
- return helper.create_response(data=populated_response)
+ return helpers.create_response(data=populated_response)
@app.route('/compute/v1/projects/<projectid>/global/images', methods=['GET'])
@authentication.required
def listimages(projectid, authorization):
- image_list = _get_templates(
- authorization=authorization
- )
+ args = {'templatefilter': 'executable', 'command': 'listTemplates'}
+ items = controllers.describe_items(
+ authorization, args, 'template',
+ _cloudstack_template_to_gce, **{})
- images = []
- if image_list['listtemplatesresponse']:
- for image in image_list['listtemplatesresponse']['template']:
- images.append(_cloudstack_template_to_gce(
- cloudstack_response=image,
- selfLink=request.base_url + '/' + image['name']))
-
- populated_response = _create_populated_image_response(projectid, images)
- return helper.create_response(data=populated_response)
+ populated_response = _create_populated_image_response(projectid, items)
+ return helpers.create_response(data=populated_response)
@app.route('/compute/v1/projects/<projectid>/global/images/<image>', methods=['GET'])
@authentication.required
def getimage(projectid, authorization, image):
- response = get_template_by_name(
- authorization=authorization,
- image=image
- )
-
- if response:
- return helper.create_response(
- data=_cloudstack_template_to_gce(response)
- )
- else:
- func_route = url_for('getimage', projectid=projectid, image=image)
- return errors.resource_not_found(func_route)
+ func_route = url_for('getimage', projectid=projectid, image=image)
+ args = {'templatefilter': 'executable', 'command': 'listTemplates'}
+ return controllers.get_item_with_name_or_error(
+ authorization, image, args, 'template', func_route,
+ _cloudstack_template_to_gce, **{})
diff --git a/gstack/controllers/index.py b/gstack/controllers/index.py
index 293c6ac..fc1f925 100755
--- a/gstack/controllers/index.py
+++ b/gstack/controllers/index.py
@@ -19,7 +19,7 @@
from gstack import app
-from gstack.controllers import helper
+from gstack import helpers
import json
@@ -28,9 +28,9 @@
with open(app.config['DATA'] + '/v1.json') as template:
discovery_template = json.loads(template.read())
- discovery_template['baseUrl'] = helper.get_root_url() + '/' + app.config['PATH']
+ discovery_template['baseUrl'] = helpers.get_root_url() + '/' + app.config['PATH']
discovery_template['basePath'] = '/' + app.config['PATH']
- discovery_template['rootUrl'] = helper.get_root_url() + '/'
+ discovery_template['rootUrl'] = helpers.get_root_url() + '/'
discovery_template['servicePath'] = app.config['PATH']
- return helper.create_response(data=discovery_template)
+ return helpers.create_response(data=discovery_template)
diff --git a/gstack/controllers/instances.py b/gstack/controllers/instances.py
index 8bb0e14..1eb0e6a 100755
--- a/gstack/controllers/instances.py
+++ b/gstack/controllers/instances.py
@@ -19,25 +19,12 @@
import json
import urllib
+from gstack import helpers
+from gstack import controllers
from flask import request, url_for
from gstack import app, authentication
from gstack.services import requester
-from gstack.controllers import zones, helper, operations, images, errors, machine_type, networks
-
-
-def _get_virtual_machines(authorization, args=None):
- command = 'listVirtualMachines'
- if not args:
- args = {}
-
- cloudstack_response = requester.make_request(
- command,
- args,
- authorization.client_id,
- authorization.client_secret
- )
-
- return cloudstack_response
+from gstack.controllers import zones, operations, images, errors, machine_type, networks
def _deploy_virtual_machine(authorization, args, projectid):
@@ -65,7 +52,7 @@
if 'network' in args:
network = networks.get_network_by_name(
authorization=authorization,
- securitygroup=args['network']
+ network=args['network']
)
converted_args['securitygroupids'] = network['id']
@@ -83,27 +70,7 @@
return cloudstack_response
-def _destroy_virtual_machine(authorization, instance):
- virtual_machine_id = _get_virtual_machine_by_name(
- authorization,
- instance)['id']
-
- if virtual_machine_id is None:
- func_route = url_for('_destroy_virtual_machine', instance=instance)
- return errors.resource_not_found(func_route)
-
- args = {
- 'id': virtual_machine_id
- }
- return requester.make_request(
- 'destroyVirtualMachine',
- args,
- authorization.client_id,
- authorization.client_secret
- )
-
-
-def _cloudstack_virtual_machine_to_gce(cloudstack_response, zone, projectid):
+def _cloudstack_virtual_machine_to_gce(cloudstack_response, projectid, zone, **kwargs):
response = {}
response['kind'] = 'compute#instance'
response['id'] = cloudstack_response['id']
@@ -118,23 +85,24 @@
response['disks'] = []
networking = {}
- accessconfig = {}
+ accessconfig = []
+ accessconfig.append({})
if 'securitygroup' in cloudstack_response:
networking['network'] = cloudstack_response['securitygroup'][0]['name']
networking['networkIP'] = cloudstack_response['nic'][0]['ipaddress']
networking['name'] = cloudstack_response['nic'][0]['id']
- accessconfig['natIP'] = cloudstack_response['nic'][0]['ipaddress']
+ accessconfig[0]['natIP'] = cloudstack_response['nic'][0]['ipaddress']
networking['accessConfigs'] = []
- accessconfig['kind'] = 'compute#accessConfig'
- accessconfig['type'] = 'ONE_TO_ONE_NAT'
- accessconfig['name'] = 'External NAT'
+ accessconfig[0]['kind'] = 'compute#accessConfig'
+ accessconfig[0]['type'] = 'ONE_TO_ONE_NAT'
+ accessconfig[0]['name'] = 'External NAT'
networking['accessConfigs'] = accessconfig
response['networkInterfaces'].append(networking)
- response['selfLink'] = urllib.unquote_plus(helper.get_root_url() + url_for(
+ response['selfLink'] = urllib.unquote_plus(helpers.get_root_url() + url_for(
'getinstance',
projectid=projectid,
instance=cloudstack_response['name'],
@@ -145,65 +113,14 @@
return response
-def _get_virtual_machine_by_name(authorization, instance):
- virtual_machine_list = _get_virtual_machines(
- authorization=authorization,
- args={
- 'keyword': instance
- }
- )
-
- if virtual_machine_list['listvirtualmachinesresponse']:
- response = helper.filter_by_name(
- data=virtual_machine_list['listvirtualmachinesresponse']['virtualmachine'],
- name=instance
- )
- return response
- else:
- return None
-
-
@app.route('/compute/v1/projects/<projectid>/aggregated/instances', methods=['GET'])
@authentication.required
def aggregatedlistinstances(authorization, projectid):
- zone_list = zones.get_zone_names(authorization=authorization)
- virtual_machine_list = _get_virtual_machines(authorization=authorization)
-
- instance = None
- filter = helper.get_filter(request.args)
-
- if 'name' in filter:
- instance = filter['name']
-
- items = {}
-
- for zone in zone_list:
- zone_instances = []
- if instance:
- virtual_machine = _get_virtual_machine_by_name(
- authorization=authorization,
- instance=instance
- )
- if virtual_machine:
- zone_instances.append(
- _cloudstack_virtual_machine_to_gce(
- cloudstack_response=virtual_machine,
- projectid=projectid,
- zone=zone
- )
- )
-
- elif virtual_machine_list['listvirtualmachinesresponse']:
- for instance in virtual_machine_list['listvirtualmachinesresponse']['virtualmachine']:
- zone_instances.append(
- _cloudstack_virtual_machine_to_gce(
- cloudstack_response=instance,
- projectid=projectid,
- zone=zone
- )
- )
- items['zone/' + zone] = {}
- items['zone/' + zone]['instances'] = zone_instances
+ args = {'command': 'listVirtualMachines'}
+ kwargs = {'projectid': projectid}
+ items = controllers.describe_items_aggregated(
+ authorization, args, 'virtualmachine', 'instances',
+ _cloudstack_virtual_machine_to_gce, **kwargs)
populated_response = {
'kind': 'compute#instanceAggregatedList',
@@ -211,45 +128,17 @@
'selfLink': request.base_url,
'items': items
}
- return helper.create_response(data=populated_response)
+ return helpers.create_response(data=populated_response)
@app.route('/compute/v1/projects/<projectid>/zones/<zone>/instances', methods=['GET'])
@authentication.required
def listinstances(authorization, projectid, zone):
- instance = None
- filter = helper.get_filter(request.args)
-
- if 'name' in filter:
- instance = filter['name']
-
- items = []
-
- if instance:
- virtual_machine = _get_virtual_machine_by_name(
- authorization=authorization,
- instance=instance
- )
- if virtual_machine:
- items.append(
- _cloudstack_virtual_machine_to_gce(
- cloudstack_response=virtual_machine,
- projectid=projectid,
- zone=zone
- )
- )
- else:
- virtual_machine_list = _get_virtual_machines(
- authorization=authorization)
- if virtual_machine_list['listvirtualmachinesresponse']:
- for instance in virtual_machine_list['listvirtualmachinesresponse']['virtualmachine']:
- items.append(
- _cloudstack_virtual_machine_to_gce(
- cloudstack_response=instance,
- projectid=projectid,
- zone=zone,
- )
- )
+ args = {'command': 'listVirtualMachines'}
+ kwargs = {'projectid': projectid, 'zone': zone}
+ items = controllers.describe_items(
+ authorization, args, 'virtualmachine',
+ _cloudstack_virtual_machine_to_gce, **kwargs)
populated_response = {
'kind': 'compute#instance_list',
@@ -258,32 +147,7 @@
'items': items
}
- return helper.create_response(data=populated_response)
-
-
-@app.route('/compute/v1/projects/<projectid>/zones/<zone>/instances/<instance>', methods=['GET'])
-@authentication.required
-def getinstance(projectid, authorization, zone, instance):
- response = _get_virtual_machine_by_name(
- authorization=authorization,
- instance=instance
- )
-
- if response:
- return helper.create_response(
- data=_cloudstack_virtual_machine_to_gce(
- cloudstack_response=response,
- projectid=projectid,
- zone=zone
- )
- )
- else:
- function_route = url_for(
- 'getinstance',
- projectid=projectid,
- zone=zone,
- instance=instance)
- return errors.resource_not_found(function_route)
+ return helpers.create_response(data=populated_response)
@app.route('/compute/v1/projects/<projectid>/zones/<zone>/instances', methods=['POST'])
@@ -293,7 +157,6 @@
args = {}
args['name'] = data['name']
args['serviceoffering'] = data['machineType'].rsplit('/', 1)[1]
- print args['serviceoffering']
args['template'] = data['disks'][0]['initializeParams']['sourceImage'].rsplit('/', 1)[1]
args['zone'] = zone
@@ -304,20 +167,9 @@
deployment_result = _deploy_virtual_machine(authorization, args, projectid)
if 'errortext' in deployment_result['deployvirtualmachineresponse']:
- populated_response = {
- 'kind': 'compute#operation',
- 'operationType': 'insert',
- 'targetLink': '',
- 'status': 'DONE',
- 'progress': 100,
- 'error': {
- 'errors': [{
- 'code': 'RESOURCE_ALREADY_EXISTS',
- 'message': 'the resource \'projects/\'' + projectid +
- '/zones/' + zone + '/instances/' + args['name']
- }]
- }
- }
+ func_route = url_for('addinstance', projectid=projectid, zone=zone)
+ return errors.resource_not_found(func_route)
+
else:
populated_response = operations.create_response(
projectid=projectid,
@@ -325,13 +177,24 @@
authorization=authorization
)
- return helper.create_response(data=populated_response)
+ return helpers.create_response(data=populated_response)
@app.route('/compute/v1/projects/<projectid>/zones/<zone>/instances/<instance>', methods=['DELETE'])
@authentication.required
def deleteinstance(projectid, authorization, zone, instance):
- deletion_result = _destroy_virtual_machine(authorization, instance)
+ args = {'command': 'listVirtualMachines'}
+ virtual_machine = controllers.get_item_with_name(authorization, instance, args, 'virtualmachine')
+
+ virtual_machine_id = virtual_machine['id']
+ args = {'id': virtual_machine_id}
+
+ deletion_result = requester.make_request(
+ 'destroyVirtualMachine',
+ args,
+ authorization.client_id,
+ authorization.client_secret
+ )
populated_response = operations.create_response(
projectid=projectid,
@@ -339,4 +202,15 @@
authorization=authorization
)
- return helper.create_response(data=populated_response)
+ return helpers.create_response(data=populated_response)
+
+
+@app.route('/compute/v1/projects/<projectid>/zones/<zone>/instances/<instance>', methods=['GET'])
+@authentication.required
+def getinstance(projectid, authorization, zone, instance):
+ func_route = url_for('getinstance', projectid=projectid, zone=zone, instance=instance)
+ args = {'command': 'listVirtualMachines'}
+ kwargs = {'projectid': projectid, 'zone': zone}
+ return controllers.get_item_with_name_or_error(
+ authorization, instance, args, 'virtualmachine', func_route,
+ _cloudstack_virtual_machine_to_gce, **kwargs)
diff --git a/gstack/controllers/machine_type.py b/gstack/controllers/machine_type.py
index f55e211..b78a6c5 100755
--- a/gstack/controllers/machine_type.py
+++ b/gstack/controllers/machine_type.py
@@ -20,42 +20,17 @@
import urllib
from gstack import app
from gstack import authentication
-from gstack.services import requester
-from gstack.controllers import errors, zones, helper
+from gstack import helpers
+from gstack import controllers
from flask import request, url_for
-def _get_machinetypes(authorization, args=None):
- command = 'listServiceOfferings'
- if not args:
- args = {}
-
- cloudstack_response = requester.make_request(
- command,
- args,
- authorization.client_id,
- authorization.client_secret
- )
- return cloudstack_response
-
-
def get_machinetype_by_name(authorization, machinetype):
- machinetype_list = _get_machinetypes(
- authorization=authorization
- )
-
- if machinetype_list['listserviceofferingsresponse']:
- response = helper.filter_by_name(
- data=machinetype_list['listserviceofferingsresponse'][
- 'serviceoffering'],
- name=machinetype
- )
- return response
- else:
- return None
+ args = {'command': 'listServiceOfferings'}
+ return controllers.get_item_with_name(authorization, machinetype, args, 'serviceoffering')
-def _cloudstack_machinetype_to_gce(cloudstack_response, projectid, zone):
+def _cloudstack_service_offering_to_gce(cloudstack_response, projectid, zone):
response = {}
response['kind'] = 'compute#machineType'
response['name'] = cloudstack_response['name']
@@ -65,7 +40,7 @@
response['guestCpus'] = cloudstack_response['cpunumber']
response['memoryMb'] = cloudstack_response['memory']
- response['selfLink'] = urllib.unquote_plus(helper.get_root_url() + url_for(
+ response['selfLink'] = urllib.unquote_plus(helpers.get_root_url() + url_for(
'getmachinetype',
projectid=projectid,
machinetype=cloudstack_response['name'],
@@ -79,27 +54,11 @@
@app.route('/compute/v1/projects/<projectid>/aggregated/machineTypes', methods=['GET'])
@authentication.required
def aggregatedlistmachinetypes(projectid, authorization):
- machine_types = _get_machinetypes(authorization)
- zonelist = zones.get_zone_names(authorization)
-
- items = {}
- for zone in zonelist:
- zone_machine_types = []
- if machine_types['listserviceofferingsresponse']:
- for machineType in machine_types['listserviceofferingsresponse']['serviceoffering']:
- zone_machine_types.append(
- _cloudstack_machinetype_to_gce(
- cloudstack_response=machineType,
- projectid=projectid,
- zone=zone
- )
- )
- else:
- zone_machine_types.append(errors.no_results_found(zone))
-
- items['zone/' + zone] = {}
- items['zone/' + zone]['zone'] = zone
- items['zone/' + zone]['machineTypes'] = zone_machine_types
+ args = {'command': 'listServiceOfferings'}
+ kwargs = {'projectid': projectid}
+ items = controllers.describe_items_aggregated(
+ authorization, args, 'serviceoffering', 'machineTypes',
+ _cloudstack_service_offering_to_gce, **kwargs)
populated_response = {
'kind': 'compute#machineTypeAggregatedList',
@@ -107,50 +66,17 @@
'selfLink': urllib.unquote_plus(request.base_url),
'items': items
}
- return helper.create_response(data=populated_response)
+ return helpers.create_response(data=populated_response)
@app.route('/compute/v1/projects/<projectid>/zones/<zone>/machineTypes', methods=['GET'])
@authentication.required
def listmachinetype(projectid, authorization, zone):
- machinetype = None
- filter = helper.get_filter(request.args)
-
- if 'name' in filter:
- machinetype = filter['name']
-
- items = []
-
- if machinetype:
- machinetype_list = _get_machinetypes(
- authorization=authorization,
- args={'keyword': machinetype}
- )
- if machinetype_list['listserviceofferingsresponse']:
- machinetype = helper.filter_by_name(
- data=machinetype_list['listserviceofferingsresponse'][
- 'serviceoffering'],
- name=machinetype
- )
- if machinetype:
- items.append(
- _cloudstack_machinetype_to_gce(
- cloudstack_response=machinetype,
- projectid=projectid,
- zone=zone
- )
- )
- else:
- machinetype_list = _get_machinetypes(authorization=authorization)
- if machinetype_list['listserviceofferingsresponse']:
- for machinetype in machinetype_list['listserviceofferingsresponse']['serviceoffering']:
- items.append(
- _cloudstack_machinetype_to_gce(
- cloudstack_response=machinetype,
- projectid=projectid,
- zone=zone
- )
- )
+ args = {'command': 'listServiceOfferings'}
+ kwargs = {'projectid': projectid, 'zone': zone}
+ items = controllers.describe_items(
+ authorization, args, 'serviceoffering',
+ _cloudstack_service_offering_to_gce, **kwargs)
populated_response = {
'kind': 'compute#imageList',
@@ -159,29 +85,15 @@
'items': items
}
- return helper.create_response(data=populated_response)
+ return helpers.create_response(data=populated_response)
@app.route('/compute/v1/projects/<projectid>/zones/<zone>/machineTypes/<machinetype>', methods=['GET'])
@authentication.required
def getmachinetype(projectid, authorization, zone, machinetype):
- response = get_machinetype_by_name(
- authorization=authorization,
- machinetype=machinetype
- )
-
- if response:
- return helper.create_response(
- data=_cloudstack_machinetype_to_gce(
- cloudstack_response=response,
- projectid=projectid,
- zone=zone
- )
- )
- else:
- func_route = url_for(
- 'getmachinetype',
- projectid=projectid,
- machinetype=machinetype,
- zone=zone)
- return errors.resource_not_found(func_route)
+ func_route = url_for('getmachinetype', projectid=projectid, zone=zone, machinetype=machinetype)
+ args = {'command': 'listServiceOfferings'}
+ kwargs = {'projectid': projectid, 'zone': zone}
+ return controllers.get_item_with_name_or_error(
+ authorization, machinetype, args, 'serviceoffering', func_route,
+ _cloudstack_service_offering_to_gce, **kwargs)
diff --git a/gstack/controllers/networks.py b/gstack/controllers/networks.py
index 03a1799..d2b5fd4 100644
--- a/gstack/controllers/networks.py
+++ b/gstack/controllers/networks.py
@@ -19,42 +19,17 @@
import urllib
import json
+from gstack import helpers
+from gstack import controllers
from flask import request, url_for
from gstack import app, authentication
from gstack.services import requester
-from gstack.controllers import helper, errors
+from gstack.controllers import errors
-def _get_networks(authorization, args=None):
- command = 'listSecurityGroups'
- if not args:
- args = {}
- cloudstack_response = requester.make_request(
- command,
- args,
- authorization.client_id,
- authorization.client_secret
- )
-
- return cloudstack_response
-
-
-def get_network_by_name(authorization, securitygroup):
- securitygroup_list = _get_networks(
- authorization=authorization,
- args={
- 'keyword': securitygroup
- }
- )
-
- if securitygroup_list['listsecuritygroupsresponse']:
- response = helper.filter_by_name(
- data=securitygroup_list['listsecuritygroupsresponse']['securitygroup'],
- name=securitygroup
- )
- return response
- else:
- return None
+def get_network_by_name(authorization, network):
+ args = {'command': 'listSecurityGroups'}
+ return controllers.get_item_with_name(authorization, network, args, 'securitygroup')
def _add_network(authorization, args=None):
@@ -73,8 +48,8 @@
def _delete_network(authorization, projectid, network):
- network_response = get_network_by_name(authorization, network)
-
+ args = {'command': 'listSecurityGroups'}
+ network_response = controllers.get_item_with_name(authorization, network, args, 'securitygroup')
if not network_response:
return None
@@ -92,17 +67,13 @@
)
-def _cloudstack_network_to_gce(cloudstack_response, selfLink=None):
+def _cloudstack_network_to_gce(cloudstack_response):
response = {}
response['kind'] = 'compute#network'
response['id'] = cloudstack_response['id']
response['name'] = cloudstack_response['name']
response['description'] = cloudstack_response['description']
-
- if selfLink:
- response['selfLink'] = urllib.unquote_plus(selfLink)
- else:
- response['selfLink'] = urllib.unquote_plus(request.base_url)
+ response['selfLink'] = urllib.unquote_plus(request.base_url) + '/' + response['name']
return response
@@ -123,42 +94,28 @@
@app.route('/compute/v1/projects/<projectid>/global/networks', methods=['GET'])
@authentication.required
def listnetworks(projectid, authorization):
- securitygroup_list = _get_networks(
- authorization=authorization
- )
-
- networks = []
- if securitygroup_list['listsecuritygroupsresponse']:
- for network in securitygroup_list['listsecuritygroupsresponse']['securitygroup']:
- networks.append(_cloudstack_network_to_gce(
- cloudstack_response=network,
- selfLink=request.base_url + '/' + network['name']))
+ args = {'command': 'listSecurityGroups'}
+ kwargs = {}
+ items = controllers.describe_items(
+ authorization, args, 'securitygroup',
+ _cloudstack_network_to_gce, **kwargs)
populated_response = _create_populated_network_response(
projectid,
- networks
+ items
)
- return helper.create_response(data=populated_response)
+ return helpers.create_response(data=populated_response)
@app.route('/compute/v1/projects/<projectid>/global/networks/<network>', methods=['GET'])
@authentication.required
def getnetwork(projectid, authorization, network):
- response = get_network_by_name(
- authorization=authorization,
- securitygroup=network
- )
-
- if response:
- return helper.create_response(
- data=_cloudstack_network_to_gce(response)
- )
- else:
- func_route = url_for(
- 'getnetwork',
- projectid=projectid,
- network=network)
- return errors.resource_not_found(func_route)
+ func_route = url_for('getnetwork', projectid=projectid, network=network)
+ args = {'command': 'listSecurityGroups'}
+ kwargs = {}
+ return controllers.get_item_with_name_or_error(
+ authorization, network, args, 'securitygroup', func_route,
+ _cloudstack_network_to_gce, **kwargs)
@app.route('/compute/v1/projects/<projectid>/global/networks', methods=['POST'])
@@ -190,7 +147,7 @@
'kind': 'compute#operation',
'operationType': 'insert',
'targetLink': urllib.unquote_plus(
- helper.get_root_url() + url_for(
+ helpers.get_root_url() + url_for(
'getnetwork',
projectid=projectid,
network=data['name']
@@ -200,7 +157,7 @@
'progress': 100
}
- return helper.create_response(data=populated_response)
+ return helpers.create_response(data=populated_response)
@app.route('/compute/v1/projects/<projectid>/global/networks/<network>', methods=['DELETE'])
@@ -224,4 +181,4 @@
'progress': 100
}
- return helper.create_response(data=populated_response)
+ return helpers.create_response(data=populated_response)
diff --git a/gstack/controllers/operations.py b/gstack/controllers/operations.py
index de31b32..9bbdd46 100644
--- a/gstack/controllers/operations.py
+++ b/gstack/controllers/operations.py
@@ -20,7 +20,7 @@
import urllib
from gstack import app, publickey_storage
from gstack import authentication
-from gstack.controllers import helper
+from gstack import helpers
from gstack.services import requester
from flask import url_for
@@ -43,7 +43,7 @@
'operationType': 'delete',
'name': async_result['jobid'],
'startTime': async_result['created'],
- 'selfLink': urllib.unquote_plus(helper.get_root_url() + url_for(
+ 'selfLink': urllib.unquote_plus(helpers.get_root_url() + url_for(
'getoperations',
projectid=projectid,
operationid=async_result['jobid']
@@ -57,14 +57,14 @@
elif async_result['jobstatus'] is 1:
populated_response['status'] = 'DONE'
populated_response['zone'] = urllib.unquote_plus(
- helper.get_root_url() +
+ helpers.get_root_url() +
url_for(
'getzone',
projectid=projectid,
zone=async_result['jobresult']['virtualmachine']['zonename'],
))
populated_response['targetLink'] = urllib.unquote_plus(
- helper.get_root_url() +
+ helpers.get_root_url() +
url_for(
'getinstance',
projectid=projectid,
@@ -112,7 +112,7 @@
'user': async_result['userid'],
'insertTime': async_result['created'],
'startTime': async_result['created'],
- 'selfLink': urllib.unquote_plus(helper.get_root_url() + url_for(
+ 'selfLink': urllib.unquote_plus(helpers.get_root_url() + url_for(
'getoperations',
projectid=projectid,
operationid=async_result['jobid']
@@ -129,14 +129,14 @@
populated_response['status'] = 'DONE'
populated_response['id'] = async_result['jobid']
populated_response['zone'] = urllib.unquote_plus(
- helper.get_root_url() +
+ helpers.get_root_url() +
url_for(
'getzone',
projectid=projectid,
zone=async_result['jobresult']['virtualmachine']['zonename'],
))
populated_response['targetLink'] = urllib.unquote_plus(
- helper.get_root_url() +
+ helpers.get_root_url() +
url_for(
'getinstance',
projectid=projectid,
@@ -182,7 +182,7 @@
@app.route('/compute/v1/projects/<projectid>/global/operations/<operationid>', methods=['GET'])
@authentication.required
def getoperations(authorization, operationid, projectid):
- return helper.create_response(create_response(
+ return helpers.create_response(create_response(
authorization=authorization,
operationid=operationid,
projectid=projectid
diff --git a/gstack/controllers/project.py b/gstack/controllers/project.py
index d6e0776..35408bd 100755
--- a/gstack/controllers/project.py
+++ b/gstack/controllers/project.py
@@ -19,14 +19,21 @@
from gstack import app, publickey_storage
from gstack import authentication
+from gstack import helpers
+from gstack import controllers
from gstack.services import requester
-from gstack.controllers import errors, helper
+from gstack.controllers import errors
from flask import jsonify, request, url_for
import json
import urllib
import collections
+def _get_account_by_name(authorization, projectid):
+ args = {'command': 'listAccounts'}
+ return controllers.get_item_with_name(authorization, projectid, args, 'account')
+
+
def _list_ssh_keys(authorization):
command = 'listTags'
args = {
@@ -63,39 +70,6 @@
return sshkeys
-def _get_accounts(authorization, args=None):
- command = 'listAccounts'
- if not args:
- args = {}
-
- cloudstack_response = requester.make_request(
- command,
- args,
- authorization.client_id,
- authorization.client_secret
- )
-
- return cloudstack_response
-
-
-def _get_account_by_name(authorization, projectid):
- account_list = _get_accounts(
- authorization=authorization,
- args={
- 'keyword': projectid
- }
- )
-
- if account_list['listaccountsresponse']:
- response = helper.filter_by_name(
- data=account_list['listaccountsresponse']['account'],
- name=projectid
- )
- return response
- else:
- return None
-
-
def _format_quota(metric, limit, usage):
quota = {}
quota['metric'] = metric
@@ -213,7 +187,7 @@
res = jsonify({
"kind": "compute#operation",
'operationType': 'setMetadata',
- 'targetLink': urllib.unquote_plus(helper.get_root_url() + url_for(
+ 'targetLink': urllib.unquote_plus(helpers.get_root_url() + url_for(
'getproject',
projectid=projectid
)),
diff --git a/gstack/controllers/regions.py b/gstack/controllers/regions.py
index 50f3e5d..dbaf5b7 100755
--- a/gstack/controllers/regions.py
+++ b/gstack/controllers/regions.py
@@ -19,31 +19,18 @@
from gstack import app
+from gstack import helpers
+from gstack import controllers
from gstack import authentication
-from gstack.services import requester
-from gstack.controllers import errors, helper
from flask import request, url_for
-def _get_regions(authorization, args=None):
- command = 'listRegions'
- if not args:
- args = {}
-
- cloudstack_response = requester.make_request(
- command,
- args,
- authorization.client_id,
- authorization.client_secret
- )
- return cloudstack_response
-
-
-def _cloudstack_region_to_gce(response_item):
+def _cloudstack_account_to_gce(cloudstack_response):
response = {}
response['kind'] = 'compute#region'
- response['description'] = response_item['name']
- response['id'] = response_item['id']
+ response['description'] = cloudstack_response['name']
+ response['name'] = cloudstack_response['name']
+ response['id'] = cloudstack_response['id']
response['status'] = 'UP'
return response
@@ -51,37 +38,26 @@
@app.route('/compute/v1/projects/<projectid>/regions', methods=['GET'])
@authentication.required
def listregions(projectid, authorization):
- cloudstack_response = _get_regions(authorization)
-
- regions = []
-
- if cloudstack_response['listregionsresponse']:
- for region in cloudstack_response['listregionsresponse']['region']:
- regions.append(_cloudstack_region_to_gce(region))
+ args = {'command': 'listRegions'}
+ kwargs = {}
+ items = controllers.describe_items(
+ authorization, args, 'region',
+ _cloudstack_account_to_gce, **kwargs)
populated_response = {
'kind': 'compute#regionList',
'id': 'projects/' + projectid + '/regions',
'selfLink': request.base_url,
- 'items': regions
+ 'items': items
}
- return helper.create_response(data=populated_response)
+ return helpers.create_response(data=populated_response)
@app.route('/compute/v1/projects/<projectid>/regions/<region>', methods=['GET'])
@authentication.required
def getregion(projectid, authorization, region):
- cloudstack_response = _get_regions(
- authorization=authorization,
- args={'name': region}
- )
-
- if region == cloudstack_response['listregionsresponse']['region'][0]['name']:
- return helper.create_response(
- data=_cloudstack_region_to_gce(
- cloudstack_response['listregionsresponse']['region'][0]
- )
- )
- else:
- function_route = url_for('getregion', projectid=projectid, region=region)
- return errors.resource_not_found(function_route)
+ func_route = url_for('getregion', projectid=projectid, region=region)
+ args = {'command': 'listRegions'}
+ return controllers.get_item_with_name_or_error(
+ authorization, region, args, 'region', func_route,
+ _cloudstack_account_to_gce, **{})
diff --git a/gstack/controllers/zones.py b/gstack/controllers/zones.py
index 8e433f3..8e9985c 100755
--- a/gstack/controllers/zones.py
+++ b/gstack/controllers/zones.py
@@ -18,15 +18,20 @@
# under the License.
from flask import request, url_for
+from gstack import helpers
+from gstack import controllers
from gstack import app, authentication
from gstack.services import requester
-from gstack.controllers import helper, errors
-def _get_zones(authorization, args=None):
+def get_zone_by_name(authorization, zone):
+ args = {'command': 'listZones'}
+ return controllers.get_item_with_name(authorization, zone, args, 'zone')
+
+
+def _get_zones(authorization):
command = 'listZones'
- if not args:
- args = {}
+ args = {}
cloudstack_response = requester.make_request(
command,
args,
@@ -37,29 +42,9 @@
return cloudstack_response
-def get_zone_by_name(authorization, zone):
- zone_list = _get_zones(
- authorization=authorization,
- args={
- 'keyword': zone
- }
- )
-
- if zone_list['listzonesresponse']:
- response = helper.filter_by_name(
- data=zone_list['listzonesresponse']['zone'],
- name=zone
- )
- return response
- else:
- return None
-
-
def get_zone_names(authorization):
zone_list = _get_zones(authorization)
- print zone_list
-
zones = []
if zone_list['listzonesresponse']:
for zone in zone_list['listzonesresponse']['zone']:
@@ -68,17 +53,13 @@
return zones
-def _cloudstack_zone_to_gce(response_item):
- translate_zone_status = {
- 'Enabled': 'UP',
- 'Disabled': 'DOWN'
- }
+def _cloudstack_zone_to_gce(cloudstack_response):
return ({
'kind': 'compute#zone',
- 'name': response_item['name'],
- 'description': response_item['name'],
- 'id': response_item['id'],
- 'status': translate_zone_status[str(response_item['allocationstate'])]
+ 'name': cloudstack_response['name'],
+ 'description': cloudstack_response['name'],
+ 'id': cloudstack_response['id'],
+ 'status': cloudstack_response['allocationstate']
})
@@ -99,21 +80,14 @@
'items': items
}
- return helper.create_response(data=populated_response)
+ return helpers.create_response(data=populated_response)
@app.route('/compute/v1/projects/<projectid>/zones/<zone>', methods=['GET'])
@authentication.required
def getzone(projectid, authorization, zone):
- response = get_zone_by_name(
- authorization=authorization,
- zone=zone
- )
-
- if response:
- return helper.create_response(
- data=_cloudstack_zone_to_gce(response)
- )
- else:
- func_route = url_for('getzone', projectid=projectid, zone=zone)
- return errors.resource_not_found(func_route)
+ func_route = url_for('getzone', projectid=projectid, zone=zone)
+ args = {'command': 'listZones'}
+ return controllers.get_item_with_name_or_error(
+ authorization, zone, args, 'zone', func_route,
+ _cloudstack_zone_to_gce, **{})
diff --git a/gstack/core.py b/gstack/core.py
new file mode 100644
index 0000000..2fb3de1
--- /dev/null
+++ b/gstack/core.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from flask_sqlalchemy import SQLAlchemy
+
+db = SQLAlchemy()
+
+
+class Service(object):
+ __model__ = None
+
+ def _isinstance(self, model, raise_error=True):
+ valid_type = isinstance(model, self.__model__)
+ if not valid_type and raise_error:
+ raise ValueError('%s is not of type %s' % (model, self.__model__))
+ return valid_type
+
+ def save(self, model):
+ self._isinstance(model)
+ db.session.add(model)
+ db.session.commit()
+ return model
+
+ def get(self, primarykey):
+ return self.__model__.query.get(primarykey)
+
+ def create(self, **kwargs):
+ return self.save(self.__model__(**kwargs))
+
+ def delete(self, model):
+ self._isinstance(model)
+ db.session.delete(model)
+ db.session.commit()
diff --git a/gstack/data/app.db b/gstack/data/app.db
deleted file mode 100644
index 5d29940..0000000
--- a/gstack/data/app.db
+++ /dev/null
Binary files differ
diff --git a/gstack/helpers.py b/gstack/helpers.py
index d8dddd9..76e73c2 100644
--- a/gstack/helpers.py
+++ b/gstack/helpers.py
@@ -18,6 +18,38 @@
# under the License.
import os
+import urllib
+from gstack import app
+from flask import jsonify
+
+
+def create_response(data):
+ res = jsonify(data)
+ res.status_code = 200
+
+ return res
+
+
+def create_errored_response(data, status_code):
+ res = jsonify(data)
+ res.status_code = status_code
+
+ return res
+
+
+def get_filter(data):
+ filter = {}
+
+ if 'filter' in data:
+ filter = urllib.unquote_plus(data['filter'])
+ filter = dict(filter.split(' eq ') for values in filter)
+
+ return filter
+
+
+def get_root_url():
+ return 'https://' + \
+ app.config['GSTACK_BIND_ADDRESS'] + ':' + app.config['GSTACK_PORT']
def read_file(name):
diff --git a/gstack/models/__init__.py b/gstack/models/__init__.py
index 7cf0837..a9d480d 100644
--- a/gstack/models/__init__.py
+++ b/gstack/models/__init__.py
@@ -16,3 +16,20 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
+
+from gstack.core import Service
+from gstack.models.accesstoken import AccessToken
+from gstack.models.refreshtoken import RefreshToken
+from gstack.models.client import Client
+
+
+class AccessTokenService(Service):
+ __model__ = AccessToken
+
+
+class RefreshTokenService(Service):
+ __model__ = RefreshToken
+
+
+class ClientService(Service):
+ __model__ = Client
diff --git a/gstack/models/accesstoken.py b/gstack/models/accesstoken.py
index 8acac57..cb4d159 100644
--- a/gstack/models/accesstoken.py
+++ b/gstack/models/accesstoken.py
@@ -21,7 +21,14 @@
class AccessToken(db.Model):
+ __tablename__ = 'accesstoken'
access_token = db.Column(db.String(100), primary_key=True, unique=True)
client_id = db.Column(db.String(100), unique=True)
expires_in = db.Column(db.Integer)
data = db.Column(db.String(500))
+
+ def __init__(self, access_token, client_id, expires_in, data):
+ self.access_token = access_token
+ self.client_id = client_id
+ self.expires_in = expires_in
+ self.data = data
diff --git a/gstack/models/client.py b/gstack/models/client.py
index f6d03dd..00a9fe6 100644
--- a/gstack/models/client.py
+++ b/gstack/models/client.py
@@ -21,5 +21,10 @@
class Client(db.Model):
+ __tablename__ = 'client'
client_id = db.Column(db.String(100), primary_key=True, unique=True)
client_secret = db.Column(db.String(100), unique=True)
+
+ def __init__(self, client_id, client_secret):
+ self.client_id = client_id
+ self.client_secret = client_secret
diff --git a/gstack/models/refreshtoken.py b/gstack/models/refreshtoken.py
index 0fba279..9b8bab0 100644
--- a/gstack/models/refreshtoken.py
+++ b/gstack/models/refreshtoken.py
@@ -21,6 +21,12 @@
class RefreshToken(db.Model):
+ __tablename__ = 'refreshtoken'
refresh_token = db.Column(db.String(100), primary_key=True, unique=True)
client_id = db.Column(db.String(100), unique=True)
data = db.Column(db.String(500))
+
+ def __init__(self, refresh_token, client_id, data):
+ self.refresh_token = refresh_token
+ self.client_id = client_id
+ self.data = data
diff --git a/gstack/oauth2provider.py b/gstack/oauth2provider.py
index 8cc2654..ce08aa1 100644
--- a/gstack/oauth2provider.py
+++ b/gstack/oauth2provider.py
@@ -55,8 +55,8 @@
existing_client.client_secret = client_secret
else:
client = Client(
- client_id=client_id,
- client_secret=client_secret
+ client_id,
+ client_secret
)
db.session.add(client)
@@ -77,15 +77,8 @@
def persist_authorization_code(self, client_id, code, scope):
return
- def persist_token_information(
- self,
- client_id,
- scope,
- access_token,
- token_type,
- expires_in,
- refresh_token,
- data):
+ def persist_token_information(self, client_id, scope, access_token, token_type,
+ expires_in, refresh_token, data):
client = Client.query.get(client_id)
if client is not None:
existing_access_token = AccessToken.query.filter_by(
@@ -100,8 +93,7 @@
else:
db.session.add(
AccessToken(
- access_token=access_token, client_id=client_id,
- expires_in=expires_in, data=json.dumps(data)
+ access_token, client_id, expires_in, json.dumps(data)
)
)
@@ -110,11 +102,7 @@
existing_refresh_token.data = json.dumps(data)
else:
db.session.add(
- RefreshToken(
- refresh_token=refresh_token, client_id=client_id,
- data=json.dumps(data)
- )
- )
+ RefreshToken(refresh_token, client_id, json.dumps(data)))
db.session.commit()
return True
diff --git a/migrations/README b/migrations/README
new file mode 100644
index 0000000..98e4f9c
--- /dev/null
+++ b/migrations/README
@@ -0,0 +1 @@
+Generic single-database configuration.
\ No newline at end of file
diff --git a/migrations/alembic.ini b/migrations/alembic.ini
new file mode 100644
index 0000000..a2a4775
--- /dev/null
+++ b/migrations/alembic.ini
@@ -0,0 +1,57 @@
+# A generic, single database configuration.
+
+[alembic]
+# path to migration scripts
+script_location = .
+
+# template used to generate migration files
+# file_template = %%(rev)s_%%(slug)s
+
+# max length of characters to apply to the
+# "slug" field
+#truncate_slug_length = 40
+
+# set to 'true' to run the environment during
+# the 'revision' command, regardless of autogenerate
+# revision_environment = false
+
+# set to 'true' to allow .pyc and .pyo files without
+# a source .py file to be detected as revisions in the
+# versions/ directory
+# sourceless = false
+
+
+# Logging configuration
+[loggers]
+keys = root,sqlalchemy,alembic
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = WARN
+handlers = console
+qualname =
+
+[logger_sqlalchemy]
+level = WARN
+handlers =
+qualname = sqlalchemy.engine
+
+[logger_alembic]
+level = INFO
+handlers =
+qualname = alembic
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(levelname)-5.5s [%(name)s] %(message)s
+datefmt = %H:%M:%S
diff --git a/migrations/env.py b/migrations/env.py
new file mode 100644
index 0000000..cab0b22
--- /dev/null
+++ b/migrations/env.py
@@ -0,0 +1,77 @@
+from __future__ import with_statement
+from alembic import context
+from sqlalchemy import engine_from_config, pool
+from logging.config import fileConfig
+
+import os
+from gstack.core import db
+
+# this is the Alembic Config object, which provides
+# access to the values within the .ini file in use.
+config = context.config
+
+# Interpret the config file for Python logging.
+# This line sets up loggers basically.
+fileConfig(config.config_file_name)
+
+
+# other values from the config, defined by the needs of env.py,
+# can be acquired:
+# my_important_option = config.get_main_option("my_important_option")
+# ... etc.
+
+database_file = os.path.join(
+ os.path.expanduser('~'),
+ '.gstack/gstack.sqlite'
+)
+
+config.set_main_option('sqlalchemy.url', 'sqlite:///' + database_file)
+
+
+def run_migrations_offline():
+ """Run migrations in 'offline' mode.
+
+ This configures the context with just a URL
+ and not an Engine, though an Engine is acceptable
+ here as well. By skipping the Engine creation
+ we don't even need a DBAPI to be available.
+
+ Calls to context.execute() here emit the given string to the
+ script output.
+
+ """
+ url = config.get_main_option("sqlalchemy.url")
+ context.configure(url=url, target_metadata=target_metadata)
+
+ with context.begin_transaction():
+ context.run_migrations()
+
+def run_migrations_online():
+ """Run migrations in 'online' mode.
+
+ In this scenario we need to create an Engine
+ and associate a connection with the context.
+
+ """
+ engine = engine_from_config(
+ config.get_section(config.config_ini_section),
+ prefix='sqlalchemy.',
+ poolclass=pool.NullPool)
+
+ connection = engine.connect()
+ context.configure(
+ connection=connection,
+ target_metadata=db.metadata
+ )
+
+ try:
+ with context.begin_transaction():
+ context.run_migrations()
+ finally:
+ connection.close()
+
+if context.is_offline_mode():
+ run_migrations_offline()
+else:
+ run_migrations_online()
+
diff --git a/migrations/script.py.mako b/migrations/script.py.mako
new file mode 100644
index 0000000..9570201
--- /dev/null
+++ b/migrations/script.py.mako
@@ -0,0 +1,22 @@
+"""${message}
+
+Revision ID: ${up_revision}
+Revises: ${down_revision}
+Create Date: ${create_date}
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = ${repr(up_revision)}
+down_revision = ${repr(down_revision)}
+
+from alembic import op
+import sqlalchemy as sa
+${imports if imports else ""}
+
+def upgrade():
+ ${upgrades if upgrades else "pass"}
+
+
+def downgrade():
+ ${downgrades if downgrades else "pass"}
diff --git a/migrations/versions/76bb287a37d_.py b/migrations/versions/76bb287a37d_.py
new file mode 100644
index 0000000..27e6df6
--- /dev/null
+++ b/migrations/versions/76bb287a37d_.py
@@ -0,0 +1,60 @@
+"""empty message
+
+Revision ID: 76bb287a37d
+Revises: None
+Create Date: 2014-06-16 19:15:05.475000
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '76bb287a37d'
+down_revision = None
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+ op.create_table('accesstoken',
+ sa.Column('access_token', sa.String(length=255), nullable=False),
+ sa.Column(
+ 'client_id',
+ sa.String(length=255),
+ nullable=True),
+ sa.Column(
+ 'expires_in',
+ sa.String(length=255),
+ nullable=True),
+ sa.Column(
+ 'data',
+ sa.String(length=255),
+ nullable=True),
+ sa.PrimaryKeyConstraint('access_token'),
+ sa.UniqueConstraint('client_id')
+ )
+ op.create_table('client',
+ sa.Column('client_id', sa.String(length=255), nullable=False),
+ sa.Column(
+ 'client_secret',
+ sa.String(length=255),
+ nullable=True),
+ sa.PrimaryKeyConstraint('client_id'),
+ sa.UniqueConstraint('client_secret')
+ )
+ op.create_table('refreshtoken',
+ sa.Column('refresh_token', sa.String(length=255), nullable=False),
+ sa.Column(
+ 'client_id',
+ sa.String(length=255),
+ nullable=True),
+ sa.Column(
+ 'data',
+ sa.String(length=255),
+ nullable=True),
+ sa.PrimaryKeyConstraint('refresh_token'),
+ sa.UniqueConstraint('client_id')
+ )
+
+
+def downgrade():
+ pass
diff --git a/pylint.rc b/pylint.rc
index 315faa8..6173b69 100644
--- a/pylint.rc
+++ b/pylint.rc
@@ -24,33 +24,7 @@
[MESSAGES CONTROL]
-# Enable the message, report, category or checker with the given id(s). You can
-# either give multiple identifier separated by comma (,) or put this option
-# multiple time.
-#enable=
-
-# Disable the message, report, category or checker with the given id(s). You
-# can either give multiple identifier separated by comma (,) or put this option
-# multiple time (only on the command line, not in the configuration file where
-# it should appear only once).
-# F0401: *Unable to import %r*
-# E0611: *No name %r in module %r*
-# E1101: *%s %r has no %r member*
-# W0142: *Used * or ** magic*
-# W0212: *Access to a protected member %s of a client class*
-# :R0201: *Method could be a function*
-# w0703: Allow catching Exception
-# R0801: 1: Similar lines in 2 files, badamson: had trouble disabling this locally
-# FIXME: should be re-enabled after it's fixed
-# hbrown: I don't think R0801 can be disabled locally
-# http://www.logilab.org/ticket/6905
-# pylint #6905: R0801 message cannot be disabled locally [open]
-# R0901: Too many ancestors
-# C0111: missing docstring
-# C0301: line too long (too many to fix right now)
-# C0103: invalid variable name (too many to fix right now)
-# I0011: disabling pylint error
-disable=F0401,E0611,E1101,W0142,W0212,R0201,W0703,R0801,R0901,C0111,C0301,C0103,E1002,W0231,W0232,W0401,W0402,W0511,W0603,W0611,W0612,W0613,W0614,W0621,W0622,W0702,W0710,R0922,I0011,E1103
+disable=F0401,E0611,E1120,W0110,E1121,R0401,E1101,W0142,W0141,E1003,E1102,W0212,R0201,W0703,R0801,R0901,C0111,C0301,C0103,E1002,W0231,W0232,W0401,W0402,W0511,W0603,W0611,W0612,W0613,W0614,W0621,W0622,W0702,W0710,R0922,I0011,E1103
diff --git a/setup.py b/setup.py
index 576eb24..13600cf 100755
--- a/setup.py
+++ b/setup.py
@@ -45,7 +45,8 @@
url="https://github.com/NOPping/gstack",
platforms=("Any"),
license="LICENSE.txt",
- package_data={'': ['LICENSE.txt', 'data/*']},
+ package_data={'': ['LICENSE.txt', 'data/*'],
+ 'ec2stack': ['templates/*.json']},
packages=[
"gstack", "gstack.controllers", "gstack.models",
"gstack.services", "gstack.data", "pyoauth2"],
@@ -55,6 +56,17 @@
"pyopenssl",
"Flask-SQLAlchemy",
"flask",
+ "alembic"
+ ],
+ classifiers=[
+ 'Development Status :: 3 - Alpha',
+ 'Environment :: Console',
+ 'Intended Audience :: System Administrators',
+ 'License :: OSI Approved :: Apache Software License',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Topic :: Utilities',
+ 'Programming Language :: Python :: 2.7',
],
zip_safe=False,
entry_points="""
diff --git a/tests/__init__.py b/tests/__init__.py
index b7a5e33..6308895 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -4,12 +4,12 @@
from unittest import TestCase
import mock
-
import json
from gstack import app, configure_app
from gstack.helpers import read_file
from . import settings
+from gstack.core import db
from .utils import FlaskTestCaseMixin
class GStackTestCase(TestCase):
@@ -22,6 +22,11 @@
def _configure_app(self):
configure_app(settings=settings)
+ def _unauthed_user(self):
+ response = self.get('/compute/v1/projects/exampleproject/global/images')
+ self.assert_unauthorized(response)
+
+
def _auth_example_user(self):
data = {}
data['code'] = 'hjrZryvgLYo3R833NkHHV8jYmxQhsD8TjKWzOm2f'
@@ -49,11 +54,14 @@
self._configure_app()
self.app = app
self.client = self.app.test_client()
- self._auth_example_user()
self.app_context = self.app.app_context()
self.app_context.push()
+ db.create_all()
+ self._unauthed_user()
+ self._auth_example_user()
+
def tearDown(self):
super(GStackTestCase, self).tearDown()
+ db.drop_all()
self.app_context.pop()
-
diff --git a/tests/data/valid_get_account.json b/tests/data/valid_get_account.json
new file mode 100644
index 0000000..65f3fcf
--- /dev/null
+++ b/tests/data/valid_get_account.json
@@ -0,0 +1,66 @@
+{
+ "secondarystoragetotal": 0,
+ "primarystorageavailable": "Unlimited",
+ "domain": "brogand93@darrenbrogan.ie",
+ "domainid": "42f2b0d0-3953-485f-984d-b8d67185d358",
+ "vpclimit": "Unlimited",
+ "iplimit": "20",
+ "memorytotal": 0,
+ "secondarystorageavailable": "Unlimited",
+ "vmtotal": 0,
+ "cputotal": 0,
+ "vpctotal": 0,
+ "id": "ddbdf378-e8d9-47e0-964b-661d0d8414b8",
+ "networkavailable": "Unlimited",
+ "projectlimit": "Unlimited",
+ "networklimit": "Unlimited",
+ "iptotal": -27,
+ "volumetotal": 0,
+ "snapshotlimit": "0",
+ "state": "enabled",
+ "networktotal": 0,
+ "accounttype": 0,
+ "cpuavailable": "Unlimited",
+ "primarystoragetotal": 0,
+ "templatelimit": "0",
+ "snapshottotal": 0,
+ "templateavailable": "0",
+ "vmlimit": "20",
+ "vpcavailable": "0",
+ "primarystoragelimit": "Unlimited",
+ "volumelimit": "20",
+ "templatetotal": 0,
+ "secondarystoragelimit": "Unlimited",
+ "user": [
+ {
+ "username": "accountname",
+ "account": "brogand93@darrenbrogan.ie",
+ "domainid": "42f2b0d0-3953-485f-984d-b8d67185d358",
+ "firstname": "brogand93",
+ "created": "2013-09-17T15:35:34+0200",
+ "lastname": "darrenbrogan.ie",
+ "iscallerchilddomain": false,
+ "domain": "brogand93@darrenbrogan.ie",
+ "email": "brogand93@darrenbrogan.ie",
+ "secretkey": "lM9fLm8XQwezvLOd10Qt3wXH7j9mRgaKbEg3nRDnj7FtlF3yx54EWd9mR5sB1ec5LQDV6gjpy6sfDo6ndUeeww",
+ "state": "enabled",
+ "apikey": "0ZyexOgzlfTx076LYBFz4oT_ShJvxUcezgvsW6gvRZ_BloSsb5uUTAm-FtHvKBvAUP0S7ZN6bIcRf4zEtbm3PQ",
+ "accounttype": 0,
+ "timezone": "CET",
+ "id": "26a772da-dc25-4f2b-b0f1-e095e3717a30",
+ "isdefault": false,
+ "accountid": "ddbdf378-e8d9-47e0-964b-661d0d8414b8"
+ }
+ ],
+ "projectavailable": "Unlimited",
+ "isdefault": false,
+ "memoryavailable": "Unlimited",
+ "projecttotal": 0,
+ "volumeavailable": "20",
+ "name": "accountname",
+ "vmavailable": "20",
+ "ipavailable": "0",
+ "memorylimit": "Unlimited",
+ "cpulimit": "Unlimited",
+ "snapshotavailable": "0"
+}
\ No newline at end of file
diff --git a/tests/data/valid_get_image.json b/tests/data/valid_get_image.json
new file mode 100644
index 0000000..3348483
--- /dev/null
+++ b/tests/data/valid_get_image.json
@@ -0,0 +1,28 @@
+{
+ "domain": "ROOT",
+ "domainid": "2edae3e4-95e4-11e3-b2e4-d19c9d3e5e1d",
+ "ostypename": "CentOS 5.3 (32-bit)",
+ "zoneid": "1e47a2fc-61c7-401c-b90e-416b472ada64",
+ "displaytext": "CentOS 5.3(64-bit) no GUI (Simulator)",
+ "ostypeid": "2e678976-95e4-11e3-b2e4-d19c9d3e5e1d",
+ "passwordenabled": false,
+ "id": "a32d70ee-95e4-11e3-b2e4-d19c9d3e5e1d",
+ "size": 2147483648,
+ "isready": true,
+ "format": "VHD",
+ "templatetype": "BUILTIN",
+ "crosszones": true,
+ "zonename": "Sandbox-simulator",
+ "status": "Download Complete",
+ "isdynamicallyscalable": false,
+ "tags": [],
+ "isfeatured": true,
+ "sshkeyenabled": false,
+ "isextractable": false,
+ "account": "system",
+ "name": "imagename",
+ "created": "2014-02-15T02:50:13+0000",
+ "hypervisor": "Simulator",
+ "ispublic": true,
+ "checksum": ""
+}
\ No newline at end of file
diff --git a/tests/data/valid_get_instance.json b/tests/data/valid_get_instance.json
new file mode 100644
index 0000000..528cba4
--- /dev/null
+++ b/tests/data/valid_get_instance.json
@@ -0,0 +1,104 @@
+{
+ "domain": "brogand93@darrenbrogan.ie",
+ "domainid": "42f2b0d0-3953-485f-984d-b8d67185d358",
+ "haenable": false,
+ "templatename": "Linux CentOS 6.5 64-bit",
+ "securitygroup": [
+ {
+ "egressrule": [],
+ "account": "brogand93@darrenbrogan.ie",
+ "description": "Default Security Group",
+ "tags": [],
+ "ingressrule": [],
+ "id": "6033ff41-53ff-4443-b0bb-f6c5c0191c34",
+ "name": "default"
+ }
+ ],
+ "zoneid": "1128bd56-b4d9-4ac6-a7b9-c715b187ce11",
+ "keypair": "brogand93@darrenbrogan.ie",
+ "cpunumber": 1,
+ "passwordenabled": true,
+ "id": "71f13c6d-1590-4e82-9cdd-22eb9bcad0db",
+ "displayvm": true,
+ "state": "Running",
+ "guestosid": "113038d0-a8cd-4d20-92be-ea313f87c3ac",
+ "memory": 1024,
+ "serviceofferingid": "b6cd1ff5-3a2f-4e9d-a4d1-8988c1191fe8",
+ "zonename": "ch-gva-2",
+ "isdynamicallyscalable": false,
+ "displayname": "foobar",
+ "tags": [
+ {
+ "account": "brogand93@darrenbrogan.ie",
+ "domainid": "42f2b0d0-3953-485f-984d-b8d67185d358",
+ "resourcetype": "UserVm",
+ "resourceid": "71f13c6d-1590-4e82-9cdd-22eb9bcad0db",
+ "domain": "brogand93@darrenbrogan.ie",
+ "value": "root:ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDqAuui+xCVPXaFD4cP2MuWnDlktg9vMT/SNzF17UiAzKEbxT/mNayTDAr",
+ "key": "0-sshkey-segment"
+ },
+ {
+ "account": "brogand93@darrenbrogan.ie",
+ "domainid": "42f2b0d0-3953-485f-984d-b8d67185d358",
+ "resourcetype": "UserVm",
+ "resourceid": "71f13c6d-1590-4e82-9cdd-22eb9bcad0db",
+ "domain": "brogand93@darrenbrogan.ie",
+ "value": "DbY/BgGYC5bHuGlb/eE1r4EGpwSXZitGkTI4ThldrSp0Em7psO8AibdpYrFxlOmDFp9wKVD6xbY2HT1ySwvKi+ZwSR5yHcEKq15e",
+ "key": "1-sshkey-segment"
+ },
+ {
+ "account": "brogand93@darrenbrogan.ie",
+ "domainid": "42f2b0d0-3953-485f-984d-b8d67185d358",
+ "resourcetype": "UserVm",
+ "resourceid": "71f13c6d-1590-4e82-9cdd-22eb9bcad0db",
+ "domain": "brogand93@darrenbrogan.ie",
+ "value": "V4eez/3qC1vIcssKmwu5+ZBneZAvWAfxHEKsQU0dsCVvHdn8g7tFXXtg4QCGtE4yzK5v3/+f1AdtIi4hvJoMyi8MV0KSa8e/ravd",
+ "key": "2-sshkey-segment"
+ },
+ {
+ "account": "brogand93@darrenbrogan.ie",
+ "domainid": "42f2b0d0-3953-485f-984d-b8d67185d358",
+ "resourcetype": "UserVm",
+ "resourceid": "71f13c6d-1590-4e82-9cdd-22eb9bcad0db",
+ "domain": "brogand93@darrenbrogan.ie",
+ "value": "Hbgj44PncFBB8O6epVdXPbClZwtkz9D6GEQaOArxk9tX8YEgTFnmsnNuaoZgs7giMj2N7jQe2qXh5R0nsTTuH brogand@microv",
+ "key": "3-sshkey-segment"
+ },
+ {
+ "account": "brogand93@darrenbrogan.ie",
+ "domainid": "42f2b0d0-3953-485f-984d-b8d67185d358",
+ "resourcetype": "UserVm",
+ "resourceid": "71f13c6d-1590-4e82-9cdd-22eb9bcad0db",
+ "domain": "brogand93@darrenbrogan.ie",
+ "value": "ac",
+ "key": "4-sshkey-segment"
+ }
+ ],
+ "nic": [
+ {
+ "networkid": "00304a04-c7ea-4e77-a786-18bc64347bf7",
+ "macaddress": "06:b0:2e:00:01:04",
+ "isolationuri": "ec2://untagged",
+ "networkname": "guestNetworkForBasicZone",
+ "gateway": "185.19.28.1",
+ "traffictype": "Guest",
+ "broadcasturi": "vlan://untagged",
+ "netmask": "255.255.254.0",
+ "type": "Shared",
+ "ipaddress": "185.19.28.199",
+ "id": "2b5cc781-d310-43cf-9d68-66719f43855d",
+ "isdefault": true
+ }
+ ],
+ "cpuspeed": 2198,
+ "templateid": "c34bf3f0-318b-4d77-b0ca-f20585d05d32",
+ "affinitygroup": [],
+ "account": "brogand93@darrenbrogan.ie",
+ "name": "instancename",
+ "created": "2014-06-02T21:11:52+0200",
+ "hypervisor": "KVM",
+ "rootdevicetype": "ROOT",
+ "rootdeviceid": 0,
+ "serviceofferingname": "Tiny",
+ "templatedisplaytext": "Linux CentOS 6.5 64-bit 10GB Disk"
+ }
\ No newline at end of file
diff --git a/tests/data/valid_get_security_group.json b/tests/data/valid_get_security_group.json
new file mode 100644
index 0000000..14a7412
--- /dev/null
+++ b/tests/data/valid_get_security_group.json
@@ -0,0 +1,33 @@
+{
+ "egressrule": [],
+ "account": "example-account",
+ "domainid": "66d69e46-a95b-437b-ac6c-bcaa5331999d",
+ "description": "Default Security Group",
+ "tags": [],
+ "domain": "example-account",
+ "ingressrule": [
+ {
+ "protocol": "tcp",
+ "cidr": "0.0.0.0/0",
+ "startport": 22,
+ "endport": 22,
+ "ruleid": "3d92cc70-8c84-4e8a-9989-6efcd7ff7905"
+ },
+ {
+ "protocol": "icmp",
+ "cidr": "0.0.0.0/0",
+ "ruleid": "2ba7dd7b-13b2-49ae-bf8e-26ffadd32c9e",
+ "icmpcode": 0,
+ "icmptype": 0
+ },
+ {
+ "protocol": "tcp",
+ "cidr": "0.0.0.0/0",
+ "startport": 8080,
+ "endport": 8080,
+ "ruleid": "c4562b3c-d1b0-4844-a771-3c3434e1a5d0"
+ }
+ ],
+ "id": "1f95ee9b-b291-48c1-9492-0eee632677e3",
+ "name": "networkname"
+}
\ No newline at end of file
diff --git a/tests/data/valid_get_service_offering.json b/tests/data/valid_get_service_offering.json
new file mode 100644
index 0000000..a5381c0
--- /dev/null
+++ b/tests/data/valid_get_service_offering.json
@@ -0,0 +1,16 @@
+{
+ "iscustomized": false,
+ "name": "machinetypename",
+ "created": "2013-02-08T17:20:17+0100",
+ "storagetype": "local",
+ "limitcpuuse": false,
+ "cpuspeed": 2198,
+ "offerha": false,
+ "isvolatile": false,
+ "cpunumber": 1,
+ "memory": 512,
+ "displaytext": "Micro 512mb 1cpu",
+ "issystem": false,
+ "id": "71004023-bb72-4a97-b1e9-bc66dfce9470",
+ "defaultuse": false
+}
\ No newline at end of file
diff --git a/tests/data/valid_get_zone.json b/tests/data/valid_get_zone.json
new file mode 100644
index 0000000..4f438e3
--- /dev/null
+++ b/tests/data/valid_get_zone.json
@@ -0,0 +1,10 @@
+{
+ "localstorageenabled": true,
+ "name": "zonename",
+ "zonetoken": "ccb0a60c-79c8-3230-ab8b-8bdbe8c45bb7",
+ "securitygroupsenabled": true,
+ "allocationstate": "Enabled",
+ "dhcpprovider": "VirtualRouter",
+ "networktype": "Basic",
+ "id": "1128bd56-b4d9-4ac6-a7b9-c715b187ce11"
+}
\ No newline at end of file
diff --git a/tests/instances_tests.py b/tests/instances_tests.py
index 9420adc..6f548ae 100644
--- a/tests/instances_tests.py
+++ b/tests/instances_tests.py
@@ -11,7 +11,6 @@
class InstancesTestCase(GStackAppTestCase):
def test_list_instances(self):
-
get = mock.Mock()
get.return_value.text = read_file('tests/data/valid_describe_instances.json')
get.return_value.status_code = 200
@@ -23,7 +22,6 @@
self.assert_ok(response)
def test_aggregated_list_instances(self):
-
get = mock.Mock()
get.return_value.text = read_file('tests/data/valid_describe_instances.json')
get.return_value.status_code = 200
@@ -42,7 +40,6 @@
self.assert_ok(response)
def test_list_instances_with_name_filter(self):
-
get = mock.Mock()
get.return_value.text = read_file('tests/data/valid_describe_instance.json')
get.return_value.status_code = 200
@@ -56,7 +53,6 @@
self.assert_ok(response)
def test_aggregated_list_instances_with_name_filter(self):
-
get = mock.Mock()
get.return_value.text = read_file('tests/data/valid_describe_instance.json')
get.return_value.status_code = 200
@@ -77,7 +73,6 @@
self.assert_ok(response)
def test_get_instance(self):
-
get = mock.Mock()
get.return_value.text = read_file('tests/data/valid_describe_instance.json')
get.return_value.status_code = 200
@@ -89,7 +84,6 @@
self.assert_ok(response)
def test_get_instance_instance_not_found(self):
-
get = mock.Mock()
get.return_value.text = read_file('tests/data/empty_describe_instances.json')
get.return_value.status_code = 200
@@ -103,13 +97,12 @@
in response.data
def test_delete_instance(self):
-
get = mock.Mock()
get.return_value.text = read_file('tests/data/valid_async_destroy_vm.json')
get.return_value.status_code = 200
- get_instance_id = mock.Mock()
- get_instance_id.return_value = {'id':'virtualmachineid'}
+ get_instance = mock.Mock()
+ get_instance.return_value = json.loads(read_file('tests/data/valid_get_instance.json'))
get_async_result = mock.Mock()
get_async_result.return_value = json.loads(read_file('tests/data/valid_run_instance.json'))
@@ -118,8 +111,8 @@
with mock.patch('requests.get', get):
with mock.patch(
- 'gstack.controllers.instances._get_virtual_machine_by_name',
- get_instance_id
+ 'gstack.controllers.get_item_with_name',
+ get_instance
):
with mock.patch(
'gstack.controllers.operations._get_async_result',
@@ -172,35 +165,35 @@
get.return_value.status_code = 200
get_templates = mock.Mock()
- get_templates.return_value = json.loads(read_file('tests/data/valid_describe_images.json'))
+ get_templates.return_value = json.loads(read_file('tests/data/valid_get_image.json'))
get_zones = mock.Mock()
- get_zones.return_value = json.loads(read_file('tests/data/valid_describe_zone.json'))
+ get_zones.return_value = json.loads(read_file('tests/data/valid_get_zone.json'))
get_service_offerings = mock.Mock()
- get_service_offerings.return_value = json.loads(read_file('tests/data/valid_describe_service_offering.json'))
+ get_service_offerings.return_value = json.loads(read_file('tests/data/valid_get_service_offering.json'))
get_networks = mock.Mock()
- get_networks.return_value = json.loads(read_file('tests/data/valid_describe_security_group.json'))
+ get_networks.return_value = json.loads(read_file('tests/data/valid_get_security_group.json'))
get_async_result = mock.Mock()
get_async_result.return_value = json.loads(read_file('tests/data/valid_run_instance.json'))
with mock.patch('requests.get', get):
with mock.patch(
- 'gstack.controllers.images._get_templates',
+ 'gstack.controllers.images.get_template_by_name',
get_templates
):
with mock.patch(
- 'gstack.controllers.zones._get_zones',
+ 'gstack.controllers.zones.get_zone_by_name',
get_zones
):
with mock.patch(
- 'gstack.controllers.machine_type._get_machinetypes',
+ 'gstack.controllers.machine_type.get_machinetype_by_name',
get_service_offerings
):
with mock.patch(
- 'gstack.controllers.networks._get_networks',
+ 'gstack.controllers.networks.get_network_by_name',
get_networks
):
with mock.patch(
diff --git a/tests/networks_tests.py b/tests/networks_tests.py
index edb8d33..d88aa82 100644
--- a/tests/networks_tests.py
+++ b/tests/networks_tests.py
@@ -100,10 +100,10 @@
get.return_value.status_code = 200
get_networks = mock.Mock()
- get_networks.return_value = json.loads(read_file('tests/data/valid_describe_security_group.json'))
+ get_networks.return_value = json.loads(read_file('tests/data/valid_get_security_group.json'))
with mock.patch('requests.get', get):
- with mock.patch('gstack.controllers.networks._get_networks', get_networks):
+ with mock.patch('gstack.controllers.get_item_with_name', get_networks):
headers = {
'authorization': 'Bearer ' + str(GStackAppTestCase.access_token),
}
@@ -119,10 +119,10 @@
get.return_value.status_code = 200
get_networks = mock.Mock()
- get_networks.return_value = json.loads(read_file('tests/data/valid_describe_security_group.json'))
+ get_networks.return_value = None
with mock.patch('requests.get', get):
- with mock.patch('gstack.controllers.networks._get_networks', get_networks):
+ with mock.patch('gstack.controllers.get_item_with_name', get_networks):
headers = {
'authorization': 'Bearer ' + str(GStackAppTestCase.access_token),
}
diff --git a/tests/project_tests.py b/tests/project_tests.py
index d3a58ee..9576ffd 100644
--- a/tests/project_tests.py
+++ b/tests/project_tests.py
@@ -11,14 +11,14 @@
def test_get_project(self):
get = mock.Mock()
- get.return_value = json.loads(read_file('tests/data/valid_describe_account.json'))
+ get.return_value = json.loads(read_file('tests/data/valid_get_account.json'))
get_tags = mock.Mock()
get_tags.return_value.text = read_file('tests/data/valid_describe_tags.json')
get_tags.return_value.status_code = 200
with mock.patch('requests.get', get_tags):
- with(mock.patch('gstack.controllers.project._get_accounts', get)):
+ with(mock.patch('gstack.controllers.get_item_with_name', get)):
headers = {'authorization': 'Bearer ' + str(GStackAppTestCase.access_token)}
response = self.get('/compute/v1/projects/accountname', headers=headers)
@@ -33,7 +33,7 @@
get_tags.return_value.status_code = 200
with mock.patch('requests.get', get_tags):
- with(mock.patch('gstack.controllers.project._get_accounts', get)):
+ with(mock.patch('gstack.controllers._get_items', get)):
headers = {'authorization': 'Bearer ' + str(GStackAppTestCase.access_token)}
response = self.get('/compute/v1/projects/invalidaccountname', headers=headers)
diff --git a/tests/settings.py b/tests/settings.py
index 867bf91..278c50d 100644
--- a/tests/settings.py
+++ b/tests/settings.py
@@ -7,5 +7,6 @@
CLOUDSTACK_PATH = '/compute'
DEBUG=False
-
TESTING = True
+
+SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
diff --git a/tests/utils.py b/tests/utils.py
index 60f57b1..c442322 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -55,3 +55,6 @@
def assert_not_found(self, response):
return self.assert_status_code(response, 404)
+ def assert_unauthorized(self, response):
+ return self.assert_status_code(response, 401)
+