From 2fdd73816c56b578a65466db4e5a86b9b191e1c1 Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Fri, 6 Jul 2012 18:28:21 +0000 Subject: Refactor instance_usage_audit. Add audit tasklog. The instance usage audit cronjob that generates periodic compute.instance.exists notifications is not particularly scalable. It is run on one server and takes longer as the number of instances grows. This change moves the generation of those events to a periodic task in the compute manager. It also adds an api extension that can be used by administrators to check for errors generating these events. Change-Id: I856d3d0c73c34e570112f1345d306308ef20a9ae --- nova/db/api.py | 67 +++++++++++++-- nova/db/sqlalchemy/api.py | 95 +++++++++++++++++++++- .../migrate_repo/versions/108_task_log.py | 62 ++++++++++++++ nova/db/sqlalchemy/models.py | 14 ++++ 4 files changed, 230 insertions(+), 8 deletions(-) create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/108_task_log.py (limited to 'nova/db') diff --git a/nova/db/api.py b/nova/db/api.py index 695c083c9..fd4babb55 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -585,20 +585,26 @@ def instance_get_all_by_filters(context, filters, sort_key='created_at', sort_dir) -def instance_get_active_by_window(context, begin, end=None, project_id=None): +def instance_get_active_by_window(context, begin, end=None, project_id=None, + host=None): """Get instances active during a certain time window. - Specifying a project_id will filter for a certain project.""" - return IMPL.instance_get_active_by_window(context, begin, end, project_id) + Specifying a project_id will filter for a certain project. + Specifying a host will filter for instances on a given compute host. + """ + return IMPL.instance_get_active_by_window(context, begin, end, + project_id, host) def instance_get_active_by_window_joined(context, begin, end=None, - project_id=None): + project_id=None, host=None): """Get instances and joins active during a certain time window. - Specifying a project_id will filter for a certain project.""" + Specifying a project_id will filter for a certain project. + Specifying a host will filter for instances on a given compute host. + """ return IMPL.instance_get_active_by_window_joined(context, begin, end, - project_id) + project_id, host) def instance_get_all_by_project(context, project_id): @@ -1948,3 +1954,52 @@ def get_instance_uuid_by_ec2_id(context, instance_id): def ec2_instance_create(context, instance_ec2_id): """Create the ec2 id to instance uuid mapping on demand""" return IMPL.ec2_instance_create(context, instance_ec2_id) + + +#################### + + +def task_log_end_task(context, task_name, + period_beginning, + period_ending, + host, + errors, + message=None, + session=None): + """Mark a task as complete for a given host/time period""" + return IMPL.task_log_end_task(context, task_name, + period_beginning, + period_ending, + host, + errors, + message, + session) + + +def task_log_begin_task(context, task_name, + period_beginning, + period_ending, + host, + task_items=None, + message=None, + session=None): + """Mark a task as started for a given host/time period""" + return IMPL.task_log_begin_task(context, task_name, + period_beginning, + period_ending, + host, + task_items, + message, + session) + + +def task_log_get_all(context, task_name, period_beginning, + period_ending, host=None, state=None, session=None): + return IMPL.task_log_get_all(context, task_name, period_beginning, + period_ending, host, state, session) + + +def task_log_get(context, task_name, period_beginning, + period_ending, host, state=None, session=None): + return IMPL.task_log_get(context, task_name, period_beginning, + period_ending, host, state, session) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index e6d7f88c7..bb79944c9 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1564,7 +1564,8 @@ def instance_get_all_by_filters(context, filters, sort_key, sort_dir): @require_context -def instance_get_active_by_window(context, begin, end=None, project_id=None): +def instance_get_active_by_window(context, begin, end=None, + project_id=None, host=None): """Return instances that were active during window.""" session = get_session() query = session.query(models.Instance) @@ -1575,13 +1576,15 @@ def instance_get_active_by_window(context, begin, end=None, project_id=None): query = query.filter(models.Instance.launched_at < end) if project_id: query = query.filter_by(project_id=project_id) + if host: + query = query.filter_by(host=host) return query.all() @require_admin_context def instance_get_active_by_window_joined(context, begin, end=None, - project_id=None): + project_id=None, host=None): """Return instances and joins that were active during window.""" session = get_session() query = session.query(models.Instance) @@ -1596,6 +1599,8 @@ def instance_get_active_by_window_joined(context, begin, end=None, query = query.filter(models.Instance.launched_at < end) if project_id: query = query.filter_by(project_id=project_id) + if host: + query = query.filter_by(host=host) return query.all() @@ -5189,3 +5194,89 @@ def get_instance_uuid_by_ec2_id(context, instance_id, session=None): @require_context def _ec2_instance_get_query(context, session=None): return model_query(context, models.InstanceIdMapping, session=session) + + +@require_admin_context +def task_log_get(context, task_name, period_beginning, + period_ending, host, state=None, session=None): + query = model_query(context, models.TaskLog, session=session).\ + filter_by(task_name=task_name).\ + filter_by(period_beginning=period_beginning).\ + filter_by(period_ending=period_ending).\ + filter_by(host=host) + if state is not None: + query = query.filter_by(state=state) + + return query.first() + + +@require_admin_context +def task_log_get_all(context, task_name, period_beginning, + period_ending, host=None, state=None, session=None): + query = model_query(context, models.TaskLog, session=session).\ + filter_by(task_name=task_name).\ + filter_by(period_beginning=period_beginning).\ + filter_by(period_ending=period_ending) + if host is not None: + query = query.filter_by(host=host) + if state is not None: + query = query.filter_by(state=state) + return query.all() + + +@require_admin_context +def task_log_begin_task(context, task_name, + period_beginning, + period_ending, + host, + task_items=None, + message=None, + session=None): + session = session or get_session() + with session.begin(): + task = task_log_get(context, task_name, + period_beginning, + period_ending, + host, + session=session) + if task: + #It's already run(ning)! + raise exception.TaskAlreadyRunning(task_name=task_name, host=host) + task = models.TaskLog() + task.task_name = task_name + task.period_beginning = period_beginning + task.period_ending = period_ending + task.host = host + task.state = "RUNNING" + if message: + task.message = message + if task_items: + task.task_items = task_items + task.save(session=session) + return task + + +@require_admin_context +def task_log_end_task(context, task_name, + period_beginning, + period_ending, + host, + errors, + message=None, + session=None): + session = session or get_session() + with session.begin(): + task = task_log_get(context, task_name, + period_beginning, + period_ending, + host, + session=session) + if not task: + #It's not running! + raise exception.TaskNotRunning(task_name=task_name, host=host) + task.state = "DONE" + if message: + task.message = message + task.errors = errors + task.save(session=session) + return task diff --git a/nova/db/sqlalchemy/migrate_repo/versions/108_task_log.py b/nova/db/sqlalchemy/migrate_repo/versions/108_task_log.py new file mode 100644 index 000000000..e6aedc1a6 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/108_task_log.py @@ -0,0 +1,62 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright 2012 SINA Corp. +# 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. + +from sqlalchemy import Boolean, Column, DateTime, Integer +from sqlalchemy import Index, MetaData, String, Table + + +def upgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + # create new table + task_log = Table('task_log', meta, + Column('created_at', DateTime(timezone=False)), + Column('updated_at', DateTime(timezone=False)), + Column('deleted_at', DateTime(timezone=False)), + Column('deleted', + Boolean(create_constraint=True, name=None)), + Column('id', Integer(), + primary_key=True, + nullable=False, + autoincrement=True), + Column('task_name', String(255), nullable=False), + Column('state', String(255), nullable=False), + Column('host', String(255), index=True, nullable=False), + Column('period_beginning', String(255), + index=True, nullable=False), + Column('period_ending', String(255), index=True, nullable=False), + Column('message', String(255), nullable=False), + Column('task_items', Integer()), + Column('errors', Integer()), + ) + try: + task_log.create() + except Exception: + meta.drop_all(tables=[task_log]) + raise + + if migrate_engine.name == "mysql": + migrate_engine.execute("ALTER TABLE task_log " + "Engine=InnoDB") + + +def downgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + task_log = Table('task_log', meta, autoload=True) + task_log.drop() diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 335989135..d117d9361 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -1043,3 +1043,17 @@ class InstanceIdMapping(BASE, NovaBase): __tablename__ = 'instance_id_mappings' id = Column(Integer, primary_key=True, nullable=False, autoincrement=True) uuid = Column(String(36), nullable=False) + + +class TaskLog(BASE, NovaBase): + """Audit log for background periodic tasks""" + __tablename__ = 'task_log' + id = Column(Integer, primary_key=True, nullable=False, autoincrement=True) + task_name = Column(String(255), nullable=False) + state = Column(String(255), nullable=False) + host = Column(String(255)) + period_beginning = Column(String(255), default=timeutils.utcnow) + period_ending = Column(String(255), default=timeutils.utcnow) + message = Column(String(255), nullable=False) + task_items = Column(Integer(), default=0) + errors = Column(Integer(), default=0) -- cgit