From 78dfbc646316578240a391fd8528fe84687008fd Mon Sep 17 00:00:00 2001 From: jfwood Date: Thu, 21 Aug 2014 14:26:58 -0500 Subject: [PATCH] Add order plugin metadata entity and logic To support certificate plugin workflows, plugins needs a means to persist their state in between method invocations by Barbican core. This CR adds that persistence feature, by adding a new SQLAlchemy model and migration file, and a new repository. Change-Id: Ic0f74d49ab8c97e5b15c61fbc8c64d00bd9c8b5e Implements: blueprint add-ssl-ca-support --- ...2_add_orders_plugin_metadata_table_and_.py | 39 +++++++++++++ barbican/model/migration/commands.py | 7 +-- barbican/model/models.py | 57 +++++++++++++++++-- barbican/model/repositories.py | 38 +++++++++++++ barbican/tasks/certificate_resources.py | 2 +- bin/barbican-db-manage.py | 7 ++- 6 files changed, 137 insertions(+), 13 deletions(-) create mode 100644 barbican/model/migration/alembic_migrations/versions/4070806f6972_add_orders_plugin_metadata_table_and_.py diff --git a/barbican/model/migration/alembic_migrations/versions/4070806f6972_add_orders_plugin_metadata_table_and_.py b/barbican/model/migration/alembic_migrations/versions/4070806f6972_add_orders_plugin_metadata_table_and_.py new file mode 100644 index 000000000..cf83fceca --- /dev/null +++ b/barbican/model/migration/alembic_migrations/versions/4070806f6972_add_orders_plugin_metadata_table_and_.py @@ -0,0 +1,39 @@ +"""Add orders plugin metadata table and relationships + +Revision ID: 4070806f6972 +Revises: 47b69e523451 +Create Date: 2014-08-21 14:06:48.237701 + +""" + +# revision identifiers, used by Alembic. +revision = '4070806f6972' +down_revision = '47b69e523451' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ctx = op.get_context() + con = op.get_bind() + table_exists = ctx.dialect.has_table(con.engine, 'order_plugin_metadata') + if not table_exists: + op.create_table( + 'order_plugin_metadata', + sa.Column('id', sa.String(length=36), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('updated_at', sa.DateTime(), nullable=False), + sa.Column('deleted_at', sa.DateTime(), nullable=True), + sa.Column('deleted', sa.Boolean(), nullable=False), + sa.Column('status', sa.String(length=20), nullable=False), + sa.Column('order_id', sa.String(length=36), nullable=False), + sa.Column('key', sa.String(length=255), nullable=False), + sa.Column('value', sa.String(length=255), nullable=False), + sa.ForeignKeyConstraint(['order_id'], ['orders.id'],), + sa.PrimaryKeyConstraint('id'), + ) + + +def downgrade(): + op.drop_table('order_plugin_metadata') diff --git a/barbican/model/migration/commands.py b/barbican/model/migration/commands.py index 9ff277d6d..864f01947 100644 --- a/barbican/model/migration/commands.py +++ b/barbican/model/migration/commands.py @@ -43,10 +43,9 @@ CONF.register_opts(db_opts) def init_config(sql_url=None): """Initialize and return the Alembic configuration.""" sqlalchemy_url = sql_url or CONF.sql_connection - if 'sqlite' in sqlalchemy_url: - LOG.warn('!!! No support for migrating sqlite databases...' - 'skipping migration processing !!!') - return None + if sqlalchemy_url and 'sqlite' in sqlalchemy_url: + LOG.warn('!!! Limited support for migration commands using sqlite' + ' databases; This operation may not succeed.') config = alembic_config.Config( os.path.join(os.path.dirname(__file__), 'alembic.ini') diff --git a/barbican/model/models.py b/barbican/model/models.py index a3d9c19ec..03aaabe63 100644 --- a/barbican/model/models.py +++ b/barbican/model/models.py @@ -268,11 +268,11 @@ class Secret(BASE, ModelBase): # Eager load this relationship via 'lazy=False'. encrypted_data = orm.relationship("EncryptedDatum", lazy=False) - secret_store_metadata = orm.\ - relationship("SecretStoreMetadatum", - collection_class=col.attribute_mapped_collection('key'), - backref="secret", - cascade="all, delete-orphan") + secret_store_metadata = orm.relationship( + "SecretStoreMetadatum", + collection_class=col.attribute_mapped_collection('key'), + backref="secret", + cascade="all, delete-orphan") def __init__(self, parsed_request=None): """Creates secret from a dict.""" @@ -453,6 +453,17 @@ class Order(BASE, ModelBase): container_id = sa.Column(sa.String(36), sa.ForeignKey('containers.id'), nullable=True) + order_plugin_metadata = orm.relationship( + "OrderPluginMetadatum", + collection_class=col.attribute_mapped_collection('key'), + backref="order", + cascade="all, delete-orphan") + + def _do_delete_children(self, session): + """Sub-class hook: delete children relationships.""" + for k, v in self.order_plugin_metadata.items(): + v.delete(session) + def _do_extra_dict_fields(self): """Sub-class hook method: return dict of fields.""" ret = {'secret': {'name': self.secret_name or self.secret_id, @@ -478,6 +489,40 @@ class Order(BASE, ModelBase): return ret +class OrderPluginMetadatum(BASE, ModelBase): + """Represents Order plugin metadatum for a single key-value pair. + + This entity is used to store plugin-specific metadata on behalf of an + Order instance. + """ + + __tablename__ = "order_plugin_metadata" + + order_id = sa.Column(sa.String(36), sa.ForeignKey('orders.id'), + nullable=False) + key = sa.Column(sa.String(255), nullable=False) + value = sa.Column(sa.String(255), nullable=False) + + def __init__(self, key, value): + super(OrderPluginMetadatum, self).__init__() + + msg = ("Must supply non-None {0} argument " + "for OrderPluginMetadatum entry.") + + if key is None: + raise exception.MissingArgumentError(msg.format("key")) + self.key = key + + if value is None: + raise exception.MissingArgumentError(msg.format("value")) + self.value = value + + def _do_extra_dict_fields(self): + """Sub-class hook method: return dict of fields.""" + return {'key': self.key, + 'value': self.value} + + class Container(BASE, ModelBase): """Represents a Container for Secrets in the datastore. @@ -622,7 +667,7 @@ class TransportKey(BASE, ModelBase): # Keep this tuple synchronized with the models in the file MODELS = [TenantSecret, Tenant, Secret, EncryptedDatum, Order, Container, ContainerConsumerMetadatum, ContainerSecret, TransportKey, - SecretStoreMetadatum, KEKDatum] + SecretStoreMetadatum, OrderPluginMetadatum, KEKDatum] def register_models(engine): diff --git a/barbican/model/repositories.py b/barbican/model/repositories.py index a9f6cd50b..5e6c80854 100644 --- a/barbican/model/repositories.py +++ b/barbican/model/repositories.py @@ -264,6 +264,8 @@ class Repositories(object): self._set_repo('secret_meta_repo', SecretStoreMetadatumRepo, kwargs) self._set_repo('order_repo', OrderRepo, kwargs) + self._set_repo('order_plugin_meta_repo', OrderPluginMetadatumRepo, + kwargs) self._set_repo('transport_key_repo', TransportKeyRepo, kwargs) def _set_repo(self, repo_name, repo_cls, specs): @@ -803,6 +805,42 @@ class OrderRepo(BaseRepo): pass +class OrderPluginMetadatumRepo(BaseRepo): + """Repository for the OrderPluginMetadatum entity (that stores key/value + plugin information on behalf of a Order). + """ + + def save(self, metadata, order_model): + """Saves the the specified metadata for the order. + + :raises NotFound if entity does not exist. + """ + now = timeutils.utcnow() + session = get_session() + with session.begin(): + for k, v in metadata.items(): + meta_model = models.OrderPluginMetadatum(k, v) + meta_model.updated_at = now + meta_model.order = order_model + meta_model.save(session=session) + + def _do_entity_name(self): + """Sub-class hook: return entity name, such as for debugging.""" + return "OrderPluginMetadatum" + + def _do_create_instance(self): + return models.OrderPluginMetadatum() + + def _do_build_get_query(self, entity_id, keystone_id, session): + """Sub-class hook: build a retrieve query.""" + query = session.query(models.OrderPluginMetadatum) + return query.filter_by(id=entity_id) + + def _do_validate(self, values): + """Sub-class hook: validate values.""" + pass + + class ContainerRepo(BaseRepo): """Repository for the Container entity.""" diff --git a/barbican/tasks/certificate_resources.py b/barbican/tasks/certificate_resources.py index e810a3b82..e2590b594 100644 --- a/barbican/tasks/certificate_resources.py +++ b/barbican/tasks/certificate_resources.py @@ -53,7 +53,7 @@ def issue_certificate_request(order_model, repos): def _get_plugin_meta(order_model): if order_model: meta_dict = dict((k, v.value) for (k, v) in - order_model.order_plugin_meta.items()) + order_model.order_plugin_metadata.items()) return meta_dict else: return dict() diff --git a/bin/barbican-db-manage.py b/bin/barbican-db-manage.py index 2c6c4e8f3..5c5f58023 100755 --- a/bin/barbican-db-manage.py +++ b/bin/barbican-db-manage.py @@ -97,8 +97,11 @@ def main(): LOG = log.getLogger(__name__) LOG.debug("Performing database schema migration...") - dm = DatabaseManager() - dm.execute() + try: + dm = DatabaseManager() + dm.execute() + except: + LOG.exception('Problem trying to execute Alembic commands') if __name__ == '__main__':