Coverage for manila/tests/scheduler/test_manager.py: 100%
212 statements
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
1# Copyright 2010 United States Government as represented by the
2# Administrator of the National Aeronautics and Space Administration.
3# All Rights Reserved.
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
16"""
17Tests For Scheduler Manager
18"""
20from importlib import reload
21from unittest import mock
23import ddt
24from oslo_config import cfg
26from manila.common import constants
27from manila import context
28from manila import db
29from manila import exception
30from manila.message import message_field
31from manila import quota
32from manila.scheduler.drivers import base
33from manila.scheduler.drivers import filter
34from manila.scheduler import manager
35from manila.share import rpcapi as share_rpcapi
36from manila.share import share_types
37from manila import test
38from manila.tests import db_utils
39from manila.tests import fake_share as fakes
40from manila import utils
42CONF = cfg.CONF
45@ddt.ddt
46class SchedulerManagerTestCase(test.TestCase):
47 """Test case for scheduler manager."""
49 driver_cls = base.Scheduler
50 driver_cls_name = 'manila.scheduler.drivers.base.Scheduler'
52 def setUp(self):
53 super(SchedulerManagerTestCase, self).setUp()
54 self.periodic_tasks = []
56 def _periodic_task(*args, **kwargs):
57 def decorator(f):
58 self.periodic_tasks.append(f)
59 return f
60 return mock.Mock(side_effect=decorator)
62 self.mock_periodic_task = self.mock_object(
63 manager.periodic_task, 'periodic_task',
64 mock.Mock(side_effect=_periodic_task))
65 reload(manager)
67 self.flags(scheduler_driver=self.driver_cls_name)
68 self.manager = manager.SchedulerManager()
69 self.context = context.RequestContext('fake_user', 'fake_project')
70 self.topic = 'fake_topic'
71 self.fake_args = (1, 2, 3)
72 self.fake_kwargs = {'cat': 'meow', 'dog': 'woof'}
74 def raise_no_valid_host(self, *args, **kwargs):
75 raise exception.NoValidHost(reason="")
77 def test_1_correct_init(self):
78 # Correct scheduler driver
79 manager = self.manager
80 self.assertIsInstance(manager.driver, self.driver_cls)
82 @ddt.data('manila.scheduler.filter_scheduler.FilterScheduler',
83 'manila.scheduler.drivers.filter.FilterScheduler')
84 def test_scheduler_driver_mapper(self, driver_class):
86 test_manager = manager.SchedulerManager(scheduler_driver=driver_class)
88 self.assertIsInstance(test_manager.driver, filter.FilterScheduler)
90 def test_init_host_with_rpc(self):
92 self.mock_object(context,
93 'get_admin_context',
94 mock.Mock(return_value='fake_admin_context'))
95 self.mock_object(self.manager, 'request_service_capabilities')
97 self.manager.init_host_with_rpc()
99 self.manager.request_service_capabilities.assert_called_once_with(
100 'fake_admin_context')
102 def test_get_host_list(self):
104 self.mock_object(self.manager.driver, 'get_host_list')
106 self.manager.get_host_list(context)
108 self.manager.driver.get_host_list.assert_called_once_with()
110 def test_get_service_capabilities(self):
112 self.mock_object(self.manager.driver, 'get_service_capabilities')
114 self.manager.get_service_capabilities(context)
116 self.manager.driver.get_service_capabilities.assert_called_once_with()
118 def test_update_service_capabilities(self):
119 service_name = 'fake_service'
120 host = 'fake_host'
121 with mock.patch.object(self.manager.driver,
122 'update_service_capabilities', mock.Mock()):
123 self.manager.update_service_capabilities(
124 self.context, service_name=service_name, host=host)
125 (self.manager.driver.update_service_capabilities.
126 assert_called_once_with(service_name, host, {}, None))
127 with mock.patch.object(self.manager.driver,
128 'update_service_capabilities', mock.Mock()):
129 capabilities = {'fake_capability': 'fake_value'}
130 self.manager.update_service_capabilities(
131 self.context, service_name=service_name, host=host,
132 capabilities=capabilities)
133 (self.manager.driver.update_service_capabilities.
134 assert_called_once_with(service_name, host,
135 capabilities, None))
137 @mock.patch.object(db, 'share_update', mock.Mock())
138 @mock.patch('manila.message.api.API.create')
139 def test_create_share_exception_puts_share_in_error_state(
140 self, _mock_message_create):
141 """Test NoValidHost exception for create_share.
143 Puts the share in 'error' state and eats the exception.
144 """
145 fake_share_id = 1
147 request_spec = {'share_id': fake_share_id}
148 ex = exception.NoValidHost(reason='')
149 with mock.patch.object(
150 self.manager.driver, 'schedule_create_share',
151 mock.Mock(side_effect=ex)):
152 self.mock_object(manager.LOG, 'error')
154 self.manager.create_share_instance(
155 self.context, request_spec=request_spec, filter_properties={})
157 db.share_update.assert_called_once_with(
158 self.context, fake_share_id, {'status': 'error'})
159 (self.manager.driver.schedule_create_share.
160 assert_called_once_with(self.context, request_spec, {}))
161 manager.LOG.error.assert_called_once_with(mock.ANY, mock.ANY)
163 _mock_message_create.assert_called_once_with(
164 self.context,
165 message_field.Action.ALLOCATE_HOST,
166 self.context.project_id, resource_type='SHARE',
167 exception=ex, resource_id=fake_share_id)
169 @mock.patch.object(db, 'share_update', mock.Mock())
170 def test_create_share_other_exception_puts_share_in_error_state(self):
171 """Test any exception except NoValidHost for create_share.
173 Puts the share in 'error' state and re-raises the exception.
174 """
175 fake_share_id = 1
177 request_spec = {'share_id': fake_share_id}
178 with mock.patch.object(self.manager.driver,
179 'schedule_create_share',
180 mock.Mock(side_effect=exception.QuotaError)):
181 self.mock_object(manager.LOG, 'error')
183 self.assertRaises(exception.QuotaError,
184 self.manager.create_share_instance,
185 self.context,
186 request_spec=request_spec,
187 filter_properties={})
189 db.share_update.assert_called_once_with(
190 self.context, fake_share_id, {'status': 'error'})
191 (self.manager.driver.schedule_create_share.
192 assert_called_once_with(self.context, request_spec, {}))
193 manager.LOG.error.assert_called_once_with(mock.ANY, mock.ANY)
195 @mock.patch.object(quota.QUOTAS, 'expire')
196 def test__expire_reservations(self, mock_expire):
197 self.manager._expire_reservations(self.context)
199 mock_expire.assert_called_once_with(self.context)
201 @mock.patch('manila.message.api.API.cleanup_expired_messages')
202 def test__clean_expired_messages(self, mock_expire):
203 self.manager._clean_expired_messages(self.context)
205 mock_expire.assert_called_once_with(self.context)
207 @mock.patch.object(db, 'service_get_all', mock.Mock())
208 @mock.patch.object(db, 'service_update', mock.Mock())
209 @mock.patch.object(utils, "service_is_up", mock.Mock())
210 def test__mark_services_as_down(self):
211 db.service_get_all.return_value = [
212 {"id": 1, "state": "up"},
213 {"id": 2, "state": "up"},
214 {"id": 3, "state": "stopped"},
215 {"id": 4, "state": "stopped"},
216 {"id": 5, "state": "down"},
217 {"id": 6, "state": "down"},
218 ]
219 utils.service_is_up.side_effect = [False, True] * 3
221 self.manager._mark_services_as_down(self.context)
223 db.service_get_all.assert_called_once_with(self.context)
224 self.assertEqual(6, utils.service_is_up.call_count)
225 db.service_update.assert_called_once_with(
226 self.context, 1, {"state": "down"})
228 def test_periodic_tasks(self):
229 self.assertEqual(3, self.mock_periodic_task.call_count)
231 self.assertEqual(3, len(self.periodic_tasks))
232 self.assertEqual(
233 self.periodic_tasks[0].__name__,
234 self.manager._expire_reservations.__name__)
235 self.assertEqual(
236 self.periodic_tasks[1].__name__,
237 self.manager._clean_expired_messages.__name__)
238 self.assertEqual(
239 self.periodic_tasks[2].__name__,
240 self.manager._mark_services_as_down.__name__)
242 def test_get_pools(self):
243 """Ensure get_pools exists and calls base_scheduler.get_pools."""
244 mock_get_pools = self.mock_object(self.manager.driver,
245 'get_pools',
246 mock.Mock(return_value='fake_pools'))
248 result = self.manager.get_pools(self.context, filters='fake_filters')
250 mock_get_pools.assert_called_once_with(self.context, 'fake_filters',
251 False)
252 self.assertEqual('fake_pools', result)
254 @mock.patch.object(db, 'share_group_update', mock.Mock())
255 def test_create_group_no_valid_host_puts_group_in_error_state(self):
256 """Test that NoValidHost is raised for create_share_group.
258 Puts the share in 'error' state and eats the exception.
259 """
261 fake_group_id = 1
262 group_id = fake_group_id
263 request_spec = {"share_group_id": group_id}
264 with mock.patch.object(
265 self.manager.driver, 'schedule_create_share_group',
266 mock.Mock(side_effect=self.raise_no_valid_host)):
267 self.manager.create_share_group(self.context,
268 fake_group_id,
269 request_spec=request_spec,
270 filter_properties={})
271 db.share_group_update.assert_called_once_with(
272 self.context, fake_group_id, {'status': 'error'})
273 (self.manager.driver.schedule_create_share_group.
274 assert_called_once_with(self.context, group_id, request_spec,
275 {}))
277 @mock.patch.object(db, 'share_group_update', mock.Mock())
278 def test_create_group_exception_puts_group_in_error_state(self):
279 """Test that exceptions for create_share_group.
281 Puts the share in 'error' state and raises the exception.
282 """
284 fake_group_id = 1
285 group_id = fake_group_id
286 request_spec = {"share_group_id": group_id}
287 with mock.patch.object(self.manager.driver,
288 'schedule_create_share_group',
289 mock.Mock(side_effect=exception.NotFound)):
290 self.assertRaises(exception.NotFound,
291 self.manager.create_share_group,
292 self.context, fake_group_id,
293 request_spec=request_spec,
294 filter_properties={})
296 def test_migrate_share_to_host(self):
298 class fake_host(object):
299 host = 'fake@backend#pool'
301 share = db_utils.create_share()
302 host = fake_host()
304 self.mock_object(db, 'share_get', mock.Mock(return_value=share))
305 self.mock_object(share_rpcapi.ShareAPI, 'migration_start',
306 mock.Mock(side_effect=TypeError))
307 self.mock_object(base.Scheduler,
308 'host_passes_filters',
309 mock.Mock(return_value=host))
310 self.mock_object(
311 share_types,
312 'revert_allocated_share_type_quotas_during_migration')
314 self.assertRaises(
315 TypeError, self.manager.migrate_share_to_host,
316 self.context, share['id'], 'fake@backend#pool', False, True, True,
317 False, True, 'fake_net_id', 'fake_type_id', {}, None)
319 db.share_get.assert_called_once_with(self.context, share['id'])
320 base.Scheduler.host_passes_filters.assert_called_once_with(
321 self.context, 'fake@backend#pool', {}, None)
322 share_rpcapi.ShareAPI.migration_start.assert_called_once_with(
323 self.context, share, host.host, False, True, True, False, True,
324 'fake_net_id', 'fake_type_id')
325 (share_types.revert_allocated_share_type_quotas_during_migration.
326 assert_called_once_with(self.context, share, 'fake_type_id'))
328 @ddt.data(exception.NoValidHost(reason='fake'), TypeError)
329 def test_migrate_share_to_host_exception(self, exc):
331 share = db_utils.create_share(status=constants.STATUS_MIGRATING)
332 host = 'fake@backend#pool'
333 request_spec = {'share_id': share['id']}
335 self.mock_object(db, 'share_get', mock.Mock(return_value=share))
336 self.mock_object(
337 base.Scheduler, 'host_passes_filters',
338 mock.Mock(side_effect=exc))
339 self.mock_object(db, 'share_update')
340 self.mock_object(db, 'share_instance_update')
341 self.mock_object(
342 share_types,
343 'revert_allocated_share_type_quotas_during_migration')
345 capture = (exception.NoValidHost if
346 isinstance(exc, exception.NoValidHost) else TypeError)
348 self.assertRaises(
349 capture, self.manager.migrate_share_to_host,
350 self.context, share['id'], host, False, True, True, False, True,
351 'fake_net_id', 'fake_type_id', request_spec, None)
353 base.Scheduler.host_passes_filters.assert_called_once_with(
354 self.context, host, request_spec, None)
355 db.share_get.assert_called_once_with(self.context, share['id'])
356 db.share_update.assert_called_once_with(
357 self.context, share['id'],
358 {'task_state': constants.TASK_STATE_MIGRATION_ERROR})
359 db.share_instance_update.assert_called_once_with(
360 self.context, share.instance['id'],
361 {'status': constants.STATUS_AVAILABLE})
362 (share_types.revert_allocated_share_type_quotas_during_migration.
363 assert_called_once_with(self.context, share, 'fake_type_id'))
365 def test_manage_share(self):
367 share = db_utils.create_share()
369 self.mock_object(db, 'share_get', mock.Mock(return_value=share))
370 self.mock_object(share_rpcapi.ShareAPI, 'manage_share')
371 self.mock_object(base.Scheduler, 'host_passes_filters')
373 self.manager.manage_share(self.context, share['id'], 'driver_options',
374 {}, None)
376 def test_manage_share_exception(self):
378 share = db_utils.create_share()
380 db_update = self.mock_object(db, 'share_update', mock.Mock())
381 self.mock_object(
382 base.Scheduler, 'host_passes_filters',
383 mock.Mock(side_effect=exception.NoValidHost('fake')))
385 share_id = share['id']
387 self.assertRaises(
388 exception.NoValidHost, self.manager.manage_share,
389 self.context, share['id'], 'driver_options',
390 {'share_id': share_id}, None)
391 db_update.assert_called_once_with(
392 self.context, share_id,
393 {'status': constants.STATUS_MANAGE_ERROR, 'size': 0})
395 def test_create_share_replica_exception_path(self):
396 """Test 'raisable' exceptions for create_share_replica."""
397 db_update = self.mock_object(db, 'share_replica_update')
398 self.mock_object(db, 'share_snapshot_instance_get_all_with_filters',
399 mock.Mock(return_value=[{'id': '123'}]))
400 snap_update = self.mock_object(db, 'share_snapshot_instance_update')
401 request_spec = fakes.fake_replica_request_spec()
402 replica_id = request_spec.get('share_instance_properties').get('id')
403 expected_updates = {
404 'status': constants.STATUS_ERROR,
405 'replica_state': constants.STATUS_ERROR,
406 }
407 with mock.patch.object(self.manager.driver, 'schedule_create_replica',
408 mock.Mock(side_effect=exception.NotFound)):
410 self.assertRaises(exception.NotFound,
411 self.manager.create_share_replica,
412 self.context,
413 request_spec=request_spec,
414 filter_properties={})
415 db_update.assert_called_once_with(
416 self.context, replica_id, expected_updates)
417 snap_update.assert_called_once_with(
418 self.context, '123', {'status': constants.STATUS_ERROR})
420 def test_create_share_replica_no_valid_host(self):
421 """Test the NoValidHost exception for create_share_replica."""
422 db_update = self.mock_object(db, 'share_replica_update')
423 request_spec = fakes.fake_replica_request_spec()
424 replica_id = request_spec.get('share_instance_properties').get('id')
425 expected_updates = {
426 'status': constants.STATUS_ERROR,
427 'replica_state': constants.STATUS_ERROR,
428 }
429 with mock.patch.object(
430 self.manager.driver, 'schedule_create_replica',
431 mock.Mock(side_effect=self.raise_no_valid_host)):
433 retval = self.manager.create_share_replica(
434 self.context, request_spec=request_spec, filter_properties={})
436 self.assertIsNone(retval)
437 db_update.assert_called_once_with(
438 self.context, replica_id, expected_updates)
440 def test_create_share_replica(self):
441 """Test happy path for create_share_replica."""
442 db_update = self.mock_object(db, 'share_replica_update')
443 mock_scheduler_driver_call = self.mock_object(
444 self.manager.driver, 'schedule_create_replica')
445 request_spec = fakes.fake_replica_request_spec()
447 retval = self.manager.create_share_replica(
448 self.context, request_spec=request_spec, filter_properties={})
450 mock_scheduler_driver_call.assert_called_once_with(
451 self.context, request_spec, {})
452 self.assertFalse(db_update.called)
453 self.assertIsNone(retval)