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:
Matt Van Dijk 2015-10-14 14:47:11 -04:00
parent d6fabc8504
commit 1c819c3e37
37 changed files with 1749 additions and 1450 deletions

View File

View 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

View File

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

View File

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

View File

View 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"}

View 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

View File

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

@ -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.%'))

View File

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