Replica report DETACH status when detaching

Detached replicas currently stay in the ACTIVE state
while detaching although the instance may not be available
throughout the whole process.

Set the task status to DETACHING until the guest finishes.

Update the scenario tests to expect this new instance state.
Also improve the assertion to provide more granular feedback.

Change-Id: Id43f382fa655bfa48feeb2521d7237d926883514
Closes-Bug: 1465600
This commit is contained in:
Petr Malik
2016-05-19 16:48:28 -04:00
parent 26d95f7601
commit 9e8020ef76
5 changed files with 56 additions and 17 deletions
+6
View File
@@ -99,6 +99,7 @@ class InstanceStatus(object):
RESTART_REQUIRED = "RESTART_REQUIRED"
PROMOTE = "PROMOTE"
EJECT = "EJECT"
DETACH = "DETACH"
def validate_volume_size(size):
@@ -301,6 +302,8 @@ class SimpleInstance(object):
return InstanceStatus.EJECT
if InstanceTasks.LOGGING.action == action:
return InstanceStatus.LOGGING
if InstanceTasks.DETACHING.action == action:
return InstanceStatus.DETACH
# Check for server status.
if self.db_info.server_status in ["BUILD", "ERROR", "REBOOT",
@@ -1002,6 +1005,9 @@ class Instance(BuiltInstance):
if not self.slave_of_id:
raise exception.BadRequest(_("Instance %s is not a replica.")
% self.id)
self.update_db(task_status=InstanceTasks.DETACHING)
task_api.API(self.context).detach_replica(self.id)
def promote_to_replica_source(self):
+2
View File
@@ -80,6 +80,8 @@ class InstanceTasks(object):
EJECTING = InstanceTask(0x09, 'EJECTING',
'Ejecting the replica source.')
LOGGING = InstanceTask(0x0a, 'LOGGING', 'Transferring guest logs.')
DETACHING = InstanceTask(0x0b, 'DETACHING',
'Detaching the instance from replica source.')
BUILDING_ERROR_DNS = InstanceTask(0x50, 'BUILDING', 'Build error: DNS.',
is_error=True)
+3
View File
@@ -1178,6 +1178,9 @@ class BuiltInstanceTasks(BuiltInstance, NotifyMixin, ConfigurationMixin):
except (GuestError, GuestTimeout):
LOG.exception(_("Failed to detach replica %s.") % self.id)
raise
finally:
if not for_failover:
self.reset_task_status()
def attach_replica(self, master):
LOG.debug("Calling attach_replica on %s" % self.id)
@@ -220,7 +220,7 @@ class ReplicationRunner(TestRunner):
self.test_helper.remove_data(data_set, host)
def run_detach_replica_from_source(self,
expected_states=['ACTIVE'],
expected_states=['DETACH', 'ACTIVE'],
expected_http_code=202):
self.assert_detach_replica_from_source(
self.instance_info.id, self.replica_1_id,
@@ -236,7 +236,7 @@ class ReplicationRunner(TestRunner):
replica_id, expected_states, expected_http_code)
self._assert_is_master(master_id, other_replica_ids)
self._assert_is_not_replica(replica_id, master_id)
self._assert_is_not_replica(replica_id)
def assert_detach_replica(
self, replica_id, expected_states, expected_http_code):
@@ -245,13 +245,19 @@ class ReplicationRunner(TestRunner):
self.assert_instance_action(
replica_id, expected_states, expected_http_code)
def _assert_is_not_replica(self, instance_id, master_id):
try:
self._assert_is_replica(instance_id, master_id)
self.fail("Non-replica '%s' is still replica of '%s'" %
(instance_id, master_id))
except AssertionError:
pass
def _assert_is_not_replica(self, instance_id):
instance = self.get_instance(instance_id)
self.assert_client_code(200)
if 'replica_of' not in instance._info:
try:
self._validate_replica(instance_id)
self.fail("The instance is still configured as a replica "
"after detached: %s" % instance_id)
except AssertionError:
pass
else:
self.fail("Unexpected replica_of ID.")
def run_delete_detached_replica(self,
expected_last_state=['SHUTDOWN'],
@@ -780,16 +780,38 @@ class BuiltInstanceTasksTest(trove_testtools.TestCase):
@patch.object(BaseInstance, 'update_db')
def test_detach_replica(self, mock_update_db):
self.instance_task.detach_replica(Mock(), True)
self.instance_task._guest.detach_replica.assert_called_with(True)
mock_update_db.assert_called_with(slave_of_id=None)
with patch.object(self.instance_task, 'reset_task_status') as tr_mock:
self.instance_task.detach_replica(Mock(), True)
self.instance_task._guest.detach_replica.assert_called_with(True)
mock_update_db.assert_called_with(slave_of_id=None)
tr_mock.assert_not_called()
with patch.object(self.instance_task, 'reset_task_status') as tr_mock:
self.instance_task.detach_replica(Mock(), False)
self.instance_task._guest.detach_replica.assert_called_with(False)
mock_update_db.assert_called_with(slave_of_id=None)
tr_mock.assert_called_once_with()
@patch.object(BaseInstance, 'update_db')
@patch('trove.taskmanager.models.LOG')
def test_error_detach_replica(self, mock_logging):
with patch.object(self.instance_task._guest, 'detach_replica',
side_effect=GuestError):
self.assertRaises(GuestError, self.instance_task.detach_replica,
Mock(), True)
def test_error_detach_replica(self, mock_logging, mock_update_db):
with patch.object(self.instance_task, 'reset_task_status') as tr_mock:
with patch.object(self.instance_task._guest, 'detach_replica',
side_effect=GuestError):
self.assertRaises(
GuestError, self.instance_task.detach_replica,
Mock(), True)
mock_update_db.assert_not_called()
tr_mock.assert_not_called()
with patch.object(self.instance_task, 'reset_task_status') as tr_mock:
with patch.object(self.instance_task._guest, 'detach_replica',
side_effect=GuestError):
self.assertRaises(
GuestError, self.instance_task.detach_replica,
Mock(), False)
mock_update_db.assert_not_called()
tr_mock.assert_called_once_with()
@patch.object(BaseInstance, 'update_db')
def test_make_read_only(self, mock_update_db):