XenAPI: Check image status before uploading data
Currently, the Xen API plugin sends the data payload to Glance without checking the image status. In consequence, if the image is not in queued status Glance will not let the image upload succeed. This is by design, please refer [0]. The issue here being; buffering of the data will happen on the Glance server. Consequently, the bound to be failed upload operation will not get a 409 until after a large chunk of data has already been streamed to the Glance server. This may result into client side wastage of bandwidth and wait time. This patch proposes a HEAD call be made from the glance plugin to Glance API; thus in turn checking the valid image state for the data transfer. [0] http://docs.openstack.org/developer/glance/statuses.html Change-Id: I24c1222a3b1ac809353724500117e58d50615ac8
This commit is contained in:
@@ -157,6 +157,9 @@ def _upload_tarball(staging_path, image_id, glance_host, glance_port,
|
||||
raise RetryableError(error)
|
||||
|
||||
try:
|
||||
validate_image_status_before_upload(conn, glance_host, glance_port,
|
||||
image_id, url, extra_headers)
|
||||
|
||||
try:
|
||||
# NOTE(sirp): httplib under python2.4 won't accept
|
||||
# a file-like object to request
|
||||
@@ -227,60 +230,125 @@ def _upload_tarball(staging_path, image_id, glance_host, glance_port,
|
||||
"Response Status: %i, Response body: %s"
|
||||
% (url, resp.status, resp.read()))
|
||||
|
||||
# Note(Jesse): This branch sorts errors into those that are permanent,
|
||||
# those that are ephemeral, and those that are unexpected.
|
||||
if resp.status in (httplib.BAD_REQUEST, # 400
|
||||
httplib.UNAUTHORIZED, # 401
|
||||
httplib.PAYMENT_REQUIRED, # 402
|
||||
httplib.FORBIDDEN, # 403
|
||||
httplib.NOT_FOUND, # 404
|
||||
httplib.METHOD_NOT_ALLOWED, # 405
|
||||
httplib.NOT_ACCEPTABLE, # 406
|
||||
httplib.PROXY_AUTHENTICATION_REQUIRED, # 407
|
||||
httplib.CONFLICT, # 409
|
||||
httplib.GONE, # 410
|
||||
httplib.LENGTH_REQUIRED, # 411
|
||||
httplib.PRECONDITION_FAILED, # 412
|
||||
httplib.REQUEST_ENTITY_TOO_LARGE, # 413
|
||||
httplib.REQUEST_URI_TOO_LONG, # 414
|
||||
httplib.UNSUPPORTED_MEDIA_TYPE, # 415
|
||||
httplib.REQUESTED_RANGE_NOT_SATISFIABLE, # 416
|
||||
httplib.EXPECTATION_FAILED, # 417
|
||||
httplib.UNPROCESSABLE_ENTITY, # 422
|
||||
httplib.LOCKED, # 423
|
||||
httplib.FAILED_DEPENDENCY, # 424
|
||||
httplib.UPGRADE_REQUIRED, # 426
|
||||
httplib.NOT_IMPLEMENTED, # 501
|
||||
httplib.HTTP_VERSION_NOT_SUPPORTED, # 505
|
||||
httplib.NOT_EXTENDED, # 510
|
||||
):
|
||||
raise PluginError("Got Permanent Error response [%i] while "
|
||||
"uploading image [%s] to glance host [%s:%s]"
|
||||
% (resp.status, image_id,
|
||||
glance_host, glance_port))
|
||||
elif resp.status in (httplib.REQUEST_TIMEOUT, # 408
|
||||
httplib.INTERNAL_SERVER_ERROR, # 500
|
||||
httplib.BAD_GATEWAY, # 502
|
||||
httplib.SERVICE_UNAVAILABLE, # 503
|
||||
httplib.GATEWAY_TIMEOUT, # 504
|
||||
httplib.INSUFFICIENT_STORAGE, # 507
|
||||
):
|
||||
raise RetryableError("Got Ephemeral Error response [%i] while "
|
||||
"uploading image [%s] to glance host [%s:%s]"
|
||||
% (resp.status, image_id,
|
||||
glance_host, glance_port))
|
||||
else:
|
||||
# Note(Jesse): Assume unexpected errors are retryable. If you are
|
||||
# seeing this error message, the error should probably be added
|
||||
# to either the ephemeral or permanent error list.
|
||||
raise RetryableError("Got Unexpected Error response [%i] while "
|
||||
"uploading image [%s] to glance host [%s:%s]"
|
||||
% (resp.status, image_id,
|
||||
glance_host, glance_port))
|
||||
check_resp_status_and_retry(resp, image_id, glance_host, glance_port)
|
||||
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def check_resp_status_and_retry(resp, image_id, glance_host, glance_port):
|
||||
# Note(Jesse): This branch sorts errors into those that are permanent,
|
||||
# those that are ephemeral, and those that are unexpected.
|
||||
if resp.status in (httplib.BAD_REQUEST, # 400
|
||||
httplib.UNAUTHORIZED, # 401
|
||||
httplib.PAYMENT_REQUIRED, # 402
|
||||
httplib.FORBIDDEN, # 403
|
||||
httplib.NOT_FOUND, # 404
|
||||
httplib.METHOD_NOT_ALLOWED, # 405
|
||||
httplib.NOT_ACCEPTABLE, # 406
|
||||
httplib.PROXY_AUTHENTICATION_REQUIRED, # 407
|
||||
httplib.CONFLICT, # 409
|
||||
httplib.GONE, # 410
|
||||
httplib.LENGTH_REQUIRED, # 411
|
||||
httplib.PRECONDITION_FAILED, # 412
|
||||
httplib.REQUEST_ENTITY_TOO_LARGE, # 413
|
||||
httplib.REQUEST_URI_TOO_LONG, # 414
|
||||
httplib.UNSUPPORTED_MEDIA_TYPE, # 415
|
||||
httplib.REQUESTED_RANGE_NOT_SATISFIABLE, # 416
|
||||
httplib.EXPECTATION_FAILED, # 417
|
||||
httplib.UNPROCESSABLE_ENTITY, # 422
|
||||
httplib.LOCKED, # 423
|
||||
httplib.FAILED_DEPENDENCY, # 424
|
||||
httplib.UPGRADE_REQUIRED, # 426
|
||||
httplib.NOT_IMPLEMENTED, # 501
|
||||
httplib.HTTP_VERSION_NOT_SUPPORTED, # 505
|
||||
httplib.NOT_EXTENDED, # 510
|
||||
):
|
||||
raise PluginError("Got Permanent Error response [%i] while "
|
||||
"uploading image [%s] to glance host [%s:%s]"
|
||||
% (resp.status, image_id,
|
||||
glance_host, glance_port))
|
||||
# NOTE(nikhil): Only a sub-set of the 500 errors are retryable. We
|
||||
# optimistically retry on 500 errors below.
|
||||
elif resp.status in (httplib.REQUEST_TIMEOUT, # 408
|
||||
httplib.INTERNAL_SERVER_ERROR, # 500
|
||||
httplib.BAD_GATEWAY, # 502
|
||||
httplib.SERVICE_UNAVAILABLE, # 503
|
||||
httplib.GATEWAY_TIMEOUT, # 504
|
||||
httplib.INSUFFICIENT_STORAGE, # 507
|
||||
):
|
||||
raise RetryableError("Got Ephemeral Error response [%i] while "
|
||||
"uploading image [%s] to glance host [%s:%s]"
|
||||
% (resp.status, image_id,
|
||||
glance_host, glance_port))
|
||||
else:
|
||||
# Note(Jesse): Assume unexpected errors are retryable. If you are
|
||||
# seeing this error message, the error should probably be added
|
||||
# to either the ephemeral or permanent error list.
|
||||
raise RetryableError("Got Unexpected Error response [%i] while "
|
||||
"uploading image [%s] to glance host [%s:%s]"
|
||||
% (resp.status, image_id,
|
||||
glance_host, glance_port))
|
||||
|
||||
|
||||
def validate_image_status_before_upload(conn, glance_host, glance_port,
|
||||
image_id, url, extra_headers):
|
||||
try:
|
||||
# NOTE(nikhil): Attempt to determine if the Image has a status
|
||||
# of 'queued'. Because data will continued to be sent to Glance
|
||||
# until it has a chance to check the Image state, discover that
|
||||
# it is not 'active' and send back a 409. Hence, the data will be
|
||||
# unnecessarily buffered by Glance. This wastes time and bandwidth.
|
||||
# LP bug #1202785
|
||||
conn.request('HEAD', '/v1/images/%s' % image_id,
|
||||
headers=extra_headers)
|
||||
head_resp = conn.getresponse()
|
||||
# NOTE(nikhil): read the response to re-use the conn object.
|
||||
body_data = head_resp.read(8192)
|
||||
if len(body_data) > 8:
|
||||
err_msg = ('Cannot upload data for image %(image_id)s as the '
|
||||
'HEAD call had more than 8192 bytes of data in '
|
||||
'the response body.' % {'image_id': image_id})
|
||||
raise PluginError("Got Permanent Error while uploading image "
|
||||
"[%s] to glance host [%s:%s]. "
|
||||
"Message: %s" % (image_id, glance_host,
|
||||
glance_port, err_msg))
|
||||
else:
|
||||
head_resp.read()
|
||||
|
||||
except Exception, error:
|
||||
logging.exception('Failed to HEAD the image %(image_id)s while '
|
||||
'checking image status before attempting to '
|
||||
'upload %(url)s' % {'image_id': image_id,
|
||||
'url': url})
|
||||
raise RetryableError(error)
|
||||
|
||||
if head_resp.status != httplib.OK:
|
||||
logging.error("Unexpected response while doing a HEAD call "
|
||||
"to image %s , url = %s , Response Status: "
|
||||
"%i" % (image_id, url, head_resp.status))
|
||||
|
||||
check_resp_status_and_retry(head_resp, image_id, glance_host,
|
||||
glance_port)
|
||||
|
||||
else:
|
||||
image_status = head_resp.getheader('x-image-meta-status')
|
||||
if image_status not in ('queued', ):
|
||||
err_msg = ('Cannot upload data for image %(image_id)s as the '
|
||||
'image status is %(image_status)s' %
|
||||
{'image_id': image_id, 'image_status': image_status})
|
||||
logging.exception(err_msg)
|
||||
raise PluginError("Got Permanent Error while uploading image "
|
||||
"[%s] to glance host [%s:%s]. "
|
||||
"Message: %s" % (image_id, glance_host,
|
||||
glance_port, err_msg))
|
||||
else:
|
||||
logging.info('Found image %(image_id)s in status '
|
||||
'%(image_status)s. Attempting to '
|
||||
'upload.' % {'image_id': image_id,
|
||||
'image_status': image_status})
|
||||
|
||||
|
||||
def download_vhd(session, image_id, glance_host, glance_port, glance_use_ssl,
|
||||
uuid_stack, sr_path, extra_headers):
|
||||
"""Download an image from Glance, unbundle it, and then deposit the VHDs
|
||||
|
||||
Reference in New Issue
Block a user