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]
|
||||
|
||||
remote_implementation = fake
|
||||
fake_mode_events = eventlet
|
||||
|
||||
log_file = rdtest.log
|
||||
|
||||
|
@ -65,6 +65,16 @@ class FlavorNotFound(ReddwarfError):
|
||||
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):
|
||||
|
||||
internal_message = _("Cannot find compute instance %(server_id)s for "
|
||||
|
@ -317,6 +317,8 @@ class Controller(object):
|
||||
exception.NotFound,
|
||||
exception.ComputeInstanceNotFound,
|
||||
exception.ModelNotFoundError,
|
||||
exception.UserNotFound,
|
||||
exception.DatabaseNotFound,
|
||||
],
|
||||
webob.exc.HTTPConflict: [],
|
||||
webob.exc.HTTPRequestEntityTooLarge: [
|
||||
|
@ -47,6 +47,7 @@ class Mysql(extensions.ExtensionsDescriptor):
|
||||
serializer = wsgi.ReddwarfResponseSerializer(
|
||||
body_serializers={'application/xml':
|
||||
wsgi.ReddwarfXMLDictSerializer()})
|
||||
|
||||
resource = extensions.ResourceExtension(
|
||||
'databases',
|
||||
service.SchemaController(),
|
||||
@ -55,6 +56,7 @@ class Mysql(extensions.ExtensionsDescriptor):
|
||||
deserializer=wsgi.ReddwarfRequestDeserializer(),
|
||||
serializer=serializer)
|
||||
resources.append(resource)
|
||||
|
||||
resource = extensions.ResourceExtension(
|
||||
'users',
|
||||
service.UserController(),
|
||||
@ -62,8 +64,21 @@ class Mysql(extensions.ExtensionsDescriptor):
|
||||
'collection_name': '{tenant_id}/instances'},
|
||||
# deserializer=extensions.ExtensionsXMLSerializer()
|
||||
deserializer=wsgi.ReddwarfRequestDeserializer(),
|
||||
serializer=serializer)
|
||||
serializer=serializer,
|
||||
collection_actions={'update': 'PUT'})
|
||||
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(
|
||||
'root',
|
||||
service.RootController(),
|
||||
@ -71,7 +86,6 @@ class Mysql(extensions.ExtensionsDescriptor):
|
||||
'collection_name': '{tenant_id}/instances'},
|
||||
deserializer=wsgi.ReddwarfRequestDeserializer(),
|
||||
serializer=serializer)
|
||||
|
||||
resources.append(resource)
|
||||
|
||||
return resources
|
||||
|
@ -56,6 +56,19 @@ class User(object):
|
||||
self.password = password
|
||||
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
|
||||
def create(cls, context, instance_id, users):
|
||||
# Load InstanceServiceStatus to verify if it's running
|
||||
@ -78,6 +91,49 @@ class User(object):
|
||||
load_and_verify(context, instance_id)
|
||||
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):
|
||||
|
||||
|
@ -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.gettextutils import _
|
||||
|
||||
from urllib import unquote
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -61,7 +63,6 @@ class UserController(wsgi.Controller):
|
||||
"""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('users', ''):
|
||||
raise exception.MissingKey(key='users')
|
||||
for user in body.get('users'):
|
||||
@ -106,7 +107,87 @@ class UserController(wsgi.Controller):
|
||||
return wsgi.Result(None, 202)
|
||||
|
||||
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):
|
||||
|
@ -43,6 +43,15 @@ class UsersView(object):
|
||||
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):
|
||||
|
||||
def data(self):
|
||||
|
@ -106,11 +106,39 @@ class API(proxy.RpcProxy):
|
||||
LOG.warn(mnfe)
|
||||
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):
|
||||
"""Make an asynchronous call to create a new database user"""
|
||||
LOG.debug(_("Creating Users for Instance %s"), self.id)
|
||||
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):
|
||||
"""Make an asynchronous call to list database users"""
|
||||
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 import cfg
|
||||
from reddwarf.common import utils
|
||||
from reddwarf.guestagent import query
|
||||
from reddwarf.guestagent.db import models
|
||||
from reddwarf.guestagent.query import Query
|
||||
from reddwarf.guestagent import pkg
|
||||
from reddwarf.instance import models as rd_models
|
||||
from reddwarf.openstack.common import log as logging
|
||||
@ -50,7 +50,7 @@ from reddwarf.openstack.common.gettextutils import _
|
||||
|
||||
ADMIN_USER_NAME = "os_admin"
|
||||
LOG = logging.getLogger(__name__)
|
||||
FLUSH = text("""FLUSH PRIVILEGES;""")
|
||||
FLUSH = text(query.FLUSH)
|
||||
|
||||
ENGINE = None
|
||||
MYSQLD_ARGS = None
|
||||
@ -308,57 +308,92 @@ class LocalSqlClient(object):
|
||||
class MySqlAdmin(object):
|
||||
"""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):
|
||||
"""Create the list of specified databases"""
|
||||
client = LocalSqlClient(get_engine())
|
||||
with client:
|
||||
with LocalSqlClient(get_engine()) as client:
|
||||
for item in databases:
|
||||
mydb = models.MySQLDatabase()
|
||||
mydb.deserialize(item)
|
||||
t = text("""CREATE DATABASE IF NOT EXISTS
|
||||
`%s` CHARACTER SET = %s COLLATE = %s;"""
|
||||
% (mydb.name, mydb.character_set, mydb.collate))
|
||||
cd = query.CreateDatabase(mydb.name,
|
||||
mydb.character_set,
|
||||
mydb.collate)
|
||||
t = text(str(cd))
|
||||
client.execute(t)
|
||||
|
||||
def create_user(self, users):
|
||||
"""Create users and grant them privileges for the
|
||||
specified databases"""
|
||||
host = "%"
|
||||
client = LocalSqlClient(get_engine())
|
||||
with client:
|
||||
with LocalSqlClient(get_engine()) as client:
|
||||
for item in users:
|
||||
user = models.MySQLUser()
|
||||
user.deserialize(item)
|
||||
# TODO(cp16net):Should users be allowed to create users
|
||||
# 'os_admin' or 'debian-sys-maint'
|
||||
t = text("""GRANT USAGE ON *.* TO '%s'@\"%s\"
|
||||
IDENTIFIED BY '%s';"""
|
||||
% (user.name, host, user.password))
|
||||
g = query.Grant(user=user.name, host=host,
|
||||
clear=user.password)
|
||||
t = text(str(g))
|
||||
client.execute(t)
|
||||
for database in user.databases:
|
||||
mydb = models.MySQLDatabase()
|
||||
mydb.deserialize(database)
|
||||
t = text("""
|
||||
GRANT ALL PRIVILEGES ON `%s`.* TO `%s`@:host;
|
||||
""" % (mydb.name, user.name))
|
||||
client.execute(t, host=host)
|
||||
g = query.Grant(permissions='ALL', database=mydb.name,
|
||||
user=user.name, host=host,
|
||||
clear=user.password)
|
||||
t = text(str(g))
|
||||
client.execute(t)
|
||||
|
||||
def delete_database(self, database):
|
||||
"""Delete the specified database"""
|
||||
client = LocalSqlClient(get_engine())
|
||||
with client:
|
||||
with LocalSqlClient(get_engine()) as client:
|
||||
mydb = models.MySQLDatabase()
|
||||
mydb.deserialize(database)
|
||||
t = text("""DROP DATABASE `%s`;""" % mydb.name)
|
||||
dd = query.DropDatabase(mydb.name)
|
||||
t = text(str(dd))
|
||||
client.execute(t)
|
||||
|
||||
def delete_user(self, user):
|
||||
"""Delete the specified users"""
|
||||
client = LocalSqlClient(get_engine())
|
||||
with client:
|
||||
with LocalSqlClient(get_engine()) as client:
|
||||
mysql_user = models.MySQLUser()
|
||||
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)
|
||||
|
||||
def enable_root(self):
|
||||
@ -367,31 +402,68 @@ class MySqlAdmin(object):
|
||||
user = models.MySQLUser()
|
||||
user.name = "root"
|
||||
user.password = generate_random_password()
|
||||
client = LocalSqlClient(get_engine())
|
||||
with client:
|
||||
with LocalSqlClient(get_engine()) as client:
|
||||
try:
|
||||
t = text("""CREATE USER :user@:host;""")
|
||||
client.execute(t, user=user.name, host=host, pwd=user.password)
|
||||
cu = query.CreateUser(user.name, host=host)
|
||||
t = text(str(cu))
|
||||
client.execute(t, **cu.keyArgs)
|
||||
except exc.OperationalError as err:
|
||||
# Ignore, user is already created, just reset the password
|
||||
# TODO(rnirmal): More fine grained error checking later on
|
||||
LOG.debug(err)
|
||||
with client:
|
||||
t = text("""UPDATE mysql.user SET Password=PASSWORD(:pwd)
|
||||
WHERE User=:user;""")
|
||||
client.execute(t, user=user.name, pwd=user.password)
|
||||
t = text("""GRANT ALL PRIVILEGES ON *.* TO :user@:host
|
||||
WITH GRANT OPTION;""")
|
||||
client.execute(t, user=user.name, host=host)
|
||||
with LocalSqlClient(get_engine()) as client:
|
||||
uu = query.UpdateUser(user.name, host=host,
|
||||
clear=user.password)
|
||||
t = text(str(uu))
|
||||
client.execute(t)
|
||||
g = query.Grant(permissions="ALL", user=user.name, host=host,
|
||||
grant_option=True, clear=user.password)
|
||||
t = text(str(g))
|
||||
client.execute(t)
|
||||
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):
|
||||
"""Return True if root access is enabled; False otherwise."""
|
||||
client = LocalSqlClient(get_engine())
|
||||
with client:
|
||||
mysql_user = models.MySQLUser()
|
||||
t = text("""SELECT User FROM mysql.user where User = 'root'
|
||||
and host != 'localhost';""")
|
||||
with LocalSqlClient(get_engine()) as client:
|
||||
t = text(query.ROOT_ENABLED)
|
||||
result = client.execute(t)
|
||||
LOG.debug("result = " + str(result))
|
||||
return result.rowcount != 0
|
||||
@ -400,23 +472,22 @@ class MySqlAdmin(object):
|
||||
"""List databases the user created on this mysql instance"""
|
||||
LOG.debug(_("---Listing Databases---"))
|
||||
databases = []
|
||||
client = LocalSqlClient(get_engine())
|
||||
with client:
|
||||
with LocalSqlClient(get_engine()) as client:
|
||||
# If you have an external volume mounted at /var/lib/mysql
|
||||
# the lost+found directory will show up in mysql as a database
|
||||
# which will create errors if you try to do any database ops
|
||||
# on it. So we remove it here if it exists.
|
||||
q = Query()
|
||||
q = query.Query()
|
||||
q.columns = [
|
||||
'schema_name as name',
|
||||
'default_character_set_name as charset',
|
||||
'default_collation_name as collation',
|
||||
]
|
||||
q.tables = ['information_schema.schemata']
|
||||
q.where = ['''schema_name not in (
|
||||
'mysql', 'information_schema',
|
||||
'lost+found', '#mysql50#lost+found'
|
||||
)''']
|
||||
q.where = ["schema_name NOT IN ("
|
||||
"'mysql', 'information_schema', "
|
||||
"'lost+found', '#mysql50#lost+found'"
|
||||
")"]
|
||||
q.order = ['schema_name ASC']
|
||||
if limit:
|
||||
q.limit = limit + 1
|
||||
@ -447,10 +518,9 @@ class MySqlAdmin(object):
|
||||
"""List users that have access to the database"""
|
||||
LOG.debug(_("---Listing Users---"))
|
||||
users = []
|
||||
client = LocalSqlClient(get_engine())
|
||||
with client:
|
||||
with LocalSqlClient(get_engine()) as client:
|
||||
mysql_user = models.MySQLUser()
|
||||
q = Query()
|
||||
q = query.Query()
|
||||
q.columns = ['User']
|
||||
q.tables = ['mysql.user']
|
||||
q.where = ["host != 'localhost'"]
|
||||
@ -471,21 +541,8 @@ class MySqlAdmin(object):
|
||||
LOG.debug("user = " + str(row))
|
||||
mysql_user = models.MySQLUser()
|
||||
mysql_user.name = row['User']
|
||||
self._associate_dbs(mysql_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())
|
||||
if result.rowcount <= limit:
|
||||
next_marker = None
|
||||
@ -493,6 +550,21 @@ class MySqlAdmin(object):
|
||||
|
||||
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):
|
||||
"""
|
||||
@ -531,25 +603,26 @@ class MySqlApp(object):
|
||||
Create a os_admin user with a random password
|
||||
with all privileges similar to the root user
|
||||
"""
|
||||
t = text("CREATE USER :user@'localhost';")
|
||||
client.execute(t, user=ADMIN_USER_NAME)
|
||||
t = text("""
|
||||
UPDATE mysql.user SET Password=PASSWORD(:pwd)
|
||||
WHERE User=:user;
|
||||
""")
|
||||
client.execute(t, pwd=password, user=ADMIN_USER_NAME)
|
||||
t = text("""
|
||||
GRANT ALL PRIVILEGES ON *.* TO :user@'localhost'
|
||||
WITH GRANT OPTION;
|
||||
""")
|
||||
client.execute(t, user=ADMIN_USER_NAME)
|
||||
localhost = "localhost"
|
||||
cu = query.CreateUser(ADMIN_USER_NAME, host=localhost)
|
||||
t = text(str(cu))
|
||||
client.execute(t, **cu.keyArgs)
|
||||
uu = query.UpdateUser(ADMIN_USER_NAME, host=localhost, clear=password)
|
||||
t = text(str(uu))
|
||||
client.execute(t)
|
||||
g = query.Grant(permissions='ALL', user=ADMIN_USER_NAME,
|
||||
host=localhost, grant_option=True, clear=password)
|
||||
t = text(str(g))
|
||||
client.execute(t)
|
||||
|
||||
@staticmethod
|
||||
def _generate_root_password(client):
|
||||
""" Generate and set a random root password and forget about it. """
|
||||
t = text("""UPDATE mysql.user SET Password=PASSWORD(:pwd)
|
||||
WHERE User='root';""")
|
||||
client.execute(t, pwd=generate_random_password())
|
||||
localhost = "localhost"
|
||||
uu = query.UpdateUser("root", host=localhost,
|
||||
clear=generate_random_password())
|
||||
t = text(str(uu))
|
||||
client.execute(t)
|
||||
|
||||
def install_and_secure(self, memory_mb):
|
||||
"""Prepare the guest machine with a secure mysql server installation"""
|
||||
@ -562,8 +635,7 @@ class MySqlApp(object):
|
||||
admin_password = generate_random_password()
|
||||
|
||||
engine = create_engine("mysql://root:@localhost:3306", echo=True)
|
||||
client = LocalSqlClient(engine)
|
||||
with client:
|
||||
with LocalSqlClient(engine) as client:
|
||||
self._generate_root_password(client)
|
||||
self._remove_anonymous_user(client)
|
||||
self._remove_remote_root_access(client)
|
||||
@ -618,13 +690,11 @@ class MySqlApp(object):
|
||||
raise RuntimeError("Could not stop MySQL!")
|
||||
|
||||
def _remove_anonymous_user(self, client):
|
||||
t = text("""DELETE FROM mysql.user WHERE User='';""")
|
||||
t = text(query.REMOVE_ANON)
|
||||
client.execute(t)
|
||||
|
||||
def _remove_remote_root_access(self, client):
|
||||
t = text("""DELETE FROM mysql.user
|
||||
WHERE User='root'
|
||||
AND Host!='localhost';""")
|
||||
t = text(query.REMOVE_ROOT)
|
||||
client.execute(t)
|
||||
|
||||
def restart(self):
|
||||
|
@ -15,6 +15,9 @@ class Manager(periodic_task.PeriodicTasks):
|
||||
"""Update the status of the MySQL service"""
|
||||
dbaas.MySqlAppStatus.get().update()
|
||||
|
||||
def change_passwords(self, context, users):
|
||||
return dbaas.MySqlAdmin().change_passwords(users)
|
||||
|
||||
def create_database(self, context, databases):
|
||||
return dbaas.MySqlAdmin().create_database(databases)
|
||||
|
||||
@ -27,6 +30,18 @@ class Manager(periodic_task.PeriodicTasks):
|
||||
def delete_user(self, context, 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,
|
||||
include_marker=False):
|
||||
return dbaas.MySqlAdmin().list_databases(limit, marker,
|
||||
|
@ -18,6 +18,8 @@
|
||||
"""
|
||||
|
||||
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.limit = limit
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
@property
|
||||
def _columns(self):
|
||||
if not self.columns:
|
||||
return "SELECT *"
|
||||
return "SELECT %s" % (', '.join(self.columns))
|
||||
return "SELECT %s" % (", ".join(self.columns))
|
||||
|
||||
@property
|
||||
def _tables(self):
|
||||
return "FROM %s" % (', '.join(self.tables))
|
||||
return "FROM %s" % (", ".join(self.tables))
|
||||
|
||||
@property
|
||||
def _where(self):
|
||||
@ -52,19 +57,19 @@ class Query(object):
|
||||
@property
|
||||
def _order(self):
|
||||
if not self.order:
|
||||
return ''
|
||||
return "ORDER BY %s" % (', '.join(self.order))
|
||||
return ""
|
||||
return "ORDER BY %s" % (", ".join(self.order))
|
||||
|
||||
@property
|
||||
def _group_by(self):
|
||||
if not self.group:
|
||||
return ''
|
||||
return "GROUP BY %s" % (', '.join(self.group))
|
||||
return ""
|
||||
return "GROUP BY %s" % (", ".join(self.group))
|
||||
|
||||
@property
|
||||
def _limit(self):
|
||||
if not self.limit:
|
||||
return ''
|
||||
return ""
|
||||
return "LIMIT %s" % str(self.limit)
|
||||
|
||||
def __str__(self):
|
||||
@ -76,7 +81,323 @@ class Query(object):
|
||||
self._group_by,
|
||||
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):
|
||||
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()
|
||||
LOG.error("Error running async task:")
|
||||
LOG.error((traceback.format_exception(type_, value, tb)))
|
||||
raise type_, value, tb
|
||||
raise type_(*value.args), None, tb
|
||||
|
||||
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_urlendcoded = "anous%2A%26%5Eer"
|
||||
password1 = "anopas*?.sword"
|
||||
db1 = "firstdb"
|
||||
db2 = "seconddb"
|
||||
db1 = "usersfirstdb"
|
||||
db2 = "usersseconddb"
|
||||
|
||||
created_users = [username, username1]
|
||||
system_users = ['root', 'debian_sys_maint']
|
||||
@ -89,6 +89,7 @@ class TestUsers(object):
|
||||
"databases": [{"name": self.db1}, {"name": self.db2}]})
|
||||
self.dbaas.users.create(instance_info.id, users)
|
||||
assert_equal(202, self.dbaas.last_http_code)
|
||||
|
||||
# Do we need this?
|
||||
if not FAKE:
|
||||
time.sleep(5)
|
||||
@ -130,6 +131,16 @@ class TestUsers(object):
|
||||
assert_raises(exceptions.BadRequest, self.dbaas.users.create,
|
||||
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])
|
||||
def test_create_users_list_system(self):
|
||||
#tests for users that should not be listed
|
||||
@ -172,7 +183,7 @@ class TestUsers(object):
|
||||
assert_true(
|
||||
db in 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)
|
||||
assert_equal(200, self.dbaas.last_http_code)
|
||||
for item in result:
|
||||
@ -181,6 +192,12 @@ class TestUsers(object):
|
||||
else:
|
||||
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
|
||||
def test_username_too_long(self):
|
||||
users = []
|
||||
|
@ -19,6 +19,7 @@ from reddwarf.openstack.common import log as logging
|
||||
import time
|
||||
|
||||
from reddwarf.tests.fakes.common import get_event_spawer
|
||||
from reddwarf.common import exception as rd_exception
|
||||
|
||||
DB = {}
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -33,6 +34,7 @@ class FakeGuest(object):
|
||||
self.root_was_enabled = False
|
||||
self.version = 1
|
||||
self.event_spawn = get_event_spawer()
|
||||
self.grants = {}
|
||||
|
||||
def get_hwinfo(self):
|
||||
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):
|
||||
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,
|
||||
mount_point=None):
|
||||
from reddwarf.instance.models import DBInstance
|
||||
@ -160,6 +165,41 @@ class FakeGuest(object):
|
||||
"""Return used volume information in bytes."""
|
||||
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):
|
||||
if id not in DB:
|
||||
|
@ -146,9 +146,9 @@ class MySqlAdminTest(testtools.TestCase):
|
||||
self.mySqlAdmin.create_database(databases)
|
||||
|
||||
args, _ = dbaas.LocalSqlClient.execute.call_args_list[0]
|
||||
expected = "CREATE DATABASE IF NOT EXISTS\n " \
|
||||
" `testDB` CHARACTER SET = latin2 COLLATE = " \
|
||||
"latin2_general_ci;"
|
||||
expected = ("CREATE DATABASE IF NOT EXISTS "
|
||||
"`testDB` CHARACTER SET = 'latin2' "
|
||||
"COLLATE = 'latin2_general_ci';")
|
||||
self.assertEquals(args[0].text, expected,
|
||||
"Create database queries are not the same")
|
||||
|
||||
@ -164,16 +164,16 @@ class MySqlAdminTest(testtools.TestCase):
|
||||
self.mySqlAdmin.create_database(databases)
|
||||
|
||||
args, _ = dbaas.LocalSqlClient.execute.call_args_list[0]
|
||||
expected = "CREATE DATABASE IF NOT EXISTS\n " \
|
||||
" `testDB` CHARACTER SET = latin2 COLLATE = " \
|
||||
"latin2_general_ci;"
|
||||
expected = ("CREATE DATABASE IF NOT EXISTS "
|
||||
"`testDB` CHARACTER SET = 'latin2' "
|
||||
"COLLATE = 'latin2_general_ci';")
|
||||
self.assertEquals(args[0].text, expected,
|
||||
"Create database queries are not the same")
|
||||
|
||||
args, _ = dbaas.LocalSqlClient.execute.call_args_list[1]
|
||||
expected = "CREATE DATABASE IF NOT EXISTS\n " \
|
||||
" `testDB2` CHARACTER SET = latin2 COLLATE = " \
|
||||
"latin2_general_ci;"
|
||||
expected = ("CREATE DATABASE IF NOT EXISTS "
|
||||
"`testDB2` CHARACTER SET = 'latin2' "
|
||||
"COLLATE = 'latin2_general_ci';")
|
||||
self.assertEquals(args[0].text, expected,
|
||||
"Create database queries are not the same")
|
||||
|
||||
@ -211,7 +211,7 @@ class MySqlAdminTest(testtools.TestCase):
|
||||
self.mySqlAdmin.delete_user(user)
|
||||
|
||||
args, _ = dbaas.LocalSqlClient.execute.call_args
|
||||
expected = "DROP USER `testUser`"
|
||||
expected = "DROP USER `testUser`;"
|
||||
self.assertEquals(args[0].text, expected,
|
||||
"Delete user queries are not the same")
|
||||
|
||||
@ -220,7 +220,9 @@ class MySqlAdminTest(testtools.TestCase):
|
||||
|
||||
def test_create_user(self):
|
||||
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
|
||||
self.assertEquals(args[0].text.strip(), expected,
|
||||
"Create user queries are not the same")
|
||||
@ -243,10 +245,12 @@ class EnableRootTest(MySqlAdminTest):
|
||||
self.mySqlAdmin.enable_root()
|
||||
args_list = dbaas.LocalSqlClient.execute.call_args_list
|
||||
args, keyArgs = args_list[0]
|
||||
|
||||
self.assertEquals(args[0].text.strip(), "CREATE USER :user@:host;",
|
||||
"Create user queries are not the same")
|
||||
self.assertEquals(keyArgs['user'], 'root')
|
||||
self.assertEquals(keyArgs['host'], '%')
|
||||
|
||||
args, keyArgs = args_list[1]
|
||||
self.assertTrue("UPDATE mysql.user" in args[0].text)
|
||||
args, keyArgs = args_list[2]
|
||||
@ -262,44 +266,44 @@ class EnableRootTest(MySqlAdminTest):
|
||||
def test_is_root_enable(self):
|
||||
self.mySqlAdmin.is_root_enabled()
|
||||
args, _ = dbaas.LocalSqlClient.execute.call_args
|
||||
self.assertTrue("""SELECT User FROM mysql.user where User = 'root'
|
||||
and host != 'localhost';""" in args[0].text)
|
||||
expected = ("""SELECT User FROM mysql.user WHERE User = 'root' """
|
||||
"""AND host != 'localhost';""")
|
||||
self.assertTrue(expected in args[0].text,
|
||||
"%s not in query." % expected)
|
||||
|
||||
def test_list_databases(self):
|
||||
self.mySqlAdmin.list_databases()
|
||||
args, _ = dbaas.LocalSqlClient.execute.call_args
|
||||
|
||||
self.assertTrue("SELECT schema_name as name," in args[0].text)
|
||||
self.assertTrue("default_character_set_name as charset,"
|
||||
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)
|
||||
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",
|
||||
]
|
||||
for text in expected:
|
||||
self.assertTrue(text in args[0].text, "%s not in query." % text)
|
||||
self.assertFalse("LIMIT " in args[0].text)
|
||||
|
||||
def test_list_databases_with_limit(self):
|
||||
limit = 2
|
||||
self.mySqlAdmin.list_databases(limit)
|
||||
args, _ = dbaas.LocalSqlClient.execute.call_args
|
||||
|
||||
self.assertTrue("SELECT schema_name as name," in args[0].text)
|
||||
self.assertTrue("default_character_set_name as charset,"
|
||||
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)
|
||||
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",
|
||||
]
|
||||
for text in expected:
|
||||
self.assertTrue(text in args[0].text, "%s not in query." % text)
|
||||
|
||||
self.assertTrue("LIMIT " + str(limit + 1) in args[0].text)
|
||||
|
||||
@ -307,19 +311,19 @@ class EnableRootTest(MySqlAdminTest):
|
||||
marker = "aMarker"
|
||||
self.mySqlAdmin.list_databases(marker=marker)
|
||||
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)
|
||||
self.assertTrue("default_character_set_name as charset,"
|
||||
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)
|
||||
for text in expected:
|
||||
self.assertTrue(text in args[0].text, "%s not in query." % text)
|
||||
|
||||
self.assertFalse("LIMIT " in args[0].text)
|
||||
|
||||
@ -330,19 +334,18 @@ class EnableRootTest(MySqlAdminTest):
|
||||
self.mySqlAdmin.list_databases(marker=marker, include_marker=True)
|
||||
args, _ = dbaas.LocalSqlClient.execute.call_args
|
||||
|
||||
self.assertTrue("SELECT schema_name as name," in args[0].text)
|
||||
self.assertTrue("default_character_set_name as charset,"
|
||||
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)
|
||||
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",
|
||||
]
|
||||
for text in expected:
|
||||
self.assertTrue(text in args[0].text, "%s not in query." % text)
|
||||
|
||||
self.assertFalse("LIMIT " in args[0].text)
|
||||
|
||||
@ -352,12 +355,14 @@ class EnableRootTest(MySqlAdminTest):
|
||||
self.mySqlAdmin.list_users()
|
||||
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("AND User > '" in args[0].text)
|
||||
|
||||
@ -366,29 +371,30 @@ class EnableRootTest(MySqlAdminTest):
|
||||
self.mySqlAdmin.list_users(limit)
|
||||
args, _ = dbaas.LocalSqlClient.execute.call_args
|
||||
|
||||
self.assertTrue("SELECT User" in args[0].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.assertTrue("LIMIT " + str(limit + 1) in args[0].text)
|
||||
expected = ["SELECT User",
|
||||
"FROM mysql.user",
|
||||
"WHERE host != 'localhost'",
|
||||
"ORDER BY User",
|
||||
("LIMIT " + str(limit + 1)),
|
||||
]
|
||||
for text in expected:
|
||||
self.assertTrue(text in args[0].text, "%s not in query." % text)
|
||||
|
||||
def test_list_users_with_marker(self):
|
||||
marker = "aMarker"
|
||||
self.mySqlAdmin.list_users(marker=marker)
|
||||
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)
|
||||
|
||||
self.assertTrue("WHERE host != 'localhost'" in args[0].text)
|
||||
self.assertTrue("ORDER BY User" in args[0].text)
|
||||
for text in expected:
|
||||
self.assertTrue(text in args[0].text, "%s not in query." % text)
|
||||
|
||||
self.assertFalse("LIMIT " in args[0].text)
|
||||
|
||||
self.assertTrue("AND User > '" + marker + "'" in args[0].text)
|
||||
|
||||
def test_list_users_with_include_marker(self):
|
||||
@ -396,12 +402,14 @@ class EnableRootTest(MySqlAdminTest):
|
||||
self.mySqlAdmin.list_users(marker=marker, include_marker=True)
|
||||
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)
|
||||
|
||||
self.assertTrue("WHERE host != 'localhost'" in args[0].text)
|
||||
self.assertTrue("ORDER BY User" in args[0].text)
|
||||
for text in expected:
|
||||
self.assertTrue(text in args[0].text, "%s not in query." % 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()
|
||||
|
||||
def assert_reported_status(self, expected_status):
|
||||
service_status = InstanceServiceStatus.find_by(instance_id=
|
||||
self.FAKE_ID)
|
||||
service_status = InstanceServiceStatus.find_by(
|
||||
instance_id=self.FAKE_ID)
|
||||
self.assertEqual(expected_status, service_status.status)
|
||||
|
||||
def mysql_starts_successfully(self):
|
||||
@ -509,17 +517,16 @@ class MySqlAppTest(testtools.TestCase):
|
||||
def test_wipe_ib_logfiles_no_file(self):
|
||||
|
||||
from reddwarf.common.exception import ProcessExecutionError
|
||||
dbaas.utils.execute_with_timeout = \
|
||||
Mock(side_effect=
|
||||
ProcessExecutionError('No such file or directory'))
|
||||
processexecerror = ProcessExecutionError('No such file or directory')
|
||||
dbaas.utils.execute_with_timeout = Mock(side_effect=processexecerror)
|
||||
|
||||
self.mySqlApp.wipe_ib_logfiles()
|
||||
|
||||
def test_wipe_ib_logfiles_error(self):
|
||||
|
||||
from reddwarf.common.exception import ProcessExecutionError
|
||||
dbaas.utils.execute_with_timeout = Mock(side_effect=
|
||||
ProcessExecutionError('Error'))
|
||||
mocked = Mock(side_effect=ProcessExecutionError('Error'))
|
||||
dbaas.utils.execute_with_timeout = mocked
|
||||
|
||||
self.assertRaises(ProcessExecutionError,
|
||||
self.mySqlApp.wipe_ib_logfiles)
|
||||
@ -553,8 +560,8 @@ class MySqlAppTest(testtools.TestCase):
|
||||
|
||||
self.mySqlApp._enable_mysql_on_boot = Mock()
|
||||
from reddwarf.common.exception import ProcessExecutionError
|
||||
dbaas.utils.execute_with_timeout = Mock(side_effect=
|
||||
ProcessExecutionError('Error'))
|
||||
mocked = Mock(side_effect=ProcessExecutionError('Error'))
|
||||
dbaas.utils.execute_with_timeout = mocked
|
||||
|
||||
self.assertRaises(RuntimeError, self.mySqlApp.start_mysql)
|
||||
|
||||
@ -834,8 +841,8 @@ class MySqlAppStatusTest(testtools.TestCase):
|
||||
def test_get_actual_db_status_error_shutdown(self):
|
||||
|
||||
from reddwarf.common.exception import ProcessExecutionError
|
||||
dbaas.utils.execute_with_timeout = Mock(side_effect=
|
||||
ProcessExecutionError())
|
||||
mocked = Mock(side_effect=ProcessExecutionError())
|
||||
dbaas.utils.execute_with_timeout = mocked
|
||||
dbaas.load_mysqld_options = Mock()
|
||||
dbaas.os.path.exists = Mock(return_value=False)
|
||||
|
||||
|
@ -96,7 +96,7 @@ class Checker(object):
|
||||
final_message = '\n'.join(self.messages)
|
||||
if _type is not None: # An error occurred
|
||||
if len(self.messages) == 0:
|
||||
raise _type, value, tb
|
||||
raise _type(*value.args), None, tb
|
||||
self._add_exception(_type, value, tb)
|
||||
if len(self.messages) != 0:
|
||||
final_message = '\n'.join(self.messages)
|
||||
|
@ -120,6 +120,7 @@ if __name__=="__main__":
|
||||
from reddwarf.tests.api import databases
|
||||
from reddwarf.tests.api import root
|
||||
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 admin_required
|
||||
from reddwarf.tests.api.mgmt import instances
|
||||
|
@ -10,7 +10,7 @@ pylint
|
||||
webtest
|
||||
wsgi_intercept
|
||||
proboscis
|
||||
python-reddwarfclient
|
||||
python-reddwarfclient==0.1.2
|
||||
mock
|
||||
mox
|
||||
testtools>=0.9.22
|
||||
|
Loading…
x
Reference in New Issue
Block a user