Merge "Cleanup guestagent models"
This commit is contained in:
commit
b0b3a5a27b
@ -24,26 +24,48 @@ def url_quote(s):
|
|||||||
return urllib_parse.quote(str(s))
|
return urllib_parse.quote(str(s))
|
||||||
|
|
||||||
|
|
||||||
def paginate_list(li, limit=None, marker=None, include_marker=False):
|
def paginate_list(li, limit=None, marker=None, include_marker=False,
|
||||||
|
key=lambda x: x):
|
||||||
"""Sort the given list and return a sublist containing a page of items.
|
"""Sort the given list and return a sublist containing a page of items.
|
||||||
|
|
||||||
:param list li: The list to be paginated.
|
:param list li: The list to be paginated.
|
||||||
:param int limit: Maximum number of iterms to be returned.
|
:param int limit: Maximum number of iterms to be returned.
|
||||||
:param marker: Key of the first item to appear on the sublist.
|
:param marker: Key of the first item to appear on the sublist.
|
||||||
:param bool include_marker: Include the marker value itself in the sublist.
|
:param bool include_marker: Include the marker value itself in the sublist.
|
||||||
|
:param lambda key: Sorting expression.
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
li.sort()
|
sli = sorted(li, key=key)
|
||||||
|
index = [key(item) for item in sli]
|
||||||
if include_marker:
|
if include_marker:
|
||||||
pos = bisect.bisect_left(li, marker)
|
pos = bisect.bisect_left(index, marker)
|
||||||
else:
|
else:
|
||||||
pos = bisect.bisect(li, marker)
|
pos = bisect.bisect(index, marker)
|
||||||
|
|
||||||
if limit and pos + limit < len(li):
|
if limit and pos + limit < len(sli):
|
||||||
page = li[pos:pos + limit]
|
page = sli[pos:pos + limit]
|
||||||
return page, page[-1]
|
return page, key(page[-1])
|
||||||
else:
|
else:
|
||||||
return li[pos:], None
|
return sli[pos:], None
|
||||||
|
|
||||||
|
|
||||||
|
def paginate_object_list(li, attr_name, limit=None, marker=None,
|
||||||
|
include_marker=False):
|
||||||
|
"""Wrapper for paginate_list to handle lists of generic objects paginated
|
||||||
|
based on an attribute.
|
||||||
|
"""
|
||||||
|
return paginate_list(li, limit=limit, marker=marker,
|
||||||
|
include_marker=include_marker,
|
||||||
|
key=lambda x: getattr(x, attr_name))
|
||||||
|
|
||||||
|
|
||||||
|
def paginate_dict_list(li, key, limit=None, marker=None, include_marker=False):
|
||||||
|
"""Wrapper for paginate_list to handle lists of dicts paginated
|
||||||
|
based on a key.
|
||||||
|
"""
|
||||||
|
return paginate_list(li, limit=limit, marker=marker,
|
||||||
|
include_marker=include_marker,
|
||||||
|
key=lambda x: x[key])
|
||||||
|
|
||||||
|
|
||||||
class PaginatedDataView(object):
|
class PaginatedDataView(object):
|
||||||
|
@ -199,9 +199,7 @@ class CouchDBAdmin(object):
|
|||||||
if not type(self).admin_user:
|
if not type(self).admin_user:
|
||||||
creds = CouchDBCredentials()
|
creds = CouchDBCredentials()
|
||||||
creds.read(system.COUCHDB_ADMIN_CREDS_FILE)
|
creds.read(system.COUCHDB_ADMIN_CREDS_FILE)
|
||||||
user = models.CouchDBUser()
|
user = models.CouchDBUser(creds.username, creds.password)
|
||||||
user.name = creds.username
|
|
||||||
user.password = creds.password
|
|
||||||
type(self).admin_user = user
|
type(self).admin_user = user
|
||||||
return type(self).admin_user
|
return type(self).admin_user
|
||||||
|
|
||||||
@ -212,13 +210,15 @@ class CouchDBAdmin(object):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _is_modifiable_database(self, name):
|
||||||
|
return name not in cfg.get_ignored_dbs()
|
||||||
|
|
||||||
def create_user(self, users):
|
def create_user(self, users):
|
||||||
LOG.debug("Creating user(s) for accessing CouchDB database(s).")
|
LOG.debug("Creating user(s) for accessing CouchDB database(s).")
|
||||||
self._admin_user()
|
self._admin_user()
|
||||||
try:
|
try:
|
||||||
for item in users:
|
for item in users:
|
||||||
user = models.CouchDBUser()
|
user = models.CouchDBUser.deserialize_user(item)
|
||||||
user.deserialize(item)
|
|
||||||
try:
|
try:
|
||||||
LOG.debug("Creating user: %s." % user.name)
|
LOG.debug("Creating user: %s." % user.name)
|
||||||
utils.execute_with_timeout(
|
utils.execute_with_timeout(
|
||||||
@ -234,8 +234,7 @@ class CouchDBAdmin(object):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
for database in user.databases:
|
for database in user.databases:
|
||||||
mydb = models.CouchDBSchema()
|
mydb = models.CouchDBSchema.deserialize_schema(database)
|
||||||
mydb.deserialize(database)
|
|
||||||
try:
|
try:
|
||||||
LOG.debug("Granting user: %s access to database: %s."
|
LOG.debug("Granting user: %s access to database: %s."
|
||||||
% (user.name, mydb.name))
|
% (user.name, mydb.name))
|
||||||
@ -258,8 +257,7 @@ class CouchDBAdmin(object):
|
|||||||
|
|
||||||
def delete_user(self, user):
|
def delete_user(self, user):
|
||||||
LOG.debug("Delete a given CouchDB user.")
|
LOG.debug("Delete a given CouchDB user.")
|
||||||
couchdb_user = models.CouchDBUser()
|
couchdb_user = models.CouchDBUser.deserialize_user(user)
|
||||||
couchdb_user.deserialize(user)
|
|
||||||
db_names = self.list_database_names()
|
db_names = self.list_database_names()
|
||||||
|
|
||||||
for db in db_names:
|
for db in db_names:
|
||||||
@ -346,8 +344,7 @@ class CouchDBAdmin(object):
|
|||||||
elif uname[17:]:
|
elif uname[17:]:
|
||||||
userlist.append(uname[17:])
|
userlist.append(uname[17:])
|
||||||
for i in range(len(userlist)):
|
for i in range(len(userlist)):
|
||||||
user = models.CouchDBUser()
|
user = models.CouchDBUser(userlist[i])
|
||||||
user.name = userlist[i]
|
|
||||||
for db in db_names:
|
for db in db_names:
|
||||||
try:
|
try:
|
||||||
out2, err = utils.execute_with_timeout(
|
out2, err = utils.execute_with_timeout(
|
||||||
@ -381,8 +378,7 @@ class CouchDBAdmin(object):
|
|||||||
return user.serialize()
|
return user.serialize()
|
||||||
|
|
||||||
def _get_user(self, username, hostname):
|
def _get_user(self, username, hostname):
|
||||||
user = models.CouchDBUser()
|
user = models.CouchDBUser(username)
|
||||||
user.name = username
|
|
||||||
db_names = self.list_database_names()
|
db_names = self.list_database_names()
|
||||||
for db in db_names:
|
for db in db_names:
|
||||||
try:
|
try:
|
||||||
@ -413,8 +409,7 @@ class CouchDBAdmin(object):
|
|||||||
'Cannot grant access for non-existant user: '
|
'Cannot grant access for non-existant user: '
|
||||||
'%(user)s') % {'user': username})
|
'%(user)s') % {'user': username})
|
||||||
else:
|
else:
|
||||||
user = models.CouchDBUser()
|
user = models.CouchDBUser(username)
|
||||||
user.name = username
|
|
||||||
if not self._is_modifiable_user(user.name):
|
if not self._is_modifiable_user(user.name):
|
||||||
LOG.warning(_('Cannot grant access for reserved user '
|
LOG.warning(_('Cannot grant access for reserved user '
|
||||||
'%(user)s') % {'user': username})
|
'%(user)s') % {'user': username})
|
||||||
@ -462,12 +457,7 @@ class CouchDBAdmin(object):
|
|||||||
|
|
||||||
def enable_root(self, root_pwd=None):
|
def enable_root(self, root_pwd=None):
|
||||||
'''Create admin user root'''
|
'''Create admin user root'''
|
||||||
if not root_pwd:
|
root_user = models.CouchDBRootUser(password=root_pwd)
|
||||||
LOG.debug('Generating root user password.')
|
|
||||||
root_pwd = utils.generate_random_password()
|
|
||||||
root_user = models.CouchDBUser()
|
|
||||||
root_user.name = 'root'
|
|
||||||
root_user.password = root_pwd
|
|
||||||
out, err = utils.execute_with_timeout(
|
out, err = utils.execute_with_timeout(
|
||||||
system.ENABLE_ROOT %
|
system.ENABLE_ROOT %
|
||||||
{'admin_name': self._admin_user().name,
|
{'admin_name': self._admin_user().name,
|
||||||
@ -497,19 +487,24 @@ class CouchDBAdmin(object):
|
|||||||
|
|
||||||
for database in databases:
|
for database in databases:
|
||||||
dbName = models.CouchDBSchema.deserialize_schema(database).name
|
dbName = models.CouchDBSchema.deserialize_schema(database).name
|
||||||
LOG.debug('Creating CouchDB database %s' % dbName)
|
if self._is_modifiable_database(dbName):
|
||||||
try:
|
LOG.debug('Creating CouchDB database %s' % dbName)
|
||||||
utils.execute_with_timeout(
|
try:
|
||||||
system.CREATE_DB_COMMAND %
|
utils.execute_with_timeout(
|
||||||
{'admin_name': self._admin_user().name,
|
system.CREATE_DB_COMMAND %
|
||||||
'admin_password': self._admin_user().password,
|
{'admin_name': self._admin_user().name,
|
||||||
'dbname': dbName},
|
'admin_password': self._admin_user().password,
|
||||||
shell=True)
|
'dbname': dbName},
|
||||||
except exception.ProcessExecutionError:
|
shell=True)
|
||||||
LOG.exception(_(
|
except exception.ProcessExecutionError:
|
||||||
"There was an error creating database: %s.") % dbName)
|
LOG.exception(_(
|
||||||
|
"There was an error creating database: %s.") % dbName)
|
||||||
|
db_create_failed.append(dbName)
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
LOG.warning(_('Cannot create database with a reserved name '
|
||||||
|
'%(db)s') % {'db': dbName})
|
||||||
db_create_failed.append(dbName)
|
db_create_failed.append(dbName)
|
||||||
pass
|
|
||||||
if len(db_create_failed) > 0:
|
if len(db_create_failed) > 0:
|
||||||
LOG.exception(_("Creating the following databases failed: %s.") %
|
LOG.exception(_("Creating the following databases failed: %s.") %
|
||||||
db_create_failed)
|
db_create_failed)
|
||||||
@ -540,21 +535,24 @@ class CouchDBAdmin(object):
|
|||||||
|
|
||||||
def delete_database(self, database):
|
def delete_database(self, database):
|
||||||
'''Delete the specified database.'''
|
'''Delete the specified database.'''
|
||||||
dbName = None
|
dbName = models.CouchDBSchema.deserialize_schema(database).name
|
||||||
try:
|
if self._is_modifiable_database(dbName):
|
||||||
dbName = models.CouchDBSchema.deserialize_schema(database).name
|
try:
|
||||||
LOG.debug("Deleting CouchDB database: %s." % dbName)
|
LOG.debug("Deleting CouchDB database: %s." % dbName)
|
||||||
utils.execute_with_timeout(
|
utils.execute_with_timeout(
|
||||||
system.DELETE_DB_COMMAND %
|
system.DELETE_DB_COMMAND %
|
||||||
{'admin_name': self._admin_user().name,
|
{'admin_name': self._admin_user().name,
|
||||||
'admin_password': self._admin_user().password,
|
'admin_password': self._admin_user().password,
|
||||||
'dbname': dbName},
|
'dbname': dbName},
|
||||||
shell=True)
|
shell=True)
|
||||||
except exception.ProcessExecutionError:
|
except exception.ProcessExecutionError:
|
||||||
LOG.exception(_(
|
LOG.exception(_(
|
||||||
"There was an error while deleting database:%s.") % dbName)
|
"There was an error while deleting database:%s.") % dbName)
|
||||||
raise exception.GuestError(_("Unable to delete database: %s.") %
|
raise exception.GuestError(_("Unable to delete database: %s.")
|
||||||
dbName)
|
% dbName)
|
||||||
|
else:
|
||||||
|
LOG.warning(_('Cannot delete a reserved database '
|
||||||
|
'%(db)s') % {'db': dbName})
|
||||||
|
|
||||||
|
|
||||||
class CouchDBCredentials(object):
|
class CouchDBCredentials(object):
|
||||||
|
@ -26,10 +26,10 @@ from .service.status import PgSqlAppStatus
|
|||||||
|
|
||||||
from trove.common import cfg
|
from trove.common import cfg
|
||||||
from trove.common.notification import EndNotification
|
from trove.common.notification import EndNotification
|
||||||
from trove.common import utils
|
|
||||||
from trove.guestagent import backup
|
from trove.guestagent import backup
|
||||||
from trove.guestagent.datastore.experimental.postgresql import pgutil
|
from trove.guestagent.datastore.experimental.postgresql import pgutil
|
||||||
from trove.guestagent.datastore import manager
|
from trove.guestagent.datastore import manager
|
||||||
|
from trove.guestagent.db import models
|
||||||
from trove.guestagent import guest_log
|
from trove.guestagent import guest_log
|
||||||
from trove.guestagent import volume
|
from trove.guestagent import volume
|
||||||
|
|
||||||
@ -61,25 +61,24 @@ class Manager(
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def datastore_log_defs(self):
|
def datastore_log_defs(self):
|
||||||
owner = 'postgres'
|
|
||||||
datastore_dir = '/var/log/postgresql/'
|
datastore_dir = '/var/log/postgresql/'
|
||||||
long_query_time = CONF.get(self.manager).get(
|
long_query_time = CONF.get(self.manager).get(
|
||||||
'guest_log_long_query_time')
|
'guest_log_long_query_time')
|
||||||
general_log_file = self.build_log_file_name(
|
general_log_file = self.build_log_file_name(
|
||||||
self.GUEST_LOG_DEFS_GENERAL_LABEL, owner,
|
self.GUEST_LOG_DEFS_GENERAL_LABEL, self.PGSQL_OWNER,
|
||||||
datastore_dir=datastore_dir)
|
datastore_dir=datastore_dir)
|
||||||
general_log_dir, general_log_filename = os.path.split(general_log_file)
|
general_log_dir, general_log_filename = os.path.split(general_log_file)
|
||||||
return {
|
return {
|
||||||
self.GUEST_LOG_DEFS_GENERAL_LABEL: {
|
self.GUEST_LOG_DEFS_GENERAL_LABEL: {
|
||||||
self.GUEST_LOG_TYPE_LABEL: guest_log.LogType.USER,
|
self.GUEST_LOG_TYPE_LABEL: guest_log.LogType.USER,
|
||||||
self.GUEST_LOG_USER_LABEL: owner,
|
self.GUEST_LOG_USER_LABEL: self.PGSQL_OWNER,
|
||||||
self.GUEST_LOG_FILE_LABEL: general_log_file,
|
self.GUEST_LOG_FILE_LABEL: general_log_file,
|
||||||
self.GUEST_LOG_ENABLE_LABEL: {
|
self.GUEST_LOG_ENABLE_LABEL: {
|
||||||
'logging_collector': 'on',
|
'logging_collector': 'on',
|
||||||
'log_destination': self._quote_str('stderr'),
|
'log_destination': self._quote('stderr'),
|
||||||
'log_directory': self._quote_str(general_log_dir),
|
'log_directory': self._quote(general_log_dir),
|
||||||
'log_filename': self._quote_str(general_log_filename),
|
'log_filename': self._quote(general_log_filename),
|
||||||
'log_statement': self._quote_str('all'),
|
'log_statement': self._quote('all'),
|
||||||
'debug_print_plan': 'on',
|
'debug_print_plan': 'on',
|
||||||
'log_min_duration_statement': long_query_time,
|
'log_min_duration_statement': long_query_time,
|
||||||
},
|
},
|
||||||
@ -90,9 +89,6 @@ class Manager(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def _quote_str(self, value):
|
|
||||||
return "'%s'" % value
|
|
||||||
|
|
||||||
def do_prepare(self, context, packages, databases, memory_mb, users,
|
def do_prepare(self, context, packages, databases, memory_mb, users,
|
||||||
device_path, mount_point, backup_info, config_contents,
|
device_path, mount_point, backup_info, config_contents,
|
||||||
root_password, overrides, cluster_config, snapshot):
|
root_password, overrides, cluster_config, snapshot):
|
||||||
@ -118,11 +114,11 @@ class Manager(
|
|||||||
def _secure(self, context):
|
def _secure(self, context):
|
||||||
# Create a new administrative user for Trove and also
|
# Create a new administrative user for Trove and also
|
||||||
# disable the built-in superuser.
|
# disable the built-in superuser.
|
||||||
self.create_database(context, [{'_name': self.ADMIN_USER}])
|
os_admin_db = models.PostgreSQLSchema(self.ADMIN_USER)
|
||||||
self._create_admin_user(context)
|
self._create_database(context, os_admin_db)
|
||||||
|
self._create_admin_user(context, databases=[os_admin_db])
|
||||||
pgutil.PG_ADMIN = self.ADMIN_USER
|
pgutil.PG_ADMIN = self.ADMIN_USER
|
||||||
postgres = {'_name': self.PG_BUILTIN_ADMIN,
|
postgres = models.PostgreSQLRootUser()
|
||||||
'_password': utils.generate_random_password()}
|
|
||||||
self.alter_user(context, postgres, 'NOSUPERUSER', 'NOLOGIN')
|
self.alter_user(context, postgres, 'NOSUPERUSER', 'NOLOGIN')
|
||||||
|
|
||||||
def create_backup(self, context, backup_info):
|
def create_backup(self, context, backup_info):
|
||||||
|
@ -207,7 +207,10 @@ class UserQuery(object):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def update_name(cls, old, new):
|
def update_name(cls, old, new):
|
||||||
"""Query to update the name of a user."""
|
"""Query to update the name of a user.
|
||||||
|
This statement also results in an automatic permission transfer to the
|
||||||
|
new username.
|
||||||
|
"""
|
||||||
|
|
||||||
return "ALTER USER \"{old}\" RENAME TO \"{new}\"".format(
|
return "ALTER USER \"{old}\" RENAME TO \"{new}\"".format(
|
||||||
old=old,
|
old=old,
|
||||||
@ -231,7 +234,8 @@ class AccessQuery(object):
|
|||||||
"SELECT datname, pg_encoding_to_char(encoding), datcollate "
|
"SELECT datname, pg_encoding_to_char(encoding), datcollate "
|
||||||
"FROM pg_database "
|
"FROM pg_database "
|
||||||
"WHERE datistemplate = false "
|
"WHERE datistemplate = false "
|
||||||
"AND 'user {user}=CTc' = ANY (datacl)".format(user=user)
|
"AND 'user \"{user}\"=CTc/{admin}' = ANY (datacl)".format(
|
||||||
|
user=user, admin=PG_ADMIN)
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -18,6 +18,7 @@ from oslo_log import log as logging
|
|||||||
from trove.common import cfg
|
from trove.common import cfg
|
||||||
from trove.common.i18n import _
|
from trove.common.i18n import _
|
||||||
from trove.guestagent.datastore.experimental.postgresql import pgutil
|
from trove.guestagent.datastore.experimental.postgresql import pgutil
|
||||||
|
from trove.guestagent.db import models
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
@ -73,21 +74,20 @@ class PgSqlAccess(object):
|
|||||||
|
|
||||||
def list_access(self, context, username, hostname):
|
def list_access(self, context, username, hostname):
|
||||||
"""List database for which the given user as access.
|
"""List database for which the given user as access.
|
||||||
|
Return a list of serialized Postgres databases.
|
||||||
The username and hostname parameters are strings.
|
|
||||||
|
|
||||||
Return value is a list of dictionaries in the following form:
|
|
||||||
|
|
||||||
[{"_name": "", "_collate": None, "_character_set": None}, ...]
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if self.user_exists(username):
|
||||||
|
return [db.serialize() for db in self._get_databases_for(username)]
|
||||||
|
|
||||||
|
raise exception.UserNotFound(username)
|
||||||
|
|
||||||
|
def _get_databases_for(self, username):
|
||||||
|
"""Return all Postgres databases accessible by a given user."""
|
||||||
results = pgutil.query(
|
results = pgutil.query(
|
||||||
pgutil.AccessQuery.list(user=username),
|
pgutil.AccessQuery.list(user=username),
|
||||||
timeout=30,
|
timeout=30,
|
||||||
)
|
)
|
||||||
|
return [models.PostgreSQLSchema(
|
||||||
# Convert to dictionaries.
|
row[0].strip(), character_set=row[1], collate=row[2])
|
||||||
results = (
|
for row in results]
|
||||||
{'_name': r[0].strip(), '_collate': None, '_character_set': None}
|
|
||||||
for r in results
|
|
||||||
)
|
|
||||||
return tuple(results)
|
|
||||||
|
@ -13,14 +13,14 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import itertools
|
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
from trove.common import cfg
|
from trove.common import cfg
|
||||||
from trove.common.i18n import _
|
from trove.common.i18n import _
|
||||||
from trove.common.notification import EndNotification
|
from trove.common.notification import EndNotification
|
||||||
|
from trove.common import pagination
|
||||||
from trove.guestagent.datastore.experimental.postgresql import pgutil
|
from trove.guestagent.datastore.experimental.postgresql import pgutil
|
||||||
|
from trove.guestagent.db import models
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
@ -34,51 +34,58 @@ class PgSqlDatabase(object):
|
|||||||
def create_database(self, context, databases):
|
def create_database(self, context, databases):
|
||||||
"""Create the list of specified databases.
|
"""Create the list of specified databases.
|
||||||
|
|
||||||
The databases parameter is a list of dictionaries in the following
|
The databases parameter is a list of serialized Postgres databases.
|
||||||
form:
|
|
||||||
|
|
||||||
{"_name": "", "_character_set": "", "_collate": ""}
|
|
||||||
|
|
||||||
Encoding and collation values are validated in
|
|
||||||
trove.guestagent.db.models.
|
|
||||||
"""
|
"""
|
||||||
with EndNotification(context):
|
with EndNotification(context):
|
||||||
for database in databases:
|
for database in databases:
|
||||||
encoding = database.get('_character_set')
|
self._create_database(
|
||||||
collate = database.get('_collate')
|
context,
|
||||||
LOG.info(
|
models.PostgreSQLSchema.deserialize_schema(database))
|
||||||
_("{guest_id}: Creating database {name}.").format(
|
|
||||||
guest_id=CONF.guest_id,
|
def _create_database(self, context, database):
|
||||||
name=database['_name'],
|
"""Create a database.
|
||||||
)
|
|
||||||
)
|
:param database: Database to be created.
|
||||||
pgutil.psql(
|
:type database: PostgreSQLSchema
|
||||||
pgutil.DatabaseQuery.create(
|
"""
|
||||||
name=database['_name'],
|
LOG.info(
|
||||||
encoding=encoding,
|
_("{guest_id}: Creating database {name}.").format(
|
||||||
collation=collate,
|
guest_id=CONF.guest_id,
|
||||||
),
|
name=database.name,
|
||||||
timeout=30,
|
)
|
||||||
)
|
)
|
||||||
|
pgutil.psql(
|
||||||
|
pgutil.DatabaseQuery.create(
|
||||||
|
name=database.name,
|
||||||
|
encoding=database.character_set,
|
||||||
|
collation=database.collate,
|
||||||
|
),
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
|
|
||||||
def delete_database(self, context, database):
|
def delete_database(self, context, database):
|
||||||
"""Delete the specified database.
|
"""Delete the specified database.
|
||||||
|
|
||||||
The database parameter is a dictionary in the following form:
|
|
||||||
|
|
||||||
{"_name": ""}
|
|
||||||
"""
|
"""
|
||||||
with EndNotification(context):
|
with EndNotification(context):
|
||||||
LOG.info(
|
self._drop_database(
|
||||||
_("{guest_id}: Dropping database {name}.").format(
|
models.PostgreSQLSchema.deserialize_schema(database))
|
||||||
guest_id=CONF.guest_id,
|
|
||||||
name=database['_name'],
|
def _drop_database(self, database):
|
||||||
)
|
"""Drop a given Postgres database.
|
||||||
)
|
|
||||||
pgutil.psql(
|
:param database: Database to be dropped.
|
||||||
pgutil.DatabaseQuery.drop(name=database['_name']),
|
:type database: PostgreSQLSchema
|
||||||
timeout=30,
|
"""
|
||||||
|
LOG.info(
|
||||||
|
_("{guest_id}: Dropping database {name}.").format(
|
||||||
|
guest_id=CONF.guest_id,
|
||||||
|
name=database.name,
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
pgutil.psql(
|
||||||
|
pgutil.DatabaseQuery.drop(name=database.name),
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
|
|
||||||
def list_databases(
|
def list_databases(
|
||||||
self,
|
self,
|
||||||
@ -87,42 +94,19 @@ class PgSqlDatabase(object):
|
|||||||
marker=None,
|
marker=None,
|
||||||
include_marker=False,
|
include_marker=False,
|
||||||
):
|
):
|
||||||
"""List databases created on this instance.
|
"""List all databases on the instance.
|
||||||
|
Return a paginated list of serialized Postgres databases.
|
||||||
Return value is a list of dictionaries in the following form:
|
|
||||||
|
|
||||||
[{"_name": "", "_character_set": "", "_collate": ""}, ...]
|
|
||||||
"""
|
"""
|
||||||
|
page, next_name = pagination.paginate_object_list(
|
||||||
|
self._get_databases(), 'name', limit, marker, include_marker)
|
||||||
|
return [db.serialize() for db in page], next_name
|
||||||
|
|
||||||
|
def _get_databases(self):
|
||||||
|
"""Return all non-system Postgres databases on the instance."""
|
||||||
results = pgutil.query(
|
results = pgutil.query(
|
||||||
pgutil.DatabaseQuery.list(ignore=cfg.get_ignored_dbs()),
|
pgutil.DatabaseQuery.list(ignore=cfg.get_ignored_dbs()),
|
||||||
timeout=30,
|
timeout=30,
|
||||||
)
|
)
|
||||||
# Convert results to dictionaries.
|
return [models.PostgreSQLSchema(
|
||||||
results = (
|
row[0].strip(), character_set=row[1], collate=row[2])
|
||||||
{'_name': r[0].strip(), '_character_set': r[1], '_collate': r[2]}
|
for row in results]
|
||||||
for r in results
|
|
||||||
)
|
|
||||||
# Force __iter__ of generator until marker found.
|
|
||||||
if marker is not None:
|
|
||||||
try:
|
|
||||||
item = next(results)
|
|
||||||
while item['_name'] != marker:
|
|
||||||
item = next(results)
|
|
||||||
except StopIteration:
|
|
||||||
pass
|
|
||||||
|
|
||||||
remainder = None
|
|
||||||
if limit is not None:
|
|
||||||
remainder = results
|
|
||||||
results = itertools.islice(results, limit)
|
|
||||||
|
|
||||||
results = tuple(results)
|
|
||||||
|
|
||||||
next_marker = None
|
|
||||||
if remainder is not None:
|
|
||||||
try:
|
|
||||||
next_marker = next(remainder)
|
|
||||||
except StopIteration:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return results, next_marker
|
|
||||||
|
@ -14,10 +14,10 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from trove.common import cfg
|
from trove.common import cfg
|
||||||
from trove.common import utils
|
|
||||||
from trove.guestagent.datastore.experimental.postgresql import pgutil
|
from trove.guestagent.datastore.experimental.postgresql import pgutil
|
||||||
from trove.guestagent.datastore.experimental.postgresql.service.users import (
|
from trove.guestagent.datastore.experimental.postgresql.service.users import (
|
||||||
PgSqlUsers)
|
PgSqlUsers)
|
||||||
|
from trove.guestagent.db import models
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
@ -72,18 +72,15 @@ class PgSqlRoot(PgSqlUsers):
|
|||||||
|
|
||||||
{"_name": "postgres", "_password": "<secret>"}
|
{"_name": "postgres", "_password": "<secret>"}
|
||||||
"""
|
"""
|
||||||
user = {
|
user = models.PostgreSQLRootUser(password=root_password)
|
||||||
"_name": "postgres",
|
|
||||||
"_password": root_password or utils.generate_random_password(),
|
|
||||||
}
|
|
||||||
query = pgutil.UserQuery.alter_user(
|
query = pgutil.UserQuery.alter_user(
|
||||||
user['_name'],
|
user.name,
|
||||||
user['_password'],
|
user.password,
|
||||||
None,
|
None,
|
||||||
*self.ADMIN_OPTIONS
|
*self.ADMIN_OPTIONS
|
||||||
)
|
)
|
||||||
pgutil.psql(query, timeout=30)
|
pgutil.psql(query, timeout=30)
|
||||||
return user
|
return user.serialize()
|
||||||
|
|
||||||
def disable_root(self, context):
|
def disable_root(self, context):
|
||||||
"""Generate a new random password for the public superuser account.
|
"""Generate a new random password for the public superuser account.
|
||||||
@ -93,4 +90,4 @@ class PgSqlRoot(PgSqlUsers):
|
|||||||
self.enable_root(context)
|
self.enable_root(context)
|
||||||
|
|
||||||
def enable_root_with_password(self, context, root_password=None):
|
def enable_root_with_password(self, context, root_password=None):
|
||||||
self.enable_root(context, root_password)
|
return self.enable_root(context, root_password)
|
||||||
|
@ -13,17 +13,19 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import itertools
|
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
from trove.common import cfg
|
from trove.common import cfg
|
||||||
|
from trove.common import exception
|
||||||
from trove.common.i18n import _
|
from trove.common.i18n import _
|
||||||
from trove.common.notification import EndNotification
|
from trove.common.notification import EndNotification
|
||||||
|
from trove.common import pagination
|
||||||
from trove.common import utils
|
from trove.common import utils
|
||||||
from trove.guestagent.datastore.experimental.postgresql import pgutil
|
from trove.guestagent.datastore.experimental.postgresql import pgutil
|
||||||
from trove.guestagent.datastore.experimental.postgresql.service.access import (
|
from trove.guestagent.datastore.experimental.postgresql.service.access import (
|
||||||
PgSqlAccess)
|
PgSqlAccess)
|
||||||
|
from trove.guestagent.db import models
|
||||||
|
from trove.guestagent.db.models import PostgreSQLSchema
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
@ -51,32 +53,46 @@ class PgSqlUsers(PgSqlAccess):
|
|||||||
'REPLICATION',
|
'REPLICATION',
|
||||||
'LOGIN']
|
'LOGIN']
|
||||||
|
|
||||||
def _create_admin_user(self, context):
|
def _create_admin_user(self, context, databases=None):
|
||||||
"""Create an administrative user for Trove.
|
"""Create an administrative user for Trove.
|
||||||
Force password encryption.
|
Force password encryption.
|
||||||
"""
|
"""
|
||||||
password = utils.generate_random_password()
|
password = utils.generate_random_password()
|
||||||
os_admin = {'_name': self.ADMIN_USER, '_password': password,
|
os_admin = models.PostgreSQLUser(self.ADMIN_USER, password)
|
||||||
'_databases': [{'_name': self.ADMIN_USER}]}
|
if databases:
|
||||||
|
os_admin.databases.extend([db.serialize() for db in databases])
|
||||||
self._create_user(context, os_admin, True, *self.ADMIN_OPTIONS)
|
self._create_user(context, os_admin, True, *self.ADMIN_OPTIONS)
|
||||||
|
|
||||||
def create_user(self, context, users):
|
def create_user(self, context, users):
|
||||||
"""Create users and grant privileges for the specified databases.
|
"""Create users and grant privileges for the specified databases.
|
||||||
|
|
||||||
The users parameter is a list of dictionaries in the following form:
|
The users parameter is a list of serialized Postgres users.
|
||||||
|
|
||||||
{"_name": "", "_password": "", "_databases": [{"_name": ""}, ...]}
|
|
||||||
"""
|
"""
|
||||||
with EndNotification(context):
|
with EndNotification(context):
|
||||||
for user in users:
|
for user in users:
|
||||||
self._create_user(context, user, None)
|
self._create_user(
|
||||||
|
context,
|
||||||
|
models.PostgreSQLUser.deserialize_user(user), None)
|
||||||
|
|
||||||
def _create_user(self, context, user, encrypt_password=None, *options):
|
def _create_user(self, context, user, encrypt_password=None, *options):
|
||||||
|
"""Create a user and grant privileges for the specified databases.
|
||||||
|
|
||||||
|
:param user: User to be created.
|
||||||
|
:type user: PostgreSQLUser
|
||||||
|
|
||||||
|
:param encrypt_password: Store passwords encrypted if True.
|
||||||
|
Fallback to configured default
|
||||||
|
behavior if None.
|
||||||
|
:type encrypt_password: boolean
|
||||||
|
|
||||||
|
:param options: Other user options.
|
||||||
|
:type options: list
|
||||||
|
"""
|
||||||
LOG.info(
|
LOG.info(
|
||||||
_("{guest_id}: Creating user {user} {with_clause}.")
|
_("{guest_id}: Creating user {user} {with_clause}.")
|
||||||
.format(
|
.format(
|
||||||
guest_id=CONF.guest_id,
|
guest_id=CONF.guest_id,
|
||||||
user=user['_name'],
|
user=user.name,
|
||||||
with_clause=pgutil.UserQuery._build_with_clause(
|
with_clause=pgutil.UserQuery._build_with_clause(
|
||||||
'<SANITIZED>',
|
'<SANITIZED>',
|
||||||
encrypt_password,
|
encrypt_password,
|
||||||
@ -86,18 +102,23 @@ class PgSqlUsers(PgSqlAccess):
|
|||||||
)
|
)
|
||||||
pgutil.psql(
|
pgutil.psql(
|
||||||
pgutil.UserQuery.create(
|
pgutil.UserQuery.create(
|
||||||
user['_name'],
|
user.name,
|
||||||
user['_password'],
|
user.password,
|
||||||
encrypt_password,
|
encrypt_password,
|
||||||
*options
|
*options
|
||||||
),
|
),
|
||||||
timeout=30,
|
timeout=30,
|
||||||
)
|
)
|
||||||
|
self._grant_access(
|
||||||
|
context, user.name,
|
||||||
|
[PostgreSQLSchema.deserialize_schema(db) for db in user.databases])
|
||||||
|
|
||||||
|
def _grant_access(self, context, username, databases):
|
||||||
self.grant_access(
|
self.grant_access(
|
||||||
context,
|
context,
|
||||||
user['_name'],
|
username,
|
||||||
None,
|
None,
|
||||||
[d['_name'] for d in user['_databases']],
|
[db.name for db in databases],
|
||||||
)
|
)
|
||||||
|
|
||||||
def list_users(
|
def list_users(
|
||||||
@ -108,121 +129,111 @@ class PgSqlUsers(PgSqlAccess):
|
|||||||
include_marker=False,
|
include_marker=False,
|
||||||
):
|
):
|
||||||
"""List all users on the instance along with their access permissions.
|
"""List all users on the instance along with their access permissions.
|
||||||
|
Return a paginated list of serialized Postgres users.
|
||||||
Return value is a list of dictionaries in the following form:
|
|
||||||
|
|
||||||
[{"_name": "", "_password": None, "_host": None,
|
|
||||||
"_databases": [{"_name": ""}, ...]}, ...]
|
|
||||||
"""
|
"""
|
||||||
|
page, next_name = pagination.paginate_object_list(
|
||||||
|
self._get_users(context), 'name', limit, marker, include_marker)
|
||||||
|
return [db.serialize() for db in page], next_name
|
||||||
|
|
||||||
|
def _get_users(self, context):
|
||||||
|
"""Return all non-system Postgres users on the instance."""
|
||||||
results = pgutil.query(
|
results = pgutil.query(
|
||||||
pgutil.UserQuery.list(ignore=cfg.get_ignored_users()),
|
pgutil.UserQuery.list(ignore=cfg.get_ignored_users()),
|
||||||
timeout=30,
|
timeout=30,
|
||||||
)
|
)
|
||||||
# Convert results into dictionaries.
|
return [self._build_user(context, row[0].strip()) for row in results]
|
||||||
results = (
|
|
||||||
{
|
|
||||||
'_name': r[0].strip(),
|
|
||||||
'_password': None,
|
|
||||||
'_host': None,
|
|
||||||
'_databases': self.list_access(context, r[0], None),
|
|
||||||
}
|
|
||||||
for r in results
|
|
||||||
)
|
|
||||||
|
|
||||||
# Force __iter__ of generator until marker found.
|
def _build_user(self, context, username):
|
||||||
if marker is not None:
|
"""Build a model representation of a Postgres user.
|
||||||
try:
|
Include all databases it has access to.
|
||||||
item = next(results)
|
"""
|
||||||
while item['_name'] != marker:
|
user = models.PostgreSQLUser(username)
|
||||||
item = next(results)
|
dbs = self.list_access(context, username, None)
|
||||||
except StopIteration:
|
for d in dbs:
|
||||||
pass
|
user.databases.append(d)
|
||||||
|
return user
|
||||||
remainder = None
|
|
||||||
if limit is not None:
|
|
||||||
remainder = results
|
|
||||||
results = itertools.islice(results, limit)
|
|
||||||
|
|
||||||
results = tuple(results)
|
|
||||||
|
|
||||||
next_marker = None
|
|
||||||
if remainder is not None:
|
|
||||||
try:
|
|
||||||
next_marker = next(remainder)
|
|
||||||
except StopIteration:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return results, next_marker
|
|
||||||
|
|
||||||
def delete_user(self, context, user):
|
def delete_user(self, context, user):
|
||||||
"""Delete the specified user.
|
"""Delete the specified user.
|
||||||
|
|
||||||
The user parameter is a dictionary in the following form:
|
|
||||||
|
|
||||||
{"_name": ""}
|
|
||||||
"""
|
"""
|
||||||
with EndNotification(context):
|
with EndNotification(context):
|
||||||
LOG.info(
|
self._drop_user(models.PostgreSQLUser.deserialize_user(user))
|
||||||
_("{guest_id}: Dropping user {name}.").format(
|
|
||||||
guest_id=CONF.guest_id,
|
def _drop_user(self, user):
|
||||||
name=user['_name'],
|
"""Drop a given Postgres user.
|
||||||
)
|
|
||||||
)
|
:param user: User to be dropped.
|
||||||
pgutil.psql(
|
:type user: PostgreSQLUser
|
||||||
pgutil.UserQuery.drop(name=user['_name']),
|
"""
|
||||||
timeout=30,
|
LOG.info(
|
||||||
|
_("{guest_id}: Dropping user {name}.").format(
|
||||||
|
guest_id=CONF.guest_id,
|
||||||
|
name=user.name,
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
pgutil.psql(
|
||||||
|
pgutil.UserQuery.drop(name=user.name),
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
|
|
||||||
def get_user(self, context, username, hostname):
|
def get_user(self, context, username, hostname):
|
||||||
"""Return a single user matching the criteria.
|
"""Return a serialized representation of a user with a given name.
|
||||||
|
"""
|
||||||
|
user = self._find_user(context, username)
|
||||||
|
return user.serialize() if user is not None else None
|
||||||
|
|
||||||
The username and hostname parameter are strings.
|
def _find_user(self, context, username):
|
||||||
|
"""Lookup a user with a given username.
|
||||||
The return value is a dictionary in the following form:
|
Return a new Postgres user instance or raise if no match is found.
|
||||||
|
|
||||||
{"_name": "", "_host": None, "_password": None,
|
|
||||||
"_databases": [{"_name": ""}, ...]}
|
|
||||||
|
|
||||||
Where "_databases" is a list of databases the user has access to.
|
|
||||||
"""
|
"""
|
||||||
results = pgutil.query(
|
results = pgutil.query(
|
||||||
pgutil.UserQuery.get(name=username),
|
pgutil.UserQuery.get(name=username),
|
||||||
timeout=30,
|
timeout=30,
|
||||||
)
|
)
|
||||||
results = tuple(results)
|
|
||||||
if len(results) < 1:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return {
|
if results:
|
||||||
"_name": results[0][0],
|
return self._build_user(context, username)
|
||||||
"_host": None,
|
|
||||||
"_password": None,
|
return None
|
||||||
"_databases": self.list_access(context, username, None),
|
|
||||||
}
|
def user_exists(self, username):
|
||||||
|
"""Return whether a given user exists on the instance."""
|
||||||
|
results = pgutil.query(
|
||||||
|
pgutil.UserQuery.get(name=username),
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
|
|
||||||
|
return bool(results)
|
||||||
|
|
||||||
def change_passwords(self, context, users):
|
def change_passwords(self, context, users):
|
||||||
"""Change the passwords of one or more existing users.
|
"""Change the passwords of one or more existing users.
|
||||||
|
The users parameter is a list of serialized Postgres users.
|
||||||
The users parameter is a list of dictionaries in the following form:
|
|
||||||
|
|
||||||
{"name": "", "password": ""}
|
|
||||||
"""
|
"""
|
||||||
with EndNotification(context):
|
with EndNotification(context):
|
||||||
for user in users:
|
for user in users:
|
||||||
self.alter_user(context, user, None)
|
self.alter_user(
|
||||||
|
context,
|
||||||
|
models.PostgreSQLUser.deserialize_user(user), None)
|
||||||
|
|
||||||
def alter_user(self, context, user, encrypt_password=None, *options):
|
def alter_user(self, context, user, encrypt_password=None, *options):
|
||||||
"""Change the password and options of an existing users.
|
"""Change the password and options of an existing users.
|
||||||
|
|
||||||
The user parameter is a dictionary of the following form:
|
:param user: User to be altered.
|
||||||
|
:type user: PostgreSQLUser
|
||||||
|
|
||||||
{"name": "", "password": ""}
|
:param encrypt_password: Store passwords encrypted if True.
|
||||||
|
Fallback to configured default
|
||||||
|
behavior if None.
|
||||||
|
:type encrypt_password: boolean
|
||||||
|
|
||||||
|
:param options: Other user options.
|
||||||
|
:type options: list
|
||||||
"""
|
"""
|
||||||
LOG.info(
|
LOG.info(
|
||||||
_("{guest_id}: Altering user {user} {with_clause}.")
|
_("{guest_id}: Altering user {user} {with_clause}.")
|
||||||
.format(
|
.format(
|
||||||
guest_id=CONF.guest_id,
|
guest_id=CONF.guest_id,
|
||||||
user=user['_name'],
|
user=user.name,
|
||||||
with_clause=pgutil.UserQuery._build_with_clause(
|
with_clause=pgutil.UserQuery._build_with_clause(
|
||||||
'<SANITIZED>',
|
'<SANITIZED>',
|
||||||
encrypt_password,
|
encrypt_password,
|
||||||
@ -232,8 +243,8 @@ class PgSqlUsers(PgSqlAccess):
|
|||||||
)
|
)
|
||||||
pgutil.psql(
|
pgutil.psql(
|
||||||
pgutil.UserQuery.alter_user(
|
pgutil.UserQuery.alter_user(
|
||||||
user['_name'],
|
user.name,
|
||||||
user['_password'],
|
user.password,
|
||||||
encrypt_password,
|
encrypt_password,
|
||||||
*options),
|
*options),
|
||||||
timeout=30,
|
timeout=30,
|
||||||
@ -250,47 +261,42 @@ class PgSqlUsers(PgSqlAccess):
|
|||||||
Each key/value pair in user_attrs is optional.
|
Each key/value pair in user_attrs is optional.
|
||||||
"""
|
"""
|
||||||
with EndNotification(context):
|
with EndNotification(context):
|
||||||
if user_attrs.get('password') is not None:
|
user = self._build_user(context, username)
|
||||||
self.change_passwords(
|
new_username = user_attrs.get('name')
|
||||||
context,
|
new_password = user_attrs.get('password')
|
||||||
(
|
|
||||||
{
|
|
||||||
"name": username,
|
|
||||||
"password": user_attrs['password'],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
if user_attrs.get('name') is not None:
|
if new_username is not None:
|
||||||
access = self.list_access(context, username, None)
|
self._rename_user(context, user, new_username)
|
||||||
LOG.info(
|
# Make sure we can retrieve the renamed user.
|
||||||
_("{guest_id}: Changing username for {old} to {new}."
|
user = self._find_user(context, new_username)
|
||||||
).format(
|
if user is None:
|
||||||
guest_id=CONF.guest_id,
|
raise exception.TroveError(_(
|
||||||
old=username,
|
"Renamed user %s could not be found on the instance.")
|
||||||
new=user_attrs['name'],
|
% new_username)
|
||||||
)
|
|
||||||
)
|
if new_password is not None:
|
||||||
pgutil.psql(
|
user.password = new_password
|
||||||
pgutil.psql.UserQuery.update_name(
|
self.alter_user(context, user)
|
||||||
old=username,
|
|
||||||
new=user_attrs['name'],
|
def _rename_user(self, context, user, new_username):
|
||||||
),
|
"""Rename a given Postgres user and transfer all access to the
|
||||||
timeout=30,
|
new name.
|
||||||
)
|
|
||||||
# Regrant all previous access after the name change.
|
:param user: User to be renamed.
|
||||||
LOG.info(
|
:type user: PostgreSQLUser
|
||||||
_("{guest_id}: Regranting permissions from {old} "
|
"""
|
||||||
"to {new}.")
|
LOG.info(
|
||||||
.format(
|
_("{guest_id}: Changing username for {old} to {new}.").format(
|
||||||
guest_id=CONF.guest_id,
|
guest_id=CONF.guest_id,
|
||||||
old=username,
|
old=user.name,
|
||||||
new=user_attrs['name'],
|
new=new_username,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.grant_access(
|
# PostgreSQL handles the permission transfer itself.
|
||||||
context,
|
pgutil.psql(
|
||||||
username=user_attrs['name'],
|
pgutil.UserQuery.update_name(
|
||||||
hostname=None,
|
old=user.name,
|
||||||
databases=(db['_name'] for db in access)
|
new=new_username,
|
||||||
)
|
),
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
|
@ -53,11 +53,23 @@ class Base(object):
|
|||||||
class DatastoreSchema(Base):
|
class DatastoreSchema(Base):
|
||||||
"""Represents a database schema."""
|
"""Represents a database schema."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, name, deserializing=False, *args, **kwargs):
|
||||||
self._name = None
|
self._name = None
|
||||||
self._collate = None
|
self._collate = None
|
||||||
self._character_set = None
|
self._character_set = None
|
||||||
|
|
||||||
|
# If both or neither are passed in this is a bug.
|
||||||
|
if not (bool(deserializing) != bool(name)):
|
||||||
|
raise RuntimeError("Bug in DatastoreSchema()")
|
||||||
|
if not deserializing:
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.name)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return str(self.serialize())
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def deserialize_schema(cls, value):
|
def deserialize_schema(cls, value):
|
||||||
if not cls._validate_dict(value):
|
if not cls._validate_dict(value):
|
||||||
@ -65,7 +77,7 @@ class DatastoreSchema(Base):
|
|||||||
"Required: %(reqs)s")
|
"Required: %(reqs)s")
|
||||||
% ({'keys': value.keys(),
|
% ({'keys': value.keys(),
|
||||||
'reqs': cls._dict_requirements()}))
|
'reqs': cls._dict_requirements()}))
|
||||||
schema = cls(deserializing=True)
|
schema = cls(name=None, deserializing=True)
|
||||||
schema.deserialize(value)
|
schema.deserialize(value)
|
||||||
return schema
|
return schema
|
||||||
|
|
||||||
@ -133,16 +145,8 @@ class MongoDBSchema(DatastoreSchema):
|
|||||||
|
|
||||||
name_regex = re.compile(r'^[a-zA-Z0-9_\-]+$')
|
name_regex = re.compile(r'^[a-zA-Z0-9_\-]+$')
|
||||||
|
|
||||||
def __init__(self, name=None, deserializing=False):
|
def __init__(self, name, *args, **kwargs):
|
||||||
super(MongoDBSchema, self).__init__()
|
super(MongoDBSchema, self).__init__(name, *args, **kwargs)
|
||||||
# need one or the other, not both, not none (!= ~ XOR)
|
|
||||||
if not (bool(deserializing) != bool(name)):
|
|
||||||
raise ValueError(_("Bad args. name: %(name)s, "
|
|
||||||
"deserializing %(deser)s.")
|
|
||||||
% ({'name': bool(name),
|
|
||||||
'deser': bool(deserializing)}))
|
|
||||||
if not deserializing:
|
|
||||||
self.name = name
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _max_schema_name_length(self):
|
def _max_schema_name_length(self):
|
||||||
@ -165,16 +169,8 @@ class CassandraSchema(DatastoreSchema):
|
|||||||
the first of which is an alpha character.
|
the first of which is an alpha character.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name=None, deserializing=False):
|
def __init__(self, name, *args, **kwargs):
|
||||||
super(CassandraSchema, self).__init__()
|
super(CassandraSchema, self).__init__(name, *args, **kwargs)
|
||||||
|
|
||||||
if not (bool(deserializing) != bool(name)):
|
|
||||||
raise ValueError(_("Bad args. name: %(name)s, "
|
|
||||||
"deserializing %(deser)s.")
|
|
||||||
% ({'name': bool(name),
|
|
||||||
'deser': bool(deserializing)}))
|
|
||||||
if not deserializing:
|
|
||||||
self.name = name
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _max_schema_name_length(self):
|
def _max_schema_name_length(self):
|
||||||
@ -201,17 +197,8 @@ class CouchDBSchema(DatastoreSchema):
|
|||||||
|
|
||||||
name_regex = re.compile(r'^[a-z][a-z0-9_$()+/-]*$')
|
name_regex = re.compile(r'^[a-z][a-z0-9_$()+/-]*$')
|
||||||
|
|
||||||
def __init__(self, name=None, deserializing=False):
|
def __init__(self, name, *args, **kwargs):
|
||||||
super(CouchDBSchema, self).__init__()
|
super(CouchDBSchema, self).__init__(name, *args, **kwargs)
|
||||||
self._ignore_dbs = cfg.get_ignored_dbs()
|
|
||||||
# need one or the other, not both, not none (!= ~ XOR)
|
|
||||||
if not (bool(deserializing) != bool(name)):
|
|
||||||
raise ValueError(_("Bad args. name: %(name)s, "
|
|
||||||
"deserializing %(deser)s.")
|
|
||||||
% ({'name': bool(name),
|
|
||||||
'deser': bool(deserializing)}))
|
|
||||||
if not deserializing:
|
|
||||||
self.name = name
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _max_schema_name_length(self):
|
def _max_schema_name_length(self):
|
||||||
@ -219,8 +206,6 @@ class CouchDBSchema(DatastoreSchema):
|
|||||||
|
|
||||||
def _is_valid_schema_name(self, value):
|
def _is_valid_schema_name(self, value):
|
||||||
# https://wiki.apache.org/couchdb/HTTP_database_API
|
# https://wiki.apache.org/couchdb/HTTP_database_API
|
||||||
if value.lower() in self._ignore_dbs:
|
|
||||||
return False
|
|
||||||
if re.match(r'^[a-z]*$', value[0]):
|
if re.match(r'^[a-z]*$', value[0]):
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
@ -240,15 +225,20 @@ class PostgreSQLSchema(DatastoreSchema):
|
|||||||
"""
|
"""
|
||||||
name_regex = re.compile(u(r'^[\u0001-\u007F\u0080-\uFFFF]+[^\s]$'))
|
name_regex = re.compile(u(r'^[\u0001-\u007F\u0080-\uFFFF]+[^\s]$'))
|
||||||
|
|
||||||
def __init__(self, name=None, deserializing=False):
|
def __init__(self, name, character_set=None, collate=None,
|
||||||
super(PostgreSQLSchema, self).__init__()
|
*args, **kwargs):
|
||||||
if not (bool(deserializing) != bool(name)):
|
super(PostgreSQLSchema, self).__init__(name, *args, **kwargs)
|
||||||
raise ValueError(_("Bad args. name: %(name)s, "
|
|
||||||
"deserializing %(deser)s.")
|
self.character_set = character_set
|
||||||
% ({'name': bool(name),
|
self.collate = collate
|
||||||
'deser': bool(deserializing)}))
|
|
||||||
if not deserializing:
|
@DatastoreSchema.collate.setter
|
||||||
self.name = name
|
def collate(self, value):
|
||||||
|
self._collate = value
|
||||||
|
|
||||||
|
@DatastoreSchema.character_set.setter
|
||||||
|
def character_set(self, value):
|
||||||
|
self._character_set = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _max_schema_name_length(self):
|
def _max_schema_name_length(self):
|
||||||
@ -586,12 +576,28 @@ class DatastoreUser(Base):
|
|||||||
|
|
||||||
_HOSTNAME_WILDCARD = '%'
|
_HOSTNAME_WILDCARD = '%'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, name, password, deserializing=False, *args, **kwargs):
|
||||||
self._name = None
|
self._name = None
|
||||||
self._password = None
|
self._password = None
|
||||||
self._host = None
|
self._host = None
|
||||||
self._databases = []
|
self._databases = []
|
||||||
|
|
||||||
|
# need only one of: deserializing, name, or (name and password)
|
||||||
|
if ((not (bool(deserializing) != bool(name))) or
|
||||||
|
(bool(deserializing) and bool(password))):
|
||||||
|
raise RuntimeError("Bug in DatastoreUser()")
|
||||||
|
if not deserializing:
|
||||||
|
if name:
|
||||||
|
self.name = name
|
||||||
|
if password is not None:
|
||||||
|
self.password = password
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.name)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return str(self.serialize())
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def deserialize_user(cls, value):
|
def deserialize_user(cls, value):
|
||||||
if not cls._validate_dict(value):
|
if not cls._validate_dict(value):
|
||||||
@ -599,7 +605,7 @@ class DatastoreUser(Base):
|
|||||||
"Required: %(reqs)s")
|
"Required: %(reqs)s")
|
||||||
% ({'keys': value.keys(),
|
% ({'keys': value.keys(),
|
||||||
'reqs': cls._dict_requirements()}))
|
'reqs': cls._dict_requirements()}))
|
||||||
user = cls(deserializing=True)
|
user = cls(name=None, password=None, deserializing=True)
|
||||||
user.deserialize(value)
|
user.deserialize(value)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
@ -713,24 +719,11 @@ class MongoDBUser(DatastoreUser):
|
|||||||
Trove stores this as <database>.<username>
|
Trove stores this as <database>.<username>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name=None, password=None, deserializing=False):
|
def __init__(self, name=None, password=None, *args, **kwargs):
|
||||||
super(MongoDBUser, self).__init__()
|
|
||||||
self._name = None
|
|
||||||
self._username = None
|
self._username = None
|
||||||
self._database = None
|
self._database = None
|
||||||
self._roles = []
|
self._roles = []
|
||||||
# need only one of: deserializing, name, or (name and password)
|
super(MongoDBUser, self).__init__(name, password, *args, **kwargs)
|
||||||
if ((not (bool(deserializing) != bool(name))) or
|
|
||||||
(bool(deserializing) and bool(password))):
|
|
||||||
raise ValueError(_("Bad args. name: %(name)s, "
|
|
||||||
"password %(pass)s, "
|
|
||||||
"deserializing %(deser)s.")
|
|
||||||
% ({'name': bool(name),
|
|
||||||
'pass': bool(password),
|
|
||||||
'deser': bool(deserializing)}))
|
|
||||||
if not deserializing:
|
|
||||||
self.name = name
|
|
||||||
self.password = password
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def username(self):
|
def username(self):
|
||||||
@ -855,20 +848,8 @@ class MongoDBUser(DatastoreUser):
|
|||||||
class CassandraUser(DatastoreUser):
|
class CassandraUser(DatastoreUser):
|
||||||
"""Represents a Cassandra user and its associated properties."""
|
"""Represents a Cassandra user and its associated properties."""
|
||||||
|
|
||||||
def __init__(self, name=None, password=None, deserializing=False):
|
def __init__(self, name, password=None, *args, **kwargs):
|
||||||
super(CassandraUser, self).__init__()
|
super(CassandraUser, self).__init__(name, password, *args, **kwargs)
|
||||||
|
|
||||||
if ((not (bool(deserializing) != bool(name))) or
|
|
||||||
(bool(deserializing) and bool(password))):
|
|
||||||
raise ValueError(_("Bad args. name: %(name)s, "
|
|
||||||
"password %(pass)s, "
|
|
||||||
"deserializing %(deser)s.")
|
|
||||||
% ({'name': bool(name),
|
|
||||||
'pass': bool(password),
|
|
||||||
'deser': bool(deserializing)}))
|
|
||||||
if not deserializing:
|
|
||||||
self.name = name
|
|
||||||
self.password = password
|
|
||||||
|
|
||||||
def _build_database_schema(self, name):
|
def _build_database_schema(self, name):
|
||||||
return CassandraSchema(name)
|
return CassandraSchema(name)
|
||||||
@ -894,60 +875,28 @@ class CassandraUser(DatastoreUser):
|
|||||||
class CouchDBUser(DatastoreUser):
|
class CouchDBUser(DatastoreUser):
|
||||||
"""Represents a CouchDB user and its associated properties."""
|
"""Represents a CouchDB user and its associated properties."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, name, password=None, *args, **kwargs):
|
||||||
self._name = None
|
super(CouchDBUser, self).__init__(name, password, *args, **kwargs)
|
||||||
self._host = None
|
|
||||||
self._password = None
|
|
||||||
self._databases = []
|
|
||||||
self._ignore_users = cfg.get_ignored_users()
|
|
||||||
|
|
||||||
def _is_valid(self, value):
|
def _build_database_schema(self, name):
|
||||||
|
return CouchDBSchema(name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _max_username_length(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _is_valid_name(self, value):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@property
|
def _is_valid_host_name(self, value):
|
||||||
def name(self):
|
return True
|
||||||
return self._name
|
|
||||||
|
|
||||||
@name.setter
|
def _is_valid_password(self, value):
|
||||||
def name(self, value):
|
return True
|
||||||
if not self._is_valid(value):
|
|
||||||
raise ValueError(_("'%s' is not a valid user name.") % value)
|
|
||||||
else:
|
|
||||||
self._name = value
|
|
||||||
|
|
||||||
@property
|
@classmethod
|
||||||
def password(self):
|
def _dict_requirements(cls):
|
||||||
return self._password
|
return ['_name']
|
||||||
|
|
||||||
@password.setter
|
|
||||||
def password(self, value):
|
|
||||||
if not self._is_valid(value):
|
|
||||||
raise ValueError(_("'%s' is not a valid password.") % value)
|
|
||||||
else:
|
|
||||||
self._password = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def databases(self):
|
|
||||||
return self._databases
|
|
||||||
|
|
||||||
@databases.setter
|
|
||||||
def databases(self, value):
|
|
||||||
mydb = ValidatedMySQLDatabase()
|
|
||||||
mydb.name = value
|
|
||||||
self._databases.append(mydb.serialize())
|
|
||||||
|
|
||||||
@property
|
|
||||||
def host(self):
|
|
||||||
if self._host is None:
|
|
||||||
return '%'
|
|
||||||
return self._host
|
|
||||||
|
|
||||||
@host.setter
|
|
||||||
def host(self, value):
|
|
||||||
if not self._is_valid_host_name(value):
|
|
||||||
raise ValueError(_("'%s' is not a valid hostname.") % value)
|
|
||||||
else:
|
|
||||||
self._host = value
|
|
||||||
|
|
||||||
|
|
||||||
class MySQLUser(Base):
|
class MySQLUser(Base):
|
||||||
@ -1046,21 +995,8 @@ class MySQLUser(Base):
|
|||||||
class PostgreSQLUser(DatastoreUser):
|
class PostgreSQLUser(DatastoreUser):
|
||||||
"""Represents a PostgreSQL user and its associated properties."""
|
"""Represents a PostgreSQL user and its associated properties."""
|
||||||
|
|
||||||
def __init__(self, name=None, password=None, deserializing=False):
|
def __init__(self, name, password=None, *args, **kwargs):
|
||||||
super(PostgreSQLUser, self).__init__()
|
super(PostgreSQLUser, self).__init__(name, password, *args, **kwargs)
|
||||||
|
|
||||||
if ((not (bool(deserializing) != bool(name))) or
|
|
||||||
(bool(deserializing) and bool(password))):
|
|
||||||
raise ValueError(_("Bad args. name: %(name)s, "
|
|
||||||
"password %(pass)s, "
|
|
||||||
"deserializing %(deser)s.")
|
|
||||||
% ({'name': bool(name),
|
|
||||||
'pass': bool(password),
|
|
||||||
'deser': bool(deserializing)}))
|
|
||||||
|
|
||||||
if not deserializing:
|
|
||||||
self.name = name
|
|
||||||
self.password = password
|
|
||||||
|
|
||||||
def _build_database_schema(self, name):
|
def _build_database_schema(self, name):
|
||||||
return PostgreSQLSchema(name)
|
return PostgreSQLSchema(name)
|
||||||
@ -1080,7 +1016,7 @@ class PostgreSQLUser(DatastoreUser):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _dict_requirements(cls):
|
def _dict_requirements(cls):
|
||||||
return ['name']
|
return ['_name']
|
||||||
|
|
||||||
|
|
||||||
class RootUser(MySQLUser):
|
class RootUser(MySQLUser):
|
||||||
@ -1116,6 +1052,17 @@ class CassandraRootUser(CassandraUser):
|
|||||||
class PostgreSQLRootUser(PostgreSQLUser):
|
class PostgreSQLRootUser(PostgreSQLUser):
|
||||||
"""Represents the PostgreSQL default superuser."""
|
"""Represents the PostgreSQL default superuser."""
|
||||||
|
|
||||||
def __init__(self, password=None):
|
def __init__(self, password=None, *args, **kwargs):
|
||||||
password = password if not None else utils.generate_random_password()
|
password = password if not None else utils.generate_random_password()
|
||||||
super(PostgreSQLRootUser, self).__init__("postgres", password=password)
|
super(PostgreSQLRootUser, self).__init__("postgres", password=password,
|
||||||
|
*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class CouchDBRootUser(CouchDBUser):
|
||||||
|
"""Represents the CouchDB default superuser."""
|
||||||
|
|
||||||
|
def __init__(self, password=None, *args, **kwargs):
|
||||||
|
if password is None:
|
||||||
|
password = utils.generate_random_password()
|
||||||
|
super(CouchDBRootUser, self).__init__("root", password=password,
|
||||||
|
*args, **kwargs)
|
||||||
|
@ -15,6 +15,9 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
from mock import Mock
|
||||||
|
|
||||||
from trove.common import pagination
|
from trove.common import pagination
|
||||||
from trove.tests.unittests import trove_testtools
|
from trove.tests.unittests import trove_testtools
|
||||||
|
|
||||||
@ -41,7 +44,8 @@ class TestPaginatedDataView(trove_testtools.TestCase):
|
|||||||
|
|
||||||
def _do_paginate_list(self, limit=None, marker=None, include_marker=False):
|
def _do_paginate_list(self, limit=None, marker=None, include_marker=False):
|
||||||
li = ['a', 'b', 'c', 'd', 'e']
|
li = ['a', 'b', 'c', 'd', 'e']
|
||||||
return pagination.paginate_list(li, limit, marker, include_marker)
|
return pagination.paginate_list(li, limit=limit, marker=marker,
|
||||||
|
include_marker=include_marker)
|
||||||
|
|
||||||
def test_paginate_list(self):
|
def test_paginate_list(self):
|
||||||
# start list
|
# start list
|
||||||
@ -75,3 +79,48 @@ class TestPaginatedDataView(trove_testtools.TestCase):
|
|||||||
li_4, marker_4 = self._do_paginate_list(marker='f')
|
li_4, marker_4 = self._do_paginate_list(marker='f')
|
||||||
self.assertEqual([], li_4)
|
self.assertEqual([], li_4)
|
||||||
self.assertIsNone(marker_4)
|
self.assertIsNone(marker_4)
|
||||||
|
|
||||||
|
li_5, marker_5 = self._do_paginate_list(limit=1, marker='f')
|
||||||
|
self.assertEqual([], li_5)
|
||||||
|
self.assertIsNone(marker_5)
|
||||||
|
|
||||||
|
def test_dict_paginate(self):
|
||||||
|
li = [{'_collate': 'en_US.UTF-8',
|
||||||
|
'_character_set': 'UTF8',
|
||||||
|
'_name': 'db1'},
|
||||||
|
{'_collate': 'en_US.UTF-8',
|
||||||
|
'_character_set': 'UTF8',
|
||||||
|
'_name': 'db3'},
|
||||||
|
{'_collate': 'en_US.UTF-8',
|
||||||
|
'_character_set': 'UTF8',
|
||||||
|
'_name': 'db2'},
|
||||||
|
{'_collate': 'en_US.UTF-8',
|
||||||
|
'_character_set': 'UTF8',
|
||||||
|
'_name': 'db5'},
|
||||||
|
{'_collate': 'en_US.UTF-8',
|
||||||
|
'_character_set': 'UTF8',
|
||||||
|
'_name': 'db4'}
|
||||||
|
]
|
||||||
|
|
||||||
|
l, m = pagination.paginate_dict_list(li, '_name', limit=1,
|
||||||
|
marker='db1',
|
||||||
|
include_marker=True)
|
||||||
|
self.assertEqual(l[0], li[0])
|
||||||
|
self.assertEqual(m, 'db1')
|
||||||
|
|
||||||
|
def test_object_paginate(self):
|
||||||
|
|
||||||
|
def build_mock_object(name):
|
||||||
|
o = Mock()
|
||||||
|
o.name = name
|
||||||
|
o.attr = 'attr'
|
||||||
|
return o
|
||||||
|
|
||||||
|
li = [build_mock_object('db1'), build_mock_object('db2'),
|
||||||
|
build_mock_object('db3')]
|
||||||
|
|
||||||
|
l, m = pagination.paginate_object_list(li, 'name', limit=1,
|
||||||
|
marker='db1',
|
||||||
|
include_marker=True)
|
||||||
|
self.assertEqual(l[0], li[0])
|
||||||
|
self.assertEqual(m, 'db1')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user