mirror of
https://github.com/ansible/awx.git
synced 2026-05-10 19:07:36 -02:30
Job splitting access logic and more feature development
*allow sharding with prompts and schedules *modify create_unified_job contract to pass class & parent_field name *make parent field name instance method & set sharded UJT field *access methods made compatible with job sharding *move shard job special logic from task manager to workflows *save sharded job prompts to workflow job exclusively *allow using sharded jobs in workflows
This commit is contained in:
@@ -136,8 +136,7 @@ class AdHocCommand(UnifiedJob, JobNotificationMixin):
|
||||
else:
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def _get_parent_field_name(cls):
|
||||
def _get_parent_field_name(self):
|
||||
return ''
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -1647,8 +1647,7 @@ class InventoryUpdate(UnifiedJob, InventorySourceOptions, JobNotificationMixin,
|
||||
null=True
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _get_parent_field_name(cls):
|
||||
def _get_parent_field_name(self):
|
||||
return 'inventory_source'
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -320,32 +320,32 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, SurveyJobTemplateMixin, Resour
|
||||
def resources_needed_to_start(self):
|
||||
return [fd for fd in ['project', 'inventory'] if not getattr(self, '{}_id'.format(fd))]
|
||||
|
||||
def create_job(self, **kwargs):
|
||||
def create_unified_job(self, **kwargs):
|
||||
'''
|
||||
Create a new job based on this template.
|
||||
'''
|
||||
if self.job_shard_count > 1:
|
||||
split_event = bool(
|
||||
self.job_shard_count > 1 and
|
||||
not kwargs.pop('_prevent_sharding', False)
|
||||
)
|
||||
if split_event:
|
||||
# A sharded Job Template will generate a WorkflowJob rather than a Job
|
||||
from awx.main.models.workflow import WorkflowJobTemplate, WorkflowJobNode
|
||||
kwargs['_unified_job_class'] = WorkflowJobTemplate._get_unified_job_class()
|
||||
kwargs['_unified_job_field_names'] = WorkflowJobTemplate._get_unified_job_field_names()
|
||||
job = self.create_unified_job(**kwargs)
|
||||
if self.job_shard_count > 1:
|
||||
if 'inventory' in kwargs:
|
||||
actual_inventory = kwargs['inventory']
|
||||
else:
|
||||
actual_inventory = self.inventory
|
||||
kwargs['_parent_field_name'] = "job_template"
|
||||
job = super(JobTemplate, self).create_unified_job(**kwargs)
|
||||
if split_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 xrange(min(self.job_shard_count,
|
||||
actual_inventory.hosts.count())):
|
||||
create_kwargs = dict(workflow_job=job,
|
||||
unified_job_template=self,
|
||||
#survey_passwords=self.survey_passwords,
|
||||
inventory=actual_inventory,
|
||||
ancestor_artifacts=dict(job_shard=idx))
|
||||
#char_prompts=self.char_prompts)
|
||||
wfjn = WorkflowJobNode.objects.create(**create_kwargs)
|
||||
for cred in self.credentials.all():
|
||||
wfjn.credentials.add(cred)
|
||||
return job
|
||||
|
||||
def get_absolute_url(self, request=None):
|
||||
@@ -531,8 +531,7 @@ class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin, TaskMana
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def _get_parent_field_name(cls):
|
||||
def _get_parent_field_name(self):
|
||||
return 'job_template'
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -496,8 +496,7 @@ class ProjectUpdate(UnifiedJob, ProjectOptions, JobNotificationMixin, TaskManage
|
||||
default='check',
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _get_parent_field_name(cls):
|
||||
def _get_parent_field_name(self):
|
||||
return 'project'
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -309,13 +309,6 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique, Notificatio
|
||||
'''
|
||||
raise NotImplementedError # Implement in subclass.
|
||||
|
||||
@classmethod
|
||||
def _get_unified_job_field_names(cls):
|
||||
'''
|
||||
Return field names that should be copied from template to new job.
|
||||
'''
|
||||
raise NotImplementedError # Implement in subclass.
|
||||
|
||||
@property
|
||||
def notification_templates(self):
|
||||
'''
|
||||
@@ -338,27 +331,32 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique, Notificatio
|
||||
password_list = self.survey_password_variables()
|
||||
encrypt_dict(kwargs.get('extra_vars', {}), password_list)
|
||||
|
||||
unified_job_class = kwargs.pop("_unified_job_class", self._get_unified_job_class())
|
||||
fields = kwargs.pop("_unified_job_field_names", self._get_unified_job_field_names())
|
||||
print("UJC: {}".format(unified_job_class))
|
||||
print("fields: {}".format(fields))
|
||||
unified_job_class = self._get_unified_job_class()
|
||||
fields = self._get_unified_job_field_names()
|
||||
parent_field_name = None
|
||||
if "_unified_job_class" in kwargs:
|
||||
# Special case where spawned job is different type than usual
|
||||
# Only used for sharded jobs
|
||||
unified_job_class = kwargs.pop("_unified_job_class")
|
||||
fields = unified_job_class._get_unified_job_field_names() & fields
|
||||
parent_field_name = kwargs.pop('_parent_field_name')
|
||||
|
||||
unallowed_fields = set(kwargs.keys()) - set(fields)
|
||||
validated_kwargs = kwargs.copy()
|
||||
if unallowed_fields:
|
||||
logger.warn('Fields {} are not allowed as overrides.'.format(unallowed_fields))
|
||||
map(kwargs.pop, unallowed_fields)
|
||||
map(validated_kwargs.pop, unallowed_fields)
|
||||
|
||||
unified_job = copy_model_by_class(self, unified_job_class, fields, kwargs)
|
||||
unified_job = copy_model_by_class(self, unified_job_class, fields, validated_kwargs)
|
||||
|
||||
if eager_fields:
|
||||
for fd, val in eager_fields.items():
|
||||
setattr(unified_job, fd, val)
|
||||
|
||||
# Set the unified job template back-link on the job
|
||||
# TODO: fix this hack properly before merge matburt
|
||||
if isinstance(self, JobTemplate) and isinstance(unified_job, WorkflowJob):
|
||||
parent_field_name = "job_template"
|
||||
else:
|
||||
parent_field_name = unified_job_class._get_parent_field_name()
|
||||
# NOTE: sharded workflow jobs _get_parent_field_name method
|
||||
# is not correct until this is set
|
||||
if not parent_field_name:
|
||||
parent_field_name = unified_job._get_parent_field_name()
|
||||
setattr(unified_job, parent_field_name, self)
|
||||
|
||||
# For JobTemplate-based jobs with surveys, add passwords to list for perma-redaction
|
||||
@@ -372,24 +370,25 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique, Notificatio
|
||||
unified_job.save()
|
||||
|
||||
# Labels and credentials copied here
|
||||
if kwargs.get('credentials'):
|
||||
if validated_kwargs.get('credentials'):
|
||||
Credential = UnifiedJob._meta.get_field('credentials').related_model
|
||||
cred_dict = Credential.unique_dict(self.credentials.all())
|
||||
prompted_dict = Credential.unique_dict(kwargs['credentials'])
|
||||
prompted_dict = Credential.unique_dict(validated_kwargs['credentials'])
|
||||
# combine prompted credentials with JT
|
||||
cred_dict.update(prompted_dict)
|
||||
kwargs['credentials'] = [cred for cred in cred_dict.values()]
|
||||
validated_kwargs['credentials'] = [cred for cred in cred_dict.values()]
|
||||
kwargs['credentials'] = validated_kwargs['credentials']
|
||||
|
||||
from awx.main.signals import disable_activity_stream
|
||||
with disable_activity_stream():
|
||||
copy_m2m_relationships(self, unified_job, fields, kwargs=kwargs)
|
||||
copy_m2m_relationships(self, unified_job, fields, kwargs=validated_kwargs)
|
||||
|
||||
if 'extra_vars' in kwargs:
|
||||
unified_job.handle_extra_data(kwargs['extra_vars'])
|
||||
if 'extra_vars' in validated_kwargs:
|
||||
unified_job.handle_extra_data(validated_kwargs['extra_vars'])
|
||||
|
||||
if not getattr(self, '_deprecated_credential_launch', False):
|
||||
# Create record of provided prompts for relaunch and rescheduling
|
||||
unified_job.create_config_from_prompts(kwargs)
|
||||
unified_job.create_config_from_prompts(kwargs, parent=self)
|
||||
|
||||
return unified_job
|
||||
|
||||
@@ -702,8 +701,7 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
|
||||
def supports_isolation(cls):
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def _get_parent_field_name(cls):
|
||||
def _get_parent_field_name(self):
|
||||
return 'unified_job_template' # Override in subclasses.
|
||||
|
||||
@classmethod
|
||||
@@ -874,16 +872,16 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
|
||||
except JobLaunchConfig.DoesNotExist:
|
||||
return None
|
||||
|
||||
def create_config_from_prompts(self, kwargs):
|
||||
def create_config_from_prompts(self, kwargs, parent=None):
|
||||
'''
|
||||
Create a launch configuration entry for this job, given prompts
|
||||
returns None if it can not be created
|
||||
'''
|
||||
if self.unified_job_template is None:
|
||||
return None
|
||||
JobLaunchConfig = self._meta.get_field('launch_config').related_model
|
||||
config = JobLaunchConfig(job=self)
|
||||
valid_fields = self.unified_job_template.get_ask_mapping().keys()
|
||||
if parent is None:
|
||||
parent = getattr(self, self._get_parent_field_name())
|
||||
valid_fields = parent.get_ask_mapping().keys()
|
||||
# Special cases allowed for workflows
|
||||
if hasattr(self, 'extra_vars'):
|
||||
valid_fields.extend(['survey_passwords', 'extra_vars'])
|
||||
@@ -900,8 +898,9 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
|
||||
setattr(config, key, value)
|
||||
config.save()
|
||||
|
||||
job_creds = (set(kwargs.get('credentials', [])) -
|
||||
set(self.unified_job_template.credentials.all()))
|
||||
job_creds = set(kwargs.get('credentials', []))
|
||||
if 'credentials' in [field.name for field in parent._meta.get_fields()]:
|
||||
job_creds = job_creds - set(parent.credentials.all())
|
||||
if job_creds:
|
||||
config.credentials.add(*job_creds)
|
||||
return config
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
# Python
|
||||
#import urlparse
|
||||
import logging
|
||||
import six
|
||||
|
||||
# Django
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
#from django import settings as tower_settings
|
||||
|
||||
# AWX
|
||||
@@ -206,6 +208,15 @@ class WorkflowJobNode(WorkflowNodeBase):
|
||||
workflow_pk=self.pk,
|
||||
error_text=errors))
|
||||
data.update(accepted_fields) # missing fields are handled in the scheduler
|
||||
try:
|
||||
# config saved on the workflow job itself
|
||||
wj_config = self.workflow_job.launch_config
|
||||
except ObjectDoesNotExist:
|
||||
wj_config = None
|
||||
if wj_config:
|
||||
accepted_fields, ignored_fields, errors = ujt_obj._accept_or_ignore_job_kwargs(**wj_config.prompts_dict())
|
||||
accepted_fields.pop('extra_vars', None) # merge handled with other extra_vars later
|
||||
data.update(accepted_fields)
|
||||
# build ancestor artifacts, save them to node model for later
|
||||
aa_dict = {}
|
||||
for parent_node in self.get_parent_nodes():
|
||||
@@ -240,6 +251,15 @@ class WorkflowJobNode(WorkflowNodeBase):
|
||||
data['extra_vars'] = extra_vars
|
||||
# ensure that unified jobs created by WorkflowJobs are marked
|
||||
data['_eager_fields'] = {'launch_type': 'workflow'}
|
||||
# Extra processing in the case that this is a sharded job
|
||||
if 'job_shard' in self.ancestor_artifacts:
|
||||
shard_str = six.text_type(self.ancestor_artifacts['job_shard'] + 1)
|
||||
data['_eager_fields']['name'] = six.text_type("{} - {}").format(
|
||||
self.unified_job_template.name[:512 - len(shard_str) - len(' - ')],
|
||||
shard_str
|
||||
)
|
||||
data['_eager_fields']['allow_simultaneous'] = True
|
||||
data['_prevent_sharding'] = True
|
||||
return data
|
||||
|
||||
|
||||
@@ -261,6 +281,12 @@ class WorkflowJobOptions(BaseModel):
|
||||
def workflow_nodes(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@classmethod
|
||||
def _get_unified_job_field_names(cls):
|
||||
return set(f.name for f in WorkflowJobOptions._meta.fields) | set(
|
||||
['name', 'description', 'schedule', 'survey_passwords', 'labels']
|
||||
)
|
||||
|
||||
def _create_workflow_nodes(self, old_node_list, user=None):
|
||||
node_links = {}
|
||||
for old_node in old_node_list:
|
||||
@@ -331,12 +357,6 @@ class WorkflowJobTemplate(UnifiedJobTemplate, WorkflowJobOptions, SurveyJobTempl
|
||||
def _get_unified_job_class(cls):
|
||||
return WorkflowJob
|
||||
|
||||
@classmethod
|
||||
def _get_unified_job_field_names(cls):
|
||||
return set(f.name for f in WorkflowJobOptions._meta.fields) | set(
|
||||
['name', 'description', 'schedule', 'survey_passwords', 'labels']
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _get_unified_jt_copy_names(cls):
|
||||
base_list = super(WorkflowJobTemplate, cls)._get_unified_jt_copy_names()
|
||||
@@ -446,8 +466,10 @@ class WorkflowJob(UnifiedJob, WorkflowJobOptions, SurveyJobMixin, JobNotificatio
|
||||
def workflow_nodes(self):
|
||||
return self.workflow_job_nodes
|
||||
|
||||
@classmethod
|
||||
def _get_parent_field_name(cls):
|
||||
def _get_parent_field_name(self):
|
||||
if self.job_template_id:
|
||||
# This is a workflow job which is a container for sharded jobs
|
||||
return 'job_template'
|
||||
return 'workflow_job_template'
|
||||
|
||||
@classmethod
|
||||
|
||||
Reference in New Issue
Block a user