Cleanup guestagent models
Cleaned up the guestagent database/user __init__() methods by moving the deserialization and error-checking code to the base. * Also had to update CouchDB service to use the new model's serialization/deserialization calls (tested with int-tests). Make use of the models in the Postgres manager The guestagent should make use of these new classes internally. Currently it creates serialized versions of those objects 'manually' using Python dicts. Change-Id: I8b1ae9207948f211de8fcc2a0d3097bd37e06d78 Related-Bug: 1498573 Closes-Bug: 1543739
This commit is contained in:
parent
2f0a8610ca
commit
7a99a124b4
@ -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