Cleaned up repo/model/config files to be more like Glance;

This commit is contained in:
John Wood 2013-04-12 17:44:03 -05:00
parent 86d1ab30b7
commit 36f7f33086
22 changed files with 649 additions and 387 deletions

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
.venv
*.sqlite
*.py[cod]
# C extensions

View File

@ -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)

View File

@ -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

View File

17
barbican/common/config.py Normal file
View File

@ -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)

View File

@ -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.")

View File

@ -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)

154
barbican/model/models.py Normal file
View File

@ -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)

View File

@ -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."""

View File

@ -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

View File

@ -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}

View File

@ -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()

View File

@ -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

View File

@ -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__':

View File

@ -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__':

90
etc/barbican-api.conf Normal file
View File

@ -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

View File

@ -1,8 +0,0 @@
# Default Cloudkeep Barbican Config File
[logging]
use_stderr=true
[test]
should_pass = true

View File

@ -1,9 +0,0 @@
config = {
'sqlalchemy': {
'url': 'sqlite:////tmp/barbican.db',
'echo': True,
'echo_pool': False,
'pool_recycle': 3600,
'encoding': 'utf-8'
}
}

View File

@ -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

View File

@ -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