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:
Ed Cranford 2013-01-24 17:05:25 -06:00
parent 7dd1c6b3f5
commit 90f8ca81d9
19 changed files with 1071 additions and 200 deletions

View File

@ -1,6 +1,7 @@
[DEFAULT]
remote_implementation = fake
fake_mode_events = eventlet
log_file = rdtest.log

View File

@ -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 "

View File

@ -317,6 +317,8 @@ class Controller(object):
exception.NotFound,
exception.ComputeInstanceNotFound,
exception.ModelNotFoundError,
exception.UserNotFound,
exception.DatabaseNotFound,
],
webob.exc.HTTPConflict: [],
webob.exc.HTTPRequestEntityTooLarge: [

View File

@ -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

View File

@ -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):

View File

@ -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):

View File

@ -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):

View File

@ -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)

View File

@ -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):

View File

@ -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,

View File

@ -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';")

View File

@ -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)

View 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)

View File

@ -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 = []

View File

@ -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:

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -10,7 +10,7 @@ pylint
webtest
wsgi_intercept
proboscis
python-reddwarfclient
python-reddwarfclient==0.1.2
mock
mox
testtools>=0.9.22