~ubuntu-branches/ubuntu/raring/nova/raring-proposed

« back to all changes in this revision

Viewing changes to nova/volume/netapp_nfs.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short, Adam Gandelman, Chuck Short
  • Date: 2012-11-23 09:04:58 UTC
  • mfrom: (1.1.66)
  • Revision ID: package-import@ubuntu.com-20121123090458-91565o7aev1i1h71
Tags: 2013.1~g1-0ubuntu1
[ Adam Gandelman ]
* debian/control: Ensure novaclient is upgraded with nova,
  require python-keystoneclient >= 1:2.9.0. (LP: #1073289)
* debian/patches/{ubuntu/*, rbd-security.patch}: Dropped, applied
  upstream.
* debian/control: Add python-testtools to Build-Depends.

[ Chuck Short ]
* New upstream version.
* Refreshed debian/patches/avoid_setuptools_git_dependency.patch.
* debian/rules: FTBFS if missing binaries.
* debian/nova-scheudler.install: Add missing rabbit-queues and
  nova-rpc-zmq-receiver.
* Remove nova-volume since it doesnt exist anymore, transition to cinder-*.
* debian/rules: install apport hook in the right place.
* debian/patches/ubuntu-show-tests.patch: Display test failures.
* debian/control: Add depends on genisoimage
* debian/control: Suggest guestmount.
* debian/control: Suggest websockify. (LP: #1076442)
* debian/nova.conf: Disable nova-volume service.
* debian/control: Depend on xen-system-* rather than the hypervisor.
* debian/control, debian/mans/nova-conductor.8, debian/nova-conductor.init,
  debian/nova-conductor.install, debian/nova-conductor.logrotate
  debian/nova-conductor.manpages, debian/nova-conductor.postrm
  debian/nova-conductor.upstart.in: Add nova-conductor service.
* debian/control: Add python-fixtures as a build deps.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
 
 
3
 
# Copyright (c) 2012 NetApp, Inc.
4
 
# All Rights Reserved.
5
 
#
6
 
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
7
 
#    not use this file except in compliance with the License. You may obtain
8
 
#    a copy of the License at
9
 
#
10
 
#         http://www.apache.org/licenses/LICENSE-2.0
11
 
#
12
 
#    Unless required by applicable law or agreed to in writing, software
13
 
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
 
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15
 
#    License for the specific language governing permissions and limitations
16
 
#    under the License.
17
 
"""
18
 
Volume driver for NetApp NFS storage.
19
 
"""
20
 
 
21
 
import os
22
 
import suds
23
 
import time
24
 
 
25
 
from nova import exception
26
 
from nova import flags
27
 
from nova.openstack.common import cfg
28
 
from nova.openstack.common import log as logging
29
 
from nova.volume.netapp import netapp_opts
30
 
from nova.volume import nfs
31
 
 
32
 
from suds.sax import text
33
 
 
34
 
LOG = logging.getLogger(__name__)
35
 
 
36
 
netapp_nfs_opts = [
37
 
    cfg.IntOpt('synchronous_snapshot_create',
38
 
               default=0,
39
 
               help='Does snapshot creation call returns immediately')
40
 
    ]
41
 
 
42
 
FLAGS = flags.FLAGS
43
 
FLAGS.register_opts(netapp_opts)
44
 
FLAGS.register_opts(netapp_nfs_opts)
45
 
 
46
 
 
47
 
class NetAppNFSDriver(nfs.NfsDriver):
48
 
    """Executes commands relating to Volumes."""
49
 
    def __init__(self, *args, **kwargs):
50
 
        # NOTE(vish): db is set by Manager
51
 
        self._execute = None
52
 
        self._context = None
53
 
        super(NetAppNFSDriver, self).__init__(*args, **kwargs)
54
 
 
55
 
    def set_execute(self, execute):
56
 
        self._execute = execute
57
 
 
58
 
    def do_setup(self, context):
59
 
        self._context = context
60
 
        self.check_for_setup_error()
61
 
        self._client = NetAppNFSDriver._get_client()
62
 
 
63
 
    def check_for_setup_error(self):
64
 
        """Returns an error if prerequisites aren't met"""
65
 
        NetAppNFSDriver._check_dfm_flags()
66
 
        super(NetAppNFSDriver, self).check_for_setup_error()
67
 
 
68
 
    def create_volume_from_snapshot(self, volume, snapshot):
69
 
        """Creates a volume from a snapshot."""
70
 
        vol_size = volume.size
71
 
        snap_size = snapshot.volume_size
72
 
 
73
 
        if vol_size != snap_size:
74
 
            msg = _('Cannot create volume of size %(vol_size)s from '
75
 
                'snapshot of size %(snap_size)s')
76
 
            raise exception.NovaException(msg % locals())
77
 
 
78
 
        self._clone_volume(snapshot.name, volume.name, snapshot.volume_id)
79
 
        share = self._get_volume_location(snapshot.volume_id)
80
 
 
81
 
        return {'provider_location': share}
82
 
 
83
 
    def create_snapshot(self, snapshot):
84
 
        """Creates a snapshot."""
85
 
        self._clone_volume(snapshot['volume_name'],
86
 
                           snapshot['name'],
87
 
                           snapshot['volume_id'])
88
 
 
89
 
    def delete_snapshot(self, snapshot):
90
 
        """Deletes a snapshot."""
91
 
        nfs_mount = self._get_provider_location(snapshot.volume_id)
92
 
 
93
 
        if self._volume_not_present(nfs_mount, snapshot.name):
94
 
            return True
95
 
 
96
 
        self._execute('rm', self._get_volume_path(nfs_mount, snapshot.name),
97
 
                      run_as_root=True)
98
 
 
99
 
    @staticmethod
100
 
    def _check_dfm_flags():
101
 
        """Raises error if any required configuration flag for OnCommand proxy
102
 
        is missing."""
103
 
        required_flags = ['netapp_wsdl_url',
104
 
                          'netapp_login',
105
 
                          'netapp_password',
106
 
                          'netapp_server_hostname',
107
 
                          'netapp_server_port']
108
 
        for flag in required_flags:
109
 
            if not getattr(FLAGS, flag, None):
110
 
                raise exception.NovaException(_('%s is not set') % flag)
111
 
 
112
 
    @staticmethod
113
 
    def _get_client():
114
 
        """Creates SOAP _client for ONTAP-7 DataFabric Service."""
115
 
        client = suds.client.Client(FLAGS.netapp_wsdl_url,
116
 
                                    username=FLAGS.netapp_login,
117
 
                                    password=FLAGS.netapp_password)
118
 
        soap_url = 'http://%s:%s/apis/soap/v1' % (
119
 
                                          FLAGS.netapp_server_hostname,
120
 
                                          FLAGS.netapp_server_port)
121
 
        client.set_options(location=soap_url)
122
 
 
123
 
        return client
124
 
 
125
 
    def _get_volume_location(self, volume_id):
126
 
        """Returns NFS mount address as <nfs_ip_address>:<nfs_mount_dir>"""
127
 
        nfs_server_ip = self._get_host_ip(volume_id)
128
 
        export_path = self._get_export_path(volume_id)
129
 
        return (nfs_server_ip + ':' + export_path)
130
 
 
131
 
    def _clone_volume(self, volume_name, clone_name, volume_id):
132
 
        """Clones mounted volume with OnCommand proxy API"""
133
 
        host_id = self._get_host_id(volume_id)
134
 
        export_path = self._get_full_export_path(volume_id, host_id)
135
 
 
136
 
        request = self._client.factory.create('Request')
137
 
        request.Name = 'clone-start'
138
 
 
139
 
        clone_start_args = ('<source-path>%s/%s</source-path>'
140
 
                            '<destination-path>%s/%s</destination-path>')
141
 
 
142
 
        request.Args = text.Raw(clone_start_args % (export_path,
143
 
                                                    volume_name,
144
 
                                                    export_path,
145
 
                                                    clone_name))
146
 
 
147
 
        resp = self._client.service.ApiProxy(Target=host_id,
148
 
                                            Request=request)
149
 
 
150
 
        if resp.Status == 'passed' and FLAGS.synchronous_snapshot_create:
151
 
            clone_id = resp.Results['clone-id'][0]
152
 
            clone_id_info = clone_id['clone-id-info'][0]
153
 
            clone_operation_id = int(clone_id_info['clone-op-id'][0])
154
 
 
155
 
            self._wait_for_clone_finished(clone_operation_id, host_id)
156
 
        elif resp.Status == 'failed':
157
 
            raise exception.NovaException(resp.Reason)
158
 
 
159
 
    def _wait_for_clone_finished(self, clone_operation_id, host_id):
160
 
        """
161
 
        Polls ONTAP7 for clone status. Returns once clone is finished.
162
 
        :param clone_operation_id: Identifier of ONTAP clone operation
163
 
        """
164
 
        clone_list_options = ('<clone-id>'
165
 
                                '<clone-id-info>'
166
 
                                  '<clone-op-id>%d</clone-op-id>'
167
 
                                  '<volume-uuid></volume-uuid>'
168
 
                                '</clone-id>'
169
 
                              '</clone-id-info>')
170
 
 
171
 
        request = self._client.factory.create('Request')
172
 
        request.Name = 'clone-list-status'
173
 
        request.Args = text.Raw(clone_list_options % clone_operation_id)
174
 
 
175
 
        resp = self._client.service.ApiProxy(Target=host_id, Request=request)
176
 
 
177
 
        while resp.Status != 'passed':
178
 
            time.sleep(1)
179
 
            resp = self._client.service.ApiProxy(Target=host_id,
180
 
                                                Request=request)
181
 
 
182
 
    def _get_provider_location(self, volume_id):
183
 
        """
184
 
        Returns provider location for given volume
185
 
        :param volume_id:
186
 
        """
187
 
        volume = self.db.volume_get(self._context, volume_id)
188
 
        return volume.provider_location
189
 
 
190
 
    def _get_host_ip(self, volume_id):
191
 
        """Returns IP address for the given volume"""
192
 
        return self._get_provider_location(volume_id).split(':')[0]
193
 
 
194
 
    def _get_export_path(self, volume_id):
195
 
        """Returns NFS export path for the given volume"""
196
 
        return self._get_provider_location(volume_id).split(':')[1]
197
 
 
198
 
    def _get_host_id(self, volume_id):
199
 
        """Returns ID of the ONTAP-7 host"""
200
 
        host_ip = self._get_host_ip(volume_id)
201
 
        server = self._client.service
202
 
 
203
 
        resp = server.HostListInfoIterStart(ObjectNameOrId=host_ip)
204
 
        tag = resp.Tag
205
 
 
206
 
        try:
207
 
            res = server.HostListInfoIterNext(Tag=tag, Maximum=1)
208
 
            if hasattr(res, 'Hosts') and res.Hosts.HostInfo:
209
 
                return res.Hosts.HostInfo[0].HostId
210
 
        finally:
211
 
            server.HostListInfoIterEnd(Tag=tag)
212
 
 
213
 
    def _get_full_export_path(self, volume_id, host_id):
214
 
        """Returns full path to the NFS share, e.g. /vol/vol0/home"""
215
 
        export_path = self._get_export_path(volume_id)
216
 
        command_args = '<pathname>%s</pathname>'
217
 
 
218
 
        request = self._client.factory.create('Request')
219
 
        request.Name = 'nfs-exportfs-storage-path'
220
 
        request.Args = text.Raw(command_args % export_path)
221
 
 
222
 
        resp = self._client.service.ApiProxy(Target=host_id,
223
 
                                            Request=request)
224
 
 
225
 
        if resp.Status == 'passed':
226
 
            return resp.Results['actual-pathname'][0]
227
 
        elif resp.Status == 'failed':
228
 
            raise exception.NovaException(resp.Reason)
229
 
 
230
 
    def _volume_not_present(self, nfs_mount, volume_name):
231
 
        """
232
 
        Check if volume exists
233
 
        """
234
 
        try:
235
 
            self._try_execute('ls', self._get_volume_path(nfs_mount,
236
 
                                                          volume_name))
237
 
        except exception.ProcessExecutionError:
238
 
            # If the volume isn't present
239
 
            return True
240
 
        return False
241
 
 
242
 
    def _try_execute(self, *command, **kwargs):
243
 
        # NOTE(vish): Volume commands can partially fail due to timing, but
244
 
        #             running them a second time on failure will usually
245
 
        #             recover nicely.
246
 
        tries = 0
247
 
        while True:
248
 
            try:
249
 
                self._execute(*command, **kwargs)
250
 
                return True
251
 
            except exception.ProcessExecutionError:
252
 
                tries = tries + 1
253
 
                if tries >= FLAGS.num_shell_tries:
254
 
                    raise
255
 
                LOG.exception(_("Recovering from a failed execute.  "
256
 
                                "Try number %s"), tries)
257
 
                time.sleep(tries ** 2)
258
 
 
259
 
    def _get_volume_path(self, nfs_share, volume_name):
260
 
        """Get volume path (local fs path) for given volume name on given nfs
261
 
        share
262
 
        @param nfs_share string, example 172.18.194.100:/var/nfs
263
 
        @param volume_name string,
264
 
            example volume-91ee65ec-c473-4391-8c09-162b00c68a8c
265
 
        """
266
 
        return os.path.join(self._get_mount_point_for_share(nfs_share),
267
 
                            volume_name)