Merge "Adds SSL configuration params to the client"

This commit is contained in:
Jenkins 2011-10-18 14:48:01 +00:00 committed by Gerrit Code Review
commit d3a31b372e
19 changed files with 1757 additions and 118 deletions

View File

@ -980,8 +980,13 @@ def get_client(options):
auth_url=os.getenv('OS_AUTH_URL'),
strategy=os.getenv('OS_AUTH_STRATEGY', 'noauth'))
use_ssl = (options.host.find('https') != -1 or (
creds['auth_url'] is not None and
creds['auth_url'].find('https') != -1))
return glance_client.Client(host=options.host, port=options.port,
auth_tok=options.auth_token, creds=creds)
use_ssl=use_ssl, auth_tok=options.auth_token,
creds=creds)
def create_options(parser):

View File

@ -63,7 +63,7 @@ if __name__ == '__main__':
conf, app = config.load_paste_app('glance-api', options, args)
server = wsgi.Server()
server.start(app, int(conf['bind_port']), conf['bind_host'])
server.start(app, int(conf['bind_port']), conf['bind_host'], conf)
server.wait()
except RuntimeError, e:
sys.exit("ERROR: %s" % e)

View File

@ -63,7 +63,7 @@ if __name__ == '__main__':
conf, app = config.load_paste_app('glance-registry', options, args)
server = wsgi.Server()
server.start(app, int(conf['bind_port']), conf['bind_host'])
server.start(app, int(conf['bind_port']), conf['bind_host'], conf)
server.wait()
except RuntimeError, e:
sys.exit("ERROR: %s" % e)

View File

@ -88,6 +88,80 @@ The filename that is searched for depends on the server application name. So,
if you are starting up the API server, ``glance-api.conf`` is searched for,
otherwise ``glance-registry.conf``.
Configuring Server Startup Options
----------------------------------
You can put the following options in the ``glance-api.conf`` and
``glance-registry.conf`` files, under the ``[DEFAULT]`` section. They enable
startup and binding behaviour for the API and registry servers, respectively.
* ``bind_host=ADDRESS``
The address of the host to bind to.
Optional. Default: ``0.0.0.0``
* ``bind_port=PORT``
The port the server should bind to.
Optional. Default: ``9191`` for the registry server, ``9292`` for the API server
* ``backlog=REQUESTS``
Number of backlog requests to configure the socket with.
Optional. Default: ``4096``
Configurating SSL Support
~~~~~~~~~~~~~~~~~~~~~~~~~
* ``cert_file=PATH``
Path to the the certificate file the server should use when binding to an
SSL-wrapped socket.
Optional. Default: not enabled.
* ``key_file=PATH``
Path to the the private key file the server should use when binding to an
SSL-wrapped socket.
Optional. Default: not enabled.
* ``registry_client_protocol=PROTOCOL``
If you run a secure Registry server, you need to set this value to ``https``
and also set ``registry_client_key_file`` and optionally
``registry_client_cert_file``.
Optional. Default: http
* ``registry_client_key_file=PATH``
The path to the key file to use in SSL connections to the
registry server, if any. Alternately, you may set the
``GLANCE_CLIENT_KEY_FILE`` environ variable to a filepath of the key file
Optional. Default: Not set.
* ``registry_client_cert_file=PATH``
Optional. Default: Not set.
The path to the cert file to use in SSL connections to the
registry server, if any. Alternately, you may set the
``GLANCE_CLIENT_CERT_FILE`` environ variable to a filepath of the cert file
* ``registry_client_ca_file=PATH``
Optional. Default: Not set.
The path to a Certifying Authority's cert file to use in SSL connections to the
registry server, if any. Alternately, you may set the
``GLANCE_CLIENT_CA_FILE`` environ variable to a filepath of the CA cert file
Configuring Logging in Glance
-----------------------------
@ -132,6 +206,12 @@ Defaults to ``%Y-%m-%d %H:%M:%S``. See the
`logging module <http://docs.python.org/library/logging.html>`_ documentation for
more information on setting this format string.
* ``log_use_syslog``
Use syslog logging functionality.
Defaults to False.
Configuring Glance Storage Backends
-----------------------------------

View File

@ -16,12 +16,6 @@ bind_host = 0.0.0.0
# Port the bind the API server to
bind_port = 9292
# Address to find the registry server
registry_host = 0.0.0.0
# Port the registry server is listening on
registry_port = 9191
# Log to this file. Make sure you do not set the same log
# file for both the API and registry servers!
log_file = /var/log/glance/api.log
@ -29,6 +23,44 @@ log_file = /var/log/glance/api.log
# Send logs to syslog (/dev/log) instead of to file specified by `log_file`
use_syslog = False
# Backlog requests when creating socket
backlog = 4096
# ================= SSL Options ===============================
# Certificate file to use when starting API server securely
# cert_file = /path/to/certfile
# Private key file to use when starting API server securely
# key_file = /path/to/keyfile
# ============ Registry Options ===============================
# Address to find the registry server
registry_host = 0.0.0.0
# Port the registry server is listening on
registry_port = 9191
# What protocol to use when connecting to the registry server?
# Set to https for secure HTTP communication
registry_client_protocol = http
# The path to the key file to use in SSL connections to the
# registry server, if any. Alternately, you may set the
# GLANCE_CLIENT_KEY_FILE environ variable to a filepath of the key file
# registry_client_key_file = /path/to/key/file
# The path to the cert file to use in SSL connections to the
# registry server, if any. Alternately, you may set the
# GLANCE_CLIENT_CERT_FILE environ variable to a filepath of the cert file
# registry_client_cert_file = /path/to/cert/file
# The path to the certifying authority cert file to use in SSL connections
# to the registry server, if any. Alternately, you may set the
# GLANCE_CLIENT_CA_FILE environ variable to a filepath of the CA cert file
# registry_client_ca_file = /path/to/ca/file
# ============ Notification System Options =====================
# Notifications can be sent when images are create, updated or deleted.

View File

@ -18,6 +18,9 @@ log_file = /var/log/glance/registry.log
# Send logs to syslog (/dev/log) instead of to file specified by `log_file`
use_syslog = False
# Backlog requests when creating socket
backlog = 4096
# SQLAlchemy connection string for the reference implementation
# registry server. Any valid SQLAlchemy connection string is fine.
# See: http://www.sqlalchemy.org/docs/05/reference/sqlalchemy/connections.html#sqlalchemy.create_engine
@ -40,6 +43,14 @@ api_limit_max = 1000
# default to `limit_param_default`
limit_param_default = 25
# ================= SSL Options ===============================
# Certificate file to use when starting registry server securely
# cert_file = /path/to/certfile
# Private key file to use when starting registry server securely
# key_file = /path/to/keyfile
[pipeline:glance-registry]
pipeline = context registryapp
# NOTE: use the following pipeline for keystone

View File

@ -37,7 +37,7 @@ class BaseController(object):
"""
context = request.context
try:
return registry.get_image_metadata(self.options, context, image_id)
return registry.get_image_metadata(context, image_id)
except exception.NotFound:
msg = _("Image with identifier %s not found") % image_id
logger.debug(msg)

View File

@ -82,6 +82,7 @@ class Controller(api.BaseController):
self.options = options
glance.store.create_stores(options)
self.notifier = notifier.Notifier(options)
registry.configure_registry_client(options)
def index(self, req):
"""
@ -108,8 +109,7 @@ class Controller(api.BaseController):
"""
params = self._get_query_params(req)
try:
images = registry.get_images_list(self.options, req.context,
**params)
images = registry.get_images_list(req.context, **params)
except exception.Invalid, e:
raise HTTPBadRequest(explanation="%s" % e)
@ -141,8 +141,7 @@ class Controller(api.BaseController):
"""
params = self._get_query_params(req)
try:
images = registry.get_images_detail(self.options, req.context,
**params)
images = registry.get_images_detail(req.context, **params)
# Strip out the Location attribute. Temporary fix for
# LP Bug #755916. This information is still coming back
# from the registry, since the API server still needs access
@ -304,9 +303,7 @@ class Controller(api.BaseController):
image_meta['size'] = image_meta.get('size', 0)
try:
image_meta = registry.add_image_metadata(self.options,
req.context,
image_meta)
image_meta = registry.add_image_metadata(req.context, image_meta)
return image_meta
except exception.Duplicate:
msg = (_("An image with identifier %s already exists")
@ -352,7 +349,7 @@ class Controller(api.BaseController):
image_id = image_meta['id']
logger.debug(_("Setting image %s to status 'saving'"), image_id)
registry.update_image_metadata(self.options, req.context, image_id,
registry.update_image_metadata(req.context, image_id,
{'status': 'saving'})
try:
logger.debug(_("Uploading image data for image %(image_id)s "
@ -387,8 +384,7 @@ class Controller(api.BaseController):
logger.debug(_("Updating image %(image_id)s data. "
"Checksum set to %(checksum)s, size set "
"to %(size)d"), locals())
registry.update_image_metadata(self.options, req.context,
image_id,
registry.update_image_metadata(req.context, image_id,
{'checksum': checksum,
'size': size})
self.notifier.info('image.upload', image_meta)
@ -435,10 +431,8 @@ class Controller(api.BaseController):
image_meta = {}
image_meta['location'] = location
image_meta['status'] = 'active'
return registry.update_image_metadata(self.options,
req.context,
image_id,
image_meta)
return registry.update_image_metadata(req.context, image_id,
image_meta)
def _kill(self, req, image_id):
"""
@ -447,9 +441,7 @@ class Controller(api.BaseController):
:param req: The WSGI/Webob Request object
:param image_id: Opaque image identifier
"""
registry.update_image_metadata(self.options,
req.context,
image_id,
registry.update_image_metadata(req.context, image_id,
{'status': 'killed'})
def _safe_kill(self, req, image_id):
@ -562,8 +554,7 @@ class Controller(api.BaseController):
raise HTTPConflict(_("Cannot upload to an unqueued image"))
try:
image_meta = registry.update_image_metadata(self.options,
req.context, id,
image_meta = registry.update_image_metadata(req.context, id,
image_meta, True)
if image_data is not None:
image_meta = self._upload_and_activate(req, image_meta)
@ -613,7 +604,7 @@ class Controller(api.BaseController):
if image['location']:
schedule_delete_from_backend(image['location'], self.options,
req.context, id)
registry.delete_image_metadata(self.options, req.context, id)
registry.delete_image_metadata(req.context, id)
except exception.NotFound, e:
msg = ("Failed to find image to delete: %(e)s" % locals())
for line in msg.split('\n'):

View File

@ -32,8 +32,7 @@ class Controller(object):
]}
"""
try:
members = registry.get_image_members(self.options, req.context,
image_id)
members = registry.get_image_members(req.context, image_id)
except exception.NotFound:
msg = _("Image with identifier %s not found") % image_id
logger.debug(msg)
@ -54,7 +53,7 @@ class Controller(object):
raise webob.exc.HTTPUnauthorized(_("No authenticated user"))
try:
registry.delete_member(self.options, req.context, image_id, id)
registry.delete_member(req.context, image_id, id)
except exception.NotFound, e:
msg = "%s" % e
logger.debug(msg)
@ -93,8 +92,7 @@ class Controller(object):
if body and 'member' in body and 'can_share' in body['member']:
can_share = bool(body['member']['can_share'])
try:
registry.add_member(self.options, req.context, image_id, id,
can_share)
registry.add_member(req.context, image_id, id, can_share)
except exception.NotFound, e:
msg = "%s" % e
logger.debug(msg)
@ -122,8 +120,7 @@ class Controller(object):
raise webob.exc.HTTPUnauthorized(_("No authenticated user"))
try:
registry.replace_members(self.options, req.context,
image_id, body)
registry.replace_members(req.context, image_id, body)
except exception.NotFound, e:
msg = "%s" % e
logger.debug(msg)
@ -149,7 +146,7 @@ class Controller(object):
]}
"""
try:
members = registry.get_member_images(self.options, req.context, id)
members = registry.get_member_images(req.context, id)
except exception.NotFound, e:
msg = "%s" % e
logger.debug(msg)

View File

@ -1,9 +1,32 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010-2011 OpenStack, LLC
# 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.
# HTTPSClientAuthConnection code comes courtesy of ActiveState website:
# http://code.activestate.com/recipes/
# 577548-https-httplib-client-connection-with-certificate-v/
import httplib
import logging
import socket
import os
import urllib
import urlparse
from eventlet.green import socket, ssl
# See http://code.google.com/p/python-nose/issues/detail?id=373
# The code below enables glance.client standalone to work with i18n _() blocks
import __builtin__
@ -43,6 +66,48 @@ class ImageBodyIterator(object):
break
class HTTPSClientAuthConnection(httplib.HTTPSConnection):
"""
Class to make a HTTPS connection, with support for
full client-based SSL Authentication
:see http://code.activestate.com/recipes/
577548-https-httplib-client-connection-with-certificate-v/
"""
def __init__(self, host, port, key_file, cert_file,
ca_file, timeout=None):
httplib.HTTPSConnection.__init__(self, host, port, key_file=key_file,
cert_file=cert_file)
self.key_file = key_file
self.cert_file = cert_file
self.ca_file = ca_file
self.timeout = timeout
def connect(self):
"""
Connect to a host on a given (SSL) port.
If ca_file is pointing somewhere, use it to check Server Certificate.
Redefined/copied and extended from httplib.py:1105 (Python 2.6.x).
This is needed to pass cert_reqs=ssl.CERT_REQUIRED as parameter to
ssl.wrap_socket(), which forces SSL to check server certificate against
our client certificate.
"""
sock = socket.create_connection((self.host, self.port), self.timeout)
if self._tunnel_host:
self.sock = sock
self._tunnel()
# If there's no CA File, don't force Server Certificate Check
if self.ca_file:
self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file,
ca_certs=self.ca_file,
cert_reqs=ssl.CERT_REQUIRED)
else:
self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file,
cert_reqs=ssl.CERT_NONE)
class BaseClient(object):
"""A base client class"""
@ -52,7 +117,8 @@ class BaseClient(object):
DEFAULT_DOC_ROOT = None
def __init__(self, host, port=None, use_ssl=False, auth_tok=None,
creds=None, doc_root=None):
creds=None, doc_root=None,
key_file=None, cert_file=None, ca_file=None):
"""
Creates a new client to some service.
@ -62,6 +128,23 @@ class BaseClient(object):
:param auth_tok: The auth token to pass to the server
:param creds: The credentials to pass to the auth plugin
:param doc_root: Prefix for all URLs we request from host
:param key_file: Optional PEM-formatted file that contains the private
key.
If use_ssl is True, and this param is None (the
default), then an environ variable
GLANCE_CLIENT_KEY_FILE is looked for. If no such
environ variable is found, ClientConnectionError
will be raised.
:param cert_file: Optional PEM-formatted certificate chain file.
If use_ssl is True, and this param is None (the
default), then an environ variable
GLANCE_CLIENT_CERT_FILE is looked for. If no such
environ variable is found, ClientConnectionError
will be raised.
:param ca_file: Optional CA cert file to use in SSL connections
If use_ssl is True, and this param is None (the
default), then an environ variable
GLANCE_CLIENT_CA_FILE is looked for.
"""
self.host = host
self.port = port or self.DEFAULT_PORT
@ -69,8 +152,48 @@ class BaseClient(object):
self.auth_tok = auth_tok
self.creds = creds or {}
self.connection = None
self.doc_root = self.DEFAULT_DOC_ROOT if doc_root is None else doc_root
# doc_root can be a nullstring, which is valid, and why we
# cannot simply do doc_root or self.DEFAULT_DOC_ROOT below.
self.doc_root = (doc_root if doc_root is not None
else self.DEFAULT_DOC_ROOT)
self.auth_plugin = self.make_auth_plugin(self.creds)
self.connect_kwargs = {}
if use_ssl:
if not key_file:
if not os.environ.get('GLANCE_CLIENT_KEY_FILE'):
msg = _("You have selected to use SSL in connecting, "
"however you have failed to supply either a "
"key_file parameter or set the "
"GLANCE_CLIENT_KEY_FILE environ variable")
raise exception.ClientConnectionError(msg)
key_file = os.environ.get('GLANCE_CLIENT_KEY_FILE')
if not os.path.exists(key_file):
msg = _("The key file you specified %s does not "
"exist") % key_file
raise exception.ClientConnectionError(msg)
self.connect_kwargs['key_file'] = key_file
if not cert_file:
if not os.environ.get('GLANCE_CLIENT_CERT_FILE'):
msg = _("You have selected to use SSL in connecting, "
"however you have failed to supply either a "
"cert_file parameter or set the "
"GLANCE_CLIENT_CERT_FILE environ variable")
raise exception.ClientConnectionError(msg)
cert_file = os.environ.get('GLANCE_CLIENT_CERT_FILE')
if not os.path.exists(cert_file):
msg = _("The key file you specified %s does not "
"exist") % cert_file
raise exception.ClientConnectionError(msg)
self.connect_kwargs['cert_file'] = cert_file
if not ca_file:
ca_file = os.environ.get('GLANCE_CLIENT_CA_FILE')
self.connect_kwargs['ca_file'] = ca_file
def set_auth_token(self, auth_tok):
"""
@ -112,7 +235,7 @@ class BaseClient(object):
Returns the proper connection type
"""
if self.use_ssl:
return httplib.HTTPSConnection
return HTTPSClientAuthConnection
else:
return httplib.HTTPConnection
@ -186,7 +309,7 @@ class BaseClient(object):
if 'x-auth-token' not in headers and self.auth_tok:
headers['x-auth-token'] = self.auth_tok
c = connection_type(self.host, self.port)
c = connection_type(self.host, self.port, **self.connect_kwargs)
if self.doc_root:
action = '/'.join([self.doc_root, action.lstrip('/')])

View File

@ -107,6 +107,11 @@ class InvalidContentType(GlanceException):
message = _("Invalid content type %(content_type)s")
class BadRegistryConnectionConfiguration(GlanceException):
message = _("Registry was not configured correctly on API server. "
"Reason: %(reason)s")
class BadStoreConfiguration(GlanceException):
message = _("Store %(store_name)s could not be configured correctly. "
"Reason: %(reason)s")
@ -122,4 +127,4 @@ class StoreAddDisabled(GlanceException):
class InvalidNotifierStrategy(GlanceException):
message = "'%(strategy)s' is not an available notifier strategy."
message = _("'%(strategy)s' is not an available notifier strategy.")

View File

@ -21,14 +21,15 @@
Utility methods for working with WSGI servers
"""
import datetime
import json
import logging
import sys
import datetime
import time
import eventlet
from eventlet.green import socket, ssl
import eventlet.wsgi
eventlet.patcher.monkey_patch(all=False, socket=True)
import routes
import routes.middleware
import webob.dec
@ -48,10 +49,57 @@ class WritableLogger(object):
self.logger.log(self.level, msg.strip("\n"))
def run_server(application, port):
"""Run a WSGI server with the given application."""
sock = eventlet.listen(('0.0.0.0', port))
eventlet.wsgi.server(sock, application)
def get_socket(host, port, conf):
"""
Bind socket to bind ip:port in conf
note: Mostly comes from Swift with a few small changes...
:param host: Host to bind to
:param port: Port to bind to
:param conf: Configuration dict to read settings from
:returns : a socket object as returned from socket.listen or
ssl.wrap_socket if conf specifies cert_file
"""
bind_addr = (host, port)
# TODO(jaypipes): eventlet's greened socket module does not actually
# support IPv6 in getaddrinfo(). We need to get around this in the
# future or monitor upstream for a fix
address_family = [addr[0] for addr in socket.getaddrinfo(bind_addr[0],
bind_addr[1], socket.AF_UNSPEC, socket.SOCK_STREAM)
if addr[0] in (socket.AF_INET, socket.AF_INET6)][0]
backlog = int(conf.get('backlog', 4096))
cert_file = conf.get('cert_file')
key_file = conf.get('key_file')
use_ssl = cert_file or key_file
if use_ssl and (not cert_file or not key_file):
raise RuntimeError(_("When running server in SSL mode, you must "
"specify both a cert_file and key_file "
"option value in your configuration file"))
sock = None
retry_until = time.time() + 30
while not sock and time.time() < retry_until:
try:
sock = eventlet.listen(bind_addr, backlog=backlog,
family=address_family)
if use_ssl:
sock = ssl.wrap_socket(sock, certfile=cert_file,
keyfile=key_file)
except socket.error, err:
if err.args[0] != errno.EADDRINUSE:
raise
sleep(0.1)
if not sock:
raise RuntimeError(_("Could not bind to %s:%s after trying for 30 "
"seconds") % bind_addr)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# in my experience, sockets can hang around forever without keepalive
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 600)
return sock
class Server(object):
@ -60,9 +108,17 @@ class Server(object):
def __init__(self, threads=1000):
self.pool = eventlet.GreenPool(threads)
def start(self, application, port, host='0.0.0.0', backlog=128):
"""Run a WSGI server with the given application."""
socket = eventlet.listen((host, port), backlog=backlog)
def start(self, application, port, host='0.0.0.0', conf=None):
"""
Run a WSGI server with the given application.
:param application: The application to run in the WSGI server
:param port: Port to bind to
:param host: Host to bind to
:param conf: Mapping of configuration options
"""
conf = conf or {}
socket = get_socket(host, port, conf)
self.pool.spawn_n(self._run, application, socket)
def wait(self):

View File

@ -21,103 +21,111 @@ Registry API
import logging
from glance.common import config
from glance.common import exception
from glance.registry import client
logger = logging.getLogger('glance.registry')
def get_registry_client(options, context):
host = options['registry_host']
port = int(options['registry_port'])
return client.RegistryClient(host, port, auth_tok=context.auth_tok)
_CLIENT_HOST = None
_CLIENT_PORT = None
_CLIENT_KWARGS = {}
def get_images_list(options, context, **kwargs):
c = get_registry_client(options, context)
def configure_registry_client(options):
"""
Sets up a registry client for use in registry lookups
:param options: Configuration options coming from controller
"""
global _CLIENT_KWARGS, _CLIENT_HOST, _CLIENT_PORT
try:
host = options['registry_host']
port = int(options['registry_port'])
except (TypeError, ValueError):
msg = _("Configuration option was not valid")
logger.error(msg)
raise exception.BadRegistryConnectionConfiguration(msg)
except IndexError:
msg = _("Could not find required configuration option")
logger.error(msg)
raise exception.BadRegistryConnectionConfiguration(msg)
use_ssl = config.get_option(options, 'registry_client_protocol',
default='http').lower() == 'https'
key_file = options.get('registry_client_key_file')
cert_file = options.get('registry_client_cert_file')
ca_file = options.get('registry_client_ca_file')
_CLIENT_HOST = host
_CLIENT_PORT = port
_CLIENT_KWARGS = {'use_ssl': use_ssl,
'key_file': key_file,
'cert_file': cert_file,
'ca_file': ca_file}
def get_registry_client(cxt):
global _CLIENT_KWARGS, _CLIENT_HOST, _CLIENT_PORT
kwargs = _CLIENT_KWARGS.copy()
kwargs['auth_tok'] = cxt.auth_tok
return client.RegistryClient(_CLIENT_HOST, _CLIENT_PORT, **kwargs)
def get_images_list(context, **kwargs):
c = get_registry_client(context)
return c.get_images(**kwargs)
def get_images_detail(options, context, **kwargs):
c = get_registry_client(options, context)
def get_images_detail(context, **kwargs):
c = get_registry_client(context)
return c.get_images_detailed(**kwargs)
def get_image_metadata(options, context, image_id):
c = get_registry_client(options, context)
def get_image_metadata(context, image_id):
c = get_registry_client(context)
return c.get_image(image_id)
def add_image_metadata(options, context, image_meta):
if options['debug']:
logger.debug(_("Adding image metadata..."))
_debug_print_metadata(image_meta)
c = get_registry_client(options, context)
new_image_meta = c.add_image(image_meta)
if options['debug']:
logger.debug(_("Returned image metadata from call to "
"RegistryClient.add_image():"))
_debug_print_metadata(new_image_meta)
return new_image_meta
def add_image_metadata(context, image_meta):
logger.debug(_("Adding image metadata..."))
c = get_registry_client(context)
return c.add_image(image_meta)
def update_image_metadata(options, context, image_id, image_meta,
def update_image_metadata(context, image_id, image_meta,
purge_props=False):
if options['debug']:
logger.debug(_("Updating image metadata for image %s..."), image_id)
_debug_print_metadata(image_meta)
c = get_registry_client(options, context)
new_image_meta = c.update_image(image_id, image_meta, purge_props)
if options['debug']:
logger.debug(_("Returned image metadata from call to "
"RegistryClient.update_image():"))
_debug_print_metadata(new_image_meta)
return new_image_meta
logger.debug(_("Updating image metadata for image %s..."), image_id)
c = get_registry_client(context)
return c.update_image(image_id, image_meta, purge_props)
def delete_image_metadata(options, context, image_id):
def delete_image_metadata(context, image_id):
logger.debug(_("Deleting image metadata for image %s..."), image_id)
c = get_registry_client(options, context)
c = get_registry_client(context)
return c.delete_image(image_id)
def get_image_members(options, context, image_id):
c = get_registry_client(options, context)
def get_image_members(context, image_id):
c = get_registry_client(context)
return c.get_image_members(image_id)
def get_member_images(options, context, member_id):
c = get_registry_client(options, context)
def get_member_images(context, member_id):
c = get_registry_client(context)
return c.get_member_images(member_id)
def replace_members(options, context, image_id, member_data):
c = get_registry_client(options, context)
def replace_members(context, image_id, member_data):
c = get_registry_client(context)
return c.replace_members(image_id, member_data)
def add_member(options, context, image_id, member_id, can_share=None):
c = get_registry_client(options, context)
def add_member(context, image_id, member_id, can_share=None):
c = get_registry_client(context)
return c.add_member(image_id, member_id, can_share=can_share)
def delete_member(options, context, image_id, member_id):
c = get_registry_client(options, context)
def delete_member(context, image_id, member_id):
c = get_registry_client(context)
return c.delete_member(image_id, member_id)
def _debug_print_metadata(image_meta):
data = image_meta.copy()
properties = data.pop('properties', None)
for key, value in sorted(data.items()):
logger.debug(" %(key)20s: %(value)s" % locals())
if properties:
logger.debug(_(" %d custom properties..."),
len(properties))
for key, value in properties.items():
logger.debug(" %(key)20s: %(value)s" % locals())

View File

@ -161,7 +161,7 @@ def schedule_delete_from_backend(uri, options, context, image_id, **kwargs):
use_delay = config.get_option(options, 'delayed_delete', type='bool',
default=False)
if not use_delay:
registry.update_image_metadata(options, context, image_id,
registry.update_image_metadata(context, image_id,
{'status': 'deleted'})
try:
return delete_from_backend(uri, **kwargs)
@ -186,5 +186,5 @@ def schedule_delete_from_backend(uri, options, context, image_id, **kwargs):
os.chmod(file_path, 0600)
os.utime(file_path, (delete_time, delete_time))
registry.update_image_metadata(options, context, image_id,
registry.update_image_metadata(context, image_id,
{'status': 'pending_delete'})

View File

@ -146,6 +146,8 @@ class ApiServer(Server):
super(ApiServer, self).__init__(test_dir, port)
self.server_name = 'api'
self.default_store = 'file'
self.key_file = ""
self.cert_file = ""
self.image_dir = os.path.join(self.test_dir,
"images")
self.pid_file = os.path.join(self.test_dir,
@ -177,6 +179,8 @@ filesystem_store_datadir=%(image_dir)s
default_store = %(default_store)s
bind_host = 0.0.0.0
bind_port = %(bind_port)s
key_file = %(key_file)s
cert_file = %(cert_file)s
registry_host = 0.0.0.0
registry_port = %(registry_port)s
log_file = %(log_file)s
@ -306,6 +310,7 @@ class FunctionalTest(unittest.TestCase):
self.test_id = random.randint(0, 100000)
self.test_dir = os.path.join("/", "tmp", "test.%d" % self.test_id)
self.api_protocol = 'http'
self.api_port = get_unused_port()
self.registry_port = get_unused_port()

File diff suppressed because it is too large Load Diff

View File

@ -19,6 +19,7 @@ import datetime
import json
import os
import StringIO
import tempfile
import unittest
import stubout
@ -48,6 +49,78 @@ class TestBadClients(unittest.TestCase):
c.get_image,
1)
def test_ssl_no_key_file(self):
"""
Test that when doing SSL connection, a key file is
required
"""
try:
c = client.Client("0.0.0.0", use_ssl=True)
except exception.ClientConnectionError:
return
self.fail("Did not raise ClientConnectionError")
def test_ssl_non_existing_key_file(self):
"""
Test that when doing SSL connection, a key file is
required to exist
"""
try:
c = client.Client("0.0.0.0", use_ssl=True,
key_file='nonexistingfile')
except exception.ClientConnectionError:
return
self.fail("Did not raise ClientConnectionError")
def test_ssl_no_cert_file(self):
"""
Test that when doing SSL connection, a cert file is
required
"""
try:
with tempfile.NamedTemporaryFile() as key_file:
key_file.write("bogus")
key_file.flush()
c = client.Client("0.0.0.0", use_ssl=True,
key_file=key_file.name)
except exception.ClientConnectionError:
return
self.fail("Did not raise ClientConnectionError")
def test_ssl_non_existing_cert_file(self):
"""
Test that when doing SSL connection, a cert file is
required to exist
"""
try:
with tempfile.NamedTemporaryFile() as key_file:
key_file.write("bogus")
key_file.flush()
c = client.Client("0.0.0.0", use_ssl=True,
key_file=key_file.name,
cert_file='nonexistingfile')
except exception.ClientConnectionError:
return
self.fail("Did not raise ClientConnectionError")
def test_ssl_optional_ca_file(self):
"""
Test that when doing SSL connection, a cert file and key file are
required to exist, but a CA file is optional.
"""
try:
with tempfile.NamedTemporaryFile() as key_file:
key_file.write("bogus")
key_file.flush()
with tempfile.NamedTemporaryFile() as cert_file:
cert_file.write("bogus")
cert_file.flush()
c = client.Client("0.0.0.0", use_ssl=True,
key_file=key_file.name,
cert_file=cert_file.name)
except exception.ClientConnectionError:
self.fail("Raised ClientConnectionError when it should not")
class TestRegistryClient(unittest.TestCase):

View File

@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC4DCCAcigAwIBAgIBATANBgkqhkiG9w0BAQUFADATMREwDwYDVQQDEwhNeVRl
c3RDQTAeFw0xMTA3MjExNTA1NDZaFw0xMjA3MjAxNTA1NDZaMCMxEDAOBgNVBAMT
B2FobWFkcGMxDzANBgNVBAoTBnNlcnZlcjCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAO9zpczf+W4DoK2z8oFbsZfbvz1y/yQOnrQYvb1zv1IieT+QA+Ti
N64N/sgR/cR7YEIXDnhij8yE1JTWMk1W6g4m7TGacUMXD/WAcsTM7kRol/FVksdn
F51qxCYqWUPQ3xiTfBg2SJWvJCUGowvz06xh8JeOEXLbALC5xrzrM3hclpdbrKYE
oe8kikI/K0TKpu52VJJrTBGPHMsw+eIqL2Ix5pWHh7DPfjBiiG7khsJxN7xSqLbX
LrhDi24nTM9pndaqABkmPYQ9qd11SoAUB82QAAGj8A7iR/DnAzAfJl1usvQp+Me6
sR3TPY27zifBbD04tiROi1swM/1xRH7qOpkCAwEAAaMvMC0wCQYDVR0TBAIwADAL
BgNVHQ8EBAMCBSAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQEFBQAD
ggEBAIJvnQjkEDFvLT7NiyFrO938BuxdQH2mX2N7Fz86myZLcGpr5NCdLvT9tD9f
6KqrR8e839pYVPZY80cBpGTmRmzW3xLsmGCFHPHt4p1tkqSP1R5iLzKDe8jawHhD
sch8P9URRhW9ZgBzA4xiv9FnIxZ70uDr04uX/sR/j41HGBS8YW6dJvr9Y2SpGqSS
rR2btnNZ945dau6CPLRNd9Fls3Qjx03PnsmZ5ikSuV0pT1sPQmhhw7rBYV/b2ff+
z/4cRtZrR00NVc74IEXLoujIjUUpFC83in10PKQmAvKYTeTdXns48eC4Cwqe8eaM
N0YtxqQvSTsUo6vPM28NR99Fbow=
-----END CERTIFICATE-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA73OlzN/5bgOgrbPygVuxl9u/PXL/JA6etBi9vXO/UiJ5P5AD
5OI3rg3+yBH9xHtgQhcOeGKPzITUlNYyTVbqDibtMZpxQxcP9YByxMzuRGiX8VWS
x2cXnWrEJipZQ9DfGJN8GDZIla8kJQajC/PTrGHwl44RctsAsLnGvOszeFyWl1us
pgSh7ySKQj8rRMqm7nZUkmtMEY8cyzD54iovYjHmlYeHsM9+MGKIbuSGwnE3vFKo
ttcuuEOLbidMz2md1qoAGSY9hD2p3XVKgBQHzZAAAaPwDuJH8OcDMB8mXW6y9Cn4
x7qxHdM9jbvOJ8FsPTi2JE6LWzAz/XFEfuo6mQIDAQABAoIBAQC6BwvBbiQXH0Re
jtWRQA5p3zPk5olnluAfJLWMEPeLNPMjuZv83u7JD2BoSOnxErTGw6jfSBtVlcCd
3Qb5ZNOzqPRPvB/QMoOYhHElidx2UxfwSz4cInCLQJ4g1HfDIuuf6TzYhpu/hnC7
Pzu+lnBVlUVYSOwvYgtYQQwwSz4Se8Mwoh2OOOTgn4wvZDbiDrMvv2UUUL1nyvAB
FdaywbD/dW8TqbnPSoj8uipq0yugDOyzzNQDM6+rN69qNrD2/vYaAsSaWxISLDqs
fEI4M1+PeDmLigQeA7V3kEZWWDwHbS92LL8BxEmmeeHN5xwZyC8xqa1jt2A/S6Af
Q7gkpG6BAoGBAP+jFn7HCCi/Lc+YEZO0km7fvfR48M6QW3ar+b1aQywJWJhbtU9E
eoX1IcLxgce3+mUO05hGz3Rvz5JSDbmWXd6GTVsMRZqJeeCKbw9xirp5i4JjLzc8
Vu2oOJhqtAa88FgpZJ3iPIrT38UBpmnrvv1nb2ZNMdZnTNhtj5WByLFpAoGBAO/K
rVuvMq370P69Lo+iAr6+t4vwpF6pC/06B+OT5vldoiF57dOjFwndAKs9HCk9rS0/
jTvo0a1tS9yU20cFLXMN98zho3BYs4BlEKpNwVmpopxcfGV6dbwka7delAEVZzyN
TDW2P5Gyq9sYys+2ldvT2zTK8hHXZSh5JAp3V+mxAoGAC6G6Fk6sGl6IkReURSpE
N3NKy2LtYhjDcKTmmi0PPWO3ekdB+rdc89dxj9M5WoMOi6afDiC6s8uaoEfHhBhJ
cSSfRHNMf3md6A+keglqjI2XQXmN3m+KbQnoeVbxlhTmwrwvbderdY2qcuZeUhd9
+z3HndoJWH4eywJBNEZRgXECgYEAjtTeEDe6a1IMuj/7xQiOtAmsEQolDlGJV6vC
WTeXJEA2u9QB6sdBiNmAdX9wD8yyI7qwKNhUVQY+YsS0HIij+t1+FibtEJV1Tmxk
0dyA6CSYPKUGX/fiu0/CbbZDWKXkGXhcxb2p/eI8ZcRNwg4TE58M+lRMfn4bvlDy
O928mvECgYEA18MfGUZENZmC9ismsqrr9uVevfB08U5b+KRjSOyI2ZwOXnzcvbc3
zt9Tp35bcpQMAxPVT2B5htXeXqhUAJMkFEajpNZGDEKlCRB2XvMeA1Dn5fSk2dBB
ADeqQczoXT2+VgXLxRJJPucYCzi3kzo0OBUsHc9Z/HZNyr8LrUgd5lI=
-----END RSA PRIVATE KEY-----