60
79
return importutils.try_import(module)
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)
88
@six.add_metaclass(abc.ABCMeta)
89
class BaseImageService(object):
90
"""Provides retrieval of disk images."""
93
def validate_href(self, image_href):
94
"""Validate image reference.
96
:param image_href: Image reference.
97
:raises: exception.ImageRefValidationFailed.
98
:returns: Information needed to further operate with an image.
102
def download(self, image_href, image_file):
103
"""Downloads image to specified location.
105
:param image_href: Image reference.
106
:param image_file: File object to write data to.
107
:raises: exception.ImageRefValidationFailed.
108
:raises: exception.ImageDownloadFailed.
112
def show(self, image_href):
113
"""Get dictionary of image properties.
115
:param image_href: Image reference.
116
:raises: exception.ImageRefValidationFailed.
117
:returns: dictionary of image properties.
121
class HttpImageService(BaseImageService):
122
"""Provides retrieval of disk images using HTTP."""
124
def validate_href(self, image_href):
125
"""Validate HTTP image reference.
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.
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,
143
def download(self, image_href, image_file):
144
"""Downloads image to specified location.
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.
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,
166
def show(self, image_href):
167
"""Get dictionary of image properties.
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.
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 "
184
'size': int(image_size),
189
class FileImageService(BaseImageService):
190
"""Provides retrieval of disk images available locally on the conductor."""
192
def validate_href(self, image_href):
193
"""Validate local image reference.
195
:param image_href: Image reference.
196
:raises: exception.ImageRefValidationFailed if source image file
198
:returns: Path to image file if it exists.
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."))
206
def download(self, image_href, image_file):
207
"""Downloads image to specified location.
209
:param image_href: Image reference.
210
:param image_file: File object to write data to.
211
:raises: exception.ImageRefValidationFailed if source image file
213
:raises: exception.ImageDownloadFailed if exceptions were raised while
214
writing to file or creating hard link.
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
220
# We should have read and write access to source file to create
222
if (local_device == os.stat(source_image_path).st_dev and
223
os.access(source_image_path, os.R_OK | os.W_OK)):
225
os.remove(dest_image_path)
226
os.link(source_image_path, dest_image_path)
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(),
232
except Exception as e:
233
raise exception.ImageDownloadFailed(image_href=image_href,
236
def show(self, image_href):
237
"""Get dictionary of image properties.
239
:param image_href: Image reference.
240
:raises: exception.ImageRefValidationFailed if image file specified
242
:returns: dictionary of image properties.
244
source_image_path = self.validate_href(image_href)
246
'size': os.path.getsize(source_image_path),
252
'http': HttpImageService,
253
'https': HttpImageService,
254
'file': FileImageService,
255
'glance': GlanceImageService,
259
def get_image_service(image_href, client=None, version=1, context=None):
260
"""Get image service instance to download the image.
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
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
273
scheme = urlparse.urlparse(image_href).scheme.lower()
275
cls = protocol_mapping[scheme or 'glance']
277
raise exception.ImageRefValidationFailed(
278
image_href=image_href,
279
reason=_('Image download protocol '
280
'%s is not supported.') % scheme
283
if cls == GlanceImageService:
284
return cls(client, version, context)