Skip to content

Commit

Permalink
Add a configurable limit for job forks
Browse files Browse the repository at this point in the history
  • Loading branch information
jakemcdermott committed Jan 7, 2020
1 parent f0882ab commit 0c2b110
Show file tree
Hide file tree
Showing 7 changed files with 57 additions and 2 deletions.
7 changes: 6 additions & 1 deletion awx/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4096,6 +4096,7 @@ class JobLaunchSerializer(BaseSerializer):
skip_tags = serializers.CharField(required=False, write_only=True, allow_blank=True)
limit = serializers.CharField(required=False, write_only=True, allow_blank=True)
verbosity = serializers.ChoiceField(required=False, choices=VERBOSITY_CHOICES, write_only=True)
forks = serializers.IntegerField(required=False, write_only=True)

class Meta:
model = JobTemplate
Expand All @@ -4106,7 +4107,7 @@ class Meta:
'ask_diff_mode_on_launch', 'ask_skip_tags_on_launch', 'ask_job_type_on_launch', 'ask_limit_on_launch',
'ask_verbosity_on_launch', 'ask_inventory_on_launch', 'ask_credential_on_launch',
'survey_enabled', 'variables_needed_to_start', 'credential_needed_to_start',
'inventory_needed_to_start', 'job_template_data', 'defaults', 'verbosity')
'inventory_needed_to_start', 'job_template_data', 'defaults', 'verbosity', 'forks')
read_only_fields = (
'ask_scm_branch_on_launch',
'ask_diff_mode_on_launch', 'ask_variables_on_launch', 'ask_limit_on_launch', 'ask_tags_on_launch',
Expand Down Expand Up @@ -4210,6 +4211,10 @@ def validate(self, attrs):
if len(passwords_lacking):
errors['passwords_needed_to_start'] = passwords_lacking

# Check that forks doesn't exceed configured maximum
if settings.MAX_FORKS > 0 and template.forks > settings.MAX_FORKS:
errors['forks'] = f'Maximum number of forks ({settings.MAX_FORKS}) exceeded.'

if errors:
raise serializers.ValidationError(errors)

Expand Down
12 changes: 12 additions & 0 deletions awx/main/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,18 @@ def _load_default_license_from_file():
category=_('Jobs'),
category_slug='jobs',
)
register(
'MAX_FORKS',
field_class=fields.IntegerField,
allow_null=False,
default=0,
label=_('Maximum number of forks per job.'),
help_text=_('Saving a Job Template with more than this number of forks will result in an error. Jobs '
'with more than this number of forks will be prevented from running. When set to 0 '
'(the default), no limit is applied.'),
category=_('Jobs'),
category_slug='jobs',
)

register(
'LOG_AGGREGATOR_HOST',
Expand Down
6 changes: 6 additions & 0 deletions awx/main/models/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

# Django
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
#from django.core.cache import cache
from django.utils.encoding import smart_str
Expand Down Expand Up @@ -293,6 +294,11 @@ def validation_errors(self):
def resources_needed_to_start(self):
return [fd for fd in ['project', 'inventory'] if not getattr(self, '{}_id'.format(fd))]

def clean_forks(self):
if settings.MAX_FORKS > 0 and self.forks > settings.MAX_FORKS:
raise ValidationError(_(f'Maximum number of forks ({settings.MAX_FORKS}) exceeded.'))
return self.forks

def create_job(self, **kwargs):
'''
Create a new job based on this template.
Expand Down
13 changes: 13 additions & 0 deletions awx/main/tests/functional/api/test_job_runtime_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,19 @@ def test_job_launch_fails_without_credential_access(job_template_prompts, runtim
dict(credentials=runtime_data['credentials']), rando, expect=403)


@pytest.mark.django_db
@pytest.mark.job_runtime_vars
def test_job_launch_fails_when_forks_exceeds_maximum(settings, deploy_jobtemplate, post, admin_user):
deploy_jobtemplate.forks = 11
deploy_jobtemplate.save()
settings.MAX_FORKS = 10

response = post(reverse('api:job_template_launch',
kwargs={'pk': deploy_jobtemplate.pk}), {}, admin_user, expect=400)

assert u'Maximum number of forks (10) exceeded.' in str(response.data['forks'])


@pytest.mark.django_db
@pytest.mark.job_runtime_vars
def test_job_block_scan_job_type_change(job_template_prompts, post, admin_user):
Expand Down
15 changes: 15 additions & 0 deletions awx/main/tests/functional/api/test_job_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,21 @@ def test_extra_credential_unique_type_xfail(get, post, organization_factory, job
assert response.data.get('count') == 1


@pytest.mark.django_db
def test_create_with_forks_exceeding_maximum_xfail(alice, post, project, inventory, settings):
project.use_role.members.add(alice)
inventory.use_role.members.add(alice)
settings.MAX_FORKS = 10
response = post(reverse('api:job_template_list'), {
'name': 'Some name',
'project': project.id,
'inventory': inventory.id,
'playbook': 'helloworld.yml',
'forks': 11,
}, alice)
assert response.status_code == 400


@pytest.mark.django_db
def test_attach_extra_credential(get, post, organization_factory, job_template_factory, credential):
objs = organization_factory("org", superusers=['admin'])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ export default ['i18n', function(i18n) {
type: 'text',
reset: 'ANSIBLE_FACT_CACHE_TIMEOUT',
},
MAX_FORKS: {
type: 'text',
reset: 'MAX_FORKS',
},
PROJECT_UPDATE_VVV: {
type: 'toggleSwitch',
},
Expand Down
2 changes: 1 addition & 1 deletion awx/ui/client/src/shared/Utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ angular.module('Utilities', ['RestServices', 'Utilities'])
addApiErrors(form.fields[field], field);
}
}
if (defaultMsg) {
if (!fieldErrors && defaultMsg) {
Alert(defaultMsg.hdr, defaultMsg.msg);
}
} else if (typeof data === 'object' && data !== null) {
Expand Down

0 comments on commit 0c2b110

Please sign in to comment.