Skip to content

Commit

Permalink
Provide config option to cap events per stack
Browse files Browse the repository at this point in the history
Previously users could write to the events table infinitely. With this
change Heat will automatically prune some events if the maximum is
reached. The behavior can be disabled by setting max_events_per_stack to
zero.

Change-Id: I4fb2fefbd9bcd10ce1767ddf58a870206a6482a1
Fixes-Bug: #1209492
  • Loading branch information
SpamapS committed Sep 26, 2013
1 parent 2af3683 commit 3226077
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 3 deletions.
11 changes: 11 additions & 0 deletions etc/heat/heat.conf.sample
Expand Up @@ -39,6 +39,17 @@
# one time. (integer value)
#max_stacks_per_tenant=100

# Controls how many events will be pruned whenever a stack's
# events exceed max_events_per_stack. Set this lower to keep
# more events at the expense of more frequent purges. (integer
# value)
#event_purge_batch_size=10

# Maximum events that will be available per stack. Older
# events will be deleted when this is reached. Set to 0 for
# unlimited events per stack. (integer value)
#max_events_per_stack=1000

# Name of the engine node. This can be an opaque identifier.It
# is not necessarily a hostname, FQDN, or IP address. (string
# value)
Expand Down
15 changes: 12 additions & 3 deletions heat/common/config.py
Expand Up @@ -104,9 +104,18 @@
cfg.IntOpt('max_stacks_per_tenant',
default=100,
help=_('Maximum number of stacks any one tenant may have'
' active at one time.'))]


' active at one time.')),
cfg.IntOpt('event_purge_batch_size',
default=10,
help=_('Controls how many events will be pruned whenever a '
' stack\'s events exceed max_events_per_stack. Set this'
' lower to keep more events at the expense of more'
' frequent purges.')),
cfg.IntOpt('max_events_per_stack',
default=1000,
help=_('Maximum events that will be available per stack. Older'
' events will be deleted when this is reached. Set to 0'
' for unlimited events per stack.'))]
rpc_opts = [
cfg.StrOpt('host',
default=socket.gethostname(),
Expand Down
31 changes: 31 additions & 0 deletions heat/db/sqlalchemy/api.py
Expand Up @@ -17,9 +17,12 @@
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
Expand Down Expand Up @@ -339,7 +342,35 @@ 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))
Expand Down
23 changes: 23 additions & 0 deletions heat/tests/test_event.py
Expand Up @@ -12,6 +12,10 @@
# License for the specific language governing permissions and limitations
# under the License.

from oslo.config import cfg

cfg.CONF.import_opt('event_purge_batch_size', 'heat.common.config')
cfg.CONF.import_opt('max_events_per_stack', 'heat.common.config')

import heat.db.api as db_api
from heat.engine import parser
Expand Down Expand Up @@ -100,6 +104,25 @@ def test_load_given_stack_event(self):
self.assertNotEqual(None, loaded_e.timestamp)
self.assertEqual({'Foo': 'goo'}, loaded_e.resource_properties)

def test_store_caps_events(self):
cfg.CONF.set_override('event_purge_batch_size', 1)
cfg.CONF.set_override('max_events_per_stack', 1)
self.resource.resource_id_set('resource_physical_id')

e = event.Event(self.ctx, self.stack, 'TEST', 'IN_PROGRESS', 'Testing',
'alabama', self.resource.properties,
self.resource.name, self.resource.type())
e.store()
self.assertEquals(1, len(db_api.event_get_all_by_stack(self.ctx,
self.stack.id)))
e = event.Event(self.ctx, self.stack, 'TEST', 'IN_PROGRESS', 'Testing',
'arizona', self.resource.properties,
self.resource.name, self.resource.type())
e.store()
events = db_api.event_get_all_by_stack(self.ctx, self.stack.id)
self.assertEquals(1, len(events))
self.assertEqual('arizona', events[0].physical_resource_id)

def test_identifier(self):
e = event.Event(self.ctx, self.stack, 'TEST', 'IN_PROGRESS', 'Testing',
'wibble', self.resource.properties,
Expand Down

0 comments on commit 3226077

Please sign in to comment.