diff --git a/app/models.py b/app/models.py index 80ccc1c562ee23950260bf34d3e6f37936fb14db..ed3672993ef3ea71a25caa148f74f45f9b93bb39 100644 --- a/app/models.py +++ b/app/models.py @@ -12,6 +12,7 @@ This module implements the models used in the app. import ipaddress import string import qrcode +import itertools import urllib.parse import sqlalchemy as sa from enum import Enum @@ -243,7 +244,10 @@ class User(db.Model, UserMixin): return [favorite for favorites in favorites_list for favorite in favorites] def launch_task(self, name, func, *args, **kwargs): - """Launch a task in the background using RQ""" + """Launch a task in the background using RQ + + The task is added to the session but not committed. + """ q = Queue() job = q.enqueue(f"app.tasks.{func}", *args, **kwargs) # The status will be set to QUEUED or DEFERRED @@ -255,7 +259,6 @@ class User(db.Model, UserMixin): user=self, ) db.session.add(task) - db.session.commit() return task def get_tasks(self, all=False): @@ -1097,6 +1100,22 @@ class Task(db.Model): } +@sa.event.listens_for(db.session, "before_flush") +def before_flush(session, flush_context, instances): + """Before flush hook + + Used to trigger core services update on any Interface modification. + + See http://docs.sqlalchemy.org/en/latest/orm/session_events.html#before-flush + """ + for instance in itertools.chain(session.new, session.dirty, session.deleted): + if isinstance(instance, Interface): + # In session.dirty, we could check session.is_modified(instance) + # but it's always True due to the updated_at column. + utils.trigger_core_services_update() + break + + # call configure_mappers after defining all the models # required by sqlalchemy_continuum sa.orm.configure_mappers() diff --git a/app/network/views.py b/app/network/views.py index 9cbbbd4aac0db97e3d0584d4a3b594309b1e2d23..c0373ed2f0cb290ba1ceb8dab1a819bbd8244c8b 100644 --- a/app/network/views.py +++ b/app/network/views.py @@ -94,7 +94,6 @@ def create_host(): flash(f"{e}", "error") else: flash(f"Host {host} created!", "success") - tasks.trigger_core_services_update() # Save network_id to the session to retrieve it after the redirect session["network_id"] = network_id return redirect(url_for("network.view_host", name=host.name)) @@ -120,6 +119,7 @@ def view_host(name): task = tasks.trigger_vm_creation( name, interface, int(form.memory.data) * 1000, form.cores.data ) + db.session.commit() current_app.logger.info(f"Creation of {name} requested: task {task.id}") flash( f"Creation of {name} requested! Refresh the page to update the status.", @@ -147,7 +147,6 @@ def edit_host(name): flash(f"{e}", "error") else: flash(f"Host {host} updated!", "success") - tasks.trigger_core_services_update() return redirect(url_for("network.view_host", name=host.name)) return render_template("network/edit_host.html", form=form) @@ -187,7 +186,6 @@ def create_interface(hostname): flash(f"{e}", "error") else: flash(f"Host {interface} created!", "success") - tasks.trigger_core_services_update() return redirect(url_for("network.create_interface", hostname=hostname)) return render_template( "network/create_interface.html", form=form, hostname=hostname @@ -261,7 +259,6 @@ def edit_interface(name): flash(f"{e}", "error") else: flash(f"Interface {interface} updated!", "success") - tasks.trigger_core_services_update() return redirect(url_for("network.view_host", name=interface.host.name)) return render_template( "network/edit_interface.html", form=form, hostname=interface.host.name diff --git a/app/tasks.py b/app/tasks.py index 3c8c69e49a026a267e3d88c7d16898ac1366e171..97d938a729474233d7c2b11ed745241550ebc558 100644 --- a/app/tasks.py +++ b/app/tasks.py @@ -118,31 +118,6 @@ def trigger_vm_creation(name, interface, memory, cores): return task -def trigger_core_services_update(): - """Trigger a job to update the core services on the TN (DNS and DHCP) - - This function should be called every time an host or interface is created/edited - - We can have one running job + one in queue to apply the latest changes. - Make sure that we don't have more than one in queue. - """ - job_template = current_app.config["AWX_CORE_SERVICES_UPDATE"] - user = utils.cse_current_user() - if user.is_task_waiting("trigger_core_services_update"): - current_app.logger.info( - 'Already one "trigger_core_services_update" task waiting. No need to trigger a new one.' - ) - return None - kwargs = {"func": "launch_job_template", "job_template": job_template} - started = user.get_task_started("trigger_core_services_update") - if started: - # There is already one running task. Trigger a new one when it's done. - kwargs["depends_on"] = started.id - current_app.logger.info(f"Launch new job to update core services: {job_template}") - task = user.launch_task("trigger_core_services_update", **kwargs) - return task - - def launch_job_template(job_template, **kwargs): rq_job = get_current_job() if job_template in ( diff --git a/app/utils.py b/app/utils.py index 4493ae5f88419b81060a4c4d51446c7eef9ff36a..758e4d436d15bc2aa9ba4907d538bb9a6b8738ce 100644 --- a/app/utils.py +++ b/app/utils.py @@ -223,3 +223,28 @@ def format_datetime(value, format="%Y-%m-%d %H:%M"): Function used as a jinja2 filter """ return value.strftime(format) + + +def trigger_core_services_update(): + """Trigger a job to update the core services on the TN (DNS and DHCP) + + This function should be called every time an interface is created/edited + + We can have one running job + one in queue to apply the latest changes. + Make sure that we don't have more than one in queue. + """ + job_template = current_app.config["AWX_CORE_SERVICES_UPDATE"] + user = cse_current_user() + if user.is_task_waiting("trigger_core_services_update"): + current_app.logger.info( + 'Already one "trigger_core_services_update" task waiting. No need to trigger a new one.' + ) + return None + kwargs = {"func": "launch_job_template", "job_template": job_template} + started = user.get_task_started("trigger_core_services_update") + if started: + # There is already one running task. Trigger a new one when it's done. + kwargs["depends_on"] = started.id + current_app.logger.info(f"Launch new job to update core services: {job_template}") + task = user.launch_task("trigger_core_services_update", **kwargs) + return task