~ubuntu-branches/ubuntu/saucy/nova/saucy-proposed

« back to all changes in this revision

Viewing changes to nova/volume/san.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short
  • Date: 2012-05-24 13:12:53 UTC
  • mfrom: (1.1.55)
  • Revision ID: package-import@ubuntu.com-20120524131253-ommql08fg1en06ut
Tags: 2012.2~f1-0ubuntu1
* New upstream release.
* Prepare for quantal:
  - Dropped debian/patches/upstream/0006-Use-project_id-in-ec2.cloud._format_image.patch
  - Dropped debian/patches/upstream/0005-Populate-image-properties-with-project_id-again.patch
  - Dropped debian/patches/upstream/0004-Fixed-bug-962840-added-a-test-case.patch
  - Dropped debian/patches/upstream/0003-Allow-unprivileged-RADOS-users-to-access-rbd-volumes.patch
  - Dropped debian/patches/upstream/0002-Stop-libvirt-test-from-deleting-instances-dir.patch
  - Dropped debian/patches/upstream/0001-fix-bug-where-nova-ignores-glance-host-in-imageref.patch 
  - Dropped debian/patches/0001-fix-useexisting-deprecation-warnings.patch
* debian/control: Add python-keystone as a dependency. (LP: #907197)
* debian/patches/kombu_tests_timeout.patch: Refreshed.
* debian/nova.conf, debian/nova-common.postinst: Convert to new ini
  file configuration
* debian/patches/nova-manage_flagfile_location.patch: Refreshed

Show diffs side-by-side

added added

removed removed

Lines of Context:
30
30
import socket
31
31
import string
32
32
import uuid
33
 
from xml.etree import ElementTree
 
33
 
 
34
from lxml import etree
34
35
 
35
36
from nova import exception
36
37
from nova import flags
44
45
 
45
46
san_opts = [
46
47
    cfg.BoolOpt('san_thin_provision',
47
 
                default='true',
 
48
                default=True,
48
49
                help='Use thin provisioning for SAN volumes?'),
49
50
    cfg.StrOpt('san_ip',
50
51
               default='',
65
66
               default=22,
66
67
               help='SSH port to use with SAN'),
67
68
    cfg.BoolOpt('san_is_local',
68
 
                default='false',
 
69
                default=False,
69
70
                help='Execute commands locally instead of over SSH; '
70
71
                     'use if the volume service is running on the SAN device'),
71
72
    cfg.StrOpt('san_zfs_volume_base',
110
111
                        username=FLAGS.san_login,
111
112
                        pkey=privatekey)
112
113
        else:
113
 
            raise exception.Error(_("Specify san_password or san_private_key"))
 
114
            msg = _("Specify san_password or san_private_key")
 
115
            raise exception.NovaException(msg)
114
116
        return ssh
115
117
 
116
118
    def _execute(self, *cmd, **kwargs):
145
147
        pass
146
148
 
147
149
    def check_for_setup_error(self):
148
 
        """Returns an error if prerequisites aren't met"""
 
150
        """Returns an error if prerequisites aren't met."""
149
151
        if not self.run_local:
150
152
            if not (FLAGS.san_password or FLAGS.san_private_key):
151
 
                raise exception.Error(_('Specify san_password or '
 
153
                raise exception.NovaException(_('Specify san_password or '
152
154
                                        'san_private_key'))
153
155
 
154
156
        # The san_ip must always be set, because we use it for the target
155
157
        if not (FLAGS.san_ip):
156
 
            raise exception.Error(_("san_ip must be set"))
 
158
            raise exception.NovaException(_("san_ip must be set"))
157
159
 
158
160
 
159
161
def _collect_lines(data):
224
226
        if "View Entry:" in out:
225
227
            return True
226
228
 
227
 
        raise exception.Error("Cannot parse list-view output: %s" % (out))
 
229
        msg = _("Cannot parse list-view output: %s") % (out)
 
230
        raise exception.NovaException()
228
231
 
229
232
    def _get_target_groups(self):
230
233
        """Gets list of target groups from host."""
451
454
 
452
455
        LOG.debug(_("CLIQ command returned %s"), out)
453
456
 
454
 
        result_xml = ElementTree.fromstring(out)
 
457
        result_xml = etree.fromstring(out)
455
458
        if check_cliq_result:
456
459
            response_node = result_xml.find("response")
457
460
            if response_node is None:
458
461
                msg = (_("Malformed response to CLIQ command "
459
462
                         "%(verb)s %(cliq_args)s. Result=%(out)s") %
460
463
                       locals())
461
 
                raise exception.Error(msg)
 
464
                raise exception.NovaException(msg)
462
465
 
463
466
            result_code = response_node.attrib.get("result")
464
467
 
466
469
                msg = (_("Error running CLIQ command %(verb)s %(cliq_args)s. "
467
470
                         " Result=%(out)s") %
468
471
                       locals())
469
 
                raise exception.Error(msg)
 
472
                raise exception.NovaException(msg)
470
473
 
471
474
        return result_xml
472
475
 
492
495
        if len(vips) == 1:
493
496
            return vips[0]
494
497
 
495
 
        _xml = ElementTree.tostring(cluster_xml)
 
498
        _xml = etree.tostring(cluster_xml)
496
499
        msg = (_("Unexpected number of virtual ips for cluster "
497
500
                 " %(cluster_name)s. Result=%(_xml)s") %
498
501
               locals())
499
 
        raise exception.Error(msg)
 
502
        raise exception.NovaException(msg)
500
503
 
501
504
    def _cliq_get_volume_info(self, volume_name):
502
505
        """Gets the volume info, including IQN"""
581
584
 
582
585
        return model_update
583
586
 
 
587
    def create_volume_from_snapshot(self, volume, snapshot):
 
588
        """Creates a volume from a snapshot."""
 
589
        raise NotImplementedError()
 
590
 
 
591
    def create_snapshot(self, snapshot):
 
592
        """Creates a snapshot."""
 
593
        raise NotImplementedError()
 
594
 
584
595
    def delete_volume(self, volume):
585
596
        """Deletes a volume."""
586
597
        cliq_args = {}
591
602
 
592
603
    def local_path(self, volume):
593
604
        # TODO(justinsb): Is this needed here?
594
 
        raise exception.Error(_("local_path not supported"))
595
 
 
596
 
    def ensure_export(self, context, volume):
597
 
        """Synchronously recreates an export for a logical volume."""
598
 
        return self._do_export(context, volume, force_create=False)
599
 
 
600
 
    def create_export(self, context, volume):
601
 
        return self._do_export(context, volume, force_create=True)
602
 
 
603
 
    def _do_export(self, context, volume, force_create):
604
 
        """Supports ensure_export and create_export"""
605
 
        volume_info = self._cliq_get_volume_info(volume['name'])
606
 
 
607
 
        is_shared = 'permission.authGroup' in volume_info
608
 
 
609
 
        model_update = {}
610
 
 
611
 
        should_export = False
612
 
 
613
 
        if force_create or not is_shared:
614
 
            should_export = True
615
 
            # Check that we have a project_id
616
 
            project_id = volume['project_id']
617
 
            if not project_id:
618
 
                project_id = context.project_id
619
 
 
620
 
            if project_id:
621
 
                #TODO(justinsb): Use a real per-project password here
622
 
                chap_username = 'proj_' + project_id
623
 
                # HP/Lefthand requires that the password be >= 12 characters
624
 
                chap_password = 'project_secret_' + project_id
625
 
            else:
626
 
                msg = (_("Could not determine project for volume %s, "
627
 
                         "can't export") %
628
 
                         (volume['name']))
629
 
                if force_create:
630
 
                    raise exception.Error(msg)
631
 
                else:
632
 
                    LOG.warn(msg)
633
 
                    should_export = False
634
 
 
635
 
        if should_export:
636
 
            cliq_args = {}
637
 
            cliq_args['volumeName'] = volume['name']
638
 
            cliq_args['chapName'] = chap_username
639
 
            cliq_args['targetSecret'] = chap_password
640
 
 
641
 
            self._cliq_run_xml("assignVolumeChap", cliq_args)
642
 
 
643
 
            model_update['provider_auth'] = ("CHAP %s %s" %
644
 
                                             (chap_username, chap_password))
645
 
 
646
 
        return model_update
647
 
 
648
 
    def remove_export(self, context, volume):
649
 
        """Removes an export for a logical volume."""
650
 
        cliq_args = {}
651
 
        cliq_args['volumeName'] = volume['name']
652
 
 
653
 
        self._cliq_run_xml("unassignVolume", cliq_args)
 
605
        raise exception.NovaException(_("local_path not supported"))
 
606
 
 
607
    def initialize_connection(self, volume, connector):
 
608
        """Assigns the volume to a server.
 
609
 
 
610
        Assign any created volume to a compute node/host so that it can be
 
611
        used from that host. HP VSA requires a volume to be assigned
 
612
        to a server.
 
613
 
 
614
        This driver returns a driver_volume_type of 'iscsi'.
 
615
        The format of the driver data is defined in _get_iscsi_properties.
 
616
        Example return value::
 
617
 
 
618
            {
 
619
                'driver_volume_type': 'iscsi'
 
620
                'data': {
 
621
                    'target_discovered': True,
 
622
                    'target_iqn': 'iqn.2010-10.org.openstack:volume-00000001',
 
623
                    'target_portal': '127.0.0.0.1:3260',
 
624
                    'volume_id': 1,
 
625
                }
 
626
            }
 
627
 
 
628
        """
 
629
        cliq_args = {}
 
630
        cliq_args['volumeName'] = volume['name']
 
631
        cliq_args['serverName'] = connector['host']
 
632
        self._cliq_run_xml("assignVolumeToServer", cliq_args)
 
633
 
 
634
        iscsi_properties = self._get_iscsi_properties(volume)
 
635
        return {
 
636
            'driver_volume_type': 'iscsi',
 
637
            'data': iscsi_properties
 
638
        }
 
639
 
 
640
    def terminate_connection(self, volume, connector):
 
641
        """Unassign the volume from the host."""
 
642
        cliq_args = {}
 
643
        cliq_args['volumeName'] = volume['name']
 
644
        cliq_args['serverName'] = connector['host']
 
645
        self._cliq_run_xml("unassignVolumeToServer", cliq_args)
654
646
 
655
647
 
656
648
class SolidFireSanISCSIDriver(SanISCSIDriver):
693
685
                                           cluster_password))[:-1]
694
686
            header['Authorization'] = 'Basic %s' % auth_key
695
687
 
696
 
        LOG.debug(_("Payload for SolidFire API call: %s") % payload)
 
688
        LOG.debug(_("Payload for SolidFire API call: %s"), payload)
697
689
        connection = httplib.HTTPSConnection(host, port)
698
690
        connection.request('POST', '/json-rpc/1.0', payload, header)
699
691
        response = connection.getresponse()
715
707
 
716
708
            connection.close()
717
709
 
718
 
        LOG.debug(_("Results of SolidFire API call: %s") % data)
 
710
        LOG.debug(_("Results of SolidFire API call: %s"), data)
719
711
        return data
720
712
 
721
713
    def _get_volumes_by_sfaccount(self, account_id):
729
721
        params = {'username': sf_account_name}
730
722
        data = self._issue_api_request('GetAccountByName', params)
731
723
        if 'result' in data and 'account' in data['result']:
732
 
            LOG.debug(_('Found solidfire account: %s') % sf_account_name)
 
724
            LOG.debug(_('Found solidfire account: %s'), sf_account_name)
733
725
            sfaccount = data['result']['account']
734
726
        return sfaccount
735
727
 
743
735
        sf_account_name = socket.gethostname() + '-' + nova_project_id
744
736
        sfaccount = self._get_sfaccount_by_name(sf_account_name)
745
737
        if sfaccount is None:
746
 
            LOG.debug(_('solidfire account: %s does not exist, create it...')
747
 
                      % sf_account_name)
 
738
            LOG.debug(_('solidfire account: %s does not exist, create it...'),
 
739
                      sf_account_name)
748
740
            chap_secret = self._generate_random_string(12)
749
741
            params = {'username': sf_account_name,
750
742
                      'initiatorSecret': chap_secret,
853
845
        volumeID is what's guaranteed unique.
854
846
 
855
847
        What we'll do here is check volumes based on account. this
856
 
        should work because nova will increment it's volume_id
 
848
        should work because nova will increment its volume_id
857
849
        so we should always get the correct volume. This assumes
858
850
        that nova does not assign duplicate ID's.
859
851
        """
877
869
                volid = v['volumeID']
878
870
 
879
871
        if found_count != 1:
880
 
            LOG.debug(_("Deleting volumeID: %s ") % volid)
 
872
            LOG.debug(_("Deleting volumeID: %s"), volid)
881
873
            raise exception.DuplicateSfVolumeNames(vol_name=volume['name'])
882
874
 
883
875
        params = {'volumeID': volid}