diff options
author | Matt Van Dijk <mvandijk@tesora.com> | 2015-03-07 02:49:30 -0500 |
---|---|---|
committer | Matthew Van Dijk <mvandijk@tesora.com> | 2015-03-27 14:04:28 +0000 |
commit | 32387f8ffd3ba6e45064d675d2b18cb666397894 (patch) | |
tree | 9925ccc5aaa9dd39bbdf8e2b8624df93b6708bf4 | |
parent | 417719c5a7089ffd1e784a9147ea48500be9129f (diff) | |
download | trove-32387f8ffd3ba6e45064d675d2b18cb666397894.tar.gz |
Fix replica source state validation
Creating a replica did not check that the specified source was available.
It is necessary that the source's state is active and that it is not busy.
This is done by verifying the state and status of the master.
The new checks will throw a HTTP 422 UnprocessableEntity in cases when the
status is not ACTIVE or source is busy performing a task
Changes:
- adding validation step that checks if master exists and in proper state.
- added appropriate API and unit tests
Co-authored by: Denis Makogon <dmakogon@mirantis.com>
DocImpact
Change-Id: Ib4e37ff0036998bc53058e400054ed93fcc2e144
Closes-Bug: #1357704
-rw-r--r-- | trove/instance/models.py | 12 | ||||
-rw-r--r-- | trove/tests/api/replication.py | 10 | ||||
-rw-r--r-- | trove/tests/unittests/instance/test_instance_models.py | 85 |
3 files changed, 85 insertions, 22 deletions
diff --git a/trove/instance/models.py b/trove/instance/models.py index 3c436102..367e7b67 100644 --- a/trove/instance/models.py +++ b/trove/instance/models.py @@ -718,6 +718,18 @@ class Instance(BuiltInstance): raise exception.Forbidden( _("Cannot create a replica of a replica %(id)s.") % {'id': slave_of_id}) + # 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 " diff --git a/trove/tests/api/replication.py b/trove/tests/api/replication.py index 0047c248..7e685e3d 100644 --- a/trove/tests/api/replication.py +++ b/trove/tests/api/replication.py @@ -68,6 +68,16 @@ def slave_is_running(running=True): 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, + slave_of="Missing replica source") + assert_equal(404, instance_info.dbaas.last_http_code) + + @test def test_create_db_on_master(self): databases = [{'name': existing_db_on_master}] # Ensure that the auth_token in the dbaas client is not stale diff --git a/trove/tests/unittests/instance/test_instance_models.py b/trove/tests/unittests/instance/test_instance_models.py index 8c80dd7c..1183afe9 100644 --- a/trove/tests/unittests/instance/test_instance_models.py +++ b/trove/tests/unittests/instance/test_instance_models.py @@ -18,7 +18,6 @@ from trove.common import cfg from trove.common import exception from trove.backup import models as backup_models from trove.datastore import models as datastore_models -from trove.datastore.models import DBDatastoreVersion from trove.common.instance import ServiceStatuses from trove.instance.models import filter_ips from trove.instance.models import InstanceServiceStatus @@ -216,35 +215,77 @@ class TestReplication(TestCase): def setUp(self): util.init_db() - self.replica_datastore_version = Mock(spec=DBDatastoreVersion) - self.replica_datastore_version.id = "UUID" - self.replica_datastore_version.manager = 'mysql' - self.root_info = DBInstance( - InstanceTasks.NONE, - id="Another_instance", - name="TestInstance", - datastore_version_id=self.replica_datastore_version.id) - self.root_info.save() - self.replica_info = DBInstance( + + self.datastore = datastore_models.DBDatastore.create( + id=str(uuid.uuid4()), + name='name', + default_version_id=str(uuid.uuid4())) + + self.datastore_version = datastore_models.DBDatastoreVersion.create( + id=self.datastore.default_version_id, + name='name', + image_id=str(uuid.uuid4()), + packages=str(uuid.uuid4()), + datastore_id=self.datastore.id, + manager='mysql', + active=1) + + self.master = DBInstance( InstanceTasks.NONE, - id="UUID", - name="TestInstance", - datastore_version_id=self.replica_datastore_version.id, - slave_of_id="Another_instance") - self.replica_info.save() - self.safe_nova = models.create_nova_client - models.create_nova_client = nova.fake_create_nova_client + id=str(uuid.uuid4()), + name="TestMasterInstance", + datastore_version_id=self.datastore_version.id) + self.master.set_task_status(InstanceTasks.NONE) + self.master.save() + self.master_status = InstanceServiceStatus( + ServiceStatuses.RUNNING, + id=str(uuid.uuid4()), + instance_id=self.master.id) + self.master_status.save() + self.safe_nova_client = models.create_nova_client + models.create_nova_client = nova.fake_create_nova_client super(TestReplication, self).setUp() def tearDown(self): - models.create_nova_client = self.safe_nova - self.replica_info.delete() - self.root_info.delete() + self.master.delete() + self.master_status.delete() + self.datastore.delete() + self.datastore_version.delete() + models.create_nova_client = self.safe_nova_client super(TestReplication, self).tearDown() + def test_replica_of_not_active_master(self): + 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", [], [], None, + self.datastore_version, 1, + None, slave_of_id=self.master.id) + + def test_replica_with_invalid_slave_of_id(self): + self.assertRaises(exception.NotFound, + Instance.create, + None, 'name', 1, "UUID", [], [], None, + self.datastore_version, 1, + 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", [], [], None, - self.replica_datastore_version, 1, + self.datastore_version, 1, None, slave_of_id=self.replica_info.id) |