~ubuntu-branches/ubuntu/utopic/cinder/utopic

« back to all changes in this revision

Viewing changes to cinder/volume/drivers/zadara.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short, James Page, Adam Gandelman, Chuck Short
  • Date: 2013-09-08 21:09:46 UTC
  • mfrom: (1.1.18)
  • Revision ID: package-import@ubuntu.com-20130908210946-3dbzq1jy5uji4wad
Tags: 1:2013.2~b3-0ubuntu1
[ James Page ]
* d/control: Switch ceph-common -> python-ceph inline with upstream
  refactoring of Ceph RBD driver, move to Suggests of python-cinder.
  (LP: #1190791). 

[ Adam Gandelman ]
* debian/patches/avoid_paramiko_vers_depends.patch: Dropped, no longer
  required.
* Add minimum requirement python-greenlet (>= 0.3.2).
* Add minimum requirement python-eventlet (>= 0.12.0).
* Add minimum requirement python-paramiko (>= 1.8).

[ Chuck Short ]
* New upstream release.
* debian/patches/skip-sqlachemy-failures.patch: Skip testfailures
  with sqlalchemy 0.8 until they are fixed upstream.
* debian/control: Add python-babel to build-depends.
* debian/control: Add python-novaclient to build-depends.

Show diffs side-by-side

added added

removed removed

Lines of Context:
18
18
"""
19
19
Volume driver for Zadara Virtual Private Storage Array (VPSA).
20
20
 
21
 
This driver requires VPSA with API ver.12.06 or higher.
 
21
This driver requires VPSA with API ver.13.07 or higher.
22
22
"""
23
23
 
24
24
 
31
31
from cinder.openstack.common import log as logging
32
32
from cinder.volume import driver
33
33
 
34
 
 
35
 
LOG = logging.getLogger("cinder.volume.driver")
 
34
LOG = logging.getLogger(__name__)
36
35
 
37
36
zadara_opts = [
38
37
    cfg.StrOpt('zadara_vpsa_ip',
56
55
               default=None,
57
56
               help='Name of VPSA storage pool for volumes'),
58
57
 
59
 
    cfg.StrOpt('zadara_default_cache_policy',
60
 
               default='write-through',
61
 
               help='Default cache policy for volumes'),
62
 
    cfg.StrOpt('zadara_default_encryption',
63
 
               default='NO',
64
 
               help='Default encryption policy for volumes'),
 
58
    cfg.BoolOpt('zadara_vol_thin',
 
59
                default=True,
 
60
                help='Default thin provisioning policy for volumes'),
 
61
    cfg.BoolOpt('zadara_vol_encrypt',
 
62
                default=False,
 
63
                help='Default encryption policy for volumes'),
65
64
    cfg.StrOpt('zadara_default_striping_mode',
66
65
               default='simple',
67
66
               help='Default striping mode for volumes'),
85
84
class ZadaraVPSAConnection(object):
86
85
    """Executes volume driver commands on VPSA."""
87
86
 
88
 
    def __init__(self, host, port, ssl, user, password):
89
 
        self.host = host
90
 
        self.port = port
91
 
        self.use_ssl = ssl
92
 
        self.user = user
93
 
        self.password = password
 
87
    def __init__(self, conf):
 
88
        self.conf = conf
94
89
        self.access_key = None
95
90
 
96
91
        self.ensure_connection()
109
104
        vpsa_commands = {
110
105
            'login': ('POST',
111
106
                      '/api/users/login.xml',
112
 
                      {'user': self.user,
113
 
                       'password': self.password}),
 
107
                      {'user': self.conf.zadara_user,
 
108
                       'password': self.conf.zadara_password}),
114
109
 
115
110
            # Volume operations
116
111
            'create_volume': ('POST',
117
112
                              '/api/volumes.xml',
118
 
                              {'display_name': kwargs.get('name'),
119
 
                               'virtual_capacity': kwargs.get('size'),
120
 
                               'raid_group_name[]': CONF.zadara_vpsa_poolname,
121
 
                               'quantity': 1,
122
 
                               'cache': CONF.zadara_default_cache_policy,
123
 
                               'crypt': CONF.zadara_default_encryption,
124
 
                               'mode': CONF.zadara_default_striping_mode,
125
 
                               'stripesize': CONF.zadara_default_stripesize,
126
 
                               'force': 'NO'}),
 
113
                              {'name': kwargs.get('name'),
 
114
                               'capacity': kwargs.get('size'),
 
115
                               'pool': self.conf.zadara_vpsa_poolname,
 
116
                               'thin': 'YES'
 
117
                               if self.conf.zadara_vol_thin else 'NO',
 
118
                               'crypt': 'YES'
 
119
                               if self.conf.zadara_vol_encrypt else 'NO'}),
127
120
            'delete_volume': ('DELETE',
128
121
                              '/api/volumes/%s.xml' % kwargs.get('vpsa_vol'),
129
122
                              {}),
130
123
 
 
124
            'expand_volume': ('POST',
 
125
                              '/api/volumes/%s/expand.xml'
 
126
                              % kwargs.get('vpsa_vol'),
 
127
                              {'capacity': kwargs.get('size')}),
 
128
 
 
129
            # Snapshot operations
 
130
            'create_snapshot': ('POST',
 
131
                                '/api/consistency_groups/%s/snapshots.xml'
 
132
                                % kwargs.get('cg_name'),
 
133
                                {'display_name': kwargs.get('snap_name')}),
 
134
            'delete_snapshot': ('DELETE',
 
135
                                '/api/snapshots/%s.xml'
 
136
                                % kwargs.get('snap_id'),
 
137
                                {}),
 
138
 
 
139
            'create_clone_from_snap': ('POST',
 
140
                                       '/api/consistency_groups/%s/clone.xml'
 
141
                                       % kwargs.get('cg_name'),
 
142
                                       {'name': kwargs.get('name'),
 
143
                                        'snapshot': kwargs.get('snap_id')}),
 
144
 
 
145
            'create_clone': ('POST',
 
146
                             '/api/consistency_groups/%s/clone.xml'
 
147
                             % kwargs.get('cg_name'),
 
148
                             {'name': kwargs.get('name')}),
 
149
 
131
150
            # Server operations
132
151
            'create_server': ('POST',
133
152
                              '/api/servers.xml',
150
169
            'list_volumes': ('GET',
151
170
                             '/api/volumes.xml',
152
171
                             {}),
 
172
            'list_pools': ('GET',
 
173
                           '/api/pools.xml',
 
174
                           {}),
153
175
            'list_controllers': ('GET',
154
176
                                 '/api/vcontrollers.xml',
155
177
                                 {}),
159
181
            'list_vol_attachments': ('GET',
160
182
                                     '/api/volumes/%s/servers.xml'
161
183
                                     % kwargs.get('vpsa_vol'),
162
 
                                     {}), }
 
184
                                     {}),
 
185
            'list_vol_snapshots': ('GET',
 
186
                                   '/api/consistency_groups/%s/snapshots.xml'
 
187
                                   % kwargs.get('cg_name'),
 
188
                                   {})}
163
189
 
164
190
        if cmd not in vpsa_commands.keys():
165
191
            raise exception.UnknownCmd(cmd=cmd)
218
244
        LOG.debug(_('Sending %(method)s to %(url)s. Body "%(body)s"'),
219
245
                  {'method': method, 'url': url, 'body': body})
220
246
 
221
 
        if self.use_ssl:
222
 
            connection = httplib.HTTPSConnection(self.host, self.port)
 
247
        if self.conf.zadara_vpsa_use_ssl:
 
248
            connection = httplib.HTTPSConnection(self.conf.zadara_vpsa_ip,
 
249
                                                 self.conf.zadara_vpsa_port)
223
250
        else:
224
 
            connection = httplib.HTTPConnection(self.host, self.port)
 
251
            connection = httplib.HTTPConnection(self.conf.zadara_vpsa_ip,
 
252
                                                self.conf.zadara_vpsa_port)
225
253
        connection.request(method, url, body)
226
254
        response = connection.getresponse()
227
255
 
244
272
class ZadaraVPSAISCSIDriver(driver.ISCSIDriver):
245
273
    """Zadara VPSA iSCSI volume driver."""
246
274
 
 
275
    VERSION = '13.07'
 
276
 
247
277
    def __init__(self, *args, **kwargs):
248
278
        super(ZadaraVPSAISCSIDriver, self).__init__(*args, **kwargs)
 
279
        self.configuration.append_config_values(zadara_opts)
249
280
 
250
281
    def do_setup(self, context):
251
282
        """
252
283
        Any initialization the volume driver does while starting.
253
284
        Establishes initial connection with VPSA and retrieves access_key.
254
285
        """
255
 
        self.vpsa = ZadaraVPSAConnection(CONF.zadara_vpsa_ip,
256
 
                                         CONF.zadara_vpsa_port,
257
 
                                         CONF.zadara_vpsa_use_ssl,
258
 
                                         CONF.zadara_user,
259
 
                                         CONF.zadara_password)
 
286
        self.vpsa = ZadaraVPSAConnection(self.configuration)
260
287
 
261
288
    def check_for_setup_error(self):
262
289
        """Returns an error (exception) if prerequisites aren't met."""
291
318
                    result_list.append(object)
292
319
        return result_list if result_list else None
293
320
 
 
321
    def _get_vpsa_volume_name_and_size(self, name):
 
322
        """Return VPSA's name & size for the volume."""
 
323
        xml_tree = self.vpsa.send_cmd('list_volumes')
 
324
        volume = self._xml_parse_helper(xml_tree, 'volumes',
 
325
                                        ('display-name', name))
 
326
        if volume is not None:
 
327
            return (volume.findtext('name'),
 
328
                    int(volume.findtext('virtual-capacity')))
 
329
 
 
330
        return (None, None)
 
331
 
294
332
    def _get_vpsa_volume_name(self, name):
295
333
        """Return VPSA's name for the volume."""
 
334
        (vol_name, size) = self._get_vpsa_volume_name_and_size(name)
 
335
        return vol_name
 
336
 
 
337
    def _get_volume_cg_name(self, name):
 
338
        """Return name of the consistency group for the volume."""
296
339
        xml_tree = self.vpsa.send_cmd('list_volumes')
297
340
        volume = self._xml_parse_helper(xml_tree, 'volumes',
298
341
                                        ('display-name', name))
299
342
        if volume is not None:
300
 
            return volume.findtext('name')
301
 
 
302
 
        return None
 
343
            return volume.findtext('cg-name')
 
344
 
 
345
        return None
 
346
 
 
347
    def _get_snap_id(self, cg_name, snap_name):
 
348
        """Return snapshot ID for particular volume."""
 
349
        xml_tree = self.vpsa.send_cmd('list_vol_snapshots',
 
350
                                      cg_name=cg_name)
 
351
        snap = self._xml_parse_helper(xml_tree, 'snapshots',
 
352
                                      ('display-name', snap_name))
 
353
        if snap is not None:
 
354
            return snap.findtext('name')
 
355
 
 
356
        return None
 
357
 
 
358
    def _get_pool_capacity(self, pool_name):
 
359
        """Return pool's total and available capacities."""
 
360
        xml_tree = self.vpsa.send_cmd('list_pools')
 
361
        pool = self._xml_parse_helper(xml_tree, 'pools',
 
362
                                      ('name', pool_name))
 
363
        if pool is not None:
 
364
            total = int(pool.findtext('capacity'))
 
365
            free = int(float(pool.findtext('available-capacity')))
 
366
            LOG.debug(_('Pool %(name)s: %(total)sGB total, %(free)sGB free'),
 
367
                      {'name': pool_name, 'total': total, 'free': free})
 
368
            return (total, free)
 
369
 
 
370
        return ('infinite', 'infinite')
303
371
 
304
372
    def _get_active_controller_details(self):
305
373
        """Return details of VPSA's active controller."""
334
402
        """Create volume."""
335
403
        self.vpsa.send_cmd(
336
404
            'create_volume',
337
 
            name=CONF.zadara_vol_name_template % volume['name'],
 
405
            name=self.configuration.zadara_vol_name_template % volume['name'],
338
406
            size=volume['size'])
339
407
 
340
408
    def delete_volume(self, volume):
344
412
        Return ok if doesn't exist. Auto detach from all servers.
345
413
        """
346
414
        # Get volume name
347
 
        name = CONF.zadara_vol_name_template % volume['name']
 
415
        name = self.configuration.zadara_vol_name_template % volume['name']
348
416
        vpsa_vol = self._get_vpsa_volume_name(name)
349
417
        if not vpsa_vol:
350
418
            msg = _('Volume %(name)s could not be found. '
351
419
                    'It might be already deleted') % {'name': name}
352
420
            LOG.warning(msg)
353
 
            if CONF.zadara_vpsa_allow_nonexistent_delete:
 
421
            if self.configuration.zadara_vpsa_allow_nonexistent_delete:
354
422
                return
355
423
            else:
356
424
                raise exception.VolumeNotFound(volume_id=name)
361
429
        servers = self._xml_parse_helper(xml_tree, 'servers',
362
430
                                         ('iqn', None), first=False)
363
431
        if servers:
364
 
            if not CONF.zadara_vpsa_auto_detach_on_delete:
 
432
            if not self.configuration.zadara_vpsa_auto_detach_on_delete:
365
433
                raise exception.VolumeAttached(volume_id=name)
366
434
 
367
435
            for server in servers:
374
442
        # Delete volume
375
443
        self.vpsa.send_cmd('delete_volume', vpsa_vol=vpsa_vol)
376
444
 
 
445
    def create_snapshot(self, snapshot):
 
446
        """Creates a snapshot."""
 
447
 
 
448
        LOG.debug(_('Create snapshot: %s'), snapshot['name'])
 
449
 
 
450
        # Retrieve the CG name for the base volume
 
451
        volume_name = self.configuration.zadara_vol_name_template\
 
452
            % snapshot['volume_name']
 
453
        cg_name = self._get_volume_cg_name(volume_name)
 
454
        if not cg_name:
 
455
            msg = _('Volume %(name)s not found') % {'name': volume_name}
 
456
            LOG.error(msg)
 
457
            raise exception.VolumeNotFound(volume_id=volume_name)
 
458
 
 
459
        self.vpsa.send_cmd('create_snapshot',
 
460
                           cg_name=cg_name,
 
461
                           snap_name=snapshot['name'])
 
462
 
 
463
    def delete_snapshot(self, snapshot):
 
464
        """Deletes a snapshot."""
 
465
 
 
466
        LOG.debug(_('Delete snapshot: %s'), snapshot['name'])
 
467
 
 
468
        # Retrieve the CG name for the base volume
 
469
        volume_name = self.configuration.zadara_vol_name_template\
 
470
            % snapshot['volume_name']
 
471
        cg_name = self._get_volume_cg_name(volume_name)
 
472
        if not cg_name:
 
473
            # If the volume isn't present, then don't attempt to delete
 
474
            LOG.warning(_("snapshot: original volume %s not found, "
 
475
                        "skipping delete operation")
 
476
                        % snapshot['volume_name'])
 
477
            return True
 
478
 
 
479
        snap_id = self._get_snap_id(cg_name, snapshot['name'])
 
480
        if not snap_id:
 
481
            # If the snapshot isn't present, then don't attempt to delete
 
482
            LOG.warning(_("snapshot: snapshot %s not found, "
 
483
                        "skipping delete operation")
 
484
                        % snapshot['name'])
 
485
            return True
 
486
 
 
487
        self.vpsa.send_cmd('delete_snapshot',
 
488
                           snap_id=snap_id)
 
489
 
 
490
    def create_volume_from_snapshot(self, volume, snapshot):
 
491
        """Creates a volume from a snapshot."""
 
492
 
 
493
        LOG.debug(_('Creating volume from snapshot: %s') % snapshot['name'])
 
494
 
 
495
        # Retrieve the CG name for the base volume
 
496
        volume_name = self.configuration.zadara_vol_name_template\
 
497
            % snapshot['volume_name']
 
498
        cg_name = self._get_volume_cg_name(volume_name)
 
499
        if not cg_name:
 
500
            msg = _('Volume %(name)s not found') % {'name': volume_name}
 
501
            LOG.error(msg)
 
502
            raise exception.VolumeNotFound(volume_id=volume_name)
 
503
 
 
504
        snap_id = self._get_snap_id(cg_name, snapshot['name'])
 
505
        if not snap_id:
 
506
            msg = _('Snapshot %(name)s not found') % {'name': snapshot['name']}
 
507
            LOG.error(msg)
 
508
            raise exception.VolumeNotFound(volume_id=snapshot['name'])
 
509
 
 
510
        self.vpsa.send_cmd('create_clone_from_snap',
 
511
                           cg_name=cg_name,
 
512
                           name=self.configuration.zadara_vol_name_template
 
513
                           % volume['name'],
 
514
                           snap_id=snap_id)
 
515
 
 
516
    def create_cloned_volume(self, volume, src_vref):
 
517
        """Creates a clone of the specified volume."""
 
518
 
 
519
        LOG.debug(_('Creating clone of volume: %s') % src_vref['name'])
 
520
 
 
521
        # Retrieve the CG name for the base volume
 
522
        volume_name = self.configuration.zadara_vol_name_template\
 
523
            % src_vref['name']
 
524
        cg_name = self._get_volume_cg_name(volume_name)
 
525
        if not cg_name:
 
526
            msg = _('Volume %(name)s not found') % {'name': volume_name}
 
527
            LOG.error(msg)
 
528
            raise exception.VolumeNotFound(volume_id=volume_name)
 
529
 
 
530
        self.vpsa.send_cmd('create_clone',
 
531
                           cg_name=cg_name,
 
532
                           name=self.configuration.zadara_vol_name_template
 
533
                           % volume['name'])
 
534
 
 
535
    def extend_volume(self, volume, new_size):
 
536
        """Extend an existing volume."""
 
537
        # Get volume name
 
538
        name = self.configuration.zadara_vol_name_template % volume['name']
 
539
        (vpsa_vol, size) = self._get_vpsa_volume_name_and_size(name)
 
540
        if not vpsa_vol:
 
541
            msg = _('Volume %(name)s could not be found. '
 
542
                    'It might be already deleted') % {'name': name}
 
543
            LOG.error(msg)
 
544
            raise exception.VolumeNotFound(volume_id=name)
 
545
 
 
546
        if new_size < size:
 
547
            raise exception.InvalidInput(
 
548
                reason='%s < current size %s' % (new_size, size))
 
549
 
 
550
        expand_size = new_size - size
 
551
        self.vpsa.send_cmd('expand_volume',
 
552
                           vpsa_vol=vpsa_vol,
 
553
                           size=expand_size)
 
554
 
377
555
    def create_export(self, context, volume):
378
556
        """Irrelevant for VPSA volumes. Export created during attachment."""
379
557
        pass
404
582
            raise exception.ZadaraServerCreateFailure(name=initiator_name)
405
583
 
406
584
        # Get volume name
407
 
        name = CONF.zadara_vol_name_template % volume['name']
 
585
        name = self.configuration.zadara_vol_name_template % volume['name']
408
586
        vpsa_vol = self._get_vpsa_volume_name(name)
409
587
        if not vpsa_vol:
410
588
            raise exception.VolumeNotFound(volume_id=name)
460
638
            raise exception.ZadaraServerNotFound(name=initiator_name)
461
639
 
462
640
        # Get volume name
463
 
        name = CONF.zadara_vol_name_template % volume['name']
 
641
        name = self.configuration.zadara_vol_name_template % volume['name']
464
642
        vpsa_vol = self._get_vpsa_volume_name(name)
465
643
        if not vpsa_vol:
466
644
            raise exception.VolumeNotFound(volume_id=name)
469
647
        self.vpsa.send_cmd('detach_volume',
470
648
                           vpsa_srv=vpsa_srv,
471
649
                           vpsa_vol=vpsa_vol)
 
650
 
 
651
    def get_volume_stats(self, refresh=False):
 
652
        """Get volume stats.
 
653
        If 'refresh' is True, run update the stats first.
 
654
        """
 
655
        if refresh:
 
656
            self._update_volume_stats()
 
657
 
 
658
        return self._stats
 
659
 
 
660
    def _update_volume_stats(self):
 
661
        """Retrieve stats info from volume group."""
 
662
 
 
663
        LOG.debug(_("Updating volume stats"))
 
664
        data = {}
 
665
 
 
666
        backend_name = self.configuration.safe_get('volume_backend_name')
 
667
        data["volume_backend_name"] = backend_name or self.__class__.__name__
 
668
        data["vendor_name"] = 'Zadara Storage'
 
669
        data["driver_version"] = self.VERSION
 
670
        data["storage_protocol"] = 'iSCSI'
 
671
        data['reserved_percentage'] = self.configuration.reserved_percentage
 
672
        data['QoS_support'] = False
 
673
 
 
674
        (total, free) = self._get_pool_capacity(self.configuration.
 
675
                                                zadara_vpsa_poolname)
 
676
        data['total_capacity_gb'] = total
 
677
        data['free_capacity_gb'] = free
 
678
 
 
679
        self._stats = data