~ubuntu-branches/ubuntu/vivid/ironic/vivid-updates

« back to all changes in this revision

Viewing changes to ironic/common/image_service.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short
  • Date: 2015-03-30 11:14:57 UTC
  • mfrom: (1.2.6)
  • Revision ID: package-import@ubuntu.com-20150330111457-kr4ju3guf22m4vbz
Tags: 2015.1~b3-0ubuntu1
* New upstream release.
  + d/control: 
    - Align with upstream dependencies.
    - Add dh-python to build-dependencies.
    - Add psmisc as a dependency. (LP: #1358820)
  + d/p/fix-requirements.patch: Rediffed.
  + d/ironic-conductor.init.in: Fixed typos in LSB headers,
    thanks to JJ Asghar. (LP: #1429962)

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
#    under the License.
16
16
 
17
17
 
18
 
from oslo.utils import importutils
 
18
import abc
 
19
import os
 
20
import shutil
 
21
 
19
22
from oslo_config import cfg
20
 
 
 
23
from oslo_utils import importutils
 
24
import requests
 
25
import sendfile
 
26
import six
 
27
import six.moves.urllib.parse as urlparse
 
28
 
 
29
from ironic.common import exception
 
30
from ironic.common.i18n import _
 
31
from ironic.openstack.common import log as logging
 
32
 
 
33
LOG = logging.getLogger(__name__)
 
34
 
 
35
IMAGE_CHUNK_SIZE = 1024 * 1024  # 1mb
 
36
 
 
37
 
 
38
CONF = cfg.CONF
 
39
# Import this opt early so that it is available when registering
 
40
# glance_opts below.
 
41
CONF.import_opt('my_ip', 'ironic.netconf')
21
42
 
22
43
glance_opts = [
23
44
    cfg.StrOpt('glance_host',
48
69
               'Set to https for SSL.'),
49
70
]
50
71
 
51
 
 
52
 
CONF = cfg.CONF
53
72
CONF.register_opts(glance_opts, group='glance')
54
73
 
55
74
 
60
79
    return importutils.try_import(module)
61
80
 
62
81
 
63
 
def Service(client=None, version=1, context=None):
 
82
def GlanceImageService(client=None, version=1, context=None):
64
83
    module = import_versioned_module(version, 'image_service')
65
84
    service_class = getattr(module, 'GlanceImageService')
66
85
    return service_class(client, version, context)
 
86
 
 
87
 
 
88
@six.add_metaclass(abc.ABCMeta)
 
89
class BaseImageService(object):
 
90
    """Provides retrieval of disk images."""
 
91
 
 
92
    @abc.abstractmethod
 
93
    def validate_href(self, image_href):
 
94
        """Validate image reference.
 
95
 
 
96
        :param image_href: Image reference.
 
97
        :raises: exception.ImageRefValidationFailed.
 
98
        :returns: Information needed to further operate with an image.
 
99
        """
 
100
 
 
101
    @abc.abstractmethod
 
102
    def download(self, image_href, image_file):
 
103
        """Downloads image to specified location.
 
104
 
 
105
        :param image_href: Image reference.
 
106
        :param image_file: File object to write data to.
 
107
        :raises: exception.ImageRefValidationFailed.
 
108
        :raises: exception.ImageDownloadFailed.
 
109
        """
 
110
 
 
111
    @abc.abstractmethod
 
112
    def show(self, image_href):
 
113
        """Get dictionary of image properties.
 
114
 
 
115
        :param image_href: Image reference.
 
116
        :raises: exception.ImageRefValidationFailed.
 
117
        :returns: dictionary of image properties.
 
118
        """
 
119
 
 
120
 
 
121
class HttpImageService(BaseImageService):
 
122
    """Provides retrieval of disk images using HTTP."""
 
123
 
 
124
    def validate_href(self, image_href):
 
125
        """Validate HTTP image reference.
 
126
 
 
127
        :param image_href: Image reference.
 
128
        :raises: exception.ImageRefValidationFailed if HEAD request failed or
 
129
            returned response code not equal to 200.
 
130
        :returns: Response to HEAD request.
 
131
        """
 
132
        try:
 
133
            response = requests.head(image_href)
 
134
            if response.status_code != 200:
 
135
                raise exception.ImageRefValidationFailed(image_href=image_href,
 
136
                    reason=_("Got HTTP code %s instead of 200 in response to "
 
137
                             "HEAD request.") % response.status_code)
 
138
        except requests.RequestException as e:
 
139
            raise exception.ImageRefValidationFailed(image_href=image_href,
 
140
                                                     reason=e)
 
141
        return response
 
142
 
 
143
    def download(self, image_href, image_file):
 
144
        """Downloads image to specified location.
 
145
 
 
146
        :param image_href: Image reference.
 
147
        :param image_file: File object to write data to.
 
148
        :raises: exception.ImageRefValidationFailed if GET request returned
 
149
            response code not equal to 200.
 
150
        :raises: exception.ImageDownloadFailed if:
 
151
            * IOError happened during file write;
 
152
            * GET request failed.
 
153
        """
 
154
        try:
 
155
            response = requests.get(image_href, stream=True)
 
156
            if response.status_code != 200:
 
157
                raise exception.ImageRefValidationFailed(image_href=image_href,
 
158
                    reason=_("Got HTTP code %s instead of 200 in response to "
 
159
                             "GET request.") % response.status_code)
 
160
            with response.raw as input_img:
 
161
                shutil.copyfileobj(input_img, image_file, IMAGE_CHUNK_SIZE)
 
162
        except (requests.RequestException, IOError) as e:
 
163
            raise exception.ImageDownloadFailed(image_href=image_href,
 
164
                                                reason=e)
 
165
 
 
166
    def show(self, image_href):
 
167
        """Get dictionary of image properties.
 
168
 
 
169
        :param image_href: Image reference.
 
170
        :raises: exception.ImageRefValidationFailed if:
 
171
            * HEAD request failed;
 
172
            * HEAD request returned response code not equal to 200;
 
173
            * Content-Length header not found in response to HEAD request.
 
174
        :returns: dictionary of image properties.
 
175
        """
 
176
        response = self.validate_href(image_href)
 
177
        image_size = response.headers.get('Content-Length')
 
178
        if image_size is None:
 
179
            raise exception.ImageRefValidationFailed(image_href=image_href,
 
180
                reason=_("Cannot determine image size as there is no "
 
181
                         "Content-Length header specified in response "
 
182
                         "to HEAD request."))
 
183
        return {
 
184
            'size': int(image_size),
 
185
            'properties': {}
 
186
        }
 
187
 
 
188
 
 
189
class FileImageService(BaseImageService):
 
190
    """Provides retrieval of disk images available locally on the conductor."""
 
191
 
 
192
    def validate_href(self, image_href):
 
193
        """Validate local image reference.
 
194
 
 
195
        :param image_href: Image reference.
 
196
        :raises: exception.ImageRefValidationFailed if source image file
 
197
            doesn't exist.
 
198
        :returns: Path to image file if it exists.
 
199
        """
 
200
        image_path = urlparse.urlparse(image_href).path
 
201
        if not os.path.isfile(image_path):
 
202
            raise exception.ImageRefValidationFailed(image_href=image_href,
 
203
                reason=_("Specified image file not found."))
 
204
        return image_path
 
205
 
 
206
    def download(self, image_href, image_file):
 
207
        """Downloads image to specified location.
 
208
 
 
209
        :param image_href: Image reference.
 
210
        :param image_file: File object to write data to.
 
211
        :raises: exception.ImageRefValidationFailed if source image file
 
212
            doesn't exist.
 
213
        :raises: exception.ImageDownloadFailed if exceptions were raised while
 
214
            writing to file or creating hard link.
 
215
        """
 
216
        source_image_path = self.validate_href(image_href)
 
217
        dest_image_path = image_file.name
 
218
        local_device = os.stat(dest_image_path).st_dev
 
219
        try:
 
220
            # We should have read and write access to source file to create
 
221
            # hard link to it.
 
222
            if (local_device == os.stat(source_image_path).st_dev and
 
223
                    os.access(source_image_path, os.R_OK | os.W_OK)):
 
224
                image_file.close()
 
225
                os.remove(dest_image_path)
 
226
                os.link(source_image_path, dest_image_path)
 
227
            else:
 
228
                filesize = os.path.getsize(source_image_path)
 
229
                with open(source_image_path, 'rb') as input_img:
 
230
                    sendfile.sendfile(image_file.fileno(), input_img.fileno(),
 
231
                                      0, filesize)
 
232
        except Exception as e:
 
233
            raise exception.ImageDownloadFailed(image_href=image_href,
 
234
                                                reason=e)
 
235
 
 
236
    def show(self, image_href):
 
237
        """Get dictionary of image properties.
 
238
 
 
239
        :param image_href: Image reference.
 
240
        :raises: exception.ImageRefValidationFailed if image file specified
 
241
            doesn't exist.
 
242
        :returns: dictionary of image properties.
 
243
        """
 
244
        source_image_path = self.validate_href(image_href)
 
245
        return {
 
246
            'size': os.path.getsize(source_image_path),
 
247
            'properties': {}
 
248
        }
 
249
 
 
250
 
 
251
protocol_mapping = {
 
252
    'http': HttpImageService,
 
253
    'https': HttpImageService,
 
254
    'file': FileImageService,
 
255
    'glance': GlanceImageService,
 
256
}
 
257
 
 
258
 
 
259
def get_image_service(image_href, client=None, version=1, context=None):
 
260
    """Get image service instance to download the image.
 
261
 
 
262
    :param image_href: String containing href to get image service for.
 
263
    :param client: Glance client to be used for download, used only if
 
264
        image_href is Glance href.
 
265
    :param version: Version of Glance API to use, used only if image_href is
 
266
        Glance href.
 
267
    :param context: request context, used only if image_href is Glance href.
 
268
    :raises: exception.ImageRefValidationFailed if no image service can
 
269
        handle specified href.
 
270
    :returns: Instance of an image service class that is able to download
 
271
        specified image.
 
272
    """
 
273
    scheme = urlparse.urlparse(image_href).scheme.lower()
 
274
    try:
 
275
        cls = protocol_mapping[scheme or 'glance']
 
276
    except KeyError:
 
277
        raise exception.ImageRefValidationFailed(
 
278
            image_href=image_href,
 
279
            reason=_('Image download protocol '
 
280
                     '%s is not supported.') % scheme
 
281
        )
 
282
 
 
283
    if cls == GlanceImageService:
 
284
        return cls(client, version, context)
 
285
    return cls()