diff options
author | Fan Zhang <zh.f@outlook.com> | 2017-11-03 09:58:27 +0800 |
---|---|---|
committer | Fan Zhang <zh.f@outlook.com> | 2017-12-08 13:23:36 +0800 |
commit | a57bf8816bd08c836cf2134c1da78ec940bd4d78 (patch) | |
tree | 8f245d206c7fd4d6e23bdd0a54f088b6d7402a93 /trove/extensions | |
parent | 8aad3ee2e45603bbbeaeb242a00f316c9bf55cdd (diff) | |
download | trove-a57bf8816bd08c836cf2134c1da78ec940bd4d78.tar.gz |
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>
Diffstat (limited to 'trove/extensions')
-rw-r--r-- | trove/extensions/redis/__init__.py | 0 | ||||
-rw-r--r-- | trove/extensions/redis/models.py | 28 | ||||
-rw-r--r-- | trove/extensions/redis/service.py | 185 | ||||
-rw-r--r-- | trove/extensions/redis/views.py | 30 |
4 files changed, 243 insertions, 0 deletions
diff --git a/trove/extensions/redis/__init__.py b/trove/extensions/redis/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/trove/extensions/redis/__init__.py diff --git a/trove/extensions/redis/models.py b/trove/extensions/redis/models.py new file mode 100644 index 00000000..62a35e26 --- /dev/null +++ b/trove/extensions/redis/models.py @@ -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 diff --git a/trove/extensions/redis/service.py b/trove/extensions/redis/service.py new file mode 100644 index 00000000..6788a3de --- /dev/null +++ b/trove/extensions/redis/service.py @@ -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) diff --git a/trove/extensions/redis/views.py b/trove/extensions/redis/views.py new file mode 100644 index 00000000..9066ce6b --- /dev/null +++ b/trove/extensions/redis/views.py @@ -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} |