Implementation of root-enable, root-disable in redis.
Implement root-enable, root-disable for redis to manage redis authentication. Change-Id: If88092c24c51192a19eeec8312701e2c6d709db9 Implements: blueprint root-enable-in-redis Signed-off-by: Fan Zhang <zh.f@outlook.com>
This commit is contained in:
parent
8aad3ee2e4
commit
a57bf8816b
@ -0,0 +1,4 @@
|
||||
---
|
||||
features:
|
||||
- OpenStack Trove now supports enable or disable authentication for Redis
|
||||
datastore via the root-enable and root-disable API's.
|
@ -854,7 +854,7 @@ redis_opts = [
|
||||
help='Class that implements datastore-specific Guest Agent API '
|
||||
'logic.'),
|
||||
cfg.StrOpt('root_controller',
|
||||
default='trove.extensions.common.service.DefaultRootController',
|
||||
default='trove.extensions.redis.service.RedisRootController',
|
||||
help='Root controller implementation for redis.'),
|
||||
cfg.StrOpt('guest_log_exposed_logs', default='',
|
||||
help='List of Guest Logs to expose for publishing.'),
|
||||
|
0
trove/common/db/redis/__init__.py
Normal file
0
trove/common/db/redis/__init__.py
Normal file
28
trove/common/db/redis/models.py
Normal file
28
trove/common/db/redis/models.py
Normal file
@ -0,0 +1,28 @@
|
||||
# Copyright 2017 Eayun, 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 RedisRootUser(models.DatastoreModelsBase):
|
||||
|
||||
def verify_dict(self):
|
||||
pass
|
||||
|
||||
def __init__(self, password=None):
|
||||
self._name = '-'
|
||||
self._password = password
|
||||
super(RedisRootUser, self).__init__()
|
@ -671,3 +671,9 @@ class DatastoreVersionAlreadyExists(BadRequest):
|
||||
class LogAccessForbidden(Forbidden):
|
||||
|
||||
message = _("You must be admin to %(action)s log '%(log)s'.")
|
||||
|
||||
|
||||
class SlaveOperationNotSupported(TroveError):
|
||||
|
||||
message = _("The '%(operation)s' operation is not supported for slaves in "
|
||||
"replication.")
|
||||
|
0
trove/extensions/redis/__init__.py
Normal file
0
trove/extensions/redis/__init__.py
Normal file
28
trove/extensions/redis/models.py
Normal file
28
trove/extensions/redis/models.py
Normal file
@ -0,0 +1,28 @@
|
||||
# Copyright 2017 Eayun, 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.remote import create_guest_client
|
||||
from trove.extensions.common.models import load_and_verify
|
||||
from trove.extensions.common.models import Root
|
||||
|
||||
|
||||
class RedisRoot(Root):
|
||||
@classmethod
|
||||
def get_auth_password(cls, context, instance_id):
|
||||
load_and_verify(context, instance_id)
|
||||
password = create_guest_client(context,
|
||||
instance_id).get_root_password()
|
||||
return password
|
185
trove/extensions/redis/service.py
Normal file
185
trove/extensions/redis/service.py
Normal file
@ -0,0 +1,185 @@
|
||||
# Copyright 2017 Eayun, 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 oslo_log import log as logging
|
||||
from trove.common import cfg
|
||||
from trove.common import exception
|
||||
from trove.common.i18n import _
|
||||
from trove.common.i18n import _LE
|
||||
from trove.common.i18n import _LI
|
||||
from trove.common import wsgi
|
||||
from trove.extensions.common.service import DefaultRootController
|
||||
from trove.extensions.redis.models import RedisRoot
|
||||
from trove.extensions.redis.views import RedisRootCreatedView
|
||||
from trove.instance.models import DBInstance
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
MANAGER = CONF.datastore_manager if CONF.datastore_manager else 'redis'
|
||||
|
||||
|
||||
class RedisRootController(DefaultRootController):
|
||||
def root_create(self, req, body, tenant_id, instance_id, is_cluster):
|
||||
"""Enable authentication for a redis instance and its replicas if any
|
||||
"""
|
||||
self._validate_can_perform_action(tenant_id, instance_id, is_cluster,
|
||||
"enable_root")
|
||||
password = DefaultRootController._get_password_from_body(body)
|
||||
slave_instances = self._get_slaves(tenant_id, instance_id)
|
||||
return self._instance_root_create(req, instance_id, password,
|
||||
slave_instances)
|
||||
|
||||
def root_delete(self, req, tenant_id, instance_id, is_cluster):
|
||||
"""Disable authentication for a redis instance and its replicas if any
|
||||
"""
|
||||
self._validate_can_perform_action(tenant_id, instance_id, is_cluster,
|
||||
"disable_root")
|
||||
slave_instances = self._get_slaves(tenant_id, instance_id)
|
||||
return self._instance_root_delete(req, instance_id, slave_instances)
|
||||
|
||||
def _instance_root_create(self, req, instance_id, password,
|
||||
slave_instances=None):
|
||||
LOG.info(_LI("Enabling authentication for instance '%s'."),
|
||||
instance_id)
|
||||
LOG.info(_LI("req : '%s'\n\n"), req)
|
||||
context = req.environ[wsgi.CONTEXT_KEY]
|
||||
user_name = context.user
|
||||
|
||||
original_auth_password = self._get_original_auth_password(
|
||||
context, instance_id)
|
||||
|
||||
# Do root-enable and roll back once if operation fails.
|
||||
try:
|
||||
root = RedisRoot.create(context, instance_id,
|
||||
user_name, password)
|
||||
if not password:
|
||||
password = root.password
|
||||
except exception.TroveError:
|
||||
self._rollback_once(req, instance_id, original_auth_password)
|
||||
raise exception.TroveError(
|
||||
_LE("Failed to do root-enable for instance "
|
||||
"'%(instance_id)s'.") % {'instance_id': instance_id}
|
||||
)
|
||||
|
||||
failed_slaves = []
|
||||
for slave_id in slave_instances:
|
||||
try:
|
||||
LOG.info(_LI("Enabling authentication for slave instance "
|
||||
"'%s'."), slave_id)
|
||||
RedisRoot.create(context, slave_id, user_name, password)
|
||||
except exception.TroveError:
|
||||
failed_slaves.append(slave_id)
|
||||
|
||||
return wsgi.Result(
|
||||
RedisRootCreatedView(root, failed_slaves).data(), 200)
|
||||
|
||||
def _instance_root_delete(self, req, instance_id, slave_instances=None):
|
||||
LOG.info(_LI("Disabling authentication for instance '%s'."),
|
||||
instance_id)
|
||||
LOG.info(_LI("req : '%s'\n\n"), req)
|
||||
context = req.environ[wsgi.CONTEXT_KEY]
|
||||
|
||||
original_auth_password = self._get_original_auth_password(
|
||||
context, instance_id)
|
||||
|
||||
# Do root-disable and roll back once if operation fails.
|
||||
try:
|
||||
RedisRoot.delete(context, instance_id)
|
||||
except exception.TroveError:
|
||||
self._rollback_once(req, instance_id, original_auth_password)
|
||||
raise exception.TroveError(
|
||||
_LE("Failed to do root-disable for instance "
|
||||
"'%(instance_id)s'.") % {'instance_id': instance_id}
|
||||
)
|
||||
|
||||
failed_slaves = []
|
||||
for slave_id in slave_instances:
|
||||
try:
|
||||
LOG.info(_LI("Disabling authentication for slave instance "
|
||||
"'%s'."), slave_id)
|
||||
RedisRoot.delete(context, slave_id)
|
||||
except exception.TroveError:
|
||||
failed_slaves.append(slave_id)
|
||||
|
||||
if len(failed_slaves) > 0:
|
||||
result = {
|
||||
'failed_slaves': failed_slaves
|
||||
}
|
||||
return wsgi.Result(result, 200)
|
||||
|
||||
return wsgi.Result(None, 204)
|
||||
|
||||
@staticmethod
|
||||
def _rollback_once(req, instance_id, original_auth_password):
|
||||
LOG.info(_LI("Rolling back enable/disable authentication "
|
||||
"for instance '%s'."), instance_id)
|
||||
context = req.environ[wsgi.CONTEXT_KEY]
|
||||
user_name = context.user
|
||||
try:
|
||||
if not original_auth_password:
|
||||
# Instance never did root-enable before.
|
||||
RedisRoot.delete(context, instance_id)
|
||||
else:
|
||||
# Instance has done root-enable successfully before.
|
||||
# So roll back with original password.
|
||||
RedisRoot.create(context, instance_id, user_name,
|
||||
original_auth_password)
|
||||
except exception.TroveError:
|
||||
LOG.exception(_("Rolling back failed for instance '%s'"),
|
||||
instance_id)
|
||||
|
||||
@staticmethod
|
||||
def _is_slave(tenant_id, instance_id):
|
||||
args = {'id': instance_id, 'tenant_id': tenant_id}
|
||||
instance_info = DBInstance.find_by(**args)
|
||||
return instance_info.slave_of_id
|
||||
|
||||
@staticmethod
|
||||
def _get_slaves(tenant_id, instance_or_cluster_id, deleted=False):
|
||||
LOG.info(_LI("Getting non-deleted slaves of instance '%s', "
|
||||
"if any."), instance_or_cluster_id)
|
||||
args = {'slave_of_id': instance_or_cluster_id, 'tenant_id': tenant_id,
|
||||
'deleted': deleted}
|
||||
db_infos = DBInstance.find_all(**args)
|
||||
slaves = []
|
||||
for db_info in db_infos:
|
||||
slaves.append(db_info.id)
|
||||
return slaves
|
||||
|
||||
@staticmethod
|
||||
def _get_original_auth_password(context, instance_id):
|
||||
# Check if instance did root-enable before and get original password.
|
||||
password = None
|
||||
if RedisRoot.load(context, instance_id):
|
||||
try:
|
||||
password = RedisRoot.get_auth_password(context, instance_id)
|
||||
except exception.TroveError:
|
||||
raise exception.TroveError(
|
||||
_LE("Failed to get original auth password of instance "
|
||||
"'%(instance_id)s'.") % {'instance_id': instance_id}
|
||||
)
|
||||
return password
|
||||
|
||||
def _validate_can_perform_action(self, tenant_id, instance_id, is_cluster,
|
||||
operation):
|
||||
if is_cluster:
|
||||
raise exception.ClusterOperationNotSupported(
|
||||
operation=operation)
|
||||
|
||||
is_slave = self._is_slave(tenant_id, instance_id)
|
||||
if is_slave:
|
||||
raise exception.SlaveOperationNotSupported(
|
||||
operation=operation)
|
30
trove/extensions/redis/views.py
Normal file
30
trove/extensions/redis/views.py
Normal file
@ -0,0 +1,30 @@
|
||||
# Copyright 2017 Eayun, 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.extensions.common.views import UserView
|
||||
|
||||
|
||||
class RedisRootCreatedView(UserView):
|
||||
def __init__(self, user, failed_slaves):
|
||||
self.failed_slaves = failed_slaves
|
||||
super(RedisRootCreatedView, self).__init__(user)
|
||||
|
||||
def data(self):
|
||||
user_dict = {
|
||||
"name": self.user.name,
|
||||
"password": self.user.password
|
||||
}
|
||||
return {"user": user_dict, "failed_slaves": self.failed_slaves}
|
@ -237,6 +237,15 @@ class API(object):
|
||||
|
||||
self._cast("delete_database", version=version, database=database)
|
||||
|
||||
def get_root_password(self):
|
||||
"""Make a synchronous call to get root password of instance.
|
||||
"""
|
||||
LOG.debug("Get root password of instance %s.", self.id)
|
||||
version = self.API_BASE_VERSION
|
||||
|
||||
return self._call("get_root_password", self.agent_high_timeout,
|
||||
version=version)
|
||||
|
||||
def enable_root(self):
|
||||
"""Make a synchronous call to enable the root user for
|
||||
access from anywhere
|
||||
|
@ -267,3 +267,19 @@ class Manager(manager.Manager):
|
||||
LOG.debug("Executing cluster_addslots to assign hash slots %s-%s.",
|
||||
first_slot, last_slot)
|
||||
self._app.cluster_addslots(first_slot, last_slot)
|
||||
|
||||
def enable_root(self, context):
|
||||
LOG.debug("Enabling authentication.")
|
||||
return self._app.enable_root()
|
||||
|
||||
def enable_root_with_password(self, context, root_password=None):
|
||||
LOG.debug("Enabling authentication with password.")
|
||||
return self._app.enable_root(root_password)
|
||||
|
||||
def disable_root(self, context):
|
||||
LOG.debug("Disabling authentication.")
|
||||
return self._app.disable_root()
|
||||
|
||||
def get_root_password(self, context):
|
||||
LOG.debug("Getting auth password.")
|
||||
return self._app.get_auth_password()
|
||||
|
@ -20,6 +20,7 @@ from redis.exceptions import BusyLoadingError, ConnectionError
|
||||
from oslo_log import log as logging
|
||||
|
||||
from trove.common import cfg
|
||||
from trove.common.db.redis.models import RedisRootUser
|
||||
from trove.common import exception
|
||||
from trove.common.i18n import _
|
||||
from trove.common import instance as rd_instance
|
||||
@ -37,6 +38,7 @@ LOG = logging.getLogger(__name__)
|
||||
TIME_OUT = 1200
|
||||
CONF = cfg.CONF
|
||||
CLUSTER_CFG = 'clustering'
|
||||
SYS_OVERRIDES_AUTH = 'auth_password'
|
||||
packager = pkg.Package()
|
||||
|
||||
|
||||
@ -401,6 +403,31 @@ class RedisApp(object):
|
||||
LOG.exception(_('Error removing node from cluster.'))
|
||||
raise
|
||||
|
||||
def enable_root(self, password=None):
|
||||
if not password:
|
||||
password = utils.generate_random_password()
|
||||
redis_password = RedisRootUser(password=password)
|
||||
try:
|
||||
self.configuration_manager.apply_system_override(
|
||||
{'requirepass': password, 'masterauth': password},
|
||||
change_id=SYS_OVERRIDES_AUTH)
|
||||
self.apply_overrides(
|
||||
self.admin, {'requirepass': password, 'masterauth': password})
|
||||
except exception.TroveError:
|
||||
LOG.exception(_('Error enabling authentication for instance.'))
|
||||
raise
|
||||
return redis_password.serialize()
|
||||
|
||||
def disable_root(self):
|
||||
try:
|
||||
self.configuration_manager.remove_system_override(
|
||||
change_id=SYS_OVERRIDES_AUTH)
|
||||
self.apply_overrides(self.admin,
|
||||
{'requirepass': '', 'masterauth': ''})
|
||||
except exception.TroveError:
|
||||
LOG.exception(_('Error disabling authentication for instance.'))
|
||||
raise
|
||||
|
||||
|
||||
class RedisAdmin(object):
|
||||
"""Handles administrative tasks on the Redis database.
|
||||
|
@ -730,6 +730,11 @@ class Manager(periodic_task.PeriodicTasks):
|
||||
raise exception.DatastoreOperationNotSupported(
|
||||
operation='change_passwords', datastore=self.manager)
|
||||
|
||||
def get_root_password(self, context):
|
||||
LOG.debug("Getting root password.")
|
||||
raise exception.DatastoreOperationNotSupported(
|
||||
operation='get_root_password', datastore=self.manager)
|
||||
|
||||
def enable_root(self, context):
|
||||
LOG.debug("Enabling root.")
|
||||
raise exception.DatastoreOperationNotSupported(
|
||||
|
@ -404,7 +404,8 @@ register(
|
||||
["redis_supported"],
|
||||
single=[common_groups,
|
||||
backup_groups,
|
||||
configuration_groups, ],
|
||||
configuration_groups,
|
||||
root_actions_groups, ],
|
||||
multi=[replication_promote_groups, ]
|
||||
# multi=[cluster_actions_groups,
|
||||
# cluster_negative_actions_groups,
|
||||
|
@ -14,6 +14,7 @@
|
||||
|
||||
from mock import DEFAULT, MagicMock, Mock, patch
|
||||
|
||||
from trove.common import utils as utils
|
||||
from trove.guestagent import backup
|
||||
from trove.guestagent.common import configuration
|
||||
from trove.guestagent.common.configuration import ImportOverrideStrategy
|
||||
@ -329,3 +330,26 @@ class RedisGuestAgentManagerTest(DatastoreManagerTest):
|
||||
self.manager._get_repl_info = MagicMock(return_value=repl_info)
|
||||
self.manager.wait_for_txn(self.context, expected_txn_id)
|
||||
self.manager._get_repl_info.assert_any_call()
|
||||
|
||||
@patch.object(configuration.ConfigurationManager, 'apply_system_override')
|
||||
@patch.object(redis_service.RedisApp, 'apply_overrides')
|
||||
@patch.object(utils, 'generate_random_password',
|
||||
return_value='password')
|
||||
def test_enable_root(self, *mock):
|
||||
root_user = {'_name': '-',
|
||||
'_password': 'password'}
|
||||
|
||||
result = self.manager.enable_root(self.context)
|
||||
self.assertEqual(root_user, result)
|
||||
|
||||
@patch.object(redis_service.RedisApp, 'disable_root')
|
||||
def test_disable_root(self, disable_root_mock):
|
||||
self.manager.disable_root(self.context)
|
||||
disable_root_mock.assert_any_call()
|
||||
|
||||
@patch.object(redis_service.RedisApp, 'get_auth_password',
|
||||
return_value="password")
|
||||
def test_get_root_password(self, get_auth_password_mock):
|
||||
result = self.manager.get_root_password(self.context)
|
||||
self.assertTrue(get_auth_password_mock.called)
|
||||
self.assertEqual('password', result)
|
||||
|
0
trove/tests/unittests/redis/__init__.py
Normal file
0
trove/tests/unittests/redis/__init__.py
Normal file
205
trove/tests/unittests/redis/test_redis_root_controller.py
Normal file
205
trove/tests/unittests/redis/test_redis_root_controller.py
Normal file
@ -0,0 +1,205 @@
|
||||
# Copyright 2017 Eayun, 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 uuid
|
||||
|
||||
from mock import Mock, patch
|
||||
|
||||
from trove.common import exception
|
||||
from trove.datastore import models as datastore_models
|
||||
from trove.extensions.common import models
|
||||
from trove.extensions.redis.service import RedisRootController
|
||||
from trove.instance import models as instance_models
|
||||
from trove.instance.models import DBInstance
|
||||
from trove.instance.tasks import InstanceTasks
|
||||
from trove.taskmanager import api as task_api
|
||||
from trove.tests.unittests import trove_testtools
|
||||
from trove.tests.unittests.util import util
|
||||
|
||||
|
||||
class TestRedisRootController(trove_testtools.TestCase):
|
||||
|
||||
@patch.object(task_api.API, 'get_client', Mock(return_value=Mock()))
|
||||
def setUp(self):
|
||||
util.init_db()
|
||||
self.context = trove_testtools.TroveTestContext(self, is_admin=True)
|
||||
self.datastore = datastore_models.DBDatastore.create(
|
||||
id=str(uuid.uuid4()),
|
||||
name='redis' + str(uuid.uuid4()),
|
||||
)
|
||||
self.datastore_version = (
|
||||
datastore_models.DBDatastoreVersion.create(
|
||||
id=str(uuid.uuid4()),
|
||||
datastore_id=self.datastore.id,
|
||||
name="3.2" + str(uuid.uuid4()),
|
||||
manager="redis",
|
||||
image_id="image_id",
|
||||
packages="",
|
||||
active=True))
|
||||
self.tenant_id = "UUID"
|
||||
self.single_db_info = DBInstance.create(
|
||||
id="redis-single",
|
||||
name="redis-single",
|
||||
flavor_id=1,
|
||||
datastore_version_id=self.datastore_version.id,
|
||||
tenant_id=self.tenant_id,
|
||||
volume_size=None,
|
||||
task_status=InstanceTasks.NONE)
|
||||
self.master_db_info = DBInstance.create(
|
||||
id="redis-master",
|
||||
name="redis-master",
|
||||
flavor_id=1,
|
||||
datastore_version_id=self.datastore_version.id,
|
||||
tenant_id=self.tenant_id,
|
||||
volume_size=None,
|
||||
task_status=InstanceTasks.NONE)
|
||||
self.slave_db_info = DBInstance.create(
|
||||
id="redis-slave",
|
||||
name="redis-slave",
|
||||
flavor_id=1,
|
||||
datastore_version_id=self.datastore_version.id,
|
||||
tenant_id=self.tenant_id,
|
||||
volume_size=None,
|
||||
task_status=InstanceTasks.NONE,
|
||||
slave_of_id=self.master_db_info.id)
|
||||
|
||||
super(TestRedisRootController, self).setUp()
|
||||
self.controller = RedisRootController()
|
||||
|
||||
def tearDown(self):
|
||||
self.datastore.delete()
|
||||
self.datastore_version.delete()
|
||||
self.master_db_info.delete()
|
||||
self.slave_db_info.delete()
|
||||
super(TestRedisRootController, self).tearDown()
|
||||
|
||||
@patch.object(instance_models.Instance, "load")
|
||||
@patch.object(models.Root, "create")
|
||||
def test_root_create_on_single_instance(self, root_create, *args):
|
||||
user = Mock()
|
||||
context = Mock()
|
||||
context.user = Mock()
|
||||
context.user.__getitem__ = Mock(return_value=user)
|
||||
req = Mock()
|
||||
req.environ = Mock()
|
||||
req.environ.__getitem__ = Mock(return_value=context)
|
||||
tenant_id = self.tenant_id
|
||||
instance_id = self.single_db_info.id
|
||||
is_cluster = False
|
||||
password = Mock()
|
||||
body = {"password": password}
|
||||
self.controller.root_create(req, body, tenant_id,
|
||||
instance_id, is_cluster)
|
||||
root_create.assert_called_with(context, instance_id,
|
||||
context.user, password)
|
||||
|
||||
@patch.object(instance_models.Instance, "load")
|
||||
@patch.object(models.Root, "create")
|
||||
def test_root_create_on_master_instance(self, root_create, *args):
|
||||
user = Mock()
|
||||
context = Mock()
|
||||
context.user = Mock()
|
||||
context.user.__getitem__ = Mock(return_value=user)
|
||||
req = Mock()
|
||||
req.environ = Mock()
|
||||
req.environ.__getitem__ = Mock(return_value=context)
|
||||
tenant_id = self.tenant_id
|
||||
instance_id = self.master_db_info.id
|
||||
slave_instance_id = self.slave_db_info.id
|
||||
is_cluster = False
|
||||
password = Mock()
|
||||
body = {"password": password}
|
||||
self.controller.root_create(req, body, tenant_id,
|
||||
instance_id, is_cluster)
|
||||
root_create.assert_called_with(context, slave_instance_id,
|
||||
context.user, password)
|
||||
|
||||
def test_root_create_on_slave(self):
|
||||
user = Mock()
|
||||
context = Mock()
|
||||
context.user = Mock()
|
||||
context.user.__getitem__ = Mock(return_value=user)
|
||||
req = Mock()
|
||||
req.environ = Mock()
|
||||
req.environ.__getitem__ = Mock(return_value=context)
|
||||
tenant_id = self.tenant_id
|
||||
instance_id = self.slave_db_info.id
|
||||
is_cluster = False
|
||||
body = {}
|
||||
self.assertRaises(
|
||||
exception.SlaveOperationNotSupported,
|
||||
self.controller.root_create,
|
||||
req, body, tenant_id, instance_id, is_cluster)
|
||||
|
||||
def test_root_create_with_cluster(self):
|
||||
req = Mock()
|
||||
tenant_id = self.tenant_id
|
||||
instance_id = self.master_db_info.id
|
||||
is_cluster = True
|
||||
body = {}
|
||||
self.assertRaises(
|
||||
exception.ClusterOperationNotSupported,
|
||||
self.controller.root_create,
|
||||
req, body, tenant_id, instance_id, is_cluster)
|
||||
|
||||
@patch.object(instance_models.Instance, "load")
|
||||
@patch.object(models.Root, "delete")
|
||||
def test_root_delete_on_single_instance(self, root_delete, *args):
|
||||
context = Mock()
|
||||
req = Mock()
|
||||
req.environ = Mock()
|
||||
req.environ.__getitem__ = Mock(return_value=context)
|
||||
tenant_id = self.tenant_id
|
||||
instance_id = self.single_db_info.id
|
||||
is_cluster = False
|
||||
self.controller.root_delete(req, tenant_id, instance_id, is_cluster)
|
||||
root_delete.assert_called_with(context, instance_id)
|
||||
|
||||
@patch.object(instance_models.Instance, "load")
|
||||
@patch.object(models.Root, "delete")
|
||||
def test_root_delete_on_master_instance(self, root_delete, *args):
|
||||
context = Mock()
|
||||
req = Mock()
|
||||
req.environ = Mock()
|
||||
req.environ.__getitem__ = Mock(return_value=context)
|
||||
tenant_id = self.tenant_id
|
||||
instance_id = self.master_db_info.id
|
||||
slave_instance_id = self.slave_db_info.id
|
||||
is_cluster = False
|
||||
self.controller.root_delete(req, tenant_id, instance_id, is_cluster)
|
||||
root_delete.assert_called_with(context, slave_instance_id)
|
||||
|
||||
def test_root_delete_on_slave(self):
|
||||
context = Mock()
|
||||
req = Mock()
|
||||
req.environ = Mock()
|
||||
req.environ.__getitem__ = Mock(return_value=context)
|
||||
tenant_id = self.tenant_id
|
||||
instance_id = self.slave_db_info.id
|
||||
is_cluster = False
|
||||
self.assertRaises(
|
||||
exception.SlaveOperationNotSupported,
|
||||
self.controller.root_delete,
|
||||
req, tenant_id, instance_id, is_cluster)
|
||||
|
||||
def test_root_delete_with_cluster(self):
|
||||
req = Mock()
|
||||
tenant_id = self.tenant_id
|
||||
instance_id = self.master_db_info.id
|
||||
is_cluster = True
|
||||
self.assertRaises(
|
||||
exception.ClusterOperationNotSupported,
|
||||
self.controller.root_delete,
|
||||
req, tenant_id, instance_id, is_cluster)
|
Loading…
Reference in New Issue
Block a user