Improve guestagent datastore models
All validation of requests is currently done against the MySQL datastore models. In order to switch to using datastore extensions for validation the models need to be refactored. The current approach to guestagent/db/models.py is getting unruly and is difficult to apply to other datastores. This change moves the models from the guestagent package to the common package, so that the extensions package can use it in the future. That is: guestagent/db/models.py -> common/db/models.py The generic models are separated from the datastore-specific models. For datastores with custom models the classes have been moved to their own sub-package. Example: common/db/mysql/models.py Using this new approach any datastores that want custom models just need to create packages similar to the common/db/mysql/ package. The code references to these models have all been updated. Non-MySQL based datastores using these references have been switched to using the generic models. The tests for these models have been improved. Change-Id: If321202a3ec4ab0f57ee8e516b885a8307d464b7 Partial-Bug: 1498573
This commit is contained in:
parent
d6fabc8504
commit
1c819c3e37
0
trove/common/db/cassandra/__init__.py
Normal file
0
trove/common/db/cassandra/__init__.py
Normal file
45
trove/common/db/cassandra/models.py
Normal file
45
trove/common/db/cassandra/models.py
Normal file
@ -0,0 +1,45 @@
|
||||
# Copyright 2016 Tesora, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from trove.common.db import models
|
||||
|
||||
|
||||
class CassandraSchema(models.DatastoreSchema):
|
||||
"""Represents a Cassandra schema and its associated properties.
|
||||
|
||||
Keyspace names are 32 or fewer alpha-numeric characters and underscores,
|
||||
the first of which is an alpha character.
|
||||
"""
|
||||
|
||||
@property
|
||||
def _max_schema_name_length(self):
|
||||
return 32
|
||||
|
||||
def _is_valid_schema_name(self, value):
|
||||
return not any(c in value for c in '/\. "$')
|
||||
|
||||
|
||||
class CassandraUser(models.DatastoreUser):
|
||||
"""Represents a Cassandra user and its associated properties."""
|
||||
|
||||
root_username = 'cassandra'
|
||||
|
||||
@property
|
||||
def _max_user_name_length(self):
|
||||
return 65535
|
||||
|
||||
@property
|
||||
def schema_model(self):
|
||||
return CassandraSchema
|
0
trove/common/db/couchdb/__init__.py
Normal file
0
trove/common/db/couchdb/__init__.py
Normal file
32
trove/common/db/couchdb/models.py
Normal file
32
trove/common/db/couchdb/models.py
Normal file
@ -0,0 +1,32 @@
|
||||
# Copyright 2016 Tesora, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from trove.common.db import models
|
||||
|
||||
|
||||
class CouchDBSchema(models.DatastoreSchema):
|
||||
"""Represents a CouchDB schema and its associated properties."""
|
||||
|
||||
@property
|
||||
def _max_schema_name_length(self):
|
||||
return 32
|
||||
|
||||
|
||||
class CouchDBUser(models.DatastoreUser):
|
||||
"""Represents a CouchDB user and its associated properties."""
|
||||
|
||||
@property
|
||||
def schema_model(self):
|
||||
return CouchDBSchema
|
438
trove/common/db/models.py
Normal file
438
trove/common/db/models.py
Normal file
@ -0,0 +1,438 @@
|
||||
# Copyright 2016 Tesora, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import abc
|
||||
|
||||
from trove.common import cfg
|
||||
from trove.common.i18n import _
|
||||
from trove.common import utils
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
"""
|
||||
The classes below are generic and can be used for any datastore, but will not
|
||||
provide validation. To add a new datastore create a sub-package (see mysql for
|
||||
example) and create new child classes inheriting from these generic classes.
|
||||
|
||||
As a guideline, for new datastores the following class methods/variables should
|
||||
be overridden if validation is desired (see their docstrings for additional
|
||||
info):
|
||||
|
||||
DatastoreModelsBase:
|
||||
__init__
|
||||
|
||||
DatastoreSchema:
|
||||
_max_schema_name_length
|
||||
_is_valid_schema_name
|
||||
verify_dict
|
||||
_create_checks
|
||||
_delete_checks
|
||||
|
||||
DatastoreUser:
|
||||
_is_valid_user_name
|
||||
_is_valid_host_name
|
||||
_is_valid_password
|
||||
_is_valid_database
|
||||
verify_dict
|
||||
_create_checks
|
||||
_delete_checks
|
||||
"""
|
||||
|
||||
|
||||
class DatastoreModelsBase(object):
|
||||
"""Base model for the datastore schema and user models."""
|
||||
|
||||
def serialize(self):
|
||||
return self.__dict__
|
||||
|
||||
def _deserialize(self, obj):
|
||||
self.__dict__ = obj
|
||||
|
||||
def __repr__(self):
|
||||
return str(self.serialize())
|
||||
|
||||
@classmethod
|
||||
def deserialize(cls, value, verify=True):
|
||||
item = cls(deserializing=True)
|
||||
item._deserialize(value)
|
||||
if verify:
|
||||
item.verify_dict()
|
||||
return item
|
||||
|
||||
@abc.abstractmethod
|
||||
def verify_dict(self):
|
||||
"""Validate the object's data dictionary.
|
||||
:returns: True if dictionary is valid.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def check_string(value, desc):
|
||||
"""Check if the value is a string/unicode.
|
||||
:param value: Value to check.
|
||||
:param desc: Description for exception message.
|
||||
:raises: ValueError if not a string/unicode.
|
||||
"""
|
||||
if not (isinstance(value, str) or
|
||||
isinstance(value, unicode)):
|
||||
raise ValueError(_("%(desc)s is not a string. Type = %(t)s.")
|
||||
% {'desc': desc, 't': type(value)})
|
||||
|
||||
|
||||
class DatastoreSchema(DatastoreModelsBase):
|
||||
"""Represents a database schema."""
|
||||
|
||||
def __init__(self, name=None, deserializing=False):
|
||||
self._name = None
|
||||
self._collate = None
|
||||
self._character_set = None
|
||||
# If both or neither are passed in this is a bug.
|
||||
if bool(deserializing) == bool(name):
|
||||
raise RuntimeError("Bug in DatastoreSchema()")
|
||||
if not deserializing:
|
||||
self.name = name
|
||||
|
||||
def __str__(self):
|
||||
return str(self.name)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, value):
|
||||
self._validate_schema_name(value)
|
||||
self._name = value
|
||||
|
||||
def _validate_schema_name(self, value):
|
||||
"""Perform checks on a given schema name.
|
||||
:param value: Validated schema name.
|
||||
:type value: string
|
||||
:raises: ValueError On validation errors.
|
||||
"""
|
||||
if not value:
|
||||
raise ValueError(_("Schema name empty."))
|
||||
self.check_string(value, 'Schema name')
|
||||
if self._max_schema_name_length and (len(value) >
|
||||
self._max_schema_name_length):
|
||||
raise ValueError(_("Schema name '%(name)s' is too long. "
|
||||
"Max length = %(max_length)d.")
|
||||
% {'name': value,
|
||||
'max_length': self._max_schema_name_length})
|
||||
elif not self._is_valid_schema_name(value):
|
||||
raise ValueError(_("'%s' is not a valid schema name.") % value)
|
||||
|
||||
@property
|
||||
def _max_schema_name_length(self):
|
||||
"""Return the maximum valid schema name length if any.
|
||||
:returns: Maximum schema name length or None if unlimited.
|
||||
"""
|
||||
return None
|
||||
|
||||
def _is_valid_schema_name(self, value):
|
||||
"""Validate a given schema name.
|
||||
:param value: Validated schema name.
|
||||
:type value: string
|
||||
:returns: TRUE if valid, FALSE otherwise.
|
||||
"""
|
||||
return True
|
||||
|
||||
def verify_dict(self):
|
||||
"""Check that the object's dictionary values are valid by reloading
|
||||
them via the property setters. The checkers should raise the
|
||||
ValueError exception if invalid. All mandatory fields should be
|
||||
checked.
|
||||
"""
|
||||
self.name = self._name
|
||||
|
||||
@property
|
||||
def ignored_dbs(self):
|
||||
return cfg.get_ignored_dbs()
|
||||
|
||||
def is_ignored(self):
|
||||
return self.name in self.ignored_dbs
|
||||
|
||||
def check_reserved(self):
|
||||
"""Check if the name is on the ignore_dbs list, meaning it is
|
||||
reserved.
|
||||
:raises: ValueError if name is on the reserved list.
|
||||
"""
|
||||
if self.is_ignored():
|
||||
raise ValueError(_('Database name "%(name)s" is on the reserved'
|
||||
'list: %(reserved)s.')
|
||||
% {'name': self.name,
|
||||
'reserved': self.ignored_dbs})
|
||||
|
||||
def _create_checks(self):
|
||||
"""Checks to be performed before database can be created."""
|
||||
self.check_reserved()
|
||||
|
||||
def check_create(self):
|
||||
"""Check if the database can be created.
|
||||
:raises: ValueError if the schema is not valid for create.
|
||||
"""
|
||||
try:
|
||||
self._create_checks()
|
||||
except ValueError as e:
|
||||
raise ValueError(_('Cannot create database: %(error)s')
|
||||
% {'error': str(e)})
|
||||
|
||||
def _delete_checks(self):
|
||||
"""Checks to be performed before database can be deleted."""
|
||||
self.check_reserved()
|
||||
|
||||
def check_delete(self):
|
||||
"""Check if the database can be deleted.
|
||||
:raises: ValueError if the schema is not valid for delete.
|
||||
"""
|
||||
try:
|
||||
self._delete_checks()
|
||||
except ValueError as e:
|
||||
raise ValueError(_('Cannot delete database: %(error)s')
|
||||
% {'error': str(e)})
|
||||
|
||||
|
||||
class DatastoreUser(DatastoreModelsBase):
|
||||
"""Represents a datastore user."""
|
||||
|
||||
_HOSTNAME_WILDCARD = '%'
|
||||
root_username = 'root'
|
||||
|
||||
def __init__(self, name=None, password=None, host=None, databases=None,
|
||||
deserializing=False):
|
||||
self._name = None
|
||||
self._password = None
|
||||
self._host = self._HOSTNAME_WILDCARD
|
||||
self._databases = []
|
||||
self._is_root = False
|
||||
if not deserializing:
|
||||
self.name = name
|
||||
if password:
|
||||
self.password = password
|
||||
if host:
|
||||
self.host = host
|
||||
if databases:
|
||||
self.databases = databases
|
||||
|
||||
@classmethod
|
||||
def root(cls, name=None, password=None, *args, **kwargs):
|
||||
if not name:
|
||||
name = cls.root_username
|
||||
if not password:
|
||||
password = utils.generate_random_password()
|
||||
user = cls(name, password, *args, **kwargs)
|
||||
user.make_root()
|
||||
return user
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, value):
|
||||
self._validate_user_name(value)
|
||||
self._name = value
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
return self._password
|
||||
|
||||
@password.setter
|
||||
def password(self, value):
|
||||
self.check_string(value, "User password")
|
||||
if self._is_valid_password(value):
|
||||
self._password = value
|
||||
else:
|
||||
raise ValueError(_("'%s' is not a valid password.") % value)
|
||||
|
||||
def _add_database(self, value):
|
||||
serial_db = self._build_database_schema(value).serialize()
|
||||
if self._is_valid_database(serial_db):
|
||||
self._databases.append(serial_db)
|
||||
|
||||
@property
|
||||
def databases(self):
|
||||
return self._databases
|
||||
|
||||
@databases.setter
|
||||
def databases(self, value):
|
||||
if isinstance(value, list):
|
||||
for dbname in value:
|
||||
self._add_database(dbname)
|
||||
else:
|
||||
self._add_database(value)
|
||||
|
||||
@property
|
||||
def host(self):
|
||||
if self._host is None:
|
||||
return self._HOSTNAME_WILDCARD
|
||||
return self._host
|
||||
|
||||
@host.setter
|
||||
def host(self, value):
|
||||
self.check_string(value, "User host name")
|
||||
if self._is_valid_host_name(value):
|
||||
self._host = value
|
||||
else:
|
||||
raise ValueError(_("'%s' is not a valid hostname.") % value)
|
||||
|
||||
def _build_database_schema(self, name):
|
||||
"""Build a schema for this user.
|
||||
:type name: string
|
||||
"""
|
||||
return self.schema_model(name)
|
||||
|
||||
def deserialize_schema(self, value):
|
||||
"""Deserialize a user's databases value.
|
||||
:type value: dict
|
||||
"""
|
||||
return self.schema_model.deserialize(value)
|
||||
|
||||
def _validate_user_name(self, value):
|
||||
"""Perform validations on a given user name.
|
||||
:param value: Validated user name.
|
||||
:type value: string
|
||||
:raises: ValueError On validation errors.
|
||||
"""
|
||||
if not value:
|
||||
raise ValueError(_("User name empty."))
|
||||
self.check_string(value, "User name")
|
||||
if self._max_user_name_length and (len(value) >
|
||||
self._max_user_name_length):
|
||||
raise ValueError(_("User name '%(name)s' is too long. "
|
||||
"Max length = %(max_length)d.")
|
||||
% {'name': value,
|
||||
'max_length': self._max_user_name_length})
|
||||
elif not self._is_valid_user_name(value):
|
||||
raise ValueError(_("'%s' is not a valid user name.") % value)
|
||||
|
||||
@property
|
||||
def _max_user_name_length(self):
|
||||
"""Return the maximum valid user name length if any.
|
||||
:returns: Maximum user name length or None if unlimited.
|
||||
"""
|
||||
return None
|
||||
|
||||
def _is_valid_user_name(self, value):
|
||||
"""Validate a given user name.
|
||||
:param value: User name to be validated.
|
||||
:type value: string
|
||||
:returns: TRUE if valid, FALSE otherwise.
|
||||
"""
|
||||
return True
|
||||
|
||||
def _is_valid_host_name(self, value):
|
||||
"""Validate a given host name.
|
||||
:param value: Host name to be validated.
|
||||
:type value: string
|
||||
:returns: TRUE if valid, FALSE otherwise.
|
||||
"""
|
||||
return True
|
||||
|
||||
def _is_valid_password(self, value):
|
||||
"""Validate a given password.
|
||||
:param value: Password to be validated.
|
||||
:type value: string
|
||||
:returns: TRUE if valid, FALSE otherwise.
|
||||
"""
|
||||
return True
|
||||
|
||||
def _is_valid_database(self, value):
|
||||
"""Validate a given database (serialized schema object).
|
||||
:param value: The database to be validated.
|
||||
:type value: dict
|
||||
:returns: TRUE if valid, FALSE otherwise.
|
||||
:raises: ValueError if operation not allowed.
|
||||
"""
|
||||
return value not in self.databases
|
||||
|
||||
def verify_dict(self):
|
||||
"""Check that the object's dictionary values are valid by reloading
|
||||
them via the property setters. The checkers should raise the
|
||||
ValueError exception if invalid. All mandatory fields should be
|
||||
checked.
|
||||
"""
|
||||
self.name = self._name
|
||||
if self.__dict__.get('_password'):
|
||||
self.password = self._password
|
||||
else:
|
||||
self._password = None
|
||||
if self.__dict__.get('_host'):
|
||||
self.host = self._host
|
||||
else:
|
||||
self._host = self._HOSTNAME_WILDCARD
|
||||
if self.__dict__.get('_databases'):
|
||||
for database in self._databases:
|
||||
# Create the schema for validation only
|
||||
self.deserialize_schema(database)
|
||||
else:
|
||||
self._databases = []
|
||||
if not self.__dict__.get('_is_root'):
|
||||
self._is_root = False
|
||||
|
||||
@property
|
||||
def schema_model(self):
|
||||
return DatastoreSchema
|
||||
|
||||
@property
|
||||
def ignored_users(self):
|
||||
if self._is_root:
|
||||
return []
|
||||
return cfg.get_ignored_users()
|
||||
|
||||
@property
|
||||
def is_ignored(self):
|
||||
return self.name in self.ignored_users
|
||||
|
||||
def make_root(self):
|
||||
self._is_root = True
|
||||
|
||||
def check_reserved(self):
|
||||
"""Check if the name is on the ignore_users list, meaning it is
|
||||
reserved.
|
||||
:raises: ValueError if name is on the reserved list.
|
||||
"""
|
||||
if self.is_ignored:
|
||||
raise ValueError(_('User name "%(name)s" is on the reserved '
|
||||
'list: %(reserved)s.')
|
||||
% {'name': self.name,
|
||||
'reserved': self.ignored_users})
|
||||
|
||||
def _create_checks(self):
|
||||
"""Checks to be performed before user can be created."""
|
||||
self.check_reserved()
|
||||
|
||||
def check_create(self):
|
||||
"""Check if the user can be created.
|
||||
:raises: ValueError if the user is not valid for create.
|
||||
"""
|
||||
try:
|
||||
self._create_checks()
|
||||
except ValueError as e:
|
||||
raise ValueError(_('Cannot create user: %(error)s')
|
||||
% {'error': str(e)})
|
||||
|
||||
def _delete_checks(self):
|
||||
"""Checks to be performed before user can be created."""
|
||||
self.check_reserved()
|
||||
|
||||
def check_delete(self):
|
||||
"""Check if the user can be deleted.
|
||||
:raises: ValueError if the user is not valid for delete.
|
||||
"""
|
||||
try:
|
||||
self._delete_checks()
|
||||
except ValueError as e:
|
||||
raise ValueError(_('Cannot delete user: %(error)s')
|
||||
% {'error': str(e)})
|
0
trove/common/db/mongodb/__init__.py
Normal file
0
trove/common/db/mongodb/__init__.py
Normal file
158
trove/common/db/mongodb/models.py
Normal file
158
trove/common/db/mongodb/models.py
Normal file
@ -0,0 +1,158 @@
|
||||
# Copyright 2016 Tesora, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from trove.common.db import models
|
||||
from trove.common.i18n import _
|
||||
|
||||
|
||||
class MongoDBSchema(models.DatastoreSchema):
|
||||
"""Represents a MongoDB database and its associated properties."""
|
||||
|
||||
@property
|
||||
def _max_schema_name_length(self):
|
||||
return 64
|
||||
|
||||
def _is_valid_schema_name(self, value):
|
||||
# check against the invalid character set from
|
||||
# http://docs.mongodb.org/manual/reference/limits
|
||||
return not any(c in value for c in '/\. "$')
|
||||
|
||||
|
||||
class MongoDBUser(models.DatastoreUser):
|
||||
"""Represents a MongoDB user and its associated properties.
|
||||
MongoDB users are identified using their name and database.
|
||||
Trove stores this as <database>.<username>
|
||||
"""
|
||||
|
||||
root_username = 'admin.root'
|
||||
|
||||
def __init__(self, name=None, password=None, host=None, databases=None,
|
||||
deserializing=False):
|
||||
super(MongoDBUser, self).__init__(name=name, password=password,
|
||||
host=host, databases=databases,
|
||||
deserializing=deserializing)
|
||||
if not deserializing:
|
||||
self._init_roles()
|
||||
|
||||
@property
|
||||
def username(self):
|
||||
return self._username
|
||||
|
||||
@username.setter
|
||||
def username(self, value):
|
||||
self._update_name(username=value)
|
||||
|
||||
@property
|
||||
def database(self):
|
||||
return MongoDBSchema.deserialize(self._database)
|
||||
|
||||
@database.setter
|
||||
def database(self, value):
|
||||
self._update_name(database=value)
|
||||
|
||||
def _validate_user_name(self, value):
|
||||
self._update_name(name=value)
|
||||
|
||||
def _update_name(self, name=None, username=None, database=None):
|
||||
"""Keep the name, username, and database values in sync."""
|
||||
if name:
|
||||
(database, username) = self._parse_name(name)
|
||||
if not (database and username):
|
||||
missing = 'username' if self.database else 'database'
|
||||
raise ValueError(_("MongoDB user's name missing %s.")
|
||||
% missing)
|
||||
else:
|
||||
if username:
|
||||
if not self.database:
|
||||
raise ValueError(_('MongoDB user missing database.'))
|
||||
database = self.database.name
|
||||
else: # database
|
||||
if not self.username:
|
||||
raise ValueError(_('MongoDB user missing username.'))
|
||||
username = self.username
|
||||
name = '%s.%s' % (database, username)
|
||||
self._name = name
|
||||
self._username = username
|
||||
self._database = self._build_database_schema(database).serialize()
|
||||
|
||||
@property
|
||||
def roles(self):
|
||||
return self._roles
|
||||
|
||||
@roles.setter
|
||||
def roles(self, value):
|
||||
if isinstance(value, list):
|
||||
for role in value:
|
||||
self._add_role(role)
|
||||
else:
|
||||
self._add_role(value)
|
||||
|
||||
def revoke_role(self, role):
|
||||
if role in self.roles:
|
||||
self._roles.remove(role)
|
||||
|
||||
def _init_roles(self):
|
||||
if '_roles' not in self.__dict__:
|
||||
self._roles = []
|
||||
for db in self._databases:
|
||||
self._roles.append({'db': db['_name'], 'role': 'readWrite'})
|
||||
|
||||
def _build_database_schema(self, name):
|
||||
return MongoDBSchema(name)
|
||||
|
||||
def deserialize_schema(self, value):
|
||||
return MongoDBSchema.deserialize(value)
|
||||
|
||||
@staticmethod
|
||||
def _parse_name(value):
|
||||
"""The name will be <database>.<username>, so split it."""
|
||||
parts = value.split('.', 1)
|
||||
if len(parts) != 2:
|
||||
raise ValueError(_(
|
||||
'MongoDB user name "%s" not in <database>.<username> format.'
|
||||
) % value)
|
||||
return parts[0], parts[1]
|
||||
|
||||
@property
|
||||
def _max_user_name_length(self):
|
||||
return 128
|
||||
|
||||
def _add_role(self, value):
|
||||
if not self._is_valid_role(value):
|
||||
raise ValueError(_('Role %s is invalid.') % value)
|
||||
self._roles.append(value)
|
||||
if value['role'] == 'readWrite':
|
||||
self.databases = value['db']
|
||||
|
||||
def _is_valid_role(self, value):
|
||||
if not isinstance(value, dict):
|
||||
return False
|
||||
if not {'db', 'role'} == set(value):
|
||||
return False
|
||||
return True
|
||||
|
||||
def verify_dict(self):
|
||||
super(MongoDBUser, self).verify_dict()
|
||||
self._init_roles()
|
||||
|
||||
@property
|
||||
def schema_model(self):
|
||||
return MongoDBSchema
|
||||
|
||||
def _create_checks(self):
|
||||
super(MongoDBUser, self)._create_checks()
|
||||
if not self.password:
|
||||
raise ValueError(_("MongoDB user to create is missing a "
|
||||
"password."))
|
0
trove/common/db/mysql/__init__.py
Normal file
0
trove/common/db/mysql/__init__.py
Normal file
244
trove/common/db/mysql/data.py
Normal file
244
trove/common/db/mysql/data.py
Normal file
@ -0,0 +1,244 @@
|
||||
# Copyright 2016 Tesora, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
charset = {"big5": ["big5_chinese_ci", "big5_bin"],
|
||||
"dec8": ["dec8_swedish_ci", "dec8_bin"],
|
||||
"cp850": ["cp850_general_ci", "cp850_bin"],
|
||||
"hp8": ["hp8_english_ci", "hp8_bin"],
|
||||
"koi8r": ["koi8r_general_ci", "koi8r_bin"],
|
||||
"latin1": ["latin1_swedish_ci",
|
||||
"latin1_german1_ci",
|
||||
"latin1_danish_ci",
|
||||
"latin1_german2_ci",
|
||||
"latin1_bin",
|
||||
"latin1_general_ci",
|
||||
"latin1_general_cs",
|
||||
"latin1_spanish_ci"],
|
||||
"latin2": ["latin2_general_ci",
|
||||
"latin2_czech_cs",
|
||||
"latin2_hungarian_ci",
|
||||
"latin2_croatian_ci",
|
||||
"latin2_bin"],
|
||||
"swe7": ["swe7_swedish_ci", "swe7_bin"],
|
||||
"ascii": ["ascii_general_ci", "ascii_bin"],
|
||||
"ujis": ["ujis_japanese_ci", "ujis_bin"],
|
||||
"sjis": ["sjis_japanese_ci", "sjis_bin"],
|
||||
"hebrew": ["hebrew_general_ci", "hebrew_bin"],
|
||||
"tis620": ["tis620_thai_ci", "tis620_bin"],
|
||||
"euckr": ["euckr_korean_ci", "euckr_bin"],
|
||||
"koi8u": ["koi8u_general_ci", "koi8u_bin"],
|
||||
"gb2312": ["gb2312_chinese_ci", "gb2312_bin"],
|
||||
"greek": ["greek_general_ci", "greek_bin"],
|
||||
"cp1250": ["cp1250_general_ci",
|
||||
"cp1250_czech_cs",
|
||||
"cp1250_croatian_ci",
|
||||
"cp1250_bin",
|
||||
"cp1250_polish_ci"],
|
||||
"gbk": ["gbk_chinese_ci", "gbk_bin"],
|
||||
"latin5": ["latin5_turkish_ci", "latin5_bin"],
|
||||
"armscii8": ["armscii8_general_ci", "armscii8_bin"],
|
||||
"utf8": ["utf8_general_ci",
|
||||
"utf8_bin",
|
||||
"utf8_unicode_ci",
|
||||
"utf8_icelandic_ci",
|
||||
"utf8_latvian_ci",
|
||||
"utf8_romanian_ci",
|
||||
"utf8_slovenian_ci",
|
||||
"utf8_polish_ci",
|
||||
"utf8_estonian_ci",
|
||||
"utf8_spanish_ci",
|
||||
"utf8_swedish_ci",
|
||||
"utf8_turkish_ci",
|
||||
"utf8_czech_ci",
|
||||
"utf8_danish_ci",
|
||||
"utf8_lithuanian_ci",
|
||||
"utf8_slovak_ci",
|
||||
"utf8_spanish2_ci",
|
||||
"utf8_roman_ci",
|
||||
"utf8_persian_ci",
|
||||
"utf8_esperanto_ci",
|
||||
"utf8_hungarian_ci"],
|
||||
"ucs2": ["ucs2_general_ci",
|
||||
"ucs2_bin",
|
||||
"ucs2_unicode_ci",
|
||||
"ucs2_icelandic_ci",
|
||||
"ucs2_latvian_ci",
|
||||
"ucs2_romanian_ci",
|
||||
"ucs2_slovenian_ci",
|
||||
"ucs2_polish_ci",
|
||||
"ucs2_estonian_ci",
|
||||
"ucs2_spanish_ci",
|
||||
"ucs2_swedish_ci",
|
||||
"ucs2_turkish_ci",
|
||||
"ucs2_czech_ci",
|
||||
"ucs2_danish_ci",
|
||||
"ucs2_lithuanian_ci",
|
||||
"ucs2_slovak_ci",
|
||||
"ucs2_spanish2_ci",
|
||||
"ucs2_roman_ci",
|
||||
"ucs2_persian_ci",
|
||||
"ucs2_esperanto_ci",
|
||||
"ucs2_hungarian_ci"],
|
||||
"cp866": ["cp866_general_ci", "cp866_bin"],
|
||||
"keybcs2": ["keybcs2_general_ci", "keybcs2_bin"],
|
||||
"macce": ["macce_general_ci", "macce_bin"],
|
||||
"macroman": ["macroman_general_ci", "macroman_bin"],
|
||||
"cp852": ["cp852_general_ci", "cp852_bin"],
|
||||
"latin7": ["latin7_general_ci",
|
||||
"latin7_estonian_cs",
|
||||
"latin7_general_cs",
|
||||
"latin7_bin"],
|
||||
"cp1251": ["cp1251_general_ci",
|
||||
"cp1251_bulgarian_ci",
|
||||
"cp1251_ukrainian_ci",
|
||||
"cp1251_bin",
|
||||
"cp1251_general_cs"],
|
||||
"cp1256": ["cp1256_general_ci", "cp1256_bin"],
|
||||
"cp1257": ["cp1257_general_ci",
|
||||
"cp1257_lithuanian_ci",
|
||||
"cp1257_bin"],
|
||||
"binary": ["binary"],
|
||||
"geostd8": ["geostd8_general_ci", "geostd8_bin"],
|
||||
"cp932": ["cp932_japanese_ci", "cp932_bin"],
|
||||
"eucjpms": ["eucjpms_japanese_ci", "eucjpms_bin"]}
|
||||
|
||||
collation = {"big5_chinese_ci": "big5",
|
||||
"big5_bin": "big5",
|
||||
"dec8_swedish_ci": "dec8",
|
||||
"dec8_bin": "dec8",
|
||||
"cp850_general_ci": "cp850",
|
||||
"cp850_bin": "cp850",
|
||||
"hp8_english_ci": "hp8",
|
||||
"hp8_bin": "hp8",
|
||||
"koi8r_general_ci": "koi8r",
|
||||
"koi8r_bin": "koi8r",
|
||||
"latin1_german1_ci": "latin1",
|
||||
"latin1_swedish_ci": "latin1",
|
||||
"latin1_danish_ci": "latin1",
|
||||
"latin1_german2_ci": "latin1",
|
||||
"latin1_bin": "latin1",
|
||||
"latin1_general_ci": "latin1",
|
||||
"latin1_general_cs": "latin1",
|
||||
"latin1_spanish_ci": "latin1",
|
||||
"latin2_czech_cs": "latin2",
|
||||
"latin2_general_ci": "latin2",
|
||||
"latin2_hungarian_ci": "latin2",
|
||||
"latin2_croatian_ci": "latin2",
|
||||
"latin2_bin": "latin2",
|
||||
"swe7_swedish_ci": "swe7",
|
||||
"swe7_bin": "swe7",
|
||||
"ascii_general_ci": "ascii",
|
||||
"ascii_bin": "ascii",
|
||||
"ujis_japanese_ci": "ujis",
|
||||
"ujis_bin": "ujis",
|
||||
"sjis_japanese_ci": "sjis",
|
||||
"sjis_bin": "sjis",
|
||||
"hebrew_general_ci": "hebrew",
|
||||
"hebrew_bin": "hebrew",
|
||||
"tis620_thai_ci": "tis620",
|
||||
"tis620_bin": "tis620",
|
||||
"euckr_korean_ci": "euckr",
|
||||
"euckr_bin": "euckr",
|
||||
"koi8u_general_ci": "koi8u",
|
||||
"koi8u_bin": "koi8u",
|
||||
"gb2312_chinese_ci": "gb2312",
|
||||
"gb2312_bin": "gb2312",
|
||||
"greek_general_ci": "greek",
|
||||
"greek_bin": "greek",
|
||||
"cp1250_general_ci": "cp1250",
|
||||
"cp1250_czech_cs": "cp1250",
|
||||
"cp1250_croatian_ci": "cp1250",
|
||||
"cp1250_bin": "cp1250",
|
||||
"cp1250_polish_ci": "cp1250",
|
||||
"gbk_chinese_ci": "gbk",
|
||||
"gbk_bin": "gbk",
|
||||
"latin5_turkish_ci": "latin5",
|
||||
"latin5_bin": "latin5",
|
||||
"armscii8_general_ci": "armscii8",
|
||||
"armscii8_bin": "armscii8",
|
||||
"utf8_general_ci": "utf8",
|
||||
"utf8_bin": "utf8",
|
||||
"utf8_unicode_ci": "utf8",
|
||||
"utf8_icelandic_ci": "utf8",
|
||||
"utf8_latvian_ci": "utf8",
|
||||
"utf8_romanian_ci": "utf8",
|
||||
"utf8_slovenian_ci": "utf8",
|
||||
"utf8_polish_ci": "utf8",
|
||||
"utf8_estonian_ci": "utf8",
|
||||
"utf8_spanish_ci": "utf8",
|
||||
"utf8_swedish_ci": "utf8",
|
||||
"utf8_turkish_ci": "utf8",
|
||||
"utf8_czech_ci": "utf8",
|
||||
"utf8_danish_ci": "utf8",
|
||||
"utf8_lithuanian_ci": "utf8",
|
||||
"utf8_slovak_ci": "utf8",
|
||||
"utf8_spanish2_ci": "utf8",
|
||||
"utf8_roman_ci": "utf8",
|
||||
"utf8_persian_ci": "utf8",
|
||||
"utf8_esperanto_ci": "utf8",
|
||||
"utf8_hungarian_ci": "utf8",
|
||||
"ucs2_general_ci": "ucs2",
|
||||
"ucs2_bin": "ucs2",
|
||||
"ucs2_unicode_ci": "ucs2",
|
||||
"ucs2_icelandic_ci": "ucs2",
|
||||
"ucs2_latvian_ci": "ucs2",
|
||||
"ucs2_romanian_ci": "ucs2",
|
||||
"ucs2_slovenian_ci": "ucs2",
|
||||
"ucs2_polish_ci": "ucs2",
|
||||
"ucs2_estonian_ci": "ucs2",
|
||||
"ucs2_spanish_ci": "ucs2",
|
||||
"ucs2_swedish_ci": "ucs2",
|
||||
"ucs2_turkish_ci": "ucs2",
|
||||
"ucs2_czech_ci": "ucs2",
|
||||
"ucs2_danish_ci": "ucs2",
|
||||
"ucs2_lithuanian_ci": "ucs2",
|
||||
"ucs2_slovak_ci": "ucs2",
|
||||
"ucs2_spanish2_ci": "ucs2",
|
||||
"ucs2_roman_ci": "ucs2",
|
||||
"ucs2_persian_ci": "ucs2",
|
||||
"ucs2_esperanto_ci": "ucs2",
|
||||
"ucs2_hungarian_ci": "ucs2",
|
||||
"cp866_general_ci": "cp866",
|
||||
"cp866_bin": "cp866",
|
||||
"keybcs2_general_ci": "keybcs2",
|
||||
"keybcs2_bin": "keybcs2",
|
||||
"macce_general_ci": "macce",
|
||||
"macce_bin": "macce",
|
||||
"macroman_general_ci": "macroman",
|
||||
"macroman_bin": "macroman",
|
||||
"cp852_general_ci": "cp852",
|
||||
"cp852_bin": "cp852",
|
||||
"latin7_estonian_cs": "latin7",
|
||||
"latin7_general_ci": "latin7",
|
||||
"latin7_general_cs": "latin7",
|
||||
"latin7_bin": "latin7",
|
||||
"cp1251_bulgarian_ci": "cp1251",
|
||||
"cp1251_ukrainian_ci": "cp1251",
|
||||
"cp1251_bin": "cp1251",
|
||||
"cp1251_general_ci": "cp1251",
|
||||
"cp1251_general_cs": "cp1251",
|
||||
"cp1256_general_ci": "cp1256",
|
||||
"cp1256_bin": "cp1256",
|
||||
"cp1257_lithuanian_ci": "cp1257",
|
||||
"cp1257_bin": "cp1257",
|
||||
"cp1257_general_ci": "cp1257",
|
||||
"binary": "binary",
|
||||
"geostd8_general_ci": "geostd8",
|
||||
"geostd8_bin": "geostd8",
|
||||
"cp932_japanese_ci": "cp932",
|
||||
"cp932_bin": "cp932",
|
||||
"eucjpms_japanese_ci": "eucjpms",
|
||||
"eucjpms_bin": "eucjpms"}
|
170
trove/common/db/mysql/models.py
Normal file
170
trove/common/db/mysql/models.py
Normal file
@ -0,0 +1,170 @@
|
||||
# Copyright 2016 Tesora, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import re
|
||||
|
||||
import netaddr
|
||||
|
||||
from trove.common import cfg
|
||||
from trove.common.db import models
|
||||
from trove.common.db.mysql import data as mysql_settings
|
||||
from trove.common.i18n import _
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class MySQLSchema(models.DatastoreSchema):
|
||||
"""Represents a MySQL database and its properties."""
|
||||
|
||||
# Defaults
|
||||
__charset__ = "utf8"
|
||||
__collation__ = "utf8_general_ci"
|
||||
dbname = re.compile("^[A-Za-z0-9_-]+[\s\?\#\@]*[A-Za-z0-9_-]+$")
|
||||
|
||||
# Complete list of acceptable values
|
||||
collation = mysql_settings.collation
|
||||
charset = mysql_settings.charset
|
||||
|
||||
def __init__(self, name=None, collate=None, character_set=None,
|
||||
deserializing=False):
|
||||
super(MySQLSchema, self).__init__(name=name,
|
||||
deserializing=deserializing)
|
||||
if not deserializing:
|
||||
if collate:
|
||||
self.collate = collate
|
||||
if character_set:
|
||||
self.character_set = character_set
|
||||
|
||||
@property
|
||||
def _max_schema_name_length(self):
|
||||
return 64
|
||||
|
||||
def _is_valid_schema_name(self, value):
|
||||
# must match the dbname regex, and
|
||||
# cannot contain a '\' character.
|
||||
return not any([
|
||||
not self.dbname.match(value),
|
||||
("%r" % value).find("\\") != -1
|
||||
])
|
||||
|
||||
@property
|
||||
def collate(self):
|
||||
"""Get the appropriate collate value."""
|
||||
if not self._collate and not self._character_set:
|
||||
return self.__collation__
|
||||
elif not self._collate:
|
||||
return self.charset[self._character_set][0]
|
||||
else:
|
||||
return self._collate
|
||||
|
||||
@collate.setter
|
||||
def collate(self, value):
|
||||
"""Validate the collation and set it."""
|
||||
if not value:
|
||||
pass
|
||||
elif self._character_set:
|
||||
if value not in self.charset[self._character_set]:
|
||||
msg = (_("%(val)s not a valid collation for charset %(char)s.")
|
||||
% {'val': value, 'char': self._character_set})
|
||||
raise ValueError(msg)
|
||||
self._collate = value
|
||||
else:
|
||||
if value not in self.collation:
|
||||
raise ValueError(_("'%s' not a valid collation.") % value)
|
||||
self._collate = value
|
||||
self._character_set = self.collation[value]
|
||||
|
||||
@property
|
||||
def character_set(self):
|
||||
"""Get the appropriate character set value."""
|
||||
if not self._character_set:
|
||||
return self.__charset__
|
||||
else:
|
||||
return self._character_set
|
||||
|
||||
@character_set.setter
|
||||
def character_set(self, value):
|
||||
"""Validate the character set and set it."""
|
||||
if not value:
|
||||
pass
|
||||
elif value not in self.charset:
|
||||
raise ValueError(_("'%s' not a valid character set.") % value)
|
||||
else:
|
||||
self._character_set = value
|
||||
|
||||
def verify_dict(self):
|
||||
# Also check the collate and character_set values if set, initialize
|
||||
# them if not.
|
||||
super(MySQLSchema, self).verify_dict()
|
||||
if self.__dict__.get('_collate'):
|
||||
self.collate = self._collate
|
||||
else:
|
||||
self._collate = None
|
||||
if self.__dict__.get('_character_set'):
|
||||
self.character_set = self._character_set
|
||||
else:
|
||||
self._character_set = None
|
||||
|
||||
|
||||
class MySQLUser(models.DatastoreUser):
|
||||
"""Represents a MySQL User and its associated properties."""
|
||||
|
||||
not_supported_chars = re.compile("^\s|\s$|'|\"|;|`|,|/|\\\\")
|
||||
|
||||
def _is_valid_string(self, value):
|
||||
if (not value or
|
||||
self.not_supported_chars.search(value) or
|
||||
("%r" % value).find("\\") != -1):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def _is_valid_user_name(self, value):
|
||||
return self._is_valid_string(value)
|
||||
|
||||
def _is_valid_password(self, value):
|
||||
return self._is_valid_string(value)
|
||||
|
||||
def _is_valid_host_name(self, value):
|
||||
if value in [None, "%"]:
|
||||
# % is MySQL shorthand for "everywhere". Always permitted.
|
||||
# Null host defaults to % anyway.
|
||||
return True
|
||||
if CONF.hostname_require_valid_ip:
|
||||
try:
|
||||
# '%' works as a MySQL wildcard, but it is not a valid
|
||||
# part of an IPAddress
|
||||
netaddr.IPAddress(value.replace('%', '1'))
|
||||
except (ValueError, netaddr.AddrFormatError):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
else:
|
||||
# If it wasn't required, anything else goes.
|
||||
return True
|
||||
|
||||
def _build_database_schema(self, name):
|
||||
return MySQLSchema(name)
|
||||
|
||||
def deserialize_schema(self, value):
|
||||
return MySQLSchema.deserialize(value)
|
||||
|
||||
@property
|
||||
def _max_user_name_length(self):
|
||||
return 16
|
||||
|
||||
@property
|
||||
def schema_model(self):
|
||||
return MySQLSchema
|
0
trove/common/db/postgresql/__init__.py
Normal file
0
trove/common/db/postgresql/__init__.py
Normal file
70
trove/common/db/postgresql/models.py
Normal file
70
trove/common/db/postgresql/models.py
Normal file
@ -0,0 +1,70 @@
|
||||
# Copyright 2016 Tesora, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import re
|
||||
|
||||
from six import u
|
||||
|
||||
from trove.common.db import models
|
||||
|
||||
|
||||
class PostgreSQLSchema(models.DatastoreSchema):
|
||||
"""Represents a PostgreSQL schema and its associated properties."""
|
||||
|
||||
name_regex = re.compile(u(r'^[\u0001-\u007F\u0080-\uFFFF]+[^\s]$'))
|
||||
|
||||
def __init__(self, name=None, collate=None, character_set=None,
|
||||
deserializing=False):
|
||||
super(PostgreSQLSchema, self).__init__(name=name,
|
||||
deserializing=deserializing)
|
||||
self.collate = collate
|
||||
self.character_set = character_set
|
||||
|
||||
@property
|
||||
def collate(self):
|
||||
return self._collate
|
||||
|
||||
@collate.setter
|
||||
def collate(self, value):
|
||||
self._collate = value
|
||||
|
||||
@property
|
||||
def character_set(self):
|
||||
return self._character_set
|
||||
|
||||
@character_set.setter
|
||||
def character_set(self, value):
|
||||
self._character_set = value
|
||||
|
||||
@property
|
||||
def _max_schema_name_length(self):
|
||||
return 63
|
||||
|
||||
def _is_valid_schema_name(self, value):
|
||||
return self.name_regex.match(value) is not None
|
||||
|
||||
|
||||
class PostgreSQLUser(models.DatastoreUser):
|
||||
"""Represents a PostgreSQL user and its associated properties."""
|
||||
|
||||
root_username = 'postgres'
|
||||
|
||||
@property
|
||||
def _max_user_name_length(self):
|
||||
return 63
|
||||
|
||||
@property
|
||||
def schema_model(self):
|
||||
return PostgreSQLSchema
|
@ -13,15 +13,15 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from trove.common.db.cassandra import models as guest_models
|
||||
from trove.extensions.common.service import DefaultRootController
|
||||
from trove.extensions.mysql import models
|
||||
from trove.guestagent.db import models as guest_models
|
||||
|
||||
|
||||
class CassandraRootController(DefaultRootController):
|
||||
|
||||
def _find_root_user(self, context, instance_id):
|
||||
user = guest_models.CassandraRootUser()
|
||||
user = guest_models.CassandraUser.root()
|
||||
# TODO(pmalik): Using MySQL model until we have datastore specific
|
||||
# extensions (bug/1498573).
|
||||
return models.User.load(
|
||||
|
@ -15,11 +15,11 @@
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from trove.common.db import models as guest_models
|
||||
from trove.common import exception
|
||||
from trove.common.remote import create_guest_client
|
||||
from trove.common import utils
|
||||
from trove.db import get_db_api
|
||||
from trove.guestagent.db import models as guest_models
|
||||
from trove.instance import models as base_models
|
||||
|
||||
|
||||
@ -64,8 +64,9 @@ class Root(object):
|
||||
else:
|
||||
root = create_guest_client(context, instance_id).enable_root()
|
||||
|
||||
root_user = guest_models.RootUser()
|
||||
root_user.deserialize(root)
|
||||
root_user = guest_models.DatastoreUser.deserialize(root,
|
||||
verify=False)
|
||||
root_user.make_root()
|
||||
|
||||
# if cluster_instances_list none, then root create is called for
|
||||
# single instance, adding an RootHistory entry for the instance_id
|
||||
|
@ -14,8 +14,8 @@
|
||||
|
||||
from six.moves.urllib.parse import unquote
|
||||
|
||||
from trove.common.db.mysql import models as guest_models
|
||||
from trove.common import exception
|
||||
from trove.guestagent.db import models as guest_models
|
||||
|
||||
|
||||
def populate_validated_databases(dbs):
|
||||
@ -27,8 +27,8 @@ def populate_validated_databases(dbs):
|
||||
databases = []
|
||||
unique_identities = set()
|
||||
for database in dbs:
|
||||
mydb = guest_models.ValidatedMySQLDatabase()
|
||||
mydb.name = database.get('name', '')
|
||||
mydb = guest_models.MySQLSchema(name=database.get('name', ''))
|
||||
mydb.check_reserved()
|
||||
if mydb.name in unique_identities:
|
||||
raise exception.DatabaseInitialDatabaseDuplicateError()
|
||||
unique_identities.add(mydb.name)
|
||||
@ -49,9 +49,9 @@ def populate_users(users, initial_databases=None):
|
||||
users_data = []
|
||||
unique_identities = set()
|
||||
for user in users:
|
||||
u = guest_models.MySQLUser()
|
||||
u.name = user.get('name', '')
|
||||
u.host = user.get('host', '%')
|
||||
u = guest_models.MySQLUser(name=user.get('name', ''),
|
||||
host=user.get('host', '%'))
|
||||
u.check_reserved()
|
||||
user_identity = (u.name, u.host)
|
||||
if user_identity in unique_identities:
|
||||
raise exception.DatabaseInitialUserDuplicateError()
|
||||
|
@ -18,13 +18,13 @@ Model classes that extend the instances functionality for MySQL instances.
|
||||
"""
|
||||
|
||||
from trove.common import cfg
|
||||
from trove.common.db.mysql import models as guest_models
|
||||
from trove.common import exception
|
||||
from trove.common.notification import StartNotification
|
||||
from trove.common.remote import create_guest_client
|
||||
from trove.common import utils
|
||||
from trove.extensions.common.models import load_and_verify
|
||||
from trove.extensions.common.models import RootHistory
|
||||
from trove.guestagent.db import models as guest_models
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
@ -46,12 +46,10 @@ class User(object):
|
||||
@classmethod
|
||||
def load(cls, context, instance_id, username, hostname, root_user=False):
|
||||
load_and_verify(context, instance_id)
|
||||
validate = guest_models.MySQLUser(name=username, host=hostname)
|
||||
if root_user:
|
||||
validate = guest_models.RootUser()
|
||||
else:
|
||||
validate = guest_models.MySQLUser()
|
||||
validate.name = username
|
||||
validate.host = hostname
|
||||
validate.make_root()
|
||||
validate.check_reserved()
|
||||
client = create_guest_client(context, instance_id)
|
||||
found_user = client.get_user(username=username, hostname=hostname)
|
||||
if not found_user:
|
||||
@ -138,14 +136,12 @@ class User(object):
|
||||
user_changed = user_attrs.get('name')
|
||||
host_changed = user_attrs.get('host')
|
||||
|
||||
validate = guest_models.MySQLUser()
|
||||
if host_changed:
|
||||
validate.host = host_changed
|
||||
if user_changed:
|
||||
validate.name = user_changed
|
||||
|
||||
user = user_changed or username
|
||||
host = host_changed or hostname
|
||||
|
||||
validate = guest_models.MySQLUser(name=user, host=host)
|
||||
validate.check_reserved()
|
||||
|
||||
userhost = "%s@%s" % (user, host)
|
||||
if user_changed or host_changed:
|
||||
existing_users, _nadda = Users.load_with_client(
|
||||
@ -194,8 +190,8 @@ class Users(object):
|
||||
include_marker=include_marker)
|
||||
model_users = []
|
||||
for user in user_list:
|
||||
mysql_user = guest_models.MySQLUser()
|
||||
mysql_user.deserialize(user)
|
||||
mysql_user = guest_models.MySQLUser.deserialize(user,
|
||||
verify=False)
|
||||
if mysql_user.name in cfg.get_ignored_users():
|
||||
continue
|
||||
# TODO(hub-cap): databases are not being returned in the
|
||||
@ -257,8 +253,8 @@ class Schemas(object):
|
||||
include_marker=include_marker)
|
||||
model_schemas = []
|
||||
for schema in schemas:
|
||||
mysql_schema = guest_models.MySQLDatabase()
|
||||
mysql_schema.deserialize(schema)
|
||||
mysql_schema = guest_models.MySQLSchema.deserialize(schema,
|
||||
verify=False)
|
||||
if mysql_schema.name in cfg.get_ignored_dbs():
|
||||
continue
|
||||
model_schemas.append(Schema(mysql_schema.name,
|
||||
|
@ -21,6 +21,7 @@ import webob.exc
|
||||
|
||||
import trove.common.apischema as apischema
|
||||
from trove.common import cfg
|
||||
from trove.common.db.mysql import models as guest_models
|
||||
from trove.common import exception
|
||||
from trove.common.i18n import _
|
||||
from trove.common import notification
|
||||
@ -34,7 +35,6 @@ from trove.extensions.mysql.common import populate_validated_databases
|
||||
from trove.extensions.mysql.common import unquote_user_host
|
||||
from trove.extensions.mysql import models
|
||||
from trove.extensions.mysql import views
|
||||
from trove.guestagent.db import models as guest_models
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -85,7 +85,8 @@ class UserController(wsgi.Controller):
|
||||
model_users = populate_users(users)
|
||||
models.User.create(context, instance_id, model_users)
|
||||
except (ValueError, AttributeError) as e:
|
||||
raise exception.BadRequest(msg=str(e))
|
||||
raise exception.BadRequest(_("User create error: %(e)s")
|
||||
% {'e': e})
|
||||
return wsgi.Result(None, 202)
|
||||
|
||||
def delete(self, req, tenant_id, instance_id, id):
|
||||
@ -95,21 +96,21 @@ class UserController(wsgi.Controller):
|
||||
context = req.environ[wsgi.CONTEXT_KEY]
|
||||
id = correct_id_with_req(id, req)
|
||||
username, host = unquote_user_host(id)
|
||||
user = None
|
||||
context.notification = notification.DBaaSUserDelete(context,
|
||||
request=req)
|
||||
with StartNotification(context, instance_id=instance_id,
|
||||
username=username):
|
||||
user = None
|
||||
try:
|
||||
user = guest_models.MySQLUser()
|
||||
user.name = username
|
||||
user.host = host
|
||||
user = guest_models.MySQLUser(name=username,
|
||||
host=host)
|
||||
found_user = models.User.load(context, instance_id, username,
|
||||
host)
|
||||
if not found_user:
|
||||
user = None
|
||||
except (ValueError, AttributeError) as e:
|
||||
raise exception.BadRequest(msg=str(e))
|
||||
raise exception.BadRequest(_("User delete error: %(e)s")
|
||||
% {'e': e})
|
||||
if not user:
|
||||
raise exception.UserNotFound(uuid=id)
|
||||
models.User.delete(context, instance_id, user.serialize())
|
||||
@ -127,7 +128,8 @@ class UserController(wsgi.Controller):
|
||||
try:
|
||||
user = models.User.load(context, instance_id, username, host)
|
||||
except (ValueError, AttributeError) as e:
|
||||
raise exception.BadRequest(msg=str(e))
|
||||
raise exception.BadRequest(_("User show error: %(e)s")
|
||||
% {'e': e})
|
||||
if not user:
|
||||
raise exception.UserNotFound(uuid=id)
|
||||
view = views.UserView(user)
|
||||
@ -151,14 +153,16 @@ class UserController(wsgi.Controller):
|
||||
user = models.User.load(context, instance_id, username,
|
||||
hostname)
|
||||
except (ValueError, AttributeError) as e:
|
||||
raise exception.BadRequest(msg=str(e))
|
||||
raise exception.BadRequest(_("Error loading user: %(e)s")
|
||||
% {'e': e})
|
||||
if not user:
|
||||
raise exception.UserNotFound(uuid=id)
|
||||
try:
|
||||
models.User.update_attributes(context, instance_id, username,
|
||||
hostname, user_attrs)
|
||||
except (ValueError, AttributeError) as e:
|
||||
raise exception.BadRequest(msg=str(e))
|
||||
raise exception.BadRequest(_("User update error: %(e)s")
|
||||
% {'e': e})
|
||||
return wsgi.Result(None, 202)
|
||||
|
||||
def update_all(self, req, body, tenant_id, instance_id):
|
||||
@ -170,16 +174,15 @@ class UserController(wsgi.Controller):
|
||||
context.notification = notification.DBaaSUserChangePassword(
|
||||
context, request=req)
|
||||
users = body['users']
|
||||
model_users = []
|
||||
with StartNotification(context, instance_id=instance_id,
|
||||
username=",".join([user['name']
|
||||
for user in users])):
|
||||
model_users = []
|
||||
for user in users:
|
||||
try:
|
||||
mu = guest_models.MySQLUser()
|
||||
mu.name = user['name']
|
||||
mu.host = user.get('host')
|
||||
mu.password = user['password']
|
||||
mu = guest_models.MySQLUser(name=user['name'],
|
||||
host=user.get('host'),
|
||||
password=user['password'])
|
||||
found_user = models.User.load(context, instance_id,
|
||||
mu.name, mu.host)
|
||||
if not found_user:
|
||||
@ -189,8 +192,14 @@ class UserController(wsgi.Controller):
|
||||
raise exception.UserNotFound(uuid=user_and_host)
|
||||
model_users.append(mu)
|
||||
except (ValueError, AttributeError) as e:
|
||||
raise exception.BadRequest(msg=str(e))
|
||||
models.User.change_password(context, instance_id, model_users)
|
||||
raise exception.BadRequest(_("Error loading user: %(e)s")
|
||||
% {'e': e})
|
||||
try:
|
||||
models.User.change_password(context, instance_id, model_users)
|
||||
except (ValueError, AttributeError) as e:
|
||||
raise exception.BadRequest(_("User password update error: "
|
||||
"%(e)s")
|
||||
% {'e': e})
|
||||
return wsgi.Result(None, 202)
|
||||
|
||||
|
||||
@ -210,7 +219,8 @@ class UserAccessController(wsgi.Controller):
|
||||
try:
|
||||
user = models.User.load(context, instance_id, username, hostname)
|
||||
except (ValueError, AttributeError) as e:
|
||||
raise exception.BadRequest(msg=str(e))
|
||||
raise exception.BadRequest(_("Error loading user: %(e)s")
|
||||
% {'e': e})
|
||||
if not user:
|
||||
raise exception.UserNotFound(uuid=user_id)
|
||||
return user
|
||||
@ -311,8 +321,12 @@ class SchemaController(wsgi.Controller):
|
||||
with StartNotification(context, instance_id=instance_id,
|
||||
dbname=".".join([db['name']
|
||||
for db in schemas])):
|
||||
model_schemas = populate_validated_databases(schemas)
|
||||
models.Schema.create(context, instance_id, model_schemas)
|
||||
try:
|
||||
model_schemas = populate_validated_databases(schemas)
|
||||
models.Schema.create(context, instance_id, model_schemas)
|
||||
except (ValueError, AttributeError) as e:
|
||||
raise exception.BadRequest(_("Database create error: %(e)s")
|
||||
% {'e': e})
|
||||
return wsgi.Result(None, 202)
|
||||
|
||||
def delete(self, req, tenant_id, instance_id, id):
|
||||
@ -324,11 +338,12 @@ class SchemaController(wsgi.Controller):
|
||||
context, request=req)
|
||||
with StartNotification(context, instance_id=instance_id, dbname=id):
|
||||
try:
|
||||
schema = guest_models.ValidatedMySQLDatabase()
|
||||
schema.name = id
|
||||
schema = guest_models.MySQLSchema(name=id)
|
||||
schema.check_delete()
|
||||
models.Schema.delete(context, instance_id, schema.serialize())
|
||||
except (ValueError, AttributeError) as e:
|
||||
raise exception.BadRequest(msg=str(e))
|
||||
raise exception.BadRequest(_("Database delete error: %(e)s")
|
||||
% {'e': e})
|
||||
return wsgi.Result(None, 202)
|
||||
|
||||
def show(self, req, tenant_id, instance_id, id):
|
||||
@ -338,7 +353,7 @@ class SchemaController(wsgi.Controller):
|
||||
class MySQLRootController(DefaultRootController):
|
||||
|
||||
def _find_root_user(self, context, instance_id):
|
||||
user = guest_models.MySQLRootUser()
|
||||
user = guest_models.MySQLUser.root()
|
||||
return models.User.load(context, instance_id,
|
||||
user.name, user.host,
|
||||
root_user=True)
|
||||
|
@ -13,15 +13,15 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from trove.common.db.postgresql import models as guest_models
|
||||
from trove.extensions.common.service import DefaultRootController
|
||||
from trove.extensions.mysql import models
|
||||
from trove.guestagent.db import models as guest_models
|
||||
|
||||
|
||||
class PostgreSQLRootController(DefaultRootController):
|
||||
|
||||
def _find_root_user(self, context, instance_id):
|
||||
user = guest_models.PostgreSQLRootUser()
|
||||
user = guest_models.PostgreSQLUser.root()
|
||||
# This is currently using MySQL model.
|
||||
# MySQL extension *should* work for now, but may lead to
|
||||
# future bugs (incompatible input validation, unused field etc).
|
||||
|
@ -26,6 +26,7 @@ from oslo_log import log as logging
|
||||
from oslo_utils import netutils
|
||||
|
||||
from trove.common import cfg
|
||||
from trove.common.db.cassandra import models
|
||||
from trove.common import exception
|
||||
from trove.common.i18n import _
|
||||
from trove.common import instance as rd_instance
|
||||
@ -40,7 +41,6 @@ from trove.guestagent.common import guestagent_utils
|
||||
from trove.guestagent.common import operating_system
|
||||
from trove.guestagent.common.operating_system import FileMode
|
||||
from trove.guestagent.datastore import service
|
||||
from trove.guestagent.db import models
|
||||
from trove.guestagent import pkg
|
||||
|
||||
|
||||
@ -141,10 +141,6 @@ class CassandraApp(object):
|
||||
return guestagent_utils.build_file_path(self.cassandra_conf_dir,
|
||||
self.CASSANDRA_LOGBACK_FILE)
|
||||
|
||||
@property
|
||||
def default_superuser_name(self):
|
||||
return "cassandra"
|
||||
|
||||
@property
|
||||
def default_superuser_password(self):
|
||||
return "cassandra"
|
||||
@ -311,7 +307,8 @@ class CassandraApp(object):
|
||||
CassandraAdmin(update_user).alter_user_password(os_admin)
|
||||
else:
|
||||
cassandra = models.CassandraUser(
|
||||
self.default_superuser_name, self.default_superuser_password)
|
||||
models.CassandraUser.root_username,
|
||||
self.default_superuser_password)
|
||||
CassandraAdmin(cassandra)._create_superuser(os_admin)
|
||||
CassandraAdmin(os_admin).drop_user(cassandra)
|
||||
|
||||
@ -328,7 +325,7 @@ class CassandraApp(object):
|
||||
self.status = CassandraAppStatus(user)
|
||||
|
||||
def store_admin_credentials(self, admin_credentials):
|
||||
user = models.CassandraUser.deserialize_user(admin_credentials)
|
||||
user = models.CassandraUser.deserialize(admin_credentials)
|
||||
self._update_admin_credentials(user)
|
||||
|
||||
def get_admin_credentials(self):
|
||||
@ -448,8 +445,8 @@ class CassandraApp(object):
|
||||
LOG.warning(
|
||||
_("Trove administrative user has not been configured yet. "
|
||||
"Using the built-in default: %s")
|
||||
% self.default_superuser_name)
|
||||
return models.CassandraUser(self.default_superuser_name,
|
||||
% models.CassandraUser.root_username)
|
||||
return models.CassandraUser(models.CassandraUser.root_username,
|
||||
self.default_superuser_password)
|
||||
|
||||
def has_user_config(self):
|
||||
@ -731,7 +728,7 @@ class CassandraApp(object):
|
||||
Create a new superuser if it does not exist and grant it full
|
||||
superuser-level access to all keyspaces.
|
||||
"""
|
||||
cassandra = models.CassandraRootUser(password=root_password)
|
||||
cassandra = models.CassandraUser.root(password=root_password)
|
||||
admin = self.build_admin()
|
||||
if self.is_root_enabled():
|
||||
admin.alter_user_password(cassandra)
|
||||
@ -897,7 +894,7 @@ class CassandraAdmin(object):
|
||||
|
||||
def _load_user(self, client, username, check_reserved=True):
|
||||
if check_reserved:
|
||||
self._check_reserved_user_name(username)
|
||||
models.CassandraUser(username).check_reserved()
|
||||
|
||||
acl = self._get_acl(client, username=username)
|
||||
return self._build_user(username, acl)
|
||||
@ -1002,8 +999,8 @@ class CassandraAdmin(object):
|
||||
Grant all non-superuser permissions on a keyspace to a given user.
|
||||
"""
|
||||
if check_reserved:
|
||||
self._check_reserved_user_name(user.name)
|
||||
self._check_reserved_keyspace_name(keyspace.name)
|
||||
user.check_reserved()
|
||||
keyspace.check_reserved()
|
||||
|
||||
for access in self.__NO_SUPERUSER_MODIFIERS:
|
||||
self._grant_permission_on_keyspace(client, access, keyspace, user)
|
||||
@ -1026,8 +1023,8 @@ class CassandraAdmin(object):
|
||||
def _revoke_all_access_on_keyspace(self, client, keyspace, user,
|
||||
check_reserved=True):
|
||||
if check_reserved:
|
||||
self._check_reserved_user_name(user.name)
|
||||
self._check_reserved_keyspace_name(keyspace.name)
|
||||
user.check_reserved()
|
||||
keyspace.check_reserved()
|
||||
|
||||
LOG.debug("Revoking all permissions on '%s' from user '%s'."
|
||||
% (keyspace.name, user.name))
|
||||
@ -1149,32 +1146,24 @@ class CassandraAdmin(object):
|
||||
|
||||
def _deserialize_keyspace(self, keyspace_dict, check_reserved=True):
|
||||
if keyspace_dict:
|
||||
db = models.CassandraSchema.deserialize_schema(keyspace_dict)
|
||||
db = models.CassandraSchema.deserialize(keyspace_dict)
|
||||
if check_reserved:
|
||||
self._check_reserved_keyspace_name(db.name)
|
||||
db.check_reserved()
|
||||
|
||||
return db
|
||||
|
||||
return None
|
||||
|
||||
def _check_reserved_keyspace_name(self, name):
|
||||
if name in self.ignore_dbs:
|
||||
raise ValueError(_("This keyspace-name is reserved: %s") % name)
|
||||
|
||||
def _deserialize_user(self, user_dict, check_reserved=True):
|
||||
if user_dict:
|
||||
user = models.CassandraUser.deserialize_user(user_dict)
|
||||
user = models.CassandraUser.deserialize(user_dict)
|
||||
if check_reserved:
|
||||
self._check_reserved_user_name(user.name)
|
||||
user.check_reserved()
|
||||
|
||||
return user
|
||||
|
||||
return None
|
||||
|
||||
def _check_reserved_user_name(self, name):
|
||||
if name in self.ignore_users:
|
||||
raise ValueError(_("This user-name is reserved: %s") % name)
|
||||
|
||||
@property
|
||||
def ignore_users(self):
|
||||
return cfg.get_ignored_users()
|
||||
|
@ -25,6 +25,7 @@ import pexpect
|
||||
import six
|
||||
|
||||
from trove.common import cfg
|
||||
from trove.common.db import models
|
||||
from trove.common import exception
|
||||
from trove.common.i18n import _
|
||||
from trove.common import instance as rd_instance
|
||||
@ -32,7 +33,6 @@ from trove.common import utils as utils
|
||||
from trove.guestagent.common import operating_system
|
||||
from trove.guestagent.datastore.experimental.couchbase import system
|
||||
from trove.guestagent.datastore import service
|
||||
from trove.guestagent.db import models
|
||||
from trove.guestagent import pkg
|
||||
|
||||
|
||||
@ -210,10 +210,7 @@ class CouchbaseRootAccess(object):
|
||||
|
||||
@classmethod
|
||||
def enable_root(cls, root_password=None):
|
||||
user = models.RootUser()
|
||||
user.name = "root"
|
||||
user.host = "%"
|
||||
user.password = root_password or utils.generate_random_password()
|
||||
user = models.DatastoreUser.root(password=root_password)
|
||||
|
||||
if root_password:
|
||||
CouchbaseRootAccess().write_password_to_file(root_password)
|
||||
|
@ -19,6 +19,7 @@ import json
|
||||
from oslo_log import log as logging
|
||||
|
||||
from trove.common import cfg
|
||||
from trove.common.db.couchdb import models
|
||||
from trove.common import exception
|
||||
from trove.common.i18n import _
|
||||
from trove.common import instance as rd_instance
|
||||
@ -29,7 +30,6 @@ from trove.guestagent.common import operating_system
|
||||
from trove.guestagent.common.operating_system import FileMode
|
||||
from trove.guestagent.datastore.experimental.couchdb import system
|
||||
from trove.guestagent.datastore import service
|
||||
from trove.guestagent.db import models
|
||||
from trove.guestagent import pkg
|
||||
|
||||
CONF = cfg.CONF
|
||||
@ -218,7 +218,7 @@ class CouchDBAdmin(object):
|
||||
self._admin_user()
|
||||
try:
|
||||
for item in users:
|
||||
user = models.CouchDBUser.deserialize_user(item)
|
||||
user = models.CouchDBUser.deserialize(item)
|
||||
try:
|
||||
LOG.debug("Creating user: %s." % user.name)
|
||||
utils.execute_with_timeout(
|
||||
@ -234,7 +234,7 @@ class CouchDBAdmin(object):
|
||||
pass
|
||||
|
||||
for database in user.databases:
|
||||
mydb = models.CouchDBSchema.deserialize_schema(database)
|
||||
mydb = models.CouchDBSchema.deserialize(database)
|
||||
try:
|
||||
LOG.debug("Granting user: %s access to database: %s."
|
||||
% (user.name, mydb.name))
|
||||
@ -257,7 +257,7 @@ class CouchDBAdmin(object):
|
||||
|
||||
def delete_user(self, user):
|
||||
LOG.debug("Delete a given CouchDB user.")
|
||||
couchdb_user = models.CouchDBUser.deserialize_user(user)
|
||||
couchdb_user = models.CouchDBUser.deserialize(user)
|
||||
db_names = self.list_database_names()
|
||||
|
||||
for db in db_names:
|
||||
@ -457,7 +457,7 @@ class CouchDBAdmin(object):
|
||||
|
||||
def enable_root(self, root_pwd=None):
|
||||
'''Create admin user root'''
|
||||
root_user = models.CouchDBRootUser(password=root_pwd)
|
||||
root_user = models.CouchDBUser.root(password=root_pwd)
|
||||
out, err = utils.execute_with_timeout(
|
||||
system.ENABLE_ROOT %
|
||||
{'admin_name': self._admin_user().name,
|
||||
@ -486,7 +486,7 @@ class CouchDBAdmin(object):
|
||||
LOG.debug("Creating CouchDB databases.")
|
||||
|
||||
for database in databases:
|
||||
dbName = models.CouchDBSchema.deserialize_schema(database).name
|
||||
dbName = models.CouchDBSchema.deserialize(database).name
|
||||
if self._is_modifiable_database(dbName):
|
||||
LOG.debug('Creating CouchDB database %s' % dbName)
|
||||
try:
|
||||
@ -535,7 +535,7 @@ class CouchDBAdmin(object):
|
||||
|
||||
def delete_database(self, database):
|
||||
'''Delete the specified database.'''
|
||||
dbName = models.CouchDBSchema.deserialize_schema(database).name
|
||||
dbName = models.CouchDBSchema.deserialize(database).name
|
||||
if self._is_modifiable_database(dbName):
|
||||
try:
|
||||
LOG.debug("Deleting CouchDB database: %s." % dbName)
|
||||
|
@ -19,6 +19,7 @@ from oslo_log import log as logging
|
||||
from oslo_utils import encodeutils
|
||||
|
||||
from trove.common import cfg
|
||||
from trove.common.db import models
|
||||
from trove.common import exception
|
||||
from trove.common.i18n import _
|
||||
from trove.common import instance as rd_instance
|
||||
@ -30,7 +31,6 @@ from trove.guestagent.common import guestagent_utils
|
||||
from trove.guestagent.common import operating_system
|
||||
from trove.guestagent.datastore.experimental.db2 import system
|
||||
from trove.guestagent.datastore import service
|
||||
from trove.guestagent.db import models
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -346,8 +346,8 @@ class DB2Admin(object):
|
||||
db_create_failed = []
|
||||
LOG.debug("Creating DB2 databases.")
|
||||
for item in databases:
|
||||
mydb = models.ValidatedMySQLDatabase()
|
||||
mydb.deserialize(item)
|
||||
mydb = models.DatastoreSchema.deserialize(item)
|
||||
mydb.check_create()
|
||||
dbName = mydb.name
|
||||
LOG.debug("Creating DB2 database: %s." % dbName)
|
||||
try:
|
||||
@ -385,8 +385,8 @@ class DB2Admin(object):
|
||||
"""Delete the specified database."""
|
||||
dbName = None
|
||||
try:
|
||||
mydb = models.ValidatedMySQLDatabase()
|
||||
mydb.deserialize(database)
|
||||
mydb = models.DatastoreSchema.deserialize(database)
|
||||
mydb.check_delete()
|
||||
dbName = mydb.name
|
||||
LOG.debug("Deleting DB2 database: %s." % dbName)
|
||||
run_command(system.DELETE_DB_COMMAND % {'dbname': dbName})
|
||||
@ -423,11 +423,8 @@ class DB2Admin(object):
|
||||
while item:
|
||||
count = count + 1
|
||||
if (limit and count <= limit) or limit is None:
|
||||
db2_db = models.MySQLDatabase()
|
||||
db2_db.name = item
|
||||
db2_db = models.DatastoreSchema(name=item)
|
||||
LOG.debug("database = %s ." % item)
|
||||
db2_db.character_set = None
|
||||
db2_db.collate = None
|
||||
next_marker = db2_db.name
|
||||
databases.append(db2_db.serialize())
|
||||
item = next(result)
|
||||
@ -448,8 +445,8 @@ class DB2Admin(object):
|
||||
LOG.debug("Creating user(s) for accessing DB2 database(s).")
|
||||
try:
|
||||
for item in users:
|
||||
user = models.MySQLUser()
|
||||
user.deserialize(item)
|
||||
user = models.DatastoreUser.deserialize(item)
|
||||
user.check_create()
|
||||
try:
|
||||
LOG.debug("Creating OS user: %s." % user.name)
|
||||
utils.execute_with_timeout(
|
||||
@ -461,8 +458,7 @@ class DB2Admin(object):
|
||||
continue
|
||||
|
||||
for database in user.databases:
|
||||
mydb = models.ValidatedMySQLDatabase()
|
||||
mydb.deserialize(database)
|
||||
mydb = models.DatastoreSchema.deserialize(database)
|
||||
try:
|
||||
LOG.debug("Granting user: %s access to database: %s."
|
||||
% (user.name, mydb.name))
|
||||
@ -481,8 +477,8 @@ class DB2Admin(object):
|
||||
|
||||
def delete_user(self, user):
|
||||
LOG.debug("Delete a given user.")
|
||||
db2_user = models.MySQLUser()
|
||||
db2_user.deserialize(user)
|
||||
db2_user = models.DatastoreUser.deserialize(user)
|
||||
db2_user.check_delete()
|
||||
userName = db2_user.name
|
||||
user_dbs = db2_user.databases
|
||||
LOG.debug("For user %s, databases to be deleted = %r." % (
|
||||
@ -495,8 +491,7 @@ class DB2Admin(object):
|
||||
|
||||
LOG.debug("databases for user = %r." % databases)
|
||||
for database in databases:
|
||||
mydb = models.ValidatedMySQLDatabase()
|
||||
mydb.deserialize(database)
|
||||
mydb = models.DatastoreSchema.deserialize(database)
|
||||
try:
|
||||
run_command(system.REVOKE_USER_ACCESS % {
|
||||
'dbname': mydb.name,
|
||||
@ -526,8 +521,7 @@ class DB2Admin(object):
|
||||
|
||||
databases, marker = self.list_databases()
|
||||
for database in databases:
|
||||
db2_db = models.MySQLDatabase()
|
||||
db2_db.deserialize(database)
|
||||
db2_db = models.DatastoreSchema.deserialize(database)
|
||||
out = None
|
||||
try:
|
||||
out, err = run_command(
|
||||
@ -562,8 +556,6 @@ class DB2Admin(object):
|
||||
|
||||
try:
|
||||
item = next(result)
|
||||
db2db = models.MySQLDatabase()
|
||||
db2db.name = db2_db.name
|
||||
|
||||
while item:
|
||||
'''
|
||||
@ -572,7 +564,7 @@ class DB2Admin(object):
|
||||
'''
|
||||
if item in user_map:
|
||||
db2user = user_map.get(item)
|
||||
db2user.databases.append(db2db.serialize())
|
||||
db2user.databases = db2_db.name
|
||||
item = next(result)
|
||||
continue
|
||||
'''
|
||||
@ -581,9 +573,8 @@ class DB2Admin(object):
|
||||
'''
|
||||
count = count + 1
|
||||
if (limit and count <= limit) or limit is None:
|
||||
db2_user = models.MySQLUser()
|
||||
db2_user.name = item
|
||||
db2_user.databases.append(db2db.serialize())
|
||||
db2_user = models.DatastoreUser(name=item,
|
||||
databases=db2_db.name)
|
||||
users.append(db2_user.serialize())
|
||||
user_map.update({item: db2_user})
|
||||
item = next(result)
|
||||
@ -606,13 +597,11 @@ class DB2Admin(object):
|
||||
|
||||
def _get_user(self, username, hostname):
|
||||
LOG.debug("Get details of a given database user %s." % username)
|
||||
user = models.MySQLUser()
|
||||
user.name = username
|
||||
user = models.DatastoreUser(name=username)
|
||||
databases, marker = self.list_databases()
|
||||
out = None
|
||||
for database in databases:
|
||||
db2_db = models.MySQLDatabase()
|
||||
db2_db.deserialize(database)
|
||||
db2_db = models.DatastoreSchema.deserialize(database)
|
||||
try:
|
||||
out, err = run_command(
|
||||
system.LIST_DB_USERS % {'dbname': db2_db.name})
|
||||
|
@ -20,6 +20,7 @@ from oslo_utils import netutils
|
||||
import pymongo
|
||||
|
||||
from trove.common import cfg
|
||||
from trove.common.db.mongodb import models
|
||||
from trove.common import exception
|
||||
from trove.common.i18n import _
|
||||
from trove.common import instance as ds_instance
|
||||
@ -31,7 +32,6 @@ from trove.guestagent.common import guestagent_utils
|
||||
from trove.guestagent.common import operating_system
|
||||
from trove.guestagent.datastore.experimental.mongodb import system
|
||||
from trove.guestagent.datastore import service
|
||||
from trove.guestagent.db import models
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -480,12 +480,6 @@ class MongoDBAdmin(object):
|
||||
type(self).admin_user = user
|
||||
return type(self).admin_user
|
||||
|
||||
def _is_modifiable_user(self, name):
|
||||
if ((name in cfg.get_ignored_users()) or
|
||||
name == system.MONGO_ADMIN_NAME):
|
||||
return False
|
||||
return True
|
||||
|
||||
@property
|
||||
def cmd_admin_auth_params(self):
|
||||
"""Returns a list of strings that constitute MongoDB command line
|
||||
@ -509,10 +503,6 @@ class MongoDBAdmin(object):
|
||||
"""
|
||||
LOG.debug('Creating user %s on database %s with roles %s.'
|
||||
% (user.username, user.database.name, str(user.roles)))
|
||||
|
||||
if not user.password:
|
||||
raise exception.BadRequest(_("User's password is empty."))
|
||||
|
||||
if client:
|
||||
self._create_user_with_client(user, client)
|
||||
else:
|
||||
@ -525,15 +515,19 @@ class MongoDBAdmin(object):
|
||||
"""
|
||||
with MongoDBClient(self._admin_user()) as client:
|
||||
for item in users:
|
||||
user = models.MongoDBUser.deserialize_user(item)
|
||||
if not self._is_modifiable_user(user.name):
|
||||
LOG.warning('Skipping creation of user with reserved '
|
||||
'name %(user)s' % {'user': user.name})
|
||||
elif self._get_user_record(user.name, client=client):
|
||||
LOG.warning('Skipping creation of user with pre-existing '
|
||||
'name %(user)s' % {'user': user.name})
|
||||
else:
|
||||
user = models.MongoDBUser.deserialize(item)
|
||||
# this could be called to create multiple users at once;
|
||||
# catch exceptions, log the message, and continue
|
||||
try:
|
||||
user.check_create()
|
||||
if self._get_user_record(user.name, client=client):
|
||||
raise ValueError('User with name %(user)s already '
|
||||
'exists.' % {'user': user.name})
|
||||
self.create_validated_user(user, client=client)
|
||||
except (ValueError, pymongo.errors.PyMongoError) as e:
|
||||
LOG.error(e)
|
||||
LOG.warning('Skipping creation of user with name %(user)s'
|
||||
% {'user': user.name})
|
||||
|
||||
def delete_validated_user(self, user):
|
||||
"""Deletes a user from their database. The caller should ensure that
|
||||
@ -549,18 +543,14 @@ class MongoDBAdmin(object):
|
||||
"""Delete the given user.
|
||||
:param user: a serialized user object
|
||||
"""
|
||||
user = models.MongoDBUser.deserialize_user(user)
|
||||
if not self._is_modifiable_user(user.name):
|
||||
raise exception.BadRequest(_(
|
||||
'Cannot delete user with reserved name %(user)s')
|
||||
% {'user': user.name})
|
||||
else:
|
||||
self.delete_validated_user(user)
|
||||
user = models.MongoDBUser.deserialize(user)
|
||||
user.check_delete()
|
||||
self.delete_validated_user(user)
|
||||
|
||||
def _get_user_record(self, name, client=None):
|
||||
"""Get the user's record."""
|
||||
user = models.MongoDBUser(name)
|
||||
if not self._is_modifiable_user(user.name):
|
||||
if user.is_ignored:
|
||||
LOG.warning('Skipping retrieval of user with reserved '
|
||||
'name %(user)s' % {'user': user.name})
|
||||
return None
|
||||
@ -576,6 +566,14 @@ class MongoDBAdmin(object):
|
||||
user.roles = user_info['roles']
|
||||
return user
|
||||
|
||||
def get_existing_user(self, name):
|
||||
"""Check that a user exists."""
|
||||
user = self._get_user_record(name)
|
||||
if not user:
|
||||
raise ValueError('User with name %(user)s does not'
|
||||
'exist.' % {'user': name})
|
||||
return user
|
||||
|
||||
def get_user(self, name):
|
||||
"""Get information for the given user."""
|
||||
LOG.debug('Getting user %s.' % name)
|
||||
@ -591,7 +589,7 @@ class MongoDBAdmin(object):
|
||||
for user_info in admin_client.admin.system.users.find():
|
||||
user = models.MongoDBUser(name=user_info['_id'])
|
||||
user.roles = user_info['roles']
|
||||
if self._is_modifiable_user(user.name):
|
||||
if not user.is_ignored:
|
||||
users.append(user)
|
||||
LOG.debug('users = ' + str(users))
|
||||
return guestagent_utils.serialize_list(
|
||||
@ -601,23 +599,24 @@ class MongoDBAdmin(object):
|
||||
def change_passwords(self, users):
|
||||
with MongoDBClient(self._admin_user()) as admin_client:
|
||||
for item in users:
|
||||
user = models.MongoDBUser.deserialize_user(item)
|
||||
if not self._is_modifiable_user(user.name):
|
||||
user = models.MongoDBUser.deserialize(item)
|
||||
# this could be called to create multiple users at once;
|
||||
# catch exceptions, log the message, and continue
|
||||
try:
|
||||
user.check_create()
|
||||
self.get_existing_user(user.name)
|
||||
self.create_validated_user(user, admin_client)
|
||||
LOG.debug('Changing password for user %(user)s'
|
||||
% {'user': user.name})
|
||||
self._create_user_with_client(user, admin_client)
|
||||
except (ValueError, pymongo.errors.PyMongoError) as e:
|
||||
LOG.error(e)
|
||||
LOG.warning('Skipping password change for user with '
|
||||
'reserved name %(user)s.'
|
||||
% {'user': user.name})
|
||||
return None
|
||||
LOG.debug('Changing password for user %(user)s'
|
||||
% {'user': user.name})
|
||||
self._create_user_with_client(user, admin_client)
|
||||
'name %(user)s' % {'user': user.name})
|
||||
|
||||
def update_attributes(self, name, user_attrs):
|
||||
"""Update user attributes."""
|
||||
user = self._get_user_record(name)
|
||||
if not user:
|
||||
raise exception.BadRequest(_(
|
||||
'Cannot update attributes for user %(user)s as it either does '
|
||||
'not exist or is a reserved user.') % {'user': name})
|
||||
user = self.get_existing_user(name)
|
||||
password = user_attrs.get('password')
|
||||
if password:
|
||||
user.password = password
|
||||
@ -632,8 +631,9 @@ class MongoDBAdmin(object):
|
||||
if not password:
|
||||
LOG.debug('Generating root user password.')
|
||||
password = utils.generate_random_password()
|
||||
root_user = models.MongoDBUser(name='admin.root', password=password)
|
||||
root_user = models.MongoDBUser.root(password=password)
|
||||
root_user.roles = {'db': 'admin', 'role': 'root'}
|
||||
root_user.check_create()
|
||||
self.create_validated_user(root_user)
|
||||
return root_user.serialize()
|
||||
|
||||
@ -652,11 +652,7 @@ class MongoDBAdmin(object):
|
||||
|
||||
def grant_access(self, username, databases):
|
||||
"""Adds the RW role to the user for each specified database."""
|
||||
user = self._get_user_record(username)
|
||||
if not user:
|
||||
raise exception.BadRequest(_(
|
||||
'Cannot grant access for reserved or non-existant user '
|
||||
'%(user)s') % {'user': username})
|
||||
user = self.get_existing_user(username)
|
||||
for db_name in databases:
|
||||
# verify the database name
|
||||
models.MongoDBSchema(db_name)
|
||||
@ -673,11 +669,7 @@ class MongoDBAdmin(object):
|
||||
|
||||
def revoke_access(self, username, database):
|
||||
"""Removes the RW role from the user for the specified database."""
|
||||
user = self._get_user_record(username)
|
||||
if not user:
|
||||
raise exception.BadRequest(_(
|
||||
'Cannot revoke access for reserved or non-existant user '
|
||||
'%(user)s') % {'user': username})
|
||||
user = self.get_existing_user(username)
|
||||
# verify the database name
|
||||
models.MongoDBSchema(database)
|
||||
role = {'db': database, 'role': 'readWrite'}
|
||||
@ -690,11 +682,7 @@ class MongoDBAdmin(object):
|
||||
def list_access(self, username):
|
||||
"""Returns a list of all databases for which the user has the RW role.
|
||||
"""
|
||||
user = self._get_user_record(username)
|
||||
if not user:
|
||||
raise exception.BadRequest(_(
|
||||
'Cannot list access for reserved or non-existant user '
|
||||
'%(user)s') % {'user': username})
|
||||
user = self.get_existing_user(username)
|
||||
return user.databases
|
||||
|
||||
def create_database(self, databases):
|
||||
@ -705,17 +693,19 @@ class MongoDBAdmin(object):
|
||||
tmp = 'dummy'
|
||||
with MongoDBClient(self._admin_user()) as admin_client:
|
||||
for item in databases:
|
||||
db_name = models.MongoDBSchema.deserialize_schema(item).name
|
||||
LOG.debug('Creating MongoDB database %s' % db_name)
|
||||
db = admin_client[db_name]
|
||||
schema = models.MongoDBSchema.deserialize(item)
|
||||
schema.check_create()
|
||||
LOG.debug('Creating MongoDB database %s' % schema.name)
|
||||
db = admin_client[schema.name]
|
||||
db[tmp].insert({'dummy': True})
|
||||
db.drop_collection(tmp)
|
||||
|
||||
def delete_database(self, database):
|
||||
"""Deletes the database."""
|
||||
with MongoDBClient(self._admin_user()) as admin_client:
|
||||
db_name = models.MongoDBSchema.deserialize_schema(database).name
|
||||
admin_client.drop_database(db_name)
|
||||
schema = models.MongoDBSchema.deserialize(database)
|
||||
schema.check_delete()
|
||||
admin_client.drop_database(schema.name)
|
||||
|
||||
def list_database_names(self):
|
||||
"""Get the list of database names."""
|
||||
@ -724,12 +714,11 @@ class MongoDBAdmin(object):
|
||||
|
||||
def list_databases(self, limit=None, marker=None, include_marker=False):
|
||||
"""Lists the databases."""
|
||||
db_names = self.list_database_names()
|
||||
for hidden in cfg.get_ignored_dbs():
|
||||
if hidden in db_names:
|
||||
db_names.remove(hidden)
|
||||
databases = [models.MongoDBSchema(db_name)
|
||||
for db_name in db_names]
|
||||
databases = []
|
||||
for db_name in self.list_database_names():
|
||||
schema = models.MongoDBSchema(name=db_name)
|
||||
if not schema.is_ignored():
|
||||
databases.append(schema)
|
||||
LOG.debug('databases = ' + str(databases))
|
||||
return guestagent_utils.serialize_list(
|
||||
databases,
|
||||
@ -767,7 +756,7 @@ class MongoDBAdmin(object):
|
||||
def db_stats(self, database, scale=1):
|
||||
"""Gets the stats for the given database."""
|
||||
with MongoDBClient(self._admin_user()) as admin_client:
|
||||
db_name = models.MongoDBSchema.deserialize_schema(database).name
|
||||
db_name = models.MongoDBSchema.deserialize(database).name
|
||||
return admin_client[db_name].command('dbStats', scale=scale)
|
||||
|
||||
def list_active_shards(self):
|
||||
|
@ -19,6 +19,7 @@ import os
|
||||
from oslo_log import log as logging
|
||||
|
||||
from trove.common import cfg
|
||||
from trove.common.db.postgresql import models
|
||||
from trove.common import exception
|
||||
from trove.common.i18n import _
|
||||
from trove.common import instance as trove_instance
|
||||
@ -29,7 +30,6 @@ from trove.guestagent.datastore.experimental.postgresql.service import (
|
||||
PgSqlAdmin)
|
||||
from trove.guestagent.datastore.experimental.postgresql.service import PgSqlApp
|
||||
from trove.guestagent.datastore import manager
|
||||
from trove.guestagent.db import models
|
||||
from trove.guestagent import guest_log
|
||||
from trove.guestagent import volume
|
||||
|
||||
|
@ -23,6 +23,7 @@ from oslo_log import log as logging
|
||||
import psycopg2
|
||||
|
||||
from trove.common import cfg
|
||||
from trove.common.db.postgresql import models
|
||||
from trove.common import exception
|
||||
from trove.common.i18n import _
|
||||
from trove.common import instance
|
||||
@ -35,7 +36,6 @@ from trove.guestagent.common import operating_system
|
||||
from trove.guestagent.common.operating_system import FileMode
|
||||
from trove.guestagent.datastore.experimental.postgresql import pgsql_query
|
||||
from trove.guestagent.datastore import service
|
||||
from trove.guestagent.db import models
|
||||
from trove.guestagent import pkg
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -505,7 +505,7 @@ class PgSqlApp(object):
|
||||
return user.serialize()
|
||||
|
||||
def build_root_user(self, password=None):
|
||||
return models.PostgreSQLRootUser(password=password)
|
||||
return models.PostgreSQLUser.root(password=password)
|
||||
|
||||
def pg_start_backup(self, backup_label):
|
||||
r = self.build_admin().query(
|
||||
@ -662,7 +662,7 @@ class PgSqlAdmin(object):
|
||||
for database in databases:
|
||||
self._create_database(
|
||||
context,
|
||||
models.PostgreSQLSchema.deserialize_schema(database))
|
||||
models.PostgreSQLSchema.deserialize(database))
|
||||
|
||||
def _create_database(self, context, database):
|
||||
"""Create a database.
|
||||
@ -689,7 +689,7 @@ class PgSqlAdmin(object):
|
||||
"""Delete the specified database.
|
||||
"""
|
||||
self._drop_database(
|
||||
models.PostgreSQLSchema.deserialize_schema(database))
|
||||
models.PostgreSQLSchema.deserialize(database))
|
||||
|
||||
def _drop_database(self, database):
|
||||
"""Drop a given Postgres database.
|
||||
@ -736,7 +736,7 @@ class PgSqlAdmin(object):
|
||||
for user in users:
|
||||
self._create_user(
|
||||
context,
|
||||
models.PostgreSQLUser.deserialize_user(user), None)
|
||||
models.PostgreSQLUser.deserialize(user), None)
|
||||
|
||||
def _create_user(self, context, user, encrypt_password=None, *options):
|
||||
"""Create a user and grant privileges for the specified databases.
|
||||
@ -775,7 +775,7 @@ class PgSqlAdmin(object):
|
||||
)
|
||||
self._grant_access(
|
||||
context, user.name,
|
||||
[models.PostgreSQLSchema.deserialize_schema(db)
|
||||
[models.PostgreSQLSchema.deserialize(db)
|
||||
for db in user.databases])
|
||||
|
||||
def _create_admin_user(self, context, user, encrypt_password=None):
|
||||
@ -827,7 +827,7 @@ class PgSqlAdmin(object):
|
||||
"""Delete the specified user.
|
||||
"""
|
||||
self._drop_user(
|
||||
context, models.PostgreSQLUser.deserialize_user(user))
|
||||
context, models.PostgreSQLUser.deserialize(user))
|
||||
|
||||
def _drop_user(self, context, user):
|
||||
"""Drop a given Postgres user.
|
||||
@ -838,7 +838,7 @@ class PgSqlAdmin(object):
|
||||
# Postgresql requires that you revoke grants before dropping the user
|
||||
dbs = self.list_access(context, user.name, None)
|
||||
for d in dbs:
|
||||
db = models.PostgreSQLSchema.deserialize_schema(d)
|
||||
db = models.PostgreSQLSchema.deserialize(d)
|
||||
self.revoke_access(context, user.name, None, db.name)
|
||||
|
||||
LOG.info(
|
||||
@ -888,7 +888,7 @@ class PgSqlAdmin(object):
|
||||
for user in users:
|
||||
self.alter_user(
|
||||
context,
|
||||
models.PostgreSQLUser.deserialize_user(user), None)
|
||||
models.PostgreSQLUser.deserialize(user), None)
|
||||
|
||||
def alter_user(self, context, user, encrypt_password=None, *options):
|
||||
"""Change the password and options of an existing users.
|
||||
|
@ -20,6 +20,7 @@ from oslo_utils import netutils
|
||||
from six.moves import configparser
|
||||
|
||||
from trove.common import cfg
|
||||
from trove.common.db import models
|
||||
from trove.common import exception
|
||||
from trove.common.i18n import _
|
||||
from trove.common.i18n import _LI
|
||||
@ -33,7 +34,6 @@ from trove.guestagent.common import operating_system
|
||||
from trove.guestagent.common.operating_system import FileMode
|
||||
from trove.guestagent.datastore.experimental.vertica import system
|
||||
from trove.guestagent.datastore import service
|
||||
from trove.guestagent.db import models
|
||||
from trove.guestagent import pkg
|
||||
from trove.guestagent import volume
|
||||
|
||||
@ -475,10 +475,7 @@ class VerticaApp(object):
|
||||
def enable_root(self, root_password=None):
|
||||
"""Resets the root password."""
|
||||
LOG.info(_LI("Enabling root."))
|
||||
user = models.RootUser()
|
||||
user.name = "root"
|
||||
user.host = "%"
|
||||
user.password = root_password or utils.generate_random_password()
|
||||
user = models.DatastoreUser.root(password=root_password)
|
||||
if not self.is_root_enabled():
|
||||
self._create_user(user.name, user.password, 'pseudosuperuser')
|
||||
else:
|
||||
|
@ -34,6 +34,7 @@ from sqlalchemy.sql.expression import text
|
||||
|
||||
from trove.common import cfg
|
||||
from trove.common.configurations import MySQLConfParser
|
||||
from trove.common.db.mysql import models
|
||||
from trove.common import exception
|
||||
from trove.common.exception import PollTimeOut
|
||||
from trove.common.i18n import _
|
||||
@ -46,7 +47,6 @@ from trove.guestagent.common import guestagent_utils
|
||||
from trove.guestagent.common import operating_system
|
||||
from trove.guestagent.common import sql_query
|
||||
from trove.guestagent.datastore import service
|
||||
from trove.guestagent.db import models
|
||||
from trove.guestagent import pkg
|
||||
|
||||
ADMIN_USER_NAME = "os_admin"
|
||||
@ -243,9 +243,7 @@ class BaseMySqlAdmin(object):
|
||||
for db in db_result:
|
||||
LOG.debug("\t db: %s." % db)
|
||||
if db['grantee'] == "'%s'@'%s'" % (user.name, user.host):
|
||||
mysql_db = models.MySQLDatabase()
|
||||
mysql_db.name = db['table_schema']
|
||||
user.databases.append(mysql_db.serialize())
|
||||
user.databases = db['table_schema']
|
||||
|
||||
def change_passwords(self, users):
|
||||
"""Change the passwords of one or more existing users."""
|
||||
@ -256,8 +254,7 @@ class BaseMySqlAdmin(object):
|
||||
user_dict = {'_name': item['name'],
|
||||
'_host': item['host'],
|
||||
'_password': item['password']}
|
||||
user = models.MySQLUser()
|
||||
user.deserialize(user_dict)
|
||||
user = models.MySQLUser.deserialize(user_dict)
|
||||
LOG.debug("\tDeserialized: %s." % user.__dict__)
|
||||
uu = sql_query.SetPassword(user.name, host=user.host,
|
||||
new_password=user.password)
|
||||
@ -295,8 +292,8 @@ class BaseMySqlAdmin(object):
|
||||
"""Create the list of specified databases."""
|
||||
with self.local_sql_client(self.mysql_app.get_engine()) as client:
|
||||
for item in databases:
|
||||
mydb = models.ValidatedMySQLDatabase()
|
||||
mydb.deserialize(item)
|
||||
mydb = models.MySQLSchema.deserialize(item)
|
||||
mydb.check_create()
|
||||
cd = sql_query.CreateDatabase(mydb.name,
|
||||
mydb.character_set,
|
||||
mydb.collate)
|
||||
@ -309,8 +306,8 @@ class BaseMySqlAdmin(object):
|
||||
"""
|
||||
with self.local_sql_client(self.mysql_app.get_engine()) as client:
|
||||
for item in users:
|
||||
user = models.MySQLUser()
|
||||
user.deserialize(item)
|
||||
user = models.MySQLUser.deserialize(item)
|
||||
user.check_create()
|
||||
# TODO(cp16net):Should users be allowed to create users
|
||||
# 'os_admin' or 'debian-sys-maint'
|
||||
g = sql_query.Grant(user=user.name, host=user.host,
|
||||
@ -318,8 +315,7 @@ class BaseMySqlAdmin(object):
|
||||
t = text(str(g))
|
||||
client.execute(t)
|
||||
for database in user.databases:
|
||||
mydb = models.ValidatedMySQLDatabase()
|
||||
mydb.deserialize(database)
|
||||
mydb = models.MySQLSchema.deserialize(database)
|
||||
g = sql_query.Grant(permissions='ALL', database=mydb.name,
|
||||
user=user.name, host=user.host,
|
||||
clear=user.password)
|
||||
@ -329,16 +325,16 @@ class BaseMySqlAdmin(object):
|
||||
def delete_database(self, database):
|
||||
"""Delete the specified database."""
|
||||
with self.local_sql_client(self.mysql_app.get_engine()) as client:
|
||||
mydb = models.ValidatedMySQLDatabase()
|
||||
mydb.deserialize(database)
|
||||
mydb = models.MySQLSchema.deserialize(database)
|
||||
mydb.check_delete()
|
||||
dd = sql_query.DropDatabase(mydb.name)
|
||||
t = text(str(dd))
|
||||
client.execute(t)
|
||||
|
||||
def delete_user(self, user):
|
||||
"""Delete the specified user."""
|
||||
mysql_user = models.MySQLUser()
|
||||
mysql_user.deserialize(user)
|
||||
mysql_user = models.MySQLUser.deserialize(user)
|
||||
mysql_user.check_delete()
|
||||
self.delete_user_by_name(mysql_user.name, mysql_user.host)
|
||||
|
||||
def delete_user_by_name(self, name, host='%'):
|
||||
@ -356,9 +352,11 @@ class BaseMySqlAdmin(object):
|
||||
|
||||
def _get_user(self, username, hostname):
|
||||
"""Return a single user matching the criteria."""
|
||||
user = models.MySQLUser()
|
||||
user = None
|
||||
try:
|
||||
user.name = username # Could possibly throw a BadRequest here.
|
||||
# Could possibly throw a ValueError here.
|
||||
user = models.MySQLUser(name=username)
|
||||
user.check_reserved()
|
||||
except ValueError as ve:
|
||||
LOG.exception(_("Error Getting user information"))
|
||||
err_msg = encodeutils.exception_to_unicode(ve)
|
||||
@ -387,11 +385,15 @@ class BaseMySqlAdmin(object):
|
||||
def grant_access(self, username, hostname, databases):
|
||||
"""Grant a user permission to use a given database."""
|
||||
user = self._get_user(username, hostname)
|
||||
mydb = models.ValidatedMySQLDatabase()
|
||||
mydb = None # cache the model as we just want name validation
|
||||
with self.local_sql_client(self.mysql_app.get_engine()) as client:
|
||||
for database in databases:
|
||||
try:
|
||||
mydb.name = database
|
||||
if mydb:
|
||||
mydb.name = database
|
||||
else:
|
||||
mydb = models.MySQLSchema(name=database)
|
||||
mydb.check_reserved()
|
||||
except ValueError:
|
||||
LOG.exception(_("Error granting access"))
|
||||
raise exception.BadRequest(_(
|
||||
@ -455,11 +457,10 @@ class BaseMySqlAdmin(object):
|
||||
if count >= limit:
|
||||
break
|
||||
LOG.debug("database = %s." % str(database))
|
||||
mysql_db = models.MySQLDatabase()
|
||||
mysql_db.name = database[0]
|
||||
mysql_db = models.MySQLSchema(name=database[0],
|
||||
character_set=database[1],
|
||||
collate=database[2])
|
||||
next_marker = mysql_db.name
|
||||
mysql_db.character_set = database[1]
|
||||
mysql_db.collate = database[2]
|
||||
databases.append(mysql_db.serialize())
|
||||
LOG.debug("databases = " + str(databases))
|
||||
if limit is not None and database_names.rowcount <= limit:
|
||||
@ -492,7 +493,6 @@ class BaseMySqlAdmin(object):
|
||||
"be omitted from the listing: %s" % ignored_user_names)
|
||||
users = []
|
||||
with self.local_sql_client(self.mysql_app.get_engine()) as client:
|
||||
mysql_user = models.MySQLUser()
|
||||
iq = sql_query.Query() # Inner query.
|
||||
iq.columns = ['User', 'Host', "CONCAT(User, '@', Host) as Marker"]
|
||||
iq.tables = ['mysql.user']
|
||||
@ -520,9 +520,9 @@ class BaseMySqlAdmin(object):
|
||||
if count >= limit:
|
||||
break
|
||||
LOG.debug("user = " + str(row))
|
||||
mysql_user = models.MySQLUser()
|
||||
mysql_user.name = row['User']
|
||||
mysql_user.host = row['Host']
|
||||
mysql_user = models.MySQLUser(name=row['User'],
|
||||
host=row['Host'])
|
||||
mysql_user.check_reserved()
|
||||
self._associate_dbs(mysql_user)
|
||||
next_marker = row['Marker']
|
||||
users.append(mysql_user.serialize())
|
||||
@ -669,7 +669,7 @@ class BaseMySqlApp(object):
|
||||
"""Generate and set a random root password and forget about it."""
|
||||
localhost = "localhost"
|
||||
uu = sql_query.SetPassword(
|
||||
"root", host=localhost,
|
||||
models.MySQLUser.root_username, host=localhost,
|
||||
new_password=utils.generate_random_password())
|
||||
t = text(str(uu))
|
||||
client.execute(t)
|
||||
@ -1052,9 +1052,8 @@ class BaseMySqlRootAccess(object):
|
||||
"""Enable the root user global access and/or
|
||||
reset the root password.
|
||||
"""
|
||||
user = models.MySQLRootUser(root_password)
|
||||
user = models.MySQLUser.root(password=root_password)
|
||||
with self.local_sql_client(self.mysql_app.get_engine()) as client:
|
||||
print(client)
|
||||
try:
|
||||
cu = sql_query.CreateUser(user.name, host=user.host)
|
||||
t = text(str(cu))
|
||||
@ -1064,7 +1063,6 @@ class BaseMySqlRootAccess(object):
|
||||
# TODO(rnirmal): More fine grained error checking later on
|
||||
LOG.debug(err)
|
||||
with self.local_sql_client(self.mysql_app.get_engine()) as client:
|
||||
print(client)
|
||||
uu = sql_query.SetPassword(user.name, host=user.host,
|
||||
new_password=user.password)
|
||||
t = text(str(uu))
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -14,13 +14,13 @@
|
||||
# under the License.
|
||||
|
||||
from oslo_log import log as logging
|
||||
from trove.common.db import models
|
||||
from trove.common import exception
|
||||
from trove.common.i18n import _
|
||||
from trove.common import utils
|
||||
from trove.guestagent.common import operating_system
|
||||
from trove.guestagent.datastore.experimental.db2 import service
|
||||
from trove.guestagent.datastore.experimental.db2 import system
|
||||
from trove.guestagent.db import models
|
||||
from trove.guestagent.strategies.backup import base
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -39,7 +39,7 @@ class DB2Backup(base.BackupRunner):
|
||||
dbNames = []
|
||||
databases, marker = self.admin.list_databases()
|
||||
for database in databases:
|
||||
mydb = models.MySQLDatabase()
|
||||
mydb = models.DatastoreSchema()
|
||||
mydb.deserialize(database)
|
||||
dbNames.append(mydb.name)
|
||||
return dbNames
|
||||
|
@ -19,6 +19,7 @@ import os
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import netutils
|
||||
from trove.common import cfg
|
||||
from trove.common.db.postgresql import models
|
||||
from trove.common import exception
|
||||
from trove.common.i18n import _
|
||||
from trove.common import stream_codecs
|
||||
@ -26,7 +27,6 @@ from trove.common import utils
|
||||
from trove.guestagent.backup.backupagent import BackupAgent
|
||||
from trove.guestagent.common import operating_system
|
||||
from trove.guestagent.common.operating_system import FileMode
|
||||
from trove.guestagent.db import models
|
||||
from trove.guestagent.strategies import backup
|
||||
from trove.guestagent.strategies.replication import base
|
||||
|
||||
|
@ -21,11 +21,11 @@ from oslo_log import log as logging
|
||||
from oslo_utils import netutils
|
||||
|
||||
from trove.common import cfg
|
||||
from trove.common.db.mysql import models
|
||||
from trove.common.i18n import _
|
||||
from trove.common import utils
|
||||
from trove.guestagent.backup.backupagent import BackupAgent
|
||||
from trove.guestagent.datastore.mysql.service import MySqlAdmin
|
||||
from trove.guestagent.db import models
|
||||
from trove.guestagent.strategies import backup
|
||||
from trove.guestagent.strategies.replication import base
|
||||
|
||||
@ -65,14 +65,20 @@ class MysqlReplicationBase(base.Replication):
|
||||
replication_user = None
|
||||
replication_password = utils.generate_random_password(16)
|
||||
|
||||
mysql_user = models.MySQLUser()
|
||||
mysql_user.password = replication_password
|
||||
mysql_user = None # cache the model as we just want name validation
|
||||
|
||||
retry_count = 0
|
||||
|
||||
while replication_user is None:
|
||||
try:
|
||||
mysql_user.name = 'slave_' + str(uuid.uuid4())[:8]
|
||||
name = 'slave_' + str(uuid.uuid4())[:8]
|
||||
if mysql_user:
|
||||
mysql_user.name = name
|
||||
else:
|
||||
mysql_user = models.MySQLUser(
|
||||
name=name, password=replication_password
|
||||
)
|
||||
mysql_user.check_create()
|
||||
MySqlAdmin().create_user([mysql_user.serialize()])
|
||||
LOG.debug("Trying to create replication user " +
|
||||
mysql_user.name)
|
||||
|
342
trove/tests/unittests/common/test_dbmodels.py
Normal file
342
trove/tests/unittests/common/test_dbmodels.py
Normal file
@ -0,0 +1,342 @@
|
||||
# Copyright 2016 Tesora, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
|
||||
from trove.common.db import models
|
||||
from trove.tests.unittests import trove_testtools
|
||||
|
||||
|
||||
class DatastoreSchemaTest(trove_testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(DatastoreSchemaTest, self).setUp()
|
||||
self.dbname = 'testdb'
|
||||
self.serial_db = {'_name': self.dbname,
|
||||
'_character_set': None,
|
||||
'_collate': None}
|
||||
|
||||
def tearDown(self):
|
||||
super(DatastoreSchemaTest, self).tearDown()
|
||||
|
||||
def _empty_schema(self):
|
||||
return models.DatastoreSchema(deserializing=True)
|
||||
|
||||
def test_init_name(self):
|
||||
database = models.DatastoreSchema(self.dbname)
|
||||
self.assertEqual(self.dbname, database.name)
|
||||
database2 = models.DatastoreSchema(name=self.dbname)
|
||||
self.assertEqual(self.dbname, database2.name)
|
||||
|
||||
def test_init_no_name(self):
|
||||
self.assertRaises(RuntimeError, models.DatastoreSchema)
|
||||
|
||||
@mock.patch.object(models.DatastoreSchema, 'verify_dict')
|
||||
def test_init_deserializing(self, mock_verify):
|
||||
database = models.DatastoreSchema.deserialize(self.serial_db)
|
||||
mock_verify.assert_any_call()
|
||||
self.assertEqual(self.dbname, database.name)
|
||||
|
||||
def test_serialize(self):
|
||||
database = models.DatastoreSchema(self.dbname)
|
||||
self.assertEqual(self.serial_db, database.serialize())
|
||||
|
||||
def test_name_property(self):
|
||||
test_name = "Anna"
|
||||
database = self._empty_schema()
|
||||
database.name = test_name
|
||||
self.assertEqual(test_name, database.name)
|
||||
|
||||
def _do_validate_bad_schema_name(self, name):
|
||||
database = self._empty_schema()
|
||||
self.assertRaises(ValueError, database._validate_schema_name, name)
|
||||
|
||||
def test_validate_name_empty(self):
|
||||
self._do_validate_bad_schema_name(None)
|
||||
|
||||
@mock.patch.object(models.DatastoreSchema, '_max_schema_name_length',
|
||||
new_callable=mock.PropertyMock)
|
||||
def test_validate_name_long(self, mock_max_len):
|
||||
mock_max_len.return_value = 5
|
||||
self._do_validate_bad_schema_name('toolong')
|
||||
|
||||
@mock.patch.object(models.DatastoreSchema, '_is_valid_schema_name')
|
||||
def test_validate_name_invalid(self, mock_is_valid):
|
||||
mock_is_valid.return_value = False
|
||||
self._do_validate_bad_schema_name('notvalid')
|
||||
|
||||
def test_verify_dict(self):
|
||||
database = models.DatastoreSchema(self.dbname)
|
||||
# using context patch because the property setter needs to work
|
||||
# properly during init for this test
|
||||
with mock.patch.object(
|
||||
models.DatastoreSchema, 'name',
|
||||
new_callable=mock.PropertyMock) as mock_name_property:
|
||||
database.verify_dict()
|
||||
mock_name_property.assert_called_with(self.dbname)
|
||||
|
||||
def test_checks_pass(self):
|
||||
database = models.DatastoreSchema(self.dbname)
|
||||
database.check_reserved()
|
||||
database.check_create()
|
||||
database.check_delete()
|
||||
|
||||
@mock.patch.object(models.DatastoreSchema, 'ignored_dbs',
|
||||
new_callable=mock.PropertyMock)
|
||||
def test_checks_fail(self, mock_ignored_dbs):
|
||||
mock_ignored_dbs.return_value = [self.dbname]
|
||||
database = models.DatastoreSchema(self.dbname)
|
||||
self.assertRaises(ValueError, database.check_reserved)
|
||||
self.assertRaises(ValueError, database.check_create)
|
||||
self.assertRaises(ValueError, database.check_delete)
|
||||
|
||||
|
||||
class DatastoreUserTest(trove_testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(DatastoreUserTest, self).setUp()
|
||||
self.username = 'testuser'
|
||||
self.password = 'password'
|
||||
self.host = '192.168.0.1'
|
||||
self.dbname = 'testdb'
|
||||
self.serial_db = {'_name': self.dbname,
|
||||
'_character_set': None,
|
||||
'_collate': None}
|
||||
self.databases = [self.serial_db]
|
||||
self.host_wildcard = '%'
|
||||
self.serial_user_basic = {
|
||||
'_name': self.username, '_password': None,
|
||||
'_host': self.host_wildcard, '_databases': [],
|
||||
'_is_root': False
|
||||
}
|
||||
self.serial_user_full = {
|
||||
'_name': self.username, '_password': self.password,
|
||||
'_host': self.host, '_databases': self.databases,
|
||||
'_is_root': False
|
||||
}
|
||||
|
||||
def tearDown(self):
|
||||
super(DatastoreUserTest, self).tearDown()
|
||||
|
||||
def _empty_user(self):
|
||||
return models.DatastoreUser(deserializing=True)
|
||||
|
||||
def _test_user_basic(self, user):
|
||||
self.assertEqual(self.username, user.name)
|
||||
self.assertEqual(None, user.password)
|
||||
self.assertEqual(self.host_wildcard, user.host)
|
||||
self.assertEqual([], user.databases)
|
||||
|
||||
def _test_user_full(self, user):
|
||||
self.assertEqual(self.username, user.name)
|
||||
self.assertEqual(self.password, user.password)
|
||||
self.assertEqual(self.host, user.host)
|
||||
self.assertEqual(self.databases, user.databases)
|
||||
|
||||
def test_init_name(self):
|
||||
user1 = models.DatastoreUser(self.username)
|
||||
self._test_user_basic(user1)
|
||||
user2 = models.DatastoreUser(name=self.username)
|
||||
self._test_user_basic(user2)
|
||||
|
||||
def test_init_no_name(self):
|
||||
self.assertRaises(ValueError, models.DatastoreUser)
|
||||
|
||||
def test_init_options(self):
|
||||
user1 = models.DatastoreUser(self.username)
|
||||
self._test_user_basic(user1)
|
||||
user2 = models.DatastoreUser(self.username, self.password,
|
||||
self.host, self.dbname)
|
||||
self._test_user_full(user2)
|
||||
user3 = models.DatastoreUser(name=self.username,
|
||||
password=self.password,
|
||||
host=self.host,
|
||||
databases=self.dbname)
|
||||
self._test_user_full(user3)
|
||||
|
||||
@mock.patch.object(models.DatastoreUser, 'verify_dict')
|
||||
def test_init_deserializing(self, mock_verify):
|
||||
user1 = models.DatastoreUser.deserialize(self.serial_user_basic)
|
||||
self._test_user_basic(user1)
|
||||
user2 = models.DatastoreUser.deserialize(self.serial_user_full)
|
||||
self._test_user_full(user2)
|
||||
self.assertEqual(2, mock_verify.call_count)
|
||||
|
||||
def test_serialize(self):
|
||||
user1 = models.DatastoreUser(self.username)
|
||||
self.assertEqual(self.serial_user_basic, user1.serialize())
|
||||
user2 = models.DatastoreUser(self.username, self.password,
|
||||
self.host, self.dbname)
|
||||
self.assertEqual(self.serial_user_full, user2.serialize())
|
||||
|
||||
@mock.patch.object(models.DatastoreUser, '_validate_user_name')
|
||||
def test_name_property(self, mock_validate):
|
||||
test_name = "Anna"
|
||||
user = self._empty_user()
|
||||
user.name = test_name
|
||||
self.assertEqual(test_name, user.name)
|
||||
mock_validate.assert_called_with(test_name)
|
||||
|
||||
def _do_validate_bad_user_name(self, name):
|
||||
user = self._empty_user()
|
||||
self.assertRaises(ValueError, user._validate_user_name, name)
|
||||
|
||||
def test_validate_name_empty(self):
|
||||
self._do_validate_bad_user_name(None)
|
||||
|
||||
@mock.patch.object(models.DatastoreUser, '_max_user_name_length',
|
||||
new_callable=mock.PropertyMock)
|
||||
def test_validate_name_long(self, mock_max_len):
|
||||
mock_max_len.return_value = 5
|
||||
self._do_validate_bad_user_name('toolong')
|
||||
|
||||
@mock.patch.object(models.DatastoreUser, '_is_valid_user_name')
|
||||
def test_validate_name_invalid(self, mock_is_valid):
|
||||
mock_is_valid.return_value = False
|
||||
self._do_validate_bad_user_name('notvalid')
|
||||
|
||||
@mock.patch.object(models.DatastoreUser, '_is_valid_password')
|
||||
def test_password_property(self, mock_validate):
|
||||
test_password = "NewPassword"
|
||||
user = self._empty_user()
|
||||
user.password = test_password
|
||||
mock_validate.assert_called_with(test_password)
|
||||
self.assertEqual(test_password, user.password)
|
||||
|
||||
@mock.patch.object(models.DatastoreUser, '_is_valid_password')
|
||||
def test_password_property_error(self, mock_validate):
|
||||
mock_validate.return_value = False
|
||||
test_password = "NewPassword"
|
||||
user = self._empty_user()
|
||||
|
||||
def test():
|
||||
user.password = test_password
|
||||
|
||||
self.assertRaises(ValueError, test)
|
||||
|
||||
@mock.patch.object(models.DatastoreUser, '_is_valid_host_name')
|
||||
def test_host_property(self, mock_validate):
|
||||
test_host = "192.168.0.2"
|
||||
user = self._empty_user()
|
||||
user.host = test_host
|
||||
mock_validate.assert_called_with(test_host)
|
||||
self.assertEqual(test_host, user.host)
|
||||
|
||||
@mock.patch.object(models.DatastoreUser, '_is_valid_host_name')
|
||||
def test_host_property_error(self, mock_validate):
|
||||
mock_validate.return_value = False
|
||||
test_host = "192.168.0.2"
|
||||
user = self._empty_user()
|
||||
|
||||
def test():
|
||||
user.host = test_host
|
||||
|
||||
self.assertRaises(ValueError, test)
|
||||
|
||||
@mock.patch.object(models.DatastoreUser, '_add_database')
|
||||
def test_databases_property(self, mock_add_database):
|
||||
test_dbname1 = 'otherdb'
|
||||
test_dbname2 = 'lastdb'
|
||||
user = self._empty_user()
|
||||
|
||||
def test(value):
|
||||
user._databases.append({'_name': value,
|
||||
'_character_set': None,
|
||||
'_collate': None})
|
||||
|
||||
mock_add_database.side_effect = test
|
||||
user.databases = self.dbname
|
||||
user.databases = [test_dbname1, test_dbname2]
|
||||
mock_add_database.assert_any_call(self.dbname)
|
||||
mock_add_database.assert_any_call(test_dbname1)
|
||||
mock_add_database.assert_any_call(test_dbname2)
|
||||
self.assertIn(self.serial_db, user.databases)
|
||||
self.assertIn({'_name': test_dbname1,
|
||||
'_character_set': None,
|
||||
'_collate': None}, user.databases)
|
||||
self.assertIn({'_name': test_dbname2,
|
||||
'_character_set': None,
|
||||
'_collate': None}, user.databases)
|
||||
|
||||
def test_build_database_schema(self):
|
||||
user = self._empty_user()
|
||||
schema = user._build_database_schema(self.dbname)
|
||||
self.assertEqual(self.serial_db, schema.serialize())
|
||||
|
||||
def test_add_database(self):
|
||||
user = self._empty_user()
|
||||
user._add_database(self.dbname)
|
||||
self.assertEqual([self.serial_db], user.databases)
|
||||
# check that adding an exsting db does nothing
|
||||
user._add_database(self.dbname)
|
||||
self.assertEqual([self.serial_db], user.databases)
|
||||
|
||||
@mock.patch.object(models, 'DatastoreSchema')
|
||||
def test_deserialize_schema(self, mock_ds_schema):
|
||||
mock_ds_schema.deserialize = mock.Mock()
|
||||
user = self._empty_user()
|
||||
user.deserialize_schema(self.serial_db)
|
||||
mock_ds_schema.deserialize.assert_called_with(self.serial_db)
|
||||
|
||||
@mock.patch.object(models.DatastoreUser, 'deserialize_schema')
|
||||
@mock.patch.object(models.DatastoreUser, 'host',
|
||||
new_callable=mock.PropertyMock)
|
||||
@mock.patch.object(models.DatastoreUser, 'password',
|
||||
new_callable=mock.PropertyMock)
|
||||
@mock.patch.object(models.DatastoreUser, 'name',
|
||||
new_callable=mock.PropertyMock)
|
||||
def _test_verify_dict_with_mocks(self, user,
|
||||
mock_name_property,
|
||||
mock_password_property,
|
||||
mock_host_property,
|
||||
mock_deserialize_schema):
|
||||
user.verify_dict()
|
||||
mock_name_property.assert_called_with(self.username)
|
||||
mock_password_property.assert_called_with(self.password)
|
||||
mock_host_property.assert_called_with(self.host)
|
||||
mock_deserialize_schema.assert_called_with(self.serial_db)
|
||||
|
||||
def test_verify_dict(self):
|
||||
user = models.DatastoreUser(self.username, self.password,
|
||||
self.host, self.dbname)
|
||||
self._test_verify_dict_with_mocks(user)
|
||||
|
||||
def test_validate_dict_defaults(self):
|
||||
user = models.DatastoreUser(self.username)
|
||||
user.verify_dict()
|
||||
self.assertEqual(None, user.password)
|
||||
self.assertEqual(self.host_wildcard, user.host)
|
||||
self.assertEqual([], user.databases)
|
||||
|
||||
def test_is_root(self):
|
||||
user = models.DatastoreUser(self.username)
|
||||
self.assertFalse(user._is_root)
|
||||
user.make_root()
|
||||
self.assertTrue(user._is_root)
|
||||
|
||||
def test_checks_pass(self):
|
||||
user = models.DatastoreUser(self.username)
|
||||
user.check_reserved()
|
||||
user.check_create()
|
||||
user.check_delete()
|
||||
|
||||
@mock.patch.object(models.DatastoreUser, 'ignored_users',
|
||||
new_callable=mock.PropertyMock)
|
||||
def test_checks_fail(self, mock_ignored_users):
|
||||
mock_ignored_users.return_value = [self.username]
|
||||
user = models.DatastoreUser(self.username)
|
||||
self.assertRaises(ValueError, user.check_reserved)
|
||||
self.assertRaises(ValueError, user.check_create)
|
||||
self.assertRaises(ValueError, user.check_delete)
|
@ -26,6 +26,7 @@ from mock import patch
|
||||
from oslo_utils import netutils
|
||||
from testtools import ExpectedException
|
||||
|
||||
from trove.common.db.cassandra import models
|
||||
from trove.common import exception
|
||||
from trove.common.instance import ServiceStatuses
|
||||
from trove.guestagent import backup
|
||||
@ -35,7 +36,6 @@ from trove.guestagent.datastore.experimental.cassandra import (
|
||||
manager as cass_manager)
|
||||
from trove.guestagent.datastore.experimental.cassandra import (
|
||||
service as cass_service)
|
||||
from trove.guestagent.db import models
|
||||
from trove.guestagent import pkg as pkg
|
||||
from trove.guestagent import volume
|
||||
from trove.tests.unittests.guestagent.test_datastore_manager import \
|
||||
@ -52,7 +52,7 @@ class GuestAgentCassandraDBManagerTest(DatastoreManagerTest):
|
||||
__N_BU = '_build_user'
|
||||
__N_RU = '_rename_user'
|
||||
__N_AUP = '_alter_user_password'
|
||||
__N_CAU = 'trove.guestagent.db.models.CassandraUser'
|
||||
__N_CAU = 'trove.common.db.cassandra.models.CassandraUser'
|
||||
__N_CU = '_create_user'
|
||||
__N_GFA = '_grant_full_access_on_keyspace'
|
||||
__N_DU = '_drop_user'
|
||||
|
@ -32,6 +32,7 @@ import sqlalchemy
|
||||
|
||||
from trove.common import cfg
|
||||
from trove.common import context as trove_context
|
||||
from trove.common.db.mysql import models as mysql_models
|
||||
from trove.common.exception import BadRequest
|
||||
from trove.common.exception import GuestError
|
||||
from trove.common.exception import PollTimeOut
|
||||
@ -77,7 +78,6 @@ from trove.guestagent.datastore.mysql.service import MySqlRootAccess
|
||||
import trove.guestagent.datastore.mysql_common.service as mysql_common_service
|
||||
import trove.guestagent.datastore.service as base_datastore_service
|
||||
from trove.guestagent.datastore.service import BaseDbStatus
|
||||
from trove.guestagent.db import models
|
||||
from trove.guestagent import dbaas as dbaas_sr
|
||||
from trove.guestagent.dbaas import get_filesystem_volume_stats
|
||||
from trove.guestagent import pkg
|
||||
@ -414,7 +414,7 @@ class MySqlAdminTest(trove_testtools.TestCase):
|
||||
local_client_patcher.start()
|
||||
|
||||
self.orig_MySQLUser_is_valid_user_name = (
|
||||
models.MySQLUser._is_valid_user_name)
|
||||
mysql_models.MySQLUser._is_valid_user_name)
|
||||
dbaas.get_engine = MagicMock(name='get_engine')
|
||||
|
||||
# trove.guestagent.common.configuration import ConfigurationManager
|
||||
@ -430,7 +430,7 @@ class MySqlAdminTest(trove_testtools.TestCase):
|
||||
|
||||
def tearDown(self):
|
||||
dbaas.get_engine = self.orig_get_engine
|
||||
models.MySQLUser._is_valid_user_name = (
|
||||
mysql_models.MySQLUser._is_valid_user_name = (
|
||||
self.orig_MySQLUser_is_valid_user_name)
|
||||
dbaas.MySqlApp.configuration_manager = \
|
||||
dbaas.orig_configuration_manager
|
||||
@ -1515,7 +1515,6 @@ class TextClauseMatcher(object):
|
||||
return "TextClause(%s)" % self.text
|
||||
|
||||
def __eq__(self, arg):
|
||||
print("Matching %s" % arg.text)
|
||||
return self.text in arg.text
|
||||
|
||||
|
||||
@ -3398,7 +3397,8 @@ class DB2AdminTest(trove_testtools.TestCase):
|
||||
|
||||
def test_delete_users_without_db(self):
|
||||
FAKE_USER.append(
|
||||
{"_name": "random2", "_password": "guesswhat", "_databases": []})
|
||||
{"_name": "random2", "_password": "guesswhat", "_host": '%',
|
||||
"_databases": []})
|
||||
with patch.object(db2service, 'run_command',
|
||||
MagicMock(return_value=None)):
|
||||
with patch.object(db2service.DB2Admin, 'list_access',
|
||||
@ -3418,6 +3418,7 @@ class DB2AdminTest(trove_testtools.TestCase):
|
||||
expected, args[0],
|
||||
"Revoke database access queries are not the same")
|
||||
self.assertEqual(1, db2service.run_command.call_count)
|
||||
FAKE_USER.pop()
|
||||
|
||||
def test_list_users(self):
|
||||
databases = []
|
||||
|
@ -1,112 +0,0 @@
|
||||
# Copyright 2012 OpenStack Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from mock import MagicMock
|
||||
|
||||
from trove.guestagent.db import models as dbmodels
|
||||
from trove.tests.unittests import trove_testtools
|
||||
|
||||
|
||||
class MySQLDatabaseTest(trove_testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(MySQLDatabaseTest, self).setUp()
|
||||
|
||||
self.mysqlDb = dbmodels.ValidatedMySQLDatabase()
|
||||
self.origin_ignore_db = self.mysqlDb._ignore_dbs
|
||||
self.mysqlDb._ignore_dbs = ['mysql']
|
||||
|
||||
def tearDown(self):
|
||||
super(MySQLDatabaseTest, self).tearDown()
|
||||
self.mysqlDb._ignore_dbs = self.origin_ignore_db
|
||||
|
||||
def test_name(self):
|
||||
self.assertIsNone(self.mysqlDb.name)
|
||||
|
||||
def test_name_setter(self):
|
||||
test_name = "Anna"
|
||||
self.mysqlDb.name = test_name
|
||||
self.assertEqual(test_name, self.mysqlDb.name)
|
||||
|
||||
def test_is_valid_positive(self):
|
||||
self.assertTrue(self.mysqlDb._is_valid('pymysql'))
|
||||
|
||||
def test_is_valid_negative(self):
|
||||
self.assertFalse(self.mysqlDb._is_valid('mysql'))
|
||||
|
||||
|
||||
class MySQLUserTest(trove_testtools.TestCase):
|
||||
def setUp(self):
|
||||
super(MySQLUserTest, self).setUp()
|
||||
self.mysqlUser = dbmodels.MySQLUser()
|
||||
|
||||
def tearDown(self):
|
||||
super(MySQLUserTest, self).tearDown()
|
||||
|
||||
def test_is_valid_negative(self):
|
||||
self.assertFalse(self.mysqlUser._is_valid(None))
|
||||
self.assertFalse(self.mysqlUser._is_valid("|;"))
|
||||
self.assertFalse(self.mysqlUser._is_valid("\\"))
|
||||
|
||||
def test_is_valid_positive(self):
|
||||
self.assertTrue(self.mysqlUser._is_valid("real_name"))
|
||||
|
||||
|
||||
class IsValidUsernameTest(trove_testtools.TestCase):
|
||||
def setUp(self):
|
||||
super(IsValidUsernameTest, self).setUp()
|
||||
self.mysqlUser = dbmodels.MySQLUser()
|
||||
self.origin_is_valid = self.mysqlUser._is_valid
|
||||
self.origin_ignore_users = self.mysqlUser._ignore_users
|
||||
self.mysqlUser._ignore_users = ["king"]
|
||||
|
||||
def tearDown(self):
|
||||
super(IsValidUsernameTest, self).tearDown()
|
||||
self.mysqlUser._is_valid = self.origin_is_valid
|
||||
self.mysqlUser._ignore_users = self.origin_ignore_users
|
||||
|
||||
def test_is_valid_user_name(self):
|
||||
value = "trove"
|
||||
self.assertTrue(self.mysqlUser._is_valid_user_name(value))
|
||||
|
||||
def test_is_valid_user_name_negative(self):
|
||||
self.mysqlUser._is_valid = MagicMock(return_value=False)
|
||||
self.assertFalse(self.mysqlUser._is_valid_user_name("trove"))
|
||||
|
||||
self.mysqlUser._is_valid = MagicMock(return_value=True)
|
||||
self.assertFalse(self.mysqlUser._is_valid_user_name("king"))
|
||||
|
||||
|
||||
class IsValidHostnameTest(trove_testtools.TestCase):
|
||||
def setUp(self):
|
||||
super(IsValidHostnameTest, self).setUp()
|
||||
self.mysqlUser = dbmodels.MySQLUser()
|
||||
|
||||
def tearDown(self):
|
||||
super(IsValidHostnameTest, self).tearDown()
|
||||
|
||||
def test_is_valid_octet(self):
|
||||
self.assertTrue(self.mysqlUser._is_valid_host_name('192.168.1.1'))
|
||||
|
||||
def test_is_valid_bad_octet(self):
|
||||
self.assertFalse(self.mysqlUser._is_valid_host_name('999.168.1.1'))
|
||||
|
||||
def test_is_valid_global_wildcard(self):
|
||||
self.assertTrue(self.mysqlUser._is_valid_host_name('%'))
|
||||
|
||||
def test_is_valid_prefix_wildcard(self):
|
||||
self.assertTrue(self.mysqlUser._is_valid_host_name('%.168.1.1'))
|
||||
|
||||
def test_is_valid_suffix_wildcard(self):
|
||||
self.assertTrue(self.mysqlUser._is_valid_host_name('192.168.1.%'))
|
@ -15,12 +15,12 @@
|
||||
import mock
|
||||
import pymongo
|
||||
|
||||
import trove.common.db.mongodb.models as models
|
||||
import trove.common.utils as utils
|
||||
import trove.guestagent.backup as backup
|
||||
from trove.guestagent.common.configuration import ImportOverrideStrategy
|
||||
import trove.guestagent.datastore.experimental.mongodb.manager as manager
|
||||
import trove.guestagent.datastore.experimental.mongodb.service as service
|
||||
import trove.guestagent.db.models as models
|
||||
import trove.guestagent.volume as volume
|
||||
from trove.tests.unittests.guestagent.test_datastore_manager import \
|
||||
DatastoreManagerTest
|
||||
@ -46,6 +46,17 @@ class GuestAgentMongoDBManagerTest(DatastoreManagerTest):
|
||||
self.pymongo_patch.start()
|
||||
|
||||
self.mount_point = '/var/lib/mongodb'
|
||||
self.host_wildcard = '%' # This is used in the test_*_user tests below
|
||||
self.serialized_user = {
|
||||
'_name': 'testdb.testuser', '_password': None,
|
||||
'_roles': [{'db': 'testdb', 'role': 'testrole'}],
|
||||
'_username': 'testuser', '_databases': [],
|
||||
'_host': self.host_wildcard,
|
||||
'_database': {'_name': 'testdb',
|
||||
'_character_set': None,
|
||||
'_collate': None},
|
||||
'_is_root': False
|
||||
}
|
||||
|
||||
def tearDown(self):
|
||||
super(GuestAgentMongoDBManagerTest, self).tearDown()
|
||||
@ -152,21 +163,12 @@ class GuestAgentMongoDBManagerTest(DatastoreManagerTest):
|
||||
|
||||
mocked_enable_root.assert_called_with('test_password')
|
||||
|
||||
# This is used in the test_*_user tests below
|
||||
_serialized_user = {'_name': 'testdb.testuser', '_password': None,
|
||||
'_roles': [{'db': 'testdb', 'role': 'testrole'}],
|
||||
'_username': 'testuser', '_databases': [],
|
||||
'_host': None,
|
||||
'_database': {'_name': 'testdb',
|
||||
'_character_set': None,
|
||||
'_collate': None}}
|
||||
|
||||
@mock.patch.object(service, 'MongoDBClient')
|
||||
@mock.patch.object(service.MongoDBAdmin, '_admin_user')
|
||||
@mock.patch.object(service.MongoDBAdmin, '_get_user_record')
|
||||
def test_create_user(self, mocked_get_user, mocked_admin_user,
|
||||
mocked_client):
|
||||
user = self._serialized_user.copy()
|
||||
user = self.serialized_user.copy()
|
||||
user['_password'] = 'testpassword'
|
||||
users = [user]
|
||||
|
||||
@ -184,7 +186,7 @@ class GuestAgentMongoDBManagerTest(DatastoreManagerTest):
|
||||
def test_delete_user(self, mocked_admin_user, mocked_client):
|
||||
client = mocked_client().__enter__()['testdb']
|
||||
|
||||
self.manager.delete_user(self.context, self._serialized_user)
|
||||
self.manager.delete_user(self.context, self.serialized_user)
|
||||
|
||||
client.remove_user.assert_called_with('testuser')
|
||||
|
||||
@ -202,20 +204,20 @@ class GuestAgentMongoDBManagerTest(DatastoreManagerTest):
|
||||
result = self.manager.get_user(self.context, 'testdb.testuser', None)
|
||||
|
||||
mocked_find.assert_called_with({'user': 'testuser', 'db': 'testdb'})
|
||||
self.assertEqual(self._serialized_user, result)
|
||||
self.assertEqual(self.serialized_user, result)
|
||||
|
||||
@mock.patch.object(service, 'MongoDBClient')
|
||||
@mock.patch.object(service.MongoDBAdmin, '_admin_user')
|
||||
def test_list_users(self, mocked_admin_user, mocked_client):
|
||||
# roles are NOT returned by list_users
|
||||
user1 = self._serialized_user.copy()
|
||||
user2 = self._serialized_user.copy()
|
||||
user1 = self.serialized_user.copy()
|
||||
user2 = self.serialized_user.copy()
|
||||
user2['_name'] = 'testdb.otheruser'
|
||||
user2['_username'] = 'otheruser'
|
||||
user2['_roles'] = [{'db': 'testdb2', 'role': 'readWrite'}]
|
||||
user2['_databases'] = [{'_name': 'testdb2',
|
||||
'_character_set': None,
|
||||
'_collate': None}]
|
||||
'_character_set': None,
|
||||
'_collate': None}]
|
||||
|
||||
mocked_find = mock.MagicMock(return_value=[
|
||||
{
|
||||
@ -256,7 +258,8 @@ class GuestAgentMongoDBManagerTest(DatastoreManagerTest):
|
||||
'_password': 'password',
|
||||
'_roles': [{'db': 'admin', 'role': 'root'}],
|
||||
'_databases': [],
|
||||
'_host': None}
|
||||
'_host': self.host_wildcard,
|
||||
'_is_root': True}
|
||||
|
||||
result = self.manager.enable_root(self.context)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user