Using same config with primary for replicas
Change-Id: Icadc95ea54e4509dc148f8e84f2eaac5840509f3
This commit is contained in:
parent
39b0df0a6b
commit
efb6a811be
@ -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 |
|
||||
+-------------------+----------------------------------------------+
|
||||
|
@ -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 \
|
||||
|
@ -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 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
|
||||
|
||||
$ openstack database instance list
|
||||
+-----------+------------+-----------+-------------------+--------+-----------+------+
|
||||
| id | name | datastore | datastore_version | status | flavor_id | size |
|
||||
+-----------+------------+-----------+-------------------+--------+-----------+------+
|
||||
| 97b...ae6 | base_1 | mysql | mysql-5.5 | ACTIVE | 10 | 2 |
|
||||
+-----------+------------+-----------+-------------------+--------+-----------+------+
|
||||
+--------------------------------------+----------------------+-----------+-------------------+---------+------------------------------------------------+-----------+------+-----------+---------+
|
||||
| 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 |
|
||||
+--------------------------------------+----------------------+-----------+-------------------+---------+------------------------------------------------+-----------+------+-----------+---------+
|
||||
|
||||
#. **Create the replica**
|
||||
#. Point your application to the (former) replica.
|
||||
|
||||
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``:
|
||||
Each server has a unique connection string. Update your application to point
|
||||
to the (former) replica instead of the primary.
|
||||
|
||||
.. code-block:: console
|
||||
Other supported operations
|
||||
--------------------------
|
||||
|
||||
$ 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
|
||||
* 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>``
|
||||
|
||||
#. **Verify replication status**
|
||||
|
||||
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``.
|
||||
|
||||
.. 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
|
||||
* 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>``
|
@ -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 \
|
||||
|
@ -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",
|
||||
|
@ -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(
|
||||
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,
|
||||
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(
|
||||
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)
|
||||
|
@ -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))
|
||||
|
@ -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])
|
||||
|
@ -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"""
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
@ -370,18 +371,6 @@ class TestReplication(trove_testtools.TestCase):
|
||||
clients.create_nova_client = self.safe_nova_client
|
||||
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,
|
||||
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user