Only enable user and database API for MySQL
Change-Id: Ic59f2fd94c5f216414effe7d13d0dd486dce9243
This commit is contained in:
parent
df4f74769e
commit
72e20e4e97
@ -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
|
||||
|
@ -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
|
||||
|
@ -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**
|
||||
|
||||
|
@ -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 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
|
||||
|
||||
+---------+------+-----------+
|
||||
| Name | Host | Databases |
|
||||
+---------+------+-----------+
|
||||
| newuser | % | testdb |
|
||||
| test | % | 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
|
||||
|
||||
+--------+
|
||||
| Name |
|
||||
+--------+
|
||||
@ -65,7 +75,6 @@ active you can use Trove API to create new databases and/or users.
|
||||
+--------+
|
||||
|
||||
$ 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
|
||||
|
||||
+--------+
|
||||
| Name |
|
||||
+--------+
|
||||
@ -92,9 +100,7 @@ With Trove API you can grant and revoke database access rights for existing user
|
||||
$ mysql -h IP_ADDRESS -u newuser -p newdb
|
||||
Enter password:
|
||||
|
||||
|
||||
$ openstack database user show access db-instance test
|
||||
|
||||
+--------+
|
||||
| Name |
|
||||
+--------+
|
||||
@ -103,16 +109,12 @@ With Trove API you can grant and revoke database access rights for existing user
|
||||
|
||||
$ 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
|
||||
|
||||
|
||||
$ mysql -h IP_ADDRESS -u newuser -p newdb
|
||||
Enter password:
|
||||
|
||||
ERROR 1044 (42000): Access denied for user 'newuser'@'%' to database 'newdb'
|
||||
|
||||
|
||||
@ -124,7 +126,6 @@ Lastly, Trove provides API for deleting databases.
|
||||
.. code-block:: console
|
||||
|
||||
$ openstack database db list db-instance
|
||||
|
||||
+--------+
|
||||
| Name |
|
||||
+--------+
|
||||
@ -136,7 +137,6 @@ Lastly, Trove provides API for deleting databases.
|
||||
$ openstack database db delete db-instance testdb
|
||||
|
||||
$ openstack database db list db-instance
|
||||
|
||||
+--------+
|
||||
| Name |
|
||||
+--------+
|
||||
@ -146,5 +146,4 @@ Lastly, Trove provides API for deleting databases.
|
||||
|
||||
$ mysql -h IP_ADDRESS -u test -p testdb
|
||||
Enter password:
|
||||
|
||||
ERROR 1049 (42000): Unknown database 'testdb'
|
@ -26,15 +26,25 @@ 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
|
||||
|
||||
|
||||
@ -42,7 +52,8 @@ 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()
|
||||
|
||||
|
||||
|
@ -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')
|
||||
|
@ -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()
|
Loading…
Reference in New Issue
Block a user