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:
parent
0138ce30e2
commit
89274df2af
1
.gitignore
vendored
1
.gitignore
vendored
@ -56,6 +56,7 @@ publish-docs/
|
|||||||
*~
|
*~
|
||||||
.*.swp
|
.*.swp
|
||||||
.bak
|
.bak
|
||||||
|
.idea/
|
||||||
|
|
||||||
# Config sample and policy sample
|
# Config sample and policy sample
|
||||||
etc/trove/*.sample
|
etc/trove/*.sample
|
||||||
|
@ -114,6 +114,28 @@ class DatastoreSchema(DatastoreModelsBase):
|
|||||||
self._validate_schema_name(value)
|
self._validate_schema_name(value)
|
||||||
self._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):
|
def _validate_schema_name(self, value):
|
||||||
"""Perform checks on a given schema name.
|
"""Perform checks on a given schema name.
|
||||||
:param value: Validated schema name.
|
:param value: Validated schema name.
|
||||||
|
@ -22,7 +22,7 @@ from trove.common import exception as trove_exception
|
|||||||
from trove.common.rpc import version as rpc_version
|
from trove.common.rpc import version as rpc_version
|
||||||
from trove.common.serializable_notification import SerializableNotification
|
from trove.common.serializable_notification import SerializableNotification
|
||||||
from trove.conductor.models import LastSeen
|
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 models as inst_models
|
||||||
from trove.instance import service_status as svc_status
|
from trove.instance import service_status as svc_status
|
||||||
|
|
||||||
@ -150,7 +150,7 @@ class Manager(periodic_task.PeriodicTasks):
|
|||||||
if user is not None:
|
if user is not None:
|
||||||
LOG.debug("calling report_root with a username: %s, "
|
LOG.debug("calling report_root with a username: %s, "
|
||||||
"is deprecated now!" % user)
|
"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):
|
def notify_end(self, context, serialized_notification, notification_args):
|
||||||
notification = SerializableNotification.deserialize(
|
notification = SerializableNotification.deserialize(
|
||||||
|
@ -41,7 +41,7 @@ def configure_db(models_mapper=None):
|
|||||||
from trove.configuration import models as configurations_models
|
from trove.configuration import models as configurations_models
|
||||||
from trove.datastore import models as datastores_models
|
from trove.datastore import models as datastores_models
|
||||||
from trove.dns import models as dns_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.extensions.security_group import models as secgrp_models
|
||||||
from trove.guestagent import models as agent_models
|
from trove.guestagent import models as agent_models
|
||||||
from trove.instance import models as base_models
|
from trove.instance import models as base_models
|
||||||
@ -52,7 +52,7 @@ def configure_db(models_mapper=None):
|
|||||||
base_models,
|
base_models,
|
||||||
datastores_models,
|
datastores_models,
|
||||||
dns_models,
|
dns_models,
|
||||||
mysql_models,
|
common_models,
|
||||||
agent_models,
|
agent_models,
|
||||||
quota_models,
|
quota_models,
|
||||||
backup_models,
|
backup_models,
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
from urllib.parse import unquote
|
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
|
from trove.common import exception
|
||||||
|
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ def populate_validated_databases(dbs):
|
|||||||
databases = []
|
databases = []
|
||||||
unique_identities = set()
|
unique_identities = set()
|
||||||
for database in dbs:
|
for database in dbs:
|
||||||
mydb = guest_models.MySQLSchema(name=database.get('name', ''))
|
mydb = guest_models.DatastoreSchema(name=database.get('name', ''))
|
||||||
mydb.check_reserved()
|
mydb.check_reserved()
|
||||||
if mydb.name in unique_identities:
|
if mydb.name in unique_identities:
|
||||||
raise exception.DatabaseInitialDatabaseDuplicateError()
|
raise exception.DatabaseInitialDatabaseDuplicateError()
|
||||||
@ -49,7 +49,7 @@ def populate_users(users, initial_databases=None):
|
|||||||
users_data = []
|
users_data = []
|
||||||
unique_identities = set()
|
unique_identities = set()
|
||||||
for user in users:
|
for user in users:
|
||||||
u = guest_models.MySQLUser(name=user.get('name', ''),
|
u = guest_models.DatastoreUser(name=user.get('name', ''),
|
||||||
host=user.get('host', '%'))
|
host=user.get('host', '%'))
|
||||||
u.check_reserved()
|
u.check_reserved()
|
||||||
user_identity = (u.name, u.host)
|
user_identity = (u.name, u.host)
|
@ -22,12 +22,17 @@ from trove.common import timeutils
|
|||||||
from trove.db import get_db_api
|
from trove.db import get_db_api
|
||||||
from trove.instance import models as base_models
|
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__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
def load_and_verify(context, instance_id,
|
def load_and_verify(context, instance_id,
|
||||||
enabled_datastore=['mysql', 'mariadb']):
|
enabled_datastore=['mysql', 'mariadb', 'postgresql']):
|
||||||
"""Check instance datastore.
|
"""Check instance datastore.
|
||||||
|
|
||||||
Some API operations are only supported for some specific datastores.
|
Some API operations are only supported for some specific datastores.
|
||||||
@ -141,3 +146,259 @@ class RootHistory(object):
|
|||||||
return history
|
return history
|
||||||
history = RootHistory(instance_id, context.user_id)
|
history = RootHistory(instance_id, context.user_id)
|
||||||
return history.save()
|
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
|
||||||
|
@ -33,6 +33,19 @@ from trove.extensions.common import views
|
|||||||
from trove.instance import models as instance_models
|
from trove.instance import models as instance_models
|
||||||
from trove.instance.models import DBInstance
|
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__)
|
LOG = logging.getLogger(__name__)
|
||||||
import_class = importutils.import_class
|
import_class = importutils.import_class
|
||||||
@ -270,3 +283,334 @@ class RootController(ExtensionController):
|
|||||||
f"root_controller not configured for datastore {manager}")
|
f"root_controller not configured for datastore {manager}")
|
||||||
raise exception.DatastoreOperationNotSupported(
|
raise exception.DatastoreOperationNotSupported(
|
||||||
datastore=manager, operation='root')
|
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()
|
||||||
|
@ -45,3 +45,49 @@ class RootEnabledView(object):
|
|||||||
|
|
||||||
def data(self):
|
def data(self):
|
||||||
return {'rootEnabled': self.is_root_enabled}
|
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}
|
||||||
|
@ -19,7 +19,7 @@ from trove.common import cfg
|
|||||||
from trove.common import clients
|
from trove.common import clients
|
||||||
from trove.common import exception
|
from trove.common import exception
|
||||||
from trove.common import timeutils
|
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.instance import models as instance_models
|
||||||
from trove import rpc
|
from trove import rpc
|
||||||
|
|
||||||
@ -131,7 +131,7 @@ class DetailedMgmtInstance(SimpleMgmtInstance):
|
|||||||
instance.volume = None
|
instance.volume = None
|
||||||
# Populate the volume_used attribute from the guest agent.
|
# Populate the volume_used attribute from the guest agent.
|
||||||
instance_models.load_guest_info(instance, context, id)
|
instance_models.load_guest_info(instance, context, id)
|
||||||
instance.root_history = mysql_models.RootHistory.load(context=context,
|
instance.root_history = common_models.RootHistory.load(context=context,
|
||||||
instance_id=id)
|
instance_id=id)
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
@ -25,11 +25,11 @@ from trove.common.i18n import _
|
|||||||
from trove.common import notification
|
from trove.common import notification
|
||||||
from trove.common.notification import StartNotification
|
from trove.common.notification import StartNotification
|
||||||
from trove.common import wsgi
|
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 models
|
||||||
from trove.extensions.mgmt.instances import views
|
from trove.extensions.mgmt.instances import views
|
||||||
from trove.extensions.mgmt.instances.views import DiagnosticsView
|
from trove.extensions.mgmt.instances.views import DiagnosticsView
|
||||||
from trove.extensions.mgmt.instances.views import HwInfoView
|
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 import models as instance_models
|
||||||
from trove.instance.service import InstanceController
|
from trove.instance.service import InstanceController
|
||||||
|
|
||||||
@ -85,7 +85,7 @@ class MgmtInstanceController(InstanceController):
|
|||||||
include_deleted = deleted_q == 'true'
|
include_deleted = deleted_q == 'true'
|
||||||
server = models.DetailedMgmtInstance.load(context, id,
|
server = models.DetailedMgmtInstance.load(context, id,
|
||||||
include_deleted)
|
include_deleted)
|
||||||
root_history = mysql_models.RootHistory.load(context=context,
|
root_history = common_models.RootHistory.load(context=context,
|
||||||
instance_id=id)
|
instance_id=id)
|
||||||
return wsgi.Result(
|
return wsgi.Result(
|
||||||
views.MgmtInstanceDetailView(
|
views.MgmtInstanceDetailView(
|
||||||
@ -189,7 +189,7 @@ class MgmtInstanceController(InstanceController):
|
|||||||
LOG.exception(e)
|
LOG.exception(e)
|
||||||
return wsgi.Result(str(e), 404)
|
return wsgi.Result(str(e), 404)
|
||||||
rhv = views.RootHistoryView(id)
|
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:
|
if reh:
|
||||||
rhv = views.RootHistoryView(reh.id, enabled=reh.created,
|
rhv = views.RootHistoryView(reh.id, enabled=reh.created,
|
||||||
user_id=reh.user)
|
user_id=reh.user)
|
||||||
|
@ -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
|
|
@ -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()
|
|
@ -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}
|
|
@ -15,7 +15,6 @@
|
|||||||
|
|
||||||
from trove.common import extensions
|
from trove.common import extensions
|
||||||
from trove.extensions.common import service as common_service
|
from trove.extensions.common import service as common_service
|
||||||
from trove.extensions.mysql import service as mysql_service
|
|
||||||
|
|
||||||
|
|
||||||
class Mysql(extensions.ExtensionDescriptor):
|
class Mysql(extensions.ExtensionDescriptor):
|
||||||
@ -40,14 +39,14 @@ class Mysql(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
resource = extensions.ResourceExtension(
|
resource = extensions.ResourceExtension(
|
||||||
'databases',
|
'databases',
|
||||||
mysql_service.SchemaController(),
|
common_service.SchemaController(),
|
||||||
parent={'member_name': 'instance',
|
parent={'member_name': 'instance',
|
||||||
'collection_name': '{tenant_id}/instances'})
|
'collection_name': '{tenant_id}/instances'})
|
||||||
resources.append(resource)
|
resources.append(resource)
|
||||||
|
|
||||||
resource = extensions.ResourceExtension(
|
resource = extensions.ResourceExtension(
|
||||||
'users',
|
'users',
|
||||||
mysql_service.UserController(),
|
common_service.UserController(),
|
||||||
parent={'member_name': 'instance',
|
parent={'member_name': 'instance',
|
||||||
'collection_name': '{tenant_id}/instances'},
|
'collection_name': '{tenant_id}/instances'},
|
||||||
member_actions={'update': 'PUT'},
|
member_actions={'update': 'PUT'},
|
||||||
@ -57,7 +56,7 @@ class Mysql(extensions.ExtensionDescriptor):
|
|||||||
collection_url = '{tenant_id}/instances/:instance_id/users'
|
collection_url = '{tenant_id}/instances/:instance_id/users'
|
||||||
resource = extensions.ResourceExtension(
|
resource = extensions.ResourceExtension(
|
||||||
'databases',
|
'databases',
|
||||||
mysql_service.UserAccessController(),
|
common_service.UserAccessController(),
|
||||||
parent={'member_name': 'user',
|
parent={'member_name': 'user',
|
||||||
'collection_name': collection_url},
|
'collection_name': collection_url},
|
||||||
collection_actions={'update': 'PUT'})
|
collection_actions={'update': 'PUT'})
|
||||||
|
@ -34,8 +34,8 @@ from trove.common import policy
|
|||||||
from trove.common import utils
|
from trove.common import utils
|
||||||
from trove.common import wsgi
|
from trove.common import wsgi
|
||||||
from trove.datastore import models as ds_models
|
from trove.datastore import models as ds_models
|
||||||
from trove.extensions.mysql.common import populate_users
|
from trove.extensions.common.common import populate_users
|
||||||
from trove.extensions.mysql.common import populate_validated_databases
|
from trove.extensions.common.common import populate_validated_databases
|
||||||
from trove.instance import models, views
|
from trove.instance import models, views
|
||||||
from trove.module import models as module_models
|
from trove.module import models as module_models
|
||||||
from trove.module import views as module_views
|
from trove.module import views as module_views
|
||||||
|
@ -57,7 +57,7 @@ from trove.common import timeutils
|
|||||||
from trove.common import utils
|
from trove.common import utils
|
||||||
from trove.common.utils import try_recover
|
from trove.common.utils import try_recover
|
||||||
from trove.configuration import models as config_models
|
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 import models as inst_models
|
||||||
from trove.instance.models import DBInstance
|
from trove.instance.models import DBInstance
|
||||||
from trove.instance.models import FreshInstance
|
from trove.instance.models import FreshInstance
|
||||||
@ -789,7 +789,7 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
|
|||||||
create_fmt_content, err)
|
create_fmt_content, err)
|
||||||
|
|
||||||
def report_root_enabled(self):
|
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):
|
def update_statuses_on_time_out(self):
|
||||||
if CONF.update_status_on_fail:
|
if CONF.update_status_on_fail:
|
||||||
|
@ -17,8 +17,8 @@ from testtools.matchers import Is
|
|||||||
from trove.common.exception import DatabaseForUserNotInDatabaseListError
|
from trove.common.exception import DatabaseForUserNotInDatabaseListError
|
||||||
from trove.common.exception import DatabaseInitialDatabaseDuplicateError
|
from trove.common.exception import DatabaseInitialDatabaseDuplicateError
|
||||||
from trove.common.exception import DatabaseInitialUserDuplicateError
|
from trove.common.exception import DatabaseInitialUserDuplicateError
|
||||||
from trove.extensions.mysql.common import populate_users
|
from trove.extensions.common.common import populate_users
|
||||||
from trove.extensions.mysql.common import populate_validated_databases
|
from trove.extensions.common.common import populate_validated_databases
|
||||||
from trove.tests.unittests import trove_testtools
|
from trove.tests.unittests import trove_testtools
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,9 +16,9 @@
|
|||||||
import jsonschema
|
import jsonschema
|
||||||
from testtools.matchers import Is
|
from testtools.matchers import Is
|
||||||
|
|
||||||
from trove.extensions.mysql.service import SchemaController
|
from trove.extensions.common.service import SchemaController
|
||||||
from trove.extensions.mysql.service import UserAccessController
|
from trove.extensions.common.service import UserAccessController
|
||||||
from trove.extensions.mysql.service import UserController
|
from trove.extensions.common.service import UserController
|
||||||
from trove.tests.unittests import trove_testtools
|
from trove.tests.unittests import trove_testtools
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,7 +51,6 @@ from trove.common import timeutils
|
|||||||
from trove.common import utils
|
from trove.common import utils
|
||||||
from trove.datastore import models as datastore_models
|
from trove.datastore import models as datastore_models
|
||||||
from trove.extensions.common import models as common_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 BaseInstance
|
||||||
from trove.instance.models import DBInstance
|
from trove.instance.models import DBInstance
|
||||||
from trove.instance.models import InstanceServiceStatus
|
from trove.instance.models import InstanceServiceStatus
|
||||||
@ -1211,7 +1210,7 @@ class RootReportTest(trove_testtools.TestCase):
|
|||||||
def test_report_root_first_time(self):
|
def test_report_root_first_time(self):
|
||||||
context = Mock()
|
context = Mock()
|
||||||
context.user_id = utils.generate_uuid()
|
context.user_id = utils.generate_uuid()
|
||||||
report = mysql_models.RootHistory.create(
|
report = common_models.RootHistory.create(
|
||||||
context, utils.generate_uuid())
|
context, utils.generate_uuid())
|
||||||
self.assertIsNotNone(report)
|
self.assertIsNotNone(report)
|
||||||
|
|
||||||
@ -1219,11 +1218,11 @@ class RootReportTest(trove_testtools.TestCase):
|
|||||||
context = Mock()
|
context = Mock()
|
||||||
context.user_id = utils.generate_uuid()
|
context.user_id = utils.generate_uuid()
|
||||||
id = utils.generate_uuid()
|
id = utils.generate_uuid()
|
||||||
history = mysql_models.RootHistory(id, context.user_id).save()
|
history = common_models.RootHistory(id, context.user_id).save()
|
||||||
with patch.object(mysql_models.RootHistory, 'load',
|
with patch.object(common_models.RootHistory, 'load',
|
||||||
Mock(return_value=history)):
|
Mock(return_value=history)):
|
||||||
report = mysql_models.RootHistory.create(context, id)
|
report = common_models.RootHistory.create(context, id)
|
||||||
self.assertTrue(mysql_models.RootHistory.load.called)
|
self.assertTrue(common_models.RootHistory.load.called)
|
||||||
self.assertEqual(history.user, report.user)
|
self.assertEqual(history.user, report.user)
|
||||||
self.assertEqual(history.id, report.id)
|
self.assertEqual(history.id, report.id)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user