Merge "Using same config with primary for replicas"

This commit is contained in:
Zuul 2020-07-27 04:08:07 +00:00 committed by Gerrit Code Review
commit be6f8565f5
11 changed files with 288 additions and 276 deletions

View File

@ -143,7 +143,7 @@ instance from the backup.
.. code-block:: console .. 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 | | Property | Value |
+-------------------+----------------------------------------------+ +-------------------+----------------------------------------------+

View File

@ -107,7 +107,7 @@ Create and access a database
.. code-block:: console .. code-block:: console
$ openstack database instance create mysql_instance_1 \ $ openstack database instance create mysql_instance_1 \
6 \ --flavor 6 \
--size 5 \ --size 5 \
--nic net-id=8799cf10-01ef-40e2-b04e-06da7cfa5668 \ --nic net-id=8799cf10-01ef-40e2-b04e-06da7cfa5668 \
--databases test --users userA:password \ --databases test --users userA:password \

View File

@ -2,109 +2,164 @@
Set up database replication Set up database replication
=========================== ===========================
You can create a replica of an existing database instance. When you make You can create replicas of an existing database instance(the primary) to
subsequent changes to the original instance, the system automatically improve the performance and scale of read-intensive workloads. Read workloads
applies those changes to the replica. 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 - A replica is created by using the same server configuration as the primary,
``--databases`` options. 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 - Currently, There is no automated failover between primary and replicas.
original, but the replica's volume must be big enough to hold the
data snapshot from the original.
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 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 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
$ 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 |
+--------------------------------------+----------------------+-----------+-------------------+---------+------------------------------------------------+-----------+------+-----------+---------+
#. 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 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
$ openstack database db create cebbf187-e223-46dd-8802-6dc04e895d0a newdb
Check the new database is also created on the replica:
.. code-block:: console
$ 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
$ 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 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 .. code-block:: console
$ openstack database instance list $ openstack database instance list
+-----------+------------+-----------+-------------------+--------+-----------+------+ +--------------------------------------+----------------------+-----------+-------------------+---------+------------------------------------------------+-----------+------+-----------+---------+
| id | name | datastore | datastore_version | status | flavor_id | size | | ID | Name | Datastore | Datastore Version | Status | Addresses | Flavor ID | Size | Region | Role |
+-----------+------------+-----------+-------------------+--------+-----------+------+ +--------------------------------------+----------------------+-----------+-------------------+---------+------------------------------------------------+-----------+------+-----------+---------+
| 97b...ae6 | base_1 | mysql | mysql-5.5 | ACTIVE | 10 | 2 | | 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 |
+--------------------------------------+----------------------+-----------+-------------------+---------+------------------------------------------------+-----------+------+-----------+---------+
#. **Create the replica** #. Point your application to the (former) replica.
Create a new instance that will be a replica of the original Each server has a unique connection string. Update your application to point
instance. You do this by passing in the ``--replica_of`` option with to the (former) replica instead of the primary.
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``:
.. code-block:: console Other supported operations
--------------------------
$ openstack database instance create replica_1 6 --size=5 --nic net-id=$netid \ * Remove a failed primary. This essentially is used to eject an already failed
--datastore_version mysql-5.5 \ primary in order to establish a new one between the replicas. Command:
--datastore mysql --replica_of ID_OF_ORIGINAL_INSTANCE ``openstack database instance eject <primary_ID>``
#. **Verify replication status** * Change replica to a standalone database server. The detached replica becomes
a standalone server that accepts both reads and writes. The standalone server
Pass in ``replica_1``'s instance ID with the :command:`openstack database instance show` command can't be made into a replica again.. Command:
to verify that the newly created ``replica_1`` instance is a replica ``openstack database instance detach <replica_ID>``
of the original ``base_1``. Note that the ``replica_of`` property is
set to the ID of ``base_1``.
.. 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.
.. 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:
.. code-block:: console
$ openstack database instance detach replica INSTANCE_ID_OF_REPLICA

View File

@ -49,7 +49,7 @@ Upgrading datastore
.. code-block:: console .. code-block:: console
$ openstack database instance create test-mysql-upgrade \ $ openstack database instance create test-mysql-upgrade \
d2 \ --flavor d2 \
--size 1 \ --size 1 \
--nic net-id=$netid \ --nic net-id=$netid \
--datastore mysql --datastore_version 5.7.29 \ --datastore mysql --datastore_version 5.7.29 \

View File

@ -381,7 +381,7 @@ instance = {
"properties": { "properties": {
"instance": { "instance": {
"type": "object", "type": "object",
"required": ["name", "flavorRef"], "required": ["name"],
"additionalProperties": True, "additionalProperties": True,
"properties": { "properties": {
"name": non_empty_string, "name": non_empty_string,
@ -398,6 +398,12 @@ instance = {
"backupRef": uuid "backupRef": uuid
} }
}, },
"replica_of": uuid,
"replica_count": {
"type": "integer",
"minimum": 1,
"maximum": 3
},
"availability_zone": non_empty_string, "availability_zone": non_empty_string,
"datastore": { "datastore": {
"type": "object", "type": "object",

View File

@ -1059,8 +1059,10 @@ class Instance(BuiltInstance):
configuration_id=None, slave_of_id=None, cluster_config=None, configuration_id=None, slave_of_id=None, cluster_config=None,
replica_count=None, volume_type=None, modules=None, replica_count=None, volume_type=None, modules=None,
locality=None, region_name=None, access=None): locality=None, region_name=None, access=None):
nova_client = clients.create_nova_client(context)
region_name = region_name or CONF.service_credentials.region_name cinder_client = clients.create_cinder_client(context)
datastore_cfg = CONF.get(datastore_version.manager)
volume_support = datastore_cfg.volume_support
call_args = { call_args = {
'name': name, 'name': name,
@ -1070,7 +1072,10 @@ class Instance(BuiltInstance):
'image_id': image_id, 'image_id': image_id,
'availability_zone': availability_zone, 'availability_zone': availability_zone,
'region_name': region_name, '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 # All nova flavors are permitted for a datastore-version unless one
# or more entries are found in datastore_version_metadata, # or more entries are found in datastore_version_metadata,
@ -1086,14 +1091,16 @@ class Instance(BuiltInstance):
datastore=datastore.name, datastore=datastore.name,
datastore_version=datastore_version.name, datastore_version=datastore_version.name,
flavor_id=flavor_id) flavor_id=flavor_id)
datastore_cfg = CONF.get(datastore_version.manager)
client = clients.create_nova_client(context)
try: try:
flavor = client.flavors.get(flavor_id) flavor = nova_client.flavors.get(flavor_id)
except nova_exceptions.NotFound: except nova_exceptions.NotFound:
raise exception.FlavorNotFound(uuid=flavor_id) 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 # If a different region is specified for the instance, ensure
# that the flavor and image are the same in both regions # that the flavor and image are the same in both regions
if region_name and region_name != CONF.service_credentials.region_name: if region_name and region_name != CONF.service_credentials.region_name:
@ -1101,13 +1108,23 @@ class Instance(BuiltInstance):
datastore, datastore_version) datastore, datastore_version)
deltas = {'instances': 1} deltas = {'instances': 1}
volume_support = datastore_cfg.volume_support
if 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, dvm.validate_volume_type(context, volume_type,
datastore.name, datastore_version.name) datastore.name, datastore_version.name)
call_args['volume_size'] = volume_size
validate_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 deltas['volumes'] = volume_size
# Instance volume should have enough space for the backup # Instance volume should have enough space for the backup
# Backup, and volume sizes are in GBs # Backup, and volume sizes are in GBs
@ -1147,60 +1164,36 @@ class Instance(BuiltInstance):
datastore2=datastore.name) datastore2=datastore.name)
if slave_of_id: 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_of'] = slave_of_id
call_args['replica_count'] = replica_count call_args['replica_count'] = replica_count
replication_support = datastore_cfg.replication_strategy replication_support = datastore_cfg.replication_strategy
if not replication_support: if not replication_support:
raise exception.ReplicationNotSupported( raise exception.ReplicationNotSupported(
datastore=datastore.name) datastore=datastore.name)
try: if (CONF.verify_replica_volume_size
# looking for replica source and replica_source.volume_size > volume_size):
replica_source = DBInstance.find_by( 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, context,
id=slave_of_id, instance_id=slave_of_id))
deleted=False) replica_source_instance.validate_can_perform_action()
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(
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})
multi_replica = slave_of_id and replica_count and replica_count > 1 multi_replica = slave_of_id and replica_count and replica_count > 1
instance_count = replica_count if multi_replica else 1 instance_count = replica_count if multi_replica else 1
if locality:
call_args['locality'] = locality
if not nics: if not nics:
nics = [] nics = []
@ -1211,8 +1204,6 @@ class Instance(BuiltInstance):
for net_id in CONF.management_networks] for net_id in CONF.management_networks]
if nics: if nics:
call_args['nics'] = nics call_args['nics'] = nics
if cluster_config:
call_args['cluster_id'] = cluster_config.get("id", None)
if not modules: if not modules:
modules = [] modules = []
@ -1228,7 +1219,6 @@ class Instance(BuiltInstance):
module_list = module_views.convert_modules_to_list(modules) module_list = module_views.convert_modules_to_list(modules)
def _create_resources(): def _create_resources():
if cluster_config: if cluster_config:
cluster_id = cluster_config.get("id", None) cluster_id = cluster_config.get("id", None)
shard_id = cluster_config.get("shard_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, slave_of_id=slave_of_id, cluster_id=cluster_id,
shard_id=shard_id, type=instance_type, shard_id=shard_id, type=instance_type,
region_id=region_name) 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 instance_id = db_info.id
cls.add_instance_modules(context, instance_id, modules)
instance_name = name instance_name = name
LOG.debug(f"Creating new instance {instance_id}")
ids.append(instance_id) ids.append(instance_id)
names.append(instance_name) names.append(instance_name)
root_passwords.append(None) root_passwords.append(None)
cls.add_instance_modules(context, instance_id, modules)
# change the name to be name + replica_number if more than one # change the name to be name + replica_number if more than one
if multi_replica: if multi_replica:
replica_number = instance_index + 1 replica_number = instance_index + 1
@ -1272,9 +1260,9 @@ class Instance(BuiltInstance):
# if a configuration group is associated with an instance, # if a configuration group is associated with an instance,
# generate an overrides dict to pass into the instance creation # generate an overrides dict to pass into the instance creation
# method # method
config = Configuration(context, configuration_id) config = Configuration(context, configuration_id)
overrides = config.get_configuration_overrides() overrides = config.get_configuration_overrides()
service_status = InstanceServiceStatus.create( service_status = InstanceServiceStatus.create(
instance_id=instance_id, instance_id=instance_id,
status=srvstatus.ServiceStatuses.NEW) status=srvstatus.ServiceStatuses.NEW)

View File

@ -32,7 +32,7 @@ from trove.common import pagination
from trove.common import policy from trove.common import policy
from trove.common import utils from trove.common import utils
from trove.common import wsgi 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_users
from trove.extensions.mysql.common import populate_validated_databases from trove.extensions.mysql.common import populate_validated_databases
from trove.instance import models, views from trove.instance import models, views
@ -341,24 +341,81 @@ class InstanceController(wsgi.Controller):
raise exception.NetworkConflict() raise exception.NetworkConflict()
def create(self, req, body, tenant_id): def create(self, req, body, tenant_id):
# TODO(hub-cap): turn this into middleware
LOG.info("Creating a database instance for tenant '%s'", LOG.info("Creating a database instance for tenant '%s'",
tenant_id) tenant_id)
LOG.debug("req : '%s'\n\n", strutils.mask_password(req)) LOG.debug("req : '%s'\n\n", strutils.mask_password(req))
LOG.debug("body : '%s'\n\n", strutils.mask_password(body)) LOG.debug("body : '%s'\n\n", strutils.mask_password(body))
context = req.environ[wsgi.CONTEXT_KEY] context = req.environ[wsgi.CONTEXT_KEY]
policy.authorize_on_tenant(context, 'instance:create') policy.authorize_on_tenant(context, 'instance:create')
context.notification = notification.DBaaSInstanceCreate(context, context.notification = notification.DBaaSInstanceCreate(
request=req) 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
name = body['instance']['name'] 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) 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( databases = populate_validated_databases(
body['instance'].get('databases', [])) body['instance'].get('databases', []))
database_names = [database.get('_name', '') for database in databases] database_names = [database.get('_name', '') for database in databases]
@ -368,7 +425,10 @@ class InstanceController(wsgi.Controller):
database_names) database_names)
except ValueError as ve: except ValueError as ve:
raise exception.BadRequest(message=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') modules = body['instance'].get('modules')
# The following operations have their own API calls. # The following operations have their own API calls.
@ -388,34 +448,22 @@ class InstanceController(wsgi.Controller):
policy.authorize_on_tenant( policy.authorize_on_tenant(
context, 'instance:extension:database:create') 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']: if 'restorePoint' in body['instance']:
backupRef = body['instance']['restorePoint']['backupRef'] backupRef = body['instance']['restorePoint']['backupRef']
backup_id = utils.get_id_from_href(backupRef) backup_id = utils.get_id_from_href(backupRef)
else: else:
backup_id = None backup_id = None
availability_zone = body['instance'].get('availability_zone')
# Only 1 nic is allowed as defined in API jsonschema. # Only 1 nic is allowed as defined in API jsonschema.
# Use list here just for backward compatibility. # Use list just for backward compatibility.
nics = body['instance'].get('nics', [])
if len(nics) > 0: if len(nics) > 0:
LOG.info('Checking user provided instance network %s', nics[0]) nic = nics[0]
self._check_nic(context, 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: if locality:
locality_domain = ['affinity', 'anti-affinity'] locality_domain = ['affinity', 'anti-affinity']
locality_domain_msg = ("Invalid locality '%s'. " locality_domain_msg = ("Invalid locality '%s'. "
@ -424,16 +472,6 @@ class InstanceController(wsgi.Controller):
"', '".join(locality_domain))) "', '".join(locality_domain)))
if locality not in locality_domain: if locality not in locality_domain:
raise exception.BadRequest(message=locality_domain_msg) 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, instance = models.Instance.create(context, name, flavor_id,
image_id, databases, users, image_id, databases, users,
@ -480,7 +518,7 @@ class InstanceController(wsgi.Controller):
with StartNotification(context, instance_id=instance.id): with StartNotification(context, instance_id=instance.id):
instance.detach_configuration() instance.detach_configuration()
if 'datastore_version' in kwargs: if 'datastore_version' in kwargs:
datastore_version = datastore_models.DatastoreVersion.load( datastore_version = ds_models.DatastoreVersion.load(
instance.datastore, kwargs['datastore_version']) instance.datastore, kwargs['datastore_version'])
context.notification = ( context.notification = (
notification.DBaaSInstanceUpgrade(context, request=req)) notification.DBaaSInstanceUpgrade(context, request=req))

View File

@ -359,8 +359,8 @@ class Manager(periodic_task.PeriodicTasks):
try: try:
for replica_index in range(0, len(ids)): for replica_index in range(0, len(ids)):
replica_number += 1 replica_number += 1
LOG.info("Creating replica %(num)d of %(count)d.", LOG.info(f"Creating replica {replica_number} "
{'num': replica_number, 'count': len(ids)}) f"({ids[replica_index]}) of {len(ids)}.")
instance_tasks = FreshInstanceTasks.load( instance_tasks = FreshInstanceTasks.load(
context, ids[replica_index]) context, ids[replica_index])

View File

@ -108,10 +108,6 @@ def instance_is_active(id):
def create_slave(): def create_slave():
result = instance_info.dbaas.instances.create( result = instance_info.dbaas.instances.create(
instance_info.name + "_slave", 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, nics=instance_info.nics,
replica_of=instance_info.id) replica_of=instance_info.id)
assert_equal(200, instance_info.dbaas.last_http_code) assert_equal(200, instance_info.dbaas.last_http_code)
@ -141,20 +137,6 @@ def validate_master(master, slaves):
groups=[tests.DBAAS_API_REPLICATION], groups=[tests.DBAAS_API_REPLICATION],
enabled=CONFIG.swift_enabled) enabled=CONFIG.swift_enabled)
class CreateReplicationSlave(object): 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 @test
def test_create_db_on_master(self): def test_create_db_on_master(self):
"""test_create_db_on_master""" """test_create_db_on_master"""

View File

@ -88,10 +88,7 @@ class ReplicationRunner(TestRunner):
client = self.auth_client client = self.auth_client
client.instances.create( client.instances.create(
self.instance_info.name + '_' + replica_name, self.instance_info.name + '_' + replica_name,
self.instance_info.dbaas_flavor_href, replica_of=master_id,
self.instance_info.volume, replica_of=master_id,
datastore=self.instance_info.dbaas_datastore,
datastore_version=self.instance_info.dbaas_datastore_version,
nics=self.instance_info.nics, nics=self.instance_info.nics,
replica_count=replica_count) replica_count=replica_count)
self.assert_client_code(client, expected_http_code) self.assert_client_code(client, expected_http_code)
@ -154,10 +151,6 @@ class ReplicationRunner(TestRunner):
client = self.auth_client client = self.auth_client
self.non_affinity_repl_id = client.instances.create( self.non_affinity_repl_id = client.instances.create(
self.instance_info.name + '_non-affinity-repl', 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, nics=self.instance_info.nics,
replica_of=self.non_affinity_master_id, replica_of=self.non_affinity_master_id,
replica_count=1).id replica_count=1).id

View File

@ -343,6 +343,7 @@ class TestReplication(trove_testtools.TestCase):
id=str(uuid.uuid4()), id=str(uuid.uuid4()),
name="TestMasterInstance", name="TestMasterInstance",
datastore_version_id=self.datastore_version.id, datastore_version_id=self.datastore_version.id,
flavor_id=str(uuid.uuid4()),
volume_size=2) volume_size=2)
self.master.set_task_status(InstanceTasks.NONE) self.master.set_task_status(InstanceTasks.NONE)
self.master.save() self.master.save()
@ -370,18 +371,6 @@ class TestReplication(trove_testtools.TestCase):
clients.create_nova_client = self.safe_nova_client clients.create_nova_client = self.safe_nova_client
super(TestReplication, self).tearDown() 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') @patch('trove.instance.models.LOG')
def test_replica_with_invalid_slave_of_id(self, mock_logging): def test_replica_with_invalid_slave_of_id(self, mock_logging):
self.assertRaises(exception.NotFound, self.assertRaises(exception.NotFound,
@ -390,45 +379,6 @@ class TestReplication(trove_testtools.TestCase):
self.datastore_version, 2, self.datastore_version, 2,
None, slave_of_id=str(uuid.uuid4())) 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): def trivial_key_function(id):
return id * id return id * id