1# Copyright 2012 Josh Durgin
2# Copyright 2013 Canonical Ltd.
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
17import math
18import os
19import tempfile
20import uuid
21
22import castellan
23import ddt
24import mock
25from mock import call
26from oslo_utils import imageutils
27from oslo_utils import units
28
29from cinder import context
30from cinder import db
31from cinder import exception
32import cinder.image.glance
33from cinder.image import image_utils
34from cinder import objects
35from cinder.objects import fields
36from cinder import test
37from cinder.tests.unit import fake_constants as fake
38from cinder.tests.unit import fake_snapshot
39from cinder.tests.unit import fake_volume
40from cinder.tests.unit.keymgr import fake as fake_keymgr
41from cinder.tests.unit import utils
42from cinder.tests.unit.volume import test_driver
43from cinder.volume import configuration as conf
44import cinder.volume.drivers.rbd as driver
45from cinder.volume.flows.manager import create_volume
46
47
48# This is used to collect raised exceptions so that tests may check what was
49# raised.
50# NOTE: this must be initialised in test setUp().
51RAISED_EXCEPTIONS = []
52
53
54class MockException(Exception):
55
56    def __init__(self, *args, **kwargs):
57        RAISED_EXCEPTIONS.append(self.__class__)
58
59
60class MockImageNotFoundException(MockException):
61    """Used as mock for rbd.ImageNotFound."""
62
63
64class MockImageBusyException(MockException):
65    """Used as mock for rbd.ImageBusy."""
66
67
68class MockImageExistsException(MockException):
69    """Used as mock for rbd.ImageExists."""
70
71
72class MockOSErrorException(MockException):
73    """Used as mock for rbd.OSError."""
74
75
76class KeyObject(object):
77    def get_encoded(arg):
78        return "asdf".encode('utf-8')
79
80
81def common_mocks(f):
82    """Decorator to set mocks common to all tests.
83
84    The point of doing these mocks here is so that we don't accidentally set
85    mocks that can't/don't get unset.
86    """
87    def _FakeRetrying(wait_func=None,
88                      original_retrying = driver.utils.retrying.Retrying,
89                      *args, **kwargs):
90        return original_retrying(wait_func=lambda *a, **k: 0,
91                                 *args, **kwargs)
92
93    def _common_inner_inner1(inst, *args, **kwargs):
94        @mock.patch('retrying.Retrying', _FakeRetrying)
95        @mock.patch.object(driver.RBDDriver, '_get_usage_info')
96        @mock.patch('cinder.volume.drivers.rbd.RBDVolumeProxy')
97        @mock.patch('cinder.volume.drivers.rbd.RADOSClient')
98        @mock.patch('cinder.backup.drivers.ceph.rbd')
99        @mock.patch('cinder.backup.drivers.ceph.rados')
100        def _common_inner_inner2(mock_rados, mock_rbd, mock_client,
101                                 mock_proxy, mock_usage_info):
102            inst.mock_rbd = mock_rbd
103            inst.mock_rados = mock_rados
104            inst.mock_client = mock_client
105            inst.mock_proxy = mock_proxy
106            inst.mock_rbd.RBD.Error = Exception
107            inst.mock_rados.Error = Exception
108            inst.mock_rbd.ImageBusy = MockImageBusyException
109            inst.mock_rbd.ImageNotFound = MockImageNotFoundException
110            inst.mock_rbd.ImageExists = MockImageExistsException
111            inst.mock_rbd.InvalidArgument = MockImageNotFoundException
112
113            inst.driver.rbd = inst.mock_rbd
114            inst.driver.rados = inst.mock_rados
115            return f(inst, *args, **kwargs)
116
117        return _common_inner_inner2()
118
119    return _common_inner_inner1
120
121
122CEPH_MON_DUMP = r"""dumped monmap epoch 1
123{ "epoch": 1,
124  "fsid": "33630410-6d93-4d66-8e42-3b953cf194aa",
125  "modified": "2013-05-22 17:44:56.343618",
126  "created": "2013-05-22 17:44:56.343618",
127  "mons": [
128        { "rank": 0,
129          "name": "a",
130          "addr": "[::1]:6789\/0"},
131        { "rank": 1,
132          "name": "b",
133          "addr": "[::1]:6790\/0"},
134        { "rank": 2,
135          "name": "c",
136          "addr": "[::1]:6791\/0"},
137        { "rank": 3,
138          "name": "d",
139          "addr": "127.0.0.1:6792\/0"},
140        { "rank": 4,
141          "name": "e",
142          "addr": "example.com:6791\/0"}],
143  "quorum": [
144        0,
145        1,
146        2]}
147"""
148
149
150class MockDriverConfig(object):
151    def __init__(self, **kwargs):
152        my_dict = vars(self)
153        my_dict.update(kwargs)
154        my_dict.setdefault('max_over_subscription_ratio', 1.0)
155        my_dict.setdefault('reserved_percentage', 0)
156        my_dict.setdefault('volume_backend_name', 'RBD')
157        my_dict.setdefault('_default', None)
158
159    def __call__(self, value):
160        return getattr(self, value, self._default)
161
162
163def mock_driver_configuration(value):
164    if value == 'max_over_subscription_ratio':
165        return 1.0
166    if value == 'reserved_percentage':
167        return 0
168    return 'RBD'
169
170
171@ddt.ddt
172class RBDTestCase(test.TestCase):
173
174    def setUp(self):
175        global RAISED_EXCEPTIONS
176        RAISED_EXCEPTIONS = []
177        super(RBDTestCase, self).setUp()
178
179        self.cfg = mock.Mock(spec=conf.Configuration)
180        self.cfg.image_conversion_dir = None
181        self.cfg.rbd_cluster_name = 'nondefault'
182        self.cfg.rbd_pool = 'rbd'
183        self.cfg.rbd_ceph_conf = '/usr/local/etc/ceph/my_ceph.conf'
184        self.cfg.rbd_keyring_conf = '/usr/local/etc/ceph/my_ceph.client.keyring'
185        self.cfg.rbd_secret_uuid = None
186        self.cfg.rbd_user = 'cinder'
187        self.cfg.volume_backend_name = None
188        self.cfg.volume_dd_blocksize = '1M'
189        self.cfg.rbd_store_chunk_size = 4
190        self.cfg.rados_connection_retries = 3
191        self.cfg.rados_connection_interval = 5
192        self.cfg.backup_use_temp_snapshot = False
193
194        mock_exec = mock.Mock()
195        mock_exec.return_value = ('', '')
196
197        self.driver = driver.RBDDriver(execute=mock_exec,
198                                       configuration=self.cfg)
199        self.driver.set_initialized()
200
201        self.context = context.get_admin_context()
202
203        self.volume_a = fake_volume.fake_volume_obj(
204            self.context,
205            **{'name': u'volume-0000000a',
206               'id': '4c39c3c7-168f-4b32-b585-77f1b3bf0a38',
207               'size': 10})
208
209        self.volume_b = fake_volume.fake_volume_obj(
210            self.context,
211            **{'name': u'volume-0000000b',
212               'id': '0c7d1f44-5a06-403f-bb82-ae7ad0d693a6',
213               'size': 10})
214
215        self.volume_c = fake_volume.fake_volume_obj(
216            self.context,
217            **{'name': u'volume-0000000a',
218               'id': '55555555-222f-4b32-b585-9991b3bf0a99',
219               'size': 12,
220               'encryption_key_id': fake.ENCRYPTION_KEY_ID})
221
222        self.snapshot = fake_snapshot.fake_snapshot_obj(
223            self.context, name='snapshot-0000000a')
224
225        self.snapshot_b = fake_snapshot.fake_snapshot_obj(
226            self.context,
227            **{'name': u'snapshot-0000000n',
228               'expected_attrs': ['volume'],
229               'volume': {'id': fake.VOLUME_ID,
230                          'name': 'cinder-volume',
231                          'size': 128,
232                          'host': 'host@fakebackend#fakepool'}
233               })
234
235    @ddt.data({'cluster_name': None, 'pool_name': 'rbd'},
236              {'cluster_name': 'volumes', 'pool_name': None})
237    @ddt.unpack
238    def test_min_config(self, cluster_name, pool_name):
239        self.cfg.rbd_cluster_name = cluster_name
240        self.cfg.rbd_pool = pool_name
241
242        with mock.patch('cinder.volume.drivers.rbd.rados'):
243            self.assertRaises(exception.InvalidConfigurationValue,
244                              self.driver.check_for_setup_error)
245
246    def test_parse_replication_config_empty(self):
247        self.driver._parse_replication_configs([])
248        self.assertEqual([], self.driver._replication_targets)
249
250    def test_parse_replication_config_missing(self):
251        """Parsing replication_device without required backend_id."""
252        cfg = [{'conf': '/usr/local/etc/ceph/secondary.conf'}]
253        self.assertRaises(exception.InvalidConfigurationValue,
254                          self.driver._parse_replication_configs,
255                          cfg)
256
257    def test_parse_replication_config_defaults(self):
258        """Parsing replication_device with default conf and user."""
259        cfg = [{'backend_id': 'secondary-backend'}]
260        expected = [{'name': 'secondary-backend',
261                     'conf': '/usr/local/etc/ceph/secondary-backend.conf',
262                     'user': 'cinder'}]
263        self.driver._parse_replication_configs(cfg)
264        self.assertEqual(expected, self.driver._replication_targets)
265
266    @ddt.data(1, 2)
267    def test_parse_replication_config(self, num_targets):
268        cfg = [{'backend_id': 'secondary-backend',
269                'conf': 'foo',
270                'user': 'bar'},
271               {'backend_id': 'tertiary-backend'}]
272        expected = [{'name': 'secondary-backend',
273                     'conf': 'foo',
274                     'user': 'bar'},
275                    {'name': 'tertiary-backend',
276                     'conf': '/usr/local/etc/ceph/tertiary-backend.conf',
277                     'user': 'cinder'}]
278        self.driver._parse_replication_configs(cfg[:num_targets])
279        self.assertEqual(expected[:num_targets],
280                         self.driver._replication_targets)
281
282    def test_do_setup_replication_disabled(self):
283        with mock.patch.object(self.driver.configuration, 'safe_get',
284                               return_value=None):
285            self.driver.do_setup(self.context)
286            self.assertFalse(self.driver._is_replication_enabled)
287            self.assertEqual([], self.driver._replication_targets)
288            self.assertEqual([], self.driver._target_names)
289            self.assertEqual({'name': self.cfg.rbd_cluster_name,
290                              'conf': self.cfg.rbd_ceph_conf,
291                              'user': self.cfg.rbd_user},
292                             self.driver._active_config)
293
294    def test_do_setup_replication(self):
295        cfg = [{'backend_id': 'secondary-backend',
296                'conf': 'foo',
297                'user': 'bar'}]
298        expected = [{'name': 'secondary-backend',
299                     'conf': 'foo',
300                     'user': 'bar'}]
301
302        with mock.patch.object(self.driver.configuration, 'safe_get',
303                               return_value=cfg):
304            self.driver.do_setup(self.context)
305            self.assertTrue(self.driver._is_replication_enabled)
306            self.assertEqual(expected, self.driver._replication_targets)
307            self.assertEqual({'name': self.cfg.rbd_cluster_name,
308                              'conf': self.cfg.rbd_ceph_conf,
309                              'user': self.cfg.rbd_user},
310                             self.driver._active_config)
311
312    def test_do_setup_replication_failed_over(self):
313        cfg = [{'backend_id': 'secondary-backend',
314                'conf': 'foo',
315                'user': 'bar'}]
316        expected = [{'name': 'secondary-backend',
317                     'conf': 'foo',
318                     'user': 'bar'}]
319        self.driver._active_backend_id = 'secondary-backend'
320
321        with mock.patch.object(self.driver.configuration, 'safe_get',
322                               return_value=cfg):
323            self.driver.do_setup(self.context)
324            self.assertTrue(self.driver._is_replication_enabled)
325            self.assertEqual(expected, self.driver._replication_targets)
326            self.assertEqual(expected[0], self.driver._active_config)
327
328    def test_do_setup_replication_failed_over_unknown(self):
329        cfg = [{'backend_id': 'secondary-backend',
330                'conf': 'foo',
331                'user': 'bar'}]
332        self.driver._active_backend_id = 'unknown-backend'
333
334        with mock.patch.object(self.driver.configuration, 'safe_get',
335                               return_value=cfg):
336            self.assertRaises(exception.InvalidReplicationTarget,
337                              self.driver.do_setup,
338                              self.context)
339
340    @mock.patch.object(driver.RBDDriver, '_enable_replication',
341                       return_value=mock.sentinel.volume_update)
342    def test_enable_replication_if_needed_replicated_volume(self, mock_enable):
343        self.volume_a.volume_type = fake_volume.fake_volume_type_obj(
344            self.context,
345            id=fake.VOLUME_TYPE_ID,
346            extra_specs={'replication_enabled': '<is> True'})
347        res = self.driver._enable_replication_if_needed(self.volume_a)
348        self.assertEqual(mock.sentinel.volume_update, res)
349        mock_enable.assert_called_once_with(self.volume_a)
350
351    @ddt.data(False, True)
352    @mock.patch.object(driver.RBDDriver, '_enable_replication')
353    def test_enable_replication_if_needed_non_replicated(self, enabled,
354                                                         mock_enable):
355        self.driver._is_replication_enabled = enabled
356        res = self.driver._enable_replication_if_needed(self.volume_a)
357        if enabled:
358            expect = {'replication_status': fields.ReplicationStatus.DISABLED}
359        else:
360            expect = None
361        self.assertEqual(expect, res)
362        mock_enable.assert_not_called()
363
364    @ddt.data([True, False], [False, False], [True, True])
365    @ddt.unpack
366    @common_mocks
367    def test_enable_replication(self, exclusive_lock_enabled,
368                                journaling_enabled):
369        """Test _enable_replication method.
370
371        We want to confirm that if the Ceph backend has globally enabled
372        'exclusive_lock' and 'journaling'. we don't try to enable them
373        again and we properly indicate with our return value that they were
374        already enabled.
375        'journaling' depends on 'exclusive_lock', so if 'exclusive-lock'
376        is disabled, 'journaling' can't be enabled so the '[False. True]'
377        case is impossible.
378        In this test case, there are three test scenarios:
379        1. 'exclusive_lock' and 'journaling' both enabled,
380        'image.features()' will not be called.
381        2. 'exclusive_lock' enabled, 'journaling' disabled,
382        'image.features()' will be only called for 'journaling'.
383        3. 'exclusice_lock' and 'journaling' are both disabled,
384        'image.features()'will be both called for 'exclusive-lock' and
385        'journaling' in this order.
386        """
387        journaling_feat = 1
388        exclusive_lock_feat = 2
389        self.driver.rbd.RBD_FEATURE_JOURNALING = journaling_feat
390        self.driver.rbd.RBD_FEATURE_EXCLUSIVE_LOCK = exclusive_lock_feat
391        image = self.mock_proxy.return_value.__enter__.return_value
392        image.features.return_value = 0
393        if exclusive_lock_enabled:
394            image.features.return_value += exclusive_lock_feat
395        if journaling_enabled:
396            image.features.return_value += journaling_feat
397        journaling_status = str(journaling_enabled).lower()
398        exclusive_lock_status = str(exclusive_lock_enabled).lower()
399        expected = {
400            'replication_driver_data': ('{"had_exclusive_lock":%s,'
401                                        '"had_journaling":%s}' %
402                                        (exclusive_lock_status,
403                                         journaling_status)),
404            'replication_status': 'enabled',
405        }
406        res = self.driver._enable_replication(self.volume_a)
407        self.assertEqual(expected, res)
408        if exclusive_lock_enabled and journaling_enabled:
409            image.update_features.assert_not_called()
410        elif exclusive_lock_enabled and not journaling_enabled:
411            image.update_features.assert_called_once_with(journaling_feat,
412                                                          True)
413        else:
414            calls = [call(exclusive_lock_feat, True),
415                     call(journaling_feat, True)]
416            image.update_features.assert_has_calls(calls, any_order=False)
417        image.mirror_image_enable.assert_called_once_with()
418
419    @ddt.data(['false', 'true'], ['true', 'true'], ['false', 'false'])
420    @ddt.unpack
421    @common_mocks
422    def test_disable_replication(self, had_journaling, had_exclusive_lock):
423        driver_data = ('{"had_journaling": %s,"had_exclusive_lock": %s}' %
424                       (had_journaling, had_exclusive_lock))
425        self.volume_a.replication_driver_data = driver_data
426        image = self.mock_proxy.return_value.__enter__.return_value
427
428        res = self.driver._disable_replication(self.volume_a)
429        expected = {'replication_status': fields.ReplicationStatus.DISABLED,
430                    'replication_driver_data': None}
431        self.assertEqual(expected, res)
432        image.mirror_image_disable.assert_called_once_with(False)
433
434        if had_journaling == 'true' and had_exclusive_lock == 'true':
435            image.update_features.assert_not_called()
436        elif had_journaling == 'false' and had_exclusive_lock == 'true':
437            image.update_features.assert_called_once_with(
438                self.driver.rbd.RBD_FEATURE_JOURNALING, False)
439        else:
440            calls = [call(self.driver.rbd.RBD_FEATURE_JOURNALING, False),
441                     call(self.driver.rbd.RBD_FEATURE_EXCLUSIVE_LOCK,
442                          False)]
443            image.update_features.assert_has_calls(calls, any_order=False)
444
445    @common_mocks
446    @mock.patch.object(driver.RBDDriver, '_enable_replication')
447    def test_create_volume(self, mock_enable_repl):
448        client = self.mock_client.return_value
449        client.__enter__.return_value = client
450
451        res = self.driver.create_volume(self.volume_a)
452
453        self.assertIsNone(res)
454        chunk_size = self.cfg.rbd_store_chunk_size * units.Mi
455        order = int(math.log(chunk_size, 2))
456        args = [client.ioctx, str(self.volume_a.name),
457                self.volume_a.size * units.Gi, order]
458        kwargs = {'old_format': False,
459                  'features': client.features}
460        self.mock_rbd.RBD.return_value.create.assert_called_once_with(
461            *args, **kwargs)
462        client.__enter__.assert_called_once_with()
463        client.__exit__.assert_called_once_with(None, None, None)
464        mock_enable_repl.assert_not_called()
465
466    @common_mocks
467    @mock.patch.object(driver.RBDDriver, '_enable_replication')
468    def test_create_volume_replicated(self, mock_enable_repl):
469        self.volume_a.volume_type = fake_volume.fake_volume_type_obj(
470            self.context,
471            id=fake.VOLUME_TYPE_ID,
472            extra_specs={'replication_enabled': '<is> True'})
473
474        client = self.mock_client.return_value
475        client.__enter__.return_value = client
476
477        expected_update = {
478            'replication_status': 'enabled',
479            'replication_driver_data': '{"had_journaling": false}'
480        }
481        mock_enable_repl.return_value = expected_update
482
483        res = self.driver.create_volume(self.volume_a)
484        self.assertEqual(expected_update, res)
485        mock_enable_repl.assert_called_once_with(self.volume_a)
486
487        chunk_size = self.cfg.rbd_store_chunk_size * units.Mi
488        order = int(math.log(chunk_size, 2))
489        self.mock_rbd.RBD.return_value.create.assert_called_once_with(
490            client.ioctx, self.volume_a.name, self.volume_a.size * units.Gi,
491            order, old_format=False, features=client.features)
492
493        client.__enter__.assert_called_once_with()
494        client.__exit__.assert_called_once_with(None, None, None)
495
496    @common_mocks
497    def test_manage_existing_get_size(self):
498        with mock.patch.object(self.driver.rbd.Image(), 'size') as \
499                mock_rbd_image_size:
500            with mock.patch.object(self.driver.rbd.Image(), 'close') \
501                    as mock_rbd_image_close:
502                mock_rbd_image_size.return_value = 2 * units.Gi
503                existing_ref = {'source-name': self.volume_a.name}
504                return_size = self.driver.manage_existing_get_size(
505                    self.volume_a,
506                    existing_ref)
507                self.assertEqual(2, return_size)
508                mock_rbd_image_size.assert_called_once_with()
509                mock_rbd_image_close.assert_called_once_with()
510
511    @common_mocks
512    def test_manage_existing_get_non_integer_size(self):
513        rbd_image = self.driver.rbd.Image.return_value
514        rbd_image.size.return_value = int(1.75 * units.Gi)
515        existing_ref = {'source-name': self.volume_a.name}
516        return_size = self.driver.manage_existing_get_size(self.volume_a,
517                                                           existing_ref)
518        self.assertEqual(2, return_size)
519        rbd_image.size.assert_called_once_with()
520        rbd_image.close.assert_called_once_with()
521
522    @common_mocks
523    def test_manage_existing_get_invalid_size(self):
524
525        with mock.patch.object(self.driver.rbd.Image(), 'size') as \
526                mock_rbd_image_size:
527            with mock.patch.object(self.driver.rbd.Image(), 'close') \
528                    as mock_rbd_image_close:
529                mock_rbd_image_size.return_value = 'abcd'
530                existing_ref = {'source-name': self.volume_a.name}
531                self.assertRaises(exception.VolumeBackendAPIException,
532                                  self.driver.manage_existing_get_size,
533                                  self.volume_a, existing_ref)
534
535                mock_rbd_image_size.assert_called_once_with()
536                mock_rbd_image_close.assert_called_once_with()
537
538    @common_mocks
539    def test_manage_existing(self):
540        client = self.mock_client.return_value
541        client.__enter__.return_value = client
542
543        with mock.patch.object(self.driver.rbd.RBD(), 'rename') as \
544                mock_rbd_image_rename:
545            exist_volume = 'vol-exist'
546            existing_ref = {'source-name': exist_volume}
547            mock_rbd_image_rename.return_value = 0
548            self.driver.manage_existing(self.volume_a, existing_ref)
549            mock_rbd_image_rename.assert_called_with(
550                client.ioctx,
551                exist_volume,
552                self.volume_a.name)
553
554    @common_mocks
555    def test_manage_existing_with_exist_rbd_image(self):
556        client = self.mock_client.return_value
557        client.__enter__.return_value = client
558
559        self.mock_rbd.RBD.return_value.rename.side_effect = (
560            MockImageExistsException)
561
562        exist_volume = 'vol-exist'
563        existing_ref = {'source-name': exist_volume}
564        self.assertRaises(self.mock_rbd.ImageExists,
565                          self.driver.manage_existing,
566                          self.volume_a, existing_ref)
567
568        # Make sure the exception was raised
569        self.assertEqual(RAISED_EXCEPTIONS,
570                         [self.mock_rbd.ImageExists])
571
572    @common_mocks
573    def test_manage_existing_with_invalid_rbd_image(self):
574        self.mock_rbd.Image.side_effect = self.mock_rbd.ImageNotFound
575
576        invalid_volume = 'vol-invalid'
577        invalid_ref = {'source-name': invalid_volume}
578
579        self.assertRaises(exception.ManageExistingInvalidReference,
580                          self.driver.manage_existing_get_size,
581                          self.volume_a, invalid_ref)
582        # Make sure the exception was raised
583        self.assertEqual([self.mock_rbd.ImageNotFound],
584                         RAISED_EXCEPTIONS)
585
586    @common_mocks
587    @mock.patch.object(driver.RBDDriver, '_get_image_status')
588    def test_get_manageable_volumes(self, mock_get_image_status):
589        cinder_vols = [{'id': '00000000-0000-0000-0000-000000000000'}]
590        vols = ['volume-00000000-0000-0000-0000-000000000000', 'vol1', 'vol2']
591        self.mock_rbd.RBD.return_value.list.return_value = vols
592        image = self.mock_proxy.return_value.__enter__.return_value
593        image.size.side_effect = [2 * units.Gi, 4 * units.Gi, 6 * units.Gi]
594        mock_get_image_status.side_effect = [
595            {'watchers': []},
596            {'watchers': [{"address": "192.168.120.61:0\/3012034728",
597                           "client": 44431941, "cookie": 94077162321152}]}]
598        res = self.driver.get_manageable_volumes(
599            cinder_vols, None, 1000, 0, ['size'], ['asc'])
600        exp = [{'size': 2, 'reason_not_safe': 'already managed',
601                'extra_info': None, 'safe_to_manage': False,
602                'reference': {'source-name':
603                              'volume-00000000-0000-0000-0000-000000000000'},
604                'cinder_id': '00000000-0000-0000-0000-000000000000'},
605               {'size': 4, 'reason_not_safe': None,
606                'safe_to_manage': True, 'reference': {'source-name': 'vol1'},
607                'cinder_id': None, 'extra_info': None},
608               {'size': 6, 'reason_not_safe': 'volume in use',
609                'safe_to_manage': False, 'reference': {'source-name': 'vol2'},
610                'cinder_id': None, 'extra_info': None}]
611        self.assertEqual(exp, res)
612
613    @common_mocks
614    def test_delete_backup_snaps(self):
615        self.driver.rbd.Image.remove_snap = mock.Mock()
616        with mock.patch.object(self.driver, '_get_backup_snaps') as \
617                mock_get_backup_snaps:
618            mock_get_backup_snaps.return_value = [{'name': 'snap1'}]
619            rbd_image = self.driver.rbd.Image()
620            self.driver._delete_backup_snaps(rbd_image)
621            mock_get_backup_snaps.assert_called_once_with(rbd_image)
622            self.assertTrue(
623                self.driver.rbd.Image.return_value.remove_snap.called)
624
625    @common_mocks
626    def test_delete_volume(self):
627        client = self.mock_client.return_value
628
629        self.driver.rbd.Image.return_value.list_snaps.return_value = []
630
631        with mock.patch.object(self.driver, '_get_clone_info') as \
632                mock_get_clone_info:
633            with mock.patch.object(self.driver, '_delete_backup_snaps') as \
634                    mock_delete_backup_snaps:
635                mock_get_clone_info.return_value = (None, None, None)
636
637                self.driver.delete_volume(self.volume_a)
638
639                mock_get_clone_info.assert_called_once_with(
640                    self.mock_rbd.Image.return_value,
641                    self.volume_a.name,
642                    None)
643                (self.driver.rbd.Image.return_value
644                    .list_snaps.assert_called_once_with())
645                client.__enter__.assert_called_once_with()
646                client.__exit__.assert_called_once_with(None, None, None)
647                mock_delete_backup_snaps.assert_called_once_with(
648                    self.mock_rbd.Image.return_value)
649                self.assertFalse(
650                    self.driver.rbd.Image.return_value.unprotect_snap.called)
651                self.assertEqual(
652                    1, self.driver.rbd.RBD.return_value.remove.call_count)
653
654    @common_mocks
655    def delete_volume_not_found(self):
656        self.mock_rbd.Image.side_effect = self.mock_rbd.ImageNotFound
657        self.assertIsNone(self.driver.delete_volume(self.volume_a))
658        self.mock_rbd.Image.assert_called_once_with()
659        # Make sure the exception was raised
660        self.assertEqual(RAISED_EXCEPTIONS, [self.mock_rbd.ImageNotFound])
661
662    @common_mocks
663    def test_delete_busy_volume(self):
664        self.mock_rbd.Image.return_value.list_snaps.return_value = []
665
666        self.mock_rbd.RBD.return_value.remove.side_effect = (
667            self.mock_rbd.ImageBusy)
668
669        with mock.patch.object(self.driver, '_get_clone_info') as \
670                mock_get_clone_info:
671            mock_get_clone_info.return_value = (None, None, None)
672            with mock.patch.object(self.driver, '_delete_backup_snaps') as \
673                    mock_delete_backup_snaps:
674                with mock.patch.object(driver, 'RADOSClient') as \
675                        mock_rados_client:
676                    self.assertRaises(exception.VolumeIsBusy,
677                                      self.driver.delete_volume, self.volume_a)
678
679                    mock_get_clone_info.assert_called_once_with(
680                        self.mock_rbd.Image.return_value,
681                        self.volume_a.name,
682                        None)
683                    (self.mock_rbd.Image.return_value.list_snaps
684                     .assert_called_once_with())
685                    mock_rados_client.assert_called_once_with(self.driver)
686                    mock_delete_backup_snaps.assert_called_once_with(
687                        self.mock_rbd.Image.return_value)
688                    self.assertFalse(
689                        self.mock_rbd.Image.return_value.unprotect_snap.called)
690                    self.assertEqual(
691                        3, self.mock_rbd.RBD.return_value.remove.call_count)
692                    self.assertEqual(3, len(RAISED_EXCEPTIONS))
693                    # Make sure the exception was raised
694                    self.assertIn(self.mock_rbd.ImageBusy, RAISED_EXCEPTIONS)
695
696    @common_mocks
697    def test_delete_volume_not_found(self):
698        self.mock_rbd.Image.return_value.list_snaps.return_value = []
699
700        self.mock_rbd.RBD.return_value.remove.side_effect = (
701            self.mock_rbd.ImageNotFound)
702
703        with mock.patch.object(self.driver, '_get_clone_info') as \
704                mock_get_clone_info:
705            mock_get_clone_info.return_value = (None, None, None)
706            with mock.patch.object(self.driver, '_delete_backup_snaps') as \
707                    mock_delete_backup_snaps:
708                with mock.patch.object(driver, 'RADOSClient') as \
709                        mock_rados_client:
710                    self.assertIsNone(self.driver.delete_volume(self.volume_a))
711                    mock_get_clone_info.assert_called_once_with(
712                        self.mock_rbd.Image.return_value,
713                        self.volume_a.name,
714                        None)
715                    (self.mock_rbd.Image.return_value.list_snaps
716                     .assert_called_once_with())
717                    mock_rados_client.assert_called_once_with(self.driver)
718                    mock_delete_backup_snaps.assert_called_once_with(
719                        self.mock_rbd.Image.return_value)
720                    self.assertFalse(
721                        self.mock_rbd.Image.return_value.unprotect_snap.called)
722                    self.assertEqual(
723                        1, self.mock_rbd.RBD.return_value.remove.call_count)
724                    # Make sure the exception was raised
725                    self.assertEqual(RAISED_EXCEPTIONS,
726                                     [self.mock_rbd.ImageNotFound])
727
728    @common_mocks
729    @mock.patch('cinder.objects.Volume.get_by_id')
730    def test_create_snapshot(self, volume_get_by_id):
731        volume_get_by_id.return_value = self.volume_a
732        proxy = self.mock_proxy.return_value
733        proxy.__enter__.return_value = proxy
734
735        self.driver.create_snapshot(self.snapshot)
736
737        args = [str(self.snapshot.name)]
738        proxy.create_snap.assert_called_with(*args)
739        proxy.protect_snap.assert_called_with(*args)
740
741    @common_mocks
742    @mock.patch('cinder.objects.Volume.get_by_id')
743    def test_delete_snapshot(self, volume_get_by_id):
744        volume_get_by_id.return_value = self.volume_a
745        proxy = self.mock_proxy.return_value
746        proxy.__enter__.return_value = proxy
747
748        self.driver.delete_snapshot(self.snapshot)
749
750        proxy.remove_snap.assert_called_with(self.snapshot.name)
751        proxy.unprotect_snap.assert_called_with(self.snapshot.name)
752
753    @common_mocks
754    @mock.patch('cinder.objects.Volume.get_by_id')
755    def test_delete_notfound_snapshot(self, volume_get_by_id):
756        volume_get_by_id.return_value = self.volume_a
757        proxy = self.mock_proxy.return_value
758        proxy.__enter__.return_value = proxy
759
760        proxy.unprotect_snap.side_effect = (
761            self.mock_rbd.ImageNotFound)
762
763        self.driver.delete_snapshot(self.snapshot)
764
765        proxy.remove_snap.assert_called_with(self.snapshot.name)
766        proxy.unprotect_snap.assert_called_with(self.snapshot.name)
767
768    @common_mocks
769    @mock.patch('cinder.objects.Volume.get_by_id')
770    def test_delete_notfound_on_remove_snapshot(self, volume_get_by_id):
771        volume_get_by_id.return_value = self.volume_a
772        proxy = self.mock_proxy.return_value
773        proxy.__enter__.return_value = proxy
774
775        proxy.remove_snap.side_effect = (
776            self.mock_rbd.ImageNotFound)
777
778        self.driver.delete_snapshot(self.snapshot)
779
780        proxy.remove_snap.assert_called_with(self.snapshot.name)
781        proxy.unprotect_snap.assert_called_with(self.snapshot.name)
782
783    @common_mocks
784    @mock.patch('cinder.objects.Volume.get_by_id')
785    def test_delete_unprotected_snapshot(self, volume_get_by_id):
786        volume_get_by_id.return_value = self.volume_a
787        proxy = self.mock_proxy.return_value
788        proxy.__enter__.return_value = proxy
789        proxy.unprotect_snap.side_effect = self.mock_rbd.InvalidArgument
790
791        self.driver.delete_snapshot(self.snapshot)
792        self.assertTrue(proxy.unprotect_snap.called)
793        self.assertTrue(proxy.remove_snap.called)
794
795    @common_mocks
796    @mock.patch('cinder.objects.Volume.get_by_id')
797    def test_delete_busy_snapshot(self, volume_get_by_id):
798        volume_get_by_id.return_value = self.volume_a
799        proxy = self.mock_proxy.return_value
800        proxy.__enter__.return_value = proxy
801
802        proxy.unprotect_snap.side_effect = (
803            self.mock_rbd.ImageBusy)
804
805        with mock.patch.object(self.driver, '_get_children_info') as \
806                mock_get_children_info:
807            mock_get_children_info.return_value = [('pool', 'volume2')]
808
809            with mock.patch.object(driver, 'LOG') as \
810                    mock_log:
811
812                self.assertRaises(exception.SnapshotIsBusy,
813                                  self.driver.delete_snapshot,
814                                  self.snapshot)
815
816                mock_get_children_info.assert_called_once_with(
817                    proxy,
818                    self.snapshot.name)
819
820                self.assertTrue(mock_log.info.called)
821                self.assertTrue(proxy.unprotect_snap.called)
822                self.assertFalse(proxy.remove_snap.called)
823
824    @common_mocks
825    def test_get_children_info(self):
826        volume = self.mock_proxy
827        volume.set_snap = mock.Mock()
828        volume.list_children = mock.Mock()
829        list_children = [('pool', 'volume2')]
830        volume.list_children.return_value = list_children
831
832        info = self.driver._get_children_info(volume,
833                                              self.snapshot['name'])
834
835        self.assertEqual(list_children, info)
836
837    @common_mocks
838    def test_get_clone_info(self):
839        volume = self.mock_rbd.Image()
840        volume.set_snap = mock.Mock()
841        volume.parent_info = mock.Mock()
842        parent_info = ('a', 'b', '%s.clone_snap' % (self.volume_a.name))
843        volume.parent_info.return_value = parent_info
844
845        info = self.driver._get_clone_info(volume, self.volume_a.name)
846
847        self.assertEqual(parent_info, info)
848
849        self.assertFalse(volume.set_snap.called)
850        volume.parent_info.assert_called_once_with()
851
852    @common_mocks
853    def test_get_clone_info_w_snap(self):
854        volume = self.mock_rbd.Image()
855        volume.set_snap = mock.Mock()
856        volume.parent_info = mock.Mock()
857        parent_info = ('a', 'b', '%s.clone_snap' % (self.volume_a.name))
858        volume.parent_info.return_value = parent_info
859
860        snapshot = self.mock_rbd.ImageSnapshot()
861
862        info = self.driver._get_clone_info(volume, self.volume_a.name,
863                                           snap=snapshot)
864
865        self.assertEqual(parent_info, info)
866
867        self.assertEqual(2, volume.set_snap.call_count)
868        volume.parent_info.assert_called_once_with()
869
870    @common_mocks
871    def test_get_clone_info_w_exception(self):
872        volume = self.mock_rbd.Image()
873        volume.set_snap = mock.Mock()
874        volume.parent_info = mock.Mock()
875        volume.parent_info.side_effect = self.mock_rbd.ImageNotFound
876
877        snapshot = self.mock_rbd.ImageSnapshot()
878
879        info = self.driver._get_clone_info(volume, self.volume_a.name,
880                                           snap=snapshot)
881
882        self.assertEqual((None, None, None), info)
883
884        self.assertEqual(2, volume.set_snap.call_count)
885        volume.parent_info.assert_called_once_with()
886        # Make sure the exception was raised
887        self.assertEqual(RAISED_EXCEPTIONS, [self.mock_rbd.ImageNotFound])
888
889    @common_mocks
890    def test_get_clone_info_deleted_volume(self):
891        volume = self.mock_rbd.Image()
892        volume.set_snap = mock.Mock()
893        volume.parent_info = mock.Mock()
894        parent_info = ('a', 'b', '%s.clone_snap' % (self.volume_a.name))
895        volume.parent_info.return_value = parent_info
896
897        info = self.driver._get_clone_info(volume,
898                                           "%s.deleted" % (self.volume_a.name))
899
900        self.assertEqual(parent_info, info)
901
902        self.assertFalse(volume.set_snap.called)
903        volume.parent_info.assert_called_once_with()
904
905    @common_mocks
906    @mock.patch.object(driver.RBDDriver, '_enable_replication')
907    def test_create_cloned_volume_same_size(self, mock_enable_repl):
908        self.cfg.rbd_max_clone_depth = 2
909
910        with mock.patch.object(self.driver, '_get_clone_depth') as \
911                mock_get_clone_depth:
912            # Try with no flatten required
913            with mock.patch.object(self.driver, '_resize') as mock_resize:
914                mock_get_clone_depth.return_value = 1
915
916                res = self.driver.create_cloned_volume(self.volume_b,
917                                                       self.volume_a)
918
919                self.assertIsNone(res)
920                (self.mock_rbd.Image.return_value.create_snap
921                    .assert_called_once_with('.'.join(
922                        (self.volume_b.name, 'clone_snap'))))
923                (self.mock_rbd.Image.return_value.protect_snap
924                    .assert_called_once_with('.'.join(
925                        (self.volume_b.name, 'clone_snap'))))
926                self.assertEqual(
927                    1, self.mock_rbd.RBD.return_value.clone.call_count)
928                self.assertEqual(
929                    2, self.mock_rbd.Image.return_value.close.call_count)
930                self.assertTrue(mock_get_clone_depth.called)
931                mock_resize.assert_not_called()
932                mock_enable_repl.assert_not_called()
933
934    @common_mocks
935    @mock.patch.object(driver.RBDDriver, '_get_clone_depth', return_value=1)
936    @mock.patch.object(driver.RBDDriver, '_resize')
937    @mock.patch.object(driver.RBDDriver, '_enable_replication')
938    def test_create_cloned_volume_replicated(self,
939                                             mock_enable_repl,
940                                             mock_resize,
941                                             mock_get_clone_depth):
942        self.cfg.rbd_max_clone_depth = 2
943        self.volume_b.volume_type = fake_volume.fake_volume_type_obj(
944            self.context,
945            id=fake.VOLUME_TYPE_ID,
946            extra_specs={'replication_enabled': '<is> True'})
947
948        expected_update = {
949            'replication_status': 'enabled',
950            'replication_driver_data': '{"had_journaling": false}'
951        }
952        mock_enable_repl.return_value = expected_update
953
954        res = self.driver.create_cloned_volume(self.volume_b, self.volume_a)
955        self.assertEqual(expected_update, res)
956        mock_enable_repl.assert_called_once_with(self.volume_b)
957
958        name = self.volume_b.name
959        image = self.mock_rbd.Image.return_value
960
961        image.create_snap.assert_called_once_with(name + '.clone_snap')
962        image.protect_snap.assert_called_once_with(name + '.clone_snap')
963        self.assertEqual(1, self.mock_rbd.RBD.return_value.clone.call_count)
964        self.assertEqual(
965            2, self.mock_rbd.Image.return_value.close.call_count)
966        mock_get_clone_depth.assert_called_once_with(
967            self.mock_client().__enter__(), self.volume_a.name)
968        mock_resize.assert_not_called()
969
970    @common_mocks
971    @mock.patch.object(driver.RBDDriver, '_enable_replication')
972    def test_create_cloned_volume_different_size(self, mock_enable_repl):
973        self.cfg.rbd_max_clone_depth = 2
974
975        with mock.patch.object(self.driver, '_get_clone_depth') as \
976                mock_get_clone_depth:
977            # Try with no flatten required
978            with mock.patch.object(self.driver, '_resize') as mock_resize:
979                mock_get_clone_depth.return_value = 1
980
981                self.volume_b.size = 20
982                res = self.driver.create_cloned_volume(self.volume_b,
983                                                       self.volume_a)
984
985                self.assertIsNone(res)
986                (self.mock_rbd.Image.return_value.create_snap
987                    .assert_called_once_with('.'.join(
988                        (self.volume_b.name, 'clone_snap'))))
989                (self.mock_rbd.Image.return_value.protect_snap
990                    .assert_called_once_with('.'.join(
991                        (self.volume_b.name, 'clone_snap'))))
992                self.assertEqual(
993                    1, self.mock_rbd.RBD.return_value.clone.call_count)
994                self.assertEqual(
995                    2, self.mock_rbd.Image.return_value.close.call_count)
996                self.assertTrue(mock_get_clone_depth.called)
997                self.assertEqual(
998                    1, mock_resize.call_count)
999                mock_enable_repl.assert_not_called()
1000
1001    @common_mocks
1002    def test_create_cloned_volume_different_size_copy_only(self):
1003        self.cfg.rbd_max_clone_depth = 0
1004
1005        with mock.patch.object(self.driver, '_get_clone_depth') as \
1006                mock_get_clone_depth:
1007            # Try with no flatten required
1008            with mock.patch.object(self.driver, '_resize') as mock_resize:
1009                mock_get_clone_depth.return_value = 1
1010
1011                self.volume_b.size = 20
1012                self.driver.create_cloned_volume(self.volume_b, self.volume_a)
1013
1014                self.assertEqual(1, mock_resize.call_count)
1015
1016    @common_mocks
1017    @mock.patch.object(driver.RBDDriver, '_enable_replication')
1018    def test_create_cloned_volume_w_flatten(self, mock_enable_repl):
1019        self.cfg.rbd_max_clone_depth = 1
1020
1021        with mock.patch.object(self.driver, '_get_clone_info') as \
1022                mock_get_clone_info:
1023            mock_get_clone_info.return_value = (
1024                ('fake_pool', self.volume_b.name,
1025                 '.'.join((self.volume_b.name, 'clone_snap'))))
1026            with mock.patch.object(self.driver, '_get_clone_depth') as \
1027                    mock_get_clone_depth:
1028                # Try with no flatten required
1029                mock_get_clone_depth.return_value = 1
1030
1031                res = self.driver.create_cloned_volume(self.volume_b,
1032                                                       self.volume_a)
1033
1034                self.assertIsNone(res)
1035                (self.mock_rbd.Image.return_value.create_snap
1036                 .assert_called_once_with('.'.join(
1037                     (self.volume_b.name, 'clone_snap'))))
1038                (self.mock_rbd.Image.return_value.protect_snap
1039                 .assert_called_once_with('.'.join(
1040                     (self.volume_b.name, 'clone_snap'))))
1041                self.assertEqual(
1042                    1, self.mock_rbd.RBD.return_value.clone.call_count)
1043                (self.mock_rbd.Image.return_value.unprotect_snap
1044                 .assert_called_once_with('.'.join(
1045                     (self.volume_b.name, 'clone_snap'))))
1046                (self.mock_rbd.Image.return_value.remove_snap
1047                 .assert_called_once_with('.'.join(
1048                     (self.volume_b.name, 'clone_snap'))))
1049
1050                # We expect the driver to close both volumes, so 2 is expected
1051                self.assertEqual(
1052                    3, self.mock_rbd.Image.return_value.close.call_count)
1053                self.assertTrue(mock_get_clone_depth.called)
1054                mock_enable_repl.assert_not_called()
1055
1056    @common_mocks
1057    @mock.patch.object(driver.RBDDriver, '_enable_replication')
1058    def test_create_cloned_volume_w_clone_exception(self, mock_enable_repl):
1059        self.cfg.rbd_max_clone_depth = 2
1060        self.mock_rbd.RBD.return_value.clone.side_effect = (
1061            self.mock_rbd.RBD.Error)
1062        with mock.patch.object(self.driver, '_get_clone_depth') as \
1063                mock_get_clone_depth:
1064            # Try with no flatten required
1065            mock_get_clone_depth.return_value = 1
1066
1067            self.assertRaises(self.mock_rbd.RBD.Error,
1068                              self.driver.create_cloned_volume,
1069                              self.volume_b, self.volume_a)
1070
1071            (self.mock_rbd.Image.return_value.create_snap
1072                .assert_called_once_with('.'.join(
1073                    (self.volume_b.name, 'clone_snap'))))
1074            (self.mock_rbd.Image.return_value.protect_snap
1075                .assert_called_once_with('.'.join(
1076                    (self.volume_b.name, 'clone_snap'))))
1077            self.assertEqual(
1078                1, self.mock_rbd.RBD.return_value.clone.call_count)
1079            (self.mock_rbd.Image.return_value.unprotect_snap
1080             .assert_called_once_with('.'.join(
1081                 (self.volume_b.name, 'clone_snap'))))
1082            (self.mock_rbd.Image.return_value.remove_snap
1083             .assert_called_once_with('.'.join(
1084                 (self.volume_b.name, 'clone_snap'))))
1085            self.assertEqual(
1086                1, self.mock_rbd.Image.return_value.close.call_count)
1087            mock_enable_repl.assert_not_called()
1088
1089    @common_mocks
1090    def test_good_locations(self):
1091        locations = ['rbd://fsid/pool/image/snap',
1092                     'rbd://%2F/%2F/%2F/%2F', ]
1093        map(self.driver._parse_location, locations)
1094
1095    @common_mocks
1096    def test_bad_locations(self):
1097        locations = ['rbd://image',
1098                     'http://path/to/somewhere/else',
1099                     'rbd://image/extra',
1100                     'rbd://image/',
1101                     'rbd://fsid/pool/image/',
1102                     'rbd://fsid/pool/image/snap/',
1103                     'rbd://///', ]
1104        for loc in locations:
1105            self.assertRaises(exception.ImageUnacceptable,
1106                              self.driver._parse_location,
1107                              loc)
1108            self.assertFalse(
1109                self.driver._is_cloneable(loc, {'disk_format': 'raw'}))
1110
1111    @common_mocks
1112    def test_cloneable(self):
1113        with mock.patch.object(self.driver, '_get_fsid') as mock_get_fsid:
1114            mock_get_fsid.return_value = 'abc'
1115            location = 'rbd://abc/pool/image/snap'
1116            info = {'disk_format': 'raw'}
1117            self.assertTrue(self.driver._is_cloneable(location, info))
1118            self.assertTrue(mock_get_fsid.called)
1119
1120    @common_mocks
1121    def test_uncloneable_different_fsid(self):
1122        with mock.patch.object(self.driver, '_get_fsid') as mock_get_fsid:
1123            mock_get_fsid.return_value = 'abc'
1124            location = 'rbd://def/pool/image/snap'
1125            self.assertFalse(
1126                self.driver._is_cloneable(location, {'disk_format': 'raw'}))
1127            self.assertTrue(mock_get_fsid.called)
1128
1129    @common_mocks
1130    def test_uncloneable_unreadable(self):
1131        with mock.patch.object(self.driver, '_get_fsid') as mock_get_fsid:
1132            mock_get_fsid.return_value = 'abc'
1133            location = 'rbd://abc/pool/image/snap'
1134
1135            self.driver.rbd.Error = Exception
1136            self.mock_proxy.side_effect = Exception
1137
1138            args = [location, {'disk_format': 'raw'}]
1139            self.assertFalse(self.driver._is_cloneable(*args))
1140            self.assertEqual(1, self.mock_proxy.call_count)
1141            self.assertTrue(mock_get_fsid.called)
1142
1143    @common_mocks
1144    def test_uncloneable_bad_format(self):
1145        with mock.patch.object(self.driver, '_get_fsid') as mock_get_fsid:
1146            mock_get_fsid.return_value = 'abc'
1147            location = 'rbd://abc/pool/image/snap'
1148            formats = ['qcow2', 'vmdk', 'vdi']
1149            for f in formats:
1150                self.assertFalse(
1151                    self.driver._is_cloneable(location, {'disk_format': f}))
1152            self.assertTrue(mock_get_fsid.called)
1153
1154    def _copy_image(self):
1155        with mock.patch.object(tempfile, 'NamedTemporaryFile'):
1156            with mock.patch.object(os.path, 'exists') as mock_exists:
1157                mock_exists.return_value = True
1158                with mock.patch.object(image_utils, 'fetch_to_raw'):
1159                    with mock.patch.object(self.driver, 'delete_volume'):
1160                        with mock.patch.object(self.driver, '_resize'):
1161                            mock_image_service = mock.MagicMock()
1162                            args = [None, self.volume_a,
1163                                    mock_image_service, None]
1164                            self.driver.copy_image_to_volume(*args)
1165
1166    @common_mocks
1167    def test_copy_image_no_volume_tmp(self):
1168        self.cfg.image_conversion_dir = None
1169        self._copy_image()
1170
1171    @common_mocks
1172    def test_copy_image_volume_tmp(self):
1173        self.cfg.image_conversion_dir = '/var/run/cinder/tmp'
1174        self._copy_image()
1175
1176    @ddt.data(True, False)
1177    @common_mocks
1178    @mock.patch('cinder.volume.drivers.rbd.RBDDriver._get_usage_info')
1179    @mock.patch('cinder.volume.drivers.rbd.RBDDriver._get_pool_stats')
1180    def test_update_volume_stats(self, replication_enabled, stats_mock,
1181                                 usage_mock):
1182        stats_mock.return_value = (mock.sentinel.free_capacity_gb,
1183                                   mock.sentinel.total_capacity_gb)
1184        usage_mock.return_value = mock.sentinel.provisioned_capacity_gb
1185
1186        expected_fsid = 'abc'
1187        expected_location_info = ('nondefault:%s:%s:%s:rbd' %
1188                                  (self.cfg.rbd_ceph_conf, expected_fsid,
1189                                   self.cfg.rbd_user))
1190        expected = dict(
1191            volume_backend_name='RBD',
1192            replication_enabled=replication_enabled,
1193            vendor_name='Open Source',
1194            driver_version=self.driver.VERSION,
1195            storage_protocol='ceph',
1196            total_capacity_gb=mock.sentinel.total_capacity_gb,
1197            free_capacity_gb=mock.sentinel.free_capacity_gb,
1198            reserved_percentage=0,
1199            thin_provisioning_support=True,
1200            provisioned_capacity_gb=mock.sentinel.provisioned_capacity_gb,
1201            max_over_subscription_ratio=1.0,
1202            multiattach=False,
1203            location_info=expected_location_info)
1204
1205        if replication_enabled:
1206            targets = [{'backend_id': 'secondary-backend'},
1207                       {'backend_id': 'tertiary-backend'}]
1208            with mock.patch.object(self.driver.configuration, 'safe_get',
1209                                   return_value=targets):
1210                self.driver._do_setup_replication()
1211            expected['replication_targets'] = [t['backend_id']for t in targets]
1212            expected['replication_targets'].append('default')
1213
1214        my_safe_get = MockDriverConfig(rbd_exclusive_cinder_pool=False)
1215        self.mock_object(self.driver.configuration, 'safe_get',
1216                         my_safe_get)
1217
1218        with mock.patch.object(self.driver, '_get_fsid') as mock_get_fsid:
1219            mock_get_fsid.return_value = expected_fsid
1220            actual = self.driver.get_volume_stats(True)
1221            self.assertDictEqual(expected, actual)
1222
1223    @common_mocks
1224    @mock.patch('cinder.volume.drivers.rbd.RBDDriver._get_usage_info')
1225    @mock.patch('cinder.volume.drivers.rbd.RBDDriver._get_pool_stats')
1226    def test_update_volume_stats_exclusive_pool(self, stats_mock, usage_mock):
1227        stats_mock.return_value = (mock.sentinel.free_capacity_gb,
1228                                   mock.sentinel.total_capacity_gb)
1229
1230        expected_fsid = 'abc'
1231        expected_location_info = ('nondefault:%s:%s:%s:rbd' %
1232                                  (self.cfg.rbd_ceph_conf, expected_fsid,
1233                                   self.cfg.rbd_user))
1234        expected = dict(
1235            volume_backend_name='RBD',
1236            replication_enabled=False,
1237            vendor_name='Open Source',
1238            driver_version=self.driver.VERSION,
1239            storage_protocol='ceph',
1240            total_capacity_gb=mock.sentinel.total_capacity_gb,
1241            free_capacity_gb=mock.sentinel.free_capacity_gb,
1242            reserved_percentage=0,
1243            thin_provisioning_support=True,
1244            max_over_subscription_ratio=1.0,
1245            multiattach=False,
1246            location_info=expected_location_info)
1247
1248        my_safe_get = MockDriverConfig(rbd_exclusive_cinder_pool=True)
1249        self.mock_object(self.driver.configuration, 'safe_get',
1250                         my_safe_get)
1251
1252        with mock.patch.object(self.driver, '_get_fsid',
1253                               return_value=expected_fsid):
1254            actual = self.driver.get_volume_stats(True)
1255
1256        self.assertDictEqual(expected, actual)
1257        usage_mock.assert_not_called()
1258
1259    @common_mocks
1260    @mock.patch('cinder.volume.drivers.rbd.RBDDriver._get_usage_info')
1261    @mock.patch('cinder.volume.drivers.rbd.RBDDriver._get_pool_stats')
1262    def test_update_volume_stats_error(self, stats_mock, usage_mock):
1263        my_safe_get = MockDriverConfig(rbd_exclusive_cinder_pool=False)
1264        self.mock_object(self.driver.configuration, 'safe_get',
1265                         my_safe_get)
1266
1267        expected_fsid = 'abc'
1268        expected_location_info = ('nondefault:%s:%s:%s:rbd' %
1269                                  (self.cfg.rbd_ceph_conf, expected_fsid,
1270                                   self.cfg.rbd_user))
1271        expected = dict(volume_backend_name='RBD',
1272                        replication_enabled=False,
1273                        vendor_name='Open Source',
1274                        driver_version=self.driver.VERSION,
1275                        storage_protocol='ceph',
1276                        total_capacity_gb='unknown',
1277                        free_capacity_gb='unknown',
1278                        reserved_percentage=0,
1279                        multiattach=False,
1280                        max_over_subscription_ratio=1.0,
1281                        thin_provisioning_support=True,
1282                        location_info=expected_location_info)
1283
1284        with mock.patch.object(self.driver, '_get_fsid') as mock_get_fsid:
1285            mock_get_fsid.return_value = expected_fsid
1286            actual = self.driver.get_volume_stats(True)
1287            self.assertDictEqual(expected, actual)
1288
1289    @ddt.data(
1290        # Normal case, no quota and dynamic total
1291        {'free_capacity': 27.0, 'total_capacity': 28.44},
1292        # No quota and static total
1293        {'dynamic_total': False,
1294         'free_capacity': 27.0, 'total_capacity': 59.96},
1295        # Quota and dynamic total
1296        {'quota_max_bytes': 3221225472, 'max_avail': 1073741824,
1297         'free_capacity': 1, 'total_capacity': 2.44},
1298        # Quota and static total
1299        {'quota_max_bytes': 3221225472, 'max_avail': 1073741824,
1300         'dynamic_total': False,
1301         'free_capacity': 1, 'total_capacity': 3.00},
1302        # Quota and dynamic total when free would be negative
1303        {'quota_max_bytes': 1073741824,
1304         'free_capacity': 0, 'total_capacity': 1.44},
1305    )
1306    @ddt.unpack
1307    @common_mocks
1308    def test_get_pool(self, free_capacity, total_capacity,
1309                      max_avail=28987613184, quota_max_bytes=0,
1310                      dynamic_total=True):
1311        client = self.mock_client.return_value
1312        client.__enter__.return_value = client
1313        client.cluster.mon_command.side_effect = [
1314            (0, '{"stats":{"total_bytes":64385286144,'
1315             '"total_used_bytes":3289628672,"total_avail_bytes":61095657472},'
1316             '"pools":[{"name":"rbd","id":2,"stats":{"kb_used":1510197,'
1317             '"bytes_used":1546440971,"max_avail":%s,"objects":412}},'
1318             '{"name":"volumes","id":3,"stats":{"kb_used":0,"bytes_used":0,'
1319             '"max_avail":28987613184,"objects":0}}]}\n' % max_avail, ''),
1320            (0, '{"pool_name":"volumes","pool_id":4,"quota_max_objects":0,'
1321             '"quota_max_bytes":%s}\n' % quota_max_bytes, ''),
1322        ]
1323        with mock.patch.object(self.driver.configuration, 'safe_get',
1324                               return_value=dynamic_total):
1325            result = self.driver._get_pool_stats()
1326        client.cluster.mon_command.assert_has_calls([
1327            mock.call('{"prefix":"df", "format":"json"}', ''),
1328            mock.call('{"prefix":"osd pool get-quota", "pool": "rbd",'
1329                      ' "format":"json"}', ''),
1330        ])
1331        self.assertEqual((free_capacity, total_capacity), result)
1332
1333    @common_mocks
1334    def test_get_pool_bytes(self, free_capacity=1.56, total_capacity=3.0,
1335                            dynamic_total=True):
1336        """test for mon_commands returning bytes instead of str"""
1337        client = self.mock_client.return_value
1338        client.__enter__.return_value = client
1339        client.cluster.mon_command.side_effect = [
1340            (0, b'{"stats":{"total_bytes":64385286144,'
1341             b'"total_used_bytes":3289628672,"total_avail_bytes":61095657472},'
1342             b'"pools":[{"name":"rbd","id":2,"stats":{"kb_used":1510197,'
1343             b'"bytes_used":1546440971,"max_avail":2897613184,"objects":412}},'
1344             b'{"name":"volumes","id":3,"stats":{"kb_used":0,"bytes_used":0,'
1345             b'"max_avail":28987613184,"objects":0}}]}\n', ''),
1346            (0, b'{"pool_name":"volumes","pool_id":4,"quota_max_objects":0,'
1347             b'"quota_max_bytes":3221225472}\n', ''),
1348        ]
1349        with mock.patch.object(self.driver.configuration, 'safe_get',
1350                               return_value=dynamic_total):
1351            result = self.driver._get_pool_stats()
1352        client.cluster.mon_command.assert_has_calls([
1353            mock.call('{"prefix":"df", "format":"json"}', ''),
1354            mock.call('{"prefix":"osd pool get-quota", "pool": "rbd",'
1355                      ' "format":"json"}', ''),
1356        ])
1357        self.assertEqual((free_capacity, total_capacity), result)
1358
1359    @common_mocks
1360    def test_get_pool_stats_failure(self):
1361        client = self.mock_client.return_value
1362        client.__enter__.return_value = client
1363        client.cluster.mon_command.return_value = (-1, '', '')
1364
1365        result = self.driver._get_pool_stats()
1366        self.assertEqual(('unknown', 'unknown'), result)
1367
1368    @common_mocks
1369    def test_get_mon_addrs(self):
1370        with mock.patch.object(self.driver, '_execute') as mock_execute:
1371            mock_execute.return_value = (CEPH_MON_DUMP, '')
1372            hosts = ['::1', '::1', '::1', '127.0.0.1', 'example.com']
1373            ports = ['6789', '6790', '6791', '6792', '6791']
1374            self.assertEqual((hosts, ports), self.driver._get_mon_addrs())
1375
1376    @common_mocks
1377    def _initialize_connection_helper(self, expected, hosts, ports):
1378
1379        with mock.patch.object(self.driver, '_get_mon_addrs') as \
1380                mock_get_mon_addrs:
1381            mock_get_mon_addrs.return_value = (hosts, ports)
1382            actual = self.driver.initialize_connection(self.volume_a, None)
1383            self.assertDictEqual(expected, actual)
1384            self.assertTrue(mock_get_mon_addrs.called)
1385
1386    @mock.patch.object(cinder.volume.drivers.rbd.RBDDriver,
1387                       '_get_keyring_contents')
1388    def test_initialize_connection(self, mock_keyring):
1389        hosts = ['::1', '::1', '::1', '127.0.0.1', 'example.com']
1390        ports = ['6789', '6790', '6791', '6792', '6791']
1391
1392        keyring_data = "[client.cinder]\n  key = test\n"
1393        mock_keyring.return_value = keyring_data
1394
1395        expected = {
1396            'driver_volume_type': 'rbd',
1397            'data': {
1398                'name': '%s/%s' % (self.cfg.rbd_pool,
1399                                   self.volume_a.name),
1400                'hosts': hosts,
1401                'ports': ports,
1402                'cluster_name': self.cfg.rbd_cluster_name,
1403                'auth_enabled': True,
1404                'auth_username': self.cfg.rbd_user,
1405                'secret_type': 'ceph',
1406                'secret_uuid': None,
1407                'volume_id': self.volume_a.id,
1408                'discard': True,
1409                'keyring': keyring_data,
1410            }
1411        }
1412        self._initialize_connection_helper(expected, hosts, ports)
1413
1414        # Check how it will work with empty keyring path
1415        mock_keyring.return_value = None
1416        expected['data']['keyring'] = None
1417        self._initialize_connection_helper(expected, hosts, ports)
1418
1419    def test__get_keyring_contents_no_config_file(self):
1420        self.cfg.rbd_keyring_conf = ''
1421        self.assertIsNone(self.driver._get_keyring_contents())
1422
1423    @mock.patch('os.path.isfile')
1424    def test__get_keyring_contents_read_file(self, mock_isfile):
1425        mock_isfile.return_value = True
1426        keyring_data = "[client.cinder]\n  key = test\n"
1427        mockopen = mock.mock_open(read_data=keyring_data)
1428        mockopen.return_value.__exit__ = mock.Mock()
1429        with mock.patch('cinder.volume.drivers.rbd.open', mockopen,
1430                        create=True):
1431            self.assertEqual(self.driver._get_keyring_contents(), keyring_data)
1432
1433    @mock.patch('os.path.isfile')
1434    def test__get_keyring_contents_raise_error(self, mock_isfile):
1435        mock_isfile.return_value = True
1436        mockopen = mock.mock_open()
1437        mockopen.return_value.__exit__ = mock.Mock()
1438        with mock.patch('cinder.volume.drivers.rbd.open', mockopen,
1439                        create=True) as mock_keyring_file:
1440            mock_keyring_file.side_effect = IOError
1441            self.assertIsNone(self.driver._get_keyring_contents())
1442
1443    @ddt.data({'rbd_chunk_size': 1, 'order': 20},
1444              {'rbd_chunk_size': 8, 'order': 23},
1445              {'rbd_chunk_size': 32, 'order': 25})
1446    @ddt.unpack
1447    @common_mocks
1448    @mock.patch.object(driver.RBDDriver, '_enable_replication')
1449    def test_clone(self, mock_enable_repl, rbd_chunk_size, order):
1450        self.cfg.rbd_store_chunk_size = rbd_chunk_size
1451        src_pool = u'images'
1452        src_image = u'image-name'
1453        src_snap = u'snapshot-name'
1454
1455        client_stack = []
1456
1457        def mock__enter__(inst):
1458            def _inner():
1459                client_stack.append(inst)
1460                return inst
1461            return _inner
1462
1463        client = self.mock_client.return_value
1464        # capture both rados client used to perform the clone
1465        client.__enter__.side_effect = mock__enter__(client)
1466
1467        res = self.driver._clone(self.volume_a, src_pool, src_image, src_snap)
1468
1469        self.assertEqual({}, res)
1470
1471        args = [client_stack[0].ioctx, str(src_image), str(src_snap),
1472                client_stack[1].ioctx, str(self.volume_a.name)]
1473        kwargs = {'features': client.features,
1474                  'order': order}
1475        self.mock_rbd.RBD.return_value.clone.assert_called_once_with(
1476            *args, **kwargs)
1477        self.assertEqual(2, client.__enter__.call_count)
1478        mock_enable_repl.assert_not_called()
1479
1480    @common_mocks
1481    @mock.patch.object(driver.RBDDriver, '_enable_replication')
1482    def test_clone_replicated(self, mock_enable_repl):
1483        rbd_chunk_size = 1
1484        order = 20
1485        self.volume_a.volume_type = fake_volume.fake_volume_type_obj(
1486            self.context,
1487            id=fake.VOLUME_TYPE_ID,
1488            extra_specs={'replication_enabled': '<is> True'})
1489
1490        expected_update = {
1491            'replication_status': 'enabled',
1492            'replication_driver_data': '{"had_journaling": false}'
1493        }
1494        mock_enable_repl.return_value = expected_update
1495
1496        self.cfg.rbd_store_chunk_size = rbd_chunk_size
1497        src_pool = u'images'
1498        src_image = u'image-name'
1499        src_snap = u'snapshot-name'
1500
1501        client_stack = []
1502
1503        def mock__enter__(inst):
1504            def _inner():
1505                client_stack.append(inst)
1506                return inst
1507            return _inner
1508
1509        client = self.mock_client.return_value
1510        # capture both rados client used to perform the clone
1511        client.__enter__.side_effect = mock__enter__(client)
1512
1513        res = self.driver._clone(self.volume_a, src_pool, src_image, src_snap)
1514
1515        self.assertEqual(expected_update, res)
1516        mock_enable_repl.assert_called_once_with(self.volume_a)
1517
1518        args = [client_stack[0].ioctx, str(src_image), str(src_snap),
1519                client_stack[1].ioctx, str(self.volume_a.name)]
1520        kwargs = {'features': client.features,
1521                  'order': order}
1522        self.mock_rbd.RBD.return_value.clone.assert_called_once_with(
1523            *args, **kwargs)
1524        self.assertEqual(2, client.__enter__.call_count)
1525
1526    @ddt.data({},
1527              {'replication_status': 'enabled',
1528               'replication_driver_data': '{"had_journaling": false}'})
1529    @common_mocks
1530    @mock.patch.object(driver.RBDDriver, '_is_cloneable', return_value=True)
1531    def test_clone_image_replication(self, return_value, mock_cloneable):
1532        mock_clone = self.mock_object(self.driver, '_clone',
1533                                      return_value=return_value)
1534        image_loc = ('rbd://fee/fi/fo/fum', None)
1535        image_meta = {'disk_format': 'raw', 'id': 'id.foo'}
1536
1537        res = self.driver.clone_image(self.context,
1538                                      self.volume_a,
1539                                      image_loc,
1540                                      image_meta,
1541                                      mock.Mock())
1542
1543        expected = return_value.copy()
1544        expected['provider_location'] = None
1545        self.assertEqual((expected, True), res)
1546
1547        mock_clone.assert_called_once_with(self.volume_a, 'fi', 'fo', 'fum')
1548
1549    @common_mocks
1550    @mock.patch.object(driver.RBDDriver, '_clone',
1551                       return_value=mock.sentinel.volume_update)
1552    @mock.patch.object(driver.RBDDriver, '_resize', mock.Mock())
1553    def test_create_vol_from_snap_replication(self, mock_clone):
1554        self.cfg.rbd_flatten_volume_from_snapshot = False
1555        snapshot = mock.Mock()
1556
1557        res = self.driver.create_volume_from_snapshot(self.volume_a, snapshot)
1558
1559        self.assertEqual(mock.sentinel.volume_update, res)
1560        mock_clone.assert_called_once_with(self.volume_a,
1561                                           self.cfg.rbd_pool,
1562                                           snapshot.volume_name,
1563                                           snapshot.name)
1564
1565    @common_mocks
1566    def test_extend_volume(self):
1567        fake_size = '20'
1568        size = int(fake_size) * units.Gi
1569        with mock.patch.object(self.driver, '_resize') as mock_resize:
1570            self.driver.extend_volume(self.volume_a, fake_size)
1571            mock_resize.assert_called_once_with(self.volume_a, size=size)
1572
1573    @ddt.data(False, True)
1574    @common_mocks
1575    def test_retype(self, enabled):
1576        """Test retyping a non replicated volume.
1577
1578        We will test on a system that doesn't have replication enabled and on
1579        one that hast it enabled.
1580        """
1581        self.driver._is_replication_enabled = enabled
1582        if enabled:
1583            expect = {'replication_status': fields.ReplicationStatus.DISABLED}
1584        else:
1585            expect = None
1586        context = {}
1587        diff = {'encryption': {},
1588                'extra_specs': {}}
1589        updates = {'name': 'testvolume',
1590                   'host': 'currenthost',
1591                   'id': fake.VOLUME_ID}
1592        fake_type = fake_volume.fake_volume_type_obj(context)
1593        volume = fake_volume.fake_volume_obj(context, **updates)
1594        volume.volume_type = None
1595
1596        # The hosts have been checked same before rbd.retype
1597        # is called.
1598        # RBD doesn't support multiple pools in a driver.
1599        host = {'host': 'currenthost'}
1600        self.assertEqual((True, expect),
1601                         self.driver.retype(context, volume,
1602                                            fake_type, diff, host))
1603
1604        # The encryptions have been checked as same before rbd.retype
1605        # is called.
1606        diff['encryption'] = {}
1607        self.assertEqual((True, expect),
1608                         self.driver.retype(context, volume,
1609                                            fake_type, diff, host))
1610
1611        # extra_specs changes are supported.
1612        diff['extra_specs'] = {'non-empty': 'non-empty'}
1613        self.assertEqual((True, expect),
1614                         self.driver.retype(context, volume,
1615                                            fake_type, diff, host))
1616        diff['extra_specs'] = {}
1617
1618        self.assertEqual((True, expect),
1619                         self.driver.retype(context, volume,
1620                                            fake_type, diff, host))
1621
1622    @ddt.data({'old_replicated': False, 'new_replicated': False},
1623              {'old_replicated': False, 'new_replicated': True},
1624              {'old_replicated': True, 'new_replicated': False},
1625              {'old_replicated': True, 'new_replicated': True})
1626    @ddt.unpack
1627    @common_mocks
1628    @mock.patch.object(driver.RBDDriver, '_disable_replication',
1629                       return_value=mock.sentinel.disable_replication)
1630    @mock.patch.object(driver.RBDDriver, '_enable_replication',
1631                       return_value=mock.sentinel.enable_replication)
1632    def test_retype_replicated(self, mock_disable, mock_enable, old_replicated,
1633                               new_replicated):
1634        """Test retyping a non replicated volume.
1635
1636        We will test on a system that doesn't have replication enabled and on
1637        one that hast it enabled.
1638        """
1639        self.driver._is_replication_enabled = True
1640        replicated_type = fake_volume.fake_volume_type_obj(
1641            self.context,
1642            id=fake.VOLUME_TYPE_ID,
1643            extra_specs={'replication_enabled': '<is> True'})
1644
1645        self.volume_a.volume_type = replicated_type if old_replicated else None
1646
1647        if new_replicated:
1648            new_type = replicated_type
1649            if old_replicated:
1650                update = None
1651            else:
1652                update = mock.sentinel.enable_replication
1653        else:
1654            new_type = fake_volume.fake_volume_type_obj(
1655                self.context,
1656                id=fake.VOLUME_TYPE2_ID),
1657            if old_replicated:
1658                update = mock.sentinel.disable_replication
1659            else:
1660                update = {'replication_status':
1661                          fields.ReplicationStatus.DISABLED}
1662
1663        res = self.driver.retype(self.context, self.volume_a, new_type, None,
1664                                 None)
1665        self.assertEqual((True, update), res)
1666
1667    @common_mocks
1668    def test_update_migrated_volume(self):
1669        client = self.mock_client.return_value
1670        client.__enter__.return_value = client
1671
1672        with mock.patch.object(self.driver.rbd.RBD(), 'rename') as mock_rename:
1673            context = {}
1674            mock_rename.return_value = 0
1675            model_update = self.driver.update_migrated_volume(context,
1676                                                              self.volume_a,
1677                                                              self.volume_b,
1678                                                              'available')
1679            mock_rename.assert_called_with(client.ioctx,
1680                                           'volume-%s' % self.volume_b.id,
1681                                           'volume-%s' % self.volume_a.id)
1682            self.assertEqual({'_name_id': None,
1683                              'provider_location': None}, model_update)
1684
1685    def test_rbd_volume_proxy_init(self):
1686        mock_driver = mock.Mock(name='driver')
1687        mock_driver._connect_to_rados.return_value = (None, None)
1688        with driver.RBDVolumeProxy(mock_driver, self.volume_a.name):
1689            self.assertEqual(1, mock_driver._connect_to_rados.call_count)
1690            self.assertFalse(mock_driver._disconnect_from_rados.called)
1691
1692        self.assertEqual(1, mock_driver._disconnect_from_rados.call_count)
1693
1694        mock_driver.reset_mock()
1695
1696        snap = u'snapshot-name'
1697        with driver.RBDVolumeProxy(mock_driver, self.volume_a.name,
1698                                   snapshot=snap):
1699            self.assertEqual(1, mock_driver._connect_to_rados.call_count)
1700            self.assertFalse(mock_driver._disconnect_from_rados.called)
1701
1702        self.assertEqual(1, mock_driver._disconnect_from_rados.call_count)
1703
1704    def test_rbd_volume_proxy_external_conn(self):
1705        mock_driver = mock.Mock(name='driver')
1706        mock_driver._connect_to_rados.return_value = (None, None)
1707        with driver.RBDVolumeProxy(mock_driver, self.volume_a.name,
1708                                   client='fake_cl', ioctx='fake_io'):
1709
1710            mock_driver._connect_to_rados.assert_not_called()
1711
1712        mock_driver._disconnect_from_rados.assert_not_called()
1713
1714    def test_rbd_volume_proxy_external_conn_no_iocxt(self):
1715        mock_driver = mock.Mock(name='driver')
1716        mock_driver._connect_to_rados.return_value = ('fake_cl', 'fake_io')
1717        with driver.RBDVolumeProxy(mock_driver, self.volume_a.name,
1718                                   client='fake_cl', pool='vol_pool'):
1719            mock_driver._connect_to_rados.assert_called_once_with(
1720                'vol_pool', None, None)
1721
1722        mock_driver._disconnect_from_rados.assert_called_once_with(
1723            'fake_cl', 'fake_io')
1724
1725    def test_rbd_volume_proxy_external_conn_error(self):
1726        mock_driver = mock.Mock(name='driver')
1727        mock_driver._connect_to_rados.return_value = (None, None)
1728
1729        class RBDError(Exception):
1730            pass
1731
1732        mock_driver.rbd.Error = RBDError
1733        mock_driver.rbd.Image.side_effect = RBDError()
1734
1735        self.assertRaises(RBDError, driver.RBDVolumeProxy,
1736                          mock_driver, self.volume_a.name,
1737                          client='fake_cl', ioctx='fake_io')
1738
1739        mock_driver._connect_to_rados.assert_not_called()
1740        mock_driver._disconnect_from_rados.assert_not_called()
1741
1742    def test_rbd_volume_proxy_conn_error(self):
1743        mock_driver = mock.Mock(name='driver')
1744        mock_driver._connect_to_rados.return_value = (
1745            'fake_client', 'fake_ioctx')
1746
1747        class RBDError(Exception):
1748            pass
1749
1750        mock_driver.rbd.Error = RBDError
1751        mock_driver.rbd.Image.side_effect = RBDError()
1752
1753        self.assertRaises(RBDError, driver.RBDVolumeProxy,
1754                          mock_driver, self.volume_a.name,
1755                          pool='fake-volumes')
1756
1757        mock_driver._connect_to_rados.assert_called_once_with(
1758            'fake-volumes', None, None)
1759        mock_driver._disconnect_from_rados.assert_called_once_with(
1760            'fake_client', 'fake_ioctx')
1761
1762    @common_mocks
1763    def test_connect_to_rados(self):
1764        # Default
1765        self.cfg.rados_connect_timeout = -1
1766
1767        self.mock_rados.Rados.return_value.open_ioctx.return_value = \
1768            self.mock_rados.Rados.return_value.ioctx
1769
1770        # default configured pool
1771        ret = self.driver._connect_to_rados()
1772        self.assertTrue(self.mock_rados.Rados.return_value.connect.called)
1773        # Expect no timeout if default is used
1774        self.mock_rados.Rados.return_value.connect.assert_called_once_with()
1775        self.assertTrue(self.mock_rados.Rados.return_value.open_ioctx.called)
1776        self.assertEqual(self.mock_rados.Rados.return_value.ioctx, ret[1])
1777        self.mock_rados.Rados.return_value.open_ioctx.assert_called_with(
1778            self.cfg.rbd_pool)
1779        conf_set = self.mock_rados.Rados.return_value.conf_set
1780        conf_set.assert_not_called()
1781
1782        # different pool
1783        ret = self.driver._connect_to_rados('alt_pool')
1784        self.assertTrue(self.mock_rados.Rados.return_value.connect.called)
1785        self.assertTrue(self.mock_rados.Rados.return_value.open_ioctx.called)
1786        self.assertEqual(self.mock_rados.Rados.return_value.ioctx, ret[1])
1787        self.mock_rados.Rados.return_value.open_ioctx.assert_called_with(
1788            'alt_pool')
1789
1790        # With timeout
1791        self.cfg.rados_connect_timeout = 1
1792        self.mock_rados.Rados.return_value.connect.reset_mock()
1793        self.driver._connect_to_rados()
1794        conf_set.assert_has_calls((mock.call('rados_osd_op_timeout', '1'),
1795                                   mock.call('rados_mon_op_timeout', '1'),
1796                                   mock.call('client_mount_timeout', '1')))
1797        self.mock_rados.Rados.return_value.connect.assert_called_once_with()
1798
1799        # error
1800        self.mock_rados.Rados.return_value.open_ioctx.reset_mock()
1801        self.mock_rados.Rados.return_value.shutdown.reset_mock()
1802        self.mock_rados.Rados.return_value.open_ioctx.side_effect = (
1803            self.mock_rados.Error)
1804        self.assertRaises(exception.VolumeBackendAPIException,
1805                          self.driver._connect_to_rados)
1806        self.assertTrue(self.mock_rados.Rados.return_value.open_ioctx.called)
1807        self.assertEqual(
1808            3, self.mock_rados.Rados.return_value.shutdown.call_count)
1809
1810    @common_mocks
1811    def test_failover_host_no_replication(self):
1812        self.driver._is_replication_enabled = False
1813        self.assertRaises(exception.UnableToFailOver,
1814                          self.driver.failover_host,
1815                          self.context, [self.volume_a], [])
1816
1817    @ddt.data(None, 'tertiary-backend')
1818    @common_mocks
1819    @mock.patch.object(driver.RBDDriver, '_get_failover_target_config')
1820    @mock.patch.object(driver.RBDDriver, '_failover_volume', autospec=True)
1821    def test_failover_host(self, secondary_id, mock_failover_vol,
1822                           mock_get_cfg):
1823        mock_failover_vol.side_effect = lambda self, v, r, d, s: v
1824        self.mock_object(self.driver.configuration, 'safe_get',
1825                         return_value=[{'backend_id': 'secondary-backend'},
1826                                       {'backend_id': 'tertiary-backend'}])
1827        self.driver._do_setup_replication()
1828        volumes = [self.volume_a, self.volume_b]
1829        remote = self.driver._replication_targets[1 if secondary_id else 0]
1830        mock_get_cfg.return_value = (remote['name'], remote)
1831
1832        res = self.driver.failover_host(self.context, volumes, secondary_id,
1833                                        [])
1834
1835        self.assertEqual((remote['name'], volumes, []), res)
1836        self.assertEqual(remote, self.driver._active_config)
1837        mock_failover_vol.assert_has_calls(
1838            [mock.call(mock.ANY, v, remote, False,
1839                       fields.ReplicationStatus.FAILED_OVER)
1840             for v in volumes])
1841        mock_get_cfg.assert_called_once_with(secondary_id)
1842
1843    @mock.patch.object(driver.RBDDriver, '_failover_volume', autospec=True)
1844    def test_failover_host_failback(self, mock_failover_vol):
1845        mock_failover_vol.side_effect = lambda self, v, r, d, s: v
1846        self.driver._active_backend_id = 'secondary-backend'
1847        self.mock_object(self.driver.configuration, 'safe_get',
1848                         return_value=[{'backend_id': 'secondary-backend'},
1849                                       {'backend_id': 'tertiary-backend'}])
1850        self.driver._do_setup_replication()
1851
1852        remote = self.driver._get_target_config('default')
1853        volumes = [self.volume_a, self.volume_b]
1854        res = self.driver.failover_host(self.context, volumes, 'default', [])
1855
1856        self.assertEqual(('default', volumes, []), res)
1857        self.assertEqual(remote, self.driver._active_config)
1858        mock_failover_vol.assert_has_calls(
1859            [mock.call(mock.ANY, v, remote, False,
1860                       fields.ReplicationStatus.ENABLED)
1861             for v in volumes])
1862
1863    @mock.patch.object(driver.RBDDriver, '_failover_volume')
1864    def test_failover_host_no_more_replica_targets(self, mock_failover_vol):
1865        mock_failover_vol.side_effect = lambda w, x, y, z: w
1866        self.driver._active_backend_id = 'secondary-backend'
1867        self.mock_object(self.driver.configuration, 'safe_get',
1868                         return_value=[{'backend_id': 'secondary-backend'}])
1869        self.driver._do_setup_replication()
1870
1871        volumes = [self.volume_a, self.volume_b]
1872        self.assertRaises(exception.InvalidReplicationTarget,
1873                          self.driver.failover_host,
1874                          self.context, volumes, None, [])
1875
1876    @ddt.data(True, False)
1877    @mock.patch.object(driver.RBDDriver, '_exec_on_volume',
1878                       side_effect=Exception)
1879    def test_failover_volume_error(self, is_demoted, mock_exec):
1880        self.volume_a.replication_driver_data = '{"had_journaling": false}'
1881        self.volume_a.volume_type = fake_volume.fake_volume_type_obj(
1882            self.context,
1883            id=fake.VOLUME_TYPE_ID,
1884            extra_specs={'replication_enabled': '<is> True'})
1885        remote = {'name': 'name', 'user': 'user', 'conf': 'conf',
1886                  'pool': 'pool'}
1887        repl_status = fields.ReplicationStatus.FAILOVER_ERROR
1888        expected = {'volume_id': self.volume_a.id,
1889                    'updates': {'status': 'error',
1890                                'previous_status': self.volume_a.status,
1891                                'replication_status': repl_status}}
1892        res = self.driver._failover_volume(
1893            self.volume_a, remote, is_demoted,
1894            fields.ReplicationStatus.FAILED_OVER)
1895        self.assertEqual(expected, res)
1896        mock_exec.assert_called_once_with(self.volume_a.name, remote,
1897                                          'mirror_image_promote',
1898                                          not is_demoted)
1899
1900    @mock.patch.object(driver.RBDDriver, '_exec_on_volume')
1901    def test_failover_volume(self, mock_exec):
1902        self.volume_a.replication_driver_data = '{"had_journaling": false}'
1903        self.volume_a.volume_type = fake_volume.fake_volume_type_obj(
1904            self.context,
1905            id=fake.VOLUME_TYPE_ID,
1906            extra_specs={'replication_enabled': '<is> True'})
1907        remote = {'name': 'name', 'user': 'user', 'conf': 'conf',
1908                  'pool': 'pool'}
1909        repl_status = fields.ReplicationStatus.FAILED_OVER
1910        expected = {'volume_id': self.volume_a.id,
1911                    'updates': {'replication_status': repl_status}}
1912        res = self.driver._failover_volume(self.volume_a, remote, True,
1913                                           repl_status)
1914        self.assertEqual(expected, res)
1915        mock_exec.assert_called_once_with(self.volume_a.name, remote,
1916                                          'mirror_image_promote', False)
1917
1918    @common_mocks
1919    def test_manage_existing_snapshot_get_size(self):
1920        with mock.patch.object(self.driver.rbd.Image(), 'size') as \
1921                mock_rbd_image_size:
1922            with mock.patch.object(self.driver.rbd.Image(), 'close') \
1923                    as mock_rbd_image_close:
1924                mock_rbd_image_size.return_value = 2 * units.Gi
1925                existing_ref = {'source-name': self.snapshot_b.name}
1926                return_size = self.driver.manage_existing_snapshot_get_size(
1927                    self.snapshot_b,
1928                    existing_ref)
1929                self.assertEqual(2, return_size)
1930                mock_rbd_image_size.assert_called_once_with()
1931                mock_rbd_image_close.assert_called_once_with()
1932
1933    @common_mocks
1934    def test_manage_existing_snapshot_get_non_integer_size(self):
1935        rbd_snapshot = self.driver.rbd.Image.return_value
1936        rbd_snapshot.size.return_value = int(1.75 * units.Gi)
1937        existing_ref = {'source-name': self.snapshot_b.name}
1938        return_size = self.driver.manage_existing_snapshot_get_size(
1939            self.snapshot_b, existing_ref)
1940        self.assertEqual(2, return_size)
1941        rbd_snapshot.size.assert_called_once_with()
1942        rbd_snapshot.close.assert_called_once_with()
1943
1944    @common_mocks
1945    def test_manage_existing_snapshot_get_invalid_size(self):
1946
1947        with mock.patch.object(self.driver.rbd.Image(), 'size') as \
1948                mock_rbd_image_size:
1949            with mock.patch.object(self.driver.rbd.Image(), 'close') \
1950                    as mock_rbd_image_close:
1951                mock_rbd_image_size.return_value = 'abcd'
1952                existing_ref = {'source-name': self.snapshot_b.name}
1953                self.assertRaises(
1954                    exception.VolumeBackendAPIException,
1955                    self.driver.manage_existing_snapshot_get_size,
1956                    self.snapshot_b, existing_ref)
1957
1958                mock_rbd_image_size.assert_called_once_with()
1959                mock_rbd_image_close.assert_called_once_with()
1960
1961    @common_mocks
1962    def test_manage_existing_snapshot_with_invalid_rbd_image(self):
1963        self.mock_rbd.Image.side_effect = self.mock_rbd.ImageNotFound
1964
1965        invalid_snapshot = 'snapshot-invalid'
1966        invalid_ref = {'source-name': invalid_snapshot}
1967
1968        self.assertRaises(exception.ManageExistingInvalidReference,
1969                          self.driver.manage_existing_snapshot_get_size,
1970                          self.snapshot_b, invalid_ref)
1971        # Make sure the exception was raised
1972        self.assertEqual([self.mock_rbd.ImageNotFound],
1973                         RAISED_EXCEPTIONS)
1974
1975    @common_mocks
1976    def test_manage_existing_snapshot(self):
1977        proxy = self.mock_proxy.return_value
1978        proxy.__enter__.return_value = proxy
1979        exist_snapshot = 'snapshot-exist'
1980        existing_ref = {'source-name': exist_snapshot}
1981        proxy.rename_snap.return_value = 0
1982        self.driver.manage_existing_snapshot(self.snapshot_b, existing_ref)
1983        proxy.rename_snap.assert_called_with(exist_snapshot,
1984                                             self.snapshot_b.name)
1985
1986    @common_mocks
1987    def test_manage_existing_snapshot_with_exist_rbd_image(self):
1988        proxy = self.mock_proxy.return_value
1989        proxy.__enter__.return_value = proxy
1990        proxy.rename_snap.side_effect = MockImageExistsException
1991
1992        exist_snapshot = 'snapshot-exist'
1993        existing_ref = {'source-name': exist_snapshot}
1994        self.assertRaises(self.mock_rbd.ImageExists,
1995                          self.driver.manage_existing_snapshot,
1996                          self.snapshot_b, existing_ref)
1997
1998        # Make sure the exception was raised
1999        self.assertEqual(RAISED_EXCEPTIONS,
2000                         [self.mock_rbd.ImageExists])
2001
2002    @mock.patch('cinder.volume.drivers.rbd.RBDVolumeProxy')
2003    @mock.patch('cinder.volume.drivers.rbd.RADOSClient')
2004    @mock.patch('cinder.volume.drivers.rbd.RBDDriver.RBDProxy')
2005    def test__get_usage_info(self, rbdproxy_mock, client_mock, volproxy_mock):
2006
2007        def FakeVolProxy(size_or_exc):
2008            return mock.Mock(return_value=mock.Mock(
2009                size=mock.Mock(side_effect=(size_or_exc,))))
2010
2011        volumes = [
2012            'volume-1',
2013            'non-existent',
2014            'non-existent',
2015            'non-cinder-volume'
2016        ]
2017
2018        client = client_mock.return_value.__enter__.return_value
2019        rbdproxy_mock.return_value.list.return_value = volumes
2020
2021        with mock.patch.object(self.driver, 'rbd',
2022                               ImageNotFound=MockImageNotFoundException,
2023                               OSError=MockOSErrorException):
2024            volproxy_mock.side_effect = [
2025                mock.MagicMock(**{'__enter__': FakeVolProxy(s)})
2026                for s in (1.0 * units.Gi,
2027                          self.driver.rbd.ImageNotFound,
2028                          self.driver.rbd.OSError,
2029                          2.0 * units.Gi)
2030            ]
2031            total_provision = self.driver._get_usage_info()
2032
2033        rbdproxy_mock.return_value.list.assert_called_once_with(client.ioctx)
2034
2035        expected_volproxy_calls = [
2036            mock.call(self.driver, v, read_only=True,
2037                      client=client.cluster, ioctx=client.ioctx)
2038            for v in volumes]
2039        self.assertEqual(expected_volproxy_calls, volproxy_mock.mock_calls)
2040
2041        self.assertEqual(3.00, total_provision)
2042
2043    def test_migrate_volume_bad_volume_status(self):
2044        self.volume_a.status = 'in-use'
2045        ret = self.driver.migrate_volume(context, self.volume_a, None)
2046        self.assertEqual((False, None), ret)
2047
2048    def test_migrate_volume_bad_host(self):
2049        host = {
2050            'capabilities': {
2051                'storage_protocol': 'not-ceph'}}
2052        ret = self.driver.migrate_volume(context, self.volume_a, host)
2053        self.assertEqual((False, None), ret)
2054
2055    def test_migrate_volume_missing_location_info(self):
2056        host = {
2057            'capabilities': {
2058                'storage_protocol': 'ceph'}}
2059        ret = self.driver.migrate_volume(context, self.volume_a, host)
2060        self.assertEqual((False, None), ret)
2061
2062    def test_migrate_volume_invalid_location_info(self):
2063        host = {
2064            'capabilities': {
2065                'storage_protocol': 'ceph',
2066                'location_info': 'foo:bar:baz'}}
2067        ret = self.driver.migrate_volume(context, self.volume_a, host)
2068        self.assertEqual((False, None), ret)
2069
2070    @mock.patch('os_brick.initiator.linuxrbd.rbd')
2071    @mock.patch('os_brick.initiator.linuxrbd.RBDClient')
2072    def test_migrate_volume_mismatch_fsid(self, mock_client, mock_rbd):
2073        host = {
2074            'capabilities': {
2075                'storage_protocol': 'ceph',
2076                'location_info': 'nondefault:None:abc:None:rbd'}}
2077
2078        mock_client().__enter__().client.get_fsid.return_value = 'abc'
2079
2080        with mock.patch.object(self.driver, '_get_fsid') as mock_get_fsid:
2081            mock_get_fsid.return_value = 'not-abc'
2082            ret = self.driver.migrate_volume(context, self.volume_a, host)
2083            self.assertEqual((False, None), ret)
2084
2085        mock_client().__enter__().client.get_fsid.return_value = 'not-abc'
2086
2087        with mock.patch.object(self.driver, '_get_fsid') as mock_get_fsid:
2088            mock_get_fsid.return_value = 'abc'
2089            ret = self.driver.migrate_volume(context, self.volume_a, host)
2090            self.assertEqual((False, None), ret)
2091
2092        host = {
2093            'capabilities': {
2094                'storage_protocol': 'ceph',
2095                'location_info': 'nondefault:None:not-abc:None:rbd'}}
2096
2097        mock_client().__enter__().client.get_fsid.return_value = 'abc'
2098
2099        with mock.patch.object(self.driver, '_get_fsid') as mock_get_fsid:
2100            mock_get_fsid.return_value = 'abc'
2101            ret = self.driver.migrate_volume(context, self.volume_a, host)
2102            self.assertEqual((False, None), ret)
2103
2104    @mock.patch('os_brick.initiator.linuxrbd.rbd')
2105    @mock.patch('os_brick.initiator.linuxrbd.RBDClient')
2106    @mock.patch('cinder.volume.drivers.rbd.RBDVolumeProxy')
2107    def test_migrate_volume(self, mock_proxy, mock_client, mock_rbd):
2108        host = {
2109            'capabilities': {
2110                'storage_protocol': 'ceph',
2111                'location_info': 'nondefault:None:abc:None:rbd'}}
2112
2113        mock_client().__enter__().client.get_fsid.return_value = 'abc'
2114
2115        with mock.patch.object(self.driver, '_get_fsid') as mock_get_fsid, \
2116                mock.patch.object(self.driver, 'delete_volume') as mock_delete:
2117            mock_get_fsid.return_value = 'abc'
2118            proxy = mock_proxy.return_value
2119            proxy.__enter__.return_value = proxy
2120            ret = self.driver.migrate_volume(context, self.volume_a,
2121                                             host)
2122            proxy.copy.assert_called_once_with(
2123                mock_client.return_value.__enter__.return_value.ioctx,
2124                self.volume_a.name)
2125            mock_delete.assert_called_once_with(self.volume_a)
2126            self.assertEqual((True, None), ret)
2127
2128    @mock.patch('tempfile.NamedTemporaryFile')
2129    @mock.patch('cinder.volume.drivers.rbd.RBDDriver.'
2130                '_check_encryption_provider',
2131                return_value={'encryption_key_id': fake.ENCRYPTION_KEY_ID})
2132    def test_create_encrypted_volume(self,
2133                                     mock_check_enc_prov,
2134                                     mock_temp_file):
2135        class DictObj(object):
2136            # convert a dict to object w/ attributes
2137            def __init__(self, d):
2138                self.__dict__ = d
2139
2140        mock_temp_file.return_value.__enter__.side_effect = [
2141            DictObj({'name': '/imgfile'}),
2142            DictObj({'name': '/passfile'})]
2143
2144        key_mgr = fake_keymgr.fake_api()
2145
2146        self.mock_object(castellan.key_manager, 'API', return_value=key_mgr)
2147        key_id = key_mgr.store(self.context, KeyObject())
2148        self.volume_c.encryption_key_id = key_id
2149
2150        enc_info = {'encryption_key_id': key_id,
2151                    'cipher': 'aes-xts-essiv',
2152                    'key_size': 256}
2153
2154        with mock.patch('cinder.volume.drivers.rbd.RBDDriver.'
2155                        '_check_encryption_provider', return_value=enc_info), \
2156                mock.patch('cinder.volume.drivers.rbd.open') as mock_open, \
2157                mock.patch.object(self.driver, '_execute') as mock_exec:
2158            self.driver._create_encrypted_volume(self.volume_c,
2159                                                 self.context)
2160            mock_open.assert_called_with('/passfile', 'w')
2161
2162            mock_exec.assert_any_call(
2163                'qemu-img', 'create', '-f', 'luks', '-o',
2164                'cipher-alg=aes-256,cipher-mode=xts,ivgen-alg=essiv',
2165                '--object',
2166                'secret,id=luks_sec,format=raw,file=/passfile',
2167                '-o', 'key-secret=luks_sec', '/imgfile', '12288M')
2168            mock_exec.assert_any_call(
2169                'rbd', 'import', '--pool', 'rbd', '--order', 22,
2170                '/imgfile', self.volume_c.name)
2171
2172    @mock.patch('cinder.objects.Volume.get_by_id')
2173    @mock.patch('cinder.db.volume_glance_metadata_get', return_value={})
2174    @common_mocks
2175    def test_get_backup_device_ceph(self, mock_gm_get, volume_get_by_id):
2176        # Use the same volume for backup (volume_a)
2177        volume_get_by_id.return_value = self.volume_a
2178        driver = self.driver
2179
2180        self._create_backup_db_entry(fake.BACKUP_ID, self.volume_a['id'], 1)
2181        backup = objects.Backup.get_by_id(self.context, fake.BACKUP_ID)
2182        backup.service = 'asdf#ceph'
2183
2184        ret = driver.get_backup_device(self.context, backup)
2185        self.assertEqual(ret, (self.volume_a, False))
2186
2187    def _create_backup_db_entry(self, backupid, volid, size,
2188                                userid=str(uuid.uuid4()),
2189                                projectid=str(uuid.uuid4())):
2190        backup = {'id': backupid, 'size': size, 'volume_id': volid,
2191                  'user_id': userid, 'project_id': projectid}
2192        return db.backup_create(self.context, backup)['id']
2193
2194    @mock.patch('cinder.volume.driver.BaseVD._get_backup_volume_temp_snapshot')
2195    @mock.patch('cinder.volume.driver.BaseVD._get_backup_volume_temp_volume')
2196    @mock.patch('cinder.objects.Volume.get_by_id')
2197    @mock.patch('cinder.db.volume_glance_metadata_get', return_value={})
2198    @common_mocks
2199    def test_get_backup_device_other(self,
2200                                     mock_gm_get,
2201                                     volume_get_by_id,
2202                                     mock_get_temp_volume,
2203                                     mock_get_temp_snapshot):
2204        # Use a cloned volume for backup (volume_b)
2205        self.volume_a.previous_status = 'in-use'
2206        mock_get_temp_volume.return_value = self.volume_b
2207        mock_get_temp_snapshot.return_value = (self.volume_b, False)
2208
2209        volume_get_by_id.return_value = self.volume_a
2210        driver = self.driver
2211
2212        self._create_backup_db_entry(fake.BACKUP_ID, self.volume_a['id'], 1)
2213        backup = objects.Backup.get_by_id(self.context, fake.BACKUP_ID)
2214        backup.service = 'asdf'
2215
2216        ret = driver.get_backup_device(self.context, backup)
2217        self.assertEqual(ret, (self.volume_b, False))
2218
2219
2220class ManagedRBDTestCase(test_driver.BaseDriverTestCase):
2221    driver_name = "cinder.volume.drivers.rbd.RBDDriver"
2222
2223    def setUp(self):
2224        super(ManagedRBDTestCase, self).setUp()
2225        self.volume.driver.set_initialized()
2226        self.volume.stats = {'allocated_capacity_gb': 0,
2227                             'pools': {}}
2228        self.called = []
2229
2230    def _create_volume_from_image(self, expected_status, raw=False,
2231                                  clone_error=False):
2232        """Try to clone a volume from an image, and check status afterwards.
2233
2234        NOTE: if clone_error is True we force the image type to raw otherwise
2235              clone_image is not called
2236        """
2237
2238        # See tests.image.fake for image types.
2239        if raw:
2240            image_id = '155d900f-4e14-4e4c-a73d-069cbf4541e6'
2241        else:
2242            image_id = 'c905cedb-7281-47e4-8a62-f26bc5fc4c77'
2243
2244        # creating volume testdata
2245        db_volume = {'display_description': 'Test Desc',
2246                     'size': 20,
2247                     'status': 'creating',
2248                     'availability_zone': 'fake_zone',
2249                     'attach_status': fields.VolumeAttachStatus.DETACHED,
2250                     'host': 'dummy'}
2251        volume = objects.Volume(context=self.context, **db_volume)
2252        volume.create()
2253
2254        try:
2255            if not clone_error:
2256                self.volume.create_volume(self.context, volume,
2257                                          request_spec={'image_id': image_id})
2258            else:
2259                self.assertRaises(exception.CinderException,
2260                                  self.volume.create_volume,
2261                                  self.context,
2262                                  volume,
2263                                  request_spec={'image_id': image_id})
2264
2265            volume = objects.Volume.get_by_id(self.context, volume.id)
2266            self.assertEqual(expected_status, volume.status)
2267        finally:
2268            # cleanup
2269            volume.destroy()
2270
2271    @mock.patch('cinder.image.image_utils.check_available_space')
2272    @mock.patch.object(cinder.image.glance, 'get_default_image_service')
2273    def test_create_vol_from_image_status_available(self, mock_gdis,
2274                                                    mock_check_space):
2275        """Clone raw image then verify volume is in available state."""
2276
2277        def _mock_clone_image(context, volume, image_location,
2278                              image_meta, image_service):
2279            return {'provider_location': None}, True
2280
2281        with mock.patch.object(self.volume.driver, 'clone_image') as \
2282                mock_clone_image:
2283            mock_clone_image.side_effect = _mock_clone_image
2284            with mock.patch.object(self.volume.driver, 'create_volume') as \
2285                    mock_create:
2286                with mock.patch.object(create_volume.CreateVolumeFromSpecTask,
2287                                       '_copy_image_to_volume') as mock_copy:
2288                    self._create_volume_from_image('available', raw=True)
2289                    self.assertFalse(mock_copy.called)
2290
2291                self.assertTrue(mock_clone_image.called)
2292                self.assertFalse(mock_create.called)
2293                self.assertTrue(mock_gdis.called)
2294
2295    @mock.patch('cinder.image.image_utils.check_available_space')
2296    @mock.patch.object(cinder.image.glance, 'get_default_image_service')
2297    @mock.patch('cinder.image.image_utils.TemporaryImages.fetch')
2298    @mock.patch('cinder.image.image_utils.qemu_img_info')
2299    def test_create_vol_from_non_raw_image_status_available(
2300            self, mock_qemu_info, mock_fetch, mock_gdis, mock_check_space):
2301        """Clone non-raw image then verify volume is in available state."""
2302
2303        def _mock_clone_image(context, volume, image_location,
2304                              image_meta, image_service):
2305            return {'provider_location': None}, False
2306
2307        image_info = imageutils.QemuImgInfo()
2308        image_info.virtual_size = '1073741824'
2309        mock_qemu_info.return_value = image_info
2310
2311        mock_fetch.return_value = mock.MagicMock(spec=utils.get_file_spec())
2312        with mock.patch.object(self.volume.driver, 'clone_image') as \
2313                mock_clone_image:
2314            mock_clone_image.side_effect = _mock_clone_image
2315            with mock.patch.object(self.volume.driver, 'create_volume') as \
2316                    mock_create:
2317                with mock.patch.object(create_volume.CreateVolumeFromSpecTask,
2318                                       '_copy_image_to_volume') as mock_copy:
2319                    self._create_volume_from_image('available', raw=False)
2320                    self.assertTrue(mock_copy.called)
2321
2322                self.assertTrue(mock_clone_image.called)
2323                self.assertTrue(mock_create.called)
2324                self.assertTrue(mock_gdis.called)
2325
2326    @mock.patch('cinder.image.image_utils.check_available_space')
2327    @mock.patch.object(cinder.image.glance, 'get_default_image_service')
2328    def test_create_vol_from_image_status_error(self, mock_gdis,
2329                                                mock_check_space):
2330        """Fail to clone raw image then verify volume is in error state."""
2331        with mock.patch.object(self.volume.driver, 'clone_image') as \
2332                mock_clone_image:
2333            mock_clone_image.side_effect = exception.CinderException
2334            with mock.patch.object(self.volume.driver, 'create_volume'):
2335                with mock.patch.object(create_volume.CreateVolumeFromSpecTask,
2336                                       '_copy_image_to_volume') as mock_copy:
2337                    self._create_volume_from_image('error', raw=True,
2338                                                   clone_error=True)
2339                    self.assertFalse(mock_copy.called)
2340
2341                self.assertTrue(mock_clone_image.called)
2342                self.assertFalse(self.volume.driver.create_volume.called)
2343                self.assertTrue(mock_gdis.called)
2344
2345    def test_clone_failure(self):
2346        driver = self.volume.driver
2347
2348        with mock.patch.object(driver, '_is_cloneable', lambda *args: False):
2349            image_loc = (mock.Mock(), None)
2350            actual = driver.clone_image(mock.Mock(),
2351                                        mock.Mock(),
2352                                        image_loc,
2353                                        {},
2354                                        mock.Mock())
2355            self.assertEqual(({}, False), actual)
2356
2357        self.assertEqual(({}, False),
2358                         driver.clone_image('', object(), None, {}, ''))
2359
2360    def test_clone_success(self):
2361        expected = ({'provider_location': None}, True)
2362        driver = self.volume.driver
2363
2364        with mock.patch.object(self.volume.driver, '_is_cloneable') as \
2365                mock_is_cloneable:
2366            mock_is_cloneable.return_value = True
2367            with mock.patch.object(self.volume.driver, '_clone') as \
2368                    mock_clone:
2369                with mock.patch.object(self.volume.driver, '_resize') as \
2370                        mock_resize:
2371                    mock_clone.return_value = {}
2372                    image_loc = ('rbd://fee/fi/fo/fum', None)
2373
2374                    volume = {'name': 'vol1'}
2375                    actual = driver.clone_image(mock.Mock(),
2376                                                volume,
2377                                                image_loc,
2378                                                {'disk_format': 'raw',
2379                                                 'id': 'id.foo'},
2380                                                mock.Mock())
2381
2382                    self.assertEqual(expected, actual)
2383                    mock_clone.assert_called_once_with(volume,
2384                                                       'fi', 'fo', 'fum')
2385                    mock_resize.assert_called_once_with(volume)
2386
2387    def test_clone_multilocation_success(self):
2388        expected = ({'provider_location': None}, True)
2389        driver = self.volume.driver
2390
2391        def cloneable_side_effect(url_location, image_meta):
2392            return url_location == 'rbd://fee/fi/fo/fum'
2393
2394        with mock.patch.object(self.volume.driver, '_is_cloneable') \
2395            as mock_is_cloneable, \
2396            mock.patch.object(self.volume.driver, '_clone') as mock_clone, \
2397            mock.patch.object(self.volume.driver, '_resize') \
2398                as mock_resize:
2399            mock_is_cloneable.side_effect = cloneable_side_effect
2400            mock_clone.return_value = {}
2401            image_loc = ('rbd://bee/bi/bo/bum',
2402                         [{'url': 'rbd://bee/bi/bo/bum'},
2403                          {'url': 'rbd://fee/fi/fo/fum'}])
2404            volume = {'name': 'vol1'}
2405            image_meta = mock.sentinel.image_meta
2406            image_service = mock.sentinel.image_service
2407
2408            actual = driver.clone_image(self.context,
2409                                        volume,
2410                                        image_loc,
2411                                        image_meta,
2412                                        image_service)
2413
2414            self.assertEqual(expected, actual)
2415            self.assertEqual(2, mock_is_cloneable.call_count)
2416            mock_clone.assert_called_once_with(volume,
2417                                               'fi', 'fo', 'fum')
2418            mock_is_cloneable.assert_called_with('rbd://fee/fi/fo/fum',
2419                                                 image_meta)
2420            mock_resize.assert_called_once_with(volume)
2421
2422    def test_clone_multilocation_failure(self):
2423        expected = ({}, False)
2424        driver = self.volume.driver
2425
2426        with mock.patch.object(driver, '_is_cloneable', return_value=False) \
2427            as mock_is_cloneable, \
2428            mock.patch.object(self.volume.driver, '_clone') as mock_clone, \
2429            mock.patch.object(self.volume.driver, '_resize') \
2430                as mock_resize:
2431            image_loc = ('rbd://bee/bi/bo/bum',
2432                         [{'url': 'rbd://bee/bi/bo/bum'},
2433                          {'url': 'rbd://fee/fi/fo/fum'}])
2434
2435            volume = {'name': 'vol1'}
2436            image_meta = mock.sentinel.image_meta
2437            image_service = mock.sentinel.image_service
2438            actual = driver.clone_image(self.context,
2439                                        volume,
2440                                        image_loc,
2441                                        image_meta,
2442                                        image_service)
2443
2444            self.assertEqual(expected, actual)
2445            self.assertEqual(2, mock_is_cloneable.call_count)
2446            mock_is_cloneable.assert_any_call('rbd://bee/bi/bo/bum',
2447                                              image_meta)
2448            mock_is_cloneable.assert_any_call('rbd://fee/fi/fo/fum',
2449                                              image_meta)
2450            self.assertFalse(mock_clone.called)
2451            self.assertFalse(mock_resize.called)
2452