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
18"""
19
20import collections
21from datetime import datetime
22import ddt
23import mock
24from oslo_config import cfg
25
26from cinder.common import constants
27from cinder import context
28from cinder import exception
29from cinder.message import message_field
30from cinder import objects
31from cinder.scheduler import driver
32from cinder.scheduler import manager
33from cinder import test
34from cinder.tests.unit import fake_constants as fake
35from cinder.tests.unit import fake_volume
36from cinder.tests.unit.scheduler import fakes as fake_scheduler
37from cinder.tests.unit import utils as tests_utils
38
39CONF = cfg.CONF
40
41
42@ddt.ddt
43class SchedulerManagerTestCase(test.TestCase):
44    """Test case for scheduler manager."""
45
46    manager_cls = manager.SchedulerManager
47    driver_cls = driver.Scheduler
48    driver_cls_name = 'cinder.scheduler.driver.Scheduler'
49
50    class AnException(Exception):
51        pass
52
53    def setUp(self):
54        super(SchedulerManagerTestCase, self).setUp()
55        self.flags(scheduler_driver=self.driver_cls_name)
56        self.manager = self.manager_cls()
57        self.manager._startup_delay = False
58        self.context = context.get_admin_context()
59        self.topic = 'fake_topic'
60        self.fake_args = (1, 2, 3)
61        self.fake_kwargs = {'cat': 'meow', 'dog': 'woof'}
62
63    def test_1_correct_init(self):
64        # Correct scheduler driver
65        manager = self.manager
66        self.assertIsInstance(manager.driver, self.driver_cls)
67
68    @mock.patch('eventlet.sleep')
69    @mock.patch('cinder.volume.rpcapi.VolumeAPI.publish_service_capabilities')
70    def test_init_host_with_rpc(self, publish_capabilities_mock, sleep_mock):
71        self.manager._startup_delay = True
72        self.manager.init_host_with_rpc()
73        publish_capabilities_mock.assert_called_once_with(mock.ANY)
74        sleep_mock.assert_called_once_with(CONF.periodic_interval)
75        self.assertFalse(self.manager._startup_delay)
76
77    @mock.patch('cinder.objects.service.Service.get_minimum_rpc_version')
78    @mock.patch('cinder.objects.service.Service.get_minimum_obj_version')
79    @mock.patch('cinder.rpc.LAST_RPC_VERSIONS', {'cinder-volume': '1.3'})
80    @mock.patch('cinder.rpc.LAST_OBJ_VERSIONS', {'cinder-volume': '1.4',
81                                                 'cinder-scheduler': '1.4'})
82    def test_reset(self, get_min_obj, get_min_rpc):
83        mgr = self.manager_cls()
84
85        volume_rpcapi = mgr.driver.volume_rpcapi
86        self.assertEqual('1.3', volume_rpcapi.client.version_cap)
87        self.assertEqual('1.4',
88                         volume_rpcapi.client.serializer._base.version_cap)
89        get_min_obj.return_value = objects.base.OBJ_VERSIONS.get_current()
90        mgr.reset()
91
92        volume_rpcapi = mgr.driver.volume_rpcapi
93        self.assertEqual(get_min_rpc.return_value,
94                         volume_rpcapi.client.version_cap)
95        self.assertEqual(get_min_obj.return_value,
96                         volume_rpcapi.client.serializer._base.version_cap)
97        self.assertIsNone(volume_rpcapi.client.serializer._base.manifest)
98
99    @mock.patch('cinder.message.api.API.cleanup_expired_messages')
100    def test_clean_expired_messages(self, mock_clean):
101
102        self.manager._clean_expired_messages(self.context)
103
104        mock_clean.assert_called_once_with(self.context)
105
106    @mock.patch('cinder.scheduler.driver.Scheduler.backend_passes_filters')
107    @mock.patch(
108        'cinder.scheduler.host_manager.BackendState.consume_from_volume')
109    @mock.patch('cinder.volume.rpcapi.VolumeAPI.extend_volume')
110    def test_extend_volume(self, mock_extend,
111                           mock_consume, mock_backend_passes):
112        volume = fake_volume.fake_volume_obj(self.context, **{'size': 1})
113        fake_backend = fake_scheduler.FakeBackendState('host1', {})
114        mock_backend_passes.return_value = fake_backend
115
116        self.manager.extend_volume(self.context, volume, 2, 'fake_reservation')
117
118        mock_consume.assert_called_once_with({'size': 1})
119        mock_extend.assert_called_once_with(
120            self.context, volume, 2, 'fake_reservation')
121
122    @ddt.data('available', 'in-use')
123    @mock.patch('cinder.scheduler.driver.Scheduler.backend_passes_filters')
124    @mock.patch(
125        'cinder.scheduler.host_manager.BackendState.consume_from_volume')
126    @mock.patch('cinder.volume.rpcapi.VolumeAPI.extend_volume')
127    @mock.patch('cinder.quota.QUOTAS.rollback')
128    @mock.patch('cinder.message.api.API.create')
129    def test_extend_volume_no_valid_host(self, status, mock_create,
130                                         mock_rollback,
131                                         mock_extend, mock_consume,
132                                         mock_backend_passes):
133        volume = fake_volume.fake_volume_obj(self.context,
134                                             **{'size': 1,
135                                                'previous_status': status})
136        no_valid_backend = exception.NoValidBackend(reason='')
137        mock_backend_passes.side_effect = [no_valid_backend]
138
139        with mock.patch.object(self.manager,
140                               '_set_volume_state_and_notify') as mock_notify:
141            self.manager.extend_volume(self.context, volume, 2,
142                                       'fake_reservation')
143            mock_notify.assert_called_once_with(
144                'extend_volume', {'volume_state': {'status': status,
145                                                   'previous_status': None}},
146                self.context, no_valid_backend, None)
147            mock_rollback.assert_called_once_with(
148                self.context, 'fake_reservation', project_id=volume.project_id)
149            mock_consume.assert_not_called()
150            mock_extend.assert_not_called()
151            mock_create.assert_called_once_with(
152                self.context,
153                message_field.Action.EXTEND_VOLUME,
154                resource_uuid=volume.id,
155                exception=no_valid_backend)
156
157    @mock.patch('cinder.quota.QuotaEngine.expire')
158    def test_clean_expired_reservation(self, mock_clean):
159
160        self.manager._clean_expired_reservation(self.context)
161
162        mock_clean.assert_called_once_with(self.context)
163
164    @mock.patch('cinder.scheduler.driver.Scheduler.'
165                'update_service_capabilities')
166    def test_update_service_capabilities_empty_dict(self, _mock_update_cap):
167        # Test no capabilities passes empty dictionary
168        service = 'fake_service'
169        host = 'fake_host'
170
171        self.manager.update_service_capabilities(self.context,
172                                                 service_name=service,
173                                                 host=host)
174        _mock_update_cap.assert_called_once_with(service, host, {}, None, None)
175
176    @mock.patch('cinder.scheduler.driver.Scheduler.'
177                'update_service_capabilities')
178    def test_update_service_capabilities_correct(self, _mock_update_cap):
179        # Test capabilities passes correctly
180        service = 'fake_service'
181        host = 'fake_host'
182        capabilities = {'fake_capability': 'fake_value'}
183
184        self.manager.update_service_capabilities(self.context,
185                                                 service_name=service,
186                                                 host=host,
187                                                 capabilities=capabilities)
188        _mock_update_cap.assert_called_once_with(service, host, capabilities,
189                                                 None, None)
190
191    @mock.patch('cinder.scheduler.driver.Scheduler.'
192                'notify_service_capabilities')
193    def test_notify_service_capabilities_no_timestamp(self, _mock_notify_cap):
194        """Test old interface that receives host."""
195        service = 'volume'
196        host = 'fake_host'
197        capabilities = {'fake_capability': 'fake_value'}
198
199        self.manager.notify_service_capabilities(self.context,
200                                                 service_name=service,
201                                                 host=host,
202                                                 capabilities=capabilities)
203        _mock_notify_cap.assert_called_once_with(service, host, capabilities,
204                                                 None)
205
206    @mock.patch('cinder.scheduler.driver.Scheduler.'
207                'notify_service_capabilities')
208    def test_notify_service_capabilities_timestamp(self, _mock_notify_cap):
209        """Test new interface that receives backend and timestamp."""
210        service = 'volume'
211        backend = 'fake_cluster'
212        capabilities = {'fake_capability': 'fake_value'}
213
214        timestamp = '1970-01-01T00:00:00.000000'
215
216        self.manager.notify_service_capabilities(self.context,
217                                                 service_name=service,
218                                                 backend=backend,
219                                                 capabilities=capabilities,
220                                                 timestamp=timestamp)
221        _mock_notify_cap.assert_called_once_with(service, backend,
222                                                 capabilities,
223                                                 datetime(1970, 1, 1))
224
225    @mock.patch('cinder.scheduler.driver.Scheduler.schedule_create_volume')
226    @mock.patch('cinder.message.api.API.create')
227    @mock.patch('cinder.db.volume_update')
228    def test_create_volume_exception_puts_volume_in_error_state(
229            self, _mock_volume_update, _mock_message_create,
230            _mock_sched_create):
231        # Test NoValidBackend exception behavior for create_volume.
232        # Puts the volume in 'error' state and eats the exception.
233        _mock_sched_create.side_effect = exception.NoValidBackend(reason="")
234        volume = fake_volume.fake_volume_obj(self.context)
235        request_spec = {'volume_id': volume.id,
236                        'volume': {'id': volume.id, '_name_id': None,
237                                   'metadata': {}, 'admin_metadata': {},
238                                   'glance_metadata': {}}}
239        request_spec_obj = objects.RequestSpec.from_primitives(request_spec)
240
241        self.manager.create_volume(self.context, volume,
242                                   request_spec=request_spec_obj,
243                                   filter_properties={})
244        _mock_volume_update.assert_called_once_with(self.context,
245                                                    volume.id,
246                                                    {'status': 'error'})
247        _mock_sched_create.assert_called_once_with(self.context,
248                                                   request_spec_obj, {})
249
250        _mock_message_create.assert_called_once_with(
251            self.context, message_field.Action.SCHEDULE_ALLOCATE_VOLUME,
252            resource_uuid=volume.id,
253            exception=mock.ANY)
254
255    @mock.patch('cinder.scheduler.driver.Scheduler.schedule_create_volume')
256    @mock.patch('eventlet.sleep')
257    def test_create_volume_no_delay(self, _mock_sleep, _mock_sched_create):
258        volume = fake_volume.fake_volume_obj(self.context)
259
260        request_spec = {'volume_id': volume.id}
261        request_spec_obj = objects.RequestSpec.from_primitives(request_spec)
262
263        self.manager.create_volume(self.context, volume,
264                                   request_spec=request_spec_obj,
265                                   filter_properties={})
266        _mock_sched_create.assert_called_once_with(self.context,
267                                                   request_spec_obj, {})
268        self.assertFalse(_mock_sleep.called)
269
270    @mock.patch('cinder.scheduler.driver.Scheduler.schedule_create_volume')
271    @mock.patch('eventlet.sleep')
272    def test_create_volume_set_worker(self, _mock_sleep, _mock_sched_create):
273        """Make sure that the worker is created when creating a volume."""
274        volume = tests_utils.create_volume(self.context, status='creating')
275
276        request_spec = {'volume_id': volume.id}
277
278        self.manager.create_volume(self.context, volume,
279                                   request_spec=request_spec,
280                                   filter_properties={})
281        volume.set_worker.assert_called_once_with()
282
283    @mock.patch('cinder.scheduler.driver.Scheduler.schedule_create_volume')
284    @mock.patch('cinder.scheduler.driver.Scheduler.is_ready')
285    @mock.patch('eventlet.sleep')
286    def test_create_volume_delay_scheduled_after_3_tries(self, _mock_sleep,
287                                                         _mock_is_ready,
288                                                         _mock_sched_create):
289        self.manager._startup_delay = True
290        volume = fake_volume.fake_volume_obj(self.context)
291
292        request_spec = {'volume_id': volume.id}
293        request_spec_obj = objects.RequestSpec.from_primitives(request_spec)
294
295        _mock_is_ready.side_effect = [False, False, True]
296
297        self.manager.create_volume(self.context, volume,
298                                   request_spec=request_spec_obj,
299                                   filter_properties={})
300        _mock_sched_create.assert_called_once_with(self.context,
301                                                   request_spec_obj, {})
302        calls = [mock.call(1)] * 2
303        _mock_sleep.assert_has_calls(calls)
304        self.assertEqual(2, _mock_sleep.call_count)
305
306    @mock.patch('cinder.scheduler.driver.Scheduler.schedule_create_volume')
307    @mock.patch('cinder.scheduler.driver.Scheduler.is_ready')
308    @mock.patch('eventlet.sleep')
309    def test_create_volume_delay_scheduled_in_1_try(self, _mock_sleep,
310                                                    _mock_is_ready,
311                                                    _mock_sched_create):
312        self.manager._startup_delay = True
313        volume = fake_volume.fake_volume_obj(self.context)
314
315        request_spec = {'volume_id': volume.id}
316        request_spec_obj = objects.RequestSpec.from_primitives(request_spec)
317
318        _mock_is_ready.return_value = True
319
320        self.manager.create_volume(self.context, volume,
321                                   request_spec=request_spec_obj,
322                                   filter_properties={})
323        _mock_sched_create.assert_called_once_with(self.context,
324                                                   request_spec_obj, {})
325        self.assertFalse(_mock_sleep.called)
326
327    @mock.patch('cinder.db.volume_get')
328    @mock.patch('cinder.scheduler.driver.Scheduler.backend_passes_filters')
329    @mock.patch('cinder.db.volume_update')
330    def test_migrate_volume_exception_returns_volume_state(
331            self, _mock_volume_update, _mock_backend_passes,
332            _mock_volume_get):
333        # Test NoValidBackend exception behavior for migrate_volume_to_host.
334        # Puts the volume in 'error_migrating' state and eats the exception.
335        fake_updates = {'migration_status': 'error'}
336        self._test_migrate_volume_exception_returns_volume_state(
337            _mock_volume_update, _mock_backend_passes, _mock_volume_get,
338            'available', fake_updates)
339
340    @mock.patch('cinder.db.volume_get')
341    @mock.patch('cinder.scheduler.driver.Scheduler.backend_passes_filters')
342    @mock.patch('cinder.db.volume_update')
343    def test_migrate_volume_exception_returns_volume_state_maintenance(
344            self, _mock_volume_update, _mock_backend_passes,
345            _mock_volume_get):
346        fake_updates = {'status': 'available',
347                        'migration_status': 'error'}
348        self._test_migrate_volume_exception_returns_volume_state(
349            _mock_volume_update, _mock_backend_passes, _mock_volume_get,
350            'maintenance', fake_updates)
351
352    def _test_migrate_volume_exception_returns_volume_state(
353            self, _mock_volume_update, _mock_backend_passes,
354            _mock_volume_get, status, fake_updates):
355        volume = tests_utils.create_volume(self.context,
356                                           status=status,
357                                           previous_status='available')
358        fake_volume_id = volume.id
359        request_spec = {'volume_id': fake_volume_id}
360        _mock_backend_passes.side_effect = exception.NoValidBackend(reason="")
361        _mock_volume_get.return_value = volume
362
363        self.manager.migrate_volume_to_host(self.context, volume, 'host', True,
364                                            request_spec=request_spec,
365                                            filter_properties={})
366        _mock_volume_update.assert_called_once_with(self.context,
367                                                    fake_volume_id,
368                                                    fake_updates)
369        _mock_backend_passes.assert_called_once_with(self.context, 'host',
370                                                     request_spec, {})
371
372    @mock.patch('cinder.db.volume_update')
373    @mock.patch('cinder.db.volume_attachment_get_all_by_volume_id')
374    @mock.patch('cinder.quota.QUOTAS.rollback')
375    def test_retype_volume_exception_returns_volume_state(
376            self, quota_rollback, _mock_vol_attachment_get, _mock_vol_update):
377        # Test NoValidBackend exception behavior for retype.
378        # Puts the volume in original state and eats the exception.
379        volume = tests_utils.create_volume(self.context,
380                                           status='retyping',
381                                           previous_status='in-use')
382        instance_uuid = '12345678-1234-5678-1234-567812345678'
383        volume_attach = tests_utils.attach_volume(self.context, volume.id,
384                                                  instance_uuid, None,
385                                                  '/dev/fake')
386        _mock_vol_attachment_get.return_value = [volume_attach]
387        reservations = mock.sentinel.reservations
388        request_spec = {'volume_id': volume.id, 'volume_type': {'id': 3},
389                        'migration_policy': 'on-demand',
390                        'quota_reservations': reservations}
391        _mock_vol_update.return_value = {'status': 'in-use'}
392        _mock_find_retype_backend = mock.Mock(
393            side_effect=exception.NoValidBackend(reason=""))
394        orig_retype = self.manager.driver.find_retype_backend
395        self.manager.driver.find_retype_backend = _mock_find_retype_backend
396
397        self.manager.retype(self.context, volume, request_spec=request_spec,
398                            filter_properties={})
399
400        _mock_find_retype_backend.assert_called_once_with(self.context,
401                                                          request_spec, {},
402                                                          'on-demand')
403        quota_rollback.assert_called_once_with(self.context, reservations)
404        _mock_vol_update.assert_called_once_with(self.context, volume.id,
405                                                 {'status': 'in-use'})
406        self.manager.driver.find_retype_host = orig_retype
407
408    def test_do_cleanup(self):
409        vol = tests_utils.create_volume(self.context, status='creating')
410        self.manager._do_cleanup(self.context, vol)
411
412        vol.refresh()
413        self.assertEqual('error', vol.status)
414
415    @mock.patch('cinder.scheduler.rpcapi.SchedulerAPI'
416                '.determine_rpc_version_cap', mock.Mock(return_value='2.0'))
417    def test_upgrading_cloud(self):
418        self.assertTrue(self.manager.upgrading_cloud)
419
420    @mock.patch('cinder.scheduler.rpcapi.SchedulerAPI'
421                '.determine_rpc_version_cap')
422    def test_upgrading_cloud_not(self, cap_mock):
423        cap_mock.return_value = self.manager.RPC_API_VERSION
424        self.assertFalse(self.manager.upgrading_cloud)
425
426    def test_cleanup_destination_scheduler(self):
427        service = objects.Service(id=1, host='hostname',
428                                  binary='cinder-scheduler')
429        result = self.manager._cleanup_destination(None, service)
430        expected = self.manager.sch_api.do_cleanup, None, service.host
431        self.assertEqual(expected, result)
432
433    def test_cleanup_destination_volume(self):
434        service = objects.Service(id=1, host='hostname', cluster_name=None,
435                                  binary=constants.VOLUME_BINARY)
436        result = self.manager._cleanup_destination(None, service)
437        expected = self.manager.volume_api.do_cleanup, service, service.host
438        self.assertEqual(expected, result)
439
440    def test_cleanup_destination_volume_cluster_cache_hit(self):
441        cluster = objects.Cluster(id=1, name='mycluster',
442                                  binary=constants.VOLUME_BINARY)
443        service = objects.Service(id=2, host='hostname',
444                                  cluster_name=cluster.name,
445                                  binary=constants.VOLUME_BINARY)
446        cluster_cache = {'cinder-volume': {'mycluster': cluster}}
447        result = self.manager._cleanup_destination(cluster_cache, service)
448        expected = self.manager.volume_api.do_cleanup, cluster, cluster.name
449        self.assertEqual(expected, result)
450
451    @mock.patch('cinder.objects.Cluster.get_by_id')
452    def test_cleanup_destination_volume_cluster_cache_miss(self, get_mock):
453        cluster = objects.Cluster(id=1, name='mycluster',
454                                  binary=constants.VOLUME_BINARY)
455        service = objects.Service(self.context,
456                                  id=2, host='hostname',
457                                  cluster_name=cluster.name,
458                                  binary=constants.VOLUME_BINARY)
459        get_mock.return_value = cluster
460        cluster_cache = collections.defaultdict(dict)
461        result = self.manager._cleanup_destination(cluster_cache, service)
462        expected = self.manager.volume_api.do_cleanup, cluster, cluster.name
463        self.assertEqual(expected, result)
464
465    @mock.patch('cinder.scheduler.manager.SchedulerManager.upgrading_cloud')
466    def test_work_cleanup_upgrading(self, upgrading_mock):
467        cleanup_request = objects.CleanupRequest(host='myhost')
468        upgrading_mock.return_value = True
469        self.assertRaises(exception.UnavailableDuringUpgrade,
470                          self.manager.work_cleanup,
471                          self.context,
472                          cleanup_request)
473
474    @mock.patch('cinder.objects.Cluster.is_up', True)
475    @mock.patch('cinder.objects.Service.is_up', False)
476    @mock.patch('cinder.scheduler.rpcapi.SchedulerAPI.do_cleanup')
477    @mock.patch('cinder.volume.rpcapi.VolumeAPI.do_cleanup')
478    @mock.patch('cinder.objects.ServiceList.get_all')
479    def test_work_cleanup(self, get_mock, vol_clean_mock, sch_clean_mock):
480        args = dict(service_id=1, cluster_name='cluster_name', host='host',
481                    binary=constants.VOLUME_BINARY, is_up=False, disabled=True,
482                    resource_id=fake.VOLUME_ID, resource_type='Volume')
483
484        cluster = objects.Cluster(id=1, name=args['cluster_name'],
485                                  binary=constants.VOLUME_BINARY)
486        services = [objects.Service(self.context,
487                                    id=2, host='hostname',
488                                    cluster_name=cluster.name,
489                                    binary=constants.VOLUME_BINARY,
490                                    cluster=cluster),
491                    objects.Service(self.context,
492                                    id=3, host='hostname',
493                                    cluster_name=None,
494                                    binary=constants.SCHEDULER_BINARY),
495                    objects.Service(self.context,
496                                    id=4, host='hostname',
497                                    cluster_name=None,
498                                    binary=constants.VOLUME_BINARY)]
499        get_mock.return_value = services
500
501        cleanup_request = objects.CleanupRequest(self.context, **args)
502        res = self.manager.work_cleanup(self.context, cleanup_request)
503        self.assertEqual((services[:2], services[2:]), res)
504        self.assertEqual(1, vol_clean_mock.call_count)
505        self.assertEqual(1, sch_clean_mock.call_count)
506
507
508class SchedulerTestCase(test.TestCase):
509    """Test case for base scheduler driver class."""
510
511    # So we can subclass this test and re-use tests if we need.
512    driver_cls = driver.Scheduler
513
514    def setUp(self):
515        super(SchedulerTestCase, self).setUp()
516        self.driver = self.driver_cls()
517        self.context = context.RequestContext(fake.USER_ID, fake.PROJECT_ID)
518        self.topic = 'fake_topic'
519
520    @mock.patch('cinder.scheduler.driver.Scheduler.'
521                'update_service_capabilities')
522    def test_update_service_capabilities(self, _mock_update_cap):
523        service_name = 'fake_service'
524        host = 'fake_host'
525        capabilities = {'fake_capability': 'fake_value'}
526        self.driver.update_service_capabilities(service_name, host,
527                                                capabilities, None)
528        _mock_update_cap.assert_called_once_with(service_name, host,
529                                                 capabilities, None)
530
531    @mock.patch('cinder.scheduler.host_manager.HostManager.'
532                'has_all_capabilities', return_value=False)
533    def test_is_ready(self, _mock_has_caps):
534        ready = self.driver.is_ready()
535        _mock_has_caps.assert_called_once_with()
536        self.assertFalse(ready)
537
538
539class SchedulerDriverBaseTestCase(SchedulerTestCase):
540    """Test schedule driver class.
541
542    Test cases for base scheduler driver class methods
543    that will fail if the driver is changed.
544    """
545
546    def test_unimplemented_schedule(self):
547        fake_args = (1, 2, 3)
548        fake_kwargs = {'cat': 'meow'}
549
550        self.assertRaises(NotImplementedError, self.driver.schedule,
551                          self.context, self.topic, 'schedule_something',
552                          *fake_args, **fake_kwargs)
553
554
555class SchedulerDriverModuleTestCase(test.TestCase):
556    """Test case for scheduler driver module methods."""
557
558    def setUp(self):
559        super(SchedulerDriverModuleTestCase, self).setUp()
560        self.context = context.RequestContext(fake.USER_ID, fake.PROJECT_ID)
561
562    @mock.patch('cinder.db.volume_update')
563    @mock.patch('cinder.objects.volume.Volume.get_by_id')
564    def test_volume_host_update_db(self, _mock_volume_get, _mock_vol_update):
565        volume = fake_volume.fake_volume_obj(self.context)
566        _mock_volume_get.return_value = volume
567
568        driver.volume_update_db(self.context, volume.id, 'fake_host',
569                                'fake_cluster')
570        scheduled_at = volume.scheduled_at.replace(tzinfo=None)
571        _mock_vol_update.assert_called_once_with(
572            self.context, volume.id, {'host': 'fake_host',
573                                      'cluster_name': 'fake_cluster',
574                                      'scheduled_at': scheduled_at})
575