summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLingxian Kong <anlin.kong@gmail.com>2020-07-25 20:39:47 +1200
committerLingxian Kong <anlin.kong@gmail.com>2020-07-27 09:28:17 +1200
commitefb6a811bedd07819d95ff462195929dd1a33922 (patch)
treecb9ae96a76c7d149fe3e8edbc7e3ff1c10544b28
parent39b0df0a6b87a6f2cb748cb81ac376088f34b88f (diff)
downloadtrove-efb6a811bedd07819d95ff462195929dd1a33922.tar.gz
Using same config with primary for replicas
Change-Id: Icadc95ea54e4509dc148f8e84f2eaac5840509f3
-rw-r--r--doc/source/user/backup-db.rst2
-rw-r--r--doc/source/user/create-db.rst2
-rw-r--r--doc/source/user/set-up-replication.rst213
-rw-r--r--doc/source/user/upgrade-datastore.rst2
-rw-r--r--trove/common/apischema.py8
-rw-r--r--trove/instance/models.py116
-rw-r--r--trove/instance/service.py118
-rw-r--r--trove/taskmanager/manager.py4
-rw-r--r--trove/tests/api/replication.py18
-rw-r--r--trove/tests/scenario/runners/replication_runners.py9
-rw-r--r--trove/tests/unittests/instance/test_instance_models.py52
11 files changed, 278 insertions, 266 deletions
diff --git a/doc/source/user/backup-db.rst b/doc/source/user/backup-db.rst
index 35bb8782..9a1a10e5 100644
--- a/doc/source/user/backup-db.rst
+++ b/doc/source/user/backup-db.rst
@@ -143,7 +143,7 @@ instance from the backup.
.. code-block:: console
- $ openstack database instance create guest2 10 --size 2 --nic net-id=$network_id --backup BACKUP_ID
+ $ openstack database instance create guest2 --flavor 10 --size 2 --nic net-id=$network_id --backup BACKUP_ID
+-------------------+----------------------------------------------+
| Property | Value |
+-------------------+----------------------------------------------+
diff --git a/doc/source/user/create-db.rst b/doc/source/user/create-db.rst
index d43ee650..b8446b84 100644
--- a/doc/source/user/create-db.rst
+++ b/doc/source/user/create-db.rst
@@ -107,7 +107,7 @@ Create and access a database
.. code-block:: console
$ openstack database instance create mysql_instance_1 \
- 6 \
+ --flavor 6 \
--size 5 \
--nic net-id=8799cf10-01ef-40e2-b04e-06da7cfa5668 \
--databases test --users userA:password \
diff --git a/doc/source/user/set-up-replication.rst b/doc/source/user/set-up-replication.rst
index 6ec3c16b..6528ea87 100644
--- a/doc/source/user/set-up-replication.rst
+++ b/doc/source/user/set-up-replication.rst
@@ -2,109 +2,164 @@
Set up database replication
===========================
-You can create a replica of an existing database instance. When you make
-subsequent changes to the original instance, the system automatically
-applies those changes to the replica.
+You can create replicas of an existing database instance(the primary) to
+improve the performance and scale of read-intensive workloads. Read workloads
+can be isolated to the replicas, while write workloads can be directed to the
+primary. When you make subsequent changes to the primary, the system
+automatically applies those changes to the replicas. Because replicas are
+read-only, they don't directly reduce write-capacity burdens on the primary.
+This feature isn't targeted at write-intensive workloads.
-- Replicas are read-only.
+- Not all the datastores support replication feature in Trove.
-- When you create a replica, do not specify the ``--users`` or
- ``--databases`` options.
+- A replica is created by using the same server configuration as the primary,
+ e.g. flavor, data volume, datastore, etc. After a replica is created, several
+ settings can be changed independently from the primary server, e.g. the data
+ volume size.
-- You can choose a smaller volume or flavor for a replica than for the
- original, but the replica's volume must be big enough to hold the
- data snapshot from the original.
+- Currently, There is no automated failover between primary and replicas.
-This example shows you how to replicate a MySQL database instance.
+- Trove can only create a new replica. Adding an already existing instance to
+ the replication group is not supported.
+
+- Creating a replica of a replica is not supported.
+
+- When deleting replication instances, replicas need to be removed before the
+ primary.
Set up replication
-~~~~~~~~~~~~~~~~~~
+------------------
-#. **Get the instance ID**
+#. Create a replica
- Get the ID of the original instance you want to replicate:
+ First, make sure you have an instance (ID:
+ cebbf187-e223-46dd-8802-6dc04e895d0a) up and running in HEALTHY status,
+ create a replica:
.. code-block:: console
- $ openstack database instance list
- +-----------+------------+-----------+-------------------+--------+-----------+------+
- | id | name | datastore | datastore_version | status | flavor_id | size |
- +-----------+------------+-----------+-------------------+--------+-----------+------+
- | 97b...ae6 | base_1 | mysql | mysql-5.5 | ACTIVE | 10 | 2 |
- +-----------+------------+-----------+-------------------+--------+-----------+------+
+ $ openstack database instance create test-mysql-replica-1 \
+ --nic net-id=$netid \
+ --replica_of cebbf187-e223-46dd-8802-6dc04e895d0a
+
+#. Wait for the replica instance successfully created, verify status of the
+ replication servers.
+
+ .. code-block:: console
-#. **Create the replica**
+ $ odbi list
+ +--------------------------------------+----------------------+-----------+-------------------+---------+------------------------------------------------+-----------+------+-----------+---------+
+ | ID | Name | Datastore | Datastore Version | Status | Addresses | Flavor ID | Size | Region | Role |
+ +--------------------------------------+----------------------+-----------+-------------------+---------+------------------------------------------------+-----------+------+-----------+---------+
+ | 71f30a72-4e47-4505-9e7f-ffd8933a331c | test-mysql-replica-1 | mysql | 5.7.29 | HEALTHY | [{'address': '10.1.0.155', 'type': 'private'}] | d2 | 2 | RegionOne | replica |
+ | cebbf187-e223-46dd-8802-6dc04e895d0a | test-mysql | mysql | 5.7.29 | HEALTHY | [{'address': '10.1.0.43', 'type': 'private'}] | d2 | 2 | RegionOne | primary |
+ +--------------------------------------+----------------------+-----------+-------------------+---------+------------------------------------------------+-----------+------+-----------+---------+
- Create a new instance that will be a replica of the original
- instance. You do this by passing in the ``--replica_of`` option with
- the :command:`openstack database instance create` command. This example creates a replica
- called ``replica_1``. ``replica_1`` is a replica of the original instance,
- ``base_1``:
+#. Verify replication status.
+
+ Replication can be verified by making some modifications to the primary and
+ ensuring that the modifications also propagate back to the replica. We will
+ create a database called "newdb" on the primary and check it's automatically
+ created on the replica.
+
+ First, get the existing databases of primary and replica, they should be the
+ same:
.. code-block:: console
- $ openstack database instance create replica_1 6 --size=5 --nic net-id=$netid \
- --datastore_version mysql-5.5 \
- --datastore mysql --replica_of ID_OF_ORIGINAL_INSTANCE
+ $ openstack database db list cebbf187-e223-46dd-8802-6dc04e895d0a # The primary
+ +--------+
+ | Name |
+ +--------+
+ | testdb |
+ +--------+
+ $ openstack database db list 71f30a72-4e47-4505-9e7f-ffd8933a331c # The replica
+ +--------+
+ | Name |
+ +--------+
+ | testdb |
+ +--------+
+
+ Create a new database on the primary:
+
+ .. code-block:: console
-#. **Verify replication status**
+ $ openstack database db create cebbf187-e223-46dd-8802-6dc04e895d0a newdb
- Pass in ``replica_1``'s instance ID with the :command:`openstack database instance show` command
- to verify that the newly created ``replica_1`` instance is a replica
- of the original ``base_1``. Note that the ``replica_of`` property is
- set to the ID of ``base_1``.
+ Check the new database is also created on the replica:
.. code-block:: console
- $ openstack database instance show INSTANCE_ID_OF_REPLICA_1
- +-------------------+--------------------------------------+
- | Property | Value |
- +-------------------+--------------------------------------+
- | created | 2014-09-16T11:16:49 |
- | datastore | mysql |
- | datastore_version | mysql-5.5 |
- | flavor | 6 |
- | id | 49c6eff6-ef91-4eff-91c0-efbda7e83c38 |
- | name | replica_1 |
- | replica_of | 97b4b853-80f6-414f-ba6f-c6f455a79ae6 |
- | status | BUILD |
- | updated | 2014-09-16T11:16:49 |
- | volume | 5 |
- +-------------------+--------------------------------------+
-
- Now pass in ``base_1``'s instance ID with the :command:`openstack database instance show` command
- to list the replica(s) associated with the original instance. Note
- that the ``replicas`` property is set to the ID of ``replica_1``. If
- there are multiple replicas, they appear as a comma-separated list.
+ $ openstack database db list 71f30a72-4e47-4505-9e7f-ffd8933a331c
+ +--------+
+ | Name |
+ +--------+
+ | newdb |
+ | testdb |
+ +--------+
+
+Failover
+--------
+
+Since replication is asynchronous, there is lag between the primary and the
+replica. The amount of lag can be influenced by a number of factors like how
+heavy the workload running on the primary server is and the latency between
+data centers. In most cases, replica lag ranges between a few seconds to a
+couple minutes.
+
+#. Before performing failover, we will create one more replica:
+
+ .. code-block:: console
+
+ $ openstack database instance create test-mysql-replica-2 \
+ --nic net-id=$netid \
+ --replica_of cebbf187-e223-46dd-8802-6dc04e895d0a
+
+ Now we have 3 instances running in a replication group:
.. code-block:: console
- $ openstack database instance show INSTANCE_ID_OF_BASE_1
- +-------------------+--------------------------------------+
- | Property | Value |
- +-------------------+--------------------------------------+
- | created | 2014-09-16T11:04:56 |
- | datastore | mysql |
- | datastore_version | mysql-5.5 |
- | flavor | 6 |
- | id | 97b4b853-80f6-414f-ba6f-c6f455a79ae6 |
- | ip | 172.16.200.2 |
- | name | base_1 |
- | replicas | 49c6eff6-ef91-4eff-91c0-efbda7e83c38 |
- | status | ACTIVE |
- | updated | 2014-09-16T11:05:06 |
- | volume | 5 |
- | volume_used | 0.11 |
- +-------------------+--------------------------------------+
-
-#. **Detach the replica**
-
- If the original instance goes down, you can detach the replica. The
- replica becomes a standalone database instance. You can then take the
- new standalone instance and create a new replica of that instance.
-
- You detach a replica using the :command:`openstack database instance detach replica` command:
+ $ odbi list
+ +--------------------------------------+----------------------+-----------+-------------------+---------+------------------------------------------------+-----------+------+-----------+---------+
+ | ID | Name | Datastore | Datastore Version | Status | Addresses | Flavor ID | Size | Region | Role |
+ +--------------------------------------+----------------------+-----------+-------------------+---------+------------------------------------------------+-----------+------+-----------+---------+
+ | 71f30a72-4e47-4505-9e7f-ffd8933a331c | test-mysql-replica-1 | mysql | 5.7.29 | HEALTHY | [{'address': '10.1.0.155', 'type': 'private'}] | d2 | 2 | RegionOne | replica |
+ | a85ece86-9f62-4aa8-bb15-eba604cd2a01 | test-mysql-replica-2 | mysql | 5.7.29 | HEALTHY | [{'address': '10.1.0.243', 'type': 'private'}] | d2 | 2 | RegionOne | replica |
+ | cebbf187-e223-46dd-8802-6dc04e895d0a | test-mysql | mysql | 5.7.29 | HEALTHY | [{'address': '10.1.0.43', 'type': 'private'}] | d2 | 2 | RegionOne | primary |
+ +--------------------------------------+----------------------+-----------+-------------------+---------+------------------------------------------------+-----------+------+-----------+---------+
+
+#. Failover(promote) "test-mysql-replica-1" to primary.
.. code-block:: console
- $ openstack database instance detach replica INSTANCE_ID_OF_REPLICA
+ $ openstack database instance promote 71f30a72-4e47-4505-9e7f-ffd8933a331c
+
+ Wait for Trove setting up the new replication, the status of the 3 instances become "PROMOTE" then "HEALTHY".
+
+ .. code-block:: console
+
+ $ openstack database instance list
+ +--------------------------------------+----------------------+-----------+-------------------+---------+------------------------------------------------+-----------+------+-----------+---------+
+ | ID | Name | Datastore | Datastore Version | Status | Addresses | Flavor ID | Size | Region | Role |
+ +--------------------------------------+----------------------+-----------+-------------------+---------+------------------------------------------------+-----------+------+-----------+---------+
+ | 71f30a72-4e47-4505-9e7f-ffd8933a331c | test-mysql-replica-1 | mysql | 5.7.29 | HEALTHY | [{'address': '10.1.0.155', 'type': 'private'}] | d2 | 2 | RegionOne | primary |
+ | a85ece86-9f62-4aa8-bb15-eba604cd2a01 | test-mysql-replica-2 | mysql | 5.7.29 | HEALTHY | [{'address': '10.1.0.243', 'type': 'private'}] | d2 | 2 | RegionOne | replica |
+ | cebbf187-e223-46dd-8802-6dc04e895d0a | test-mysql | mysql | 5.7.29 | HEALTHY | [{'address': '10.1.0.43', 'type': 'private'}] | d2 | 2 | RegionOne | replica |
+ +--------------------------------------+----------------------+-----------+-------------------+---------+------------------------------------------------+-----------+------+-----------+---------+
+
+#. Point your application to the (former) replica.
+
+ Each server has a unique connection string. Update your application to point
+ to the (former) replica instead of the primary.
+
+Other supported operations
+--------------------------
+
+* Remove a failed primary. This essentially is used to eject an already failed
+ primary in order to establish a new one between the replicas. Command:
+ ``openstack database instance eject <primary_ID>``
+
+* Change replica to a standalone database server. The detached replica becomes
+ a standalone server that accepts both reads and writes. The standalone server
+ can't be made into a replica again.. Command:
+ ``openstack database instance detach <replica_ID>`` \ No newline at end of file
diff --git a/doc/source/user/upgrade-datastore.rst b/doc/source/user/upgrade-datastore.rst
index 3947b37c..4fb8dccc 100644
--- a/doc/source/user/upgrade-datastore.rst
+++ b/doc/source/user/upgrade-datastore.rst
@@ -49,7 +49,7 @@ Upgrading datastore
.. code-block:: console
$ openstack database instance create test-mysql-upgrade \
- d2 \
+ --flavor d2 \
--size 1 \
--nic net-id=$netid \
--datastore mysql --datastore_version 5.7.29 \
diff --git a/trove/common/apischema.py b/trove/common/apischema.py
index 0e3d60b1..0388face 100644
--- a/trove/common/apischema.py
+++ b/trove/common/apischema.py
@@ -381,7 +381,7 @@ instance = {
"properties": {
"instance": {
"type": "object",
- "required": ["name", "flavorRef"],
+ "required": ["name"],
"additionalProperties": True,
"properties": {
"name": non_empty_string,
@@ -398,6 +398,12 @@ instance = {
"backupRef": uuid
}
},
+ "replica_of": uuid,
+ "replica_count": {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 3
+ },
"availability_zone": non_empty_string,
"datastore": {
"type": "object",
diff --git a/trove/instance/models.py b/trove/instance/models.py
index 47b4c72c..daa8d3bf 100644
--- a/trove/instance/models.py
+++ b/trove/instance/models.py
@@ -1059,8 +1059,10 @@ class Instance(BuiltInstance):
configuration_id=None, slave_of_id=None, cluster_config=None,
replica_count=None, volume_type=None, modules=None,
locality=None, region_name=None, access=None):
-
- region_name = region_name or CONF.service_credentials.region_name
+ nova_client = clients.create_nova_client(context)
+ cinder_client = clients.create_cinder_client(context)
+ datastore_cfg = CONF.get(datastore_version.manager)
+ volume_support = datastore_cfg.volume_support
call_args = {
'name': name,
@@ -1070,7 +1072,10 @@ class Instance(BuiltInstance):
'image_id': image_id,
'availability_zone': availability_zone,
'region_name': region_name,
+ 'locality': locality
}
+ if cluster_config:
+ call_args['cluster_id'] = cluster_config.get("id", None)
# All nova flavors are permitted for a datastore-version unless one
# or more entries are found in datastore_version_metadata,
@@ -1086,14 +1091,16 @@ class Instance(BuiltInstance):
datastore=datastore.name,
datastore_version=datastore_version.name,
flavor_id=flavor_id)
-
- datastore_cfg = CONF.get(datastore_version.manager)
- client = clients.create_nova_client(context)
try:
- flavor = client.flavors.get(flavor_id)
+ flavor = nova_client.flavors.get(flavor_id)
except nova_exceptions.NotFound:
raise exception.FlavorNotFound(uuid=flavor_id)
+ replica_source = None
+ if slave_of_id:
+ replica_source = DBInstance.find_by(
+ context, id=slave_of_id, deleted=False)
+
# If a different region is specified for the instance, ensure
# that the flavor and image are the same in both regions
if region_name and region_name != CONF.service_credentials.region_name:
@@ -1101,13 +1108,23 @@ class Instance(BuiltInstance):
datastore, datastore_version)
deltas = {'instances': 1}
- volume_support = datastore_cfg.volume_support
if volume_support:
- call_args['volume_type'] = volume_type
+ if replica_source:
+ try:
+ volume = cinder_client.volumes.get(
+ replica_source.volume_id)
+ except Exception as e:
+ LOG.error(f'Failed to get volume from Cinder, error: '
+ f'{str(e)}')
+ raise exception.NotFound(uuid=replica_source.volume_id)
+ volume_type = volume.volume_type
+ volume_size = volume.size
+
dvm.validate_volume_type(context, volume_type,
datastore.name, datastore_version.name)
- call_args['volume_size'] = volume_size
validate_volume_size(volume_size)
+ call_args['volume_type'] = volume_type
+ call_args['volume_size'] = volume_size
deltas['volumes'] = volume_size
# Instance volume should have enough space for the backup
# Backup, and volume sizes are in GBs
@@ -1147,60 +1164,36 @@ class Instance(BuiltInstance):
datastore2=datastore.name)
if slave_of_id:
- Backup.verify_swift_auth_token(context)
-
- if databases or users:
- raise exception.ReplicaCreateWithUsersDatabasesError()
call_args['replica_of'] = slave_of_id
call_args['replica_count'] = replica_count
+
replication_support = datastore_cfg.replication_strategy
if not replication_support:
raise exception.ReplicationNotSupported(
datastore=datastore.name)
- try:
- # looking for replica source
- replica_source = DBInstance.find_by(
- context,
- id=slave_of_id,
- deleted=False)
- if replica_source.slave_of_id:
- raise exception.Forbidden(
- _("Cannot create a replica of a replica %(id)s.")
- % {'id': slave_of_id})
- if (CONF.verify_replica_volume_size
- and replica_source.volume_size > volume_size):
- raise exception.Forbidden(
- _("Replica volume size should not be smaller than"
- " master's, replica volume size: %(replica_size)s"
- " and master volume size: %(master_size)s.")
- % {'replica_size': volume_size,
- 'master_size': replica_source.volume_size})
- # load the replica source status to check if
- # source is available
- load_simple_instance_server_status(
+ if (CONF.verify_replica_volume_size
+ and replica_source.volume_size > volume_size):
+ raise exception.Forbidden(
+ _("Replica volume size should not be smaller than"
+ " master's, replica volume size: %(replica_size)s"
+ " and master volume size: %(master_size)s.")
+ % {'replica_size': volume_size,
+ 'master_size': replica_source.volume_size})
+ # load the replica source status to check if
+ # source is available
+ load_simple_instance_server_status(
+ context,
+ replica_source)
+ replica_source_instance = Instance(
+ context, replica_source,
+ None,
+ InstanceServiceStatus.find_by(
context,
- replica_source)
- replica_source_instance = Instance(
- context, replica_source,
- None,
- InstanceServiceStatus.find_by(
- context,
- instance_id=slave_of_id))
- replica_source_instance.validate_can_perform_action()
- except exception.ModelNotFoundError:
- LOG.exception(
- "Cannot create a replica of %(id)s "
- "as that instance could not be found.",
- {'id': slave_of_id})
- raise exception.NotFound(uuid=slave_of_id)
- elif replica_count and replica_count != 1:
- raise exception.Forbidden(_(
- "Replica count only valid when creating replicas. Cannot "
- "create %(count)d instances.") % {'count': replica_count})
+ instance_id=slave_of_id))
+ replica_source_instance.validate_can_perform_action()
+
multi_replica = slave_of_id and replica_count and replica_count > 1
instance_count = replica_count if multi_replica else 1
- if locality:
- call_args['locality'] = locality
if not nics:
nics = []
@@ -1211,8 +1204,6 @@ class Instance(BuiltInstance):
for net_id in CONF.management_networks]
if nics:
call_args['nics'] = nics
- if cluster_config:
- call_args['cluster_id'] = cluster_config.get("id", None)
if not modules:
modules = []
@@ -1228,7 +1219,6 @@ class Instance(BuiltInstance):
module_list = module_views.convert_modules_to_list(modules)
def _create_resources():
-
if cluster_config:
cluster_id = cluster_config.get("id", None)
shard_id = cluster_config.get("shard_id", None)
@@ -1251,17 +1241,15 @@ class Instance(BuiltInstance):
slave_of_id=slave_of_id, cluster_id=cluster_id,
shard_id=shard_id, type=instance_type,
region_id=region_name)
- LOG.debug("Tenant %(tenant)s created new Trove instance "
- "%(db)s in region %(region)s.",
- {'tenant': context.project_id, 'db': db_info.id,
- 'region': region_name})
-
instance_id = db_info.id
- cls.add_instance_modules(context, instance_id, modules)
instance_name = name
+ LOG.debug(f"Creating new instance {instance_id}")
ids.append(instance_id)
names.append(instance_name)
root_passwords.append(None)
+
+ cls.add_instance_modules(context, instance_id, modules)
+
# change the name to be name + replica_number if more than one
if multi_replica:
replica_number = instance_index + 1
@@ -1272,9 +1260,9 @@ class Instance(BuiltInstance):
# if a configuration group is associated with an instance,
# generate an overrides dict to pass into the instance creation
# method
-
config = Configuration(context, configuration_id)
overrides = config.get_configuration_overrides()
+
service_status = InstanceServiceStatus.create(
instance_id=instance_id,
status=srvstatus.ServiceStatuses.NEW)
diff --git a/trove/instance/service.py b/trove/instance/service.py
index 17de296e..8542b024 100644
--- a/trove/instance/service.py
+++ b/trove/instance/service.py
@@ -32,7 +32,7 @@ from trove.common import pagination
from trove.common import policy
from trove.common import utils
from trove.common import wsgi
-from trove.datastore import models as datastore_models
+from trove.datastore import models as ds_models
from trove.extensions.mysql.common import populate_users
from trove.extensions.mysql.common import populate_validated_databases
from trove.instance import models, views
@@ -341,24 +341,81 @@ class InstanceController(wsgi.Controller):
raise exception.NetworkConflict()
def create(self, req, body, tenant_id):
- # TODO(hub-cap): turn this into middleware
LOG.info("Creating a database instance for tenant '%s'",
tenant_id)
LOG.debug("req : '%s'\n\n", strutils.mask_password(req))
LOG.debug("body : '%s'\n\n", strutils.mask_password(body))
context = req.environ[wsgi.CONTEXT_KEY]
policy.authorize_on_tenant(context, 'instance:create')
- context.notification = notification.DBaaSInstanceCreate(context,
- request=req)
- datastore_args = body['instance'].get('datastore', {})
- datastore, datastore_version = (
- datastore_models.get_datastore_version(**datastore_args))
- image_id = datastore_version.image_id
+ context.notification = notification.DBaaSInstanceCreate(
+ context, request=req)
+
name = body['instance']['name']
- flavor_ref = body['instance']['flavorRef']
+ slave_of_id = body['instance'].get('replica_of')
+ replica_count = body['instance'].get('replica_count')
+ flavor_ref = body['instance'].get('flavorRef')
+ datastore_args = body['instance'].get('datastore', {})
+ volume_info = body['instance'].get('volume', {})
+ availability_zone = body['instance'].get('availability_zone')
+ nics = body['instance'].get('nics', [])
+ locality = body['instance'].get('locality')
+ region_name = body['instance'].get(
+ 'region_name', CONF.service_credentials.region_name
+ )
+ access = body['instance'].get('access', None)
+
+ if slave_of_id:
+ if flavor_ref:
+ msg = 'Cannot specify flavor when creating replicas.'
+ raise exception.BadRequest(message=msg)
+ if datastore_args:
+ msg = 'Cannot specify datastore when creating replicas.'
+ raise exception.BadRequest(message=msg)
+ if volume_info:
+ msg = 'Cannot specify volume when creating replicas.'
+ raise exception.BadRequest(message=msg)
+ if locality:
+ msg = 'Cannot specify locality when creating replicas.'
+ raise exception.BadRequest(message=msg)
+ backup_model.verify_swift_auth_token(context)
+ else:
+ if replica_count and replica_count > 1:
+ msg = (f"Replica count only valid when creating replicas. "
+ f"Cannot create {replica_count} instances.")
+ raise exception.BadRequest(message=msg)
+
flavor_id = utils.get_id_from_href(flavor_ref)
- configuration = self._configuration_parse(context, body)
+ if volume_info:
+ volume_size = int(volume_info.get('size'))
+ volume_type = volume_info.get('type')
+ else:
+ volume_size = None
+ volume_type = None
+
+ if slave_of_id:
+ try:
+ replica_source = models.DBInstance.find_by(
+ context, id=slave_of_id, deleted=False)
+ flavor_id = replica_source.flavor_id
+ except exception.ModelNotFoundError:
+ LOG.error(f"Cannot create a replica of {slave_of_id} as that "
+ f"instance could not be found.")
+ raise exception.NotFound(uuid=slave_of_id)
+ if replica_source.slave_of_id:
+ raise exception.Forbidden(
+ f"Cannot create a replica of a replica {slave_of_id}")
+
+ datastore_version = ds_models.DatastoreVersion.load_by_uuid(
+ replica_source.datastore_version_id)
+ datastore = ds_models.Datastore.load(
+ datastore_version.datastore_id)
+ else:
+ datastore, datastore_version = ds_models.get_datastore_version(
+ **datastore_args)
+
+ image_id = datastore_version.image_id
+
databases = populate_validated_databases(
body['instance'].get('databases', []))
database_names = [database.get('_name', '') for database in databases]
@@ -368,7 +425,10 @@ class InstanceController(wsgi.Controller):
database_names)
except ValueError as ve:
raise exception.BadRequest(message=ve)
+ if slave_of_id and (databases or users):
+ raise exception.ReplicaCreateWithUsersDatabasesError()
+ configuration = self._configuration_parse(context, body)
modules = body['instance'].get('modules')
# The following operations have their own API calls.
@@ -388,34 +448,22 @@ class InstanceController(wsgi.Controller):
policy.authorize_on_tenant(
context, 'instance:extension:database:create')
- if 'volume' in body['instance']:
- volume_info = body['instance']['volume']
- volume_size = int(volume_info['size'])
- volume_type = volume_info.get('type')
- else:
- volume_size = None
- volume_type = None
-
if 'restorePoint' in body['instance']:
backupRef = body['instance']['restorePoint']['backupRef']
backup_id = utils.get_id_from_href(backupRef)
else:
backup_id = None
- availability_zone = body['instance'].get('availability_zone')
-
# Only 1 nic is allowed as defined in API jsonschema.
- # Use list here just for backward compatibility.
- nics = body['instance'].get('nics', [])
+ # Use list just for backward compatibility.
if len(nics) > 0:
- LOG.info('Checking user provided instance network %s', nics[0])
- self._check_nic(context, nics[0])
+ nic = nics[0]
+ LOG.info('Checking user provided instance network %s', nic)
+ if slave_of_id and nic.get('ip_address'):
+ msg = "Cannot specify IP address when creating replicas."
+ raise exception.BadRequest(message=msg)
+ self._check_nic(context, nic)
- slave_of_id = body['instance'].get('replica_of',
- # also check for older name
- body['instance'].get('slave_of'))
- replica_count = body['instance'].get('replica_count')
- locality = body['instance'].get('locality')
if locality:
locality_domain = ['affinity', 'anti-affinity']
locality_domain_msg = ("Invalid locality '%s'. "
@@ -424,16 +472,6 @@ class InstanceController(wsgi.Controller):
"', '".join(locality_domain)))
if locality not in locality_domain:
raise exception.BadRequest(message=locality_domain_msg)
- if slave_of_id:
- dupe_locality_msg = (
- 'Cannot specify locality when adding replicas to existing '
- 'master.')
- raise exception.BadRequest(message=dupe_locality_msg)
-
- region_name = body['instance'].get(
- 'region_name', CONF.service_credentials.region_name
- )
- access = body['instance'].get('access', None)
instance = models.Instance.create(context, name, flavor_id,
image_id, databases, users,
@@ -480,7 +518,7 @@ class InstanceController(wsgi.Controller):
with StartNotification(context, instance_id=instance.id):
instance.detach_configuration()
if 'datastore_version' in kwargs:
- datastore_version = datastore_models.DatastoreVersion.load(
+ datastore_version = ds_models.DatastoreVersion.load(
instance.datastore, kwargs['datastore_version'])
context.notification = (
notification.DBaaSInstanceUpgrade(context, request=req))
diff --git a/trove/taskmanager/manager.py b/trove/taskmanager/manager.py
index bbec7d0b..01eaa741 100644
--- a/trove/taskmanager/manager.py
+++ b/trove/taskmanager/manager.py
@@ -359,8 +359,8 @@ class Manager(periodic_task.PeriodicTasks):
try:
for replica_index in range(0, len(ids)):
replica_number += 1
- LOG.info("Creating replica %(num)d of %(count)d.",
- {'num': replica_number, 'count': len(ids)})
+ LOG.info(f"Creating replica {replica_number} "
+ f"({ids[replica_index]}) of {len(ids)}.")
instance_tasks = FreshInstanceTasks.load(
context, ids[replica_index])
diff --git a/trove/tests/api/replication.py b/trove/tests/api/replication.py
index 4dd49307..0d4e341a 100644
--- a/trove/tests/api/replication.py
+++ b/trove/tests/api/replication.py
@@ -108,10 +108,6 @@ def instance_is_active(id):
def create_slave():
result = instance_info.dbaas.instances.create(
instance_info.name + "_slave",
- instance_info.dbaas_flavor_href,
- {'size': 2},
- datastore=instance_info.dbaas_datastore,
- datastore_version=instance_info.dbaas_datastore_version,
nics=instance_info.nics,
replica_of=instance_info.id)
assert_equal(200, instance_info.dbaas.last_http_code)
@@ -141,20 +137,6 @@ def validate_master(master, slaves):
groups=[tests.DBAAS_API_REPLICATION],
enabled=CONFIG.swift_enabled)
class CreateReplicationSlave(object):
-
- @test
- def test_replica_provisioning_with_missing_replica_source(self):
- assert_raises(exceptions.NotFound,
- instance_info.dbaas.instances.create,
- instance_info.name + "_slave",
- instance_info.dbaas_flavor_href,
- instance_info.volume,
- datastore=instance_info.dbaas_datastore,
- datastore_version=instance_info.dbaas_datastore_version,
- nics=instance_info.nics,
- replica_of="Missing replica source")
- assert_equal(404, instance_info.dbaas.last_http_code)
-
@test
def test_create_db_on_master(self):
"""test_create_db_on_master"""
diff --git a/trove/tests/scenario/runners/replication_runners.py b/trove/tests/scenario/runners/replication_runners.py
index a9115fed..2d862ebd 100644
--- a/trove/tests/scenario/runners/replication_runners.py
+++ b/trove/tests/scenario/runners/replication_runners.py
@@ -88,10 +88,7 @@ class ReplicationRunner(TestRunner):
client = self.auth_client
client.instances.create(
self.instance_info.name + '_' + replica_name,
- self.instance_info.dbaas_flavor_href,
- self.instance_info.volume, replica_of=master_id,
- datastore=self.instance_info.dbaas_datastore,
- datastore_version=self.instance_info.dbaas_datastore_version,
+ replica_of=master_id,
nics=self.instance_info.nics,
replica_count=replica_count)
self.assert_client_code(client, expected_http_code)
@@ -154,10 +151,6 @@ class ReplicationRunner(TestRunner):
client = self.auth_client
self.non_affinity_repl_id = client.instances.create(
self.instance_info.name + '_non-affinity-repl',
- self.instance_info.dbaas_flavor_href,
- self.instance_info.volume,
- datastore=self.instance_info.dbaas_datastore,
- datastore_version=self.instance_info.dbaas_datastore_version,
nics=self.instance_info.nics,
replica_of=self.non_affinity_master_id,
replica_count=1).id
diff --git a/trove/tests/unittests/instance/test_instance_models.py b/trove/tests/unittests/instance/test_instance_models.py
index f63314b5..10befc8c 100644
--- a/trove/tests/unittests/instance/test_instance_models.py
+++ b/trove/tests/unittests/instance/test_instance_models.py
@@ -343,6 +343,7 @@ class TestReplication(trove_testtools.TestCase):
id=str(uuid.uuid4()),
name="TestMasterInstance",
datastore_version_id=self.datastore_version.id,
+ flavor_id=str(uuid.uuid4()),
volume_size=2)
self.master.set_task_status(InstanceTasks.NONE)
self.master.save()
@@ -371,18 +372,6 @@ class TestReplication(trove_testtools.TestCase):
super(TestReplication, self).tearDown()
@patch('trove.instance.models.LOG')
- def test_replica_of_not_active_master(self, mock_logging):
- self.master.set_task_status(InstanceTasks.BUILDING)
- self.master.save()
- self.master_status.set_status(ServiceStatuses.BUILDING)
- self.master_status.save()
- self.assertRaises(exception.UnprocessableEntity,
- Instance.create,
- None, 'name', 1, "UUID", [], [], self.datastore,
- self.datastore_version, 2,
- None, slave_of_id=self.master.id)
-
- @patch('trove.instance.models.LOG')
def test_replica_with_invalid_slave_of_id(self, mock_logging):
self.assertRaises(exception.NotFound,
Instance.create,
@@ -390,45 +379,6 @@ class TestReplication(trove_testtools.TestCase):
self.datastore_version, 2,
None, slave_of_id=str(uuid.uuid4()))
- def test_create_replica_from_replica(self):
- self.replica_datastore_version = Mock(
- spec=datastore_models.DBDatastoreVersion)
- self.replica_datastore_version.id = "UUID"
- self.replica_datastore_version.manager = 'mysql'
- self.replica_info = DBInstance(
- InstanceTasks.NONE,
- id="UUID",
- name="TestInstance",
- datastore_version_id=self.replica_datastore_version.id,
- slave_of_id=self.master.id)
- self.replica_info.save()
- self.assertRaises(exception.Forbidden, Instance.create,
- None, 'name', 2, "UUID", [], [], self.datastore,
- self.datastore_version, 2,
- None, slave_of_id=self.replica_info.id)
-
- def test_create_replica_with_users(self):
- self.users.append({"name": "testuser", "password": "123456"})
- self.assertRaises(exception.ReplicaCreateWithUsersDatabasesError,
- Instance.create, None, 'name', 2, "UUID", [],
- self.users, self.datastore, self.datastore_version,
- 1, None, slave_of_id=self.master.id)
-
- def test_create_replica_with_databases(self):
- self.databases.append({"name": "testdb"})
- self.assertRaises(exception.ReplicaCreateWithUsersDatabasesError,
- Instance.create, None, 'name', 1, "UUID",
- self.databases, [], self.datastore,
- self.datastore_version, 2, None,
- slave_of_id=self.master.id)
-
- def test_replica_volume_size_smaller_than_master(self):
- self.assertRaises(exception.Forbidden,
- Instance.create,
- None, 'name', 1, "UUID", [], [], self.datastore,
- self.datastore_version, 1,
- None, slave_of_id=self.master.id)
-
def trivial_key_function(id):
return id * id