Skip to content

Feature guess job timeout tests #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/lint-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
timeout-minutes: 10
strategy:
matrix:
python-version: [ "3.10", "3.11", "3.12" ]
python-version: [ "3.12" ]
services:
redis:
image: redis
Expand Down
40 changes: 39 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,45 @@ exempt_models = (
)
```

---
## `job_timeout`

Default: 300

The maximum execution time of a background task. Sync, Merge and Revert jobs will also be given additional modifiers below multiplied by the count of changes in the branch.

## `job_timeout_modifier`

Default:
```
{
"default_create": 1, # seconds
"default_update": .3, # seconds
"default_delete": 1, # seconds
},
```

This will add additional job timeout padding into the `job_timeout` based on the count of objects changed in a branch. You can also individually set model's time padding based on your own database performance.

### Example for padding DCIM Devices
```
{
"default_create": 1, # seconds
"default_update": .3, # seconds
"default_delete": 1, # seconds
"dcim.device": {
"create": 2, # seconds
"update": 1, # seconds
"delete": 2, # seconds
}
},
```

## `job_timeout_warning`

Default: 900

This will display a warning if the active branch or viewing branch details when the job timeout (plus padding) exceeds this set value. The warning can be suppressed if set to `None`


## `max_working_branches`

Expand Down
15 changes: 15 additions & 0 deletions netbox_branching/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,21 @@ class AppConfig(PluginConfig):

# This string is prefixed to the name of each new branch schema during provisioning
'schema_prefix': 'branch_',

# The maximum execution time of a background task.
'job_timeout': 5 * 60, # seconds

# This will add additional job timeout padding into the `job_timeout`
# based on the count of objects changed in a branch.
'job_timeout_modifier': {
"default_create": 1, # seconds
"default_update": .3, # seconds
"default_delete": 1, # seconds
},

# This will display a warning if the active branch or viewing branch
# details when the job timeout (plus padding) exceeds this set value.
'job_timeout_warning': 15 * 60, # seconds
}

def ready(self):
Expand Down
9 changes: 6 additions & 3 deletions netbox_branching/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ def sync(self, request, pk):
job = SyncBranchJob.enqueue(
instance=branch,
user=request.user,
commit=commit
commit=commit,
job_timeout=branch.job_timeout
)

return Response(JobSerializer(job, context={'request': request}).data)
Expand Down Expand Up @@ -78,7 +79,8 @@ def merge(self, request, pk):
job = MergeBranchJob.enqueue(
instance=branch,
user=request.user,
commit=commit
commit=commit,
job_timeout=branch.job_timeout
)

return Response(JobSerializer(job, context={'request': request}).data)
Expand Down Expand Up @@ -107,7 +109,8 @@ def revert(self, request, pk):
job = RevertBranchJob.enqueue(
instance=branch,
user=request.user,
commit=commit
commit=commit,
job_timeout=branch.job_timeout
)

return Response(JobSerializer(job, context={'request': request}).data)
Expand Down
21 changes: 20 additions & 1 deletion netbox_branching/models/branches.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@

from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.db import DEFAULT_DB_ALIAS, connection, models, transaction
from django.db.models import Count
from django.db.models.signals import post_save
from django.db.utils import ProgrammingError
from django.test import RequestFactory
Expand Down Expand Up @@ -127,6 +129,22 @@ def connection_name(self):
def synced_time(self):
return self.last_sync or self.created

@property
def job_timeout(self):
# Get the count of changes for each action and content type then try an calculate a more true job timeout
qs = self.get_changes().values_list('changed_object_type', 'action').annotate(count=Count('pk'))
job_timeout_modifier = get_plugin_config('netbox_branching', "job_timeout_modifier", {})
timeout = get_plugin_config('netbox_branching', "job_timeout")
for ct, action, count in qs:
ct = ContentType.objects.get(pk=ct)
key = ct.natural_key()
model = f"{key[0]}.{key[1]}"
if model in job_timeout_modifier and action in job_timeout_modifier[model]:
timeout += job_timeout_modifier[model][action] * count
else:
timeout += job_timeout_modifier[f"default_{action}"] * count
return int(timeout)

def clean(self):

# Enforce the maximum number of total branches
Expand Down Expand Up @@ -168,7 +186,8 @@ def save(self, provision=True, *args, **kwargs):
request = current_request.get()
ProvisionBranchJob.enqueue(
instance=self,
user=request.user if request else None
user=request.user if request else None,
job_timeout=self.job_timeout
)

def delete(self, *args, **kwargs):
Expand Down
44 changes: 43 additions & 1 deletion netbox_branching/template_content.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
from django.contrib.contenttypes.models import ContentType

from netbox.plugins import PluginTemplateExtension
from netbox.plugins import PluginTemplateExtension, get_plugin_config
from .choices import BranchStatusChoices
from .contextvars import active_branch
from .models import Branch, ChangeDiff

__all__ = (
'BranchNotification',
'BranchSelector',
'BranchWarning',
'ScriptNotification',
'ShareButton',
'template_extensions',
Expand All @@ -31,6 +32,46 @@ def buttons(self):
})


class BranchWarning(PluginTemplateExtension):

def alerts(self):
# Warn the use if the branch being displayed or the active branch has a job timeout that is extremely long
if (job_timeout_warning := get_plugin_config("netbox_branching", "job_timeout_warning")) is None:
# If the platform owner wants to suppress the warning return nothing
return ''
if isinstance(self.context['object'], Branch):
# The current view is a Branch, Let see what the job timeout is
job_timeout = self.context['object'].job_timeout
job_timeout_minutes = job_timeout // 60
else:
job_timeout = None
job_timeout_minutes = None

active_job_timeout = None
active_job_timeout_minutes = None
if hasattr(active_branch.get(), 'job_timeout'):
# There is an Active Branch, Let see what the job timeout is
if not (hasattr(self.context['object'], 'id') and active_branch.get().id == self.context['object'].id):
# The current view is not the active branch, so we can show
# the active branch job timeout waning too
active_job_timeout = active_branch.get().job_timeout
active_job_timeout_minutes = active_job_timeout // 60
if (
(job_timeout is not None and job_timeout > job_timeout_warning)
or
(active_job_timeout is not None and active_job_timeout > job_timeout_warning)
):
return self.render('netbox_branching/inc/branch_warning.html', extra_context={
'active_job_timeout': active_job_timeout,
'active_job_timeout_minutes': active_job_timeout_minutes,
'job_timeout': job_timeout,
'job_timeout_minutes': job_timeout_minutes,
'job_timeout_warning': job_timeout_warning,
})
# Nether the active branch nor the branch being displayed has a job timeout that is too long
return ''


class BranchNotification(PluginTemplateExtension):

def alerts(self):
Expand Down Expand Up @@ -66,6 +107,7 @@ def alerts(self):
template_extensions = (
BranchSelector,
BranchNotification,
BranchWarning,
ScriptNotification,
ShareButton,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{% load i18n %}
{% if active_job_timeout and active_job_timeout > job_timeout_warning%}
<div class="alert alert-warning" role="alert">
<i class="mdi mdi-alert"></i>
{% trans "The active branch may take upto" %} {{ active_job_timeout_minutes }} {% trans " minutes to merge - please consider splitting large changes into smaller branches" %}
</div>
{% endif %}
{% if job_timeout and job_timeout > job_timeout_warning %}
<div class="alert alert-warning" role="alert">
<i class="mdi mdi-alert"></i>
{% trans "This branch may take upto" %} {{ job_timeout_minutes }} {% trans " minutes to merge - please consider splitting large changes into smaller branches" %}
</div>
{% endif %}
Loading
Loading