def update(self, artifact_id, type_name=None, type_version=None, remove_props=None, **kwargs): """Update attributes of an artifact. :param artifact_id: ID of the artifact to modify. :param remove_props: List of property names to remove :param \*\*kwargs: Artifact attribute names and their new values. """ type_name, type_version = self._check_type_params( type_name, type_version) url = glare_urls['update_get_delete'].format(version=self.version, type_name=type_name, type_version=type_version, artifact_id=artifact_id) hdrs = {'Content-Type': 'application/openstack-images-v2.1-json-patch'} artifact_obj = self.get(artifact_id, type_name, type_version) changes = [] if remove_props: for prop in remove_props: if prop in ArtifactType.generic_properties: msg = "Generic properties cannot be removed" raise exc.HTTPBadRequest(msg) if prop not in kwargs: changes.append({'op': 'remove', 'path': '/' + prop}) for prop in kwargs: if prop in artifact_obj.generic_properties: op = 'add' if getattr(artifact_obj, prop) is None else 'replace' elif prop in artifact_obj.type_specific_properties: if artifact_obj.type_specific_properties[prop] is None: op = 'add' else: op = 'replace' else: msg = ("Property '%s' doesn't exist in type '%s' with version" " '%s'" % (prop, type_name, type_version)) raise exc.HTTPBadRequest(msg) changes.append({ 'op': op, 'path': '/' + prop, 'value': kwargs[prop] }) resp, body = self.http_client.patch(url, headers=hdrs, data=changes) return ArtifactType(**body)
def _check_type_params(self, type_name, type_version): """Check that type name and type versions were specified""" type_name = type_name or self.type_name type_version = type_version or self.type_version if type_name is None: msg = "Type name must be specified" raise exc.HTTPBadRequest(msg) if type_version is None: msg = "Type version must be specified" raise exc.HTTPBadRequest(msg) return type_name, type_version
def image_import(self, image_id, method='glance-direct', uri=None, backend=None, stores=None, allow_failure=True, all_stores=None): """Import Image via method.""" headers = {} url = '/v2/images/%s/import' % image_id data = {'method': {'name': method}} if stores: data['stores'] = stores if allow_failure: data['all_stores_must_succeed'] = False if backend is not None: headers['x-image-meta-store'] = backend if all_stores: data['all_stores'] = True if allow_failure: data['all_stores_must_succeed'] = False if uri: if method == 'web-download': data['method']['uri'] = uri else: raise exc.HTTPBadRequest('URI is only supported with method: ' '"web-download"') resp, body = self.http_client.post(url, data=data, headers=headers) return body, resp
def get(self, artifact_id, type_name=None, type_version=None, show_level=None): """Get information about an artifact. :param artifact_id: ID of the artifact to get. :param show_level: value of datalization. Possible values: "none", "basic", "direct", "transitive" """ type_name, type_version = self._check_type_params( type_name, type_version) url = glare_urls['update_get_delete'].format(version=self.version, type_name=type_name, type_version=type_version, artifact_id=artifact_id) if show_level: if show_level not in ArtifactType.supported_show_levels: msg = "Invalid show level: %s" % show_level raise exc.HTTPBadRequest(msg) url += '?show_level=%s' % show_level resp, body = self.http_client.get(url) return ArtifactType(**body)
def _validate_sort_param(sort): """Validates sorting argument for invalid keys and directions values. :param sort: comma-separated list of sort keys with optional <:dir> after each key """ for sort_param in sort.strip().split(','): key, _sep, dir = sort_param.partition(':') if dir and dir not in SORT_DIR_VALUES: msg = ('Invalid sort direction: %(sort_dir)s.' ' It must be one of the following: %(available)s.' ) % {'sort_dir': dir, 'available': ', '.join(SORT_DIR_VALUES)} raise exc.HTTPBadRequest(msg) if key not in SORT_KEY_VALUES: msg = ('Invalid sort key: %(sort_key)s.' ' It must be one of the following: %(available)s.' ) % {'sort_key': key, 'available': ', '.join(SORT_KEY_VALUES)} raise exc.HTTPBadRequest(msg) return sort
def image_import(self, image_id, method='glance-direct', uri=None): """Import Image via method.""" url = '/v2/images/%s/import' % image_id data = {'method': {'name': method}} if uri: if method == 'web-download': data['method']['uri'] = uri else: raise exc.HTTPBadRequest('URI is only supported with method: ' '"web-download"') resp, body = self.http_client.post(url, data=data) return body, resp
def __init__(self, **kwargs): try: for prop in self.generic_properties: setattr(self, prop, kwargs.pop(prop)) except KeyError: msg = "Invalid parameters were provided" raise exc.HTTPBadRequest(msg) self.type_specific_properties = {} for key, value in six.iteritems(kwargs): try: if _is_dependency(value): self.type_specific_properties[key] = ArtifactType(**value) elif _is_dependencies_list(value): self.type_specific_properties[key] = [ArtifactType(**elem) for elem in value] else: self.type_specific_properties[key] = value except exc.HTTPBadRequest: # if it's not possible to generate artifact object then # assign the value as a regular dict. self.type_specific_properties[key] = value
def list(self, **kwargs): """Retrieve a listing of Image objects. :param page_size: Number of images to request in each paginated request. :returns: generator over list of Images. """ limit = kwargs.get('limit') # NOTE(flaper87): Don't use `get('page_size', DEFAULT_SIZE)` otherwise, # it could be possible to send invalid data to the server by passing # page_size=None. page_size = kwargs.get('page_size') or DEFAULT_PAGE_SIZE def paginate(url, page_size, limit=None): next_url = url req_id_hdr = {} while True: if limit and page_size > limit: # NOTE(flaper87): Avoid requesting 2000 images when limit # is 1 next_url = next_url.replace("limit=%s" % page_size, "limit=%s" % limit) resp, body = self.http_client.get(next_url, headers=req_id_hdr) # NOTE(rsjethani): Store curent request id so that it can be # used in subsequent requests. Refer bug #1525259 req_id_hdr['x-openstack-request-id'] = \ utils._extract_request_id(resp) for image in body['images']: # NOTE(bcwaldon): remove 'self' for now until we have # an elegant way to pass it into the model constructor # without conflict. image.pop('self', None) # We do not validate the model when listing. # This prevents side-effects of injecting invalid # schema values via v1. yield self.unvalidated_model(**image), resp if limit: limit -= 1 if limit <= 0: raise StopIteration try: next_url = body['next'] except KeyError: return filters = kwargs.get('filters', {}) # NOTE(flaper87): We paginate in the client, hence we use # the page_size as Glance's limit. filters['limit'] = page_size tags = filters.pop('tag', []) tags_url_params = [] for tag in tags: if not isinstance(tag, six.string_types): raise exc.HTTPBadRequest("Invalid tag value %s" % tag) tags_url_params.append({'tag': encodeutils.safe_encode(tag)}) for param, value in filters.items(): if isinstance(value, six.string_types): filters[param] = encodeutils.safe_encode(value) url = '/v2/images?%s' % parse.urlencode(filters) for param in tags_url_params: url = '%s&%s' % (url, parse.urlencode(param)) if 'sort' in kwargs: if 'sort_key' in kwargs or 'sort_dir' in kwargs: raise exc.HTTPBadRequest("The 'sort' argument is not supported" " with 'sort_key' or 'sort_dir'.") url = '%s&sort=%s' % (url, self._validate_sort_param( kwargs['sort'])) else: sort_dir = self._wrap(kwargs.get('sort_dir', [])) sort_key = self._wrap(kwargs.get('sort_key', [])) if len(sort_key) != len(sort_dir) and len(sort_dir) > 1: raise exc.HTTPBadRequest( "Unexpected number of sort directions: " "either provide a single sort direction or an equal " "number of sort keys and sort directions.") for key in sort_key: url = '%s&sort_key=%s' % (url, key) for dir in sort_dir: url = '%s&sort_dir=%s' % (url, dir) if isinstance(kwargs.get('marker'), six.string_types): url = '%s&marker=%s' % (url, kwargs['marker']) for image, resp in paginate(url, page_size, limit): yield image, resp
def _get_image_with_locations_or_fail(self, image_id): image = self.get(image_id) if getattr(image, 'locations', None) is None: raise exc.HTTPBadRequest('The administrator has disabled ' 'API access to image locations') return image
def list(self, **kwargs): """Retrieve a listing of Image objects. :param page_size: Number of images to request in each paginated request. :returns: generator over list of Images. """ ori_validate_fun = self.model.validate empty_fun = lambda *args, **kwargs: None limit = kwargs.get('limit') # NOTE(flaper87): Don't use `get('page_size', DEFAULT_SIZE)` otherwise, # it could be possible to send invalid data to the server by passing # page_size=None. page_size = kwargs.get('page_size') or DEFAULT_PAGE_SIZE def paginate(url, page_size, limit=None): next_url = url while True: if limit and page_size > limit: # NOTE(flaper87): Avoid requesting 2000 images when limit # is 1 next_url = next_url.replace("limit=%s" % page_size, "limit=%s" % limit) resp, body = self.http_client.get(next_url) for image in body['images']: # NOTE(bcwaldon): remove 'self' for now until we have # an elegant way to pass it into the model constructor # without conflict. image.pop('self', None) yield self.model(**image) # NOTE(zhiyan): In order to resolve the performance issue # of JSON schema validation for image listing case, we # don't validate each image entry but do it only on first # image entry for each page. self.model.validate = empty_fun if limit: limit -= 1 if limit <= 0: raise StopIteration # NOTE(zhiyan); Reset validation function. self.model.validate = ori_validate_fun try: next_url = body['next'] except KeyError: return filters = kwargs.get('filters', {}) # NOTE(flaper87): We paginate in the client, hence we use # the page_size as Glance's limit. filters['limit'] = page_size tags = filters.pop('tag', []) tags_url_params = [] for tag in tags: if isinstance(tag, six.string_types): tags_url_params.append({'tag': encodeutils.safe_encode(tag)}) for param, value in six.iteritems(filters): if isinstance(value, six.string_types): filters[param] = encodeutils.safe_encode(value) url = '/v2/images?%s' % parse.urlencode(filters) for param in tags_url_params: url = '%s&%s' % (url, parse.urlencode(param)) if 'sort' in kwargs: if 'sort_key' in kwargs or 'sort_dir' in kwargs: raise exc.HTTPBadRequest("The 'sort' argument is not supported" " with 'sort_key' or 'sort_dir'.") url = '%s&sort=%s' % (url, self._validate_sort_param( kwargs['sort'])) else: sort_dir = self._wrap(kwargs.get('sort_dir', [])) sort_key = self._wrap(kwargs.get('sort_key', [])) if len(sort_key) != len(sort_dir) and len(sort_dir) > 1: raise exc.HTTPBadRequest( "Unexpected number of sort directions: " "either provide a single sort direction or an equal " "number of sort keys and sort directions.") for key in sort_key: url = '%s&sort_key=%s' % (url, key) for dir in sort_dir: url = '%s&sort_dir=%s' % (url, dir) for image in paginate(url, page_size, limit): yield image
def list(self, **kwargs): """Retrieve a listing of Image objects. :param page_size: Number of images to request in each paginated request. :returns: generator over list of Images. """ limit = kwargs.get('limit') # NOTE(flaper87): Don't use `get('page_size', DEFAULT_SIZE)` otherwise, # it could be possible to send invalid data to the server by passing # page_size=None. page_size = kwargs.get('page_size') or DEFAULT_PAGE_SIZE def paginate(url, page_size, limit=None): next_url = url while True: if limit and page_size > limit: # NOTE(flaper87): Avoid requesting 2000 images when limit # is 1 next_url = next_url.replace("limit=%s" % page_size, "limit=%s" % limit) resp, body = self.http_client.get(next_url) for image in body['images']: # NOTE(bcwaldon): remove 'self' for now until we have # an elegant way to pass it into the model constructor # without conflict. image.pop('self', None) # We do not validate the model when listing. # This prevents side-effects of injecting invalid # schema values via v1. yield self.unvalidated_model(**image) if limit: limit -= 1 if limit <= 0: raise StopIteration try: next_url = body['next'] except KeyError: return filters = kwargs.get('filters', {}) # NOTE(flaper87): We paginate in the client, hence we use # the page_size as Glance's limit. filters['limit'] = page_size tags = filters.pop('tag', []) tags_url_params = [] for tag in tags: if not isinstance(tag, six.string_types): raise exc.HTTPBadRequest("Invalid tag value %s" % tag) tags_url_params.append({'tag': encodeutils.safe_encode(tag)}) for param, value in six.iteritems(filters): if isinstance(value, six.string_types): filters[param] = encodeutils.safe_encode(value) url = '/v2/images?%s' % parse.urlencode(filters) for param in tags_url_params: url = '%s&%s' % (url, parse.urlencode(param)) if isinstance(kwargs.get('marker'), six.string_types): url = '%s&marker=%s' % (url, kwargs['marker']) for image in paginate(url, page_size, limit): yield image