Working on missing postgresql features

* Db list operation added for postgresql
* User list operation added for postgresql
* User create operation added for postgresql
* User delete operation added for postgresql
* Db create operation added for postgresql
* Db delete operation added for postgresql
* User grant access operation added for postgresql
* User revoke access and update attributes operations added for postgresql
* Common/db/models collate and character_set setter functions are updated.

Depends-On: https://review.opendev.org/c/openstack/trove-tempest-plugin/+/930276

Change-Id: I4c000363dd046fc82f099de0a30c16b1f01d6d2c
This commit is contained in:
Reşit Demir 2023-03-10 06:47:14 +00:00 committed by wu.chunyang
parent 0138ce30e2
commit 89274df2af
20 changed files with 707 additions and 755 deletions

1
.gitignore vendored
View File

@ -56,6 +56,7 @@ publish-docs/
*~
.*.swp
.bak
.idea/
# Config sample and policy sample
etc/trove/*.sample

View File

@ -114,6 +114,28 @@ class DatastoreSchema(DatastoreModelsBase):
self._validate_schema_name(value)
self._name = value
@property
def collate(self):
return self._collate
@collate.setter
def collate(self, value):
if not value:
pass
else:
self._collate = value
@property
def character_set(self):
return self._character_set
@character_set.setter
def character_set(self, value):
if not value:
pass
else:
self._character_set = value
def _validate_schema_name(self, value):
"""Perform checks on a given schema name.
:param value: Validated schema name.

View File

@ -22,7 +22,7 @@ from trove.common import exception as trove_exception
from trove.common.rpc import version as rpc_version
from trove.common.serializable_notification import SerializableNotification
from trove.conductor.models import LastSeen
from trove.extensions.mysql import models as mysql_models
from trove.extensions.common import models as common_models
from trove.instance import models as inst_models
from trove.instance import service_status as svc_status
@ -150,7 +150,7 @@ class Manager(periodic_task.PeriodicTasks):
if user is not None:
LOG.debug("calling report_root with a username: %s, "
"is deprecated now!" % user)
mysql_models.RootHistory.create(context, instance_id)
common_models.RootHistory.create(context, instance_id)
def notify_end(self, context, serialized_notification, notification_args):
notification = SerializableNotification.deserialize(

View File

@ -41,7 +41,7 @@ def configure_db(models_mapper=None):
from trove.configuration import models as configurations_models
from trove.datastore import models as datastores_models
from trove.dns import models as dns_models
from trove.extensions.mysql import models as mysql_models
from trove.extensions.common import models as common_models
from trove.extensions.security_group import models as secgrp_models
from trove.guestagent import models as agent_models
from trove.instance import models as base_models
@ -52,7 +52,7 @@ def configure_db(models_mapper=None):
base_models,
datastores_models,
dns_models,
mysql_models,
common_models,
agent_models,
quota_models,
backup_models,

View File

@ -14,7 +14,7 @@
from urllib.parse import unquote
from trove.common.db.mysql import models as guest_models
from trove.common.db import models as guest_models
from trove.common import exception
@ -27,7 +27,7 @@ def populate_validated_databases(dbs):
databases = []
unique_identities = set()
for database in dbs:
mydb = guest_models.MySQLSchema(name=database.get('name', ''))
mydb = guest_models.DatastoreSchema(name=database.get('name', ''))
mydb.check_reserved()
if mydb.name in unique_identities:
raise exception.DatabaseInitialDatabaseDuplicateError()
@ -49,8 +49,8 @@ def populate_users(users, initial_databases=None):
users_data = []
unique_identities = set()
for user in users:
u = guest_models.MySQLUser(name=user.get('name', ''),
host=user.get('host', '%'))
u = guest_models.DatastoreUser(name=user.get('name', ''),
host=user.get('host', '%'))
u.check_reserved()
user_identity = (u.name, u.host)
if user_identity in unique_identities:

View File

@ -22,12 +22,17 @@ from trove.common import timeutils
from trove.db import get_db_api
from trove.instance import models as base_models
from trove.common import cfg
from trove.common.notification import StartNotification
from trove.common import utils
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
def load_and_verify(context, instance_id,
enabled_datastore=['mysql', 'mariadb']):
enabled_datastore=['mysql', 'mariadb', 'postgresql']):
"""Check instance datastore.
Some API operations are only supported for some specific datastores.
@ -141,3 +146,259 @@ class RootHistory(object):
return history
history = RootHistory(instance_id, context.user_id)
return history.save()
def load_via_context(cls, context, instance_id):
"""Creates guest and fetches pagination arguments from the context."""
load_and_verify(context, instance_id,
enabled_datastore=['mysql', 'mariadb', 'postgresql'])
limit = utils.pagination_limit(context.limit, cls.DEFAULT_LIMIT)
client = create_guest_client(context, instance_id)
# The REST API standard dictates that we *NEVER* include the marker.
return cls.load_with_client(client=client, limit=limit,
marker=context.marker, include_marker=False)
def persisted_models():
return {'root_enabled_history': RootHistory}
class User(object):
_data_fields = ['name', 'host', 'password', 'databases']
def __init__(self, name, host, password, databases):
self.name = name
self.host = host
self.password = password
self.databases = databases
@classmethod
def load(cls, context, instance_id, username, hostname, root_user=False):
load_and_verify(context, instance_id,
enabled_datastore=['mysql', 'mariadb', 'postgresql'])
validate = guest_models.DatastoreUser(name=username, host=hostname)
if root_user:
validate.make_root()
validate.check_reserved()
client = create_guest_client(context, instance_id)
found_user = client.get_user(username=username, hostname=hostname)
if not found_user:
return None
database_names = [{'name': db['_name']}
for db in found_user['_databases']]
return cls(found_user['_name'],
found_user['_host'],
found_user['_password'],
database_names)
@classmethod
def create(cls, context, instance_id, users):
# Load InstanceServiceStatus to verify if it's running
load_and_verify(context, instance_id,
enabled_datastore=['mysql', 'mariadb', 'postgresql'])
client = create_guest_client(context, instance_id)
for user in users:
user_name = user['_name']
host_name = user['_host']
userhost = "%s@%s" % (user_name, host_name)
existing_users, _nadda = Users.load_with_client(
client,
limit=1,
marker=userhost,
include_marker=True)
if (len(existing_users) > 0 and
str(existing_users[0].name) == str(user_name) and
str(existing_users[0].host) == str(host_name)):
raise exception.UserAlreadyExists(name=user_name,
host=host_name)
return client.create_user(users)
@classmethod
def delete(cls, context, instance_id, user):
load_and_verify(context, instance_id,
enabled_datastore=['mysql', 'mariadb', 'postgresql'])
with StartNotification(context, instance_id=instance_id,
username=user):
create_guest_client(context, instance_id).delete_user(user)
@classmethod
def access(cls, context, instance_id, username, hostname):
load_and_verify(context, instance_id,
enabled_datastore=['mysql', 'mariadb', 'postgresql'])
client = create_guest_client(context, instance_id)
databases = client.list_access(username, hostname)
dbs = []
for db in databases:
dbs.append(Schema(name=db['_name'],
collate=db['_collate'],
character_set=db['_character_set']))
return UserAccess(dbs)
@classmethod
def grant(cls, context, instance_id, username, hostname, databases):
load_and_verify(context, instance_id,
enabled_datastore=['mysql', 'mariadb', 'postgresql'])
client = create_guest_client(context, instance_id)
client.grant_access(username, hostname, databases)
@classmethod
def revoke(cls, context, instance_id, username, hostname, database):
load_and_verify(context, instance_id,
enabled_datastore=['mysql', 'mariadb', 'postgresql'])
client = create_guest_client(context, instance_id)
client.revoke_access(username, hostname, database)
@classmethod
def change_password(cls, context, instance_id, users):
load_and_verify(context, instance_id,
enabled_datastore=['mysql', 'mariadb', 'postgresql'])
client = create_guest_client(context, instance_id)
change_users = []
for user in users:
change_user = {'name': user.name,
'host': user.host,
'password': user.password,
}
change_users.append(change_user)
client.change_passwords(change_users)
@classmethod
def update_attributes(cls, context, instance_id, username, hostname,
user_attrs):
load_and_verify(context, instance_id)
client = create_guest_client(context, instance_id)
user_changed = user_attrs.get('name')
host_changed = user_attrs.get('host')
user = user_changed or username
host = host_changed or hostname
validate = guest_models.DatastoreUser(name=user, host=host)
validate.check_reserved()
userhost = "%s@%s" % (user, host)
if user_changed or host_changed:
existing_users, _nadda = Users.load_with_client(
client,
limit=1,
marker=userhost,
include_marker=True)
if (len(existing_users) > 0 and
existing_users[0].name == user and
existing_users[0].host == host):
raise exception.UserAlreadyExists(name=user,
host=host)
client.update_attributes(username, hostname, user_attrs)
class Users(object):
DEFAULT_LIMIT = CONF.users_page_size
@classmethod
def load(cls, context, instance_id):
return load_via_context(cls, context, instance_id)
@classmethod
def load_with_client(cls, client, limit, marker, include_marker):
user_list, next_marker = client.list_users(
limit=limit,
marker=marker,
include_marker=include_marker)
model_users = []
for user in user_list:
guest_user = guest_models.DatastoreUser.deserialize(user,
verify=False)
if guest_user.name in cfg.get_ignored_users():
continue
# TODO(hub-cap): databases are not being returned in the
# reference agent
dbs = []
for db in guest_user.databases:
dbs.append({'name': db['_name']})
model_users.append(User(guest_user.name,
guest_user.host,
guest_user.password,
dbs))
return model_users, next_marker
class UserAccess(object):
_data_fields = ['databases']
def __init__(self, databases):
self.databases = databases
class Schema(object):
_data_fields = ['name', 'collate', 'character_set']
def __init__(self, name, collate, character_set):
self.name = name
self.collate = collate
self.character_set = character_set
@classmethod
def create(cls, context, instance_id, schemas):
load_and_verify(context, instance_id,
enabled_datastore=['mysql', 'mariadb', 'postgresql'])
client = create_guest_client(context, instance_id)
for schema in schemas:
schema_name = schema['_name']
existing_schema, _nadda = Schemas.load_with_client(
client,
limit=1,
marker=schema_name,
include_marker=True)
if (len(existing_schema) > 0 and
str(existing_schema[0].name) == str(schema_name)):
raise exception.DatabaseAlreadyExists(name=schema_name)
return client.create_database(schemas)
@classmethod
def delete(cls, context, instance_id, schema):
load_and_verify(context, instance_id,
enabled_datastore=['mysql', 'mariadb', 'postgresql'])
create_guest_client(context, instance_id).delete_database(schema)
class Schemas(object):
DEFAULT_LIMIT = CONF.databases_page_size
@classmethod
def load(cls, context, instance_id):
return load_via_context(cls, context, instance_id)
@classmethod
def load_with_client(cls, client, limit, marker, include_marker):
schemas, next_marker = client.list_databases(
limit=limit,
marker=marker,
include_marker=include_marker)
model_schemas = []
for schema in schemas:
guest_schema = guest_models.DatastoreSchema.deserialize(
schema, verify=False)
if guest_schema.name in cfg.get_ignored_dbs():
continue
model_schemas.append(Schema(guest_schema.name,
guest_schema.collate,
guest_schema.character_set))
return model_schemas, next_marker
@classmethod
def find(cls, context, instance_id, schema_id):
load_and_verify(context, instance_id,
enabled_datastore=['mysql', 'mariadb', 'postgresql'])
client = create_guest_client(context, instance_id)
model_schemas, _ = cls.load_with_client(client, 1, schema_id, True)
if model_schemas and model_schemas[0].name == schema_id:
return model_schemas[0]
return None

View File

@ -33,6 +33,19 @@ from trove.extensions.common import views
from trove.instance import models as instance_models
from trove.instance.models import DBInstance
from oslo_utils import strutils
import webob.exc
import trove.common.apischema as apischema
from trove.common.db import models as guest_models
from trove.common import notification
from trove.common.notification import StartNotification
from trove.common import pagination
from trove.common.utils import correct_id_with_req
from trove.extensions.common.common import populate_users
from trove.extensions.common.common import populate_validated_databases
from trove.extensions.common.common import unquote_user_host
LOG = logging.getLogger(__name__)
import_class = importutils.import_class
@ -270,3 +283,334 @@ class RootController(ExtensionController):
f"root_controller not configured for datastore {manager}")
raise exception.DatastoreOperationNotSupported(
datastore=manager, operation='root')
class UserController(ExtensionController):
"""Controller for instance functionality."""
schemas = apischema.user
@classmethod
def get_schema(cls, action, body):
action_schema = super(UserController, cls).get_schema(action, body)
if 'update_all' == action:
update_type = list(body.keys())[0]
action_schema = action_schema.get(update_type, {})
return action_schema
def index(self, req, tenant_id, instance_id):
"""Return all users."""
LOG.info("Listing users for instance '%(id)s'\n"
"req : '%(req)s'\n\n",
{"id": instance_id, "req": req})
context = req.environ[wsgi.CONTEXT_KEY]
self.authorize_target_action(context, 'user:index', instance_id)
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."""
LOG.info("Creating users for instance '%(id)s'\n"
"req : '%(req)s'\n\n"
"body: '%(body)s'\n'n",
{"id": instance_id,
"req": strutils.mask_password(req),
"body": strutils.mask_password(body)})
context = req.environ[wsgi.CONTEXT_KEY]
self.authorize_target_action(context, 'user:create', instance_id)
context.notification = notification.DBaaSUserCreate(context,
request=req)
users = body['users']
with StartNotification(context, instance_id=instance_id,
username=",".join([user['name']
for user in users])):
try:
model_users = populate_users(users)
models.User.create(context, instance_id, model_users)
except (ValueError, AttributeError) as e:
raise exception.BadRequest(_("User create error: %(e)s")
% {'e': e})
return wsgi.Result(None, 202)
def delete(self, req, tenant_id, instance_id, id):
LOG.info("Delete instance '%(id)s'\n"
"req : '%(req)s'\n\n",
{"id": instance_id, "req": req})
context = req.environ[wsgi.CONTEXT_KEY]
self.authorize_target_action(context, 'user:delete', instance_id)
id = correct_id_with_req(id, req)
username, host = unquote_user_host(id)
user = None
context.notification = notification.DBaaSUserDelete(context,
request=req)
with StartNotification(context, instance_id=instance_id,
username=username):
try:
user = guest_models.DatastoreUser(name=username,
host=host)
found_user = models.User.load(context, instance_id, username,
host)
if not found_user:
user = None
except (ValueError, AttributeError) as e:
raise exception.BadRequest(_("User delete error: %(e)s")
% {'e': e})
if not user:
raise exception.UserNotFound(uuid=id)
models.User.delete(context, instance_id, user.serialize())
return wsgi.Result(None, 202)
def show(self, req, tenant_id, instance_id, id):
"""Return a single user."""
LOG.info("Showing a user for instance '%(id)s'\n"
"req : '%(req)s'\n\n",
{"id": instance_id, "req": req})
context = req.environ[wsgi.CONTEXT_KEY]
self.authorize_target_action(context, 'user:show', instance_id)
id = correct_id_with_req(id, req)
username, host = unquote_user_host(id)
user = None
try:
user = models.User.load(context, instance_id, username, host)
except (ValueError, AttributeError) as e:
raise exception.BadRequest(_("User show error: %(e)s")
% {'e': e})
if not user:
raise exception.UserNotFound(uuid=id)
view = views.UserView(user)
return wsgi.Result(view.data(), 200)
def update(self, req, body, tenant_id, instance_id, id):
"""Change attributes for one user."""
LOG.info("Updating user attributes for instance '%(id)s'\n"
"req : '%(req)s'\n\n",
{"id": instance_id, "req": strutils.mask_password(req)})
context = req.environ[wsgi.CONTEXT_KEY]
self.authorize_target_action(context, 'user:update', instance_id)
id = correct_id_with_req(id, req)
username, hostname = unquote_user_host(id)
user = None
user_attrs = body['user']
context.notification = notification.DBaaSUserUpdateAttributes(
context, request=req)
with StartNotification(context, instance_id=instance_id,
username=username):
try:
user = models.User.load(context, instance_id, username,
hostname)
except (ValueError, AttributeError) as e:
raise exception.BadRequest(_("Error loading user: %(e)s")
% {'e': e})
if not user:
raise exception.UserNotFound(uuid=id)
try:
models.User.update_attributes(context, instance_id, username,
hostname, user_attrs)
except (ValueError, AttributeError) as e:
raise exception.BadRequest(_("User update error: %(e)s")
% {'e': e})
return wsgi.Result(None, 202)
def update_all(self, req, body, tenant_id, instance_id):
"""Change the password of one or more users."""
LOG.info("Updating user password for instance '%(id)s'\n"
"req : '%(req)s'\n\n",
{"id": instance_id, "req": strutils.mask_password(req)})
context = req.environ[wsgi.CONTEXT_KEY]
self.authorize_target_action(context, 'user:update_all', instance_id)
context.notification = notification.DBaaSUserChangePassword(
context, request=req)
users = body['users']
model_users = []
with StartNotification(context, instance_id=instance_id,
username=",".join([user['name']
for user in users])):
for user in users:
try:
mu = guest_models.DatastoreUser(name=user['name'],
host=user.get('host'),
password=user['password'])
found_user = models.User.load(context, instance_id,
mu.name, mu.host)
if not found_user:
user_and_host = mu.name
if mu.host:
user_and_host += '@' + mu.host
raise exception.UserNotFound(uuid=user_and_host)
model_users.append(mu)
except (ValueError, AttributeError) as e:
raise exception.BadRequest(_("Error loading user: %(e)s")
% {'e': e})
try:
models.User.change_password(context, instance_id, model_users)
except (ValueError, AttributeError) as e:
raise exception.BadRequest(_("User password update error: "
"%(e)s")
% {'e': e})
return wsgi.Result(None, 202)
class UserAccessController(ExtensionController):
"""Controller for adding and removing database access for a user."""
schemas = apischema.user
@classmethod
def get_schema(cls, action, body):
schema = {}
if 'update_all' == action:
schema = cls.schemas.get(action).get('databases')
return schema
def _get_user(self, context, instance_id, user_id):
username, hostname = unquote_user_host(user_id)
try:
user = models.User.load(context, instance_id, username, hostname)
except (ValueError, AttributeError) as e:
raise exception.BadRequest(_("Error loading user: %(e)s")
% {'e': e})
if not user:
raise exception.UserNotFound(uuid=user_id)
return user
def index(self, req, tenant_id, instance_id, user_id):
"""Show permissions for the given user."""
LOG.info("Showing user access for instance '%(id)s'\n"
"req : '%(req)s'\n\n",
{"id": instance_id, "req": req})
context = req.environ[wsgi.CONTEXT_KEY]
self.authorize_target_action(
context, 'user_access:index', instance_id)
# Make sure this user exists.
user_id = correct_id_with_req(user_id, req)
user = self._get_user(context, instance_id, user_id)
if not user:
LOG.error("No such user: %(user)s ", {'user': user})
raise exception.UserNotFound(uuid=user)
username, hostname = unquote_user_host(user_id)
access = models.User.access(context, instance_id, username, hostname)
view = views.UserAccessView(access.databases)
return wsgi.Result(view.data(), 200)
def update(self, req, body, tenant_id, instance_id, user_id):
"""Grant access for a user to one or more databases."""
LOG.info("Granting user access for instance '%(id)s'\n"
"req : '%(req)s'\n\n",
{"id": instance_id, "req": req})
context = req.environ[wsgi.CONTEXT_KEY]
self.authorize_target_action(
context, 'user_access:update', instance_id)
context.notification = notification.DBaaSUserGrant(
context, request=req)
user_id = correct_id_with_req(user_id, req)
user = self._get_user(context, instance_id, user_id)
if not user:
LOG.error("No such user: %(user)s ", {'user': user})
raise exception.UserNotFound(uuid=user)
username, hostname = unquote_user_host(user_id)
databases = [db['name'] for db in body['databases']]
with StartNotification(context, instance_id=instance_id,
username=username, database=databases):
models.User.grant(context, instance_id, username, hostname,
databases)
return wsgi.Result(None, 202)
def delete(self, req, tenant_id, instance_id, user_id, id):
"""Revoke access for a user."""
LOG.info("Revoking user access for instance '%(id)s'\n"
"req : '%(req)s'\n\n",
{"id": instance_id, "req": req})
context = req.environ[wsgi.CONTEXT_KEY]
self.authorize_target_action(
context, 'user_access:delete', instance_id)
context.notification = notification.DBaaSUserRevoke(
context, request=req)
user_id = correct_id_with_req(user_id, req)
user = self._get_user(context, instance_id, user_id)
if not user:
LOG.error("No such user: %(user)s ", {'user': user})
raise exception.UserNotFound(uuid=user)
username, hostname = unquote_user_host(user_id)
access = models.User.access(context, instance_id, username, hostname)
databases = [db.name for db in access.databases]
with StartNotification(context, instance_id=instance_id,
username=username, database=databases):
if id not in databases:
raise exception.DatabaseNotFound(uuid=id)
models.User.revoke(context, instance_id, username, hostname, id)
return wsgi.Result(None, 202)
class SchemaController(ExtensionController):
"""Controller for instance functionality."""
schemas = apischema.dbschema
def index(self, req, tenant_id, instance_id):
"""Return all schemas."""
LOG.info("Listing schemas for instance '%(id)s'\n"
"req : '%(req)s'\n\n",
{"id": instance_id, "req": req})
context = req.environ[wsgi.CONTEXT_KEY]
self.authorize_target_action(
context, 'database:index', instance_id)
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."""
LOG.info("Creating schema for instance '%(id)s'\n"
"req : '%(req)s'\n\n"
"body: '%(body)s'\n'n",
{"id": instance_id,
"req": req,
"body": body})
context = req.environ[wsgi.CONTEXT_KEY]
self.authorize_target_action(
context, 'database:create', instance_id)
schemas = body['databases']
context.notification = notification.DBaaSDatabaseCreate(context,
request=req)
with StartNotification(context, instance_id=instance_id,
dbname=".".join([db['name']
for db in schemas])):
try:
model_schemas = populate_validated_databases(schemas)
models.Schema.create(context, instance_id, model_schemas)
except (ValueError, AttributeError) as e:
raise exception.BadRequest(_("Database create error: %(e)s")
% {'e': e})
return wsgi.Result(None, 202)
def delete(self, req, tenant_id, instance_id, id):
LOG.info("Deleting schema for instance '%(id)s'\n"
"req : '%(req)s'\n\n",
{"id": instance_id, "req": req})
context = req.environ[wsgi.CONTEXT_KEY]
self.authorize_target_action(
context, 'database:delete', instance_id)
context.notification = notification.DBaaSDatabaseDelete(
context, request=req)
with StartNotification(context, instance_id=instance_id, dbname=id):
try:
schema = guest_models.DatastoreSchema(name=id)
schema.check_delete()
if not models.Schemas.find(context, instance_id, id):
raise exception.DatabaseNotFound(uuid=id)
models.Schema.delete(context, instance_id, schema.serialize())
except (ValueError, AttributeError) as e:
raise exception.BadRequest(_("Database delete error: %(e)s")
% {'e': e})
return wsgi.Result(None, 202)
def show(self, req, tenant_id, instance_id, id):
context = req.environ[wsgi.CONTEXT_KEY]
self.authorize_target_action(
context, 'database:show', instance_id)
raise webob.exc.HTTPNotImplemented()

View File

@ -45,3 +45,49 @@ class RootEnabledView(object):
def data(self):
return {'rootEnabled': self.is_root_enabled}
class UsersView(object):
def __init__(self, users):
self.users = users
def data(self):
userlist = [{"name": user.name,
"host": user.host,
"databases": user.databases}
for user in self.users]
return {"users": userlist}
class UserAccessView(object):
def __init__(self, databases):
self.databases = databases
def data(self):
dbs = [{"name": db.name} for db in self.databases]
return {"databases": dbs}
class SchemaView(object):
def __init__(self, schema):
self.schema = schema
def data(self):
return {"name": self.schema.name}
class SchemasView(object):
def __init__(self, schemas):
self.schemas = schemas
def data(self):
data = []
# These are model instances
for schema in self.schemas:
data.append(SchemaView(schema).data())
return {"databases": data}

View File

@ -19,7 +19,7 @@ from trove.common import cfg
from trove.common import clients
from trove.common import exception
from trove.common import timeutils
from trove.extensions.mysql import models as mysql_models
from trove.extensions.common import models as common_models
from trove.instance import models as instance_models
from trove import rpc
@ -131,8 +131,8 @@ class DetailedMgmtInstance(SimpleMgmtInstance):
instance.volume = None
# Populate the volume_used attribute from the guest agent.
instance_models.load_guest_info(instance, context, id)
instance.root_history = mysql_models.RootHistory.load(context=context,
instance_id=id)
instance.root_history = common_models.RootHistory.load(context=context,
instance_id=id)
return instance

View File

@ -25,11 +25,11 @@ from trove.common.i18n import _
from trove.common import notification
from trove.common.notification import StartNotification
from trove.common import wsgi
from trove.extensions.common import models as common_models
from trove.extensions.mgmt.instances import models
from trove.extensions.mgmt.instances import views
from trove.extensions.mgmt.instances.views import DiagnosticsView
from trove.extensions.mgmt.instances.views import HwInfoView
from trove.extensions.mysql import models as mysql_models
from trove.instance import models as instance_models
from trove.instance.service import InstanceController
@ -85,8 +85,8 @@ class MgmtInstanceController(InstanceController):
include_deleted = deleted_q == 'true'
server = models.DetailedMgmtInstance.load(context, id,
include_deleted)
root_history = mysql_models.RootHistory.load(context=context,
instance_id=id)
root_history = common_models.RootHistory.load(context=context,
instance_id=id)
return wsgi.Result(
views.MgmtInstanceDetailView(
server,
@ -189,7 +189,7 @@ class MgmtInstanceController(InstanceController):
LOG.exception(e)
return wsgi.Result(str(e), 404)
rhv = views.RootHistoryView(id)
reh = mysql_models.RootHistory.load(context=context, instance_id=id)
reh = common_models.RootHistory.load(context=context, instance_id=id)
if reh:
rhv = views.RootHistoryView(reh.id, enabled=reh.created,
user_id=reh.user)

View File

@ -1,273 +0,0 @@
# Copyright 2010-2011 OpenStack Foundation
# 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.
"""
Model classes that extend the instances functionality for MySQL instances.
"""
from trove.common import cfg
from trove.common.clients import create_guest_client
from trove.common.db.mysql import models as guest_models
from trove.common import exception
from trove.common.notification import StartNotification
from trove.common import utils
from trove.extensions.common.models import load_and_verify
from trove.extensions.common.models import RootHistory
CONF = cfg.CONF
def persisted_models():
return {'root_enabled_history': RootHistory}
class User(object):
_data_fields = ['name', 'host', 'password', 'databases']
def __init__(self, name, host, password, databases):
self.name = name
self.host = host
self.password = password
self.databases = databases
@classmethod
def load(cls, context, instance_id, username, hostname, root_user=False):
load_and_verify(context, instance_id)
validate = guest_models.MySQLUser(name=username, host=hostname)
if root_user:
validate.make_root()
validate.check_reserved()
client = create_guest_client(context, instance_id)
found_user = client.get_user(username=username, hostname=hostname)
if not found_user:
return None
database_names = [{'name': db['_name']}
for db in found_user['_databases']]
return cls(found_user['_name'],
found_user['_host'],
found_user['_password'],
database_names)
@classmethod
def create(cls, context, instance_id, users):
# Load InstanceServiceStatus to verify if it's running
load_and_verify(context, instance_id)
client = create_guest_client(context, instance_id)
for user in users:
user_name = user['_name']
host_name = user['_host']
userhost = "%s@%s" % (user_name, host_name)
existing_users, _nadda = Users.load_with_client(
client,
limit=1,
marker=userhost,
include_marker=True)
if (len(existing_users) > 0 and
str(existing_users[0].name) == str(user_name) and
str(existing_users[0].host) == str(host_name)):
raise exception.UserAlreadyExists(name=user_name,
host=host_name)
return client.create_user(users)
@classmethod
def delete(cls, context, instance_id, user):
load_and_verify(context, instance_id)
with StartNotification(context, instance_id=instance_id,
username=user):
create_guest_client(context, instance_id).delete_user(user)
@classmethod
def access(cls, context, instance_id, username, hostname):
load_and_verify(context, instance_id)
client = create_guest_client(context, instance_id)
databases = client.list_access(username, hostname)
dbs = []
for db in databases:
dbs.append(Schema(name=db['_name'],
collate=db['_collate'],
character_set=db['_character_set']))
return UserAccess(dbs)
@classmethod
def grant(cls, context, instance_id, username, hostname, databases):
load_and_verify(context, instance_id)
client = create_guest_client(context, instance_id)
client.grant_access(username, hostname, databases)
@classmethod
def revoke(cls, context, instance_id, username, hostname, database):
load_and_verify(context, instance_id)
client = create_guest_client(context, instance_id)
client.revoke_access(username, hostname, database)
@classmethod
def change_password(cls, context, instance_id, users):
load_and_verify(context, instance_id)
client = create_guest_client(context, instance_id)
change_users = []
for user in users:
change_user = {'name': user.name,
'host': user.host,
'password': user.password,
}
change_users.append(change_user)
client.change_passwords(change_users)
@classmethod
def update_attributes(cls, context, instance_id, username, hostname,
user_attrs):
load_and_verify(context, instance_id)
client = create_guest_client(context, instance_id)
user_changed = user_attrs.get('name')
host_changed = user_attrs.get('host')
user = user_changed or username
host = host_changed or hostname
validate = guest_models.MySQLUser(name=user, host=host)
validate.check_reserved()
userhost = "%s@%s" % (user, host)
if user_changed or host_changed:
existing_users, _nadda = Users.load_with_client(
client,
limit=1,
marker=userhost,
include_marker=True)
if (len(existing_users) > 0 and
existing_users[0].name == user and
existing_users[0].host == host):
raise exception.UserAlreadyExists(name=user,
host=host)
client.update_attributes(username, hostname, user_attrs)
class UserAccess(object):
_data_fields = ['databases']
def __init__(self, databases):
self.databases = databases
def load_via_context(cls, context, instance_id):
"""Creates guest and fetches pagination arguments from the context."""
load_and_verify(context, instance_id)
limit = utils.pagination_limit(context.limit, cls.DEFAULT_LIMIT)
client = create_guest_client(context, instance_id)
# The REST API standard dictates that we *NEVER* include the marker.
return cls.load_with_client(client=client, limit=limit,
marker=context.marker, include_marker=False)
class Users(object):
DEFAULT_LIMIT = CONF.users_page_size
@classmethod
def load(cls, context, instance_id):
return load_via_context(cls, context, instance_id)
@classmethod
def load_with_client(cls, client, limit, marker, include_marker):
user_list, next_marker = client.list_users(
limit=limit,
marker=marker,
include_marker=include_marker)
model_users = []
for user in user_list:
mysql_user = guest_models.MySQLUser.deserialize(user,
verify=False)
if mysql_user.name in cfg.get_ignored_users():
continue
# TODO(hub-cap): databases are not being returned in the
# reference agent
dbs = []
for db in mysql_user.databases:
dbs.append({'name': db['_name']})
model_users.append(User(mysql_user.name,
mysql_user.host,
mysql_user.password,
dbs))
return model_users, next_marker
class Schema(object):
_data_fields = ['name', 'collate', 'character_set']
def __init__(self, name, collate, character_set):
self.name = name
self.collate = collate
self.character_set = character_set
@classmethod
def create(cls, context, instance_id, schemas):
load_and_verify(context, instance_id)
client = create_guest_client(context, instance_id)
for schema in schemas:
schema_name = schema['_name']
existing_schema, _nadda = Schemas.load_with_client(
client,
limit=1,
marker=schema_name,
include_marker=True)
if (len(existing_schema) > 0 and
str(existing_schema[0].name) == str(schema_name)):
raise exception.DatabaseAlreadyExists(name=schema_name)
return client.create_database(schemas)
@classmethod
def delete(cls, context, instance_id, schema):
load_and_verify(context, instance_id)
create_guest_client(context, instance_id).delete_database(schema)
class Schemas(object):
DEFAULT_LIMIT = CONF.databases_page_size
@classmethod
def load(cls, context, instance_id):
return load_via_context(cls, context, instance_id)
@classmethod
def load_with_client(cls, client, limit, marker, include_marker):
schemas, next_marker = client.list_databases(
limit=limit,
marker=marker,
include_marker=include_marker)
model_schemas = []
for schema in schemas:
mysql_schema = guest_models.MySQLSchema.deserialize(schema,
verify=False)
if mysql_schema.name in cfg.get_ignored_dbs():
continue
model_schemas.append(Schema(mysql_schema.name,
mysql_schema.collate,
mysql_schema.character_set))
return model_schemas, next_marker
@classmethod
def find(cls, context, instance_id, schema_id):
load_and_verify(context, instance_id)
client = create_guest_client(context, instance_id)
model_schemas, _ = cls.load_with_client(client, 1, schema_id, True)
if model_schemas and model_schemas[0].name == schema_id:
return model_schemas[0]
return None

View File

@ -1,373 +0,0 @@
# Copyright 2011 OpenStack Foundation
# 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.
from oslo_log import log as logging
from oslo_utils import importutils
from oslo_utils import strutils
import webob.exc
import trove.common.apischema as apischema
from trove.common import cfg
from trove.common.db.mysql import models as guest_models
from trove.common import exception
from trove.common.i18n import _
from trove.common import notification
from trove.common.notification import StartNotification
from trove.common import pagination
from trove.common.utils import correct_id_with_req
from trove.common import wsgi
from trove.extensions.common.service import ExtensionController
from trove.extensions.mysql.common import populate_users
from trove.extensions.mysql.common import populate_validated_databases
from trove.extensions.mysql.common import unquote_user_host
from trove.extensions.mysql import models
from trove.extensions.mysql import views
LOG = logging.getLogger(__name__)
import_class = importutils.import_class
CONF = cfg.CONF
class UserController(ExtensionController):
"""Controller for instance functionality."""
schemas = apischema.user
@classmethod
def get_schema(cls, action, body):
action_schema = super(UserController, cls).get_schema(action, body)
if 'update_all' == action:
update_type = list(body.keys())[0]
action_schema = action_schema.get(update_type, {})
return action_schema
def index(self, req, tenant_id, instance_id):
"""Return all users."""
LOG.info("Listing users for instance '%(id)s'\n"
"req : '%(req)s'\n\n",
{"id": instance_id, "req": req})
context = req.environ[wsgi.CONTEXT_KEY]
self.authorize_target_action(context, 'user:index', instance_id)
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."""
LOG.info("Creating users for instance '%(id)s'\n"
"req : '%(req)s'\n\n"
"body: '%(body)s'\n'n",
{"id": instance_id,
"req": strutils.mask_password(req),
"body": strutils.mask_password(body)})
context = req.environ[wsgi.CONTEXT_KEY]
self.authorize_target_action(context, 'user:create', instance_id)
context.notification = notification.DBaaSUserCreate(context,
request=req)
users = body['users']
with StartNotification(context, instance_id=instance_id,
username=",".join([user['name']
for user in users])):
try:
model_users = populate_users(users)
models.User.create(context, instance_id, model_users)
except (ValueError, AttributeError) as e:
raise exception.BadRequest(_("User create error: %(e)s")
% {'e': e})
return wsgi.Result(None, 202)
def delete(self, req, tenant_id, instance_id, id):
LOG.info("Delete instance '%(id)s'\n"
"req : '%(req)s'\n\n",
{"id": instance_id, "req": req})
context = req.environ[wsgi.CONTEXT_KEY]
self.authorize_target_action(context, 'user:delete', instance_id)
id = correct_id_with_req(id, req)
username, host = unquote_user_host(id)
user = None
context.notification = notification.DBaaSUserDelete(context,
request=req)
with StartNotification(context, instance_id=instance_id,
username=username):
try:
user = guest_models.MySQLUser(name=username,
host=host)
found_user = models.User.load(context, instance_id, username,
host)
if not found_user:
user = None
except (ValueError, AttributeError) as e:
raise exception.BadRequest(_("User delete error: %(e)s")
% {'e': e})
if not user:
raise exception.UserNotFound(uuid=id)
models.User.delete(context, instance_id, user.serialize())
return wsgi.Result(None, 202)
def show(self, req, tenant_id, instance_id, id):
"""Return a single user."""
LOG.info("Showing a user for instance '%(id)s'\n"
"req : '%(req)s'\n\n",
{"id": instance_id, "req": req})
context = req.environ[wsgi.CONTEXT_KEY]
self.authorize_target_action(context, 'user:show', instance_id)
id = correct_id_with_req(id, req)
username, host = unquote_user_host(id)
user = None
try:
user = models.User.load(context, instance_id, username, host)
except (ValueError, AttributeError) as e:
raise exception.BadRequest(_("User show error: %(e)s")
% {'e': e})
if not user:
raise exception.UserNotFound(uuid=id)
view = views.UserView(user)
return wsgi.Result(view.data(), 200)
def update(self, req, body, tenant_id, instance_id, id):
"""Change attributes for one user."""
LOG.info("Updating user attributes for instance '%(id)s'\n"
"req : '%(req)s'\n\n",
{"id": instance_id, "req": strutils.mask_password(req)})
context = req.environ[wsgi.CONTEXT_KEY]
self.authorize_target_action(context, 'user:update', instance_id)
id = correct_id_with_req(id, req)
username, hostname = unquote_user_host(id)
user = None
user_attrs = body['user']
context.notification = notification.DBaaSUserUpdateAttributes(
context, request=req)
with StartNotification(context, instance_id=instance_id,
username=username):
try:
user = models.User.load(context, instance_id, username,
hostname)
except (ValueError, AttributeError) as e:
raise exception.BadRequest(_("Error loading user: %(e)s")
% {'e': e})
if not user:
raise exception.UserNotFound(uuid=id)
try:
models.User.update_attributes(context, instance_id, username,
hostname, user_attrs)
except (ValueError, AttributeError) as e:
raise exception.BadRequest(_("User update error: %(e)s")
% {'e': e})
return wsgi.Result(None, 202)
def update_all(self, req, body, tenant_id, instance_id):
"""Change the password of one or more users."""
LOG.info("Updating user password for instance '%(id)s'\n"
"req : '%(req)s'\n\n",
{"id": instance_id, "req": strutils.mask_password(req)})
context = req.environ[wsgi.CONTEXT_KEY]
self.authorize_target_action(context, 'user:update_all', instance_id)
context.notification = notification.DBaaSUserChangePassword(
context, request=req)
users = body['users']
model_users = []
with StartNotification(context, instance_id=instance_id,
username=",".join([user['name']
for user in users])):
for user in users:
try:
mu = guest_models.MySQLUser(name=user['name'],
host=user.get('host'),
password=user['password'])
found_user = models.User.load(context, instance_id,
mu.name, mu.host)
if not found_user:
user_and_host = mu.name
if mu.host:
user_and_host += '@' + mu.host
raise exception.UserNotFound(uuid=user_and_host)
model_users.append(mu)
except (ValueError, AttributeError) as e:
raise exception.BadRequest(_("Error loading user: %(e)s")
% {'e': e})
try:
models.User.change_password(context, instance_id, model_users)
except (ValueError, AttributeError) as e:
raise exception.BadRequest(_("User password update error: "
"%(e)s")
% {'e': e})
return wsgi.Result(None, 202)
class UserAccessController(ExtensionController):
"""Controller for adding and removing database access for a user."""
schemas = apischema.user
@classmethod
def get_schema(cls, action, body):
schema = {}
if 'update_all' == action:
schema = cls.schemas.get(action).get('databases')
return schema
def _get_user(self, context, instance_id, user_id):
username, hostname = unquote_user_host(user_id)
try:
user = models.User.load(context, instance_id, username, hostname)
except (ValueError, AttributeError) as e:
raise exception.BadRequest(_("Error loading user: %(e)s")
% {'e': e})
if not user:
raise exception.UserNotFound(uuid=user_id)
return user
def index(self, req, tenant_id, instance_id, user_id):
"""Show permissions for the given user."""
LOG.info("Showing user access for instance '%(id)s'\n"
"req : '%(req)s'\n\n",
{"id": instance_id, "req": req})
context = req.environ[wsgi.CONTEXT_KEY]
self.authorize_target_action(
context, 'user_access:index', instance_id)
# Make sure this user exists.
user_id = correct_id_with_req(user_id, req)
user = self._get_user(context, instance_id, user_id)
if not user:
LOG.error("No such user: %(user)s ", {'user': user})
raise exception.UserNotFound(uuid=user)
username, hostname = unquote_user_host(user_id)
access = models.User.access(context, instance_id, username, hostname)
view = views.UserAccessView(access.databases)
return wsgi.Result(view.data(), 200)
def update(self, req, body, tenant_id, instance_id, user_id):
"""Grant access for a user to one or more databases."""
LOG.info("Granting user access for instance '%(id)s'\n"
"req : '%(req)s'\n\n",
{"id": instance_id, "req": req})
context = req.environ[wsgi.CONTEXT_KEY]
self.authorize_target_action(
context, 'user_access:update', instance_id)
context.notification = notification.DBaaSUserGrant(
context, request=req)
user_id = correct_id_with_req(user_id, req)
user = self._get_user(context, instance_id, user_id)
if not user:
LOG.error("No such user: %(user)s ", {'user': user})
raise exception.UserNotFound(uuid=user)
username, hostname = unquote_user_host(user_id)
databases = [db['name'] for db in body['databases']]
with StartNotification(context, instance_id=instance_id,
username=username, database=databases):
models.User.grant(context, instance_id, username, hostname,
databases)
return wsgi.Result(None, 202)
def delete(self, req, tenant_id, instance_id, user_id, id):
"""Revoke access for a user."""
LOG.info("Revoking user access for instance '%(id)s'\n"
"req : '%(req)s'\n\n",
{"id": instance_id, "req": req})
context = req.environ[wsgi.CONTEXT_KEY]
self.authorize_target_action(
context, 'user_access:delete', instance_id)
context.notification = notification.DBaaSUserRevoke(
context, request=req)
user_id = correct_id_with_req(user_id, req)
user = self._get_user(context, instance_id, user_id)
if not user:
LOG.error("No such user: %(user)s ", {'user': user})
raise exception.UserNotFound(uuid=user)
username, hostname = unquote_user_host(user_id)
access = models.User.access(context, instance_id, username, hostname)
databases = [db.name for db in access.databases]
with StartNotification(context, instance_id=instance_id,
username=username, database=databases):
if id not in databases:
raise exception.DatabaseNotFound(uuid=id)
models.User.revoke(context, instance_id, username, hostname, id)
return wsgi.Result(None, 202)
class SchemaController(ExtensionController):
"""Controller for instance functionality."""
schemas = apischema.dbschema
def index(self, req, tenant_id, instance_id):
"""Return all schemas."""
LOG.info("Listing schemas for instance '%(id)s'\n"
"req : '%(req)s'\n\n",
{"id": instance_id, "req": req})
context = req.environ[wsgi.CONTEXT_KEY]
self.authorize_target_action(
context, 'database:index', instance_id)
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."""
LOG.info("Creating schema for instance '%(id)s'\n"
"req : '%(req)s'\n\n"
"body: '%(body)s'\n'n",
{"id": instance_id,
"req": req,
"body": body})
context = req.environ[wsgi.CONTEXT_KEY]
self.authorize_target_action(
context, 'database:create', instance_id)
schemas = body['databases']
context.notification = notification.DBaaSDatabaseCreate(context,
request=req)
with StartNotification(context, instance_id=instance_id,
dbname=".".join([db['name']
for db in schemas])):
try:
model_schemas = populate_validated_databases(schemas)
models.Schema.create(context, instance_id, model_schemas)
except (ValueError, AttributeError) as e:
raise exception.BadRequest(_("Database create error: %(e)s")
% {'e': e})
return wsgi.Result(None, 202)
def delete(self, req, tenant_id, instance_id, id):
LOG.info("Deleting schema for instance '%(id)s'\n"
"req : '%(req)s'\n\n",
{"id": instance_id, "req": req})
context = req.environ[wsgi.CONTEXT_KEY]
self.authorize_target_action(
context, 'database:delete', instance_id)
context.notification = notification.DBaaSDatabaseDelete(
context, request=req)
with StartNotification(context, instance_id=instance_id, dbname=id):
try:
schema = guest_models.MySQLSchema(name=id)
schema.check_delete()
if not models.Schemas.find(context, instance_id, id):
raise exception.DatabaseNotFound(uuid=id)
models.Schema.delete(context, instance_id, schema.serialize())
except (ValueError, AttributeError) as e:
raise exception.BadRequest(_("Database delete error: %(e)s")
% {'e': e})
return wsgi.Result(None, 202)
def show(self, req, tenant_id, instance_id, id):
context = req.environ[wsgi.CONTEXT_KEY]
self.authorize_target_action(
context, 'database:show', instance_id)
raise webob.exc.HTTPNotImplemented()

View File

@ -1,74 +0,0 @@
# Copyright 2011 OpenStack Foundation
# 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.
class UserView(object):
def __init__(self, user):
self.user = user
def data(self):
user_dict = {
"name": self.user.name,
"host": self.user.host,
"databases": self.user.databases
}
return {"user": user_dict}
class UsersView(object):
def __init__(self, users):
self.users = users
def data(self):
userlist = [{"name": user.name,
"host": user.host,
"databases": user.databases}
for user in self.users]
return {"users": userlist}
class UserAccessView(object):
def __init__(self, databases):
self.databases = databases
def data(self):
dbs = [{"name": db.name} for db in self.databases]
return {"databases": dbs}
class SchemaView(object):
def __init__(self, schema):
self.schema = schema
def data(self):
return {"name": self.schema.name}
class SchemasView(object):
def __init__(self, schemas):
self.schemas = schemas
def data(self):
data = []
# These are model instances
for schema in self.schemas:
data.append(SchemaView(schema).data())
return {"databases": data}

View File

@ -15,7 +15,6 @@
from trove.common import extensions
from trove.extensions.common import service as common_service
from trove.extensions.mysql import service as mysql_service
class Mysql(extensions.ExtensionDescriptor):
@ -40,14 +39,14 @@ class Mysql(extensions.ExtensionDescriptor):
resource = extensions.ResourceExtension(
'databases',
mysql_service.SchemaController(),
common_service.SchemaController(),
parent={'member_name': 'instance',
'collection_name': '{tenant_id}/instances'})
resources.append(resource)
resource = extensions.ResourceExtension(
'users',
mysql_service.UserController(),
common_service.UserController(),
parent={'member_name': 'instance',
'collection_name': '{tenant_id}/instances'},
member_actions={'update': 'PUT'},
@ -57,7 +56,7 @@ class Mysql(extensions.ExtensionDescriptor):
collection_url = '{tenant_id}/instances/:instance_id/users'
resource = extensions.ResourceExtension(
'databases',
mysql_service.UserAccessController(),
common_service.UserAccessController(),
parent={'member_name': 'user',
'collection_name': collection_url},
collection_actions={'update': 'PUT'})

View File

@ -34,8 +34,8 @@ from trove.common import policy
from trove.common import utils
from trove.common import wsgi
from trove.datastore import models as ds_models
from trove.extensions.mysql.common import populate_users
from trove.extensions.mysql.common import populate_validated_databases
from trove.extensions.common.common import populate_users
from trove.extensions.common.common import populate_validated_databases
from trove.instance import models, views
from trove.module import models as module_models
from trove.module import views as module_views

View File

@ -57,7 +57,7 @@ from trove.common import timeutils
from trove.common import utils
from trove.common.utils import try_recover
from trove.configuration import models as config_models
from trove.extensions.mysql import models as mysql_models
from trove.extensions.common import models as common_models
from trove.instance import models as inst_models
from trove.instance.models import DBInstance
from trove.instance.models import FreshInstance
@ -789,7 +789,7 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
create_fmt_content, err)
def report_root_enabled(self):
mysql_models.RootHistory.create(self.context, self.id)
common_models.RootHistory.create(self.context, self.id)
def update_statuses_on_time_out(self):
if CONF.update_status_on_fail:

View File

@ -17,8 +17,8 @@ from testtools.matchers import Is
from trove.common.exception import DatabaseForUserNotInDatabaseListError
from trove.common.exception import DatabaseInitialDatabaseDuplicateError
from trove.common.exception import DatabaseInitialUserDuplicateError
from trove.extensions.mysql.common import populate_users
from trove.extensions.mysql.common import populate_validated_databases
from trove.extensions.common.common import populate_users
from trove.extensions.common.common import populate_validated_databases
from trove.tests.unittests import trove_testtools

View File

@ -16,9 +16,9 @@
import jsonschema
from testtools.matchers import Is
from trove.extensions.mysql.service import SchemaController
from trove.extensions.mysql.service import UserAccessController
from trove.extensions.mysql.service import UserController
from trove.extensions.common.service import SchemaController
from trove.extensions.common.service import UserAccessController
from trove.extensions.common.service import UserController
from trove.tests.unittests import trove_testtools

View File

@ -51,7 +51,6 @@ from trove.common import timeutils
from trove.common import utils
from trove.datastore import models as datastore_models
from trove.extensions.common import models as common_models
from trove.extensions.mysql import models as mysql_models
from trove.instance.models import BaseInstance
from trove.instance.models import DBInstance
from trove.instance.models import InstanceServiceStatus
@ -1211,7 +1210,7 @@ class RootReportTest(trove_testtools.TestCase):
def test_report_root_first_time(self):
context = Mock()
context.user_id = utils.generate_uuid()
report = mysql_models.RootHistory.create(
report = common_models.RootHistory.create(
context, utils.generate_uuid())
self.assertIsNotNone(report)
@ -1219,11 +1218,11 @@ class RootReportTest(trove_testtools.TestCase):
context = Mock()
context.user_id = utils.generate_uuid()
id = utils.generate_uuid()
history = mysql_models.RootHistory(id, context.user_id).save()
with patch.object(mysql_models.RootHistory, 'load',
history = common_models.RootHistory(id, context.user_id).save()
with patch.object(common_models.RootHistory, 'load',
Mock(return_value=history)):
report = mysql_models.RootHistory.create(context, id)
self.assertTrue(mysql_models.RootHistory.load.called)
report = common_models.RootHistory.create(context, id)
self.assertTrue(common_models.RootHistory.load.called)
self.assertEqual(history.user, report.user)
self.assertEqual(history.id, report.id)