Merge pull request #86 from ed-/pagination

Pagination
This commit is contained in:
Tim Simpson 2012-06-01 12:41:16 -07:00
commit 53c37b8e48
15 changed files with 439 additions and 114 deletions

View File

@ -34,6 +34,10 @@ class ReddwarfContext(context.RequestContext):
"""
def __init__(self, **kwargs):
self.limit = kwargs['limit']
self.marker = kwargs['marker']
del kwargs['limit']
del kwargs['marker']
super(ReddwarfContext, self).__init__(**kwargs)
def to_dict(self):
@ -43,6 +47,8 @@ class ReddwarfContext(context.RequestContext):
'show_deleted': self.show_deleted,
'read_only': self.read_only,
'auth_tok': self.auth_tok,
'limit': self.limit,
'marker': self.marker
}
@classmethod

View File

@ -0,0 +1,97 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC.
# All Rights Reserved.
#
# Licensed 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
import urlparse
from xml.dom import minidom
class PaginatedDataView(object):
def __init__(self, collection_type, collection, current_page_url,
next_page_marker=None):
self.collection_type = collection_type
self.collection = collection
self.current_page_url = current_page_url
self.next_page_marker = next_page_marker
def data(self):
return {self.collection_type: self.collection,
'links': self._links,
}
def _links(self):
if not self.next_page_marker:
return []
app_url = AppUrl(self.current_page_url)
next_url = app_url.change_query_params(marker=self.next_page_marker)
next_link = {'rel': 'next',
'href': str(next_url),
}
return [next_link]
class SimplePaginatedDataView(object):
# In some cases, we can't create a PaginatedDataView because
# we don't have a collection query object to create a view on.
# In that case, we have to supply the URL and collection manually.
def __init__(self, url, name, view, marker):
self.url = url
self.name = name
self.view = view
self.marker = marker
def data(self):
if not self.marker:
return self.view.data()
app_url = AppUrl(self.url)
next_url = str(app_url.change_query_params(marker=self.marker))
next_link = {'rel': 'next',
'href': next_url}
view_data = {self.name: self.view.data()[self.name],
'links': [next_link]}
return view_data
class AppUrl(object):
def __init__(self, url):
self.url = url
def __str__(self):
return self.url
def change_query_params(self, **kwargs):
# Seeks out the query params in a URL and changes/appends to them
# from the kwargs given. So change_query_params(foo='bar')
# would remove from the URL any old instance of foo=something and
# then add &foo=bar to the URL.
parsed_url = urlparse.urlparse(self.url)
# Build a dictionary out of the query parameters in the URL
query_params = dict(urlparse.parse_qsl(parsed_url.query))
# Use kwargs to change or update any values in the query dict.
query_params.update(kwargs)
# Build a new query based on the updated query dict.
new_query_params = urllib.urlencode(query_params)
return self.__class__(
urlparse.ParseResult(parsed_url.scheme,
parsed_url.netloc, parsed_url.path,
parsed_url.params, new_query_params,
parsed_url.fragment).geturl())

View File

@ -38,7 +38,7 @@ def create_nova_client(context):
'http://0.0.0.0:5000/v2.0')
client = Client(context.user, context.auth_tok,
project_id=context.tenant, auth_url=PROXY_AUTH_URL)
client.client.auth_token=context.auth_tok
client.client.auth_token = context.auth_tok
client.client.management_url = "%s/%s/" % (COMPUTE_URL, context.tenant)
return client
@ -52,8 +52,8 @@ def create_nova_volume_client(context):
'http://0.0.0.0:5000/v2.0')
client = Client(context.user, context.auth_tok,
project_id=context.tenant, auth_url=PROXY_AUTH_URL)
client.client.auth_token=context.auth_tok
client.client.management_url="%s/%s/" % (VOLUME_URL, context.tenant)
client.client.auth_token = context.auth_tok
client.client.management_url = "%s/%s/" % (VOLUME_URL, context.tenant)
return client

View File

@ -265,12 +265,18 @@ class ContextMiddleware(openstack_wsgi.Middleware):
def __init__(self, application):
super(ContextMiddleware, self).__init__(application)
def _extract_limits(self, params):
return dict([(key, params[key]) for key in params.keys()
if key in ["limit", "marker"]])
def process_request(self, request):
tenant_id = request.headers.get('X-Tenant-Id', None)
auth_tok = request.headers['X-Auth-Token']
user = request.headers.get('X-User', None)
context = rd_context.ReddwarfContext(auth_tok=auth_tok, user=user,
tenant=tenant_id)
auth_tok = request.headers["X-Auth-Token"]
limits = self._extract_limits(request.params)
context = rd_context.ReddwarfContext(auth_tok=auth_tok,
tenant=tenant_id,
limit=limits.get('limit'),
marker=limits.get('marker'))
request.environ[CONTEXT_KEY] = context
@classmethod

View File

@ -54,21 +54,19 @@ class Query(object):
def delete(self):
db_api.delete_all(self._query_func, self._model, **self._conditions)
#TODO(hub-cap): Reenable pagination when we have a need for it
# def limit(self, limit=200, marker=None, marker_column=None):
# return db_api.find_all_by_limit(self._query_func,
# self._model,
# self._conditions,
# limit=limit,
# marker=marker,
# marker_column=marker_column)
#
# def paginated_collection(self, limit=200, marker=None,
# marker_column=None):
# collection = self.limit(int(limit) + 1, marker, marker_column)
# if len(collection) > int(limit):
# return (collection[0:-1], collection[-2]['id'])
# return (collection, None)
def limit(self, limit=200, marker=None, marker_column=None):
return db_api.find_all_by_limit(self._query_func,
self._model,
self._conditions,
limit=limit,
marker=marker,
marker_column=marker_column)
def paginated_collection(self, limit=200, marker=None, marker_column=None):
collection = self.limit(int(limit) + 1, marker, marker_column)
if len(collection) > int(limit):
return (collection[0:-1], collection[-2]['id'])
return (collection, None)
class Queryable(object):

View File

@ -157,10 +157,16 @@ class RootHistory(object):
class Users(object):
DEFAULT_LIMIT = int(config.Config.get('users_page_size', '20'))
@classmethod
def load(cls, context, instance_id):
load_and_verify(context, instance_id)
user_list = create_guest_client(context, instance_id).list_users()
limit = int(context.limit or Users.DEFAULT_LIMIT)
limit = Users.DEFAULT_LIMIT if limit > Users.DEFAULT_LIMIT else limit
client = create_guest_client(context, instance_id)
user_list, next_marker = client.list_users(limit=limit,
marker=context.marker)
model_users = []
for user in user_list:
mysql_user = guest_models.MySQLUser()
@ -173,7 +179,7 @@ class Users(object):
model_users.append(User(mysql_user.name,
mysql_user.password,
dbs))
return model_users
return model_users, next_marker
class Schema(object):
@ -198,10 +204,17 @@ class Schema(object):
class Schemas(object):
DEFAULT_LIMIT = int(config.Config.get('databases_page_size', '20'))
@classmethod
def load(cls, context, instance_id):
load_and_verify(context, instance_id)
schemas = create_guest_client(context, instance_id).list_databases()
limit = int(context.limit or Schemas.DEFAULT_LIMIT)
if limit > Schemas.DEFAULT_LIMIT:
limit = Schemas.DEFAULT_LIMIT
client = create_guest_client(context, instance_id)
schemas, next_marker = client.list_databases(limit=limit,
marker=context.marker)
model_schemas = []
for schema in schemas:
mysql_schema = guest_models.MySQLDatabase()
@ -209,4 +222,4 @@ class Schemas(object):
model_schemas.append(Schema(mysql_schema.name,
mysql_schema.collate,
mysql_schema.character_set))
return model_schemas
return model_schemas, next_marker

View File

@ -19,6 +19,7 @@ import logging
import webob.exc
from reddwarf.common import exception
from reddwarf.common import pagination
from reddwarf.common import wsgi
from reddwarf.guestagent.db import models as guest_models
from reddwarf.instance import models as instance_models
@ -56,6 +57,10 @@ class BaseController(wsgi.Controller):
return utils.stringify_keys(utils.exclude(model_params,
*self.exclude_attr))
def _extract_limits(self, params):
return dict([(key, params[key]) for key in params.keys()
if key in ["limit", "marker"]])
class RootController(BaseController):
"""Controller for instance functionality"""
@ -101,8 +106,11 @@ class UserController(BaseController):
LOG.info(_("Listing users for instance '%s'") % instance_id)
LOG.info(_("req : '%s'\n\n") % req)
context = req.environ[wsgi.CONTEXT_KEY]
users = models.Users.load(context, instance_id)
return wsgi.Result(views.UsersView(users).data(), 200)
users, next_marker = models.Users.load(context, instance_id)
view = views.UsersView(users)
paged = pagination.SimplePaginatedDataView(req.url, 'users', view,
next_marker)
return wsgi.Result(paged.data(), 200)
def create(self, req, body, tenant_id, instance_id):
"""Creates a set of users"""
@ -145,9 +153,11 @@ class SchemaController(BaseController):
LOG.info(_("Listing schemas for instance '%s'") % instance_id)
LOG.info(_("req : '%s'\n\n") % req)
context = req.environ[wsgi.CONTEXT_KEY]
schemas = models.Schemas.load(context, instance_id)
# Not exactly sure why we cant return a wsgi.Result() here
return wsgi.Result(views.SchemasView(schemas).data(), 200)
schemas, next_marker = models.Schemas.load(context, instance_id)
view = views.SchemasView(schemas)
paged = pagination.SimplePaginatedDataView(req.url, 'databases', view,
next_marker)
return wsgi.Result(paged.data(), 200)
def create(self, req, body, tenant_id, instance_id):
"""Creates a set of schemas"""

View File

@ -39,9 +39,12 @@ class API(object):
self.id = id
def _call(self, method_name, **kwargs):
LOG.debug("Calling %s" % method_name)
try:
return rpc.call(self.context, self._get_routing_key(),
result = rpc.call(self.context, self._get_routing_key(),
{"method": method_name, "args": kwargs})
LOG.debug("Result is %s" % result)
return result
except Exception as e:
LOG.error(e)
raise exception.GuestError(original_message=str(e))
@ -72,10 +75,10 @@ class API(object):
LOG.debug(_("Creating Users for Instance %s"), self.id)
self._cast("create_user", users=users)
def list_users(self):
def list_users(self, limit=None, marker=None):
"""Make an asynchronous call to list database users"""
LOG.debug(_("Listing Users for Instance %s"), self.id)
return self._call("list_users")
return self._call("list_users", limit=limit, marker=marker)
def delete_user(self, user):
"""Make an asynchronous call to delete an existing database user"""
@ -88,10 +91,10 @@ class API(object):
LOG.debug(_("Creating databases for Instance %s"), self.id)
self._cast("create_database", databases=databases)
def list_databases(self):
def list_databases(self, limit=None, marker=None):
"""Make an asynchronous call to list databases"""
LOG.debug(_("Listing databases for Instance %s"), self.id)
return self._call("list_databases")
return self._call("list_databases", limit=limit, marker=marker)
def delete_database(self, database):
"""Make an asynchronous call to delete an existing database

View File

@ -47,6 +47,7 @@ from reddwarf.common import config
from reddwarf.common import utils
from reddwarf.guestagent.db import models
from reddwarf.guestagent.volume import VolumeDevice
from reddwarf.guestagent.query import Query
from reddwarf.instance import models as rd_models
@ -390,7 +391,7 @@ class MySqlAdmin(object):
LOG.debug("result = " + str(result))
return result.rowcount != 0
def list_databases(self):
def list_databases(self, limit=None, marker=None):
"""List databases the user created on this mysql instance"""
LOG.debug(_("---Listing Databases---"))
databases = []
@ -400,51 +401,74 @@ class MySqlAdmin(object):
# the lost+found directory will show up in mysql as a database
# which will create errors if you try to do any database ops
# on it. So we remove it here if it exists.
t = text('''
SELECT
schema_name as name,
default_character_set_name as charset,
default_collation_name as collation
FROM
information_schema.schemata
WHERE
schema_name not in
('mysql', 'information_schema',
'lost+found', '#mysql50#lost+found')
ORDER BY
schema_name ASC;
''')
q = Query()
q.columns = [
'schema_name as name',
'default_character_set_name as charset',
'default_collation_name as collation',
]
q.tables = ['information_schema.schemata']
q.where = ['''schema_name not in (
'mysql', 'information_schema',
'lost+found', '#mysql50#lost+found'
)''']
q.order = ['schema_name ASC']
if limit:
q.limit = limit + 1
if marker:
q.where.append("schema_name > '%s'" % marker)
t = text(str(q))
database_names = client.execute(t)
next_marker = None
LOG.debug(_("database_names = %r") % database_names)
for database in database_names:
for count, database in enumerate(database_names):
if count >= limit:
break
LOG.debug(_("database = %s ") % str(database))
mysql_db = models.MySQLDatabase()
mysql_db.name = database[0]
next_marker = mysql_db.name
mysql_db.character_set = database[1]
mysql_db.collate = database[2]
databases.append(mysql_db.serialize())
LOG.debug(_("databases = ") + str(databases))
return databases
if database_names.rowcount <= limit:
next_marker = None
return databases, next_marker
def list_users(self):
def list_users(self, limit=None, marker=None):
"""List users that have access to the database"""
LOG.debug(_("---Listing Users---"))
users = []
client = LocalSqlClient(get_engine())
with client:
mysql_user = models.MySQLUser()
t = text("""select User from mysql.user where host !=
'localhost';""")
q = Query()
q.columns = ['User']
q.tables = ['mysql.user']
q.where = ["host != 'localhost'"]
q.order = ['User']
if marker:
q.where.append("User > '%s'" % marker)
if limit:
q.limit = limit + 1
t = text(str(q))
result = client.execute(t)
next_marker = None
LOG.debug("result = " + str(result))
for row in result:
for count, row in enumerate(result):
if count >= limit:
break
LOG.debug("user = " + str(row))
mysql_user = models.MySQLUser()
mysql_user.name = row['User']
next_marker = row['User']
# Now get the databases
t = text("""SELECT grantee, table_schema
from information_schema.SCHEMA_PRIVILEGES
group by grantee, table_schema;""")
q = Query()
q.columns = ['grantee', 'table_schema']
q.tables = ['information_schema.SCHEMA_PRIVILEGES']
q.group = ['grantee', 'table_schema']
t = text(str(q))
db_result = client.execute(t)
for db in db_result:
matches = re.match("^'(.+)'@", db['grantee'])
@ -454,8 +478,10 @@ class MySqlAdmin(object):
mysql_db.name = db['table_schema']
mysql_user.databases.append(mysql_db.serialize())
users.append(mysql_user.serialize())
if result.rowcount <= limit:
next_marker = None
LOG.debug("users = " + str(users))
return users
return users, next_marker
class DBaaSAgent(object):
@ -479,11 +505,11 @@ class DBaaSAgent(object):
def delete_user(self, user):
MySqlAdmin().delete_user(user)
def list_databases(self):
return MySqlAdmin().list_databases()
def list_databases(self, limit=None, marker=None):
return MySqlAdmin().list_databases(limit, marker)
def list_users(self):
return MySqlAdmin().list_users()
def list_users(self, limit=None, marker=None):
return MySqlAdmin().list_users(limit, marker)
def enable_root(self):
return MySqlAdmin().enable_root()

View File

@ -0,0 +1,71 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2011 OpenStack, LLC.
# All Rights Reserved.
#
# Licensed 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.
"""
Intermediary class for building SQL queries for use by the guest agent.
"""
class Query(object):
def __init__(self, columns=[], tables=[], where=[], order=[], limit=0):
self.columns = columns
self.tables = tables
self.where = where
self.order = order
self.limit = limit
@property
def _columns(self):
return ', '.join(self.columns) if self.columns else "*"
@property
def _tables(self):
return ', '.join(self.tables)
@property
def _where(self):
if not self.where:
return ""
return "WHERE %s" % (" AND ".join(self.where))
@property
def _order(self):
if not self.order:
return ''
return "ORDER BY %s" % (', '.join(self.order))
@property
def _limit(self):
if not self.limit:
return ''
return "LIMIT %s" % str(self.limit)
def __str__(self):
query = [
"SELECT %s" % self._columns,
"FROM %s" % self._tables,
self._where,
self._order,
self._limit
]
return '\n'.join(query)
def __repr__(self):
return str(self)

View File

@ -24,17 +24,20 @@ import time
from reddwarf import db
from novaclient import exceptions as nova_exceptions
from reddwarf.common import config
from reddwarf.common import exception as rd_exceptions
from reddwarf.common import pagination
from reddwarf.common import utils
from reddwarf.instance.tasks import InstanceTask
from reddwarf.instance.tasks import InstanceTasks
from reddwarf.common.models import ModelBase
from novaclient import exceptions as nova_exceptions
from reddwarf.common.remote import create_dns_client
from reddwarf.common.remote import create_guest_client
from reddwarf.common.remote import create_nova_client
from reddwarf.common.remote import create_nova_volume_client
from reddwarf.common.remote import create_guest_client
from reddwarf.guestagent import api as guest_api
from reddwarf.instance.tasks import InstanceTask
from reddwarf.instance.tasks import InstanceTasks
from eventlet import greenthread
@ -273,13 +276,13 @@ class Instance(object):
if volume_size:
volume_info = cls._create_volume(context, db_info, volume_size)
block_device_mapping = volume_info['block_device']
device_path=volume_info['device_path']
mount_point=volume_info['mount_point']
device_path = volume_info['device_path']
mount_point = volume_info['mount_point']
volumes = volume_info['volumes']
else:
block_device_mapping = None
device_path=None
mount_point=None
device_path = None
mount_point = None
volumes = []
client = create_nova_client(context)
@ -384,9 +387,7 @@ class Instance(object):
@property
def links(self):
#TODO(tim.simpson): Review whether we should be returning the server
# links.
return self._build_links(self.server.links)
return self.server.links
@property
def addresses(self):
@ -580,18 +581,31 @@ def create_server_list_matcher(server_list):
class Instances(object):
DEFAULT_LIMIT = int(config.Config.get('instances_page_size', '20'))
@staticmethod
def load(context):
if context is None:
raise TypeError("Argument context not defined.")
client = create_nova_client(context)
servers = client.servers.list()
db_infos = DBInstance.find_all()
limit = int(context.limit or Instances.DEFAULT_LIMIT)
if limit > Instances.DEFAULT_LIMIT:
limit = Instances.DEFAULT_LIMIT
data_view = DBInstance.find_by_pagination('instances', db_infos, "foo",
limit=limit,
marker=context.marker)
next_marker = data_view.next_page_marker
ret = []
find_server = create_server_list_matcher(servers)
for db in db_infos:
LOG.debug("checking for db [id=%s, compute_instance_id=%s]" %
(db.id, db.compute_instance_id))
for db in data_view.collection:
status = InstanceServiceStatus.find_by(instance_id=db.id)
try:
# TODO(hub-cap): Figure out if this is actually correct.
# We are not sure if we should be doing some validation.
@ -622,7 +636,7 @@ class Instances(object):
"or instance was deleted"))
continue
ret.append(Instance(context, db, server, status, volumes))
return ret
return ret, next_marker
class DatabaseModelBase(ModelBase):
@ -681,6 +695,16 @@ class DatabaseModelBase(ModelBase):
"""Override in inheritors to format/modify any conditions."""
return raw_conditions
@classmethod
def find_by_pagination(cls, collection_type, collection_query,
paginated_url, **kwargs):
elements, next_marker = collection_query.paginated_collection(**kwargs)
return pagination.PaginatedDataView(collection_type,
elements,
paginated_url,
next_marker)
class DBInstance(DatabaseModelBase):
"""Defines the task being executed plus the start time."""

View File

@ -21,10 +21,10 @@ import webob.exc
from reddwarf.common import config
from reddwarf.common import exception
from reddwarf.common import pagination
from reddwarf.common import utils
from reddwarf.common import wsgi
from reddwarf.instance import models, views
from reddwarf.common import exception as rd_exceptions
#TODO(ed-): Import these properly after this is restructured
from reddwarf.flavor import models as flavormodels
@ -70,6 +70,10 @@ class BaseController(wsgi.Controller):
config.Config.get('reddwarf_volume_support', 'False'))
pass
def _extract_limits(self, params):
return dict([(key, params[key]) for key in params.keys()
if key in ["limit", "marker"]])
def _extract_required_params(self, params, model_name):
params = params or {}
model_params = params.get(model_name, {})
@ -112,16 +116,16 @@ class InstanceController(BaseController):
if key in _actions:
if selected_action is not None:
msg = _("Only one action can be specified per request.")
raise rd_exceptions.BadRequest(msg)
raise exception.BadRequest(msg)
selected_action = _actions[key]
else:
msg = _("Invalid instance action: %s") % key
raise rd_exceptions.BadRequest(msg)
raise exception.BadRequest(msg)
if selected_action:
return selected_action(instance, body)
else:
raise rd_exceptions.BadRequest(_("Invalid request body."))
raise exception.BadRequest(_("Invalid request body."))
def _action_restart(self, instance, body):
instance.validate_can_perform_restart_or_reboot()
@ -150,20 +154,20 @@ class InstanceController(BaseController):
if selected_option is not None:
msg = _("Not allowed to resize volume and flavor at the "
"same time.")
raise rd_exceptions.BadRequest(msg)
raise exception.BadRequest(msg)
selected_option = options[key]
args = body['resize'][key]
else:
raise rd_exceptions.BadRequest("Invalid resize argument %s"
raise exception.BadRequest("Invalid resize argument %s"
% key)
if selected_option:
return selected_option(instance, args)
else:
raise rd_exceptions.BadRequest(_("Missing resize arguments."))
raise exception.BadRequest(_("Missing resize arguments."))
def _action_resize_volume(self, instance, volume):
if 'size' not in volume:
raise rd_exceptions.BadRequest(
raise exception.BadRequest(
"Missing 'size' property of 'volume' in request body.")
new_size = volume['size']
instance.resize_volume(new_size)
@ -186,13 +190,17 @@ class InstanceController(BaseController):
LOG.info(_("req : '%s'\n\n") % req)
LOG.info(_("Indexing a database instance for tenant '%s'") % tenant_id)
context = req.environ[wsgi.CONTEXT_KEY]
servers = models.Instances.load(context)
servers, marker = models.Instances.load(context)
# TODO(cp16net): need to set the return code correctly
view_cls = views.InstancesDetailView if detailed \
else views.InstancesView
return wsgi.Result(view_cls(servers,
add_addresses=self.add_addresses,
add_volumes=self.add_volumes).data(), 200)
view_cls = (views.InstancesDetailView if detailed
else views.InstancesView)
view = view_cls(servers, req=req, add_addresses=self.add_addresses,
add_volumes=self.add_volumes)
paged = pagination.SimplePaginatedDataView(req.url, 'instances', view,
marker)
return wsgi.Result(paged.data(), 200)
def show(self, req, tenant_id, id):
"""Return a single instance."""
@ -210,7 +218,7 @@ class InstanceController(BaseController):
LOG.error(e)
return wsgi.Result(str(e), 404)
# TODO(cp16net): need to set the return code correctly
return wsgi.Result(views.InstanceDetailView(server,
return wsgi.Result(views.InstanceDetailView(server, req=req,
add_addresses=self.add_addresses,
add_volumes=self.add_volumes).data(), 200)
@ -274,7 +282,7 @@ class InstanceController(BaseController):
image_id, databases,
service_type, volume_size)
return wsgi.Result(views.InstanceDetailView(instance,
return wsgi.Result(views.InstanceDetailView(instance, req=req,
add_volumes=self.add_volumes).data(), 200)
@staticmethod
@ -282,7 +290,7 @@ class InstanceController(BaseController):
"""Check that the body is not empty"""
if not body:
msg = "The request contains an empty body"
raise rd_exceptions.ReddwarfError(msg)
raise exception.ReddwarfError(msg)
@staticmethod
def _validate_volume_size(size):
@ -293,19 +301,19 @@ class InstanceController(BaseController):
LOG.error(err)
msg = ("Required element/key - instance volume 'size' was not "
"specified as a number (value was %s)." % size)
raise rd_exceptions.ReddwarfError(msg)
raise exception.ReddwarfError(msg)
if int(volume_size) != volume_size or int(volume_size) < 1:
msg = ("Volume 'size' needs to be a positive "
"integer value, %s cannot be accepted."
% volume_size)
raise rd_exceptions.ReddwarfError(msg)
raise exception.ReddwarfError(msg)
#TODO(cp16net) add in the volume validation when volumes are supported
# max_size = FLAGS.reddwarf_max_accepted_volume_size
# if int(volume_size) > max_size:
# msg = ("Volume 'size' cannot exceed maximum "
# "of %d Gb, %s cannot be accepted."
# % (max_size, volume_size))
# raise rd_exceptions.ReddwarfError(msg)
# raise exception.ReddwarfError(msg)
@staticmethod
def _validate(body):
@ -320,7 +328,7 @@ class InstanceController(BaseController):
volume_size = body['instance']['volume']['size']
except KeyError as e:
LOG.error(_("Create Instance Required field(s) - %s") % e)
raise rd_exceptions.ReddwarfError("Required element/key - %s "
raise exception.ReddwarfError("Required element/key - %s "
"was not specified" % e)
@staticmethod
@ -331,7 +339,7 @@ class InstanceController(BaseController):
body['resize']['flavorRef']
except KeyError as e:
LOG.error(_("Resize Instance Required field(s) - %s") % e)
raise rd_exceptions.ReddwarfError("Required element/key - %s "
raise exception.ReddwarfError("Required element/key - %s "
"was not specified" % e)

View File

@ -41,10 +41,12 @@ def get_volumes(volumes):
class InstanceView(object):
def __init__(self, instance, add_addresses=False, add_volumes=False):
def __init__(self, instance, req=None, add_addresses=False,
add_volumes=False):
self.instance = instance
self.add_addresses = add_addresses
self.add_volumes = add_volumes
self.req = req
def data(self):
ip = get_ip_address(self.instance.addresses)
@ -53,7 +55,7 @@ class InstanceView(object):
"id": self.instance.id,
"name": self.instance.name,
"status": self.instance.status,
"links": self.instance.links
"links": self._build_links()
}
dns_support = config.Config.get("reddwarf_dns_support", 'False')
if utils.bool_from_string(dns_support):
@ -65,12 +67,51 @@ class InstanceView(object):
LOG.debug(instance_dict)
return {"instance": instance_dict}
def _build_links(self):
# TODO(ed-): Make generic, move to common?
result = []
scheme = 'https' # Forcing https
links = [link for link in self.instance.links]
links = [link['href'] for link in links if link['rel'] == 'self']
href_link = links[0]
splitpath = href_link.split('/')
endpoint = ''
if self.req:
endpoint = self.req.host
splitpath = self.req.path.split('/')
detailed = ''
if splitpath[-1] == 'detail':
detailed = '/detail'
splitpath.pop(-1)
instance_id = self.instance.id
if str(splitpath[-1]) == str(instance_id):
splitpath.pop(-1)
href_template = "%(scheme)s://%(endpoint)s%(path)s/%(instance_id)s"
for link in self.instance.links:
rlink = link
href = rlink['href']
if rlink['rel'] == 'self':
path = '/'.join(splitpath)
href = href_template % locals()
elif rlink['rel'] == 'bookmark':
splitpath.pop(2) # Remove the version.
splitpath.pop(1) # Remove the tenant id.
path = '/'.join(splitpath)
href = href_template % locals()
rlink['href'] = href
result.append(rlink)
return result
class InstanceDetailView(InstanceView):
def __init__(self, instance, add_addresses=False,
def __init__(self, instance, req=None, add_addresses=False,
add_volumes=False):
super(InstanceDetailView, self).__init__(instance,
req=req,
add_addresses=add_addresses,
add_volumes=add_volumes)
@ -84,10 +125,12 @@ class InstanceDetailView(InstanceView):
class InstancesView(object):
def __init__(self, instances, add_addresses=False, add_volumes=False):
def __init__(self, instances, req=None, add_addresses=False,
add_volumes=False):
self.instances = instances
self.add_addresses = add_addresses
self.add_volumes = add_volumes
self.req = req
def data(self):
data = []
@ -97,13 +140,14 @@ class InstancesView(object):
return {'instances': data}
def data_for_instance(self, instance):
return InstanceView(instance,
self.add_addresses).data()['instance']
view = InstanceView(instance, req=self.req,
add_addresses=self.add_addresses)
return view.data()['instance']
class InstancesDetailView(InstancesView):
def data_for_instance(self, instance):
return InstanceDetailView(instance,
return InstanceDetailView(instance, req=self.req,
add_addresses=self.add_addresses,
add_volumes=self.add_volumes).data()['instance']

View File

@ -63,11 +63,30 @@ class FakeGuest(object):
def is_root_enabled(self):
return self.root_was_enabled
def list_databases(self):
return [self.dbs[name] for name in self.dbs]
def list_databases(self, limit=None, marker=None):
dbs = [self.dbs[name] for name in self.dbs]
names = [db['_name'] for db in dbs]
if marker in names:
# Cut off everything left of and including the marker item.
dbs = dbs[names.index(marker) + 1:]
next_marker = None
if limit:
if len(dbs) > limit:
next_marker = dbs[limit - 1]['_name']
dbs = dbs[:limit]
return dbs, next_marker
def list_users(self):
return [self.users[name] for name in self.users]
def list_users(self, limit=None, marker=None):
users = [self.users[name] for name in self.users]
names = [user['_name'] for user in users]
if marker in names:
users = users[names.index(marker) + 1:]
next_marker = None
if limit:
if len(users) > limit:
next_marker = users[limit - 1]['_name']
users = users[:limit]
return users, next_marker
def prepare(self, databases, memory_mb, users, device_path=None,
mount_point=None):
@ -100,6 +119,7 @@ class FakeGuest(object):
status.status = ServiceStatuses.SHUTDOWN
status.save()
def get_or_create(id):
if id not in DB:
DB[id] = FakeGuest(id)

View File

@ -108,7 +108,6 @@ class FakeServer(object):
raise RuntimeError("Not in resize confirm mode.")
self._current_status = "ACTIVE"
def delete(self):
self.schedule_status = []
self._current_status = "SHUTDOWN"
@ -127,12 +126,15 @@ class FakeServer(object):
def resize(self, new_flavor_id):
self._current_status = "RESIZE"
def set_to_confirm_mode():
self._current_status = "VERIFY_RESIZE"
def set_flavor():
flavor = self.parent.flavors.get(new_flavor_id)
self.flavor_ref = flavor.links[0]['href']
self.events.add_event(1, set_to_confirm_mode)
self.events.add_event(1, set_flavor)
def schedule_status(self, new_status, time_from_now):
@ -248,10 +250,6 @@ class FakeServerVolumes(object):
return [ServerVolumes(server.block_device_mapping)]
class FakeVolume(object):
def __init__(self, parent, owner, id, size, display_name,
@ -332,6 +330,7 @@ class FakeVolumes(object):
FLAVORS = FakeFlavors()
class FakeClient(object):
def __init__(self, context):