summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLingxian Kong <anlin.kong@gmail.com>2020-09-11 13:18:02 +1200
committerLingxian Kong <anlin.kong@gmail.com>2020-09-13 19:13:53 +1200
commit72e20e4e97c54d59e6330180b65635928c9b274e (patch)
tree3b2b04cc6618dde8c6727815361703dd00bcbdd3
parentdf4f74769ee731c57cf8dcef01dec8946baa58c6 (diff)
downloadtrove-72e20e4e97c54d59e6330180b65635928c9b274e.tar.gz
Only enable user and database API for MySQL
Change-Id: Ic59f2fd94c5f216414effe7d13d0dd486dce9243
-rw-r--r--api-ref/source/databases.inc5
-rw-r--r--api-ref/source/users.inc4
-rw-r--r--doc/source/user/create-db.rst12
-rw-r--r--doc/source/user/manage-db-and-users.rst103
-rw-r--r--trove/extensions/common/models.py27
-rw-r--r--trove/extensions/common/service.py10
-rw-r--r--trove/tests/unittests/extensions/redis/__init__.py0
-rw-r--r--trove/tests/unittests/extensions/redis/test_service.py236
8 files changed, 94 insertions, 303 deletions
diff --git a/api-ref/source/databases.inc b/api-ref/source/databases.inc
index c7c1df77..e0d91ef7 100644
--- a/api-ref/source/databases.inc
+++ b/api-ref/source/databases.inc
@@ -4,7 +4,10 @@
Databases
=========
-
+Currently, the Database and User API is only supported by mysql datastore. For
+others, the recommended way is to get root password
+(``POST /v1.0/{project_id}/instances/{instance_id}/root``) and communicate with
+the database service directly for database and user management.
Create database
diff --git a/api-ref/source/users.inc b/api-ref/source/users.inc
index 686a84fd..91be9969 100644
--- a/api-ref/source/users.inc
+++ b/api-ref/source/users.inc
@@ -4,6 +4,10 @@
Users
=====
+Currently, the Database and User API is only supported by mysql datastore. For
+others, the recommended way is to get root password
+(``POST /v1.0/{project_id}/instances/{instance_id}/root``) and communicate with
+the database service directly for database and user management.
Create user
diff --git a/doc/source/user/create-db.rst b/doc/source/user/create-db.rst
index b8446b84..6118ba39 100644
--- a/doc/source/user/create-db.rst
+++ b/doc/source/user/create-db.rst
@@ -1,16 +1,16 @@
.. _create_db:
-============================
-Create and access a database
-============================
+=====================================
+Create and access a database instance
+=====================================
Assume that you have installed the Database service and populated your
data store with images for the type and versions of databases that you
-want, and that you can create and access a database.
+want, and that you can create and access a database instance.
This example shows you how to create and access a MySQL 5.7 database.
-Create and access a database
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Create and access a database instance
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#. **Determine which flavor to use for your database**
diff --git a/doc/source/user/manage-db-and-users.rst b/doc/source/user/manage-db-and-users.rst
index 38a80442..ad25b1d8 100644
--- a/doc/source/user/manage-db-and-users.rst
+++ b/doc/source/user/manage-db-and-users.rst
@@ -2,60 +2,70 @@
Manage databases and users on Trove instances
=============================================
-Assume that you installed Trove service and uploaded images with datastore
-of your choice.
-This section shows how to manage users and databases in a MySQL 5.7 instance.
+Assume that you installed Trove service and uploaded images with datastore of
+your choice. This section shows how to manage users and databases in a MySQL
+5.7 instance.
-Add new database and user to an existing Trove instance
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.. warning::
-Trove provides API to manage users and databases on
-datastores including relational (e.g. MySQL, PostgreSQL) and non-relational
-(e.g. Redis, Cassandra). Once a Trove instance with a datastore of choice is
-active you can use Trove API to create new databases and/or users.
+ Currently, the Database and User API is only supported by mysql datastore.
+ For others, the recommended way is to get root password (``POST
+ /v1.0/{project_id}/instances/{instance_id}/root``) and communicate with the
+ database service directly for database and user management.
+
+Manage root user
+~~~~~~~~~~~~~~~~
+
+For all the datastores, the user could enable root and get root password for
+further database operations.
.. code-block:: console
- $ openstack database user list db-instance
+ $ openstack database root enable f22ce0d9-8c9c-403a-8599-2269761a66de
+ +----------+--------------------------------------+
+ | Field | Value |
+ +----------+--------------------------------------+
+ | name | root |
+ | password | I5nPpBj1qf1eGR1idQorj1szppXGpYyYNj4h |
+ +----------+--------------------------------------+
+
+If needed, ``openstack database root disable <instance_id>`` command could
+disable the root user.
+
+Database and User management via Trove API
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Trove provides API to manage users and databases for mysql datastore.
+
+.. code-block:: console
+ $ openstack database user list db-instance
+------+------+-----------+
| Name | Host | Databases |
+------+------+-----------+
| test | % | testdb |
+------+------+-----------+
-
- $ openstack database user create db-instance newuser userpass --databases testdb
-
- $ openstack database user list db-instance
-
+ $ openstack database user create db-instance newuser userpass --databases testdb
+ $ openstack database user list db-instance
+---------+------+-----------+
| Name | Host | Databases |
+---------+------+-----------+
| newuser | % | testdb |
| test | % | testdb |
+---------+------+-----------+
-
-
- $ mysql -h 172.24.4.199 -u newuser -p testdb
+ $ mysql -h 172.24.4.199 -u newuser -p testdb
Enter password:
-
mysql> show databases;
-
+--------------------+
| Database |
+--------------------+
| information_schema |
| testdb |
+--------------------+
-
2 rows in set (0.00 sec)
-
- $ openstack database db create db-instance newdb
-
-
- $ openstack database db list db-instance
-
+ $ openstack database db create db-instance newdb
+ $ openstack database db list db-instance
+--------+
| Name |
+--------+
@@ -64,8 +74,7 @@ active you can use Trove API to create new databases and/or users.
| testdb |
+--------+
- $ mysql -h 172.24.4.199 -u newuser -p newdb
-
+ $ mysql -h 172.24.4.199 -u newuser -p newdb
Enter password:
ERROR 1044 (42000): Access denied for user 'newuser'@'%' to database 'newdb'
@@ -73,15 +82,14 @@ active you can use Trove API to create new databases and/or users.
Manage access to databases
~~~~~~~~~~~~~~~~~~~~~~~~~~
-With Trove API you can grant and revoke database access rights for existing users.
+With Trove API you can grant and revoke database access rights for existing
+users.
.. code-block:: console
- $ openstack database user grant access db-instance newuser newdb
-
-
- $ openstack database user show access db-instance newuser
+ $ openstack database user grant access db-instance newuser newdb
+ $ openstack database user show access db-instance newuser
+--------+
| Name |
+--------+
@@ -89,30 +97,24 @@ With Trove API you can grant and revoke database access rights for existing user
| testdb |
+--------+
- $ mysql -h IP_ADDRESS -u newuser -p newdb
+ $ mysql -h IP_ADDRESS -u newuser -p newdb
Enter password:
-
- $ openstack database user show access db-instance test
-
+ $ openstack database user show access db-instance test
+--------+
| Name |
+--------+
| testdb |
+--------+
- $ mysql -h IP_ADDRESS -u test -p newdb
+ $ mysql -h IP_ADDRESS -u test -p newdb
Enter password:
-
ERROR 1044 (42000): Access denied for user 'test'@'%' to database 'newdb'
+ $ openstack database user revoke access db-instance newuser newdb
- $ openstack database user revoke access db-instance newuser newdb
-
-
- $ mysql -h IP_ADDRESS -u newuser -p newdb
+ $ mysql -h IP_ADDRESS -u newuser -p newdb
Enter password:
-
ERROR 1044 (42000): Access denied for user 'newuser'@'%' to database 'newdb'
@@ -123,8 +125,7 @@ Lastly, Trove provides API for deleting databases.
.. code-block:: console
- $ openstack database db list db-instance
-
+ $ openstack database db list db-instance
+--------+
| Name |
+--------+
@@ -133,10 +134,9 @@ Lastly, Trove provides API for deleting databases.
| testdb |
+--------+
- $ openstack database db delete db-instance testdb
-
- $ openstack database db list db-instance
+ $ openstack database db delete db-instance testdb
+ $ openstack database db list db-instance
+--------+
| Name |
+--------+
@@ -144,7 +144,6 @@ Lastly, Trove provides API for deleting databases.
| sys |
+--------+
- $ mysql -h IP_ADDRESS -u test -p testdb
+ $ mysql -h IP_ADDRESS -u test -p testdb
Enter password:
-
ERROR 1049 (42000): Unknown database 'testdb' \ No newline at end of file
diff --git a/trove/extensions/common/models.py b/trove/extensions/common/models.py
index 81f202c2..34079cd1 100644
--- a/trove/extensions/common/models.py
+++ b/trove/extensions/common/models.py
@@ -26,23 +26,34 @@ from trove.instance import models as base_models
LOG = logging.getLogger(__name__)
-def load_and_verify(context, instance_id):
- # Load InstanceServiceStatus to verify if its running
+def load_and_verify(context, instance_id,
+ enabled_datastore=['mysql', 'mariadb']):
+ """Check instance datastore.
+
+ Some API operations are only supported for some specific datastores.
+ """
instance = base_models.Instance.load(context, instance_id)
+
+ if instance.datastore.name not in enabled_datastore:
+ raise exception.UnprocessableEntity(
+ f"Operation not supported for datastore {instance.datastore.name}."
+ )
+
if not instance.is_datastore_running:
raise exception.UnprocessableEntity(
"Instance %s is not ready, status: %s." %
(instance.id, instance.datastore_status.status)
)
- else:
- return instance
+
+ return instance
class Root(object):
@classmethod
def load(cls, context, instance_id):
- load_and_verify(context, instance_id)
+ load_and_verify(context, instance_id,
+ enabled_datastore=['mysql', 'mariadb', 'postgresql'])
# TODO(pdmars): remove the is_root_enabled call from the guest agent,
# just check the database for this information.
# If the root history returns null or raises an exception, the root
@@ -58,7 +69,8 @@ class Root(object):
@classmethod
def create(cls, context, instance_id, root_password,
cluster_instances_list=None):
- load_and_verify(context, instance_id)
+ load_and_verify(context, instance_id,
+ enabled_datastore=['mysql', 'mariadb', 'postgresql'])
if root_password:
root = create_guest_client(context,
instance_id).enable_root_with_password(
@@ -79,7 +91,8 @@ class Root(object):
@classmethod
def delete(cls, context, instance_id):
- load_and_verify(context, instance_id)
+ load_and_verify(context, instance_id,
+ enabled_datastore=['mysql', 'mariadb', 'postgresql'])
create_guest_client(context, instance_id).disable_root()
diff --git a/trove/extensions/common/service.py b/trove/extensions/common/service.py
index 5b8bf161..d645de4d 100644
--- a/trove/extensions/common/service.py
+++ b/trove/extensions/common/service.py
@@ -260,7 +260,15 @@ class RootController(ExtensionController):
try:
clazz = CONF.get(manager).get('root_controller')
LOG.debug("Loading Root Controller class %s.", clazz)
+
+ if not clazz:
+ raise exception.DatastoreOperationNotSupported(
+ datastore=manager, operation='root')
+
root_controller = import_class(clazz)
return root_controller()
except NoSuchOptError:
- return None
+ LOG.warning(
+ f"root_controller not configured for datastore {manager}")
+ raise exception.DatastoreOperationNotSupported(
+ datastore=manager, operation='root')
diff --git a/trove/tests/unittests/extensions/redis/__init__.py b/trove/tests/unittests/extensions/redis/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/trove/tests/unittests/extensions/redis/__init__.py
+++ /dev/null
diff --git a/trove/tests/unittests/extensions/redis/test_service.py b/trove/tests/unittests/extensions/redis/test_service.py
deleted file mode 100644
index 0b8b60aa..00000000
--- a/trove/tests/unittests/extensions/redis/test_service.py
+++ /dev/null
@@ -1,236 +0,0 @@
-# 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 unittest.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.models import RedisRoot
-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,
- 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,
- 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(RedisRoot, "get_auth_password")
- @patch.object(models.Root, "delete")
- @patch.object(models.Root, "load")
- def test_root_delete_on_single_instance(self, root_load,
- 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
- root_load.return_value = True
- self.controller.root_delete(req, tenant_id, instance_id, is_cluster)
- root_load.assert_called_with(context, instance_id)
- root_delete.assert_called_with(context, instance_id)
-
- @patch.object(instance_models.Instance, "load")
- @patch.object(RedisRoot, "get_auth_password")
- @patch.object(models.Root, "delete")
- @patch.object(models.Root, "load")
- def test_root_delete_on_master_instance(self, root_load,
- 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
- root_load.return_value = True
- self.controller.root_delete(req, tenant_id, instance_id, is_cluster)
- root_load.assert_called_with(context, instance_id)
- 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)
-
- @patch.object(instance_models.Instance, "load")
- @patch.object(models.Root, "delete")
- @patch.object(models.Root, "load")
- def test_root_delete_without_root_enabled(self, root_load,
- 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
- root_load.return_value = False
- self.assertRaises(
- exception.RootHistoryNotFound,
- self.controller.root_delete,
- req, tenant_id, instance_id, is_cluster)
- root_load.assert_called_with(context, instance_id)
- root_delete.assert_not_called()