initial version
Change-Id: I699e0ab082657880998d8618fe29eb7f56c6c661
This commit is contained in:
0
watcher/db/sqlalchemy/__init__.py
Normal file
0
watcher/db/sqlalchemy/__init__.py
Normal file
54
watcher/db/sqlalchemy/alembic.ini
Normal file
54
watcher/db/sqlalchemy/alembic.ini
Normal file
@@ -0,0 +1,54 @@
|
||||
# A generic, single database configuration.
|
||||
|
||||
[alembic]
|
||||
# path to migration scripts
|
||||
script_location = %(here)s/alembic
|
||||
|
||||
# template used to generate migration files
|
||||
# file_template = %%(rev)s_%%(slug)s
|
||||
|
||||
# max length of characters to apply to the
|
||||
# "slug" field
|
||||
#truncate_slug_length = 40
|
||||
|
||||
# set to 'true' to run the environment during
|
||||
# the 'revision' command, regardless of autogenerate
|
||||
# revision_environment = false
|
||||
|
||||
#sqlalchemy.url = driver://user:pass@localhost/dbname
|
||||
|
||||
|
||||
# Logging configuration
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
|
||||
[formatters]
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = WARN
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = WARN
|
||||
handlers =
|
||||
qualname = sqlalchemy.engine
|
||||
|
||||
[logger_alembic]
|
||||
level = INFO
|
||||
handlers =
|
||||
qualname = alembic
|
||||
|
||||
[handler_console]
|
||||
class = StreamHandler
|
||||
args = (sys.stderr,)
|
||||
level = NOTSET
|
||||
formatter = generic
|
||||
|
||||
[formatter_generic]
|
||||
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||
datefmt = %H:%M:%S
|
||||
15
watcher/db/sqlalchemy/alembic/README
Normal file
15
watcher/db/sqlalchemy/alembic/README
Normal file
@@ -0,0 +1,15 @@
|
||||
Please see https://alembic.readthedocs.org/en/latest/index.html for general documentation
|
||||
|
||||
To create alembic migrations use:
|
||||
$ watcher-db-manage revision --message "description of revision" --autogenerate
|
||||
|
||||
Stamp db with most recent migration version, without actually running migrations
|
||||
$ watcher-db-manage stamp head
|
||||
|
||||
Upgrade can be performed by:
|
||||
$ watcher-db-manage upgrade
|
||||
$ watcher-db-manage upgrade head
|
||||
|
||||
Downgrading db:
|
||||
$ watcher-db-manage downgrade
|
||||
$ watcher-db-manage downgrade base
|
||||
54
watcher/db/sqlalchemy/alembic/env.py
Normal file
54
watcher/db/sqlalchemy/alembic/env.py
Normal file
@@ -0,0 +1,54 @@
|
||||
# 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.
|
||||
|
||||
from logging import config as log_config
|
||||
|
||||
from alembic import context
|
||||
|
||||
from watcher.db.sqlalchemy import api as sqla_api
|
||||
from watcher.db.sqlalchemy import models
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
config = context.config
|
||||
|
||||
# Interpret the config file for Python logging.
|
||||
# This line sets up loggers basically.
|
||||
log_config.fileConfig(config.config_file_name)
|
||||
|
||||
# add your model's MetaData object here
|
||||
# for 'autogenerate' support
|
||||
# from myapp import mymodel
|
||||
target_metadata = models.Base.metadata
|
||||
|
||||
# other values from the config, defined by the needs of env.py,
|
||||
# can be acquired:
|
||||
# my_important_option = config.get_main_option("my_important_option")
|
||||
# ... etc.
|
||||
|
||||
|
||||
def run_migrations_online():
|
||||
"""Run migrations in 'online' mode.
|
||||
|
||||
In this scenario we need to create an Engine
|
||||
and associate a connection with the context.
|
||||
|
||||
"""
|
||||
engine = sqla_api.get_engine()
|
||||
with engine.connect() as connection:
|
||||
context.configure(connection=connection,
|
||||
target_metadata=target_metadata)
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
run_migrations_online()
|
||||
22
watcher/db/sqlalchemy/alembic/script.py.mako
Normal file
22
watcher/db/sqlalchemy/alembic/script.py.mako
Normal file
@@ -0,0 +1,22 @@
|
||||
"""${message}
|
||||
|
||||
Revision ID: ${up_revision}
|
||||
Revises: ${down_revision}
|
||||
Create Date: ${create_date}
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = ${repr(up_revision)}
|
||||
down_revision = ${repr(down_revision)}
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
${imports if imports else ""}
|
||||
|
||||
def upgrade():
|
||||
${upgrades if upgrades else "pass"}
|
||||
|
||||
|
||||
def downgrade():
|
||||
${downgrades if downgrades else "pass"}
|
||||
@@ -0,0 +1,90 @@
|
||||
"""Initial revision
|
||||
|
||||
Revision ID: 414bf1d36e7d
|
||||
Revises: None
|
||||
Create Date: 2015-04-08 15:05:50.942578
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '414bf1d36e7d'
|
||||
down_revision = None
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade():
|
||||
### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('audit_templates',
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('deleted_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('deleted', sa.Integer(), nullable=True),
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('uuid', sa.String(length=36), nullable=True),
|
||||
sa.Column('name', sa.String(length=63), nullable=True),
|
||||
sa.Column('description', sa.String(length=255), nullable=True),
|
||||
sa.Column('host_aggregate', sa.Integer(), nullable=True),
|
||||
sa.Column('goal', sa.String(length=63), nullable=True),
|
||||
sa.Column('extra', watcher.db.sqlalchemy.models.JSONEncodedDict(), nullable=True),
|
||||
sa.Column('version', sa.String(length=15), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('name', name='uniq_audit_templates0name'),
|
||||
sa.UniqueConstraint('uuid', name='uniq_audit_templates0uuid')
|
||||
)
|
||||
op.create_table('audits',
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('deleted_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('deleted', sa.Integer(), nullable=True),
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('uuid', sa.String(length=36), nullable=True),
|
||||
sa.Column('type', sa.String(length=20), nullable=True),
|
||||
sa.Column('state', sa.String(length=20), nullable=True),
|
||||
sa.Column('deadline', sa.DateTime(), nullable=True),
|
||||
sa.Column('audit_template_id', sa.Integer(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['audit_template_id'], ['audit_templates.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('uuid', name='uniq_audits0uuid')
|
||||
)
|
||||
op.create_table('action_plans',
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('deleted_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('deleted', sa.Integer(), nullable=True),
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('uuid', sa.String(length=36), nullable=True),
|
||||
sa.Column('first_action_id', sa.Integer(), nullable=True),
|
||||
sa.Column('audit_id', sa.Integer(), nullable=True),
|
||||
sa.Column('state', sa.String(length=20), nullable=True),
|
||||
sa.ForeignKeyConstraint(['audit_id'], ['audits.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('uuid', name='uniq_action_plans0uuid')
|
||||
)
|
||||
op.create_table('actions',
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('deleted_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('deleted', sa.Integer(), nullable=True),
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('uuid', sa.String(length=36), nullable=True),
|
||||
sa.Column('action_plan_id', sa.Integer(), nullable=True),
|
||||
sa.Column('description', sa.String(length=255), nullable=True),
|
||||
sa.Column('state', sa.String(length=20), nullable=True),
|
||||
sa.Column('alarm', sa.String(length=36), nullable=True),
|
||||
sa.Column('next', sa.String(length=36), nullable=True),
|
||||
sa.ForeignKeyConstraint(['action_plan_id'], ['action_plans.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('uuid', name='uniq_actions0uuid')
|
||||
)
|
||||
### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('actions')
|
||||
op.drop_table('action_plans')
|
||||
op.drop_table('audits')
|
||||
op.drop_table('audit_templates')
|
||||
### end Alembic commands ###
|
||||
617
watcher/db/sqlalchemy/api.py
Normal file
617
watcher/db/sqlalchemy/api.py
Normal file
@@ -0,0 +1,617 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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.
|
||||
|
||||
|
||||
"""SQLAlchemy storage backend."""
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_db import exception as db_exc
|
||||
from oslo_db.sqlalchemy import session as db_session
|
||||
from oslo_db.sqlalchemy import utils as db_utils
|
||||
from sqlalchemy.orm.exc import MultipleResultsFound
|
||||
from sqlalchemy.orm.exc import NoResultFound
|
||||
|
||||
from watcher.common import exception
|
||||
from watcher.common import utils
|
||||
from watcher.db import api
|
||||
from watcher.db.sqlalchemy import models
|
||||
from watcher.objects.audit import AuditStatus
|
||||
from watcher.openstack.common._i18n import _
|
||||
from watcher.openstack.common import log
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
_FACADE = None
|
||||
|
||||
|
||||
def _create_facade_lazily():
|
||||
global _FACADE
|
||||
if _FACADE is None:
|
||||
_FACADE = db_session.EngineFacade.from_config(CONF)
|
||||
return _FACADE
|
||||
|
||||
|
||||
def get_engine():
|
||||
facade = _create_facade_lazily()
|
||||
return facade.get_engine()
|
||||
|
||||
|
||||
def get_session(**kwargs):
|
||||
facade = _create_facade_lazily()
|
||||
return facade.get_session(**kwargs)
|
||||
|
||||
|
||||
def get_backend():
|
||||
"""The backend is this module itself."""
|
||||
return Connection()
|
||||
|
||||
|
||||
def model_query(model, *args, **kwargs):
|
||||
"""Query helper for simpler session usage.
|
||||
|
||||
:param session: if present, the session to use
|
||||
"""
|
||||
|
||||
session = kwargs.get('session') or get_session()
|
||||
query = session.query(model, *args)
|
||||
return query
|
||||
|
||||
|
||||
def add_identity_filter(query, value):
|
||||
"""Adds an identity filter to a query.
|
||||
|
||||
Filters results by ID, if supplied value is a valid integer.
|
||||
Otherwise attempts to filter results by UUID.
|
||||
|
||||
:param query: Initial query to add filter to.
|
||||
:param value: Value for filtering results by.
|
||||
:return: Modified query.
|
||||
"""
|
||||
if utils.is_int_like(value):
|
||||
return query.filter_by(id=value)
|
||||
elif utils.is_uuid_like(value):
|
||||
return query.filter_by(uuid=value)
|
||||
else:
|
||||
raise exception.InvalidIdentity(identity=value)
|
||||
|
||||
|
||||
def _paginate_query(model, limit=None, marker=None, sort_key=None,
|
||||
sort_dir=None, query=None):
|
||||
if not query:
|
||||
query = model_query(model)
|
||||
sort_keys = ['id']
|
||||
if sort_key and sort_key not in sort_keys:
|
||||
sort_keys.insert(0, sort_key)
|
||||
query = db_utils.paginate_query(query, model, limit, sort_keys,
|
||||
marker=marker, sort_dir=sort_dir)
|
||||
return query.all()
|
||||
|
||||
|
||||
class Connection(api.Connection):
|
||||
"""SqlAlchemy connection."""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def _add_audit_templates_filters(self, query, filters):
|
||||
if filters is None:
|
||||
filters = []
|
||||
|
||||
if 'name' in filters:
|
||||
query = query.filter_by(name=filters['name'])
|
||||
if 'host_aggregate' in filters:
|
||||
query = query.filter_by(host_aggregate=filters['host_aggregate'])
|
||||
if 'goal' in filters:
|
||||
query = query.filter_by(goal=filters['goal'])
|
||||
|
||||
return query
|
||||
|
||||
def _add_audits_filters(self, query, filters):
|
||||
if filters is None:
|
||||
filters = []
|
||||
|
||||
if 'type' in filters:
|
||||
query = query.filter_by(type=filters['type'])
|
||||
if 'state' in filters:
|
||||
query = query.filter_by(state=filters['state'])
|
||||
if 'audit_template_id' in filters:
|
||||
query = query.filter_by(
|
||||
audit_template_id=filters['audit_template_id'])
|
||||
if 'audit_template_uuid' in filters:
|
||||
query = query.join(
|
||||
models.AuditTemplate,
|
||||
models.Audit.audit_template_id == models.AuditTemplate.id)
|
||||
query = query.filter(
|
||||
models.AuditTemplate.uuid == filters['audit_template_uuid'])
|
||||
if 'audit_template_name' in filters:
|
||||
query = query.join(
|
||||
models.AuditTemplate,
|
||||
models.Audit.audit_template_id == models.AuditTemplate.id)
|
||||
query = query.filter(
|
||||
models.AuditTemplate.name ==
|
||||
filters['audit_template_name'])
|
||||
return query
|
||||
|
||||
def _add_action_plans_filters(self, query, filters):
|
||||
if filters is None:
|
||||
filters = []
|
||||
|
||||
if 'state' in filters:
|
||||
query = query.filter_by(state=filters['state'])
|
||||
if 'audit_id' in filters:
|
||||
query = query.filter_by(audit_id=filters['audit_id'])
|
||||
if 'audit_uuid' in filters:
|
||||
query = query.join(models.Audit,
|
||||
models.ActionPlan.audit_id == models.Audit.id)
|
||||
query = query.filter(models.Audit.uuid == filters['audit_uuid'])
|
||||
return query
|
||||
|
||||
def _add_actions_filters(self, query, filters):
|
||||
if filters is None:
|
||||
filters = []
|
||||
|
||||
if 'action_plan_id' in filters:
|
||||
query = query.filter_by(action_plan_id=filters['action_plan_id'])
|
||||
if 'action_plan_uuid' in filters:
|
||||
query = query.join(
|
||||
models.ActionPlan,
|
||||
models.Action.action_plan_id == models.ActionPlan.id)
|
||||
query = query.filter(
|
||||
models.ActionPlan.uuid == filters['action_plan_uuid'])
|
||||
if 'audit_uuid' in filters:
|
||||
stmt = model_query(models.ActionPlan).join(
|
||||
models.Audit,
|
||||
models.Audit.id == models.ActionPlan.audit_id)\
|
||||
.filter_by(uuid=filters['audit_uuid']).subquery()
|
||||
query = query.filter_by(action_plan_id=stmt.c.id)
|
||||
|
||||
if 'state' in filters:
|
||||
query = query.filter_by(state=filters['state'])
|
||||
if 'alarm' in filters:
|
||||
query = query.filter_by(alarm=filters['alarm'])
|
||||
|
||||
return query
|
||||
|
||||
def get_audit_template_list(self, context, filters=None, limit=None,
|
||||
marker=None, sort_key=None, sort_dir=None):
|
||||
|
||||
query = model_query(models.AuditTemplate)
|
||||
query = self._add_audit_templates_filters(query, filters)
|
||||
if not context.show_deleted:
|
||||
query = query.filter_by(deleted_at=None)
|
||||
|
||||
return _paginate_query(models.AuditTemplate, limit, marker,
|
||||
sort_key, sort_dir, query)
|
||||
|
||||
def create_audit_template(self, values):
|
||||
# ensure defaults are present for new audit_templates
|
||||
if not values.get('uuid'):
|
||||
values['uuid'] = utils.generate_uuid()
|
||||
|
||||
audit_template = models.AuditTemplate()
|
||||
audit_template.update(values)
|
||||
|
||||
try:
|
||||
audit_template.save()
|
||||
except db_exc.DBDuplicateEntry:
|
||||
raise exception.AuditTemplateAlreadyExists(uuid=values['uuid'],
|
||||
name=values['name'])
|
||||
return audit_template
|
||||
|
||||
def get_audit_template_by_id(self, context, audit_template_id):
|
||||
query = model_query(models.AuditTemplate)
|
||||
query = query.filter_by(id=audit_template_id)
|
||||
try:
|
||||
audit_template = query.one()
|
||||
if not context.show_deleted:
|
||||
if audit_template.deleted_at is not None:
|
||||
raise exception.AuditTemplateNotFound(
|
||||
audit_template=audit_template_id)
|
||||
return audit_template
|
||||
except NoResultFound:
|
||||
raise exception.AuditTemplateNotFound(
|
||||
audit_template=audit_template_id)
|
||||
|
||||
def get_audit_template_by_uuid(self, context, audit_template_uuid):
|
||||
query = model_query(models.AuditTemplate)
|
||||
query = query.filter_by(uuid=audit_template_uuid)
|
||||
|
||||
try:
|
||||
audit_template = query.one()
|
||||
if not context.show_deleted:
|
||||
if audit_template.deleted_at is not None:
|
||||
raise exception.AuditTemplateNotFound(
|
||||
audit_template=audit_template_uuid)
|
||||
return audit_template
|
||||
except NoResultFound:
|
||||
raise exception.AuditTemplateNotFound(
|
||||
audit_template=audit_template_uuid)
|
||||
|
||||
def get_audit_template_by_name(self, context, audit_template_name):
|
||||
query = model_query(models.AuditTemplate)
|
||||
query = query.filter_by(name=audit_template_name)
|
||||
try:
|
||||
audit_template = query.one()
|
||||
if not context.show_deleted:
|
||||
if audit_template.deleted_at is not None:
|
||||
raise exception.AuditTemplateNotFound(
|
||||
audit_template=audit_template_name)
|
||||
return audit_template
|
||||
except MultipleResultsFound:
|
||||
raise exception.Conflict(
|
||||
'Multiple audit templates exist with same name.'
|
||||
' Please use the audit template uuid instead.')
|
||||
except NoResultFound:
|
||||
raise exception.AuditTemplateNotFound(
|
||||
audit_template=audit_template_name)
|
||||
|
||||
def destroy_audit_template(self, audit_template_id):
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
query = model_query(models.AuditTemplate, session=session)
|
||||
query = add_identity_filter(query, audit_template_id)
|
||||
|
||||
try:
|
||||
query.one()
|
||||
except NoResultFound:
|
||||
raise exception.AuditTemplateNotFound(node=audit_template_id)
|
||||
|
||||
query.delete()
|
||||
|
||||
def update_audit_template(self, audit_template_id, values):
|
||||
if 'uuid' in values:
|
||||
msg = _("Cannot overwrite UUID for an existing AuditTemplate.")
|
||||
raise exception.InvalidParameterValue(err=msg)
|
||||
|
||||
return self._do_update_audit_template(audit_template_id, values)
|
||||
|
||||
def _do_update_audit_template(self, audit_template_id, values):
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
query = model_query(models.AuditTemplate, session=session)
|
||||
query = add_identity_filter(query, audit_template_id)
|
||||
try:
|
||||
ref = query.with_lockmode('update').one()
|
||||
except NoResultFound:
|
||||
raise exception.AuditTemplateNotFound(
|
||||
audit_template=audit_template_id)
|
||||
|
||||
ref.update(values)
|
||||
return ref
|
||||
|
||||
def soft_delete_audit_template(self, audit_template_id):
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
query = model_query(models.AuditTemplate, session=session)
|
||||
query = add_identity_filter(query, audit_template_id)
|
||||
|
||||
try:
|
||||
query.one()
|
||||
except NoResultFound:
|
||||
raise exception.AuditTemplateNotFound(node=audit_template_id)
|
||||
|
||||
query.soft_delete()
|
||||
|
||||
def get_audit_list(self, context, filters=None, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
query = model_query(models.Audit)
|
||||
query = self._add_audits_filters(query, filters)
|
||||
if not context.show_deleted:
|
||||
query = query.filter(~(models.Audit.state == 'DELETED'))
|
||||
|
||||
return _paginate_query(models.Audit, limit, marker,
|
||||
sort_key, sort_dir, query)
|
||||
|
||||
def create_audit(self, values):
|
||||
# ensure defaults are present for new audits
|
||||
if not values.get('uuid'):
|
||||
values['uuid'] = utils.generate_uuid()
|
||||
|
||||
if values.get('state') is None:
|
||||
values['state'] = AuditStatus.PENDING
|
||||
|
||||
audit = models.Audit()
|
||||
audit.update(values)
|
||||
|
||||
try:
|
||||
audit.save()
|
||||
except db_exc.DBDuplicateEntry:
|
||||
raise exception.AuditAlreadyExists(uuid=values['uuid'])
|
||||
return audit
|
||||
|
||||
def get_audit_by_id(self, context, audit_id):
|
||||
query = model_query(models.Audit)
|
||||
query = query.filter_by(id=audit_id)
|
||||
try:
|
||||
audit = query.one()
|
||||
if not context.show_deleted:
|
||||
if audit.state == 'DELETED':
|
||||
raise exception.AuditNotFound(audit=audit_id)
|
||||
return audit
|
||||
except NoResultFound:
|
||||
raise exception.AuditNotFound(audit=audit_id)
|
||||
|
||||
def get_audit_by_uuid(self, context, audit_uuid):
|
||||
query = model_query(models.Audit)
|
||||
query = query.filter_by(uuid=audit_uuid)
|
||||
|
||||
try:
|
||||
audit = query.one()
|
||||
if not context.show_deleted:
|
||||
if audit.state == 'DELETED':
|
||||
raise exception.AuditNotFound(audit=audit_uuid)
|
||||
return audit
|
||||
except NoResultFound:
|
||||
raise exception.AuditNotFound(audit=audit_uuid)
|
||||
|
||||
def destroy_audit(self, audit_id):
|
||||
def is_audit_referenced(session, audit_id):
|
||||
"""Checks whether the audit is referenced by action_plan(s)."""
|
||||
query = model_query(models.ActionPlan, session=session)
|
||||
query = self._add_action_plans_filters(
|
||||
query, {'audit_id': audit_id})
|
||||
return query.count() != 0
|
||||
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
query = model_query(models.Audit, session=session)
|
||||
query = add_identity_filter(query, audit_id)
|
||||
|
||||
try:
|
||||
audit_ref = query.one()
|
||||
except NoResultFound:
|
||||
raise exception.AuditNotFound(audit=audit_id)
|
||||
|
||||
if is_audit_referenced(session, audit_ref['id']):
|
||||
raise exception.AuditReferenced(audit=audit_id)
|
||||
|
||||
query.delete()
|
||||
|
||||
def update_audit(self, audit_id, values):
|
||||
if 'uuid' in values:
|
||||
msg = _("Cannot overwrite UUID for an existing Audit.")
|
||||
raise exception.InvalidParameterValue(err=msg)
|
||||
|
||||
return self._do_update_audit(audit_id, values)
|
||||
|
||||
def _do_update_audit(self, audit_id, values):
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
query = model_query(models.Audit, session=session)
|
||||
query = add_identity_filter(query, audit_id)
|
||||
try:
|
||||
ref = query.with_lockmode('update').one()
|
||||
except NoResultFound:
|
||||
raise exception.AuditNotFound(audit=audit_id)
|
||||
|
||||
ref.update(values)
|
||||
return ref
|
||||
|
||||
def soft_delete_audit(self, audit_id):
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
query = model_query(models.Audit, session=session)
|
||||
query = add_identity_filter(query, audit_id)
|
||||
|
||||
try:
|
||||
query.one()
|
||||
except NoResultFound:
|
||||
raise exception.AuditNotFound(node=audit_id)
|
||||
|
||||
query.soft_delete()
|
||||
|
||||
def get_action_list(self, context, filters=None, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
query = model_query(models.Action)
|
||||
query = self._add_actions_filters(query, filters)
|
||||
if not context.show_deleted:
|
||||
query = query.filter(~(models.Action.state == 'DELETED'))
|
||||
return _paginate_query(models.Action, limit, marker,
|
||||
sort_key, sort_dir, query)
|
||||
|
||||
def create_action(self, values):
|
||||
# ensure defaults are present for new actions
|
||||
if not values.get('uuid'):
|
||||
values['uuid'] = utils.generate_uuid()
|
||||
|
||||
action = models.Action()
|
||||
action.update(values)
|
||||
try:
|
||||
action.save()
|
||||
except db_exc.DBDuplicateEntry:
|
||||
raise exception.ActionAlreadyExists(uuid=values['uuid'])
|
||||
return action
|
||||
|
||||
def get_action_by_id(self, context, action_id):
|
||||
query = model_query(models.Action)
|
||||
query = query.filter_by(id=action_id)
|
||||
try:
|
||||
action = query.one()
|
||||
if not context.show_deleted:
|
||||
if action.state == 'DELETED':
|
||||
raise exception.ActionNotFound(
|
||||
action=action_id)
|
||||
return action
|
||||
except NoResultFound:
|
||||
raise exception.ActionNotFound(action=action_id)
|
||||
|
||||
def get_action_by_uuid(self, context, action_uuid):
|
||||
query = model_query(models.Action)
|
||||
query = query.filter_by(uuid=action_uuid)
|
||||
try:
|
||||
action = query.one()
|
||||
if not context.show_deleted:
|
||||
if action.state == 'DELETED':
|
||||
raise exception.ActionNotFound(
|
||||
action=action_uuid)
|
||||
return action
|
||||
except NoResultFound:
|
||||
raise exception.ActionNotFound(action=action_uuid)
|
||||
|
||||
def destroy_action(self, action_id):
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
query = model_query(models.Action, session=session)
|
||||
query = add_identity_filter(query, action_id)
|
||||
count = query.delete()
|
||||
if count != 1:
|
||||
raise exception.ActionNotFound(action_id)
|
||||
|
||||
def update_action(self, action_id, values):
|
||||
# NOTE(dtantsur): this can lead to very strange errors
|
||||
if 'uuid' in values:
|
||||
msg = _("Cannot overwrite UUID for an existing Action.")
|
||||
raise exception.InvalidParameterValue(err=msg)
|
||||
|
||||
return self._do_update_action(action_id, values)
|
||||
|
||||
def _do_update_action(self, action_id, values):
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
query = model_query(models.Action, session=session)
|
||||
query = add_identity_filter(query, action_id)
|
||||
try:
|
||||
ref = query.with_lockmode('update').one()
|
||||
except NoResultFound:
|
||||
raise exception.ActionNotFound(action=action_id)
|
||||
|
||||
ref.update(values)
|
||||
return ref
|
||||
|
||||
def soft_delete_action(self, action_id):
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
query = model_query(models.Action, session=session)
|
||||
query = add_identity_filter(query, action_id)
|
||||
|
||||
try:
|
||||
query.one()
|
||||
except NoResultFound:
|
||||
raise exception.ActionNotFound(node=action_id)
|
||||
|
||||
query.soft_delete()
|
||||
|
||||
def get_action_plan_list(
|
||||
self, context, columns=None, filters=None, limit=None,
|
||||
marker=None, sort_key=None, sort_dir=None):
|
||||
query = model_query(models.ActionPlan)
|
||||
query = self._add_action_plans_filters(query, filters)
|
||||
if not context.show_deleted:
|
||||
query = query.filter(~(models.ActionPlan.state == 'DELETED'))
|
||||
|
||||
return _paginate_query(models.ActionPlan, limit, marker,
|
||||
sort_key, sort_dir, query)
|
||||
|
||||
def create_action_plan(self, values):
|
||||
# ensure defaults are present for new audits
|
||||
if not values.get('uuid'):
|
||||
values['uuid'] = utils.generate_uuid()
|
||||
|
||||
action_plan = models.ActionPlan()
|
||||
action_plan.update(values)
|
||||
|
||||
try:
|
||||
action_plan.save()
|
||||
except db_exc.DBDuplicateEntry:
|
||||
raise exception.ActionPlanAlreadyExists(uuid=values['uuid'])
|
||||
return action_plan
|
||||
|
||||
def get_action_plan_by_id(self, context, action_plan_id):
|
||||
query = model_query(models.ActionPlan)
|
||||
query = query.filter_by(id=action_plan_id)
|
||||
try:
|
||||
action_plan = query.one()
|
||||
if not context.show_deleted:
|
||||
if action_plan.state == 'DELETED':
|
||||
raise exception.ActionPlanNotFound(
|
||||
action_plan=action_plan_id)
|
||||
return action_plan
|
||||
except NoResultFound:
|
||||
raise exception.ActionPlanNotFound(action_plan=action_plan_id)
|
||||
|
||||
def get_action_plan_by_uuid(self, context, action_plan__uuid):
|
||||
query = model_query(models.ActionPlan)
|
||||
query = query.filter_by(uuid=action_plan__uuid)
|
||||
|
||||
try:
|
||||
action_plan = query.one()
|
||||
if not context.show_deleted:
|
||||
if action_plan.state == 'DELETED':
|
||||
raise exception.ActionPlanNotFound(
|
||||
action_plan=action_plan__uuid)
|
||||
return action_plan
|
||||
except NoResultFound:
|
||||
raise exception.ActionPlanNotFound(action_plan=action_plan__uuid)
|
||||
|
||||
def destroy_action_plan(self, action_plan_id):
|
||||
def is_action_plan_referenced(session, action_plan_id):
|
||||
"""Checks whether the action_plan is referenced by action(s)."""
|
||||
query = model_query(models.Action, session=session)
|
||||
query = self._add_actions_filters(
|
||||
query, {'action_plan_id': action_plan_id})
|
||||
return query.count() != 0
|
||||
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
query = model_query(models.ActionPlan, session=session)
|
||||
query = add_identity_filter(query, action_plan_id)
|
||||
|
||||
try:
|
||||
action_plan_ref = query.one()
|
||||
except NoResultFound:
|
||||
raise exception.ActionPlanNotFound(action_plan=action_plan_id)
|
||||
|
||||
if is_action_plan_referenced(session, action_plan_ref['id']):
|
||||
raise exception.ActionPlanReferenced(
|
||||
action_plan=action_plan_id)
|
||||
|
||||
query.delete()
|
||||
|
||||
def update_action_plan(self, action_plan_id, values):
|
||||
if 'uuid' in values:
|
||||
msg = _("Cannot overwrite UUID for an existing Audit.")
|
||||
raise exception.InvalidParameterValue(err=msg)
|
||||
|
||||
return self._do_update_action_plan(action_plan_id, values)
|
||||
|
||||
def _do_update_action_plan(self, action_plan_id, values):
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
query = model_query(models.ActionPlan, session=session)
|
||||
query = add_identity_filter(query, action_plan_id)
|
||||
try:
|
||||
ref = query.with_lockmode('update').one()
|
||||
except NoResultFound:
|
||||
raise exception.ActionPlanNotFound(action_plan=action_plan_id)
|
||||
|
||||
ref.update(values)
|
||||
return ref
|
||||
|
||||
def soft_delete_action_plan(self, action_plan_id):
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
query = model_query(models.ActionPlan, session=session)
|
||||
query = add_identity_filter(query, action_plan_id)
|
||||
|
||||
try:
|
||||
query.one()
|
||||
except NoResultFound:
|
||||
raise exception.ActionPlanNotFound(node=action_plan_id)
|
||||
|
||||
query.soft_delete()
|
||||
113
watcher/db/sqlalchemy/migration.py
Normal file
113
watcher/db/sqlalchemy/migration.py
Normal file
@@ -0,0 +1,113 @@
|
||||
# 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.
|
||||
|
||||
import os
|
||||
|
||||
import alembic
|
||||
from alembic import config as alembic_config
|
||||
import alembic.migration as alembic_migration
|
||||
from oslo_db import exception as db_exc
|
||||
|
||||
from watcher.db.sqlalchemy import api as sqla_api
|
||||
from watcher.db.sqlalchemy import models
|
||||
|
||||
|
||||
def _alembic_config():
|
||||
path = os.path.join(os.path.dirname(__file__), 'alembic.ini')
|
||||
config = alembic_config.Config(path)
|
||||
return config
|
||||
|
||||
|
||||
def version(config=None, engine=None):
|
||||
"""Current database version.
|
||||
|
||||
:returns: Database version
|
||||
:rtype: string
|
||||
"""
|
||||
if engine is None:
|
||||
engine = sqla_api.get_engine()
|
||||
with engine.connect() as conn:
|
||||
context = alembic_migration.MigrationContext.configure(conn)
|
||||
return context.get_current_revision()
|
||||
|
||||
|
||||
def upgrade(revision, config=None):
|
||||
"""Used for upgrading database.
|
||||
|
||||
:param version: Desired database version
|
||||
:type version: string
|
||||
"""
|
||||
revision = revision or 'head'
|
||||
config = config or _alembic_config()
|
||||
|
||||
alembic.command.upgrade(config, revision or 'head')
|
||||
|
||||
|
||||
def create_schema(config=None, engine=None):
|
||||
"""Create database schema from models description.
|
||||
|
||||
Can be used for initial installation instead of upgrade('head').
|
||||
"""
|
||||
if engine is None:
|
||||
engine = sqla_api.get_engine()
|
||||
|
||||
# NOTE(viktors): If we will use metadata.create_all() for non empty db
|
||||
# schema, it will only add the new tables, but leave
|
||||
# existing as is. So we should avoid of this situation.
|
||||
if version(engine=engine) is not None:
|
||||
raise db_exc.DbMigrationError("DB schema is already under version"
|
||||
" control. Use upgrade() instead")
|
||||
|
||||
models.Base.metadata.create_all(engine)
|
||||
stamp('head', config=config)
|
||||
|
||||
|
||||
def downgrade(revision, config=None):
|
||||
"""Used for downgrading database.
|
||||
|
||||
:param version: Desired database version
|
||||
:type version: string
|
||||
"""
|
||||
revision = revision or 'base'
|
||||
config = config or _alembic_config()
|
||||
return alembic.command.downgrade(config, revision)
|
||||
|
||||
|
||||
def stamp(revision, config=None):
|
||||
"""Stamps database with provided revision.
|
||||
|
||||
Don't run any migrations.
|
||||
|
||||
:param revision: Should match one from repository or head - to stamp
|
||||
database with most recent revision
|
||||
:type revision: string
|
||||
"""
|
||||
config = config or _alembic_config()
|
||||
return alembic.command.stamp(config, revision=revision)
|
||||
|
||||
|
||||
def revision(message=None, autogenerate=False, config=None):
|
||||
"""Creates template for migration.
|
||||
|
||||
:param message: Text that will be used for migration title
|
||||
:type message: string
|
||||
:param autogenerate: If True - generates diff based on current database
|
||||
state
|
||||
:type autogenerate: bool
|
||||
"""
|
||||
config = config or _alembic_config()
|
||||
return alembic.command.revision(config, message=message,
|
||||
autogenerate=autogenerate)
|
||||
189
watcher/db/sqlalchemy/models.py
Normal file
189
watcher/db/sqlalchemy/models.py
Normal file
@@ -0,0 +1,189 @@
|
||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
SQLAlchemy models for watcher service
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_db import options as db_options
|
||||
from oslo_db.sqlalchemy import models
|
||||
import six.moves.urllib.parse as urlparse
|
||||
from sqlalchemy import Column
|
||||
from sqlalchemy import DateTime
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy import ForeignKey
|
||||
from sqlalchemy import Integer
|
||||
from sqlalchemy import schema
|
||||
from sqlalchemy import String
|
||||
from sqlalchemy.types import TypeDecorator, TEXT
|
||||
|
||||
from watcher.common import paths
|
||||
|
||||
|
||||
sql_opts = [
|
||||
cfg.StrOpt('mysql_engine',
|
||||
default='InnoDB',
|
||||
help='MySQL engine to use.')
|
||||
]
|
||||
|
||||
_DEFAULT_SQL_CONNECTION = 'sqlite:///' + paths.state_path_def('watcher.sqlite')
|
||||
|
||||
cfg.CONF.register_opts(sql_opts, 'database')
|
||||
db_options.set_defaults(cfg.CONF, _DEFAULT_SQL_CONNECTION, 'watcher.sqlite')
|
||||
|
||||
|
||||
def table_args():
|
||||
engine_name = urlparse.urlparse(cfg.CONF.database.connection).scheme
|
||||
if engine_name == 'mysql':
|
||||
return {'mysql_engine': cfg.CONF.database.mysql_engine,
|
||||
'mysql_charset': "utf8"}
|
||||
return None
|
||||
|
||||
|
||||
class JsonEncodedType(TypeDecorator):
|
||||
"""Abstract base type serialized as json-encoded string in db."""
|
||||
type = None
|
||||
impl = TEXT
|
||||
|
||||
def process_bind_param(self, value, dialect):
|
||||
if value is None:
|
||||
# Save default value according to current type to keep the
|
||||
# interface the consistent.
|
||||
value = self.type()
|
||||
elif not isinstance(value, self.type):
|
||||
raise TypeError("%s supposes to store %s objects, but %s given"
|
||||
% (self.__class__.__name__,
|
||||
self.type.__name__,
|
||||
type(value).__name__))
|
||||
serialized_value = json.dumps(value)
|
||||
return serialized_value
|
||||
|
||||
def process_result_value(self, value, dialect):
|
||||
if value is not None:
|
||||
value = json.loads(value)
|
||||
return value
|
||||
|
||||
|
||||
class JSONEncodedDict(JsonEncodedType):
|
||||
"""Represents dict serialized as json-encoded string in db."""
|
||||
type = dict
|
||||
|
||||
|
||||
class JSONEncodedList(JsonEncodedType):
|
||||
"""Represents list serialized as json-encoded string in db."""
|
||||
type = list
|
||||
|
||||
|
||||
class WatcherBase(models.SoftDeleteMixin,
|
||||
models.TimestampMixin, models.ModelBase):
|
||||
metadata = None
|
||||
|
||||
def as_dict(self):
|
||||
d = {}
|
||||
for c in self.__table__.columns:
|
||||
d[c.name] = self[c.name]
|
||||
return d
|
||||
|
||||
def save(self, session=None):
|
||||
import watcher.db.sqlalchemy.api as db_api
|
||||
|
||||
if session is None:
|
||||
session = db_api.get_session()
|
||||
|
||||
super(WatcherBase, self).save(session)
|
||||
|
||||
|
||||
Base = declarative_base(cls=WatcherBase)
|
||||
|
||||
|
||||
class AuditTemplate(Base):
|
||||
"""Represents an audit template."""
|
||||
|
||||
__tablename__ = 'audit_templates'
|
||||
__table_args__ = (
|
||||
schema.UniqueConstraint('uuid', name='uniq_audit_templates0uuid'),
|
||||
schema.UniqueConstraint('name', name='uniq_audit_templates0name'),
|
||||
table_args()
|
||||
)
|
||||
id = Column(Integer, primary_key=True)
|
||||
uuid = Column(String(36))
|
||||
name = Column(String(63), nullable=True)
|
||||
description = Column(String(255), nullable=True)
|
||||
host_aggregate = Column(Integer, nullable=True)
|
||||
goal = Column(String(63), nullable=True)
|
||||
extra = Column(JSONEncodedDict)
|
||||
version = Column(String(15), nullable=True)
|
||||
|
||||
|
||||
class Audit(Base):
|
||||
"""Represents an audit."""
|
||||
|
||||
__tablename__ = 'audits'
|
||||
__table_args__ = (
|
||||
schema.UniqueConstraint('uuid', name='uniq_audits0uuid'),
|
||||
table_args()
|
||||
)
|
||||
id = Column(Integer, primary_key=True)
|
||||
uuid = Column(String(36))
|
||||
type = Column(String(20))
|
||||
state = Column(String(20), nullable=True)
|
||||
deadline = Column(DateTime, nullable=True)
|
||||
audit_template_id = Column(Integer, ForeignKey('audit_templates.id'),
|
||||
nullable=False)
|
||||
|
||||
|
||||
class Action(Base):
|
||||
"""Represents an action."""
|
||||
|
||||
__tablename__ = 'actions'
|
||||
__table_args__ = (
|
||||
schema.UniqueConstraint('uuid', name='uniq_actions0uuid'),
|
||||
table_args()
|
||||
)
|
||||
id = Column(Integer, primary_key=True)
|
||||
uuid = Column(String(36))
|
||||
action_plan_id = Column(Integer, ForeignKey('action_plans.id'),
|
||||
nullable=True)
|
||||
# only for the first version
|
||||
action_type = Column(String(255))
|
||||
applies_to = Column(String(255))
|
||||
src = Column(String(255))
|
||||
dst = Column(String(255))
|
||||
parameter = Column(String(255))
|
||||
description = Column(String(255))
|
||||
state = Column(String(20), nullable=True)
|
||||
alarm = Column(String(36))
|
||||
next = Column(String(36), nullable=True)
|
||||
|
||||
|
||||
class ActionPlan(Base):
|
||||
"""Represents an action plan."""
|
||||
|
||||
__tablename__ = 'action_plans'
|
||||
__table_args__ = (
|
||||
schema.UniqueConstraint('uuid', name='uniq_action_plans0uuid'),
|
||||
table_args()
|
||||
)
|
||||
id = Column(Integer, primary_key=True)
|
||||
uuid = Column(String(36))
|
||||
first_action_id = Column(Integer)
|
||||
# first_action_id = Column(Integer, ForeignKeyConstraint(
|
||||
# ['first_action_id'], ['actions.id'], name='fk_first_action_id'),
|
||||
# nullable=True)
|
||||
audit_id = Column(Integer, ForeignKey('audits.id'),
|
||||
nullable=True)
|
||||
state = Column(String(20), nullable=True)
|
||||
Reference in New Issue
Block a user