Source code for glance.registry.client.v1.client

# Copyright 2013 OpenStack Foundation
# 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.

"""
Simple client class to speak with any RESTful service that implements
the Glance Registry API
"""

from oslo_log import log as logging
from oslo_serialization import jsonutils
from oslo_utils import excutils
import six

from glance.common.client import BaseClient
from glance.common import crypt
from glance.common import exception
from glance.i18n import _LE
from glance.registry.api.v1 import images

LOG = logging.getLogger(__name__)


[docs]class RegistryClient(BaseClient): """A client for the Registry image metadata service.""" DEFAULT_PORT = 9191 def __init__(self, host=None, port=None, metadata_encryption_key=None, identity_headers=None, **kwargs): """ :param metadata_encryption_key: Key used to encrypt 'location' metadata """ self.metadata_encryption_key = metadata_encryption_key # NOTE (dprince): by default base client overwrites host and port # settings when using keystone. configure_via_auth=False disables # this behaviour to ensure we still send requests to the Registry API self.identity_headers = identity_headers # store available passed request id for do_request call self._passed_request_id = kwargs.pop('request_id', None) BaseClient.__init__(self, host, port, configure_via_auth=False, **kwargs)
[docs] def decrypt_metadata(self, image_metadata): if self.metadata_encryption_key: if image_metadata.get('location'): location = crypt.urlsafe_decrypt(self.metadata_encryption_key, image_metadata['location']) image_metadata['location'] = location if image_metadata.get('location_data'): ld = [] for loc in image_metadata['location_data']: url = crypt.urlsafe_decrypt(self.metadata_encryption_key, loc['url']) ld.append({'id': loc['id'], 'url': url, 'metadata': loc['metadata'], 'status': loc['status']}) image_metadata['location_data'] = ld return image_metadata
[docs] def encrypt_metadata(self, image_metadata): if self.metadata_encryption_key: location_url = image_metadata.get('location') if location_url: location = crypt.urlsafe_encrypt(self.metadata_encryption_key, location_url, 64) image_metadata['location'] = location if image_metadata.get('location_data'): ld = [] for loc in image_metadata['location_data']: if loc['url'] == location_url: url = location else: url = crypt.urlsafe_encrypt( self.metadata_encryption_key, loc['url'], 64) ld.append({'url': url, 'metadata': loc['metadata'], 'status': loc['status'], # NOTE(zhiyan): New location has no ID field. 'id': loc.get('id')}) image_metadata['location_data'] = ld return image_metadata
[docs] def get_images(self, **kwargs): """ Returns a list of image id/name mappings from Registry :param filters: dict of keys & expected values to filter results :param marker: image id after which to start page :param limit: max number of images to return :param sort_key: results will be ordered by this image attribute :param sort_dir: direction in which to order results (asc, desc) """ params = self._extract_params(kwargs, images.SUPPORTED_PARAMS) res = self.do_request("GET", "/images", params=params) image_list = jsonutils.loads(res.read())['images'] for image in image_list: image = self.decrypt_metadata(image) return image_list
[docs] def do_request(self, method, action, **kwargs): try: kwargs['headers'] = kwargs.get('headers', {}) kwargs['headers'].update(self.identity_headers or {}) if self._passed_request_id: request_id = self._passed_request_id if six.PY3 and isinstance(request_id, bytes): request_id = request_id.decode('utf-8') kwargs['headers']['X-Openstack-Request-ID'] = request_id res = super(RegistryClient, self).do_request(method, action, **kwargs) status = res.status request_id = res.getheader('x-openstack-request-id') if six.PY3 and isinstance(request_id, bytes): request_id = request_id.decode('utf-8') LOG.debug("Registry request %(method)s %(action)s HTTP %(status)s" " request id %(request_id)s", {'method': method, 'action': action, 'status': status, 'request_id': request_id}) # a 404 condition is not fatal, we shouldn't log at a fatal # level for it. except exception.NotFound: raise # The following exception logging should only really be used # in extreme and unexpected cases. except Exception as exc: with excutils.save_and_reraise_exception(): exc_name = exc.__class__.__name__ LOG.exception(_LE("Registry client request %(method)s " "%(action)s raised %(exc_name)s"), {'method': method, 'action': action, 'exc_name': exc_name}) return res
[docs] def get_images_detailed(self, **kwargs): """ Returns a list of detailed image data mappings from Registry :param filters: dict of keys & expected values to filter results :param marker: image id after which to start page :param limit: max number of images to return :param sort_key: results will be ordered by this image attribute :param sort_dir: direction in which to order results (asc, desc) """ params = self._extract_params(kwargs, images.SUPPORTED_PARAMS) res = self.do_request("GET", "/images/detail", params=params) image_list = jsonutils.loads(res.read())['images'] for image in image_list: image = self.decrypt_metadata(image) return image_list
[docs] def get_image(self, image_id): """Returns a mapping of image metadata from Registry.""" res = self.do_request("GET", "/images/%s" % image_id) data = jsonutils.loads(res.read())['image'] return self.decrypt_metadata(data)
[docs] def add_image(self, image_metadata): """ Tells registry about an image's metadata """ headers = { 'Content-Type': 'application/json', } if 'image' not in image_metadata: image_metadata = dict(image=image_metadata) encrypted_metadata = self.encrypt_metadata(image_metadata['image']) image_metadata['image'] = encrypted_metadata body = jsonutils.dump_as_bytes(image_metadata) res = self.do_request("POST", "/images", body=body, headers=headers) # Registry returns a JSONified dict(image=image_info) data = jsonutils.loads(res.read()) image = data['image'] return self.decrypt_metadata(image)
[docs] def update_image(self, image_id, image_metadata, purge_props=False, from_state=None): """ Updates Registry's information about an image """ if 'image' not in image_metadata: image_metadata = dict(image=image_metadata) encrypted_metadata = self.encrypt_metadata(image_metadata['image']) image_metadata['image'] = encrypted_metadata image_metadata['from_state'] = from_state body = jsonutils.dump_as_bytes(image_metadata) headers = { 'Content-Type': 'application/json', } if purge_props: headers["X-Glance-Registry-Purge-Props"] = "true" res = self.do_request("PUT", "/images/%s" % image_id, body=body, headers=headers) data = jsonutils.loads(res.read()) image = data['image'] return self.decrypt_metadata(image)
[docs] def delete_image(self, image_id): """ Deletes Registry's information about an image """ res = self.do_request("DELETE", "/images/%s" % image_id) data = jsonutils.loads(res.read()) image = data['image'] return image
[docs] def get_image_members(self, image_id): """Return a list of membership associations from Registry.""" res = self.do_request("GET", "/images/%s/members" % image_id) data = jsonutils.loads(res.read())['members'] return data
[docs] def get_member_images(self, member_id): """Return a list of membership associations from Registry.""" res = self.do_request("GET", "/shared-images/%s" % member_id) data = jsonutils.loads(res.read())['shared_images'] return data
[docs] def replace_members(self, image_id, member_data): """Replace registry's information about image membership.""" if isinstance(member_data, (list, tuple)): member_data = dict(memberships=list(member_data)) elif (isinstance(member_data, dict) and 'memberships' not in member_data): member_data = dict(memberships=[member_data]) body = jsonutils.dump_as_bytes(member_data) headers = {'Content-Type': 'application/json', } res = self.do_request("PUT", "/images/%s/members" % image_id, body=body, headers=headers) return self.get_status_code(res) == 204
[docs] def add_member(self, image_id, member_id, can_share=None): """Add to registry's information about image membership.""" body = None headers = {} # Build up a body if can_share is specified if can_share is not None: body = jsonutils.dump_as_bytes( dict(member=dict(can_share=can_share))) headers['Content-Type'] = 'application/json' url = "/images/%s/members/%s" % (image_id, member_id) res = self.do_request("PUT", url, body=body, headers=headers) return self.get_status_code(res) == 204
[docs] def delete_member(self, image_id, member_id): """Delete registry's information about image membership.""" res = self.do_request("DELETE", "/images/%s/members/%s" % (image_id, member_id)) return self.get_status_code(res) == 204

Project Source