File: cinder_utils.py

package info (click to toggle)
python-glance-store 5.2.0-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,908 kB
  • sloc: python: 18,046; sh: 41; makefile: 34
file content (228 lines) | stat: -rw-r--r-- 9,744 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# Copyright 2021 RedHat Inc.
# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

import logging

from cinderclient.apiclient import exceptions as apiclient_exception
from cinderclient import exceptions as cinder_exception
from keystoneauth1 import exceptions as keystone_exc
from oslo_utils import excutils
import retrying

from glance_store import exceptions
from glance_store.i18n import _LE

LOG = logging.getLogger(__name__)


def handle_exceptions(method):
    """Transforms the exception for the volume but keeps its traceback intact.
    """
    def wrapper(self, ctx, volume_id, *args, **kwargs):
        try:
            res = method(self, ctx, volume_id, *args, **kwargs)
        except (keystone_exc.NotFound,
                cinder_exception.NotFound,
                cinder_exception.OverLimit) as e:
            raise exceptions.BackendException(str(e))
        return res
    return wrapper


def _retry_on_internal_server_error(e):
    if isinstance(e, apiclient_exception.InternalServerError):
        return True
    return False


def _retry_on_bad_request(e):
    if isinstance(e, cinder_exception.BadRequest):
        return True
    return False


class API(object):
    """API for interacting with the cinder."""

    @handle_exceptions
    def create(self, client, size, name,
               volume_type=None, metadata=None):

        kwargs = dict(volume_type=volume_type,
                      metadata=metadata,
                      name=name)

        volume = client.volumes.create(size, **kwargs)
        return volume

    def delete(self, client, volume_id):
        client.volumes.delete(volume_id)

    @retrying.retry(stop_max_attempt_number=5,
                    retry_on_exception=_retry_on_bad_request,
                    wait_exponential_multiplier=1000,
                    wait_exponential_max=10000)
    @handle_exceptions
    def attachment_create(self, client, volume_id, connector=None,
                          mountpoint=None, mode=None):
        """Create a volume attachment. This requires microversion >= 3.54.

        The attachment_create call was introduced in microversion 3.27. We
        need 3.54 as minimum here as we need attachment_complete to finish the
        attaching process and it which was introduced in version 3.44 and
        we also pass the attach mode which was introduced in version 3.54.

        :param client: cinderclient object
        :param volume_id: UUID of the volume on which to create the attachment.
        :param connector: host connector dict; if None, the attachment will
            be 'reserved' but not yet attached.
        :param mountpoint: Optional mount device name for the attachment,
            e.g. "/dev/vdb". This is only used if a connector is provided.
        :param mode: The mode in which the attachment is made i.e.
            read only(ro) or read/write(rw)
        :returns: a dict created from the
            cinderclient.v3.attachments.VolumeAttachment object with a backward
            compatible connection_info dict
        """
        if connector and mountpoint and 'mountpoint' not in connector:
            connector['mountpoint'] = mountpoint

        try:
            attachment_ref = client.attachments.create(
                volume_id, connector, mode=mode)
            return attachment_ref
        except cinder_exception.ClientException as ex:
            with excutils.save_and_reraise_exception():
                # While handling simultaneous requests, the volume can be
                # in different states and we retry on attachment_create
                # until the volume reaches a valid state for attachment.
                # Hence, it is better to not log 400 cases as no action
                # from users is needed in this case
                if getattr(ex, 'code', None) != 400:
                    LOG.error(_LE('Create attachment failed for volume '
                                  '%(volume_id)s. Error: %(msg)s '
                                  'Code: %(code)s'),
                              {'volume_id': volume_id,
                               'msg': str(ex),
                               'code': getattr(ex, 'code', None)})

    @handle_exceptions
    def attachment_get(self, client, attachment_id):
        """Gets a volume attachment.

        :param client: cinderclient object
        :param attachment_id: UUID of the volume attachment to get.
        :returns: a dict created from the
            cinderclient.v3.attachments.VolumeAttachment object with a backward
            compatible connection_info dict
        """
        try:
            attachment_ref = client.attachments.show(
                attachment_id)
            return attachment_ref
        except cinder_exception.ClientException as ex:
            with excutils.save_and_reraise_exception():
                LOG.error(_LE('Show attachment failed for attachment '
                              '%(id)s. Error: %(msg)s Code: %(code)s'),
                          {'id': attachment_id,
                           'msg': str(ex),
                           'code': getattr(ex, 'code', None)})

    @handle_exceptions
    def attachment_update(self, client, attachment_id, connector,
                          mountpoint=None):
        """Updates the connector on the volume attachment. An attachment
        without a connector is considered reserved but not fully attached.

        :param client: cinderclient object
        :param attachment_id: UUID of the volume attachment to update.
        :param connector: host connector dict. This is required when updating
            a volume attachment. To terminate a connection, the volume
            attachment for that connection must be deleted.
        :param mountpoint: Optional mount device name for the attachment,
            e.g. "/dev/vdb". Theoretically this is optional per volume backend,
            but in practice it's normally required so it's best to always
            provide a value.
        :returns: a dict created from the
            cinderclient.v3.attachments.VolumeAttachment object with a backward
            compatible connection_info dict
        """
        if mountpoint and 'mountpoint' not in connector:
            connector['mountpoint'] = mountpoint

        try:
            attachment_ref = client.attachments.update(
                attachment_id, connector)
            return attachment_ref
        except cinder_exception.ClientException as ex:
            with excutils.save_and_reraise_exception():
                LOG.error(_LE('Update attachment failed for attachment '
                              '%(id)s. Error: %(msg)s Code: %(code)s'),
                          {'id': attachment_id,
                           'msg': str(ex),
                           'code': getattr(ex, 'code', None)})

    @handle_exceptions
    def attachment_complete(self, client, attachment_id):
        """Marks a volume attachment complete.

        This call should be used to inform Cinder that a volume attachment is
        fully connected on the host so Cinder can apply the necessary state
        changes to the volume info in its database.

        :param client: cinderclient object
        :param attachment_id: UUID of the volume attachment to update.
        """
        try:
            client.attachments.complete(attachment_id)
        except cinder_exception.ClientException as ex:
            with excutils.save_and_reraise_exception():
                LOG.error(_LE('Complete attachment failed for attachment '
                              '%(id)s. Error: %(msg)s Code: %(code)s'),
                          {'id': attachment_id,
                           'msg': str(ex),
                           'code': getattr(ex, 'code', None)})

    @handle_exceptions
    @retrying.retry(stop_max_attempt_number=5,
                    retry_on_exception=_retry_on_internal_server_error)
    def attachment_delete(self, client, attachment_id):
        try:
            client.attachments.delete(attachment_id)
        except cinder_exception.ClientException as ex:
            with excutils.save_and_reraise_exception():
                LOG.error(_LE('Delete attachment failed for attachment '
                              '%(id)s. Error: %(msg)s Code: %(code)s'),
                          {'id': attachment_id,
                           'msg': str(ex),
                           'code': getattr(ex, 'code', None)})

    @handle_exceptions
    def extend_volume(self, client, volume, new_size):
        """Extend volume

        :param client: cinderclient object
        :param volume: UUID of the volume to extend
        :param new_size: new size of the volume after extend
        """
        try:
            client.volumes.extend(volume, new_size)
        except cinder_exception.ClientException as ex:
            with excutils.save_and_reraise_exception():
                LOG.error(_LE('Extend volume failed for volume '
                              '%(id)s. Error: %(msg)s Code: %(code)s'),
                          {'id': volume.id,
                           'msg': str(ex),
                           'code': getattr(ex, 'code', None)})