Modify-user features.
Grant, revoke, list user access. Change user password and get single user. Partially implements blueprint modify-users Change-Id: I0001a7a9d1c527b88a1ed965f0f077c864e602cf
This commit is contained in:
parent
7dd1c6b3f5
commit
90f8ca81d9
@ -1,6 +1,7 @@
|
|||||||
[DEFAULT]
|
[DEFAULT]
|
||||||
|
|
||||||
remote_implementation = fake
|
remote_implementation = fake
|
||||||
|
fake_mode_events = eventlet
|
||||||
|
|
||||||
log_file = rdtest.log
|
log_file = rdtest.log
|
||||||
|
|
||||||
|
@ -65,6 +65,16 @@ class FlavorNotFound(ReddwarfError):
|
|||||||
message = _("Resource %(uuid)s cannot be found")
|
message = _("Resource %(uuid)s cannot be found")
|
||||||
|
|
||||||
|
|
||||||
|
class UserNotFound(NotFound):
|
||||||
|
|
||||||
|
message = _("User %(uuid)s cannot be found on the instance.")
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseNotFound(NotFound):
|
||||||
|
|
||||||
|
message = _("Database %(uuid)s cannot be found on the instance.")
|
||||||
|
|
||||||
|
|
||||||
class ComputeInstanceNotFound(NotFound):
|
class ComputeInstanceNotFound(NotFound):
|
||||||
|
|
||||||
internal_message = _("Cannot find compute instance %(server_id)s for "
|
internal_message = _("Cannot find compute instance %(server_id)s for "
|
||||||
|
@ -317,6 +317,8 @@ class Controller(object):
|
|||||||
exception.NotFound,
|
exception.NotFound,
|
||||||
exception.ComputeInstanceNotFound,
|
exception.ComputeInstanceNotFound,
|
||||||
exception.ModelNotFoundError,
|
exception.ModelNotFoundError,
|
||||||
|
exception.UserNotFound,
|
||||||
|
exception.DatabaseNotFound,
|
||||||
],
|
],
|
||||||
webob.exc.HTTPConflict: [],
|
webob.exc.HTTPConflict: [],
|
||||||
webob.exc.HTTPRequestEntityTooLarge: [
|
webob.exc.HTTPRequestEntityTooLarge: [
|
||||||
|
@ -47,6 +47,7 @@ class Mysql(extensions.ExtensionsDescriptor):
|
|||||||
serializer = wsgi.ReddwarfResponseSerializer(
|
serializer = wsgi.ReddwarfResponseSerializer(
|
||||||
body_serializers={'application/xml':
|
body_serializers={'application/xml':
|
||||||
wsgi.ReddwarfXMLDictSerializer()})
|
wsgi.ReddwarfXMLDictSerializer()})
|
||||||
|
|
||||||
resource = extensions.ResourceExtension(
|
resource = extensions.ResourceExtension(
|
||||||
'databases',
|
'databases',
|
||||||
service.SchemaController(),
|
service.SchemaController(),
|
||||||
@ -55,6 +56,7 @@ class Mysql(extensions.ExtensionsDescriptor):
|
|||||||
deserializer=wsgi.ReddwarfRequestDeserializer(),
|
deserializer=wsgi.ReddwarfRequestDeserializer(),
|
||||||
serializer=serializer)
|
serializer=serializer)
|
||||||
resources.append(resource)
|
resources.append(resource)
|
||||||
|
|
||||||
resource = extensions.ResourceExtension(
|
resource = extensions.ResourceExtension(
|
||||||
'users',
|
'users',
|
||||||
service.UserController(),
|
service.UserController(),
|
||||||
@ -62,8 +64,21 @@ class Mysql(extensions.ExtensionsDescriptor):
|
|||||||
'collection_name': '{tenant_id}/instances'},
|
'collection_name': '{tenant_id}/instances'},
|
||||||
# deserializer=extensions.ExtensionsXMLSerializer()
|
# deserializer=extensions.ExtensionsXMLSerializer()
|
||||||
deserializer=wsgi.ReddwarfRequestDeserializer(),
|
deserializer=wsgi.ReddwarfRequestDeserializer(),
|
||||||
serializer=serializer)
|
serializer=serializer,
|
||||||
|
collection_actions={'update': 'PUT'})
|
||||||
resources.append(resource)
|
resources.append(resource)
|
||||||
|
|
||||||
|
collection_url = '{tenant_id}/instances/:instance_id/users'
|
||||||
|
resource = extensions.ResourceExtension(
|
||||||
|
'databases',
|
||||||
|
service.UserAccessController(),
|
||||||
|
parent={'member_name': 'user',
|
||||||
|
'collection_name': collection_url},
|
||||||
|
deserializer=wsgi.ReddwarfRequestDeserializer(),
|
||||||
|
serializer=serializer,
|
||||||
|
collection_actions={'update': 'PUT'})
|
||||||
|
resources.append(resource)
|
||||||
|
|
||||||
resource = extensions.ResourceExtension(
|
resource = extensions.ResourceExtension(
|
||||||
'root',
|
'root',
|
||||||
service.RootController(),
|
service.RootController(),
|
||||||
@ -71,7 +86,6 @@ class Mysql(extensions.ExtensionsDescriptor):
|
|||||||
'collection_name': '{tenant_id}/instances'},
|
'collection_name': '{tenant_id}/instances'},
|
||||||
deserializer=wsgi.ReddwarfRequestDeserializer(),
|
deserializer=wsgi.ReddwarfRequestDeserializer(),
|
||||||
serializer=serializer)
|
serializer=serializer)
|
||||||
|
|
||||||
resources.append(resource)
|
resources.append(resource)
|
||||||
|
|
||||||
return resources
|
return resources
|
||||||
|
@ -56,6 +56,19 @@ class User(object):
|
|||||||
self.password = password
|
self.password = password
|
||||||
self.databases = databases
|
self.databases = databases
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load(cls, context, instance_id, user):
|
||||||
|
load_and_verify(context, instance_id)
|
||||||
|
client = create_guest_client(context, instance_id)
|
||||||
|
found_user = client.get_user(username=user)
|
||||||
|
if not found_user:
|
||||||
|
return None
|
||||||
|
database_names = [{'name': db['_name']}
|
||||||
|
for db in found_user['_databases']]
|
||||||
|
return cls(found_user['_name'],
|
||||||
|
found_user['_password'],
|
||||||
|
database_names)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, context, instance_id, users):
|
def create(cls, context, instance_id, users):
|
||||||
# Load InstanceServiceStatus to verify if it's running
|
# Load InstanceServiceStatus to verify if it's running
|
||||||
@ -78,6 +91,49 @@ class User(object):
|
|||||||
load_and_verify(context, instance_id)
|
load_and_verify(context, instance_id)
|
||||||
create_guest_client(context, instance_id).delete_user(username)
|
create_guest_client(context, instance_id).delete_user(username)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def access(cls, context, instance_id, username):
|
||||||
|
load_and_verify(context, instance_id)
|
||||||
|
client = create_guest_client(context, instance_id)
|
||||||
|
databases = client.list_access(username)
|
||||||
|
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, databases):
|
||||||
|
load_and_verify(context, instance_id)
|
||||||
|
client = create_guest_client(context, instance_id)
|
||||||
|
client.grant_access(username, databases)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def revoke(cls, context, instance_id, username, database):
|
||||||
|
load_and_verify(context, instance_id)
|
||||||
|
client = create_guest_client(context, instance_id)
|
||||||
|
client.revoke_access(username, 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,
|
||||||
|
'password': user.password,
|
||||||
|
}
|
||||||
|
change_users.append(change_user)
|
||||||
|
client.change_passwords(change_users)
|
||||||
|
|
||||||
|
|
||||||
|
class UserAccess(object):
|
||||||
|
_data_fields = ['databases']
|
||||||
|
|
||||||
|
def __init__(self, databases):
|
||||||
|
self.databases = databases
|
||||||
|
|
||||||
|
|
||||||
class Root(object):
|
class Root(object):
|
||||||
|
|
||||||
|
@ -28,6 +28,8 @@ from reddwarf.guestagent.db import models as guest_models
|
|||||||
from reddwarf.openstack.common import log as logging
|
from reddwarf.openstack.common import log as logging
|
||||||
from reddwarf.openstack.common.gettextutils import _
|
from reddwarf.openstack.common.gettextutils import _
|
||||||
|
|
||||||
|
from urllib import unquote
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -61,7 +63,6 @@ class UserController(wsgi.Controller):
|
|||||||
"""Validate that the request has all the required parameters"""
|
"""Validate that the request has all the required parameters"""
|
||||||
if not body:
|
if not body:
|
||||||
raise exception.BadRequest("The request contains an empty body")
|
raise exception.BadRequest("The request contains an empty body")
|
||||||
|
|
||||||
if not body.get('users', ''):
|
if not body.get('users', ''):
|
||||||
raise exception.MissingKey(key='users')
|
raise exception.MissingKey(key='users')
|
||||||
for user in body.get('users'):
|
for user in body.get('users'):
|
||||||
@ -106,7 +107,87 @@ class UserController(wsgi.Controller):
|
|||||||
return wsgi.Result(None, 202)
|
return wsgi.Result(None, 202)
|
||||||
|
|
||||||
def show(self, req, tenant_id, instance_id, id):
|
def show(self, req, tenant_id, instance_id, id):
|
||||||
raise webob.exc.HTTPNotImplemented()
|
"""Return a single user."""
|
||||||
|
LOG.info(_("Showing a user for instance '%s'") % instance_id)
|
||||||
|
LOG.info(_("req : '%s'\n\n") % req)
|
||||||
|
context = req.environ[wsgi.CONTEXT_KEY]
|
||||||
|
username = unquote(id)
|
||||||
|
user = models.User.load(context, instance_id, username)
|
||||||
|
if not user:
|
||||||
|
raise exception.UserNotFound(uuid=username)
|
||||||
|
view = views.UserView(user)
|
||||||
|
return wsgi.Result(view.data(), 200)
|
||||||
|
|
||||||
|
def update(self, req, body, tenant_id, instance_id):
|
||||||
|
"""Change the password of one or more users."""
|
||||||
|
LOG.info(_("Updating user passwords for instance '%s'") % instance_id)
|
||||||
|
LOG.info(_("req : '%s'\n\n") % req)
|
||||||
|
context = req.environ[wsgi.CONTEXT_KEY]
|
||||||
|
self.validate(body)
|
||||||
|
users = body['users']
|
||||||
|
model_users = []
|
||||||
|
for user in users:
|
||||||
|
mu = guest_models.MySQLUser()
|
||||||
|
mu.name = user['name']
|
||||||
|
mu.password = user['password']
|
||||||
|
model_users.append(mu)
|
||||||
|
models.User.change_password(context, instance_id, model_users)
|
||||||
|
return wsgi.Result(None, 202)
|
||||||
|
|
||||||
|
|
||||||
|
class UserAccessController(wsgi.Controller):
|
||||||
|
"""Controller for adding and removing database access for a user."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def validate(cls, body):
|
||||||
|
"""Validate that the request has all the required parameters"""
|
||||||
|
if not body:
|
||||||
|
raise exception.BadRequest("The request contains an empty body")
|
||||||
|
if not body.get('databases', ''):
|
||||||
|
raise exception.MissingKey(key='databases')
|
||||||
|
for database in body.get('databases'):
|
||||||
|
if not database.get('name', ''):
|
||||||
|
raise exception.MissingKey(key='name')
|
||||||
|
|
||||||
|
def index(self, req, tenant_id, instance_id, user_id):
|
||||||
|
"""Show permissions for the given user."""
|
||||||
|
LOG.info(_("Showing user access for instance '%s'") % instance_id)
|
||||||
|
LOG.info(_("req : '%s'\n\n") % req)
|
||||||
|
context = req.environ[wsgi.CONTEXT_KEY]
|
||||||
|
# Make sure this user exists.
|
||||||
|
username = unquote(user_id)
|
||||||
|
user = models.User.load(context, instance_id, username)
|
||||||
|
if not user:
|
||||||
|
raise exception.UserNotFound(uuid=user_id)
|
||||||
|
access = models.User.access(context, instance_id, username)
|
||||||
|
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."""
|
||||||
|
context = req.environ[wsgi.CONTEXT_KEY]
|
||||||
|
self.validate(body)
|
||||||
|
user = models.User.load(context, instance_id, user_id)
|
||||||
|
if not user:
|
||||||
|
raise exception.UserNotFound(uuid=user_id)
|
||||||
|
databases = [db['name'] for db in body['databases']]
|
||||||
|
models.User.grant(context, instance_id, user_id, databases)
|
||||||
|
return wsgi.Result(None, 202)
|
||||||
|
|
||||||
|
def delete(self, req, tenant_id, instance_id, user_id, id):
|
||||||
|
"""Revoke access for a user."""
|
||||||
|
context = req.environ[wsgi.CONTEXT_KEY]
|
||||||
|
user = models.User.load(context, instance_id, user_id)
|
||||||
|
if not user:
|
||||||
|
raise exception.UserNotFound(uuid=user_id)
|
||||||
|
# Make sure the database exists for the user.
|
||||||
|
username = unquote(user_id)
|
||||||
|
access = models.User.access(context, instance_id, username)
|
||||||
|
databases = [db.name for db in access.databases]
|
||||||
|
if not id in databases:
|
||||||
|
raise exception.DatabaseNotFound(uuid=id)
|
||||||
|
models.User.revoke(context, instance_id, user_id, id)
|
||||||
|
return wsgi.Result(None, 202)
|
||||||
|
|
||||||
|
|
||||||
class SchemaController(wsgi.Controller):
|
class SchemaController(wsgi.Controller):
|
||||||
|
@ -43,6 +43,15 @@ class UsersView(object):
|
|||||||
return {"users": data}
|
return {"users": data}
|
||||||
|
|
||||||
|
|
||||||
|
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 RootCreatedView(UserView):
|
class RootCreatedView(UserView):
|
||||||
|
|
||||||
def data(self):
|
def data(self):
|
||||||
|
@ -106,11 +106,39 @@ class API(proxy.RpcProxy):
|
|||||||
LOG.warn(mnfe)
|
LOG.warn(mnfe)
|
||||||
raise exception.GuestTimeout()
|
raise exception.GuestTimeout()
|
||||||
|
|
||||||
|
def change_passwords(self, users):
|
||||||
|
"""Make an asynchronous call to change the passwords of one or more
|
||||||
|
users."""
|
||||||
|
LOG.debug(_("Changing passwords for users on Instance %s"), self.id)
|
||||||
|
self._cast("change_passwords", users=users)
|
||||||
|
|
||||||
def create_user(self, users):
|
def create_user(self, users):
|
||||||
"""Make an asynchronous call to create a new database user"""
|
"""Make an asynchronous call to create a new database user"""
|
||||||
LOG.debug(_("Creating Users for Instance %s"), self.id)
|
LOG.debug(_("Creating Users for Instance %s"), self.id)
|
||||||
self._cast("create_user", users=users)
|
self._cast("create_user", users=users)
|
||||||
|
|
||||||
|
def get_user(self, username):
|
||||||
|
"""Make an asynchronous call to get a single database user."""
|
||||||
|
LOG.debug(_("Getting a user on Instance %s"), self.id)
|
||||||
|
LOG.debug("User name is %s" % username)
|
||||||
|
return self._call("get_user", AGENT_LOW_TIMEOUT, username=username)
|
||||||
|
|
||||||
|
def list_access(self, username):
|
||||||
|
"""Show all the databases to which a user has more than USAGE."""
|
||||||
|
LOG.debug(_("Showing user grants on Instance %s"), self.id)
|
||||||
|
LOG.debug("User name is %s" % username)
|
||||||
|
return self._call("list_access", AGENT_LOW_TIMEOUT, username=username)
|
||||||
|
|
||||||
|
def grant_access(self, username, databases):
|
||||||
|
"""Give a user permission to use a given database."""
|
||||||
|
return self._call("grant_access", AGENT_LOW_TIMEOUT,
|
||||||
|
username=username, databases=databases)
|
||||||
|
|
||||||
|
def revoke_access(self, username, database):
|
||||||
|
"""Remove a user's permission to use a given database."""
|
||||||
|
return self._call("revoke_access", AGENT_LOW_TIMEOUT,
|
||||||
|
username=username, database=database)
|
||||||
|
|
||||||
def list_users(self, limit=None, marker=None, include_marker=False):
|
def list_users(self, limit=None, marker=None, include_marker=False):
|
||||||
"""Make an asynchronous call to list database users"""
|
"""Make an asynchronous call to list database users"""
|
||||||
LOG.debug(_("Listing Users for Instance %s"), self.id)
|
LOG.debug(_("Listing Users for Instance %s"), self.id)
|
||||||
|
@ -40,8 +40,8 @@ from reddwarf import db
|
|||||||
from reddwarf.common.exception import ProcessExecutionError
|
from reddwarf.common.exception import ProcessExecutionError
|
||||||
from reddwarf.common import cfg
|
from reddwarf.common import cfg
|
||||||
from reddwarf.common import utils
|
from reddwarf.common import utils
|
||||||
|
from reddwarf.guestagent import query
|
||||||
from reddwarf.guestagent.db import models
|
from reddwarf.guestagent.db import models
|
||||||
from reddwarf.guestagent.query import Query
|
|
||||||
from reddwarf.guestagent import pkg
|
from reddwarf.guestagent import pkg
|
||||||
from reddwarf.instance import models as rd_models
|
from reddwarf.instance import models as rd_models
|
||||||
from reddwarf.openstack.common import log as logging
|
from reddwarf.openstack.common import log as logging
|
||||||
@ -50,7 +50,7 @@ from reddwarf.openstack.common.gettextutils import _
|
|||||||
|
|
||||||
ADMIN_USER_NAME = "os_admin"
|
ADMIN_USER_NAME = "os_admin"
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
FLUSH = text("""FLUSH PRIVILEGES;""")
|
FLUSH = text(query.FLUSH)
|
||||||
|
|
||||||
ENGINE = None
|
ENGINE = None
|
||||||
MYSQLD_ARGS = None
|
MYSQLD_ARGS = None
|
||||||
@ -308,57 +308,92 @@ class LocalSqlClient(object):
|
|||||||
class MySqlAdmin(object):
|
class MySqlAdmin(object):
|
||||||
"""Handles administrative tasks on the MySQL database."""
|
"""Handles administrative tasks on the MySQL database."""
|
||||||
|
|
||||||
|
def _associate_dbs(self, user):
|
||||||
|
"""Internal. Given a MySQLUser, populate its databases attribute."""
|
||||||
|
LOG.debug("Associating dbs to user %s" % user.name)
|
||||||
|
with LocalSqlClient(get_engine()) as client:
|
||||||
|
q = query.Query()
|
||||||
|
q.columns = ["grantee", "table_schema"]
|
||||||
|
q.tables = ["information_schema.SCHEMA_PRIVILEGES"]
|
||||||
|
q.group = ["grantee", "table_schema"]
|
||||||
|
q.where = ["privilege_type != 'USAGE'"]
|
||||||
|
t = text(str(q))
|
||||||
|
db_result = client.execute(t)
|
||||||
|
for db in db_result:
|
||||||
|
LOG.debug("\t db: %s" % db)
|
||||||
|
if db['grantee'] == "'%s'@'%%'" % (user.name):
|
||||||
|
mysql_db = models.MySQLDatabase()
|
||||||
|
mysql_db.name = db['table_schema']
|
||||||
|
user.databases.append(mysql_db.serialize())
|
||||||
|
|
||||||
|
def change_passwords(self, users):
|
||||||
|
"""Change the passwords of one or more existing users."""
|
||||||
|
LOG.debug("Changing the password of some users.""")
|
||||||
|
LOG.debug("Users is %s" % users)
|
||||||
|
with LocalSqlClient(get_engine()) as client:
|
||||||
|
for item in users:
|
||||||
|
LOG.debug("\tUser: %s" % item)
|
||||||
|
user_dict = {'_name': item['name'],
|
||||||
|
'_password': item['password'],
|
||||||
|
}
|
||||||
|
user = models.MySQLUser()
|
||||||
|
user.deserialize(user_dict)
|
||||||
|
LOG.debug("\tDeserialized: %s" % user.__dict__)
|
||||||
|
uu = query.UpdateUser(user.name, clear=user.password)
|
||||||
|
t = text(str(uu))
|
||||||
|
client.execute(t)
|
||||||
|
|
||||||
def create_database(self, databases):
|
def create_database(self, databases):
|
||||||
"""Create the list of specified databases"""
|
"""Create the list of specified databases"""
|
||||||
client = LocalSqlClient(get_engine())
|
with LocalSqlClient(get_engine()) as client:
|
||||||
with client:
|
|
||||||
for item in databases:
|
for item in databases:
|
||||||
mydb = models.MySQLDatabase()
|
mydb = models.MySQLDatabase()
|
||||||
mydb.deserialize(item)
|
mydb.deserialize(item)
|
||||||
t = text("""CREATE DATABASE IF NOT EXISTS
|
cd = query.CreateDatabase(mydb.name,
|
||||||
`%s` CHARACTER SET = %s COLLATE = %s;"""
|
mydb.character_set,
|
||||||
% (mydb.name, mydb.character_set, mydb.collate))
|
mydb.collate)
|
||||||
|
t = text(str(cd))
|
||||||
client.execute(t)
|
client.execute(t)
|
||||||
|
|
||||||
def create_user(self, users):
|
def create_user(self, users):
|
||||||
"""Create users and grant them privileges for the
|
"""Create users and grant them privileges for the
|
||||||
specified databases"""
|
specified databases"""
|
||||||
host = "%"
|
host = "%"
|
||||||
client = LocalSqlClient(get_engine())
|
with LocalSqlClient(get_engine()) as client:
|
||||||
with client:
|
|
||||||
for item in users:
|
for item in users:
|
||||||
user = models.MySQLUser()
|
user = models.MySQLUser()
|
||||||
user.deserialize(item)
|
user.deserialize(item)
|
||||||
# TODO(cp16net):Should users be allowed to create users
|
# TODO(cp16net):Should users be allowed to create users
|
||||||
# 'os_admin' or 'debian-sys-maint'
|
# 'os_admin' or 'debian-sys-maint'
|
||||||
t = text("""GRANT USAGE ON *.* TO '%s'@\"%s\"
|
g = query.Grant(user=user.name, host=host,
|
||||||
IDENTIFIED BY '%s';"""
|
clear=user.password)
|
||||||
% (user.name, host, user.password))
|
t = text(str(g))
|
||||||
client.execute(t)
|
client.execute(t)
|
||||||
for database in user.databases:
|
for database in user.databases:
|
||||||
mydb = models.MySQLDatabase()
|
mydb = models.MySQLDatabase()
|
||||||
mydb.deserialize(database)
|
mydb.deserialize(database)
|
||||||
t = text("""
|
g = query.Grant(permissions='ALL', database=mydb.name,
|
||||||
GRANT ALL PRIVILEGES ON `%s`.* TO `%s`@:host;
|
user=user.name, host=host,
|
||||||
""" % (mydb.name, user.name))
|
clear=user.password)
|
||||||
client.execute(t, host=host)
|
t = text(str(g))
|
||||||
|
client.execute(t)
|
||||||
|
|
||||||
def delete_database(self, database):
|
def delete_database(self, database):
|
||||||
"""Delete the specified database"""
|
"""Delete the specified database"""
|
||||||
client = LocalSqlClient(get_engine())
|
with LocalSqlClient(get_engine()) as client:
|
||||||
with client:
|
|
||||||
mydb = models.MySQLDatabase()
|
mydb = models.MySQLDatabase()
|
||||||
mydb.deserialize(database)
|
mydb.deserialize(database)
|
||||||
t = text("""DROP DATABASE `%s`;""" % mydb.name)
|
dd = query.DropDatabase(mydb.name)
|
||||||
|
t = text(str(dd))
|
||||||
client.execute(t)
|
client.execute(t)
|
||||||
|
|
||||||
def delete_user(self, user):
|
def delete_user(self, user):
|
||||||
"""Delete the specified users"""
|
"""Delete the specified users"""
|
||||||
client = LocalSqlClient(get_engine())
|
with LocalSqlClient(get_engine()) as client:
|
||||||
with client:
|
|
||||||
mysql_user = models.MySQLUser()
|
mysql_user = models.MySQLUser()
|
||||||
mysql_user.deserialize(user)
|
mysql_user.deserialize(user)
|
||||||
t = text("""DROP USER `%s`""" % mysql_user.name)
|
du = query.DropUser(mysql_user.name)
|
||||||
|
t = text(str(du))
|
||||||
client.execute(t)
|
client.execute(t)
|
||||||
|
|
||||||
def enable_root(self):
|
def enable_root(self):
|
||||||
@ -367,31 +402,68 @@ class MySqlAdmin(object):
|
|||||||
user = models.MySQLUser()
|
user = models.MySQLUser()
|
||||||
user.name = "root"
|
user.name = "root"
|
||||||
user.password = generate_random_password()
|
user.password = generate_random_password()
|
||||||
client = LocalSqlClient(get_engine())
|
with LocalSqlClient(get_engine()) as client:
|
||||||
with client:
|
|
||||||
try:
|
try:
|
||||||
t = text("""CREATE USER :user@:host;""")
|
cu = query.CreateUser(user.name, host=host)
|
||||||
client.execute(t, user=user.name, host=host, pwd=user.password)
|
t = text(str(cu))
|
||||||
|
client.execute(t, **cu.keyArgs)
|
||||||
except exc.OperationalError as err:
|
except exc.OperationalError as err:
|
||||||
# Ignore, user is already created, just reset the password
|
# Ignore, user is already created, just reset the password
|
||||||
# TODO(rnirmal): More fine grained error checking later on
|
# TODO(rnirmal): More fine grained error checking later on
|
||||||
LOG.debug(err)
|
LOG.debug(err)
|
||||||
with client:
|
with LocalSqlClient(get_engine()) as client:
|
||||||
t = text("""UPDATE mysql.user SET Password=PASSWORD(:pwd)
|
uu = query.UpdateUser(user.name, host=host,
|
||||||
WHERE User=:user;""")
|
clear=user.password)
|
||||||
client.execute(t, user=user.name, pwd=user.password)
|
t = text(str(uu))
|
||||||
t = text("""GRANT ALL PRIVILEGES ON *.* TO :user@:host
|
client.execute(t)
|
||||||
WITH GRANT OPTION;""")
|
g = query.Grant(permissions="ALL", user=user.name, host=host,
|
||||||
client.execute(t, user=user.name, host=host)
|
grant_option=True, clear=user.password)
|
||||||
|
t = text(str(g))
|
||||||
|
client.execute(t)
|
||||||
return user.serialize()
|
return user.serialize()
|
||||||
|
|
||||||
|
def get_user(self, username):
|
||||||
|
user = self._get_user(username)
|
||||||
|
if not user:
|
||||||
|
return None
|
||||||
|
return user.serialize()
|
||||||
|
|
||||||
|
def _get_user(self, username):
|
||||||
|
"""Return a single user matching the criteria"""
|
||||||
|
user = models.MySQLUser()
|
||||||
|
user.name = username
|
||||||
|
with LocalSqlClient(get_engine()) as client:
|
||||||
|
q = query.Query()
|
||||||
|
q.columns = ['User', 'Password']
|
||||||
|
q.tables = ['mysql.user']
|
||||||
|
q.where = ["Host != 'localhost'",
|
||||||
|
"User = '%s'" % username,
|
||||||
|
]
|
||||||
|
q.order = ['User']
|
||||||
|
t = text(str(q))
|
||||||
|
result = client.execute(t).fetchall()
|
||||||
|
LOG.debug("Result: %s" % result)
|
||||||
|
if len(result) != 1:
|
||||||
|
return None
|
||||||
|
found_user = result[0]
|
||||||
|
user.password = found_user['Password']
|
||||||
|
self._associate_dbs(user)
|
||||||
|
return user
|
||||||
|
|
||||||
|
def grant_access(self, username, databases):
|
||||||
|
"""Give a user permission to use a given database."""
|
||||||
|
user = self._get_user(username)
|
||||||
|
with LocalSqlClient(get_engine()) as client:
|
||||||
|
for database in databases:
|
||||||
|
g = query.Grant(permissions='ALL', database=database,
|
||||||
|
user=user.name, host='%', hashed=user.password)
|
||||||
|
t = text(str(g))
|
||||||
|
client.execute(t)
|
||||||
|
|
||||||
def is_root_enabled(self):
|
def is_root_enabled(self):
|
||||||
"""Return True if root access is enabled; False otherwise."""
|
"""Return True if root access is enabled; False otherwise."""
|
||||||
client = LocalSqlClient(get_engine())
|
with LocalSqlClient(get_engine()) as client:
|
||||||
with client:
|
t = text(query.ROOT_ENABLED)
|
||||||
mysql_user = models.MySQLUser()
|
|
||||||
t = text("""SELECT User FROM mysql.user where User = 'root'
|
|
||||||
and host != 'localhost';""")
|
|
||||||
result = client.execute(t)
|
result = client.execute(t)
|
||||||
LOG.debug("result = " + str(result))
|
LOG.debug("result = " + str(result))
|
||||||
return result.rowcount != 0
|
return result.rowcount != 0
|
||||||
@ -400,23 +472,22 @@ class MySqlAdmin(object):
|
|||||||
"""List databases the user created on this mysql instance"""
|
"""List databases the user created on this mysql instance"""
|
||||||
LOG.debug(_("---Listing Databases---"))
|
LOG.debug(_("---Listing Databases---"))
|
||||||
databases = []
|
databases = []
|
||||||
client = LocalSqlClient(get_engine())
|
with LocalSqlClient(get_engine()) as client:
|
||||||
with client:
|
|
||||||
# If you have an external volume mounted at /var/lib/mysql
|
# If you have an external volume mounted at /var/lib/mysql
|
||||||
# the lost+found directory will show up in mysql as a database
|
# the lost+found directory will show up in mysql as a database
|
||||||
# which will create errors if you try to do any database ops
|
# which will create errors if you try to do any database ops
|
||||||
# on it. So we remove it here if it exists.
|
# on it. So we remove it here if it exists.
|
||||||
q = Query()
|
q = query.Query()
|
||||||
q.columns = [
|
q.columns = [
|
||||||
'schema_name as name',
|
'schema_name as name',
|
||||||
'default_character_set_name as charset',
|
'default_character_set_name as charset',
|
||||||
'default_collation_name as collation',
|
'default_collation_name as collation',
|
||||||
]
|
]
|
||||||
q.tables = ['information_schema.schemata']
|
q.tables = ['information_schema.schemata']
|
||||||
q.where = ['''schema_name not in (
|
q.where = ["schema_name NOT IN ("
|
||||||
'mysql', 'information_schema',
|
"'mysql', 'information_schema', "
|
||||||
'lost+found', '#mysql50#lost+found'
|
"'lost+found', '#mysql50#lost+found'"
|
||||||
)''']
|
")"]
|
||||||
q.order = ['schema_name ASC']
|
q.order = ['schema_name ASC']
|
||||||
if limit:
|
if limit:
|
||||||
q.limit = limit + 1
|
q.limit = limit + 1
|
||||||
@ -447,10 +518,9 @@ class MySqlAdmin(object):
|
|||||||
"""List users that have access to the database"""
|
"""List users that have access to the database"""
|
||||||
LOG.debug(_("---Listing Users---"))
|
LOG.debug(_("---Listing Users---"))
|
||||||
users = []
|
users = []
|
||||||
client = LocalSqlClient(get_engine())
|
with LocalSqlClient(get_engine()) as client:
|
||||||
with client:
|
|
||||||
mysql_user = models.MySQLUser()
|
mysql_user = models.MySQLUser()
|
||||||
q = Query()
|
q = query.Query()
|
||||||
q.columns = ['User']
|
q.columns = ['User']
|
||||||
q.tables = ['mysql.user']
|
q.tables = ['mysql.user']
|
||||||
q.where = ["host != 'localhost'"]
|
q.where = ["host != 'localhost'"]
|
||||||
@ -471,21 +541,8 @@ class MySqlAdmin(object):
|
|||||||
LOG.debug("user = " + str(row))
|
LOG.debug("user = " + str(row))
|
||||||
mysql_user = models.MySQLUser()
|
mysql_user = models.MySQLUser()
|
||||||
mysql_user.name = row['User']
|
mysql_user.name = row['User']
|
||||||
|
self._associate_dbs(mysql_user)
|
||||||
next_marker = row['User']
|
next_marker = row['User']
|
||||||
# Now get the databases
|
|
||||||
q = Query()
|
|
||||||
q.columns = ['grantee', 'table_schema']
|
|
||||||
q.tables = ['information_schema.SCHEMA_PRIVILEGES']
|
|
||||||
q.group = ['grantee', 'table_schema']
|
|
||||||
t = text(str(q))
|
|
||||||
db_result = client.execute(t)
|
|
||||||
for db in db_result:
|
|
||||||
matches = re.match("^'(.+)'@", db['grantee'])
|
|
||||||
if (matches is not None and
|
|
||||||
matches.group(1) == mysql_user.name):
|
|
||||||
mysql_db = models.MySQLDatabase()
|
|
||||||
mysql_db.name = db['table_schema']
|
|
||||||
mysql_user.databases.append(mysql_db.serialize())
|
|
||||||
users.append(mysql_user.serialize())
|
users.append(mysql_user.serialize())
|
||||||
if result.rowcount <= limit:
|
if result.rowcount <= limit:
|
||||||
next_marker = None
|
next_marker = None
|
||||||
@ -493,6 +550,21 @@ class MySqlAdmin(object):
|
|||||||
|
|
||||||
return users, next_marker
|
return users, next_marker
|
||||||
|
|
||||||
|
def revoke_access(self, username, database):
|
||||||
|
"""Give a user permission to use a given database."""
|
||||||
|
user = self._get_user(username)
|
||||||
|
with LocalSqlClient(get_engine()) as client:
|
||||||
|
r = query.Revoke(database=database, user=user.name, host='%',
|
||||||
|
hashed=user.password)
|
||||||
|
t = text(str(r))
|
||||||
|
client.execute(t)
|
||||||
|
|
||||||
|
def list_access(self, username):
|
||||||
|
"""Show all the databases to which the user has more than
|
||||||
|
USAGE granted."""
|
||||||
|
user = self._get_user(username)
|
||||||
|
return user.databases
|
||||||
|
|
||||||
|
|
||||||
class KeepAliveConnection(interfaces.PoolListener):
|
class KeepAliveConnection(interfaces.PoolListener):
|
||||||
"""
|
"""
|
||||||
@ -531,25 +603,26 @@ class MySqlApp(object):
|
|||||||
Create a os_admin user with a random password
|
Create a os_admin user with a random password
|
||||||
with all privileges similar to the root user
|
with all privileges similar to the root user
|
||||||
"""
|
"""
|
||||||
t = text("CREATE USER :user@'localhost';")
|
localhost = "localhost"
|
||||||
client.execute(t, user=ADMIN_USER_NAME)
|
cu = query.CreateUser(ADMIN_USER_NAME, host=localhost)
|
||||||
t = text("""
|
t = text(str(cu))
|
||||||
UPDATE mysql.user SET Password=PASSWORD(:pwd)
|
client.execute(t, **cu.keyArgs)
|
||||||
WHERE User=:user;
|
uu = query.UpdateUser(ADMIN_USER_NAME, host=localhost, clear=password)
|
||||||
""")
|
t = text(str(uu))
|
||||||
client.execute(t, pwd=password, user=ADMIN_USER_NAME)
|
client.execute(t)
|
||||||
t = text("""
|
g = query.Grant(permissions='ALL', user=ADMIN_USER_NAME,
|
||||||
GRANT ALL PRIVILEGES ON *.* TO :user@'localhost'
|
host=localhost, grant_option=True, clear=password)
|
||||||
WITH GRANT OPTION;
|
t = text(str(g))
|
||||||
""")
|
client.execute(t)
|
||||||
client.execute(t, user=ADMIN_USER_NAME)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _generate_root_password(client):
|
def _generate_root_password(client):
|
||||||
""" Generate and set a random root password and forget about it. """
|
""" Generate and set a random root password and forget about it. """
|
||||||
t = text("""UPDATE mysql.user SET Password=PASSWORD(:pwd)
|
localhost = "localhost"
|
||||||
WHERE User='root';""")
|
uu = query.UpdateUser("root", host=localhost,
|
||||||
client.execute(t, pwd=generate_random_password())
|
clear=generate_random_password())
|
||||||
|
t = text(str(uu))
|
||||||
|
client.execute(t)
|
||||||
|
|
||||||
def install_and_secure(self, memory_mb):
|
def install_and_secure(self, memory_mb):
|
||||||
"""Prepare the guest machine with a secure mysql server installation"""
|
"""Prepare the guest machine with a secure mysql server installation"""
|
||||||
@ -562,8 +635,7 @@ class MySqlApp(object):
|
|||||||
admin_password = generate_random_password()
|
admin_password = generate_random_password()
|
||||||
|
|
||||||
engine = create_engine("mysql://root:@localhost:3306", echo=True)
|
engine = create_engine("mysql://root:@localhost:3306", echo=True)
|
||||||
client = LocalSqlClient(engine)
|
with LocalSqlClient(engine) as client:
|
||||||
with client:
|
|
||||||
self._generate_root_password(client)
|
self._generate_root_password(client)
|
||||||
self._remove_anonymous_user(client)
|
self._remove_anonymous_user(client)
|
||||||
self._remove_remote_root_access(client)
|
self._remove_remote_root_access(client)
|
||||||
@ -618,13 +690,11 @@ class MySqlApp(object):
|
|||||||
raise RuntimeError("Could not stop MySQL!")
|
raise RuntimeError("Could not stop MySQL!")
|
||||||
|
|
||||||
def _remove_anonymous_user(self, client):
|
def _remove_anonymous_user(self, client):
|
||||||
t = text("""DELETE FROM mysql.user WHERE User='';""")
|
t = text(query.REMOVE_ANON)
|
||||||
client.execute(t)
|
client.execute(t)
|
||||||
|
|
||||||
def _remove_remote_root_access(self, client):
|
def _remove_remote_root_access(self, client):
|
||||||
t = text("""DELETE FROM mysql.user
|
t = text(query.REMOVE_ROOT)
|
||||||
WHERE User='root'
|
|
||||||
AND Host!='localhost';""")
|
|
||||||
client.execute(t)
|
client.execute(t)
|
||||||
|
|
||||||
def restart(self):
|
def restart(self):
|
||||||
|
@ -15,6 +15,9 @@ class Manager(periodic_task.PeriodicTasks):
|
|||||||
"""Update the status of the MySQL service"""
|
"""Update the status of the MySQL service"""
|
||||||
dbaas.MySqlAppStatus.get().update()
|
dbaas.MySqlAppStatus.get().update()
|
||||||
|
|
||||||
|
def change_passwords(self, context, users):
|
||||||
|
return dbaas.MySqlAdmin().change_passwords(users)
|
||||||
|
|
||||||
def create_database(self, context, databases):
|
def create_database(self, context, databases):
|
||||||
return dbaas.MySqlAdmin().create_database(databases)
|
return dbaas.MySqlAdmin().create_database(databases)
|
||||||
|
|
||||||
@ -27,6 +30,18 @@ class Manager(periodic_task.PeriodicTasks):
|
|||||||
def delete_user(self, context, user):
|
def delete_user(self, context, user):
|
||||||
dbaas.MySqlAdmin().delete_user(user)
|
dbaas.MySqlAdmin().delete_user(user)
|
||||||
|
|
||||||
|
def get_user(self, context, username):
|
||||||
|
return dbaas.MySqlAdmin().get_user(username)
|
||||||
|
|
||||||
|
def grant_access(self, context, username, databases):
|
||||||
|
return dbaas.MySqlAdmin().grant_access(username, databases)
|
||||||
|
|
||||||
|
def revoke_access(self, context, username, database):
|
||||||
|
return dbaas.MySqlAdmin().revoke_access(username, database)
|
||||||
|
|
||||||
|
def list_access(self, context, username):
|
||||||
|
return dbaas.MySqlAdmin().list_access(username)
|
||||||
|
|
||||||
def list_databases(self, context, limit=None, marker=None,
|
def list_databases(self, context, limit=None, marker=None,
|
||||||
include_marker=False):
|
include_marker=False):
|
||||||
return dbaas.MySqlAdmin().list_databases(limit, marker,
|
return dbaas.MySqlAdmin().list_databases(limit, marker,
|
||||||
|
@ -18,6 +18,8 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
Intermediary class for building SQL queries for use by the guest agent.
|
Intermediary class for building SQL queries for use by the guest agent.
|
||||||
|
Do not hard-code strings into the guest agent; use this module to build
|
||||||
|
them for you.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -33,15 +35,18 @@ class Query(object):
|
|||||||
self.group = group or []
|
self.group = group or []
|
||||||
self.limit = limit
|
self.limit = limit
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return str(self)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _columns(self):
|
def _columns(self):
|
||||||
if not self.columns:
|
if not self.columns:
|
||||||
return "SELECT *"
|
return "SELECT *"
|
||||||
return "SELECT %s" % (', '.join(self.columns))
|
return "SELECT %s" % (", ".join(self.columns))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _tables(self):
|
def _tables(self):
|
||||||
return "FROM %s" % (', '.join(self.tables))
|
return "FROM %s" % (", ".join(self.tables))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _where(self):
|
def _where(self):
|
||||||
@ -52,19 +57,19 @@ class Query(object):
|
|||||||
@property
|
@property
|
||||||
def _order(self):
|
def _order(self):
|
||||||
if not self.order:
|
if not self.order:
|
||||||
return ''
|
return ""
|
||||||
return "ORDER BY %s" % (', '.join(self.order))
|
return "ORDER BY %s" % (", ".join(self.order))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _group_by(self):
|
def _group_by(self):
|
||||||
if not self.group:
|
if not self.group:
|
||||||
return ''
|
return ""
|
||||||
return "GROUP BY %s" % (', '.join(self.group))
|
return "GROUP BY %s" % (", ".join(self.group))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _limit(self):
|
def _limit(self):
|
||||||
if not self.limit:
|
if not self.limit:
|
||||||
return ''
|
return ""
|
||||||
return "LIMIT %s" % str(self.limit)
|
return "LIMIT %s" % str(self.limit)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -76,7 +81,323 @@ class Query(object):
|
|||||||
self._group_by,
|
self._group_by,
|
||||||
self._limit,
|
self._limit,
|
||||||
]
|
]
|
||||||
return '\n'.join(query)
|
return " ".join(query) + ";"
|
||||||
|
|
||||||
|
|
||||||
|
class Grant(object):
|
||||||
|
|
||||||
|
PERMISSIONS = ["ALL",
|
||||||
|
"ALL PRIVILEGES",
|
||||||
|
"ALTER ROUTINE",
|
||||||
|
"ALTER",
|
||||||
|
"CREATE ROUTINE",
|
||||||
|
"CREATE TEMPORARY TABLES",
|
||||||
|
"CREATE USER",
|
||||||
|
"CREATE VIEW",
|
||||||
|
"CREATE",
|
||||||
|
"DELETE",
|
||||||
|
"DROP",
|
||||||
|
"EVENT",
|
||||||
|
"EXECUTE",
|
||||||
|
"FILE",
|
||||||
|
"INDEX",
|
||||||
|
"INSERT",
|
||||||
|
"LOCK TABLES",
|
||||||
|
"PROCESS",
|
||||||
|
"REFERENCES",
|
||||||
|
"RELOAD",
|
||||||
|
"REPLICATION CLIENT",
|
||||||
|
"REPLICATION SLAVE",
|
||||||
|
"SELECT",
|
||||||
|
"SHOW DATABASES",
|
||||||
|
"SHOW VIEW",
|
||||||
|
"SHUTDOWN",
|
||||||
|
"SUPER",
|
||||||
|
"TRIGGER",
|
||||||
|
"UPDATE",
|
||||||
|
"USAGE",
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self, permissions=None, database=None, table=None, user=None,
|
||||||
|
host=None, clear=None, hashed=None, grant_option=True):
|
||||||
|
self.permissions = permissions or []
|
||||||
|
self.database = database
|
||||||
|
self.table = table
|
||||||
|
self.user = user
|
||||||
|
self.host = host
|
||||||
|
self.clear = clear
|
||||||
|
self.hashed = hashed
|
||||||
|
self.grant_option = grant_option
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return str(self)
|
return str(self)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _permissions(self):
|
||||||
|
if not self.permissions:
|
||||||
|
return "USAGE"
|
||||||
|
if "ALL" in self.permissions:
|
||||||
|
return "ALL PRIVILEGES"
|
||||||
|
if "ALL PRIVILEGES" in self.permissions:
|
||||||
|
return "ALL PRIVILEGES"
|
||||||
|
filtered = [perm for perm in set(self.permissions)
|
||||||
|
if perm in self.PERMISSIONS]
|
||||||
|
return ", ".join(sorted(filtered))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _database(self):
|
||||||
|
if not self.database:
|
||||||
|
return "*"
|
||||||
|
return "`%s`" % self.database
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _table(self):
|
||||||
|
if self.table:
|
||||||
|
return "'%s'" % self.table
|
||||||
|
return "*"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _user(self):
|
||||||
|
return self.user or ""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _identity(self):
|
||||||
|
if self.clear:
|
||||||
|
return "IDENTIFIED BY '%s'" % self.clear
|
||||||
|
if self.hashed:
|
||||||
|
return "IDENTIFIED BY PASSWORD '%s'" % self.hashed
|
||||||
|
return ""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _host(self):
|
||||||
|
return self.host or "%"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _user_host(self):
|
||||||
|
return "`%s`@`%s`" % (self._user, self._host)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _what(self):
|
||||||
|
# Permissions to be granted to the user.
|
||||||
|
return "GRANT %s" % self._permissions
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _where(self):
|
||||||
|
# Database and table to which the user is granted permissions.
|
||||||
|
return "ON %s.%s" % (self._database, self._table)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _whom(self):
|
||||||
|
# User and host to be granted permission. Optionally, password, too.
|
||||||
|
whom = [("TO %s" % self._user_host),
|
||||||
|
self._identity,
|
||||||
|
]
|
||||||
|
return " ".join(whom)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _with(self):
|
||||||
|
clauses = []
|
||||||
|
|
||||||
|
if self.grant_option:
|
||||||
|
clauses.append("GRANT OPTION")
|
||||||
|
|
||||||
|
if not clauses:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
return "WITH %s" % ", ".join(clauses)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
query = [self._what,
|
||||||
|
self._where,
|
||||||
|
self._whom,
|
||||||
|
self._with,
|
||||||
|
]
|
||||||
|
return " ".join(query) + ";"
|
||||||
|
|
||||||
|
|
||||||
|
class Revoke(Grant):
|
||||||
|
|
||||||
|
def __init__(self, permissions=None, database=None, table=None, user=None,
|
||||||
|
host=None, clear=None, hashed=None):
|
||||||
|
self.permissions = permissions or []
|
||||||
|
self.database = database
|
||||||
|
self.table = table
|
||||||
|
self.user = user
|
||||||
|
self.host = host
|
||||||
|
self.clear = clear
|
||||||
|
self.hashed = hashed
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
query = [self._what,
|
||||||
|
self._where,
|
||||||
|
self._whom,
|
||||||
|
]
|
||||||
|
return " ".join(query) + ";"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _permissions(self):
|
||||||
|
if not self.permissions:
|
||||||
|
return "ALL"
|
||||||
|
if "ALL" in self.permissions:
|
||||||
|
return "ALL"
|
||||||
|
if "ALL PRIVILEGES" in self.permissions:
|
||||||
|
return "ALL"
|
||||||
|
filtered = [perm for perm in self.permissions
|
||||||
|
if perm in self.PERMISSIONS]
|
||||||
|
return ", ".join(sorted(filtered))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _what(self):
|
||||||
|
# Permissions to be revoked from the user.
|
||||||
|
return "REVOKE %s" % self._permissions
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _whom(self):
|
||||||
|
# User and host from whom to revoke permission.
|
||||||
|
# Optionally, password, too.
|
||||||
|
whom = [("FROM %s" % self._user_host),
|
||||||
|
self._identity,
|
||||||
|
]
|
||||||
|
return " ".join(whom)
|
||||||
|
|
||||||
|
|
||||||
|
class CreateDatabase(object):
|
||||||
|
|
||||||
|
def __init__(self, database, charset=None, collate=None):
|
||||||
|
self.database = database
|
||||||
|
self.charset = charset
|
||||||
|
self.collate = collate
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _charset(self):
|
||||||
|
if not self.charset:
|
||||||
|
return ""
|
||||||
|
return "CHARACTER SET = '%s'" % self.charset
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _collate(self):
|
||||||
|
if not self.collate:
|
||||||
|
return ""
|
||||||
|
return "COLLATE = '%s'" % self.collate
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
query = [("CREATE DATABASE IF NOT EXISTS `%s`" % self.database),
|
||||||
|
self._charset,
|
||||||
|
self._collate,
|
||||||
|
]
|
||||||
|
return " ".join(query) + ";"
|
||||||
|
|
||||||
|
|
||||||
|
class DropDatabase(object):
|
||||||
|
|
||||||
|
def __init__(self, database):
|
||||||
|
self.database = database
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "DROP DATABASE `%s`;" % self.database
|
||||||
|
|
||||||
|
|
||||||
|
class CreateUser(object):
|
||||||
|
|
||||||
|
def __init__(self, user, host=None, clear=None, hashed=None):
|
||||||
|
self.user = user
|
||||||
|
self.host = host
|
||||||
|
self.clear = clear # A clear password
|
||||||
|
self.hashed = hashed # A hashed password
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def keyArgs(self):
|
||||||
|
return {'user': self.user,
|
||||||
|
'host': self._host,
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _host(self):
|
||||||
|
if not self.host:
|
||||||
|
return "%"
|
||||||
|
return self.host
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _identity(self):
|
||||||
|
if self.clear:
|
||||||
|
return "IDENTIFIED BY '%s'" % self.clear
|
||||||
|
if self.hashed:
|
||||||
|
return "IDENTIFIED BY PASSWORD '%s'" % self.hashed
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
#query = [("CREATE USER '%s'@'%s'" % (self.user, self._host)),
|
||||||
|
query = ["CREATE USER :user@:host"]
|
||||||
|
if self._identity:
|
||||||
|
query.append(self._identity)
|
||||||
|
return " ".join(query) + ";"
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateUser(object):
|
||||||
|
|
||||||
|
def __init__(self, user, host=None, clear=None):
|
||||||
|
self.user = user
|
||||||
|
self.host = host
|
||||||
|
self.clear = clear
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _set_password(self):
|
||||||
|
return "SET Password=PASSWORD('%s')" % self.clear
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _host(self):
|
||||||
|
if not self.host:
|
||||||
|
return "%"
|
||||||
|
return self.host
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _where(self):
|
||||||
|
clauses = []
|
||||||
|
if self.user:
|
||||||
|
clauses.append("User = '%s'" % self.user)
|
||||||
|
if self.host:
|
||||||
|
clauses.append("Host = '%s'" % self._host)
|
||||||
|
if not clauses:
|
||||||
|
return ""
|
||||||
|
return "WHERE %s" % " AND ".join(clauses)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
query = ["UPDATE mysql.user",
|
||||||
|
self._set_password,
|
||||||
|
self._where,
|
||||||
|
]
|
||||||
|
return " ".join(query) + ";"
|
||||||
|
|
||||||
|
|
||||||
|
class DropUser(object):
|
||||||
|
|
||||||
|
def __init__(self, user):
|
||||||
|
self.user = user
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "DROP USER `%s`;" % self.user
|
||||||
|
|
||||||
|
|
||||||
|
### Miscellaneous queries that need no parameters.
|
||||||
|
|
||||||
|
FLUSH = "FLUSH PRIVILEGES;"
|
||||||
|
ROOT_ENABLED = ("SELECT User FROM mysql.user "
|
||||||
|
"WHERE User = 'root' AND host != 'localhost';")
|
||||||
|
REMOVE_ANON = "DELETE FROM mysql.user WHERE User = '';"
|
||||||
|
REMOVE_ROOT = ("DELETE FROM mysql.user "
|
||||||
|
"WHERE User = 'root' AND Host != 'localhost';")
|
||||||
|
@ -50,7 +50,7 @@ class API(ManagerAPI):
|
|||||||
type_, value, tb = sys.exc_info()
|
type_, value, tb = sys.exc_info()
|
||||||
LOG.error("Error running async task:")
|
LOG.error("Error running async task:")
|
||||||
LOG.error((traceback.format_exception(type_, value, tb)))
|
LOG.error((traceback.format_exception(type_, value, tb)))
|
||||||
raise type_, value, tb
|
raise type_(*value.args), None, tb
|
||||||
|
|
||||||
get_event_spawer()(0, func)
|
get_event_spawer()(0, func)
|
||||||
|
|
||||||
|
199
reddwarf/tests/api/user_access.py
Normal file
199
reddwarf/tests/api/user_access.py
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
# Copyright 2013 OpenStack LLC
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import time
|
||||||
|
import re
|
||||||
|
|
||||||
|
from reddwarfclient import exceptions
|
||||||
|
|
||||||
|
from proboscis import after_class
|
||||||
|
from proboscis import before_class
|
||||||
|
from proboscis import test
|
||||||
|
from proboscis.asserts import *
|
||||||
|
|
||||||
|
from reddwarf import tests
|
||||||
|
from reddwarf.tests.api.instances import instance_info
|
||||||
|
from reddwarf.tests import util
|
||||||
|
from reddwarf.tests.util import test_config
|
||||||
|
from reddwarf.tests.api.users import TestUsers
|
||||||
|
|
||||||
|
GROUP = "dbaas.api.useraccess"
|
||||||
|
|
||||||
|
|
||||||
|
@test(depends_on_classes=[TestUsers],
|
||||||
|
groups=[tests.DBAAS_API, GROUP, tests.INSTANCES],
|
||||||
|
runs_after=[TestUsers])
|
||||||
|
class TestUserAccess(object):
|
||||||
|
"""
|
||||||
|
Test the creation and deletion of user grants.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@before_class
|
||||||
|
def setUp(self):
|
||||||
|
self.dbaas = util.create_dbaas_client(instance_info.user)
|
||||||
|
self.users = ["test_access_user"]
|
||||||
|
self.databases = [("test_access_db%02i" % i) for i in range(4)]
|
||||||
|
# None of the ghosts are real databases or users.
|
||||||
|
self.ghostdbs = ["test_user_access_ghost_db"]
|
||||||
|
self.ghostusers = ["test_ghostuser"]
|
||||||
|
self.revokedbs = self.databases[:1]
|
||||||
|
self.remainingdbs = self.databases[1:]
|
||||||
|
|
||||||
|
def _test_access(self, expecteddbs):
|
||||||
|
for user in self.users:
|
||||||
|
access = self.dbaas.users.list_access(instance_info.id, user)
|
||||||
|
assert_equal(200, self.dbaas.last_http_code)
|
||||||
|
access = [db.name for db in access]
|
||||||
|
assert_equal(set(access), set(expecteddbs))
|
||||||
|
|
||||||
|
def _grant_access(self, databases):
|
||||||
|
for user in self.users:
|
||||||
|
self.dbaas.users.grant(instance_info.id, user, databases)
|
||||||
|
assert_equal(202, self.dbaas.last_http_code)
|
||||||
|
|
||||||
|
def _revoke_access(self, databases):
|
||||||
|
for user in self.users:
|
||||||
|
for database in databases:
|
||||||
|
self.dbaas.users.revoke(instance_info.id, user, database)
|
||||||
|
assert_true(self.dbaas.last_http_code in [202, 404])
|
||||||
|
|
||||||
|
def _reset_access(self):
|
||||||
|
for user in self.users:
|
||||||
|
for database in self.databases + self.ghostdbs:
|
||||||
|
try:
|
||||||
|
self.dbaas.users.revoke(instance_info.id, user, database)
|
||||||
|
assert_true(self.dbaas.last_http_code in [202, 404])
|
||||||
|
except exceptions.NotFound as nf:
|
||||||
|
# This is all right here, since we're resetting.
|
||||||
|
pass
|
||||||
|
self._test_access([])
|
||||||
|
|
||||||
|
def _ensure_nothing_else_created(self):
|
||||||
|
# Make sure grants and revokes do not create users or databases.
|
||||||
|
databases = self.dbaas.databases.list(instance_info.id)
|
||||||
|
database_names = [db.name for db in databases]
|
||||||
|
for ghost in self.ghostdbs:
|
||||||
|
assert_true(ghost not in database_names)
|
||||||
|
users = self.dbaas.users.list(instance_info.id)
|
||||||
|
user_names = [user.name for user in users]
|
||||||
|
for ghost in self.ghostusers:
|
||||||
|
assert_true(ghost not in user_names)
|
||||||
|
|
||||||
|
@test()
|
||||||
|
def test_create_user_and_dbs(self):
|
||||||
|
users = [{"name": user, "password": "password", "databases": []}
|
||||||
|
for user in self.users]
|
||||||
|
self.dbaas.users.create(instance_info.id, users)
|
||||||
|
assert_equal(202, self.dbaas.last_http_code)
|
||||||
|
|
||||||
|
databases = [{"name": db} for db in self.databases]
|
||||||
|
self.dbaas.databases.create(instance_info.id, databases)
|
||||||
|
assert_equal(202, self.dbaas.last_http_code)
|
||||||
|
|
||||||
|
@test(depends_on=[test_create_user_and_dbs])
|
||||||
|
def test_no_access(self):
|
||||||
|
# No users have any access to any database.
|
||||||
|
self._reset_access()
|
||||||
|
self._test_access([])
|
||||||
|
|
||||||
|
@test(depends_on=[test_no_access])
|
||||||
|
def test_grant_full_access(self):
|
||||||
|
# The users are granted access to all test databases.
|
||||||
|
self._reset_access()
|
||||||
|
self._grant_access(self.databases)
|
||||||
|
self._test_access(self.databases)
|
||||||
|
|
||||||
|
@test(depends_on=[test_grant_full_access])
|
||||||
|
def test_grant_idempotence(self):
|
||||||
|
# Grant operations can be repeated with no ill effects.
|
||||||
|
self._reset_access()
|
||||||
|
self._grant_access(self.databases)
|
||||||
|
self._grant_access(self.databases)
|
||||||
|
self._test_access(self.databases)
|
||||||
|
|
||||||
|
@test(depends_on=[test_grant_full_access])
|
||||||
|
def test_revoke_one_database(self):
|
||||||
|
# Revoking permission removes that database from a user's list.
|
||||||
|
self._reset_access()
|
||||||
|
self._grant_access(self.databases)
|
||||||
|
self._test_access(self.databases)
|
||||||
|
self._revoke_access(self.revokedbs)
|
||||||
|
self._test_access(self.remainingdbs)
|
||||||
|
|
||||||
|
@test(depends_on=[test_grant_full_access])
|
||||||
|
def test_revoke_non_idempotence(self):
|
||||||
|
# Revoking access cannot be repeated.
|
||||||
|
self._reset_access()
|
||||||
|
self._grant_access(self.databases)
|
||||||
|
self._revoke_access(self.revokedbs)
|
||||||
|
assert_raises(exceptions.NotFound,
|
||||||
|
self._revoke_access,
|
||||||
|
self.revokedbs)
|
||||||
|
self._test_access(self.remainingdbs)
|
||||||
|
|
||||||
|
@test(depends_on=[test_grant_full_access])
|
||||||
|
def test_revoke_all_access(self):
|
||||||
|
# Revoking access to all databases will leave their access empty.
|
||||||
|
self._reset_access()
|
||||||
|
self._grant_access(self.databases)
|
||||||
|
self._revoke_access(self.databases)
|
||||||
|
self._test_access([])
|
||||||
|
|
||||||
|
@test(depends_on=[test_grant_full_access])
|
||||||
|
def test_grant_ghostdbs(self):
|
||||||
|
# Grants to imaginary databases are acceptable, and are honored.
|
||||||
|
self._reset_access()
|
||||||
|
self._ensure_nothing_else_created()
|
||||||
|
self._grant_access(self.ghostdbs)
|
||||||
|
self._ensure_nothing_else_created()
|
||||||
|
|
||||||
|
@test(depends_on=[test_grant_full_access])
|
||||||
|
def test_revoke_ghostdbs(self):
|
||||||
|
# Revokes to imaginary databases are acceptable, and are honored.
|
||||||
|
self._reset_access()
|
||||||
|
self._ensure_nothing_else_created()
|
||||||
|
self._grant_access(self.ghostdbs)
|
||||||
|
self._revoke_access(self.ghostdbs)
|
||||||
|
self._ensure_nothing_else_created()
|
||||||
|
|
||||||
|
@test(depends_on=[test_grant_full_access])
|
||||||
|
def test_grant_ghostusers(self):
|
||||||
|
# You cannot grant permissions to imaginary users, as imaginary users
|
||||||
|
# don't have passwords we can pull from mysql.users
|
||||||
|
self._reset_access()
|
||||||
|
for user in self.ghostusers:
|
||||||
|
assert_raises(exceptions.NotFound,
|
||||||
|
self.dbaas.users.grant,
|
||||||
|
instance_info.id, user, self.databases)
|
||||||
|
assert_equal(404, self.dbaas.last_http_code)
|
||||||
|
|
||||||
|
@test(depends_on=[test_grant_full_access])
|
||||||
|
def test_revoke_ghostusers(self):
|
||||||
|
# You cannot revoke permissions from imaginary users, as imaginary
|
||||||
|
# users don't have passwords we can pull from mysql.users
|
||||||
|
self._reset_access()
|
||||||
|
for user in self.ghostusers:
|
||||||
|
for database in self.databases:
|
||||||
|
assert_raises(exceptions.NotFound,
|
||||||
|
self.dbaas.users.revoke,
|
||||||
|
instance_info.id, user, database)
|
||||||
|
assert_equal(404, self.dbaas.last_http_code)
|
||||||
|
|
||||||
|
@after_class(always_run=True)
|
||||||
|
def tearDown(self):
|
||||||
|
self._reset_access()
|
||||||
|
|
||||||
|
for database in self.databases:
|
||||||
|
self.dbaas.databases.delete(instance_info.id, database)
|
||||||
|
assert_equal(202, self.dbaas.last_http_code)
|
@ -55,8 +55,8 @@ class TestUsers(object):
|
|||||||
username1 = "anous*&^er"
|
username1 = "anous*&^er"
|
||||||
username1_urlendcoded = "anous%2A%26%5Eer"
|
username1_urlendcoded = "anous%2A%26%5Eer"
|
||||||
password1 = "anopas*?.sword"
|
password1 = "anopas*?.sword"
|
||||||
db1 = "firstdb"
|
db1 = "usersfirstdb"
|
||||||
db2 = "seconddb"
|
db2 = "usersseconddb"
|
||||||
|
|
||||||
created_users = [username, username1]
|
created_users = [username, username1]
|
||||||
system_users = ['root', 'debian_sys_maint']
|
system_users = ['root', 'debian_sys_maint']
|
||||||
@ -89,6 +89,7 @@ class TestUsers(object):
|
|||||||
"databases": [{"name": self.db1}, {"name": self.db2}]})
|
"databases": [{"name": self.db1}, {"name": self.db2}]})
|
||||||
self.dbaas.users.create(instance_info.id, users)
|
self.dbaas.users.create(instance_info.id, users)
|
||||||
assert_equal(202, self.dbaas.last_http_code)
|
assert_equal(202, self.dbaas.last_http_code)
|
||||||
|
|
||||||
# Do we need this?
|
# Do we need this?
|
||||||
if not FAKE:
|
if not FAKE:
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
@ -130,6 +131,16 @@ class TestUsers(object):
|
|||||||
assert_raises(exceptions.BadRequest, self.dbaas.users.create,
|
assert_raises(exceptions.BadRequest, self.dbaas.users.create,
|
||||||
instance_info.id, users)
|
instance_info.id, users)
|
||||||
|
|
||||||
|
@test(depends_on=[test_create_users_list])
|
||||||
|
def test_get_one_user(self):
|
||||||
|
user = self.dbaas.users.get(instance_info.id, user=self.username)
|
||||||
|
assert_equal(200, self.dbaas.last_http_code)
|
||||||
|
assert_equal(user.name, self.username)
|
||||||
|
assert_equal(1, len(user.databases))
|
||||||
|
for db in user.databases:
|
||||||
|
assert_equal(db["name"], self.db1)
|
||||||
|
self.check_database_for_user(self.username, self.password, [self.db1])
|
||||||
|
|
||||||
@test(depends_on=[test_create_users_list])
|
@test(depends_on=[test_create_users_list])
|
||||||
def test_create_users_list_system(self):
|
def test_create_users_list_system(self):
|
||||||
#tests for users that should not be listed
|
#tests for users that should not be listed
|
||||||
@ -172,7 +183,7 @@ class TestUsers(object):
|
|||||||
assert_true(
|
assert_true(
|
||||||
db in actual_list,
|
db in actual_list,
|
||||||
"No match for db %s in dblist. %s :(" % (db, actual_list))
|
"No match for db %s in dblist. %s :(" % (db, actual_list))
|
||||||
# Confirm via API.
|
# Confirm via API list.
|
||||||
result = self.dbaas.users.list(instance_info.id)
|
result = self.dbaas.users.list(instance_info.id)
|
||||||
assert_equal(200, self.dbaas.last_http_code)
|
assert_equal(200, self.dbaas.last_http_code)
|
||||||
for item in result:
|
for item in result:
|
||||||
@ -181,6 +192,12 @@ class TestUsers(object):
|
|||||||
else:
|
else:
|
||||||
fail("User %s not added to collection." % user)
|
fail("User %s not added to collection." % user)
|
||||||
|
|
||||||
|
# Confirm via API get.
|
||||||
|
result = self.dbaas.users.get(instance_info.id, user)
|
||||||
|
assert_equal(200, self.dbaas.last_http_code)
|
||||||
|
if result.name != user:
|
||||||
|
fail("User %s not found via get." % user)
|
||||||
|
|
||||||
@test
|
@test
|
||||||
def test_username_too_long(self):
|
def test_username_too_long(self):
|
||||||
users = []
|
users = []
|
||||||
|
@ -19,6 +19,7 @@ from reddwarf.openstack.common import log as logging
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from reddwarf.tests.fakes.common import get_event_spawer
|
from reddwarf.tests.fakes.common import get_event_spawer
|
||||||
|
from reddwarf.common import exception as rd_exception
|
||||||
|
|
||||||
DB = {}
|
DB = {}
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -33,6 +34,7 @@ class FakeGuest(object):
|
|||||||
self.root_was_enabled = False
|
self.root_was_enabled = False
|
||||||
self.version = 1
|
self.version = 1
|
||||||
self.event_spawn = get_event_spawer()
|
self.event_spawn = get_event_spawer()
|
||||||
|
self.grants = {}
|
||||||
|
|
||||||
def get_hwinfo(self):
|
def get_hwinfo(self):
|
||||||
return {'mem_total': 524288, 'num_cpus': 1}
|
return {'mem_total': 524288, 'num_cpus': 1}
|
||||||
@ -108,6 +110,9 @@ class FakeGuest(object):
|
|||||||
def list_users(self, limit=None, marker=None, include_marker=False):
|
def list_users(self, limit=None, marker=None, include_marker=False):
|
||||||
return self._list_resource(self.users, limit, marker, include_marker)
|
return self._list_resource(self.users, limit, marker, include_marker)
|
||||||
|
|
||||||
|
def get_user(self, username):
|
||||||
|
return self.users.get(username, None)
|
||||||
|
|
||||||
def prepare(self, memory_mb, databases, users, device_path=None,
|
def prepare(self, memory_mb, databases, users, device_path=None,
|
||||||
mount_point=None):
|
mount_point=None):
|
||||||
from reddwarf.instance.models import DBInstance
|
from reddwarf.instance.models import DBInstance
|
||||||
@ -160,6 +165,41 @@ class FakeGuest(object):
|
|||||||
"""Return used volume information in bytes."""
|
"""Return used volume information in bytes."""
|
||||||
return {'used': 175756487}
|
return {'used': 175756487}
|
||||||
|
|
||||||
|
def grant_access(self, username, databases):
|
||||||
|
"""Add a database to a users's grant list."""
|
||||||
|
if username not in self.users:
|
||||||
|
raise rd_exception.UserNotFound(
|
||||||
|
"User %s cannot be found on the instance." % username)
|
||||||
|
current_grants = self.grants.get((username, '%'), set())
|
||||||
|
for db in databases:
|
||||||
|
current_grants.add(db)
|
||||||
|
self.grants[(username, '%')] = current_grants
|
||||||
|
|
||||||
|
def revoke_access(self, username, database):
|
||||||
|
"""Remove a database from a users's grant list."""
|
||||||
|
if username not in self.users:
|
||||||
|
raise rd_exception.UserNotFound(
|
||||||
|
"User %s cannot be found on the instance." % username)
|
||||||
|
g = self.grants.get((username, '%'), set())
|
||||||
|
if database not in self.grants.get((username, '%'), set()):
|
||||||
|
raise rd_exception.DatabaseNotFound(
|
||||||
|
"Database %s cannot be found on the instance." % database)
|
||||||
|
current_grants = self.grants.get((username, '%'), set())
|
||||||
|
if database in current_grants:
|
||||||
|
current_grants.remove(database)
|
||||||
|
self.grants[(username, '%')] = current_grants
|
||||||
|
|
||||||
|
def list_access(self, username):
|
||||||
|
if username not in self.users:
|
||||||
|
raise rd_exception.UserNotFound(
|
||||||
|
"User %s cannot be found on the instance." % username)
|
||||||
|
current_grants = self.grants.get((username, '%'), set())
|
||||||
|
dbs = [{'_name': db,
|
||||||
|
'_collate': '',
|
||||||
|
'_character_set': '',
|
||||||
|
} for db in current_grants]
|
||||||
|
return dbs
|
||||||
|
|
||||||
|
|
||||||
def get_or_create(id):
|
def get_or_create(id):
|
||||||
if id not in DB:
|
if id not in DB:
|
||||||
|
@ -146,9 +146,9 @@ class MySqlAdminTest(testtools.TestCase):
|
|||||||
self.mySqlAdmin.create_database(databases)
|
self.mySqlAdmin.create_database(databases)
|
||||||
|
|
||||||
args, _ = dbaas.LocalSqlClient.execute.call_args_list[0]
|
args, _ = dbaas.LocalSqlClient.execute.call_args_list[0]
|
||||||
expected = "CREATE DATABASE IF NOT EXISTS\n " \
|
expected = ("CREATE DATABASE IF NOT EXISTS "
|
||||||
" `testDB` CHARACTER SET = latin2 COLLATE = " \
|
"`testDB` CHARACTER SET = 'latin2' "
|
||||||
"latin2_general_ci;"
|
"COLLATE = 'latin2_general_ci';")
|
||||||
self.assertEquals(args[0].text, expected,
|
self.assertEquals(args[0].text, expected,
|
||||||
"Create database queries are not the same")
|
"Create database queries are not the same")
|
||||||
|
|
||||||
@ -164,16 +164,16 @@ class MySqlAdminTest(testtools.TestCase):
|
|||||||
self.mySqlAdmin.create_database(databases)
|
self.mySqlAdmin.create_database(databases)
|
||||||
|
|
||||||
args, _ = dbaas.LocalSqlClient.execute.call_args_list[0]
|
args, _ = dbaas.LocalSqlClient.execute.call_args_list[0]
|
||||||
expected = "CREATE DATABASE IF NOT EXISTS\n " \
|
expected = ("CREATE DATABASE IF NOT EXISTS "
|
||||||
" `testDB` CHARACTER SET = latin2 COLLATE = " \
|
"`testDB` CHARACTER SET = 'latin2' "
|
||||||
"latin2_general_ci;"
|
"COLLATE = 'latin2_general_ci';")
|
||||||
self.assertEquals(args[0].text, expected,
|
self.assertEquals(args[0].text, expected,
|
||||||
"Create database queries are not the same")
|
"Create database queries are not the same")
|
||||||
|
|
||||||
args, _ = dbaas.LocalSqlClient.execute.call_args_list[1]
|
args, _ = dbaas.LocalSqlClient.execute.call_args_list[1]
|
||||||
expected = "CREATE DATABASE IF NOT EXISTS\n " \
|
expected = ("CREATE DATABASE IF NOT EXISTS "
|
||||||
" `testDB2` CHARACTER SET = latin2 COLLATE = " \
|
"`testDB2` CHARACTER SET = 'latin2' "
|
||||||
"latin2_general_ci;"
|
"COLLATE = 'latin2_general_ci';")
|
||||||
self.assertEquals(args[0].text, expected,
|
self.assertEquals(args[0].text, expected,
|
||||||
"Create database queries are not the same")
|
"Create database queries are not the same")
|
||||||
|
|
||||||
@ -211,7 +211,7 @@ class MySqlAdminTest(testtools.TestCase):
|
|||||||
self.mySqlAdmin.delete_user(user)
|
self.mySqlAdmin.delete_user(user)
|
||||||
|
|
||||||
args, _ = dbaas.LocalSqlClient.execute.call_args
|
args, _ = dbaas.LocalSqlClient.execute.call_args
|
||||||
expected = "DROP USER `testUser`"
|
expected = "DROP USER `testUser`;"
|
||||||
self.assertEquals(args[0].text, expected,
|
self.assertEquals(args[0].text, expected,
|
||||||
"Delete user queries are not the same")
|
"Delete user queries are not the same")
|
||||||
|
|
||||||
@ -220,7 +220,9 @@ class MySqlAdminTest(testtools.TestCase):
|
|||||||
|
|
||||||
def test_create_user(self):
|
def test_create_user(self):
|
||||||
self.mySqlAdmin.create_user(FAKE_USER)
|
self.mySqlAdmin.create_user(FAKE_USER)
|
||||||
expected = 'GRANT ALL PRIVILEGES ON `testDB`.* TO `random`@:host;'
|
expected = ("GRANT ALL PRIVILEGES ON `testDB`.* TO `random`@`%` "
|
||||||
|
"IDENTIFIED BY 'guesswhat' "
|
||||||
|
"WITH GRANT OPTION;")
|
||||||
args, _ = dbaas.LocalSqlClient.execute.call_args
|
args, _ = dbaas.LocalSqlClient.execute.call_args
|
||||||
self.assertEquals(args[0].text.strip(), expected,
|
self.assertEquals(args[0].text.strip(), expected,
|
||||||
"Create user queries are not the same")
|
"Create user queries are not the same")
|
||||||
@ -243,10 +245,12 @@ class EnableRootTest(MySqlAdminTest):
|
|||||||
self.mySqlAdmin.enable_root()
|
self.mySqlAdmin.enable_root()
|
||||||
args_list = dbaas.LocalSqlClient.execute.call_args_list
|
args_list = dbaas.LocalSqlClient.execute.call_args_list
|
||||||
args, keyArgs = args_list[0]
|
args, keyArgs = args_list[0]
|
||||||
|
|
||||||
self.assertEquals(args[0].text.strip(), "CREATE USER :user@:host;",
|
self.assertEquals(args[0].text.strip(), "CREATE USER :user@:host;",
|
||||||
"Create user queries are not the same")
|
"Create user queries are not the same")
|
||||||
self.assertEquals(keyArgs['user'], 'root')
|
self.assertEquals(keyArgs['user'], 'root')
|
||||||
self.assertEquals(keyArgs['host'], '%')
|
self.assertEquals(keyArgs['host'], '%')
|
||||||
|
|
||||||
args, keyArgs = args_list[1]
|
args, keyArgs = args_list[1]
|
||||||
self.assertTrue("UPDATE mysql.user" in args[0].text)
|
self.assertTrue("UPDATE mysql.user" in args[0].text)
|
||||||
args, keyArgs = args_list[2]
|
args, keyArgs = args_list[2]
|
||||||
@ -262,44 +266,44 @@ class EnableRootTest(MySqlAdminTest):
|
|||||||
def test_is_root_enable(self):
|
def test_is_root_enable(self):
|
||||||
self.mySqlAdmin.is_root_enabled()
|
self.mySqlAdmin.is_root_enabled()
|
||||||
args, _ = dbaas.LocalSqlClient.execute.call_args
|
args, _ = dbaas.LocalSqlClient.execute.call_args
|
||||||
self.assertTrue("""SELECT User FROM mysql.user where User = 'root'
|
expected = ("""SELECT User FROM mysql.user WHERE User = 'root' """
|
||||||
and host != 'localhost';""" in args[0].text)
|
"""AND host != 'localhost';""")
|
||||||
|
self.assertTrue(expected in args[0].text,
|
||||||
|
"%s not in query." % expected)
|
||||||
|
|
||||||
def test_list_databases(self):
|
def test_list_databases(self):
|
||||||
self.mySqlAdmin.list_databases()
|
self.mySqlAdmin.list_databases()
|
||||||
args, _ = dbaas.LocalSqlClient.execute.call_args
|
args, _ = dbaas.LocalSqlClient.execute.call_args
|
||||||
|
expected = ["SELECT schema_name as name,",
|
||||||
self.assertTrue("SELECT schema_name as name," in args[0].text)
|
"default_character_set_name as charset,",
|
||||||
self.assertTrue("default_character_set_name as charset,"
|
"default_collation_name as collation",
|
||||||
in args[0].text)
|
"FROM information_schema.schemata",
|
||||||
self.assertTrue("default_collation_name as collation" in args[0].text)
|
("schema_name NOT IN ("
|
||||||
|
"'mysql', 'information_schema', "
|
||||||
self.assertTrue("FROM information_schema.schemata" in args[0].text)
|
"'lost+found', '#mysql50#lost+found'"
|
||||||
|
")"),
|
||||||
self.assertTrue('''schema_name not in (
|
"ORDER BY schema_name ASC",
|
||||||
'mysql', 'information_schema',
|
]
|
||||||
'lost+found', '#mysql50#lost+found'
|
for text in expected:
|
||||||
)''' in args[0].text)
|
self.assertTrue(text in args[0].text, "%s not in query." % text)
|
||||||
self.assertTrue("ORDER BY schema_name ASC" in args[0].text)
|
|
||||||
self.assertFalse("LIMIT " in args[0].text)
|
self.assertFalse("LIMIT " in args[0].text)
|
||||||
|
|
||||||
def test_list_databases_with_limit(self):
|
def test_list_databases_with_limit(self):
|
||||||
limit = 2
|
limit = 2
|
||||||
self.mySqlAdmin.list_databases(limit)
|
self.mySqlAdmin.list_databases(limit)
|
||||||
args, _ = dbaas.LocalSqlClient.execute.call_args
|
args, _ = dbaas.LocalSqlClient.execute.call_args
|
||||||
|
expected = ["SELECT schema_name as name,",
|
||||||
self.assertTrue("SELECT schema_name as name," in args[0].text)
|
"default_character_set_name as charset,",
|
||||||
self.assertTrue("default_character_set_name as charset,"
|
"default_collation_name as collation",
|
||||||
in args[0].text)
|
"FROM information_schema.schemata",
|
||||||
self.assertTrue("default_collation_name as collation" in args[0].text)
|
("schema_name NOT IN ("
|
||||||
|
"'mysql', 'information_schema', "
|
||||||
self.assertTrue("FROM information_schema.schemata" in args[0].text)
|
"'lost+found', '#mysql50#lost+found'"
|
||||||
|
")"),
|
||||||
self.assertTrue('''schema_name not in (
|
"ORDER BY schema_name ASC",
|
||||||
'mysql', 'information_schema',
|
]
|
||||||
'lost+found', '#mysql50#lost+found'
|
for text in expected:
|
||||||
)''' in args[0].text)
|
self.assertTrue(text in args[0].text, "%s not in query." % text)
|
||||||
self.assertTrue("ORDER BY schema_name ASC" in args[0].text)
|
|
||||||
|
|
||||||
self.assertTrue("LIMIT " + str(limit + 1) in args[0].text)
|
self.assertTrue("LIMIT " + str(limit + 1) in args[0].text)
|
||||||
|
|
||||||
@ -307,19 +311,19 @@ class EnableRootTest(MySqlAdminTest):
|
|||||||
marker = "aMarker"
|
marker = "aMarker"
|
||||||
self.mySqlAdmin.list_databases(marker=marker)
|
self.mySqlAdmin.list_databases(marker=marker)
|
||||||
args, _ = dbaas.LocalSqlClient.execute.call_args
|
args, _ = dbaas.LocalSqlClient.execute.call_args
|
||||||
|
expected = ["SELECT schema_name as name,",
|
||||||
|
"default_character_set_name as charset,",
|
||||||
|
"default_collation_name as collation",
|
||||||
|
"FROM information_schema.schemata",
|
||||||
|
("schema_name NOT IN ("
|
||||||
|
"'mysql', 'information_schema', "
|
||||||
|
"'lost+found', '#mysql50#lost+found'"
|
||||||
|
")"),
|
||||||
|
"ORDER BY schema_name ASC",
|
||||||
|
]
|
||||||
|
|
||||||
self.assertTrue("SELECT schema_name as name," in args[0].text)
|
for text in expected:
|
||||||
self.assertTrue("default_character_set_name as charset,"
|
self.assertTrue(text in args[0].text, "%s not in query." % text)
|
||||||
in args[0].text)
|
|
||||||
self.assertTrue("default_collation_name as collation" in args[0].text)
|
|
||||||
|
|
||||||
self.assertTrue("FROM information_schema.schemata" in args[0].text)
|
|
||||||
|
|
||||||
self.assertTrue('''schema_name not in (
|
|
||||||
'mysql', 'information_schema',
|
|
||||||
'lost+found', '#mysql50#lost+found'
|
|
||||||
)''' in args[0].text)
|
|
||||||
self.assertTrue("ORDER BY schema_name ASC" in args[0].text)
|
|
||||||
|
|
||||||
self.assertFalse("LIMIT " in args[0].text)
|
self.assertFalse("LIMIT " in args[0].text)
|
||||||
|
|
||||||
@ -330,19 +334,18 @@ class EnableRootTest(MySqlAdminTest):
|
|||||||
self.mySqlAdmin.list_databases(marker=marker, include_marker=True)
|
self.mySqlAdmin.list_databases(marker=marker, include_marker=True)
|
||||||
args, _ = dbaas.LocalSqlClient.execute.call_args
|
args, _ = dbaas.LocalSqlClient.execute.call_args
|
||||||
|
|
||||||
self.assertTrue("SELECT schema_name as name," in args[0].text)
|
expected = ["SELECT schema_name as name,",
|
||||||
self.assertTrue("default_character_set_name as charset,"
|
"default_character_set_name as charset,",
|
||||||
in args[0].text)
|
"default_collation_name as collation",
|
||||||
self.assertTrue("default_collation_name as collation" in args[0].text)
|
"FROM information_schema.schemata",
|
||||||
|
("schema_name NOT IN ("
|
||||||
self.assertTrue("FROM information_schema.schemata"
|
"'mysql', 'information_schema', "
|
||||||
in args[0].text)
|
"'lost+found', '#mysql50#lost+found'"
|
||||||
|
")"),
|
||||||
self.assertTrue('''schema_name not in (
|
"ORDER BY schema_name ASC",
|
||||||
'mysql', 'information_schema',
|
]
|
||||||
'lost+found', '#mysql50#lost+found'
|
for text in expected:
|
||||||
)''' in args[0].text)
|
self.assertTrue(text in args[0].text, "%s not in query." % text)
|
||||||
self.assertTrue("ORDER BY schema_name ASC" in args[0].text)
|
|
||||||
|
|
||||||
self.assertFalse("LIMIT " in args[0].text)
|
self.assertFalse("LIMIT " in args[0].text)
|
||||||
|
|
||||||
@ -352,12 +355,14 @@ class EnableRootTest(MySqlAdminTest):
|
|||||||
self.mySqlAdmin.list_users()
|
self.mySqlAdmin.list_users()
|
||||||
args, _ = dbaas.LocalSqlClient.execute.call_args
|
args, _ = dbaas.LocalSqlClient.execute.call_args
|
||||||
|
|
||||||
self.assertTrue("SELECT User" in args[0].text)
|
expected = ["SELECT User",
|
||||||
|
"FROM mysql.user",
|
||||||
|
"WHERE host != 'localhost'",
|
||||||
|
"ORDER BY User",
|
||||||
|
]
|
||||||
|
for text in expected:
|
||||||
|
self.assertTrue(text in args[0].text, "%s not in query." % text)
|
||||||
|
|
||||||
self.assertTrue("FROM mysql.user" in args[0].text)
|
|
||||||
|
|
||||||
self.assertTrue("WHERE host != 'localhost'" in args[0].text)
|
|
||||||
self.assertTrue("ORDER BY User" in args[0].text)
|
|
||||||
self.assertFalse("LIMIT " in args[0].text)
|
self.assertFalse("LIMIT " in args[0].text)
|
||||||
self.assertFalse("AND User > '" in args[0].text)
|
self.assertFalse("AND User > '" in args[0].text)
|
||||||
|
|
||||||
@ -366,29 +371,30 @@ class EnableRootTest(MySqlAdminTest):
|
|||||||
self.mySqlAdmin.list_users(limit)
|
self.mySqlAdmin.list_users(limit)
|
||||||
args, _ = dbaas.LocalSqlClient.execute.call_args
|
args, _ = dbaas.LocalSqlClient.execute.call_args
|
||||||
|
|
||||||
self.assertTrue("SELECT User" in args[0].text)
|
expected = ["SELECT User",
|
||||||
|
"FROM mysql.user",
|
||||||
self.assertTrue("FROM mysql.user" in args[0].text)
|
"WHERE host != 'localhost'",
|
||||||
|
"ORDER BY User",
|
||||||
self.assertTrue("WHERE host != 'localhost'" in args[0].text)
|
("LIMIT " + str(limit + 1)),
|
||||||
self.assertTrue("ORDER BY User" in args[0].text)
|
]
|
||||||
|
for text in expected:
|
||||||
self.assertTrue("LIMIT " + str(limit + 1) in args[0].text)
|
self.assertTrue(text in args[0].text, "%s not in query." % text)
|
||||||
|
|
||||||
def test_list_users_with_marker(self):
|
def test_list_users_with_marker(self):
|
||||||
marker = "aMarker"
|
marker = "aMarker"
|
||||||
self.mySqlAdmin.list_users(marker=marker)
|
self.mySqlAdmin.list_users(marker=marker)
|
||||||
args, _ = dbaas.LocalSqlClient.execute.call_args
|
args, _ = dbaas.LocalSqlClient.execute.call_args
|
||||||
|
|
||||||
self.assertTrue("SELECT User" in args[0].text)
|
expected = ["SELECT User",
|
||||||
|
"FROM mysql.user",
|
||||||
|
"WHERE host != 'localhost'",
|
||||||
|
"ORDER BY User",
|
||||||
|
]
|
||||||
|
|
||||||
self.assertTrue("FROM mysql.user" in args[0].text)
|
for text in expected:
|
||||||
|
self.assertTrue(text in args[0].text, "%s not in query." % text)
|
||||||
self.assertTrue("WHERE host != 'localhost'" in args[0].text)
|
|
||||||
self.assertTrue("ORDER BY User" in args[0].text)
|
|
||||||
|
|
||||||
self.assertFalse("LIMIT " in args[0].text)
|
self.assertFalse("LIMIT " in args[0].text)
|
||||||
|
|
||||||
self.assertTrue("AND User > '" + marker + "'" in args[0].text)
|
self.assertTrue("AND User > '" + marker + "'" in args[0].text)
|
||||||
|
|
||||||
def test_list_users_with_include_marker(self):
|
def test_list_users_with_include_marker(self):
|
||||||
@ -396,12 +402,14 @@ class EnableRootTest(MySqlAdminTest):
|
|||||||
self.mySqlAdmin.list_users(marker=marker, include_marker=True)
|
self.mySqlAdmin.list_users(marker=marker, include_marker=True)
|
||||||
args, _ = dbaas.LocalSqlClient.execute.call_args
|
args, _ = dbaas.LocalSqlClient.execute.call_args
|
||||||
|
|
||||||
self.assertTrue("SELECT User" in args[0].text)
|
expected = ["SELECT User",
|
||||||
|
"FROM mysql.user",
|
||||||
|
"WHERE host != 'localhost'",
|
||||||
|
"ORDER BY User",
|
||||||
|
]
|
||||||
|
|
||||||
self.assertTrue("FROM mysql.user" in args[0].text)
|
for text in expected:
|
||||||
|
self.assertTrue(text in args[0].text, "%s not in query." % text)
|
||||||
self.assertTrue("WHERE host != 'localhost'" in args[0].text)
|
|
||||||
self.assertTrue("ORDER BY User" in args[0].text)
|
|
||||||
|
|
||||||
self.assertFalse("LIMIT " in args[0].text)
|
self.assertFalse("LIMIT " in args[0].text)
|
||||||
|
|
||||||
@ -429,8 +437,8 @@ class MySqlAppTest(testtools.TestCase):
|
|||||||
InstanceServiceStatus.find_by(instance_id=self.FAKE_ID).delete()
|
InstanceServiceStatus.find_by(instance_id=self.FAKE_ID).delete()
|
||||||
|
|
||||||
def assert_reported_status(self, expected_status):
|
def assert_reported_status(self, expected_status):
|
||||||
service_status = InstanceServiceStatus.find_by(instance_id=
|
service_status = InstanceServiceStatus.find_by(
|
||||||
self.FAKE_ID)
|
instance_id=self.FAKE_ID)
|
||||||
self.assertEqual(expected_status, service_status.status)
|
self.assertEqual(expected_status, service_status.status)
|
||||||
|
|
||||||
def mysql_starts_successfully(self):
|
def mysql_starts_successfully(self):
|
||||||
@ -509,17 +517,16 @@ class MySqlAppTest(testtools.TestCase):
|
|||||||
def test_wipe_ib_logfiles_no_file(self):
|
def test_wipe_ib_logfiles_no_file(self):
|
||||||
|
|
||||||
from reddwarf.common.exception import ProcessExecutionError
|
from reddwarf.common.exception import ProcessExecutionError
|
||||||
dbaas.utils.execute_with_timeout = \
|
processexecerror = ProcessExecutionError('No such file or directory')
|
||||||
Mock(side_effect=
|
dbaas.utils.execute_with_timeout = Mock(side_effect=processexecerror)
|
||||||
ProcessExecutionError('No such file or directory'))
|
|
||||||
|
|
||||||
self.mySqlApp.wipe_ib_logfiles()
|
self.mySqlApp.wipe_ib_logfiles()
|
||||||
|
|
||||||
def test_wipe_ib_logfiles_error(self):
|
def test_wipe_ib_logfiles_error(self):
|
||||||
|
|
||||||
from reddwarf.common.exception import ProcessExecutionError
|
from reddwarf.common.exception import ProcessExecutionError
|
||||||
dbaas.utils.execute_with_timeout = Mock(side_effect=
|
mocked = Mock(side_effect=ProcessExecutionError('Error'))
|
||||||
ProcessExecutionError('Error'))
|
dbaas.utils.execute_with_timeout = mocked
|
||||||
|
|
||||||
self.assertRaises(ProcessExecutionError,
|
self.assertRaises(ProcessExecutionError,
|
||||||
self.mySqlApp.wipe_ib_logfiles)
|
self.mySqlApp.wipe_ib_logfiles)
|
||||||
@ -553,8 +560,8 @@ class MySqlAppTest(testtools.TestCase):
|
|||||||
|
|
||||||
self.mySqlApp._enable_mysql_on_boot = Mock()
|
self.mySqlApp._enable_mysql_on_boot = Mock()
|
||||||
from reddwarf.common.exception import ProcessExecutionError
|
from reddwarf.common.exception import ProcessExecutionError
|
||||||
dbaas.utils.execute_with_timeout = Mock(side_effect=
|
mocked = Mock(side_effect=ProcessExecutionError('Error'))
|
||||||
ProcessExecutionError('Error'))
|
dbaas.utils.execute_with_timeout = mocked
|
||||||
|
|
||||||
self.assertRaises(RuntimeError, self.mySqlApp.start_mysql)
|
self.assertRaises(RuntimeError, self.mySqlApp.start_mysql)
|
||||||
|
|
||||||
@ -834,8 +841,8 @@ class MySqlAppStatusTest(testtools.TestCase):
|
|||||||
def test_get_actual_db_status_error_shutdown(self):
|
def test_get_actual_db_status_error_shutdown(self):
|
||||||
|
|
||||||
from reddwarf.common.exception import ProcessExecutionError
|
from reddwarf.common.exception import ProcessExecutionError
|
||||||
dbaas.utils.execute_with_timeout = Mock(side_effect=
|
mocked = Mock(side_effect=ProcessExecutionError())
|
||||||
ProcessExecutionError())
|
dbaas.utils.execute_with_timeout = mocked
|
||||||
dbaas.load_mysqld_options = Mock()
|
dbaas.load_mysqld_options = Mock()
|
||||||
dbaas.os.path.exists = Mock(return_value=False)
|
dbaas.os.path.exists = Mock(return_value=False)
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@ class Checker(object):
|
|||||||
final_message = '\n'.join(self.messages)
|
final_message = '\n'.join(self.messages)
|
||||||
if _type is not None: # An error occurred
|
if _type is not None: # An error occurred
|
||||||
if len(self.messages) == 0:
|
if len(self.messages) == 0:
|
||||||
raise _type, value, tb
|
raise _type(*value.args), None, tb
|
||||||
self._add_exception(_type, value, tb)
|
self._add_exception(_type, value, tb)
|
||||||
if len(self.messages) != 0:
|
if len(self.messages) != 0:
|
||||||
final_message = '\n'.join(self.messages)
|
final_message = '\n'.join(self.messages)
|
||||||
|
@ -120,6 +120,7 @@ if __name__=="__main__":
|
|||||||
from reddwarf.tests.api import databases
|
from reddwarf.tests.api import databases
|
||||||
from reddwarf.tests.api import root
|
from reddwarf.tests.api import root
|
||||||
from reddwarf.tests.api import users
|
from reddwarf.tests.api import users
|
||||||
|
from reddwarf.tests.api import user_access
|
||||||
from reddwarf.tests.api.mgmt import accounts
|
from reddwarf.tests.api.mgmt import accounts
|
||||||
from reddwarf.tests.api.mgmt import admin_required
|
from reddwarf.tests.api.mgmt import admin_required
|
||||||
from reddwarf.tests.api.mgmt import instances
|
from reddwarf.tests.api.mgmt import instances
|
||||||
|
@ -10,7 +10,7 @@ pylint
|
|||||||
webtest
|
webtest
|
||||||
wsgi_intercept
|
wsgi_intercept
|
||||||
proboscis
|
proboscis
|
||||||
python-reddwarfclient
|
python-reddwarfclient==0.1.2
|
||||||
mock
|
mock
|
||||||
mox
|
mox
|
||||||
testtools>=0.9.22
|
testtools>=0.9.22
|
||||||
|
Loading…
Reference in New Issue
Block a user