commit
53c37b8e48
@ -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
|
||||
|
97
reddwarf/common/pagination.py
Normal file
97
reddwarf/common/pagination.py
Normal 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())
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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"""
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
71
reddwarf/guestagent/query.py
Normal file
71
reddwarf/guestagent/query.py
Normal 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)
|
@ -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."""
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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']
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
Loading…
x
Reference in New Issue
Block a user