blob: 0ee85bc334d26a84a63cba57d1759cde94dfee33 [file] [log] [blame]
#########
# Copyright (c) 2014 GigaSpaces Technologies Ltd. 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.
import mock
import unittest
from cloudify import mocks as cfy_mocks
from cloudify import exceptions as cfy_exc
from cloudify.state import current_ctx
from cinder_plugin import volume
from nova_plugin import server
from openstack_plugin_common import (OPENSTACK_ID_PROPERTY,
OPENSTACK_TYPE_PROPERTY,
OPENSTACK_NAME_PROPERTY)
class TestCinderVolume(unittest.TestCase):
def _mock(self, **kwargs):
ctx = cfy_mocks.MockCloudifyContext(**kwargs)
current_ctx.set(ctx)
return ctx
def tearDown(self):
current_ctx.clear()
def test_create_new(self):
volume_name = 'fake volume name'
volume_description = 'fake volume'
volume_id = '00000000-0000-0000-0000-000000000000'
volume_size = 10
volume_properties = {
'volume': {
'size': volume_size,
'description': volume_description
},
'use_external_resource': False,
'device_name': '/dev/fake',
'resource_id': volume_name,
}
creating_volume_m = mock.Mock()
creating_volume_m.id = volume_id
creating_volume_m.status = volume.VOLUME_STATUS_CREATING
available_volume_m = mock.Mock()
available_volume_m.id = volume_id
available_volume_m.status = volume.VOLUME_STATUS_AVAILABLE
cinder_client_m = mock.Mock()
cinder_client_m.volumes = mock.Mock()
cinder_client_m.volumes.create = mock.Mock(
return_value=creating_volume_m)
cinder_client_m.volumes.get = mock.Mock(
return_value=available_volume_m)
ctx_m = self._mock(node_id='a', properties=volume_properties)
volume.create(cinder_client=cinder_client_m, args={}, ctx=ctx_m,
status_attempts=10, status_timeout=2)
cinder_client_m.volumes.create.assert_called_once_with(
size=volume_size,
name=volume_name,
description=volume_description)
cinder_client_m.volumes.get.assert_called_once_with(volume_id)
self.assertEqual(
volume_id,
ctx_m.instance.runtime_properties[OPENSTACK_ID_PROPERTY])
self.assertEqual(
volume.VOLUME_OPENSTACK_TYPE,
ctx_m.instance.runtime_properties[OPENSTACK_TYPE_PROPERTY])
def test_create_use_existing(self):
volume_id = '00000000-0000-0000-0000-000000000000'
volume_properties = {
'use_external_resource': True,
'device_name': '/dev/fake',
'resource_id': volume_id,
}
existing_volume_m = mock.Mock()
existing_volume_m.id = volume_id
existing_volume_m.status = volume.VOLUME_STATUS_AVAILABLE
cinder_client_m = mock.Mock()
cinder_client_m.volumes = mock.Mock()
cinder_client_m.volumes.create = mock.Mock()
cinder_client_m.cosmo_get_if_exists = mock.Mock(
return_value=existing_volume_m)
cinder_client_m.get_id_from_resource = mock.Mock(
return_value=volume_id)
ctx_m = self._mock(node_id='a', properties=volume_properties)
volume.create(cinder_client=cinder_client_m, args={}, ctx=ctx_m,
status_attempts=10, status_timeout=2)
self.assertFalse(cinder_client_m.volumes.create.called)
self.assertEqual(
volume_id,
ctx_m.instance.runtime_properties[OPENSTACK_ID_PROPERTY])
self.assertEqual(
volume.VOLUME_OPENSTACK_TYPE,
ctx_m.instance.runtime_properties[OPENSTACK_TYPE_PROPERTY])
def test_delete(self):
volume_id = '00000000-0000-0000-0000-000000000000'
volume_name = 'test-volume'
volume_properties = {
'use_external_resource': False,
}
cinder_client_m = mock.Mock()
cinder_client_m.cosmo_delete_resource = mock.Mock()
ctx_m = self._mock(node_id='a', properties=volume_properties)
ctx_m.instance.runtime_properties[OPENSTACK_ID_PROPERTY] = volume_id
ctx_m.instance.runtime_properties[OPENSTACK_TYPE_PROPERTY] = \
volume.VOLUME_OPENSTACK_TYPE
ctx_m.instance.runtime_properties[OPENSTACK_NAME_PROPERTY] = \
volume_name
volume.delete(cinder_client=cinder_client_m, ctx=ctx_m)
cinder_client_m.cosmo_delete_resource.assert_called_once_with(
volume.VOLUME_OPENSTACK_TYPE, volume_id)
self.assertTrue(
OPENSTACK_ID_PROPERTY not in ctx_m.instance.runtime_properties)
self.assertTrue(OPENSTACK_TYPE_PROPERTY
not in ctx_m.instance.runtime_properties)
self.assertTrue(OPENSTACK_NAME_PROPERTY
not in ctx_m.instance.runtime_properties)
@mock.patch('openstack_plugin_common.NovaClientWithSugar')
@mock.patch('openstack_plugin_common.CinderClientWithSugar')
@mock.patch.object(volume, 'wait_until_status', return_value=(None, True))
def test_attach(self, wait_until_status_m, cinder_m, nova_m):
volume_id = '00000000-0000-0000-0000-000000000000'
server_id = '11111111-1111-1111-1111-111111111111'
device_name = '/dev/fake'
volume_ctx = cfy_mocks.MockContext({
'node': cfy_mocks.MockContext({
'properties': {volume.DEVICE_NAME_PROPERTY: device_name}
}),
'instance': cfy_mocks.MockContext({
'runtime_properties': {
OPENSTACK_ID_PROPERTY: volume_id,
}
})
})
server_ctx = cfy_mocks.MockContext({
'node': cfy_mocks.MockContext({
'properties': {}
}),
'instance': cfy_mocks.MockContext({
'runtime_properties': {
server.OPENSTACK_ID_PROPERTY: server_id
}
})
})
ctx_m = self._mock(node_id='a',
target=server_ctx,
source=volume_ctx)
nova_instance = nova_m.return_value
cinder_instance = cinder_m.return_value
server.attach_volume(ctx=ctx_m, status_attempts=10,
status_timeout=2)
nova_instance.volumes.create_server_volume.assert_called_once_with(
server_id, volume_id, device_name)
wait_until_status_m.assert_called_once_with(
cinder_client=cinder_instance,
volume_id=volume_id,
status=volume.VOLUME_STATUS_IN_USE,
num_tries=10,
timeout=2,
)
@mock.patch('openstack_plugin_common.NovaClientWithSugar')
@mock.patch('openstack_plugin_common.CinderClientWithSugar')
def _test_cleanup__after_attach_fails(
self, expected_err_cls, expect_cleanup,
wait_until_status_m, cinder_m, nova_m):
volume_id = '00000000-0000-0000-0000-000000000000'
server_id = '11111111-1111-1111-1111-111111111111'
attachment_id = '22222222-2222-2222-2222-222222222222'
device_name = '/dev/fake'
attachment = {'id': attachment_id,
'server_id': server_id,
'volume_id': volume_id}
volume_ctx = cfy_mocks.MockContext({
'node': cfy_mocks.MockContext({
'properties': {volume.DEVICE_NAME_PROPERTY: device_name}
}),
'instance': cfy_mocks.MockContext({
'runtime_properties': {
OPENSTACK_ID_PROPERTY: volume_id,
}
})
})
server_ctx = cfy_mocks.MockContext({
'node': cfy_mocks.MockContext({
'properties': {}
}),
'instance': cfy_mocks.MockContext({
'runtime_properties': {
server.OPENSTACK_ID_PROPERTY: server_id
}
})
})
ctx_m = self._mock(node_id='a',
target=server_ctx,
source=volume_ctx)
attached_volume = mock.Mock(id=volume_id,
status=volume.VOLUME_STATUS_IN_USE,
attachments=[attachment])
nova_instance = nova_m.return_value
cinder_instance = cinder_m.return_value
cinder_instance.volumes.get.return_value = attached_volume
with self.assertRaises(expected_err_cls):
server.attach_volume(ctx=ctx_m, status_attempts=10,
status_timeout=2)
nova_instance.volumes.create_server_volume.assert_called_once_with(
server_id, volume_id, device_name)
volume.wait_until_status.assert_any_call(
cinder_client=cinder_instance,
volume_id=volume_id,
status=volume.VOLUME_STATUS_IN_USE,
num_tries=10,
timeout=2,
)
if expect_cleanup:
nova_instance.volumes.delete_server_volume.assert_called_once_with(
server_id, attachment_id)
self.assertEqual(2, volume.wait_until_status.call_count)
volume.wait_until_status.assert_called_with(
cinder_client=cinder_instance,
volume_id=volume_id,
status=volume.VOLUME_STATUS_AVAILABLE,
num_tries=10,
timeout=2)
def test_cleanup_after_waituntilstatus_throws_recoverable_error(self):
err = cfy_exc.RecoverableError('Some recoverable error')
with mock.patch.object(volume, 'wait_until_status',
side_effect=[err, (None, True)]) as wait_mock:
self._test_cleanup__after_attach_fails(type(err), True, wait_mock)
def test_cleanup_after_waituntilstatus_throws_any_not_nonrecov_error(self):
class ArbitraryNonRecoverableException(Exception):
pass
err = ArbitraryNonRecoverableException('An exception')
with mock.patch.object(volume, 'wait_until_status',
side_effect=[err, (None, True)]) as wait_mock:
self._test_cleanup__after_attach_fails(type(err), True, wait_mock)
def test_cleanup_after_waituntilstatus_lets_nonrecov_errors_pass(self):
err = cfy_exc.NonRecoverableError('Some non recoverable error')
with mock.patch.object(volume, 'wait_until_status',
side_effect=[err, (None, True)]) as wait_mock:
self._test_cleanup__after_attach_fails(type(err), False, wait_mock)
@mock.patch.object(volume, 'wait_until_status', return_value=(None, False))
def test_cleanup_after_waituntilstatus_times_out(self, wait_mock):
self._test_cleanup__after_attach_fails(cfy_exc.RecoverableError, True,
wait_mock)
@mock.patch('openstack_plugin_common.NovaClientWithSugar')
@mock.patch('openstack_plugin_common.CinderClientWithSugar')
@mock.patch.object(volume, 'wait_until_status', return_value=(None, True))
def test_detach(self, wait_until_status_m, cinder_m, nova_m):
volume_id = '00000000-0000-0000-0000-000000000000'
server_id = '11111111-1111-1111-1111-111111111111'
attachment_id = '22222222-2222-2222-2222-222222222222'
attachment = {'id': attachment_id,
'server_id': server_id,
'volume_id': volume_id}
volume_ctx = cfy_mocks.MockContext({
'node': cfy_mocks.MockContext({
'properties': {}
}),
'instance': cfy_mocks.MockContext({
'runtime_properties': {
OPENSTACK_ID_PROPERTY: volume_id,
}
})
})
server_ctx = cfy_mocks.MockContext({
'node': cfy_mocks.MockContext({
'properties': {}
}),
'instance': cfy_mocks.MockContext({
'runtime_properties': {
server.OPENSTACK_ID_PROPERTY: server_id
}
})
})
ctx_m = self._mock(node_id='a',
target=server_ctx,
source=volume_ctx)
attached_volume = mock.Mock(id=volume_id,
status=volume.VOLUME_STATUS_IN_USE,
attachments=[attachment])
nova_instance = nova_m.return_value
cinder_instance = cinder_m.return_value
cinder_instance.volumes.get.return_value = attached_volume
server.detach_volume(ctx=ctx_m, status_attempts=10, status_timeout=2)
nova_instance.volumes.delete_server_volume.assert_called_once_with(
server_id, attachment_id)
volume.wait_until_status.assert_called_once_with(
cinder_client=cinder_instance,
volume_id=volume_id,
status=volume.VOLUME_STATUS_AVAILABLE,
num_tries=10,
timeout=2,
)