Merge "Add volume migrate action"
This commit is contained in:
249
watcher/tests/applier/actions/test_volume_migration.py
Normal file
249
watcher/tests/applier/actions/test_volume_migration.py
Normal file
@@ -0,0 +1,249 @@
|
||||
#
|
||||
# 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 __future__ import unicode_literals
|
||||
|
||||
import jsonschema
|
||||
import mock
|
||||
|
||||
from watcher.applier.actions import base as baction
|
||||
from watcher.applier.actions import volume_migration
|
||||
from watcher.common import cinder_helper
|
||||
from watcher.common import clients
|
||||
from watcher.common import keystone_helper
|
||||
from watcher.common import nova_helper
|
||||
from watcher.common import utils as w_utils
|
||||
from watcher.tests import base
|
||||
|
||||
|
||||
class TestMigration(base.TestCase):
|
||||
|
||||
VOLUME_UUID = "45a37aeb-95ab-4ddb-a305-7d9f62c2f5ba"
|
||||
INSTANCE_UUID = "45a37aec-85ab-4dda-a303-7d9f62c2f5bb"
|
||||
|
||||
def setUp(self):
|
||||
super(TestMigration, self).setUp()
|
||||
|
||||
self.m_osc_cls = mock.Mock()
|
||||
self.m_osc = mock.Mock(spec=clients.OpenStackClients)
|
||||
self.m_osc_cls.return_value = self.m_osc
|
||||
|
||||
self.m_n_helper_cls = mock.Mock()
|
||||
self.m_n_helper = mock.Mock(spec=nova_helper.NovaHelper)
|
||||
self.m_n_helper_cls.return_value = self.m_n_helper
|
||||
|
||||
self.m_c_helper_cls = mock.Mock()
|
||||
self.m_c_helper = mock.Mock(spec=cinder_helper.CinderHelper)
|
||||
self.m_c_helper_cls.return_value = self.m_c_helper
|
||||
|
||||
self.m_k_helper_cls = mock.Mock()
|
||||
self.m_k_helper = mock.Mock(spec=keystone_helper.KeystoneHelper)
|
||||
self.m_k_helper_cls.return_value = self.m_k_helper
|
||||
|
||||
m_openstack_clients = mock.patch.object(
|
||||
clients, "OpenStackClients", self.m_osc_cls)
|
||||
m_nova_helper = mock.patch.object(
|
||||
nova_helper, "NovaHelper", self.m_n_helper_cls)
|
||||
|
||||
m_cinder_helper = mock.patch.object(
|
||||
cinder_helper, "CinderHelper", self.m_c_helper_cls)
|
||||
|
||||
m_keystone_helper = mock.patch.object(
|
||||
keystone_helper, "KeystoneHelper", self.m_k_helper_cls)
|
||||
|
||||
m_openstack_clients.start()
|
||||
m_nova_helper.start()
|
||||
m_cinder_helper.start()
|
||||
m_keystone_helper.start()
|
||||
|
||||
self.addCleanup(m_keystone_helper.stop)
|
||||
self.addCleanup(m_cinder_helper.stop)
|
||||
self.addCleanup(m_nova_helper.stop)
|
||||
self.addCleanup(m_openstack_clients.stop)
|
||||
|
||||
self.action = volume_migration.VolumeMigrate(mock.Mock())
|
||||
|
||||
self.input_parameters_swap = {
|
||||
"migration_type": "swap",
|
||||
"destination_node": "storage1-poolname",
|
||||
"destination_type": "storage1-typename",
|
||||
baction.BaseAction.RESOURCE_ID: self.VOLUME_UUID,
|
||||
}
|
||||
self.action_swap = volume_migration.VolumeMigrate(mock.Mock())
|
||||
self.action_swap.input_parameters = self.input_parameters_swap
|
||||
|
||||
self.input_parameters_migrate = {
|
||||
"migration_type": "cold",
|
||||
"destination_node": "storage1-poolname",
|
||||
"destination_type": "",
|
||||
baction.BaseAction.RESOURCE_ID: self.VOLUME_UUID,
|
||||
}
|
||||
self.action_migrate = volume_migration.VolumeMigrate(mock.Mock())
|
||||
self.action_migrate.input_parameters = self.input_parameters_migrate
|
||||
|
||||
self.input_parameters_retype = {
|
||||
"migration_type": "cold",
|
||||
"destination_node": "",
|
||||
"destination_type": "storage1-typename",
|
||||
baction.BaseAction.RESOURCE_ID: self.VOLUME_UUID,
|
||||
}
|
||||
self.action_retype = volume_migration.VolumeMigrate(mock.Mock())
|
||||
self.action_retype.input_parameters = self.input_parameters_retype
|
||||
|
||||
@staticmethod
|
||||
def fake_volume(**kwargs):
|
||||
volume = mock.MagicMock()
|
||||
volume.id = kwargs.get('id', TestMigration.VOLUME_UUID)
|
||||
volume.size = kwargs.get('size', '1')
|
||||
volume.status = kwargs.get('status', 'available')
|
||||
volume.snapshot_id = kwargs.get('snapshot_id', None)
|
||||
volume.availability_zone = kwargs.get('availability_zone', 'nova')
|
||||
return volume
|
||||
|
||||
@staticmethod
|
||||
def fake_instance(**kwargs):
|
||||
instance = mock.MagicMock()
|
||||
instance.id = kwargs.get('id', TestMigration.INSTANCE_UUID)
|
||||
instance.status = kwargs.get('status', 'ACTIVE')
|
||||
return instance
|
||||
|
||||
def test_parameters_swap(self):
|
||||
params = {baction.BaseAction.RESOURCE_ID:
|
||||
self.VOLUME_UUID,
|
||||
self.action.MIGRATION_TYPE: 'swap',
|
||||
self.action.DESTINATION_NODE: None,
|
||||
self.action.DESTINATION_TYPE: 'type-1'}
|
||||
self.action_swap.input_parameters = params
|
||||
self.assertTrue(self.action_swap.validate_parameters)
|
||||
|
||||
def test_parameters_migrate(self):
|
||||
params = {baction.BaseAction.RESOURCE_ID:
|
||||
self.VOLUME_UUID,
|
||||
self.action.MIGRATION_TYPE: 'cold',
|
||||
self.action.DESTINATION_NODE: 'node-1',
|
||||
self.action.DESTINATION_TYPE: None}
|
||||
self.action_migrate.input_parameters = params
|
||||
self.assertTrue(self.action_migrate.validate_parameters)
|
||||
|
||||
def test_parameters_retype(self):
|
||||
params = {baction.BaseAction.RESOURCE_ID:
|
||||
self.VOLUME_UUID,
|
||||
self.action.MIGRATION_TYPE: 'cold',
|
||||
self.action.DESTINATION_NODE: None,
|
||||
self.action.DESTINATION_TYPE: 'type-1'}
|
||||
self.action_retype.input_parameters = params
|
||||
self.assertTrue(self.action_retype.validate_parameters)
|
||||
|
||||
def test_parameters_exception_resource_id(self):
|
||||
params = {baction.BaseAction.RESOURCE_ID: "EFEF",
|
||||
self.action.MIGRATION_TYPE: 'swap',
|
||||
self.action.DESTINATION_NODE: None,
|
||||
self.action.DESTINATION_TYPE: 'type-1'}
|
||||
self.action_swap.input_parameters = params
|
||||
self.assertRaises(jsonschema.ValidationError,
|
||||
self.action_swap.validate_parameters)
|
||||
|
||||
def test_migrate_success(self):
|
||||
volume = self.fake_volume()
|
||||
|
||||
self.m_c_helper.can_cold.return_value = True
|
||||
self.m_c_helper.get_volume.return_value = volume
|
||||
result = self.action_migrate.execute()
|
||||
self.assertTrue(result)
|
||||
self.m_c_helper.migrate.assert_called_once_with(
|
||||
volume,
|
||||
"storage1-poolname"
|
||||
)
|
||||
|
||||
def test_migrate_fail(self):
|
||||
self.m_c_helper.can_cold.return_value = False
|
||||
result = self.action_migrate.execute()
|
||||
self.assertFalse(result)
|
||||
self.m_c_helper.migrate.assert_not_called()
|
||||
|
||||
def test_retype_success(self):
|
||||
volume = self.fake_volume()
|
||||
|
||||
self.m_c_helper.can_cold.return_value = True
|
||||
self.m_c_helper.get_volume.return_value = volume
|
||||
result = self.action_retype.execute()
|
||||
self.assertTrue(result)
|
||||
self.m_c_helper.retype.assert_called_once_with(
|
||||
volume,
|
||||
"storage1-typename",
|
||||
)
|
||||
|
||||
def test_retype_fail(self):
|
||||
self.m_c_helper.can_cold.return_value = False
|
||||
result = self.action_migrate.execute()
|
||||
self.assertFalse(result)
|
||||
self.m_c_helper.migrate.assert_not_called()
|
||||
|
||||
def test_swap_success(self):
|
||||
volume = self.fake_volume(
|
||||
status='in-use', attachments=[{'server_id': 'server_id'}])
|
||||
self.m_n_helper.find_instance.return_value = self.fake_instance()
|
||||
|
||||
new_volume = self.fake_volume(id=w_utils.generate_uuid())
|
||||
user = mock.Mock()
|
||||
session = mock.MagicMock()
|
||||
self.m_k_helper.create_user.return_value = user
|
||||
self.m_k_helper.create_session.return_value = session
|
||||
self.m_c_helper.get_volume.return_value = volume
|
||||
self.m_c_helper.create_volume.return_value = new_volume
|
||||
|
||||
result = self.action_swap.execute()
|
||||
self.assertTrue(result)
|
||||
|
||||
self.m_n_helper.swap_volume.assert_called_once_with(
|
||||
volume,
|
||||
new_volume
|
||||
)
|
||||
self.m_k_helper.delete_user.assert_called_once_with(user)
|
||||
|
||||
def test_swap_fail(self):
|
||||
# _can_swap fail
|
||||
instance = self.fake_instance(status='STOPPED')
|
||||
self.m_n_helper.find_instance.return_value = instance
|
||||
|
||||
result = self.action_swap.execute()
|
||||
self.assertFalse(result)
|
||||
|
||||
def test_can_swap_success(self):
|
||||
volume = self.fake_volume(
|
||||
status='in-use', attachments=[{'server_id': 'server_id'}])
|
||||
instance = self.fake_instance()
|
||||
|
||||
self.m_n_helper.find_instance.return_value = instance
|
||||
result = self.action_swap._can_swap(volume)
|
||||
self.assertTrue(result)
|
||||
|
||||
instance = self.fake_instance(status='PAUSED')
|
||||
self.m_n_helper.find_instance.return_value = instance
|
||||
result = self.action_swap._can_swap(volume)
|
||||
self.assertTrue(result)
|
||||
|
||||
instance = self.fake_instance(status='RESIZED')
|
||||
self.m_n_helper.find_instance.return_value = instance
|
||||
result = self.action_swap._can_swap(volume)
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_can_swap_fail(self):
|
||||
|
||||
volume = self.fake_volume(
|
||||
status='in-use', attachments=[{'server_id': 'server_id'}])
|
||||
instance = self.fake_instance(status='STOPPED')
|
||||
self.m_n_helper.find_instance.return_value = instance
|
||||
result = self.action_swap._can_swap(volume)
|
||||
self.assertFalse(result)
|
||||
@@ -1,3 +1,4 @@
|
||||
#
|
||||
# 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
|
||||
@@ -13,6 +14,7 @@
|
||||
#
|
||||
|
||||
import mock
|
||||
import time
|
||||
|
||||
from watcher.common import cinder_helper
|
||||
from watcher.common import clients
|
||||
@@ -124,3 +126,120 @@ class TestCinderHelper(base.TestCase):
|
||||
'nobackend')
|
||||
|
||||
self.assertEqual("", volume_type_name)
|
||||
|
||||
@staticmethod
|
||||
def fake_volume(**kwargs):
|
||||
volume = mock.MagicMock()
|
||||
volume.id = kwargs.get('id', '45a37aeb-95ab-4ddb-a305-7d9f62c2f5ba')
|
||||
volume.name = kwargs.get('name', 'fakename')
|
||||
volume.size = kwargs.get('size', '1')
|
||||
volume.status = kwargs.get('status', 'available')
|
||||
volume.snapshot_id = kwargs.get('snapshot_id', None)
|
||||
volume.availability_zone = kwargs.get('availability_zone', 'nova')
|
||||
volume.volume_type = kwargs.get('volume_type', 'fake_type')
|
||||
return volume
|
||||
|
||||
def test_can_cold_success(self, mock_cinder):
|
||||
cinder_util = cinder_helper.CinderHelper()
|
||||
|
||||
volume = self.fake_volume()
|
||||
cinder_util.cinder.volumes.get.return_value = volume
|
||||
result = cinder_util.can_cold(volume)
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_can_cold_fail(self, mock_cinder):
|
||||
cinder_util = cinder_helper.CinderHelper()
|
||||
|
||||
volume = self.fake_volume(status='in-use')
|
||||
cinder_util.cinder.volumes.get.return_value = volume
|
||||
result = cinder_util.can_cold(volume)
|
||||
self.assertFalse(result)
|
||||
|
||||
volume = self.fake_volume(snapshot_id='snapshot_id')
|
||||
cinder_util.cinder.volumes.get.return_value = volume
|
||||
result = cinder_util.can_cold(volume)
|
||||
self.assertFalse(result)
|
||||
|
||||
volume = self.fake_volume()
|
||||
setattr(volume, 'os-vol-host-attr:host', 'host@backend#pool')
|
||||
cinder_util.cinder.volumes.get.return_value = volume
|
||||
result = cinder_util.can_cold(volume, 'host@backend#pool')
|
||||
self.assertFalse(result)
|
||||
|
||||
@mock.patch.object(time, 'sleep', mock.Mock())
|
||||
def test_migrate_success(self, mock_cinder):
|
||||
|
||||
cinder_util = cinder_helper.CinderHelper()
|
||||
|
||||
volume = self.fake_volume()
|
||||
setattr(volume, 'os-vol-host-attr:host', 'source_node')
|
||||
setattr(volume, 'migration_status', 'success')
|
||||
cinder_util.cinder.volumes.get.return_value = volume
|
||||
|
||||
volume_type = self.fake_volume_type()
|
||||
cinder_util.cinder.volume_types.list.return_value = [volume_type]
|
||||
|
||||
result = cinder_util.migrate(volume, 'host@backend#pool')
|
||||
self.assertTrue(result)
|
||||
|
||||
@mock.patch.object(time, 'sleep', mock.Mock())
|
||||
def test_migrate_fail(self, mock_cinder):
|
||||
|
||||
cinder_util = cinder_helper.CinderHelper()
|
||||
|
||||
volume = self.fake_volume()
|
||||
cinder_util.cinder.volumes.get.return_value = volume
|
||||
|
||||
volume_type = self.fake_volume_type()
|
||||
volume_type.name = 'notbackend'
|
||||
cinder_util.cinder.volume_types.list.return_value = [volume_type]
|
||||
|
||||
self.assertRaisesRegex(
|
||||
exception.Invalid,
|
||||
"Volume type must be same for migrating",
|
||||
cinder_util.migrate, volume, 'host@backend#pool')
|
||||
|
||||
volume = self.fake_volume()
|
||||
setattr(volume, 'os-vol-host-attr:host', 'source_node')
|
||||
setattr(volume, 'migration_status', 'error')
|
||||
cinder_util.cinder.volumes.get.return_value = volume
|
||||
|
||||
volume_type = self.fake_volume_type()
|
||||
cinder_util.cinder.volume_types.list.return_value = [volume_type]
|
||||
|
||||
result = cinder_util.migrate(volume, 'host@backend#pool')
|
||||
self.assertFalse(result)
|
||||
|
||||
@mock.patch.object(time, 'sleep', mock.Mock())
|
||||
def test_retype_success(self, mock_cinder):
|
||||
cinder_util = cinder_helper.CinderHelper()
|
||||
|
||||
volume = self.fake_volume()
|
||||
setattr(volume, 'os-vol-host-attr:host', 'source_node')
|
||||
setattr(volume, 'migration_status', 'success')
|
||||
cinder_util.cinder.volumes.get.return_value = volume
|
||||
|
||||
result = cinder_util.retype(volume, 'notfake_type')
|
||||
self.assertTrue(result)
|
||||
|
||||
@mock.patch.object(time, 'sleep', mock.Mock())
|
||||
def test_retype_fail(self, mock_cinder):
|
||||
cinder_util = cinder_helper.CinderHelper()
|
||||
|
||||
volume = self.fake_volume()
|
||||
setattr(volume, 'os-vol-host-attr:host', 'source_node')
|
||||
setattr(volume, 'migration_status', 'success')
|
||||
cinder_util.cinder.volumes.get.return_value = volume
|
||||
|
||||
self.assertRaisesRegex(
|
||||
exception.Invalid,
|
||||
"Volume type must be different for retyping",
|
||||
cinder_util.retype, volume, 'fake_type')
|
||||
|
||||
volume = self.fake_volume()
|
||||
setattr(volume, 'os-vol-host-attr:host', 'source_node')
|
||||
setattr(volume, 'migration_status', 'error')
|
||||
cinder_util.cinder.volumes.get.return_value = volume
|
||||
|
||||
result = cinder_util.retype(volume, 'notfake_type')
|
||||
self.assertFalse(result)
|
||||
|
||||
@@ -363,3 +363,28 @@ class TestNovaHelper(base.TestCase):
|
||||
|
||||
nova_util.get_flavor_instance(instance, cache)
|
||||
self.assertEqual(instance.flavor['name'], cache['name'])
|
||||
|
||||
@staticmethod
|
||||
def fake_volume(**kwargs):
|
||||
volume = mock.MagicMock()
|
||||
volume.id = kwargs.get('id', '45a37aeb-95ab-4ddb-a305-7d9f62c2f5ba')
|
||||
volume.size = kwargs.get('size', '1')
|
||||
volume.status = kwargs.get('status', 'available')
|
||||
volume.snapshot_id = kwargs.get('snapshot_id', None)
|
||||
volume.availability_zone = kwargs.get('availability_zone', 'nova')
|
||||
return volume
|
||||
|
||||
@mock.patch.object(time, 'sleep', mock.Mock())
|
||||
def test_swap_volume(self, mock_glance, mock_cinder,
|
||||
mock_neutron, mock_nova):
|
||||
nova_util = nova_helper.NovaHelper()
|
||||
server = self.fake_server(self.instance_uuid)
|
||||
self.fake_nova_find_list(nova_util, find=server, list=server)
|
||||
|
||||
old_volume = self.fake_volume(
|
||||
status='in-use', attachments=[{'server_id': self.instance_uuid}])
|
||||
new_volume = self.fake_volume(
|
||||
id=utils.generate_uuid(), status='in-use')
|
||||
|
||||
result = nova_util.swap_volume(old_volume, new_volume)
|
||||
self.assertTrue(result)
|
||||
|
||||
Reference in New Issue
Block a user