heat/heat/db/sqlalchemy/api.py

500 lines
14 KiB
Python

# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# 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.
'''Implementation of SQLAlchemy backend.'''
from datetime import datetime
from datetime import timedelta
from oslo.config import cfg
import sqlalchemy
from sqlalchemy.orm.session import Session
cfg.CONF.import_opt('max_events_per_stack', 'heat.common.config')
from heat.openstack.common.gettextutils import _
from heat.common import crypt
from heat.common import exception
from heat.db.sqlalchemy import models
from heat.db.sqlalchemy.session import get_engine
from heat.db.sqlalchemy.session import get_session
def model_query(context, *args):
session = _session(context)
query = session.query(*args)
return query
def soft_delete_aware_query(context, *args, **kwargs):
"""Stack query helper that accounts for context's `show_deleted` field.
:param show_deleted: if present, overrides context's show_deleted field.
"""
query = model_query(context, *args)
show_deleted = kwargs.get('show_deleted')
if not show_deleted:
query = query.filter_by(deleted_at=None)
return query
def _session(context):
return (context and context.session) or get_session()
def raw_template_get(context, template_id):
result = model_query(context, models.RawTemplate).get(template_id)
if not result:
raise exception.NotFound('raw template with id %s not found' %
template_id)
return result
def raw_template_create(context, values):
raw_template_ref = models.RawTemplate()
raw_template_ref.update(values)
raw_template_ref.save(_session(context))
return raw_template_ref
def resource_get(context, resource_id):
result = model_query(context, models.Resource).get(resource_id)
if not result:
raise exception.NotFound("resource with id %s not found" % resource_id)
return result
def resource_get_by_name_and_stack(context, resource_name, stack_id):
result = model_query(context, models.Resource).\
filter_by(name=resource_name).\
filter_by(stack_id=stack_id).first()
return result
def resource_get_by_physical_resource_id(context, physical_resource_id):
results = (model_query(context, models.Resource)
.filter_by(nova_instance=physical_resource_id)
.all())
for result in results:
if context is None or result.stack.tenant == context.tenant_id:
return result
return None
def resource_get_all(context):
results = model_query(context, models.Resource).all()
if not results:
raise exception.NotFound('no resources were found')
return results
def resource_data_get(resource, key):
"""Lookup value of resource's data by key."""
result = resource_data_get_by_key(resource.context, resource.id, key)
return result.value
def _encrypt(value):
return crypt.encrypt(value.encode('utf-8'))
def _decrypt(enc_value):
value = crypt.decrypt(enc_value)
if value is not None:
return unicode(value, 'utf-8')
def resource_data_get_by_key(context, resource_id, key):
result = (model_query(context, models.ResourceData)
.filter_by(resource_id=resource_id)
.filter_by(key=key)
.first())
if not result:
raise exception.NotFound('No resource data found')
if result.redact and result.value:
result.value = _decrypt(result.value)
return result
def resource_data_set(resource, key, value, redact=False):
"""Save resource's key/value pair to database."""
if redact:
value = _encrypt(value)
try:
current = resource_data_get_by_key(resource.context, resource.id, key)
except exception.NotFound:
current = models.ResourceData()
current.key = key
current.resource_id = resource.id
current.redact = redact
current.value = value
current.save()
return current
def resource_exchange_stacks(context, resource_id1, resource_id2):
query = model_query(context, models.Resource)
session = query.session
session.begin()
res1 = query.get(resource_id1)
res2 = query.get(resource_id2)
res1.stack, res2.stack = res2.stack, res1.stack
session.commit()
def resource_data_delete(resource, key):
result = resource_data_get_by_key(resource.context, resource.id, key)
result.delete()
def resource_create(context, values):
resource_ref = models.Resource()
resource_ref.update(values)
resource_ref.save(_session(context))
return resource_ref
def resource_get_all_by_stack(context, stack_id):
results = model_query(context, models.Resource).\
filter_by(stack_id=stack_id).all()
if not results:
raise exception.NotFound("no resources for stack_id %s were found" %
stack_id)
return results
def stack_get_by_name(context, stack_name, owner_id=None):
query = soft_delete_aware_query(context, models.Stack).\
filter_by(tenant=context.tenant_id).\
filter_by(name=stack_name).\
filter_by(owner_id=owner_id)
return query.first()
def stack_get(context, stack_id, admin=False, show_deleted=False):
result = model_query(context, models.Stack).get(stack_id)
if result is None or result.deleted_at is not None and not show_deleted:
return None
# If the admin flag is True, we allow retrieval of a specific
# stack without the tenant scoping
if admin:
return result
if (result is not None and context is not None and
result.tenant != context.tenant_id):
return None
return result
def stack_get_all(context):
results = soft_delete_aware_query(context, models.Stack).\
filter_by(owner_id=None).all()
return results
def _query_stack_get_all_by_tenant(context):
query = soft_delete_aware_query(context, models.Stack).\
filter_by(owner_id=None).\
filter_by(tenant=context.tenant_id)
return query
def stack_get_all_by_tenant(context):
return _query_stack_get_all_by_tenant(context).all()
def stack_count_all_by_tenant(context):
return _query_stack_get_all_by_tenant(context).count()
def stack_create(context, values):
stack_ref = models.Stack()
stack_ref.update(values)
stack_ref.save(_session(context))
return stack_ref
def stack_update(context, stack_id, values):
stack = stack_get(context, stack_id)
if not stack:
raise exception.NotFound('Attempt to update a stack with id: %s %s' %
(stack_id, 'that does not exist'))
old_template_id = stack.raw_template_id
stack.update(values)
stack.save(_session(context))
def stack_delete(context, stack_id):
s = stack_get(context, stack_id)
if not s:
raise exception.NotFound('Attempt to delete a stack with id: %s %s' %
(stack_id, 'that does not exist'))
session = Session.object_session(s)
for r in s.resources:
session.delete(r)
s.soft_delete(session=session)
session.flush()
def user_creds_create(context):
values = context.to_dict()
user_creds_ref = models.UserCreds()
if values.get('trust_id'):
user_creds_ref.trust_id = _encrypt(values.get('trust_id'))
user_creds_ref.trustor_user_id = values.get('trustor_user_id')
user_creds_ref.username = None
user_creds_ref.password = None
user_creds_ref.tenant = values.get('tenant')
user_creds_ref.tenant_id = values.get('tenant_id')
else:
user_creds_ref.update(values)
user_creds_ref.password = _encrypt(values['password'])
user_creds_ref.save(_session(context))
return user_creds_ref
def user_creds_get(user_creds_id):
db_result = model_query(None, models.UserCreds).get(user_creds_id)
# Return a dict copy of db results, do not decrypt details into db_result
# or it can be committed back to the DB in decrypted form
result = dict(db_result)
result['password'] = _decrypt(result['password'])
result['trust_id'] = _decrypt(result['trust_id'])
return result
def event_get(context, event_id):
result = model_query(context, models.Event).get(event_id)
return result
def event_get_all(context):
stacks = soft_delete_aware_query(context, models.Stack)
stack_ids = [stack.id for stack in stacks]
results = model_query(context, models.Event).\
filter(models.Event.stack_id.in_(stack_ids)).all()
return results
def event_get_all_by_tenant(context):
stacks = soft_delete_aware_query(context, models.Stack).\
filter_by(tenant=context.tenant_id).all()
results = []
for stack in stacks:
results.extend(model_query(context, models.Event).
filter_by(stack_id=stack.id).all())
return results
def _query_all_by_stack(context, stack_id):
query = model_query(context, models.Event).\
filter_by(stack_id=stack_id)
return query
def event_get_all_by_stack(context, stack_id):
return _query_all_by_stack(context, stack_id).all()
def event_count_all_by_stack(context, stack_id):
return _query_all_by_stack(context, stack_id).count()
def _delete_event_rows(context, stack_id, limit):
# MySQL does not support LIMIT in subqueries,
# sqlite does not support JOIN in DELETE.
# So we must manually supply the IN() values.
# pgsql SHOULD work with the pure DELETE/JOIN below but that must be
# confirmed via integration tests.
query = _query_all_by_stack(context, stack_id)
session = _session(context)
if 'postgres' not in session.connection().dialect.name:
ids = [r.id for r in query.order_by(
models.Event.id).limit(limit).all()]
q = session.query(models.Event).filter(
models.Event.id.in_(ids))
else:
stmt = session.query(
models.Event.id).filter_by(
stack_id=stack_id).order_by(
models.Event.id).limit(limit).subquery()
q = query.join(stmt, models.Event.id == stmt.c.id)
return q.delete(synchronize_session='fetch')
def event_create(context, values):
if 'stack_id' in values and cfg.CONF.max_events_per_stack:
if ((event_count_all_by_stack(context, values['stack_id']) >=
cfg.CONF.max_events_per_stack)):
# prune
_delete_event_rows(
context, values['stack_id'], cfg.CONF.event_purge_batch_size)
event_ref = models.Event()
event_ref.update(values)
event_ref.save(_session(context))
return event_ref
def watch_rule_get(context, watch_rule_id):
result = model_query(context, models.WatchRule).get(watch_rule_id)
return result
def watch_rule_get_by_name(context, watch_rule_name):
result = model_query(context, models.WatchRule).\
filter_by(name=watch_rule_name).first()
return result
def watch_rule_get_all(context):
results = model_query(context, models.WatchRule).all()
return results
def watch_rule_get_all_by_stack(context, stack_id):
results = model_query(context, models.WatchRule).\
filter_by(stack_id=stack_id).all()
return results
def watch_rule_create(context, values):
obj_ref = models.WatchRule()
obj_ref.update(values)
obj_ref.save(_session(context))
return obj_ref
def watch_rule_update(context, watch_id, values):
wr = watch_rule_get(context, watch_id)
if not wr:
raise exception.NotFound('Attempt to update a watch with id: %s %s' %
(watch_id, 'that does not exist'))
wr.update(values)
wr.save(_session(context))
def watch_rule_delete(context, watch_id):
wr = watch_rule_get(context, watch_id)
if not wr:
raise exception.NotFound('Attempt to delete watch_rule: %s %s' %
(watch_id, 'that does not exist'))
session = Session.object_session(wr)
for d in wr.watch_data:
session.delete(d)
session.delete(wr)
session.flush()
def watch_data_create(context, values):
obj_ref = models.WatchData()
obj_ref.update(values)
obj_ref.save(_session(context))
return obj_ref
def watch_data_get_all(context):
results = model_query(context, models.WatchData).all()
return results
def watch_data_delete(context, watch_name):
ds = model_query(context, models.WatchRule).\
filter_by(name=watch_name).all()
if not ds:
raise exception.NotFound('Attempt to delete watch_data: %s %s' %
(watch_name, 'that does not exist'))
session = Session.object_session(ds)
for d in ds:
session.delete(d)
session.flush()
def purge_deleted(age):
if age is not None:
try:
age = int(age)
except ValueError:
raise exception.Error(_("age should be an integer"))
if age < 0:
raise exception.Error(_("age should be a positive integer"))
else:
age = 90
time_line = datetime.now() - timedelta(days=age)
engine = get_engine()
meta = sqlalchemy.MetaData()
meta.bind = engine
stack = sqlalchemy.Table('stack', meta, autoload=True)
event = sqlalchemy.Table('event', meta, autoload=True)
raw_template = sqlalchemy.Table('raw_template', meta, autoload=True)
user_creds = sqlalchemy.Table('user_creds', meta, autoload=True)
stmt = sqlalchemy.select([stack.c.id,
stack.c.raw_template_id,
stack.c.user_creds_id]).\
where(stack.c.deleted_at < time_line)
deleted_stacks = engine.execute(stmt)
for s in deleted_stacks:
event_del = event.delete().where(event.c.stack_id == s[0])
engine.execute(event_del)
stack_del = stack.delete().where(stack.c.id == s[0])
engine.execute(stack_del)
raw_template_del = raw_template.delete().\
where(raw_template.c.id == s[1])
engine.execute(raw_template_del)
user_creds_del = user_creds.delete().where(user_creds.c.id == s[2])
engine.execute(user_creds_del)