diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py index 6004bebba4..dab2b395dc 100644 --- a/awx/main/models/inventory.py +++ b/awx/main/models/inventory.py @@ -256,7 +256,7 @@ class Inventory(CommonModelNameNotUnique, ResourceMixin, RelatedJobsMixin): if towervars: fetch_fields.append('enabled') hosts = self.hosts.filter(**hosts_kw).order_by('name').only(*fetch_fields) - if slice_count > 1: + if slice_count > 1 and slice_number > 0: offset = slice_number - 1 hosts = hosts[offset::slice_count] diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index 2203b7e31a..88b67a7d68 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -332,9 +332,19 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, SurveyJobTemplateMixin, Resour ''' return self.create_unified_job(**kwargs) + def get_effective_slice_ct(self, kwargs): + actual_inventory = self.inventory + if self.ask_inventory_on_launch and 'inventory' in kwargs: + actual_inventory = kwargs['inventory'] + if actual_inventory: + return min(self.job_slice_count, actual_inventory.hosts.count()) + else: + return self.job_slice_count + def create_unified_job(self, **kwargs): prevent_slicing = kwargs.pop('_prevent_slicing', False) - slice_event = bool(self.job_slice_count > 1 and (not prevent_slicing)) + slice_ct = self.get_effective_slice_ct(kwargs) + slice_event = bool(slice_ct > 1 and (not prevent_slicing)) if slice_event: # A Slice Job Template will generate a WorkflowJob rather than a Job from awx.main.models.workflow import WorkflowJobTemplate, WorkflowJobNode @@ -342,18 +352,16 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, SurveyJobTemplateMixin, Resour kwargs['_parent_field_name'] = "job_template" kwargs.setdefault('_eager_fields', {}) kwargs['_eager_fields']['is_sliced_job'] = True + elif self.job_slice_count > 1 and (not prevent_slicing): + # Unique case where JT was set to slice but hosts not available + kwargs.setdefault('_eager_fields', {}) + kwargs['_eager_fields']['job_slice_count'] = 1 elif prevent_slicing: kwargs.setdefault('_eager_fields', {}) kwargs['_eager_fields'].setdefault('job_slice_count', 1) job = super(JobTemplate, self).create_unified_job(**kwargs) if slice_event: - try: - wj_config = job.launch_config - except JobLaunchConfig.DoesNotExist: - wj_config = JobLaunchConfig() - actual_inventory = wj_config.inventory if wj_config.inventory else self.inventory - for idx in range(min(self.job_slice_count, - actual_inventory.hosts.count())): + for idx in range(slice_ct): create_kwargs = dict(workflow_job=job, unified_job_template=self, ancestor_artifacts=dict(job_slice=idx + 1)) diff --git a/awx/main/tests/functional/models/test_job.py b/awx/main/tests/functional/models/test_job.py index 385daaf915..31b430d268 100644 --- a/awx/main/tests/functional/models/test_job.py +++ b/awx/main/tests/functional/models/test_job.py @@ -1,6 +1,6 @@ import pytest -from awx.main.models import JobTemplate, Job, JobHostSummary, WorkflowJob +from awx.main.models import JobTemplate, Job, JobHostSummary, WorkflowJob, Inventory @pytest.mark.django_db @@ -96,3 +96,13 @@ class TestSlicingModels: assert node.limit is None # data not saved in node prompts job = node.job assert job.limit == 'foobar' + + def test_effective_slice_count(self, job_template, inventory, organization): + job_template.inventory = inventory + assert job_template.inventory.hosts.count() == 0 + job_template.job_slice_count = 2 + job_template.inventory.hosts.create(name='foo1') + assert job_template.get_effective_slice_ct({}) + inventory2 = Inventory.objects.create(organization=organization, name='fooinv') + [inventory2.hosts.create(name='foo{}'.format(i)) for i in range(3)] + assert job_template.get_effective_slice_ct({'inventory': inventory2})