diff --git a/doc/source/user/backup-db.rst b/doc/source/user/backup-db.rst index 35bb87821e..9a1a10e5b5 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 d43ee65021..b8446b84e4 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 6ec3c16b4a..6528ea87bb 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 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 `` -#. **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 `` \ No newline at end of file diff --git a/doc/source/user/upgrade-datastore.rst b/doc/source/user/upgrade-datastore.rst index 3947b37cd0..4fb8dcccb5 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 0e3d60b1cd..0388faceb1 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 47b4c72c3f..daa8d3bfde 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( + 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) diff --git a/trove/instance/service.py b/trove/instance/service.py index 17de296ed0..8542b024a1 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 bbec7d0b63..01eaa741c7 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 4dd493074b..0d4e341aee 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 a9115fed1e..2d862ebdd2 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 f63314b507..10befc8cac 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() @@ -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