322 lines
15 KiB
Python
322 lines
15 KiB
Python
# Copyright 2014 eBay Software Foundation
|
|
# Copyright [2015] Hewlett-Packard Development Company, L.P.
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
from unittest.mock import MagicMock, Mock, patch, PropertyMock
|
|
from proboscis.asserts import assert_equal
|
|
|
|
from trove.backup.models import Backup
|
|
from trove.common.exception import TroveError, ReplicationSlaveAttachError
|
|
from trove.common import server_group as srv_grp
|
|
from trove.instance.tasks import InstanceTasks
|
|
from trove.taskmanager.manager import Manager
|
|
from trove.taskmanager import models
|
|
from trove.taskmanager import service
|
|
from trove.tests.unittests import trove_testtools
|
|
|
|
|
|
class TestManager(trove_testtools.TestCase):
|
|
|
|
def setUp(self):
|
|
super(TestManager, self).setUp()
|
|
self.manager = Manager()
|
|
self.context = trove_testtools.TroveTestContext(self)
|
|
self.mock_slave1 = Mock()
|
|
self.mock_slave2 = Mock()
|
|
type(self.mock_slave1).id = PropertyMock(return_value='some-inst-id')
|
|
type(self.mock_slave2).id = PropertyMock(return_value='inst1')
|
|
|
|
self.mock_old_master = Mock()
|
|
type(self.mock_old_master).slaves = PropertyMock(
|
|
return_value=[self.mock_slave1, self.mock_slave2])
|
|
self.mock_master = Mock()
|
|
type(self.mock_master).slaves = PropertyMock(
|
|
return_value=[self.mock_slave1, self.mock_slave2])
|
|
|
|
def tearDown(self):
|
|
super(TestManager, self).tearDown()
|
|
self.manager = None
|
|
|
|
def test_getattr_lookup(self):
|
|
self.assertTrue(callable(self.manager.delete_cluster))
|
|
self.assertTrue(callable(self.manager.mongodb_add_shard_cluster))
|
|
|
|
def test_most_current_replica(self):
|
|
master = Mock()
|
|
master.id = 32
|
|
|
|
def test_case(txn_list, selected_master):
|
|
with patch.object(self.manager, '_get_replica_txns',
|
|
return_value=txn_list):
|
|
result = self.manager._most_current_replica(master, None)
|
|
assert_equal(result, selected_master)
|
|
|
|
with self.assertRaisesRegex(TroveError,
|
|
'not all replicating from same'):
|
|
test_case([['a', '2a99e-32bf', 2], ['b', '2a', 1]], None)
|
|
|
|
test_case([['a', '2a99e-32bf', 2]], 'a')
|
|
test_case([['a', '2a', 1], ['b', '2a', 2]], 'b')
|
|
test_case([['a', '2a', 2], ['b', '2a', 1]], 'a')
|
|
test_case([['a', '2a', 1], ['b', '2a', 1]], 'a')
|
|
test_case([['a', None, 0]], 'a')
|
|
test_case([['a', None, 0], ['b', '2a', 1]], 'b')
|
|
|
|
def test_detach_replica(self):
|
|
slave = Mock()
|
|
master = Mock()
|
|
with patch.object(models.BuiltInstanceTasks, 'load',
|
|
side_effect=[slave, master]):
|
|
self.manager.detach_replica(self.context, 'some-inst-id')
|
|
slave.detach_replica.assert_called_with(master)
|
|
|
|
@patch.object(Manager, '_set_task_status')
|
|
def test_promote_to_replica_source(self, mock_set_task_status):
|
|
with patch.object(models.BuiltInstanceTasks, 'load',
|
|
side_effect=[self.mock_slave1,
|
|
self.mock_old_master,
|
|
self.mock_slave2]):
|
|
self.manager.promote_to_replica_source(
|
|
self.context, 'some-inst-id')
|
|
|
|
self.mock_slave1.detach_replica.assert_called_with(
|
|
self.mock_old_master, for_failover=True)
|
|
self.mock_old_master.attach_replica.assert_called_with(
|
|
self.mock_slave1, restart=False)
|
|
self.mock_slave1.make_read_only.assert_called_with(False)
|
|
|
|
self.mock_slave2.detach_replica.assert_called_with(
|
|
self.mock_old_master, for_failover=True)
|
|
self.mock_slave2.attach_replica.assert_called_with(self.mock_slave1,
|
|
restart=True)
|
|
|
|
self.mock_old_master.demote_replication_master.assert_any_call()
|
|
|
|
mock_set_task_status.assert_called_with(([self.mock_old_master] +
|
|
[self.mock_slave1,
|
|
self.mock_slave2]),
|
|
InstanceTasks.NONE)
|
|
|
|
@patch.object(Manager, '_set_task_status')
|
|
@patch.object(Manager, '_most_current_replica')
|
|
def test_eject_replica_source(self, mock_most_current_replica,
|
|
mock_set_task_status):
|
|
with patch.object(models.BuiltInstanceTasks, 'load',
|
|
side_effect=[self.mock_master, self.mock_slave1,
|
|
self.mock_slave2]):
|
|
self.manager.eject_replica_source(self.context, 'some-inst-id')
|
|
mock_most_current_replica.assert_called_with(self.mock_master,
|
|
[self.mock_slave1,
|
|
self.mock_slave2])
|
|
mock_set_task_status.assert_called_with(([self.mock_master] +
|
|
[self.mock_slave1,
|
|
self.mock_slave2]),
|
|
InstanceTasks.NONE)
|
|
|
|
@patch.object(Manager, '_set_task_status')
|
|
@patch('trove.taskmanager.manager.LOG')
|
|
def test_exception_TroveError_promote_to_replica_source(self, *args):
|
|
self.mock_slave2.detach_replica = Mock(side_effect=TroveError)
|
|
with patch.object(models.BuiltInstanceTasks, 'load',
|
|
side_effect=[self.mock_slave1, self.mock_old_master,
|
|
self.mock_slave2]):
|
|
self.assertRaises(ReplicationSlaveAttachError,
|
|
self.manager.promote_to_replica_source,
|
|
self.context, 'some-inst-id')
|
|
|
|
@patch.object(Manager, '_set_task_status')
|
|
@patch.object(Manager, '_most_current_replica')
|
|
@patch('trove.taskmanager.manager.LOG')
|
|
def test_exception_TroveError_eject_replica_source(
|
|
self, mock_logging, mock_most_current_replica,
|
|
mock_set_task_status):
|
|
self.mock_slave2.detach_replica = Mock(side_effect=TroveError)
|
|
mock_most_current_replica.return_value = self.mock_slave1
|
|
with patch.object(models.BuiltInstanceTasks, 'load',
|
|
side_effect=[self.mock_master, self.mock_slave1,
|
|
self.mock_slave2]):
|
|
self.assertRaises(ReplicationSlaveAttachError,
|
|
self.manager.eject_replica_source,
|
|
self.context, 'some-inst-id')
|
|
|
|
@patch.object(Manager, '_set_task_status')
|
|
def test_error_promote_to_replica_source(self, *args):
|
|
self.mock_slave2.detach_replica = Mock(
|
|
side_effect=RuntimeError('Error'))
|
|
|
|
with patch.object(models.BuiltInstanceTasks, 'load',
|
|
side_effect=[self.mock_slave1, self.mock_old_master,
|
|
self.mock_slave2]):
|
|
self.assertRaisesRegex(RuntimeError, 'Error',
|
|
self.manager.promote_to_replica_source,
|
|
self.context, 'some-inst-id')
|
|
|
|
@patch('trove.taskmanager.manager.LOG')
|
|
def test_error_demote_replication_master_promote_to_replica_source(
|
|
self, mock_logging):
|
|
self.mock_old_master.demote_replication_master = Mock(
|
|
side_effect=RuntimeError('Error'))
|
|
|
|
with patch.object(models.BuiltInstanceTasks, 'load',
|
|
side_effect=[self.mock_slave1, self.mock_old_master,
|
|
self.mock_slave2]):
|
|
self.assertRaises(ReplicationSlaveAttachError,
|
|
self.manager.promote_to_replica_source,
|
|
self.context, 'some-inst-id')
|
|
|
|
@patch.object(Manager, '_set_task_status')
|
|
@patch.object(Manager, '_most_current_replica')
|
|
def test_error_eject_replica_source(self, mock_most_current_replica,
|
|
mock_set_task_status):
|
|
self.mock_slave2.detach_replica = Mock(
|
|
side_effect=RuntimeError('Error'))
|
|
mock_most_current_replica.return_value = self.mock_slave1
|
|
with patch.object(models.BuiltInstanceTasks, 'load',
|
|
side_effect=[self.mock_master, self.mock_slave1,
|
|
self.mock_slave2]):
|
|
self.assertRaisesRegex(RuntimeError, 'Error',
|
|
self.manager.eject_replica_source,
|
|
self.context, 'some-inst-id')
|
|
|
|
@patch.object(Backup, 'delete')
|
|
@patch.object(models.BuiltInstanceTasks, 'load')
|
|
def test_create_replication_slave(self, mock_load, mock_backup_delete):
|
|
mock_tasks = Mock()
|
|
mock_snapshot = {'dataset': {'snapshot_id': 'test-id'}}
|
|
mock_tasks.get_replication_master_snapshot = Mock(
|
|
return_value=mock_snapshot)
|
|
mock_flavor = Mock()
|
|
with patch.object(models.FreshInstanceTasks, 'load',
|
|
return_value=mock_tasks):
|
|
self.manager.create_instance(self.context, ['id1'], Mock(),
|
|
mock_flavor, Mock(), None, None,
|
|
'mysql', 'mysql-server', 2,
|
|
'temp-backup-id', None,
|
|
'some_password', None, Mock(),
|
|
'some-master-id', None, None,
|
|
None, None)
|
|
mock_tasks.get_replication_master_snapshot.assert_called_with(
|
|
self.context, 'some-master-id', mock_flavor,
|
|
parent_backup_id='temp-backup-id')
|
|
mock_backup_delete.assert_called_with(self.context, 'test-id')
|
|
|
|
@patch.object(models.FreshInstanceTasks, 'load')
|
|
@patch.object(Backup, 'delete')
|
|
@patch.object(models.BuiltInstanceTasks, 'load')
|
|
@patch('trove.taskmanager.manager.LOG')
|
|
def test_exception_create_replication_slave(self, mock_logging, mock_tasks,
|
|
mock_delete, mock_load):
|
|
mock_load.return_value.create_instance = Mock(side_effect=TroveError)
|
|
self.assertRaises(TroveError, self.manager.create_instance,
|
|
self.context, ['id1', 'id2'], Mock(), Mock(),
|
|
Mock(), None, None, 'mysql', 'mysql-server', 2,
|
|
'temp-backup-id', None, 'some_password', None,
|
|
Mock(), 'some-master-id', None, None, None, None)
|
|
|
|
def test_AttributeError_create_instance(self):
|
|
self.assertRaisesRegex(
|
|
AttributeError, 'Cannot create multiple non-replica instances.',
|
|
self.manager.create_instance, self.context, ['id1', 'id2'],
|
|
Mock(), Mock(), Mock(), None, None, 'mysql', 'mysql-server', 2,
|
|
'temp-backup-id', None, 'some_password', None, Mock(), None, None,
|
|
None, None, None)
|
|
|
|
def test_create_instance(self):
|
|
mock_tasks = Mock()
|
|
mock_flavor = Mock()
|
|
mock_override = Mock()
|
|
mock_csg = Mock()
|
|
type(mock_csg.return_value).id = PropertyMock(
|
|
return_value='sg-id')
|
|
with patch.object(models.FreshInstanceTasks, 'load',
|
|
return_value=mock_tasks):
|
|
with patch.object(srv_grp.ServerGroup, 'create', mock_csg):
|
|
self.manager.create_instance(
|
|
self.context, 'id1', 'inst1', mock_flavor,
|
|
'mysql-image-id', None, None, 'mysql', 'mysql-server', 2,
|
|
'temp-backup-id', None, 'password', None, mock_override,
|
|
None, None, None, None, 'affinity')
|
|
|
|
mock_tasks.create_instance.assert_called_with(
|
|
mock_flavor,
|
|
'mysql-image-id', None,
|
|
None, 'mysql',
|
|
'mysql-server', 2,
|
|
'temp-backup-id', None,
|
|
'password', None,
|
|
mock_override,
|
|
None, None, None, None,
|
|
{'group': 'sg-id'},
|
|
access=None, ds_version=None)
|
|
mock_tasks.wait_for_instance.assert_called_with(3600, mock_flavor)
|
|
|
|
def test_create_cluster(self):
|
|
mock_tasks = Mock()
|
|
with patch.object(models, 'load_cluster_tasks',
|
|
return_value=mock_tasks):
|
|
self.manager.create_cluster(self.context, 'some-cluster-id')
|
|
mock_tasks.create_cluster.assert_called_with(self.context,
|
|
'some-cluster-id')
|
|
|
|
def test_delete_cluster(self):
|
|
mock_tasks = Mock()
|
|
with patch.object(models, 'load_cluster_tasks',
|
|
return_value=mock_tasks):
|
|
self.manager.delete_cluster(self.context, 'some-cluster-id')
|
|
mock_tasks.delete_cluster.assert_called_with(self.context,
|
|
'some-cluster-id')
|
|
|
|
def test_shrink_cluster_with_success(self):
|
|
self._assert_shrink_cluster(True)
|
|
|
|
def test_shrink_cluster_with_error(self):
|
|
self._assert_shrink_cluster(False)
|
|
|
|
@patch('trove.taskmanager.manager.EndNotification')
|
|
@patch('trove.taskmanager.manager.models.load_cluster_tasks')
|
|
def _assert_shrink_cluster(self, success, mock_load, mock_notification):
|
|
if success:
|
|
mock_load.side_effect = Mock()
|
|
else:
|
|
mock_load.side_effect = Exception
|
|
|
|
end_notification = MagicMock()
|
|
mock_notification.return_value = end_notification
|
|
context = Mock()
|
|
cluster_id = Mock()
|
|
instance_ids = Mock()
|
|
|
|
try:
|
|
self.manager.shrink_cluster(context, cluster_id, instance_ids)
|
|
self.assertTrue(success)
|
|
except Exception:
|
|
self.assertFalse(success)
|
|
|
|
mock_load.assert_called_once_with(context, cluster_id)
|
|
mock_notification.assert_called_once_with(context,
|
|
cluster_id=cluster_id,
|
|
instance_ids=instance_ids)
|
|
exit_error_type = end_notification.__exit__.call_args_list[0][0][0]
|
|
if success:
|
|
self.assertFalse(exit_error_type)
|
|
else:
|
|
self.assertTrue(exit_error_type)
|
|
|
|
|
|
class TestTaskManagerService(trove_testtools.TestCase):
|
|
def test_app_factory(self):
|
|
test_service = service.app_factory(Mock())
|
|
self.assertIsInstance(test_service, service.TaskService)
|