summaryrefslogtreecommitdiff
path: root/trove/extensions
diff options
context:
space:
mode:
authorFan Zhang <zh.f@outlook.com>2017-11-03 09:58:27 +0800
committerFan Zhang <zh.f@outlook.com>2017-12-08 13:23:36 +0800
commita57bf8816bd08c836cf2134c1da78ec940bd4d78 (patch)
tree8f245d206c7fd4d6e23bdd0a54f088b6d7402a93 /trove/extensions
parent8aad3ee2e45603bbbeaeb242a00f316c9bf55cdd (diff)
downloadtrove-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__.py0
-rw-r--r--trove/extensions/redis/models.py28
-rw-r--r--trove/extensions/redis/service.py185
-rw-r--r--trove/extensions/redis/views.py30
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}