1# Copyright 2013 IBM Corp.
2# Copyright (c) 2013 OpenStack Foundation
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
17
18import copy
19
20import mock
21
22from cinder import context
23from cinder import exception
24from cinder.i18n import _
25from cinder.objects import fields
26from cinder import test
27from cinder.volume import configuration as conf
28from cinder.volume.drivers.ibm.ibm_storage import ibm_storage
29from cinder.volume import volume_types
30
31FAKE = "fake"
32FAKE2 = "fake2"
33CANNOT_DELETE = "Can not delete"
34TOO_BIG_VOLUME_SIZE = 12000
35POOL_SIZE = 100
36GROUP_ID = 1
37VOLUME = {'size': 16,
38          'name': FAKE,
39          'id': 1,
40          'status': 'available'}
41VOLUME2 = {'size': 32,
42           'name': FAKE2,
43           'id': 2,
44           'status': 'available'}
45GROUP_VOLUME = {'size': 16,
46                'name': FAKE,
47                'id': 3,
48                'group_id': GROUP_ID,
49                'status': 'available'}
50
51MANAGED_FAKE = "managed_fake"
52MANAGED_VOLUME = {'size': 16,
53                  'name': MANAGED_FAKE,
54                  'id': 2}
55
56REPLICA_FAKE = "repicated_fake"
57REPLICATED_VOLUME = {'size': 64,
58                     'name': REPLICA_FAKE,
59                     'id': '2',
60                     'replication_status': fields.ReplicationStatus.ENABLED}
61
62REPLICATED_VOLUME_DISABLED = REPLICATED_VOLUME.copy()
63REPLICATED_VOLUME_DISABLED['replication_status'] = (
64    fields.ReplicationStatus.DISABLED)
65
66REPLICATION_TARGETS = [{'target_device_id': 'fakedevice'}]
67SECONDARY = 'fakedevice'
68FAKE_FAILOVER_HOST = 'fakehost@fakebackend#fakepool'
69FAKE_PROVIDER_LOCATION = 'fake_provider_location'
70FAKE_DRIVER_DATA = 'fake_driver_data'
71
72CONTEXT = {}
73
74FAKESNAPSHOT = 'fakesnapshot'
75SNAPSHOT = {'name': 'fakesnapshot',
76            'id': 3}
77
78GROUP = {'id': GROUP_ID, }
79GROUP_SNAPSHOT_ID = 1
80GROUP_SNAPSHOT = {'id': GROUP_SNAPSHOT_ID,
81                  'group_id': GROUP_ID}
82
83CONNECTOR = {'initiator': "iqn.2012-07.org.fake:01:948f189c4695", }
84
85FAKE_PROXY = 'cinder.tests.unit.volume.drivers.ibm.test_ibm_storage' \
86    '.IBMStorageFakeProxyDriver'
87
88
89class IBMStorageFakeProxyDriver(object):
90    """Fake IBM Storage driver
91
92    Fake IBM Storage driver for IBM XIV, Spectrum Accelerate,
93    FlashSystem A9000, FlashSystem A9000R and DS8000 storage systems.
94    """
95
96    def __init__(self, ibm_storage_info, logger, expt,
97                 driver=None, active_backend_id=None, host=None):
98        """Initialize Proxy."""
99
100        self.ibm_storage_info = ibm_storage_info
101        self.logger = logger
102        self.exception = expt
103        self.storage_portal = \
104            self.storage_iqn = FAKE
105
106        self.volumes = {}
107        self.snapshots = {}
108        self.driver = driver
109
110    def setup(self, context):
111        if self.ibm_storage_info['user'] != self.driver\
112                .configuration.san_login:
113            raise self.exception.NotAuthorized()
114
115        if self.ibm_storage_info['address'] != self.driver\
116                .configuration.san_ip:
117            raise self.exception.HostNotFound(host='fake')
118
119    def create_volume(self, volume):
120        if volume['size'] > POOL_SIZE:
121            raise self.exception.VolumeBackendAPIException(data='blah')
122        self.volumes[volume['name']] = volume
123
124    def volume_exists(self, volume):
125        return self.volumes.get(volume['name'], None) is not None
126
127    def delete_volume(self, volume):
128        if self.volumes.get(volume['name'], None) is not None:
129            del self.volumes[volume['name']]
130
131    def manage_volume_get_size(self, volume, existing_ref):
132        if self.volumes.get(existing_ref['source-name'], None) is None:
133            raise self.exception.VolumeNotFound(volume_id=volume['id'])
134        return self.volumes[existing_ref['source-name']]['size']
135
136    def manage_volume(self, volume, existing_ref):
137        if self.volumes.get(existing_ref['source-name'], None) is None:
138            raise self.exception.VolumeNotFound(volume_id=volume['id'])
139        volume['size'] = MANAGED_VOLUME['size']
140        return {}
141
142    def unmanage_volume(self, volume):
143        pass
144
145    def initialize_connection(self, volume, connector):
146        if not self.volume_exists(volume):
147            raise self.exception.VolumeNotFound(volume_id=volume['id'])
148        lun_id = volume['id']
149
150        self.volumes[volume['name']]['attached'] = connector
151
152        return {'driver_volume_type': 'iscsi',
153                'data': {'target_discovered': True,
154                         'target_portal': self.storage_portal,
155                         'target_iqn': self.storage_iqn,
156                         'target_lun': lun_id,
157                         'volume_id': volume['id'],
158                         'multipath': True,
159                         'provider_location': "%s,1 %s %s" % (
160                             self.storage_portal,
161                             self.storage_iqn,
162                             lun_id), },
163                }
164
165    def terminate_connection(self, volume, connector):
166        if not self.volume_exists(volume):
167            raise self.exception.VolumeNotFound(volume_id=volume['id'])
168        if not self.is_volume_attached(volume, connector):
169            raise self.exception.NotFound(_('Volume not found for '
170                                            'instance %(instance_id)s.')
171                                          % {'instance_id': 'fake'})
172        del self.volumes[volume['name']]['attached']
173
174    def is_volume_attached(self, volume, connector):
175        if not self.volume_exists(volume):
176            raise self.exception.VolumeNotFound(volume_id=volume['id'])
177
178        return (self.volumes[volume['name']].get('attached', None)
179                == connector)
180
181    def get_replication_status(self, context, volume):
182        if volume['replication_status'] == 'invalid_status_val':
183            raise exception.CinderException()
184        return {'replication_status': 'active'}
185
186    def retype(self, ctxt, volume, new_type, diff, host):
187        volume['easytier'] = new_type['extra_specs']['easytier']
188        return True, volume
189
190    def create_group(self, ctxt, group):
191
192        volumes = [volume for k, volume in self.volumes.items()
193                   if volume['group_id'] == group['id']]
194
195        if volumes:
196            raise exception.CinderException(
197                message='The group id of volume may be wrong.')
198
199        return {'status': fields.GroupStatus.AVAILABLE}
200
201    def delete_group(self, ctxt, group, volumes):
202        for volume in self.volumes.values():
203            if group.get('id') == volume.get('group_id'):
204                if volume['name'] == CANNOT_DELETE:
205                    raise exception.VolumeBackendAPIException(
206                        message='Volume can not be deleted')
207                else:
208                    volume['status'] = 'deleted'
209                    volumes.append(volume)
210
211        # Delete snapshots in group
212        self.snapshots = {k: snap for k, snap in self.snapshots.items()
213                          if not(snap.get('group_id') == group.get('id'))}
214
215        # Delete volume in group
216        self.volumes = {k: vol for k, vol in self.volumes.items()
217                        if not(vol.get('group_id') == group.get('id'))}
218
219        return {'status': fields.GroupStatus.DELETED}, volumes
220
221    def update_group(self, context, group, add_volumes, remove_volumes):
222        model_update = {'status': fields.GroupStatus.AVAILABLE}
223        return model_update, None, None
224
225    def create_group_from_src(self, context, group, volumes, group_snapshot,
226                              snapshots, source_group=None, source_vols=None):
227
228        return None, None
229
230    def create_group_snapshot(self, ctxt, group_snapshot, snapshots):
231        for volume in self.volumes.values():
232            if group_snapshot.get('group_id') == volume.get('group_id'):
233                if volume['size'] > POOL_SIZE / 2:
234                    raise self.exception.VolumeBackendAPIException(data='blah')
235
236                snapshot = copy.deepcopy(volume)
237                snapshot['name'] = (
238                    CANNOT_DELETE if snapshot['name'] == CANNOT_DELETE
239                    else snapshot['name'] + 'Snapshot')
240                snapshot['status'] = 'available'
241                snapshot['group_snapshot_id'] = group_snapshot.get('id')
242                snapshot['group_id'] = group_snapshot.get('group_id')
243                self.snapshots[snapshot['name']] = snapshot
244                snapshots.append(snapshot)
245
246        return {'status': fields.GroupSnapshotStatus.AVAILABLE}, snapshots
247
248    def delete_group_snapshot(self, ctxt, group_snapshot, snapshots):
249        updated_snapshots = []
250        for snapshot in snapshots:
251            if snapshot['name'] == CANNOT_DELETE:
252                raise exception.VolumeBackendAPIException(
253                    message='Snapshot can not be deleted')
254            else:
255                snapshot['status'] = 'deleted'
256                updated_snapshots.append(snapshot)
257
258        # Delete snapshots in group
259        self.snapshots = {k: snap for k, snap in self.snapshots.items()
260                          if not(snap.get('group_id')
261                                 == group_snapshot.get('group_snapshot_id'))}
262
263        return {'status': 'deleted'}, updated_snapshots
264
265    def freeze_backend(self, context):
266        return True
267
268    def thaw_backend(self, context):
269        return True
270
271    def failover_host(self, context, volumes, secondary_id, groups=None):
272        target_id = 'BLA'
273        volume_update_list = []
274        for volume in volumes:
275            status = 'failed-over'
276            if volume['replication_status'] == 'invalid_status_val':
277                status = 'error'
278            volume_update_list.append(
279                {'volume_id': volume['id'],
280                 'updates': {'replication_status': status}})
281
282        return target_id, volume_update_list, []
283
284    def enable_replication(self, context, group, volumes):
285        vol_status = []
286        for vol in volumes:
287            vol_status.append(
288                {'id': vol['id'],
289                 'replication_status': fields.ReplicationStatus.ENABLED})
290        return (
291            {'replication_status': fields.ReplicationStatus.ENABLED},
292            vol_status)
293
294    def disable_replication(self, context, group, volumes):
295        volume_update_list = []
296        for volume in volumes:
297            volume_update_list.append(
298                {'id': volume['id'],
299                 'replication_status': fields.ReplicationStatus.DISABLED})
300        return (
301            {'replication_status': fields.ReplicationStatus.DISABLED},
302            volume_update_list)
303
304    def failover_replication(self, context, group, volumes, secondary_id):
305        volume_update_list = []
306        for volume in volumes:
307            volume_update_list.append(
308                {'id': volume['id'],
309                 'replication_status': fields.ReplicationStatus.FAILED_OVER})
310        return ({'replication_status': fields.ReplicationStatus.FAILED_OVER},
311                volume_update_list)
312
313    def get_replication_error_status(self, context, groups):
314        return(
315            [{'group_id': groups[0]['id'],
316              'replication_status': fields.ReplicationStatus.ERROR}],
317            [{'volume_id': VOLUME['id'],
318              'replication_status': fields.ReplicationStatus.ERROR}])
319
320
321class IBMStorageVolumeDriverTest(test.TestCase):
322    """Test IBM Storage driver
323
324    Test IBM Storage driver for IBM XIV, Spectrum Accelerate,
325    FlashSystem A9000, FlashSystem A9000R and DS8000 storage Systems.
326    """
327
328    def setUp(self):
329        """Initialize IBM Storage Driver."""
330        super(IBMStorageVolumeDriverTest, self).setUp()
331
332        configuration = mock.Mock(conf.Configuration)
333        configuration.san_is_local = False
334        configuration.proxy = FAKE_PROXY
335        configuration.connection_type = 'iscsi'
336        configuration.chap = 'disabled'
337        configuration.san_ip = FAKE
338        configuration.management_ips = FAKE
339        configuration.san_login = FAKE
340        configuration.san_clustername = FAKE
341        configuration.san_password = FAKE
342        configuration.append_config_values(mock.ANY)
343
344        self.driver = ibm_storage.IBMStorageDriver(
345            configuration=configuration)
346
347    def test_initialized_should_set_ibm_storage_info(self):
348        """Test that the san flags are passed to the IBM proxy."""
349
350        self.assertEqual(
351            self.driver.proxy.ibm_storage_info['user'],
352            self.driver.configuration.san_login)
353        self.assertEqual(
354            self.driver.proxy.ibm_storage_info['password'],
355            self.driver.configuration.san_password)
356        self.assertEqual(
357            self.driver.proxy.ibm_storage_info['address'],
358            self.driver.configuration.san_ip)
359        self.assertEqual(
360            self.driver.proxy.ibm_storage_info['vol_pool'],
361            self.driver.configuration.san_clustername)
362
363    def test_setup_should_fail_if_credentials_are_invalid(self):
364        """Test that the proxy validates credentials."""
365
366        self.driver.proxy.ibm_storage_info['user'] = 'invalid'
367        self.assertRaises(exception.NotAuthorized, self.driver.do_setup, None)
368
369    def test_setup_should_fail_if_connection_is_invalid(self):
370        """Test that the proxy validates connection."""
371
372        self.driver.proxy.ibm_storage_info['address'] = \
373            'invalid'
374        self.assertRaises(exception.HostNotFound, self.driver.do_setup, None)
375
376    def test_create_volume(self):
377        """Test creating a volume."""
378
379        self.driver.do_setup(None)
380        self.driver.create_volume(VOLUME)
381        has_volume = self.driver.proxy.volume_exists(VOLUME)
382        self.assertTrue(has_volume)
383        self.driver.delete_volume(VOLUME)
384
385    def test_volume_exists(self):
386        """Test the volume exist method with a volume that doesn't exist."""
387
388        self.driver.do_setup(None)
389
390        self.assertFalse(
391            self.driver.proxy.volume_exists({'name': FAKE})
392        )
393
394    def test_delete_volume(self):
395        """Verify that a volume is deleted."""
396
397        self.driver.do_setup(None)
398        self.driver.create_volume(VOLUME)
399        self.driver.delete_volume(VOLUME)
400        has_volume = self.driver.proxy.volume_exists(VOLUME)
401        self.assertFalse(has_volume)
402
403    def test_delete_volume_should_fail_for_not_existing_volume(self):
404        """Verify that deleting a non-existing volume is OK."""
405
406        self.driver.do_setup(None)
407        self.driver.delete_volume(VOLUME)
408
409    def test_create_volume_should_fail_if_no_pool_space_left(self):
410        """Verify that the proxy validates volume pool space."""
411
412        self.driver.do_setup(None)
413        self.assertRaises(exception.VolumeBackendAPIException,
414                          self.driver.create_volume,
415                          {'name': FAKE,
416                           'id': 1,
417                           'size': TOO_BIG_VOLUME_SIZE})
418
419    def test_initialize_connection(self):
420        """Test that inititialize connection attaches volume to host."""
421
422        self.driver.do_setup(None)
423        self.driver.create_volume(VOLUME)
424        self.driver.initialize_connection(VOLUME, CONNECTOR)
425
426        self.assertTrue(
427            self.driver.proxy.is_volume_attached(VOLUME, CONNECTOR))
428
429        self.driver.terminate_connection(VOLUME, CONNECTOR)
430        self.driver.delete_volume(VOLUME)
431
432    def test_initialize_connection_should_fail_for_non_existing_volume(self):
433        """Verify that initialize won't work for non-existing volume."""
434
435        self.driver.do_setup(None)
436        self.assertRaises(exception.VolumeNotFound,
437                          self.driver.initialize_connection,
438                          VOLUME,
439                          CONNECTOR)
440
441    def test_terminate_connection(self):
442        """Test terminating a connection."""
443
444        self.driver.do_setup(None)
445        self.driver.create_volume(VOLUME)
446        self.driver.initialize_connection(VOLUME, CONNECTOR)
447        self.driver.terminate_connection(VOLUME, CONNECTOR)
448
449        self.assertFalse(self.driver.proxy.is_volume_attached(
450            VOLUME,
451            CONNECTOR))
452
453        self.driver.delete_volume(VOLUME)
454
455    def test_terminate_connection_should_fail_on_non_existing_volume(self):
456        """Test that terminate won't work for non-existing volumes."""
457
458        self.driver.do_setup(None)
459        self.assertRaises(exception.VolumeNotFound,
460                          self.driver.terminate_connection,
461                          VOLUME,
462                          CONNECTOR)
463
464    def test_manage_existing_get_size(self):
465        """Test that manage_existing_get_size returns the expected size. """
466
467        self.driver.do_setup(None)
468        self.driver.create_volume(MANAGED_VOLUME)
469        existing_ref = {'source-name': MANAGED_VOLUME['name']}
470        return_size = self.driver.manage_existing_get_size(
471            VOLUME,
472            existing_ref)
473        self.assertEqual(MANAGED_VOLUME['size'], return_size)
474
475        # cover both case, whether driver renames the volume or not
476        self.driver.delete_volume(VOLUME)
477        self.driver.delete_volume(MANAGED_VOLUME)
478
479    def test_manage_existing_get_size_should_fail_on_non_existing_volume(self):
480        """Test that manage_existing_get_size fails on non existing volume. """
481
482        self.driver.do_setup(None)
483        # on purpose - do NOT create managed volume
484        existing_ref = {'source-name': MANAGED_VOLUME['name']}
485        self.assertRaises(exception.VolumeNotFound,
486                          self.driver.manage_existing_get_size,
487                          VOLUME,
488                          existing_ref)
489
490    def test_manage_existing(self):
491        """Test that manage_existing returns successfully. """
492
493        self.driver.do_setup(None)
494        self.driver.create_volume(MANAGED_VOLUME)
495        existing_ref = {'source-name': MANAGED_VOLUME['name']}
496        self.driver.manage_existing(VOLUME, existing_ref)
497        self.assertEqual(MANAGED_VOLUME['size'], VOLUME['size'])
498
499        # cover both case, whether driver renames the volume or not
500        self.driver.delete_volume(VOLUME)
501        self.driver.delete_volume(MANAGED_VOLUME)
502
503    def test_manage_existing_should_fail_on_non_existing_volume(self):
504        """Test that manage_existing fails on non existing volume. """
505
506        self.driver.do_setup(None)
507        # on purpose - do NOT create managed volume
508        existing_ref = {'source-name': MANAGED_VOLUME['name']}
509        self.assertRaises(exception.VolumeNotFound,
510                          self.driver.manage_existing,
511                          VOLUME,
512                          existing_ref)
513
514    def test_get_replication_status(self):
515        """Test that get_replication_status return successfully. """
516
517        self.driver.do_setup(None)
518
519        # assume the replicated volume is inactive
520        replicated_volume = copy.deepcopy(REPLICATED_VOLUME)
521        replicated_volume['replication_status'] = 'inactive'
522        model_update = self.driver.get_replication_status(
523            CONTEXT,
524            replicated_volume
525        )
526        self.assertEqual('active', model_update['replication_status'])
527
528    def test_get_replication_status_fail_on_exception(self):
529        """Test that get_replication_status fails on exception"""
530
531        self.driver.do_setup(None)
532
533        replicated_volume = copy.deepcopy(REPLICATED_VOLUME)
534        # on purpose - set invalid value to replication_status
535        # expect an exception.
536        replicated_volume['replication_status'] = 'invalid_status_val'
537        self.assertRaises(
538            exception.CinderException,
539            self.driver.get_replication_status,
540            CONTEXT,
541            replicated_volume
542        )
543
544    def test_retype(self):
545        """Test that retype returns successfully."""
546
547        self.driver.do_setup(None)
548
549        # prepare parameters
550        ctxt = context.get_admin_context()
551
552        host = {
553            'host': 'foo',
554            'capabilities': {
555                'location_info': 'ibm_storage_fake_1',
556                'extent_size': '1024'
557            }
558        }
559
560        key_specs_old = {'easytier': False, 'warning': 2, 'autoexpand': True}
561        key_specs_new = {'easytier': True, 'warning': 5, 'autoexpand': False}
562        old_type_ref = volume_types.create(ctxt, 'old', key_specs_old)
563        new_type_ref = volume_types.create(ctxt, 'new', key_specs_new)
564
565        diff, equal = volume_types.volume_types_diff(
566            ctxt,
567            old_type_ref['id'],
568            new_type_ref['id'],
569        )
570
571        volume = copy.deepcopy(VOLUME)
572        old_type = volume_types.get_volume_type(ctxt, old_type_ref['id'])
573        volume['volume_type'] = old_type
574        volume['host'] = host
575        new_type = volume_types.get_volume_type(ctxt, new_type_ref['id'])
576
577        self.driver.create_volume(volume)
578        ret = self.driver.retype(ctxt, volume, new_type, diff, host)
579        self.assertTrue(bool(ret))
580        self.assertEqual('1', volume['easytier'])
581
582    def test_retype_fail_on_exception(self):
583        """Test that retype fails on exception."""
584
585        self.driver.do_setup(None)
586
587        # prepare parameters
588        ctxt = context.get_admin_context()
589
590        host = {
591            'host': 'foo',
592            'capabilities': {
593                'location_info': 'ibm_storage_fake_1',
594                'extent_size': '1024'
595            }
596        }
597
598        key_specs_old = {'easytier': False, 'warning': 2, 'autoexpand': True}
599        old_type_ref = volume_types.create(ctxt, 'old', key_specs_old)
600        new_type_ref = volume_types.create(ctxt, 'new')
601
602        diff, equal = volume_types.volume_types_diff(
603            ctxt,
604            old_type_ref['id'],
605            new_type_ref['id'],
606        )
607
608        volume = copy.deepcopy(VOLUME)
609        old_type = volume_types.get_volume_type(ctxt, old_type_ref['id'])
610        volume['volume_type'] = old_type
611        volume['host'] = host
612        new_type = volume_types.get_volume_type(ctxt, new_type_ref['id'])
613
614        self.driver.create_volume(volume)
615        self.assertRaises(
616            KeyError,
617            self.driver.retype,
618            ctxt, volume, new_type, diff, host
619        )
620
621    def test_create_group(self):
622        """Test that create_group return successfully."""
623
624        self.driver.do_setup(None)
625
626        ctxt = context.get_admin_context()
627
628        # Create group
629        model_update = self.driver.create_group(ctxt, GROUP)
630
631        self.assertEqual(fields.GroupStatus.AVAILABLE,
632                         model_update['status'],
633                         "Group created failed")
634
635    def test_create_group_fail_on_group_not_empty(self):
636        """Test create_group with empty group."""
637
638        self.driver.do_setup(None)
639
640        ctxt = context.get_admin_context()
641
642        # Create volumes
643        # And add the volumes into the group before creating group
644        self.driver.create_volume(GROUP_VOLUME)
645
646        self.assertRaises(exception.CinderException,
647                          self.driver.create_group,
648                          ctxt, GROUP)
649
650    def test_delete_group(self):
651        """Test that delete_group return successfully."""
652
653        self.driver.do_setup(None)
654
655        ctxt = context.get_admin_context()
656
657        # Create group
658        self.driver.create_group(ctxt, GROUP)
659
660        # Create volumes and add them to group
661        self.driver.create_volume(GROUP_VOLUME)
662
663        # Delete group
664        model_update, volumes = self.driver.delete_group(ctxt, GROUP,
665                                                         [GROUP_VOLUME])
666
667        # Verify the result
668        self.assertEqual(fields.GroupStatus.DELETED,
669                         model_update['status'],
670                         'Group deleted failed')
671        for volume in volumes:
672            self.assertEqual('deleted',
673                             volume['status'],
674                             'Group deleted failed')
675
676    def test_delete_group_fail_on_volume_not_delete(self):
677        """Test delete_group with volume delete failure."""
678
679        self.driver.do_setup(None)
680
681        ctxt = context.get_admin_context()
682
683        # Create group
684        self.driver.create_group(ctxt, GROUP)
685
686        # Set the volume not to be deleted
687        volume = copy.deepcopy(GROUP_VOLUME)
688        volume['name'] = CANNOT_DELETE
689
690        # Create volumes and add them to group
691        self.driver.create_volume(volume)
692
693        self.assertRaises(exception.VolumeBackendAPIException,
694                          self.driver.delete_group,
695                          ctxt, GROUP, [volume])
696
697    def test_create_group_snapshot(self):
698        """Test that create_group_snapshot return successfully."""
699
700        self.driver.do_setup(None)
701
702        ctxt = context.get_admin_context()
703
704        # Create group
705        self.driver.create_group(ctxt, GROUP)
706
707        # Create volumes and add them to group
708        self.driver.create_volume(VOLUME)
709
710        # Create group snapshot
711        model_update, snapshots = self.driver.create_group_snapshot(
712            ctxt, GROUP_SNAPSHOT, [VOLUME])
713
714        # Verify the result
715        self.assertEqual(fields.GroupSnapshotStatus.AVAILABLE,
716                         model_update['status'],
717                         'Group Snapshot created failed')
718        for snap in snapshots:
719            self.assertEqual('available',
720                             snap['status'])
721
722        # Clean the environment
723        self.driver.delete_group_snapshot(ctxt, GROUP_SNAPSHOT, [VOLUME])
724        self.driver.delete_group(ctxt, GROUP, [VOLUME])
725
726    def test_create_group_snapshot_fail_on_no_pool_space_left(self):
727        """Test create_group_snapshot when no pool space left."""
728
729        self.driver.do_setup(None)
730
731        ctxt = context.get_admin_context()
732
733        # Create group
734        self.driver.create_group(ctxt, GROUP)
735
736        # Set the volume size
737        volume = copy.deepcopy(GROUP_VOLUME)
738        volume['size'] = POOL_SIZE / 2 + 1
739
740        # Create volumes and add them to group
741        self.driver.create_volume(volume)
742
743        self.assertRaises(exception.VolumeBackendAPIException,
744                          self.driver.create_group_snapshot,
745                          ctxt, GROUP_SNAPSHOT, [volume])
746
747        # Clean the environment
748        self.driver.volumes = None
749        self.driver.delete_group(ctxt, GROUP, [volume])
750
751    def test_delete_group_snapshot(self):
752        """Test that delete_group_snapshot return successfully."""
753
754        self.driver.do_setup(None)
755
756        ctxt = context.get_admin_context()
757
758        # Create group
759        self.driver.create_group(ctxt, GROUP)
760
761        # Create volumes and add them to group
762        self.driver.create_volume(GROUP_VOLUME)
763
764        # Create group snapshot
765        self.driver.create_group_snapshot(ctxt, GROUP_SNAPSHOT, [GROUP_VOLUME])
766
767        # Delete group snapshot
768        model_update, snapshots = self.driver.delete_group_snapshot(
769            ctxt, GROUP_SNAPSHOT, [GROUP_VOLUME])
770
771        # Verify the result
772        self.assertEqual(fields.GroupSnapshotStatus.DELETED,
773                         model_update['status'],
774                         'Group Snapshot deleted failed')
775        for snap in snapshots:
776            self.assertEqual('deleted',
777                             snap['status'])
778
779        # Clean the environment
780        self.driver.delete_group(ctxt, GROUP, [GROUP_VOLUME])
781
782    def test_delete_group_snapshot_fail_on_snapshot_not_delete(self):
783        """Test delete_group_snapshot when the snapshot cannot be deleted."""
784
785        self.driver.do_setup(None)
786
787        ctxt = context.get_admin_context()
788
789        # Create group
790        self.driver.create_group(ctxt, GROUP)
791
792        # Set the snapshot not to be deleted
793        volume = copy.deepcopy(GROUP_VOLUME)
794        volume['name'] = CANNOT_DELETE
795
796        # Create volumes and add them to group
797        self.driver.create_volume(volume)
798
799        # Create group snapshot
800        self.driver.create_group_snapshot(ctxt, GROUP_SNAPSHOT, [volume])
801
802        self.assertRaises(exception.VolumeBackendAPIException,
803                          self.driver.delete_group_snapshot,
804                          ctxt, GROUP_SNAPSHOT, [volume])
805
806    def test_update_group_without_volumes(self):
807        """Test update_group when there are no volumes specified."""
808
809        self.driver.do_setup(None)
810
811        ctxt = context.get_admin_context()
812
813        # Update group
814        model_update, added, removed = self.driver.update_group(
815            ctxt, GROUP, [], [])
816
817        self.assertEqual(fields.GroupStatus.AVAILABLE,
818                         model_update['status'],
819                         "Group update failed")
820        self.assertIsNone(added,
821                          "added volumes list is not empty")
822        self.assertIsNone(removed,
823                          "removed volumes list is not empty")
824
825    def test_update_group_with_volumes(self):
826        """Test update_group when there are volumes specified."""
827
828        self.driver.do_setup(None)
829
830        ctxt = context.get_admin_context()
831
832        # Update group
833        model_update, added, removed = self.driver.update_group(
834            ctxt, GROUP, [VOLUME], [VOLUME2])
835
836        self.assertEqual(fields.GroupStatus.AVAILABLE,
837                         model_update['status'],
838                         "Group update failed")
839        self.assertIsNone(added,
840                          "added volumes list is not empty")
841        self.assertIsNone(removed,
842                          "removed volumes list is not empty")
843
844    def test_create_group_from_src_without_volumes(self):
845        """Test create_group_from_src with no volumes specified."""
846
847        self.driver.do_setup(None)
848
849        ctxt = context.get_admin_context()
850
851        # Create group from source
852        model_update, volumes_model_update = (
853            self.driver.create_group_from_src(
854                ctxt, GROUP, [], GROUP_SNAPSHOT, []))
855
856        # model_update can be None or return available in status
857        if model_update:
858            self.assertEqual(fields.GroupStatus.AVAILABLE,
859                             model_update['status'],
860                             "Group create from source failed")
861        # volumes_model_update can be None or return available in status
862        if volumes_model_update:
863            self.assertFalse(volumes_model_update,
864                             "volumes list is not empty")
865
866    def test_create_group_from_src_with_volumes(self):
867        """Test create_group_from_src with volumes specified."""
868
869        self.driver.do_setup(None)
870
871        ctxt = context.get_admin_context()
872
873        # Create group from source
874        model_update, volumes_model_update = (
875            self.driver.create_group_from_src(
876                ctxt, GROUP, [VOLUME], GROUP_SNAPSHOT, [SNAPSHOT]))
877
878        # model_update can be None or return available in status
879        if model_update:
880            self.assertEqual(fields.GroupStatus.AVAILABLE,
881                             model_update['status'],
882                             "Group create from source failed")
883        # volumes_model_update can be None or return available in status
884        if volumes_model_update:
885            self.assertEqual(fields.GroupStatus.AVAILABLE,
886                             volumes_model_update['status'],
887                             "volumes list status failed")
888
889    def test_freeze_backend(self):
890        """Test that freeze_backend returns successful"""
891
892        self.driver.do_setup(None)
893
894        # not much we can test here...
895        self.assertTrue(self.driver.freeze_backend(CONTEXT))
896
897    def test_thaw_backend(self):
898        """Test that thaw_backend returns successful"""
899
900        self.driver.do_setup(None)
901
902        # not much we can test here...
903        self.assertTrue(self.driver.thaw_backend(CONTEXT))
904
905    def test_failover_host(self):
906        """Test that failover_host returns expected values"""
907
908        self.driver.do_setup(None)
909
910        replicated_volume = copy.deepcopy(REPLICATED_VOLUME)
911        # assume the replication_status is active
912        replicated_volume['replication_status'] = 'active'
913
914        expected_target_id = 'BLA'
915        expected_volume_update_list = [
916            {'volume_id': REPLICATED_VOLUME['id'],
917             'updates': {'replication_status': 'failed-over'}}]
918
919        target_id, volume_update_list, __ = self.driver.failover_host(
920            CONTEXT,
921            [replicated_volume],
922            SECONDARY,
923            []
924        )
925
926        self.assertEqual(expected_target_id, target_id)
927        self.assertEqual(expected_volume_update_list, volume_update_list)
928
929    def test_failover_host_bad_state(self):
930        """Test that failover_host returns with error"""
931
932        self.driver.do_setup(None)
933
934        replicated_volume = copy.deepcopy(REPLICATED_VOLUME)
935        # assume the replication_status is active
936        replicated_volume['replication_status'] = 'invalid_status_val'
937
938        expected_target_id = 'BLA'
939        expected_volume_update_list = [
940            {'volume_id': REPLICATED_VOLUME['id'],
941             'updates': {'replication_status': 'error'}}]
942
943        target_id, volume_update_list, __ = self.driver.failover_host(
944            CONTEXT,
945            [replicated_volume],
946            SECONDARY,
947            []
948        )
949
950        self.assertEqual(expected_target_id, target_id)
951        self.assertEqual(expected_volume_update_list, volume_update_list)
952
953    def test_enable_replication(self):
954        self.driver.do_setup(None)
955        model_update, volumes_model_update = self.driver.enable_replication(
956            CONTEXT, GROUP, [REPLICATED_VOLUME])
957        self.assertEqual(fields.ReplicationStatus.ENABLED,
958                         model_update['replication_status'])
959        for vol in volumes_model_update:
960            self.assertEqual(fields.ReplicationStatus.ENABLED,
961                             vol['replication_status'])
962
963    def test_disable_replication(self):
964        self.driver.do_setup(None)
965        model_update, volumes_model_update = self.driver.disable_replication(
966            CONTEXT, GROUP, [REPLICATED_VOLUME_DISABLED])
967        self.assertEqual(fields.ReplicationStatus.DISABLED,
968                         model_update['replication_status'])
969        for vol in volumes_model_update:
970            self.assertEqual(fields.ReplicationStatus.DISABLED,
971                             volumes_model_update[0]['replication_status'])
972
973    def test_failover_replication(self):
974        self.driver.do_setup(None)
975        model_update, volumes_model_update = self.driver.failover_replication(
976            CONTEXT, GROUP, [VOLUME], SECONDARY)
977        self.assertEqual(fields.ReplicationStatus.FAILED_OVER,
978                         model_update['replication_status'])
979
980    def test_get_replication_error_status(self):
981        self.driver.do_setup(None)
982        group_model_updates, volume_model_updates = (
983            self.driver.get_replication_error_status(CONTEXT, [GROUP]))
984        self.assertEqual(fields.ReplicationStatus.ERROR,
985                         group_model_updates[0]['replication_status'])
986        self.assertEqual(fields.ReplicationStatus.ERROR,
987                         volume_model_updates[0]['replication_status'])
988