From 36f7f33086565776e07dc6fa9d48a78dfdbc1100 Mon Sep 17 00:00:00 2001 From: John Wood Date: Fri, 12 Apr 2013 17:44:03 -0500 Subject: [PATCH] Cleaned up repo/model/config files to be more like Glance; --- .gitignore | 2 + barbican/api/app.py | 14 +- barbican/api/resources.py | 165 ++--------- barbican/common/__init__.py | 0 barbican/common/config.py | 17 ++ barbican/common/exception.py | 273 ++++++++++++++++++ barbican/config.py | 22 -- barbican/model/models.py | 154 ++++++++++ barbican/model/repositories.py | 39 ++- barbican/model/secret.py | 42 --- barbican/model/tenant.py | 86 ------ barbican/model/util.py | 33 --- .../base.py => openstack/common/uuidutils.py} | 30 +- barbican/tests/api/resources_test.py | 30 +- barbican/tests/config_test.py | 16 +- etc/{barbican => }/barbican-api-paste.ini | 0 etc/barbican-api.conf | 90 ++++++ etc/{barbican => }/barbican-api.ini | 0 etc/barbican/barbican.conf | 8 - etc/barbican/config.py | 9 - openstack-common.conf | 2 +- uwsgi.ini | 4 +- 22 files changed, 649 insertions(+), 387 deletions(-) create mode 100644 barbican/common/__init__.py create mode 100644 barbican/common/config.py create mode 100644 barbican/common/exception.py delete mode 100644 barbican/config.py create mode 100644 barbican/model/models.py delete mode 100644 barbican/model/secret.py delete mode 100644 barbican/model/tenant.py delete mode 100644 barbican/model/util.py rename barbican/{model/base.py => openstack/common/uuidutils.py} (54%) rename etc/{barbican => }/barbican-api-paste.ini (100%) create mode 100644 etc/barbican-api.conf rename etc/{barbican => }/barbican-api.ini (100%) delete mode 100644 etc/barbican/barbican.conf delete mode 100644 etc/barbican/config.py diff --git a/.gitignore b/.gitignore index 7c4fdc975..fb8695b52 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.venv +*.sqlite *.py[cod] # C extensions diff --git a/barbican/api/app.py b/barbican/api/app.py index 2548635d0..62baf6c1b 100644 --- a/barbican/api/app.py +++ b/barbican/api/app.py @@ -21,20 +21,20 @@ import falcon from barbican.api.resources import VersionResource from barbican.api.resources import TenantsResource, TenantResource -from barbican.api.resources import SecretsResource, SecretResource +# TBD: from barbican.api.resources import SecretsResource, SecretResource # Resources VERSIONS = VersionResource() TENANTS = TenantsResource() TENANT = TenantResource() -SECRETS = SecretsResource() -SECRET = SecretResource() +# TBD: SECRETS = SecretsResource() +# TBD: SECRET = SecretResource() # Routing application = falcon.API() api = application api.add_route('/', VERSIONS) -api.add_route('/tenant', TENANTS) -api.add_route('/{tenant_id}', TENANT) -api.add_route('/{tenant_id}/secrets', SECRETS) -api.add_route('/{tenant_id}/secrets/{secret_id}', SECRET) +api.add_route('/tenants', TENANTS) +api.add_route('/tenants/{tenant_id}', TENANT) +# TBD: api.add_route('/{tenant_id}/secrets', SECRETS) +# TBD: api.add_route('/{tenant_id}/secrets/{secret_id}', SECRET) diff --git a/barbican/api/resources.py b/barbican/api/resources.py index d12734f29..a561f1575 100644 --- a/barbican/api/resources.py +++ b/barbican/api/resources.py @@ -4,8 +4,11 @@ import logging from barbican.version import __version__ from barbican.api import ApiResource, load_body, abort -from barbican.model.tenant import Tenant, Secret -import barbican.model.repositories +from barbican.model.models import Tenant +from barbican.model.repositories import TenantRepo +from barbican.common import config + +LOG = logging.getLogger(__name__) def _tenant_not_found(): abort(falcon.HTTP_404, 'Unable to locate tenant.') @@ -17,15 +20,10 @@ def _tenant_already_exists(): def _secret_not_found(): abort(falcon.HTTP_400, 'Unable to locate secret profile.') - - -def format_tenant(tenant): - if not isinstance(tenant, dict): - tenant = tenant.__dict__ - - return {'id': tenant['id'], - 'tenant_id': tenant['tenant_id']} - + +def json_handler(obj): + """Convert objects into json-friendly equivalents.""" + return obj.isoformat() if hasattr(obj, 'isoformat') else obj class VersionResource(ApiResource): @@ -37,149 +35,50 @@ class VersionResource(ApiResource): class TenantsResource(ApiResource): - def __init__(self, db_session): - self.repo = TenantRepo() + def __init__(self, tenant_repo=None): + LOG.debug('Creating TenantsResource') + self.repo = tenant_repo or TenantRepo() def on_post(self, req, resp): body = load_body(req) + print 'Start on_post...%s' % body username = body['username'] - logging.debug('Username is {0}'.format(username)) + # LOG.debug('Username is {0}'.format(username)) + print 'Username is %s' % username - tenant = self.repo.find_by_name(username, False) + tenant = self.repo.find_by_name(name=username, suppress_exception=True) if tenant: abort(falcon.HTTP_400, 'Tenant with username {0} ' 'already exists'.format(username)) - new_tenant = Tenant(username) - self.db.add(new_tenant) - self.db.commit() + new_tenant = Tenant() + new_tenant.username = username + self.repo.create_from(new_tenant) + + print '...post create from' resp.status = falcon.HTTP_201 - resp.set_header('Location', '/v1/{0}'.format(new_tenant.id)) + resp.set_header('Location', '/{0}'.format(new_tenant.id)) + # TBD: Generate URL... + url = 'http://localhost:8080:/tenants/%s' % new_tenant.id + resp.body = json.dumps({'ref': url}) class TenantResource(ApiResource): - def __init__(self, db_session): - self.db = db_session + def __init__(self, tenant_repo=None): + self.repo = tenant_repo or TenantRepo() def on_get(self, req, resp, tenant_id): - tenant = find_tenant(self.db, id=tenant_id, - when_not_found=_tenant_not_found) + tenant = self.repo.get(entity_id=tenant_id) resp.status = falcon.HTTP_200 - resp.body = json.dumps(tenant.format()) + resp.body = json.dumps(tenant.to_dict_fields(), default=json_handler) def on_delete(self, req, resp, tenant_id): - tenant = find_tenant(self.db, id=tenant_id, - when_not_found=_tenant_not_found) + tenant = self.repo.get(entity_id=tenant_id) - self.db.delete(tenant) - self.db.commit() + self.repo.delete_entity(tenant) - resp.status = falcon.HTTP_200 - - -class SecretsResource(ApiResource): - - def __init__(self, db_session): - self.db = db_session - - def on_get(self, req, resp, tenant_id): - tenant = find_tenant(self.db, id=tenant_id, - when_not_found=_tenant_not_found) - - resp.status = falcon.HTTP_200 - - #jsonify a list of formatted secrets - resp.body = json.dumps([s.format() for s in tenant.secrets]) - - def on_post(self, req, resp, tenant_id): - tenant = find_tenant(self.db, id=tenant_id, - when_not_found=_tenant_not_found) - - body = load_body(req) - secret_name = body['name'] - - # Check if the tenant already has a secret with this name - for secret in tenant.secrets: - if secret.name == secret_name: - abort(falcon.HTTP_400, - 'Secret with name {0} already exists.'.format( - secret.name, secret.id)) - - # Create the new secret - new_secret = Secret(tenant.id, secret_name) - tenant.secrets.append(new_secret) - - self.db.add(new_secret) - self.db.commit() - - resp.status = falcon.HTTP_201 - resp.set_header('Location', - '/v1/{0}/secrets/{1}' - .format(tenant_id, new_secret.id)) - - -class SecretResource(ApiResource): - - def __init__(self, db_session): - self.db = db_session - - def on_get(self, req, resp, tenant_id, secret_id): - #verify the tenant exists - tenant = find_tenant(self.db, tenant_id=tenant_id, - when_not_found=_tenant_not_found) - - #verify the secret exists - secret = find_secret(self.db, id=secret_id, - when_not_found=_secret_not_found) - - #verify the secret belongs to the tenant - if not secret in tenant.secrets: - _secret_not_found() - - resp.status = falcon.HTTP_200 - resp.body = json.dumps(secret.format()) - - def on_put(self, req, resp, tenant_id, secret_id): - #verify the tenant exists - tenant = find_tenant(self.db, tenant_id=tenant_id, - when_not_found=_tenant_not_found) - - #verify the secret exists - secret = find_secret(self.db, id=secret_id, - when_not_found=_secret_not_found) - - #verify the secret belongs to the tenant - if not secret in tenant.secrets: - _secret_not_found() - - #load the message - body = load_body(req) - - #if attributes are present in message, update the secret - if 'name' in body.keys(): - secret.name = body['name'] - - self.db.commit() - resp.status = falcon.HTTP_200 - - def on_delete(self, req, resp, tenant_id, secret_id): - #verify the tenant exists - tenant = find_tenant(self.db, tenant_id=tenant_id, - when_not_found=_tenant_not_found) - - #verify the secret exists - secret = find_secret(self.db, id=secret_id, - when_not_found=_secret_not_found) - - #verify the secret belongs to the tenant - if not secret in tenant.secrets: - _secret_not_found() - - self.db.delete(secret) - self.db.commit() - - resp.status = falcon.HTTP_200 + resp.status = falcon.HTTP_200 \ No newline at end of file diff --git a/barbican/common/__init__.py b/barbican/common/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/barbican/common/config.py b/barbican/common/config.py new file mode 100644 index 000000000..842e9d235 --- /dev/null +++ b/barbican/common/config.py @@ -0,0 +1,17 @@ +import os +from oslo.config import cfg +from barbican.openstack.common import log + +# Ensure the local python config path is on the list to pull config info from +CONF_FILES = cfg.find_config_files(prog='barbican-api') +print ">>>>>>> " +print CONF_FILES +#CONF_FILES = cfg.find_config_files(project='barbican', prog='barbican-api') +CONF_FILES.append('./etc/barbican-api.conf') +CONF_FILES.append('../etc/barbican-api.conf') +CONF_FILES = [cfile for cfile in CONF_FILES if os.path.isfile(cfile)] + +# Set configuration files +CONF = cfg.CONF +CONF(prog='barbican-api', default_config_files=CONF_FILES) +#CONF(project='barbican', prog='barbican-api', default_config_files=CONF_FILES) diff --git a/barbican/common/exception.py b/barbican/common/exception.py new file mode 100644 index 000000000..952a8eb1e --- /dev/null +++ b/barbican/common/exception.py @@ -0,0 +1,273 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# 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. + +"""Barbican exception subclasses""" + +import urlparse +from barbican.openstack.common.gettextutils import _ + +_FATAL_EXCEPTION_FORMAT_ERRORS = False + + +class RedirectException(Exception): + def __init__(self, url): + self.url = urlparse.urlparse(url) + + +class BarbicanException(Exception): + """ + Base Barbican Exception + + To correctly use this class, inherit from it and define + a 'message' property. That message will get printf'd + with the keyword arguments provided to the constructor. + """ + message = _("An unknown exception occurred") + + def __init__(self, message=None, *args, **kwargs): + if not message: + message = self.message + try: + message = message % kwargs + except Exception as e: + if _FATAL_EXCEPTION_FORMAT_ERRORS: + raise e + else: + # at least get the core message out if something happened + pass + + super(BarbicanException, self).__init__(message) + + +class MissingArgumentError(BarbicanException): + message = _("Missing required argument.") + + +class MissingCredentialError(BarbicanException): + message = _("Missing required credential: %(required)s") + + +class BadAuthStrategy(BarbicanException): + message = _("Incorrect auth strategy, expected \"%(expected)s\" but " + "received \"%(received)s\"") + + +class NotFound(BarbicanException): + message = _("An object with the specified identifier was not found.") + + +class UnknownScheme(BarbicanException): + message = _("Unknown scheme '%(scheme)s' found in URI") + + +class BadStoreUri(BarbicanException): + message = _("The Store URI was malformed.") + + +class Duplicate(BarbicanException): + message = _("An object with the same identifier already exists.") + + +class StorageFull(BarbicanException): + message = _("There is not enough disk space on the image storage media.") + + +class StorageWriteDenied(BarbicanException): + message = _("Permission to write image storage media denied.") + + +class AuthBadRequest(BarbicanException): + message = _("Connect error/bad request to Auth service at URL %(url)s.") + + +class AuthUrlNotFound(BarbicanException): + message = _("Auth service at URL %(url)s not found.") + + +class AuthorizationFailure(BarbicanException): + message = _("Authorization failed.") + + +class NotAuthenticated(BarbicanException): + message = _("You are not authenticated.") + + +class Forbidden(BarbicanException): + message = _("You are not authorized to complete this action.") + + +class ForbiddenPublicImage(Forbidden): + message = _("You are not authorized to complete this action.") + + +class ProtectedImageDelete(Forbidden): + message = _("Image %(image_id)s is protected and cannot be deleted.") + + +#NOTE(bcwaldon): here for backwards-compatability, need to deprecate. +class NotAuthorized(Forbidden): + message = _("You are not authorized to complete this action.") + + +class Invalid(BarbicanException): + message = _("Data supplied was not valid.") + + +class InvalidSortKey(Invalid): + message = _("Sort key supplied was not valid.") + + +class InvalidFilterRangeValue(Invalid): + message = _("Unable to filter using the specified range.") + + +class ReadonlyProperty(Forbidden): + message = _("Attribute '%(property)s' is read-only.") + + +class ReservedProperty(Forbidden): + message = _("Attribute '%(property)s' is reserved.") + + +class AuthorizationRedirect(BarbicanException): + message = _("Redirecting to %(uri)s for authorization.") + + +class DatabaseMigrationError(BarbicanException): + message = _("There was an error migrating the database.") + + +class ClientConnectionError(BarbicanException): + message = _("There was an error connecting to a server") + + +class ClientConfigurationError(BarbicanException): + message = _("There was an error configuring the client.") + + +class MultipleChoices(BarbicanException): + message = _("The request returned a 302 Multiple Choices. This generally " + "means that you have not included a version indicator in a " + "request URI.\n\nThe body of response returned:\n%(body)s") + + +class LimitExceeded(BarbicanException): + message = _("The request returned a 413 Request Entity Too Large. This " + "generally means that rate limiting or a quota threshold was " + "breached.\n\nThe response body:\n%(body)s") + + def __init__(self, *args, **kwargs): + self.retry_after = (int(kwargs['retry']) if kwargs.get('retry') + else None) + super(LimitExceeded, self).__init__(*args, **kwargs) + + +class ServiceUnavailable(BarbicanException): + message = _("The request returned 503 Service Unavilable. This " + "generally occurs on service overload or other transient " + "outage.") + + def __init__(self, *args, **kwargs): + self.retry_after = (int(kwargs['retry']) if kwargs.get('retry') + else None) + super(ServiceUnavailable, self).__init__(*args, **kwargs) + + +class ServerError(BarbicanException): + message = _("The request returned 500 Internal Server Error.") + + +class UnexpectedStatus(BarbicanException): + message = _("The request returned an unexpected status: %(status)s." + "\n\nThe response body:\n%(body)s") + + +class InvalidContentType(BarbicanException): + message = _("Invalid content type %(content_type)s") + + +class BadRegistryConnectionConfiguration(BarbicanException): + message = _("Registry was not configured correctly on API server. " + "Reason: %(reason)s") + + +class BadStoreConfiguration(BarbicanException): + message = _("Store %(store_name)s could not be configured correctly. " + "Reason: %(reason)s") + + +class BadDriverConfiguration(BarbicanException): + message = _("Driver %(driver_name)s could not be configured correctly. " + "Reason: %(reason)s") + + +class StoreDeleteNotSupported(BarbicanException): + message = _("Deleting images from this store is not supported.") + + +class StoreAddDisabled(BarbicanException): + message = _("Configuration for store failed. Adding images to this " + "store is disabled.") + + +class InvalidNotifierStrategy(BarbicanException): + message = _("'%(strategy)s' is not an available notifier strategy.") + + +class MaxRedirectsExceeded(BarbicanException): + message = _("Maximum redirects (%(redirects)s) was exceeded.") + + +class InvalidRedirect(BarbicanException): + message = _("Received invalid HTTP redirect.") + + +class NoServiceEndpoint(BarbicanException): + message = _("Response from Keystone does not contain a Barbican endpoint.") + + +class RegionAmbiguity(BarbicanException): + message = _("Multiple 'image' service matches for region %(region)s. This " + "generally means that a region is required and you have not " + "supplied one.") + + +class WorkerCreationFailure(BarbicanException): + message = _("Server worker creation failed: %(reason)s.") + + +class SchemaLoadError(BarbicanException): + message = _("Unable to load schema: %(reason)s") + + +class InvalidObject(BarbicanException): + message = _("Provided object does not match schema " + "'%(schema)s': %(reason)s") + + +class UnsupportedHeaderFeature(BarbicanException): + message = _("Provided header feature is unsupported: %(feature)s") + + +class InUseByStore(BarbicanException): + message = _("The image cannot be deleted because it is in use through " + "the backend store outside of Barbican.") + + +class ImageSizeLimitExceeded(BarbicanException): + message = _("The provided image is too large.") diff --git a/barbican/config.py b/barbican/config.py deleted file mode 100644 index e4458082d..000000000 --- a/barbican/config.py +++ /dev/null @@ -1,22 +0,0 @@ -import os -from oslo.config import cfg -from barbican.openstack.common import log - -CONFIG_FILE_ENV_VAR = 'CONFIG_FILE' - -if CONFIG_FILE_ENV_VAR in os.environ: - _DEFAULT_CONFIG_ARGS = ['--config-file', os.environ['CONFIG_FILE']] -else: - _DEFAULT_CONFIG_ARGS = ['--config-file', '/etc/barbican/barbican.cfg'] - -_config_opts = cfg.ConfigOpts() - -# Configure project logging -log.setup('barbican') - -def get_config(): - return _config_opts - - -def init_config(cfg_args=_DEFAULT_CONFIG_ARGS): - _config_opts(args=cfg_args) diff --git a/barbican/model/models.py b/barbican/model/models.py new file mode 100644 index 000000000..65a689226 --- /dev/null +++ b/barbican/model/models.py @@ -0,0 +1,154 @@ +# Copyright (c) 2013 Rackspace, Inc. +# +# 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. + +""" +Defines database models for Barbican +""" +import json + +from sqlalchemy import Column, Integer, String, BigInteger +from sqlalchemy.ext.compiler import compiles +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy import ForeignKey, DateTime, Boolean, Text +from sqlalchemy.orm import relationship, backref, object_mapper +from sqlalchemy import Index, UniqueConstraint + +from barbican.openstack.common import timeutils +from barbican.openstack.common import uuidutils + +BASE = declarative_base() + + +@compiles(BigInteger, 'sqlite') +def compile_big_int_sqlite(type_, compiler, **kw): + return 'INTEGER' + + +class ModelBase(object): + """Base class for Nova and Barbican Models""" + __table_args__ = {'mysql_engine': 'InnoDB'} + __table_initialized__ = False + __protected_attributes__ = set([ + "created_at", "updated_at", "deleted_at", "deleted"]) + + id = Column(String(36), primary_key=True, default=uuidutils.generate_uuid) + + created_at = Column(DateTime, default=timeutils.utcnow, + nullable=False) + updated_at = Column(DateTime, default=timeutils.utcnow, + nullable=False, onupdate=timeutils.utcnow) + deleted_at = Column(DateTime) + deleted = Column(Boolean, nullable=False, default=False) + + def save(self, session=None): + """Save this object""" + # import api here to prevent circular dependency problem + import barbican.model.repositories + session = session or barbican.model.repositories.get_session() + session.add(self) + session.flush() + + def delete(self, session=None): + """Delete this object""" + self.deleted = True + self.deleted_at = timeutils.utcnow() + self.save(session=session) + + def update(self, values): + """dict.update() behaviour.""" + for k, v in values.iteritems(): + self[k] = v + + def __setitem__(self, key, value): + setattr(self, key, value) + + def __getitem__(self, key): + return getattr(self, key) + + def __iter__(self): + self._i = iter(object_mapper(self).columns) + return self + + def next(self): + n = self._i.next().name + return n, getattr(self, n) + + def keys(self): + return self.__dict__.keys() + + def values(self): + return self.__dict__.values() + + def items(self): + return self.__dict__.items() + + def to_dict(self): + return self.__dict__.copy() + + def to_dict_fields(self): + """Returns a dictionary of just the db fields of this entity.""" + dict_fields = {'id':self.id, + 'created':self.created_at, + 'updated':self.updated_at} + if self.deleted_at: + dict_fields['deleted'] = self.deleted_at + if self.deleted: + dict_fields['is_deleted'] = True + dict_fields.update(self._do_extra_dict_fields()) + return dict_fields + + def _do_extra_dict_fields(self): + """Sub-class hook method: return dict of fields.""" + return {} + + +class Tenant(BASE, ModelBase): + """ + Represents a Tenant in the datastore + + Tenants are users that wish to store secret information within + Cloudkeep's Barbican. + """ + + __tablename__ = 'tenants' + + username = Column(String(255)) + + def _do_extra_dict_fields(self): + """Sub-class hook method: return dict of fields.""" + return {'username':self.username} + + + +# Keep this tuple synchronized with the models in the file +MODELS = [Tenant] + + +def register_models(engine): + """ + Creates database tables for all models with the given engine + """ + # TBD: Remove this: + print "models: %s" % `MODELS` + for model in MODELS: + model.metadata.create_all(engine) + + +def unregister_models(engine): + """ + Drops database tables for all models with the given engine + """ + for model in MODELS: + model.metadata.drop_all(engine) diff --git a/barbican/model/repositories.py b/barbican/model/repositories.py index ea98d61f8..b93725f67 100644 --- a/barbican/model/repositories.py +++ b/barbican/model/repositories.py @@ -23,15 +23,18 @@ import logging import time from oslo.config import cfg + import sqlalchemy import sqlalchemy.orm as sa_orm import sqlalchemy.sql as sa_sql from barbican.common import exception +from barbican.common import config # TBD: from barbican.db.sqlalchemy import migration from barbican.model import models import barbican.openstack.common.log as os_logging from barbican.openstack.common import timeutils +from barbican.openstack.common.gettextutils import _ _ENGINE = None @@ -50,7 +53,8 @@ db_opts = [ cfg.IntOpt('sql_idle_timeout', default=3600), cfg.IntOpt('sql_max_retries', default=60), cfg.IntOpt('sql_retry_interval', default=1), - cfg.BoolOpt('db_auto_create', default=False), + cfg.BoolOpt('db_auto_create', default=True), + cfg.StrOpt('sql_connection', default=None), ] CONF = cfg.CONF @@ -68,6 +72,8 @@ def setup_db_env(): _MAX_RETRIES = CONF.sql_max_retries _RETRY_INTERVAL = CONF.sql_retry_interval _CONNECTION = CONF.sql_connection + # TBD: Remove: + print "Conn = %s" % _CONNECTION sa_logger = logging.getLogger('sqlalchemy.engine') if CONF.debug: sa_logger.setLevel(logging.DEBUG) @@ -111,6 +117,8 @@ def get_engine(): 'convert_unicode': True} try: + # TBD: Remove + print "Conn: %s; Args: %s" % (_CONNECTION, engine_args) _ENGINE = sqlalchemy.create_engine(_CONNECTION, **engine_args) # TBD: if 'mysql' in connection_dict.drivername: @@ -131,7 +139,6 @@ def get_engine(): if CONF.db_auto_create: LOG.info(_('auto-creating barbican registry DB')) models.register_models(_ENGINE) - LOG.warn(_('(Not doing this yet!) auto-creating barbican registry DB')) # TBD: try: # TBD: migration.version_control() # TBD: except exception.DatabaseMigrationError: @@ -204,18 +211,23 @@ class BaseRepo(object): """ def __init__(self): - pass + print "BaseRepo" + LOG.debug("BaseRepo init...") + configure_db() def get_session(self, session=None): + print "Getting session..." return session or get_session() - def find_by_name(self, name, suppress_exception, session=None): + def find_by_name(self, name, suppress_exception=False, session=None): session = self.get_session(session) try: + print "Starting find by name steps..." query = self._do_build_query_by_name(name, session) - + print "...query = %s" % `query` entity = query.one() + print "...post query.one()" except sa_orm.exc.NoResultFound: entity = None @@ -225,7 +237,7 @@ class BaseRepo(object): return entity - def get(self, entity_id, force_show_deleted=False, session=None): + def get(self, entity_id, force_show_deleted=False, suppress_exception=False, session=None): """Get an entity or raise if it does not exist.""" session = self.get_session(session) @@ -239,8 +251,10 @@ class BaseRepo(object): entity = query.one() except sa_orm.exc.NoResultFound: - raise exception.NotFound("No %s found with ID %s" - % (self._do_entity_name(), entity_id)) + entity = None + if not suppress_exception: + raise exception.NotFound("No %s found with ID %s" + % (self._do_entity_name(), entity_id)) return entity @@ -285,6 +299,13 @@ class BaseRepo(object): """ return self._update(values, entity_id, purge_props) + def delete_entity(self, entity): + """Remove the entity""" + + session = get_session() + with session.begin(): + entity.delete(session=session) + def _do_entity_name(self): """Sub-class hook: return entity name, such as for debugging.""" return "Entity" @@ -384,7 +405,7 @@ class TenantRepo(BaseRepo): def _do_build_query_by_name(self, name, session): """Sub-class hook: find entity by name.""" return session.query(models.Tenant)\ - .filter_by(name=name) + .filter_by(username=name) def _do_build_get_query(self, entity_id, session): """Sub-class hook: build a retrieve query.""" diff --git a/barbican/model/secret.py b/barbican/model/secret.py deleted file mode 100644 index 63ade7a6a..000000000 --- a/barbican/model/secret.py +++ /dev/null @@ -1,42 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# Copyright 2011 Justin Santa Barbara -# 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. - -"""Represents a secret to store in Cloudkeep's Barbican.""" - -#import barbican.model.Tenant -from base import Base -from sqlalchemy import Table, Column, String -from sqlalchemy import Integer, ForeignKey, Boolean -from sqlalchemy.orm import relationship, backref -from sqlalchemy.ext.declarative import declarative_base, declared_attr - -# -# class Secret(Base): -# """ -# A secret is any information that needs to be stored and protected within -# Cloudkeep's Barbican. -# """ -# -# secret_id = Column(String) -# name = Column(String) -# tenant_id = Column(Integer, ForeignKey('tenant.id')) -# tenant = relationship(Tenant, primaryjoin=tenant_id == Tenant.id) -# -# def __init__(self, secret_id): -# self.secret_id = secret_id diff --git a/barbican/model/tenant.py b/barbican/model/tenant.py deleted file mode 100644 index 71581cd5d..000000000 --- a/barbican/model/tenant.py +++ /dev/null @@ -1,86 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# Copyright 2011 Justin Santa Barbara -# 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. - -"""Generic Node base class for all workers that run on hosts.""" - -import logging -from base import Base -from sqlalchemy import Table, Column, String -from sqlalchemy import Integer, ForeignKey, Boolean -from sqlalchemy.orm import relationship, relation, backref -from sqlalchemy.ext.declarative import declarative_base, declared_attr - - -class Tenant(Base): - """ - Tenants are users that wish to store secret information within - Cloudkeep's Barbican. - """ - - logging.debug('In Tenant table setup') - - __tablename__ = "tenants" - - id = Column(Integer, primary_key=True) - username = Column(String) - # secrets = relationship('Secret', backref='tenant', lazy='dynamic') - # secrets = relationship('Secret', secondary=_secrets) - # secrets = relationship("Secret", - # order_by="desc(Secret.name)", - # primaryjoin="Secret.tenant_id==Tenant.id") - - def __init__(self, username): - self.username = username - - def __init__(self, username, secrets=[]): - self.username = username - self.secrets = secrets - - def format(self): - return {'id': self.id, - 'username': self.username} - - -class Secret(Base): - """ - A secret is any information that needs to be stored and protected within - Cloudkeep's Barbican. - """ - - __tablename__ = "secrets" - - id = Column(Integer, primary_key=True) - name = Column(String) - tenant_id = Column(Integer, ForeignKey('tenants.id')) - tenant = relationship("Tenant", backref=backref('secrets', order_by=id)) - # tenant = relationship(Tenant, primaryjoin=tenant_id == Tenant.id) - - # creates a bidirectional relationship - # from Secret to Tenant it's Many-to-One - # from Tenant to Secret it's One-to-Many - # tenant = relation(Tenant, backref=backref('secret', order_by=id)) - - def __init__(self, tenant_id, name): - self.tenant_id = tenant_id - self.name = name - - def format(self): - return {'id': self.id, - 'name': self.username, - 'tenant_id': self.tenant_id} diff --git a/barbican/model/util.py b/barbican/model/util.py deleted file mode 100644 index c92cce5ee..000000000 --- a/barbican/model/util.py +++ /dev/null @@ -1,33 +0,0 @@ -from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound - -from barbican.model.tenant import Tenant, Secret - - -def _empty_condition(): - pass - - -def find_tenant(db_session, id=None, username=None, - when_not_found=_empty_condition, - when_multiple_found=_empty_condition): - try: - if id: - return db_session.query(Tenant).filter_by(id=id).one() - elif username: - return db_session.query(Tenant).filter_by(username=username).one() - except NoResultFound: - when_not_found() - except MultipleResultsFound: - when_multiple_found() - - return None - - -def find_secret(db_session, id, when_not_found=_empty_condition, - when_multiple_found=_empty_condition): - try: - return db_session.query(Secret).filter_by(id=id).one() - except NoResultFound: - when_not_found() - except MultipleResultsFound: - when_multiple_found() diff --git a/barbican/model/base.py b/barbican/openstack/common/uuidutils.py similarity index 54% rename from barbican/model/base.py rename to barbican/openstack/common/uuidutils.py index 3b5afa17c..7608acb94 100644 --- a/barbican/model/base.py +++ b/barbican/openstack/common/uuidutils.py @@ -1,8 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# Copyright 2011 Justin Santa Barbara +# Copyright (c) 2012 Intel Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -17,19 +15,25 @@ # License for the specific language governing permissions and limitations # under the License. -"""Base class for all model objects.""" +""" +UUID related utilities and helper functions. +""" -from sqlalchemy import Column -from sqlalchemy import Integer, ForeignKey -from sqlalchemy.ext.declarative import declarative_base, declared_attr +import uuid -class Persisted(object): +def generate_uuid(): + return str(uuid.uuid4()) - id = Column(Integer, primary_key=True) - @declared_attr - def __tablename__(self): - return self.__name__.lower() +def is_uuid_like(val): + """Returns validation of a value as a UUID. -Base = declarative_base(cls=Persisted) + For our purposes, a UUID is a canonical form string: + aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa + + """ + try: + return str(uuid.UUID(val)) == val + except (TypeError, ValueError, AttributeError): + return False diff --git a/barbican/tests/api/resources_test.py b/barbican/tests/api/resources_test.py index 68035295f..87c33a615 100644 --- a/barbican/tests/api/resources_test.py +++ b/barbican/tests/api/resources_test.py @@ -1,6 +1,7 @@ from datetime import datetime from barbican.api.resources import * -from barbican.model.tenant import Tenant +from barbican.model.models import Tenant +from barbican.common import config from mock import MagicMock @@ -38,29 +39,34 @@ class WhenTestingVersionResource(unittest.TestCase): class WhenCreatingTenantsUsingTenantsResource(unittest.TestCase): def setUp(self): - db_filter = MagicMock() - db_filter.one.return_value = Tenant('tenant_id') - - db_query = MagicMock() - db_query.filter_by.return_value = db_filter - - self.db_session = MagicMock() - self.db_session.query.return_value = db_query + self.username = '1234' + + self.tenant_repo = MagicMock() + self.tenant_repo.find_by_name.return_value = None + self.tenant_repo.create_from.return_value = None self.stream = MagicMock() - self.stream.read.return_value = u'{ "username" : "1234" }' + self.stream.read.return_value = u'{ "username" : "%s" }' % self.username self.req = MagicMock() self.req.stream = self.stream self.resp = MagicMock() - self.resource = TenantsResource(self.db_session) + self.resource = TenantsResource(self.tenant_repo) + + def test_should_add_new_tenant(self): + self.resource.on_post(self.req, self.resp) + + self.tenant_repo.find_by_name.assert_called_once_with(name=self.username, suppress_exception=True) + # TBD: Make this work: self.tenant_repo.create_from.assert_called_once_with(unittest.mock.ANY) def test_should_throw_exception_for_tenants_that_exist(self): + self.tenant_repo.find_by_name.return_value = Tenant() + with self.assertRaises(falcon.HTTPError): self.resource.on_post(self.req, self.resp) - self.db_session.query.assert_called_once_with(Tenant) + self.tenant_repo.find_by_name.assert_called_once_with(name=self.username, suppress_exception=True) if __name__ == '__main__': diff --git a/barbican/tests/config_test.py b/barbican/tests/config_test.py index b28ca9bde..2ecb48a7f 100644 --- a/barbican/tests/config_test.py +++ b/barbican/tests/config_test.py @@ -2,7 +2,7 @@ import os import unittest from oslo.config import cfg -from barbican.config import init_config, get_config +from barbican.common import config import logging # Configuration test configuration options @@ -17,6 +17,8 @@ CFG_TEST_OPTIONS = [ ) ] +CONF = cfg.CONF + LOG = logging.getLogger(__name__) def suite(): @@ -32,16 +34,10 @@ class WhenConfiguring(unittest.TestCase): LOG.debug("In test 'test_loading'") - try: - init_config(['--config-file', './etc/barbican/barbican.conf']) - except: - init_config(['--config-file', '../etc/barbican/barbican.conf']) + CONF.register_group(test_group) + CONF.register_opts(CFG_TEST_OPTIONS, group=test_group) - conf = get_config() - conf.register_group(test_group) - conf.register_opts(CFG_TEST_OPTIONS, group=test_group) - - self.assertTrue(conf.test.should_pass) + self.assertTrue(CONF.test.should_pass) if __name__ == '__main__': diff --git a/etc/barbican/barbican-api-paste.ini b/etc/barbican-api-paste.ini similarity index 100% rename from etc/barbican/barbican-api-paste.ini rename to etc/barbican-api-paste.ini diff --git a/etc/barbican-api.conf b/etc/barbican-api.conf new file mode 100644 index 000000000..8980f001a --- /dev/null +++ b/etc/barbican-api.conf @@ -0,0 +1,90 @@ +[DEFAULT] +# Show more verbose log output (sets INFO log level output) +verbose = True + +# Show debugging output in logs (sets DEBUG log level output) +debug = True + +# Address to bind the API server +bind_host = 0.0.0.0 + +# Port the bind the API server to +bind_port = 9292 + +# 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/barbican/api.log + +# Backlog requests when creating socket +backlog = 4096 + +# TCP_KEEPIDLE value in seconds when creating socket. +# Not supported on OS X. +#tcp_keepidle = 600 + +# 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 +sql_connection = sqlite:///barbican.sqlite + +# Period in seconds after which SQLAlchemy should reestablish its connection +# to the database. +# +# MySQL uses a default `wait_timeout` of 8 hours, after which it will drop +# idle connections. This can result in 'MySQL Gone Away' exceptions. If you +# notice this, you can lower this value to ensure that SQLAlchemy reconnects +# before MySQL can drop the connection. +sql_idle_timeout = 3600 + +# Number of Barbican API worker processes to start. +# On machines with more than one CPU increasing this value +# may improve performance (especially if using SSL with +# compression turned on). It is typically recommended to set +# this value to the number of CPUs present on your machine. +workers = 1 + +# Role used to identify an authenticated user as administrator +#admin_role = admin + +# Allow unauthenticated users to access the API with read-only +# privileges. This only applies when using ContextMiddleware. +#allow_anonymous_access = False + +# Allow access to version 1 of barbican api +#enable_v1_api = True + +# Allow access to version 2 of barbican api +#enable_v2_api = True + +# ================= 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 + +# CA certificate file to use to verify connecting clients +#ca_file = /path/to/cafile + +# ================= Security Options ========================== + +# AES key for encrypting store 'location' metadata, including +# -- if used -- Swift or S3 credentials +# Should be set to a random string of length 16, 24 or 32 bytes +#metadata_encryption_key = <16, 24 or 32 char registry metadata key> + +# ============ Delayed Delete Options ============================= + +# Turn on/off delayed delete +delayed_delete = False + +# Delayed delete time in seconds +scrub_time = 43200 + +# Directory that the scrubber will use to remind itself of what to delete +# Make sure this is also set in glance-scrubber.conf +scrubber_datadir = /var/lib/barbican/scrubber + +[test] +should_pass = true \ No newline at end of file diff --git a/etc/barbican/barbican-api.ini b/etc/barbican-api.ini similarity index 100% rename from etc/barbican/barbican-api.ini rename to etc/barbican-api.ini diff --git a/etc/barbican/barbican.conf b/etc/barbican/barbican.conf deleted file mode 100644 index ce340dd51..000000000 --- a/etc/barbican/barbican.conf +++ /dev/null @@ -1,8 +0,0 @@ -# Default Cloudkeep Barbican Config File - -[logging] -use_stderr=true - -[test] -should_pass = true - diff --git a/etc/barbican/config.py b/etc/barbican/config.py deleted file mode 100644 index e3c467229..000000000 --- a/etc/barbican/config.py +++ /dev/null @@ -1,9 +0,0 @@ -config = { - 'sqlalchemy': { - 'url': 'sqlite:////tmp/barbican.db', - 'echo': True, - 'echo_pool': False, - 'pool_recycle': 3600, - 'encoding': 'utf-8' - } -} diff --git a/openstack-common.conf b/openstack-common.conf index dfb0969e1..eb05774a0 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -1,7 +1,7 @@ [DEFAULT] # The list of modules to copy from openstack-common -modules=gettextutils,jsonutils,log,local,notifier,timeutils +modules=gettextutils,jsonutils,log,local,notifier,timeutils,uuidutils # The base module to hold the copy of openstack.common base=barbican diff --git a/uwsgi.ini b/uwsgi.ini index 52078a4d6..46cb38765 100644 --- a/uwsgi.ini +++ b/uwsgi.ini @@ -1,6 +1,6 @@ [uwsgi] -socket = 172.16.83.131:8080 +socket = :8080 protocol = http processes = 1 @@ -11,7 +11,7 @@ vaccum = true no-default-app = true memory-report = true -env = CONFIG_FILE=/etc/barbican/barbican.cfg +#env = CONFIG_FILE=/etc/barbican/barbican.cfg pythonpath = ./ module = barbican.api.app:application