396 lines
18 KiB
Python
396 lines
18 KiB
Python
# Copyright 2010 United States Government as represented by the
|
|
# Administrator of the National Aeronautics and Space Administration.
|
|
# 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.
|
|
"""
|
|
Tests For Scheduler
|
|
"""
|
|
|
|
import mock
|
|
from oslo_config import cfg
|
|
|
|
from cinder import context
|
|
from cinder import db
|
|
from cinder import exception
|
|
from cinder.message import defined_messages
|
|
from cinder.objects import fields
|
|
from cinder.scheduler import driver
|
|
from cinder.scheduler import filter_scheduler
|
|
from cinder.scheduler import manager
|
|
from cinder import test
|
|
from cinder.tests.unit import fake_consistencygroup
|
|
from cinder.tests.unit import fake_constants as fake
|
|
from cinder.tests.unit import fake_volume
|
|
from cinder.tests.unit import utils as tests_utils
|
|
|
|
CONF = cfg.CONF
|
|
|
|
|
|
class SchedulerManagerTestCase(test.TestCase):
|
|
"""Test case for scheduler manager."""
|
|
|
|
manager_cls = manager.SchedulerManager
|
|
driver_cls = driver.Scheduler
|
|
driver_cls_name = 'cinder.scheduler.driver.Scheduler'
|
|
|
|
class AnException(Exception):
|
|
pass
|
|
|
|
def setUp(self):
|
|
super(SchedulerManagerTestCase, self).setUp()
|
|
self.flags(scheduler_driver=self.driver_cls_name)
|
|
self.manager = self.manager_cls()
|
|
self.manager._startup_delay = False
|
|
self.context = context.get_admin_context()
|
|
self.topic = 'fake_topic'
|
|
self.fake_args = (1, 2, 3)
|
|
self.fake_kwargs = {'cat': 'meow', 'dog': 'woof'}
|
|
|
|
def test_1_correct_init(self):
|
|
# Correct scheduler driver
|
|
manager = self.manager
|
|
self.assertIsInstance(manager.driver, self.driver_cls)
|
|
|
|
@mock.patch('eventlet.sleep')
|
|
@mock.patch('cinder.volume.rpcapi.VolumeAPI.publish_service_capabilities')
|
|
def test_init_host_with_rpc(self, publish_capabilities_mock, sleep_mock):
|
|
self.manager._startup_delay = True
|
|
self.manager.init_host_with_rpc()
|
|
publish_capabilities_mock.assert_called_once_with(mock.ANY)
|
|
sleep_mock.assert_called_once_with(CONF.periodic_interval)
|
|
self.assertFalse(self.manager._startup_delay)
|
|
|
|
@mock.patch('cinder.objects.service.Service.get_minimum_rpc_version')
|
|
@mock.patch('cinder.objects.service.Service.get_minimum_obj_version')
|
|
@mock.patch('cinder.rpc.LAST_RPC_VERSIONS', {'cinder-volume': '1.3'})
|
|
@mock.patch('cinder.rpc.LAST_OBJ_VERSIONS', {'cinder-volume': '1.5'})
|
|
def test_reset(self, get_min_obj, get_min_rpc):
|
|
mgr = self.manager_cls()
|
|
|
|
volume_rpcapi = mgr.driver.volume_rpcapi
|
|
self.assertEqual('1.3', volume_rpcapi.client.version_cap)
|
|
self.assertEqual('1.5',
|
|
volume_rpcapi.client.serializer._base.version_cap)
|
|
mgr.reset()
|
|
|
|
volume_rpcapi = mgr.driver.volume_rpcapi
|
|
self.assertEqual(get_min_rpc.return_value,
|
|
volume_rpcapi.client.version_cap)
|
|
self.assertEqual(get_min_obj.return_value,
|
|
volume_rpcapi.client.serializer._base.version_cap)
|
|
|
|
@mock.patch('cinder.scheduler.driver.Scheduler.'
|
|
'update_service_capabilities')
|
|
def test_update_service_capabilities_empty_dict(self, _mock_update_cap):
|
|
# Test no capabilities passes empty dictionary
|
|
service = 'fake_service'
|
|
host = 'fake_host'
|
|
|
|
self.manager.update_service_capabilities(self.context,
|
|
service_name=service,
|
|
host=host)
|
|
_mock_update_cap.assert_called_once_with(service, host, {})
|
|
|
|
@mock.patch('cinder.scheduler.driver.Scheduler.'
|
|
'update_service_capabilities')
|
|
def test_update_service_capabilities_correct(self, _mock_update_cap):
|
|
# Test capabilities passes correctly
|
|
service = 'fake_service'
|
|
host = 'fake_host'
|
|
capabilities = {'fake_capability': 'fake_value'}
|
|
|
|
self.manager.update_service_capabilities(self.context,
|
|
service_name=service,
|
|
host=host,
|
|
capabilities=capabilities)
|
|
_mock_update_cap.assert_called_once_with(service, host, capabilities)
|
|
|
|
@mock.patch('cinder.scheduler.driver.Scheduler.schedule_create_volume')
|
|
@mock.patch('cinder.message.api.API.create')
|
|
@mock.patch('cinder.db.volume_update')
|
|
def test_create_volume_exception_puts_volume_in_error_state(
|
|
self, _mock_volume_update, _mock_message_create,
|
|
_mock_sched_create):
|
|
# Test NoValidHost exception behavior for create_volume.
|
|
# Puts the volume in 'error' state and eats the exception.
|
|
_mock_sched_create.side_effect = exception.NoValidHost(reason="")
|
|
volume = fake_volume.fake_volume_obj(self.context)
|
|
topic = 'fake_topic'
|
|
request_spec = {'volume_id': volume.id}
|
|
|
|
self.manager.create_volume(self.context, topic, volume.id,
|
|
request_spec=request_spec,
|
|
filter_properties={},
|
|
volume=volume)
|
|
_mock_volume_update.assert_called_once_with(self.context,
|
|
volume.id,
|
|
{'status': 'error'})
|
|
_mock_sched_create.assert_called_once_with(self.context, request_spec,
|
|
{})
|
|
|
|
_mock_message_create.assert_called_once_with(
|
|
self.context, defined_messages.UNABLE_TO_ALLOCATE,
|
|
self.context.project_id, resource_type='VOLUME',
|
|
resource_uuid=volume.id)
|
|
|
|
@mock.patch('cinder.scheduler.driver.Scheduler.schedule_create_volume')
|
|
@mock.patch('eventlet.sleep')
|
|
def test_create_volume_no_delay(self, _mock_sleep, _mock_sched_create):
|
|
volume = fake_volume.fake_volume_obj(self.context)
|
|
topic = 'fake_topic'
|
|
|
|
request_spec = {'volume_id': volume.id}
|
|
|
|
self.manager.create_volume(self.context, topic, volume.id,
|
|
request_spec=request_spec,
|
|
filter_properties={},
|
|
volume=volume)
|
|
_mock_sched_create.assert_called_once_with(self.context, request_spec,
|
|
{})
|
|
self.assertFalse(_mock_sleep.called)
|
|
|
|
@mock.patch('cinder.scheduler.driver.Scheduler.schedule_create_volume')
|
|
@mock.patch('cinder.scheduler.driver.Scheduler.is_ready')
|
|
@mock.patch('eventlet.sleep')
|
|
def test_create_volume_delay_scheduled_after_3_tries(self, _mock_sleep,
|
|
_mock_is_ready,
|
|
_mock_sched_create):
|
|
self.manager._startup_delay = True
|
|
volume = fake_volume.fake_volume_obj(self.context)
|
|
topic = 'fake_topic'
|
|
|
|
request_spec = {'volume_id': volume.id}
|
|
|
|
_mock_is_ready.side_effect = [False, False, True]
|
|
|
|
self.manager.create_volume(self.context, topic, volume.id,
|
|
request_spec=request_spec,
|
|
filter_properties={},
|
|
volume=volume)
|
|
_mock_sched_create.assert_called_once_with(self.context, request_spec,
|
|
{})
|
|
calls = [mock.call(1)] * 2
|
|
_mock_sleep.assert_has_calls(calls)
|
|
self.assertEqual(2, _mock_sleep.call_count)
|
|
|
|
@mock.patch('cinder.scheduler.driver.Scheduler.schedule_create_volume')
|
|
@mock.patch('cinder.scheduler.driver.Scheduler.is_ready')
|
|
@mock.patch('eventlet.sleep')
|
|
def test_create_volume_delay_scheduled_in_1_try(self, _mock_sleep,
|
|
_mock_is_ready,
|
|
_mock_sched_create):
|
|
self.manager._startup_delay = True
|
|
volume = fake_volume.fake_volume_obj(self.context)
|
|
topic = 'fake_topic'
|
|
|
|
request_spec = {'volume_id': volume.id}
|
|
|
|
_mock_is_ready.return_value = True
|
|
|
|
self.manager.create_volume(self.context, topic, volume.id,
|
|
request_spec=request_spec,
|
|
filter_properties={},
|
|
volume=volume)
|
|
_mock_sched_create.assert_called_once_with(self.context, request_spec,
|
|
{})
|
|
self.assertFalse(_mock_sleep.called)
|
|
|
|
@mock.patch('cinder.db.volume_get')
|
|
@mock.patch('cinder.scheduler.driver.Scheduler.host_passes_filters')
|
|
@mock.patch('cinder.db.volume_update')
|
|
def test_migrate_volume_exception_returns_volume_state(
|
|
self, _mock_volume_update, _mock_host_passes,
|
|
_mock_volume_get):
|
|
# Test NoValidHost exception behavior for migrate_volume_to_host.
|
|
# Puts the volume in 'error_migrating' state and eats the exception.
|
|
fake_updates = {'migration_status': 'error'}
|
|
self._test_migrate_volume_exception_returns_volume_state(
|
|
_mock_volume_update, _mock_host_passes, _mock_volume_get,
|
|
'available', fake_updates)
|
|
|
|
@mock.patch('cinder.db.volume_get')
|
|
@mock.patch('cinder.scheduler.driver.Scheduler.host_passes_filters')
|
|
@mock.patch('cinder.db.volume_update')
|
|
def test_migrate_volume_exception_returns_volume_state_maintenance(
|
|
self, _mock_volume_update, _mock_host_passes,
|
|
_mock_volume_get):
|
|
fake_updates = {'status': 'available',
|
|
'migration_status': 'error'}
|
|
self._test_migrate_volume_exception_returns_volume_state(
|
|
_mock_volume_update, _mock_host_passes, _mock_volume_get,
|
|
'maintenance', fake_updates)
|
|
|
|
def _test_migrate_volume_exception_returns_volume_state(
|
|
self, _mock_volume_update, _mock_host_passes,
|
|
_mock_volume_get, status, fake_updates):
|
|
volume = tests_utils.create_volume(self.context,
|
|
status=status,
|
|
previous_status='available')
|
|
fake_volume_id = volume.id
|
|
topic = 'fake_topic'
|
|
request_spec = {'volume_id': fake_volume_id}
|
|
_mock_host_passes.side_effect = exception.NoValidHost(reason="")
|
|
_mock_volume_get.return_value = volume
|
|
|
|
self.manager.migrate_volume_to_host(self.context, topic,
|
|
fake_volume_id, 'host', True,
|
|
request_spec=request_spec,
|
|
filter_properties={},
|
|
volume=volume)
|
|
_mock_volume_update.assert_called_once_with(self.context,
|
|
fake_volume_id,
|
|
fake_updates)
|
|
_mock_host_passes.assert_called_once_with(self.context, 'host',
|
|
request_spec, {})
|
|
|
|
@mock.patch('cinder.db.volume_update')
|
|
@mock.patch('cinder.db.volume_attachment_get_used_by_volume_id')
|
|
def test_retype_volume_exception_returns_volume_state(
|
|
self, _mock_vol_attachment_get, _mock_vol_update):
|
|
# Test NoValidHost exception behavior for retype.
|
|
# Puts the volume in original state and eats the exception.
|
|
volume = tests_utils.create_volume(self.context,
|
|
status='retyping',
|
|
previous_status='in-use')
|
|
instance_uuid = '12345678-1234-5678-1234-567812345678'
|
|
volume_attach = tests_utils.attach_volume(self.context, volume.id,
|
|
instance_uuid, None,
|
|
'/dev/fake')
|
|
_mock_vol_attachment_get.return_value = [volume_attach]
|
|
topic = 'fake_topic'
|
|
request_spec = {'volume_id': volume.id, 'volume_type': {'id': 3},
|
|
'migration_policy': 'on-demand'}
|
|
_mock_vol_update.return_value = {'status': 'in-use'}
|
|
_mock_find_retype_host = mock.Mock(
|
|
side_effect=exception.NoValidHost(reason=""))
|
|
orig_retype = self.manager.driver.find_retype_host
|
|
self.manager.driver.find_retype_host = _mock_find_retype_host
|
|
|
|
self.manager.retype(self.context, topic, volume.id,
|
|
request_spec=request_spec,
|
|
filter_properties={},
|
|
volume=volume)
|
|
|
|
_mock_find_retype_host.assert_called_once_with(self.context,
|
|
request_spec, {},
|
|
'on-demand')
|
|
_mock_vol_update.assert_called_once_with(self.context, volume.id,
|
|
{'status': 'in-use'})
|
|
self.manager.driver.find_retype_host = orig_retype
|
|
|
|
def test_create_consistencygroup_exceptions(self):
|
|
with mock.patch.object(filter_scheduler.FilterScheduler,
|
|
'schedule_create_consistencygroup') as mock_cg:
|
|
original_driver = self.manager.driver
|
|
consistencygroup_obj = \
|
|
fake_consistencygroup.fake_consistencyobject_obj(self.context)
|
|
self.manager.driver = filter_scheduler.FilterScheduler
|
|
LOG = self.mock_object(manager, 'LOG')
|
|
self.stubs.Set(db, 'consistencygroup_update', mock.Mock())
|
|
|
|
ex = exception.CinderException('test')
|
|
mock_cg.side_effect = ex
|
|
group_id = fake.consistency_group_id
|
|
self.assertRaises(exception.CinderException,
|
|
self.manager.create_consistencygroup,
|
|
self.context,
|
|
'volume',
|
|
consistencygroup_obj)
|
|
self.assertTrue(LOG.exception.call_count > 0)
|
|
db.consistencygroup_update.assert_called_once_with(
|
|
self.context, group_id, {'status': (
|
|
fields.ConsistencyGroupStatus.ERROR)})
|
|
|
|
mock_cg.reset_mock()
|
|
LOG.exception.reset_mock()
|
|
db.consistencygroup_update.reset_mock()
|
|
|
|
mock_cg.side_effect = exception.NoValidHost(
|
|
reason="No weighed hosts available")
|
|
self.manager.create_consistencygroup(
|
|
self.context, 'volume', consistencygroup_obj)
|
|
self.assertTrue(LOG.error.call_count > 0)
|
|
db.consistencygroup_update.assert_called_once_with(
|
|
self.context, group_id, {'status': (
|
|
fields.ConsistencyGroupStatus.ERROR)})
|
|
|
|
self.manager.driver = original_driver
|
|
|
|
|
|
class SchedulerTestCase(test.TestCase):
|
|
"""Test case for base scheduler driver class."""
|
|
|
|
# So we can subclass this test and re-use tests if we need.
|
|
driver_cls = driver.Scheduler
|
|
|
|
def setUp(self):
|
|
super(SchedulerTestCase, self).setUp()
|
|
self.driver = self.driver_cls()
|
|
self.context = context.RequestContext(fake.user_id, fake.project_id)
|
|
self.topic = 'fake_topic'
|
|
|
|
@mock.patch('cinder.scheduler.driver.Scheduler.'
|
|
'update_service_capabilities')
|
|
def test_update_service_capabilities(self, _mock_update_cap):
|
|
service_name = 'fake_service'
|
|
host = 'fake_host'
|
|
capabilities = {'fake_capability': 'fake_value'}
|
|
self.driver.update_service_capabilities(service_name, host,
|
|
capabilities)
|
|
_mock_update_cap.assert_called_once_with(service_name, host,
|
|
capabilities)
|
|
|
|
@mock.patch('cinder.scheduler.host_manager.HostManager.'
|
|
'has_all_capabilities', return_value=False)
|
|
def test_is_ready(self, _mock_has_caps):
|
|
ready = self.driver.is_ready()
|
|
_mock_has_caps.assert_called_once_with()
|
|
self.assertFalse(ready)
|
|
|
|
|
|
class SchedulerDriverBaseTestCase(SchedulerTestCase):
|
|
"""Test schedule driver class.
|
|
|
|
Test cases for base scheduler driver class methods
|
|
that will fail if the driver is changed.
|
|
"""
|
|
|
|
def test_unimplemented_schedule(self):
|
|
fake_args = (1, 2, 3)
|
|
fake_kwargs = {'cat': 'meow'}
|
|
|
|
self.assertRaises(NotImplementedError, self.driver.schedule,
|
|
self.context, self.topic, 'schedule_something',
|
|
*fake_args, **fake_kwargs)
|
|
|
|
|
|
class SchedulerDriverModuleTestCase(test.TestCase):
|
|
"""Test case for scheduler driver module methods."""
|
|
|
|
def setUp(self):
|
|
super(SchedulerDriverModuleTestCase, self).setUp()
|
|
self.context = context.RequestContext(fake.user_id, fake.project_id)
|
|
|
|
@mock.patch('cinder.db.volume_update')
|
|
@mock.patch('cinder.objects.volume.Volume.get_by_id')
|
|
def test_volume_host_update_db(self, _mock_volume_get, _mock_vol_update):
|
|
volume = fake_volume.fake_volume_obj(self.context)
|
|
_mock_volume_get.return_value = volume
|
|
|
|
driver.volume_update_db(self.context, volume.id, 'fake_host')
|
|
scheduled_at = volume.scheduled_at.replace(tzinfo=None)
|
|
_mock_vol_update.assert_called_once_with(
|
|
self.context, volume.id, {'host': 'fake_host',
|
|
'scheduled_at': scheduled_at})
|