From ebae1b170f0c9ba1e3a8dc6e184426e7fefd03fe Mon Sep 17 00:00:00 2001 From: Benjamin Bertrand <benjamin.bertrand@esss.se> Date: Wed, 27 Mar 2019 22:20:38 +0100 Subject: [PATCH] Add depends_on field on Task Allow to trigger a new core service update only if there is none depending on the triggered inventory update. JIRA INFRA-895 --- app/models.py | 7 +++++ app/static/js/tasks.js | 21 ++++++++++---- app/templates/_helpers.html | 10 +++++++ app/templates/task/tasks.html | 1 + app/templates/task/view_task.html | 5 ++++ app/utils.py | 15 ++++++---- ...777d44627f_add_depends_on_field_on_task.py | 28 +++++++++++++++++++ 7 files changed, 76 insertions(+), 11 deletions(-) create mode 100644 migrations/versions/cb777d44627f_add_depends_on_field_on_task.py diff --git a/app/models.py b/app/models.py index 965bb63..4fd0936 100644 --- a/app/models.py +++ b/app/models.py @@ -327,6 +327,7 @@ class User(db.Model, UserMixin): command=job.get_call_string(), status=JobStatus(job.status), user=self, + depends_on_id=kwargs.get("depends_on", None), ) db.session.add(task) return task @@ -1641,6 +1642,11 @@ class Task(db.Model): nullable=False, default=utils.fetch_current_user_id, ) + depends_on_id = db.Column(postgresql.UUID, db.ForeignKey("task.id")) + + reverse_dependencies = db.relationship( + "Task", backref=db.backref("depends_on", remote_side=[id]) + ) @property def awx_job_url(self): @@ -1672,6 +1678,7 @@ class Task(db.Model): "awx_resource": self.awx_resource, "awx_job_id": self.awx_job_id, "awx_job_url": self.awx_job_url, + "depends_on": self.depends_on_id, "command": self.command, "exception": self.exception, "user": str(self.user), diff --git a/app/static/js/tasks.js b/app/static/js/tasks.js index 5d1730c..80a75d0 100644 --- a/app/static/js/tasks.js +++ b/app/static/js/tasks.js @@ -1,5 +1,14 @@ $(document).ready(function() { + function render_task_link(data) { + // render funtion to create link to Task view page + if ( data === null ) { + return data; + } + var url = $SCRIPT_ROOT + "/task/tasks/view/" + data; + return '<a href="' + url + '">' + data + '</a>'; + } + $("#allTasks").on('change', function() { // reload the data from the Ajax source tasks_table.ajax.reload(); @@ -21,12 +30,7 @@ $(document).ready(function() { "columns": [ { data: 'id', render: function(data, type, row) { - // render funtion to create link to Task view page - if ( data === null ) { - return data; - } - var url = $SCRIPT_ROOT + "/task/tasks/view/" + data; - return '<a href="' + url + '">' + data + '</a>'; + return render_task_link(data); } }, { data: 'name' }, @@ -42,6 +46,11 @@ $(document).ready(function() { return '<a href="' + row.awx_job_url + '">' + row.awx_job_id + '</a>'; } }, + { data: 'depends_on', + render: function(data, type, row) { + return render_task_link(data); + } + }, { data: 'command' }, { data: 'user' }, ] diff --git a/app/templates/_helpers.html b/app/templates/_helpers.html index 353685d..1bf33fc 100644 --- a/app/templates/_helpers.html +++ b/app/templates/_helpers.html @@ -97,6 +97,16 @@ {% endfor %} {%- endmacro %} +{% macro link_to_task(task) -%} + <a href="{{ url_for('task.view_task', id_=task.id) }}">{{ task.id }}</a> +{%- endmacro %} + +{% macro link_to_tasks(tasks) -%} + {% for task in tasks %} + {{ link_to_task(task) }} + {% endfor %} +{%- endmacro %} + {% macro render_field(field) -%} {% set field_class = kwargs.pop('class_', '') + ' form-control' %} {% set label_size = kwargs.pop('label_size', '2') %} diff --git a/app/templates/task/tasks.html b/app/templates/task/tasks.html index 2c0c6f8..64ea3fd 100644 --- a/app/templates/task/tasks.html +++ b/app/templates/task/tasks.html @@ -35,6 +35,7 @@ <th>Ended at</th> <th>Status</th> <th>AWX job</th> + <th>Depends on</th> <th>Command</th> <th>User</th> </tr> diff --git a/app/templates/task/view_task.html b/app/templates/task/view_task.html index bfd1a8f..15ae5fb 100644 --- a/app/templates/task/view_task.html +++ b/app/templates/task/view_task.html @@ -1,4 +1,5 @@ {% extends "task/tasks.html" %} +{% from "_helpers.html" import link_to_task, link_to_tasks %} {% block title %}View Task - CSEntry{% endblock %} @@ -30,6 +31,10 @@ {% else %} <dd class="col-sm-9">{{ task.awx_job_id }}</dd> {% endif %} + <dt class="col-sm-3">Depends on</dt> + <dd class="col-sm-9">{{ link_to_task(task.depends_on) }}</dd> + <dt class="col-sm-3">Reverse dependencies</dt> + <dd class="col-sm-9">{{ link_to_tasks(task.reverse_dependencies) }}</dd> {% if task.exception %} <dt class="col-sm-3">Exception</dt> <dd class="col-sm-9"><pre>{{ task.exception }}</pre></dd> diff --git a/app/utils.py b/app/utils.py index a4c2f95..6a20b77 100644 --- a/app/utils.py +++ b/app/utils.py @@ -240,11 +240,16 @@ def trigger_core_services_update(): """ # Start by triggering an inventory update inventory_task = trigger_inventory_update() - # If there is already a core service update in queue, we could probably skip starting a new - # one but we would have to be sure that the inventory sync it depends on didn't already - # start - # Without this optimization, we might trigger a job that won't change anything. - # If jobs can run in parallel this shouldn't be an issue. + # If there is already a core service update depending on this inventory update, + # there is no need to trigger a new one + for reverse_dependency in inventory_task.reverse_dependencies: + if reverse_dependency.name == "trigger_core_services_update": + current_app.logger.info( + f'Already one "trigger_core_services_update" {reverse_dependency} ' + f"depending on the inventory update {inventory_task}. " + "No need to trigger a new one." + ) + return None job_template = current_app.config["AWX_CORE_SERVICES_UPDATE"] resource = current_app.config.get("AWX_CORE_SERVICES_UPDATE_RESOURCE", "job") kwargs = { diff --git a/migrations/versions/cb777d44627f_add_depends_on_field_on_task.py b/migrations/versions/cb777d44627f_add_depends_on_field_on_task.py new file mode 100644 index 0000000..e234cbc --- /dev/null +++ b/migrations/versions/cb777d44627f_add_depends_on_field_on_task.py @@ -0,0 +1,28 @@ +"""Add depends_on field on Task + +Revision ID: cb777d44627f +Revises: 166572b78449 +Create Date: 2019-03-27 20:51:05.385857 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "cb777d44627f" +down_revision = "166572b78449" +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column("task", sa.Column("depends_on_id", postgresql.UUID(), nullable=True)) + op.create_foreign_key( + op.f("fk_task_depends_on_id_task"), "task", "task", ["depends_on_id"], ["id"] + ) + + +def downgrade(): + op.drop_constraint(op.f("fk_task_depends_on_id_task"), "task", type_="foreignkey") + op.drop_column("task", "depends_on_id") -- GitLab