From 691942cac30fb7f22c817b330cf93d0998367bcc Mon Sep 17 00:00:00 2001 From: Chris Church Date: Thu, 20 Mar 2014 19:16:58 -0400 Subject: [PATCH] AC-1040 Unified jobs updates to get unit tests to pass (hopefully). --- awx/api/serializers.py | 43 ++-- awx/api/views.py | 2 +- awx/main/access.py | 2 +- awx/main/fields.py | 1 + awx/main/migrations/0035_v148_changes.py | 104 ++++++++- awx/main/migrations/0037_v148_changes.py | 23 +- awx/main/models/activity_stream.py | 4 +- awx/main/models/inventory.py | 104 ++++----- awx/main/models/jobs.py | 24 ++- awx/main/models/projects.py | 96 ++++----- awx/main/models/schedules.py | 3 + awx/main/models/unified_jobs.py | 256 ++++++++++++++++++++++- awx/main/tests/base.py | 11 +- awx/main/tests/jobs.py | 2 +- awx/main/tests/projects.py | 4 +- awx/settings/local_settings.py.example | 6 +- 16 files changed, 519 insertions(+), 166 deletions(-) diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 7d803ce4b8..f5383d94b8 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -339,7 +339,8 @@ class ProjectSerializer(BaseSerializer): 'scm_branch', 'scm_clean', 'scm_delete_on_update', 'scm_delete_on_next_update', 'scm_update_on_launch', 'credential', - 'last_update_failed', 'status', 'last_updated') + #'last_update_failed', 'status', 'last_updated') + 'last_job_failed', 'status', 'last_job_run') def get_related(self, obj): if obj is None: @@ -356,12 +357,17 @@ class ProjectSerializer(BaseSerializer): if obj.credential and obj.credential.active: res['credential'] = reverse('api:credential_detail', args=(obj.credential.pk,)) - if obj.current_update: - res['current_update'] = reverse('api:project_update_detail', - args=(obj.current_update.pk,)) - if obj.last_update: - res['last_update'] = reverse('api:project_update_detail', - args=(obj.last_update.pk,)) + #if obj.current_update: + # res['current_update'] = reverse('api:project_update_detail', + #if obj.last_update: + # res['last_update'] = reverse('api:project_update_detail', + # args=(obj.last_update.pk,)) + if obj.current_job: + res['current_job'] = reverse('api:project_update_detail', + args=(obj.current_job.pk,)) + if obj.last_job: + res['last_job'] = reverse('api:project_update_detail', + args=(obj.last_job.pk,)) return res def validate_local_path(self, attrs, source): @@ -694,8 +700,9 @@ class InventorySourceSerializer(BaseSerializer): fields = ('id', 'type', 'url', 'related', 'summary_fields', 'created', 'modified', 'inventory', 'group', 'source', 'source_path', 'source_vars', 'credential', 'source_regions', 'overwrite', - 'overwrite_vars', 'update_on_launch', 'update_interval', - 'last_update_failed', 'status', 'last_updated') + 'overwrite_vars', 'update_on_launch', #'update_interval', + #'last_update_failed', 'status', 'last_updated') + 'last_job_failed', 'status', 'last_job_run') read_only_fields = ('inventory', 'group') def get_related(self, obj): @@ -716,12 +723,18 @@ class InventorySourceSerializer(BaseSerializer): if obj.credential and obj.credential.active: res['credential'] = reverse('api:credential_detail', args=(obj.credential.pk,)) - if obj.current_update: - res['current_update'] = reverse('api:inventory_update_detail', - args=(obj.current_update.pk,)) - if obj.last_update: - res['last_update'] = reverse('api:inventory_update_detail', - args=(obj.last_update.pk,)) + #if obj.current_update: + # res['current_update'] = reverse('api:inventory_update_detail', + # args=(obj.current_update.pk,)) + #if obj.last_update: + # res['last_update'] = reverse('api:inventory_update_detail', + # args=(obj.last_update.pk,)) + if obj.current_job: + res['current_job'] = reverse('api:inventory_update_detail', + args=(obj.current_job.pk,)) + if obj.last_job: + res['last_job'] = reverse('api:inventory_update_detail', + args=(obj.last_job.pk,)) return res def get_summary_fields(self, obj): diff --git a/awx/api/views.py b/awx/api/views.py index 7e3539876a..3bbe2b5f3e 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -644,7 +644,7 @@ class ProjectList(ListCreateAPIView): projects_qs = Project.objects.filter(active=True) projects_qs = projects_qs.select_related('current_update', 'last_updated') for project in projects_qs: - project.set_status_and_last_updated() + project._set_status_and_last_job_run() return super(ProjectList, self).get(request, *args, **kwargs) class ProjectDetail(RetrieveUpdateDestroyAPIView): diff --git a/awx/main/access.py b/awx/main/access.py index f799b7ada7..495f9d9af5 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -396,7 +396,7 @@ class GroupAccess(BaseAccess): def get_queryset(self): qs = self.model.objects.filter(active=True).distinct() - qs = qs.select_related('created_by', 'inventory', 'inventory_source') + qs = qs.select_related('created_by', 'inventory')#, 'inventory_source') qs = qs.prefetch_related('parents', 'children') inventories_qs = self.user.get_queryset(Inventory) return qs.filter(inventory__in=inventories_qs) diff --git a/awx/main/fields.py b/awx/main/fields.py index 5c4b07d1e3..683d89ee72 100644 --- a/awx/main/fields.py +++ b/awx/main/fields.py @@ -23,6 +23,7 @@ class AutoSingleRelatedObjectDescriptor(SingleRelatedObjectDescriptor): except self.related.model.DoesNotExist: obj = self.related.model(**{self.related.field.name: instance}) if self.related.field.rel.parent_link: + raise NotImplementedError('not supported with polymorphic!') for f in instance._meta.local_fields: setattr(obj, f.name, getattr(instance, f.name)) obj.save() diff --git a/awx/main/migrations/0035_v148_changes.py b/awx/main/migrations/0035_v148_changes.py index 9629ae38d0..b60eac4783 100644 --- a/awx/main/migrations/0035_v148_changes.py +++ b/awx/main/migrations/0035_v148_changes.py @@ -53,15 +53,36 @@ class Migration(DataMigration): new_ctype = orm['contenttypes.ContentType'].objects.get(app_label=orm.Project._meta.app_label, model=orm.Project._meta.module_name) for project in orm.Project.objects.order_by('pk'): d = self._get_dict_from_common_model(project) - d['polymorphic_ctype_id'] = new_ctype.pk + d.update({ + 'polymorphic_ctype_id': new_ctype.pk, + 'local_path': project.local_path, + 'scm_type': project.scm_type, + 'scm_url': project.scm_url, + 'scm_branch': project.scm_branch, + 'scm_clean': project.scm_clean, + 'scm_delete_on_update': project.scm_delete_on_update, + 'credential_id': project.credential_id, + 'scm_delete_on_next_update': project.scm_delete_on_next_update, + 'scm_update_on_launch': project.scm_update_on_launch, + }) new_project, created = orm.ProjectNew.objects.get_or_create(old_pk=project.pk, defaults=d) # Copy ProjectUpdate old to new. new_ctype = orm['contenttypes.ContentType'].objects.get(app_label=orm.ProjectUpdate._meta.app_label, model=orm.ProjectUpdate._meta.module_name) for project_update in orm.ProjectUpdate.objects.order_by('pk'): + project = project_update.project d = self._get_dict_from_common_task_model(project_update) - d['project_id'] = orm.ProjectNew.objects.get(old_pk=project_update.project_id).pk - d['polymorphic_ctype_id'] = new_ctype.pk + d.update({ + 'polymorphic_ctype_id': new_ctype.pk, + 'project_id': orm.ProjectNew.objects.get(old_pk=project_update.project_id).pk, + 'local_path': project.local_path, + 'scm_type': project.scm_type, + 'scm_url': project.scm_url, + 'scm_branch': project.scm_branch, + 'scm_clean': project.scm_clean, + 'scm_delete_on_update': project.scm_delete_on_update, + 'credential_id': project.credential_id, + }) new_project_update, created = orm.ProjectUpdateNew.objects.get_or_create(old_pk=project_update.pk, defaults=d) # Update Project last run. @@ -71,6 +92,9 @@ class Migration(DataMigration): new_project.current_job = orm.ProjectUpdateNew.objects.get(old_pk=project.current_update_id) if project.last_update: new_project.last_job = orm.ProjectUpdateNew.objects.get(old_pk=project.last_update_id) + new_project.last_job_failed = project.last_update_failed + new_project.last_job_run = project.last_updated + new_project.status = project.status new_project.save() # Update Organization projects. @@ -97,15 +121,38 @@ class Migration(DataMigration): new_ctype = orm['contenttypes.ContentType'].objects.get(app_label=orm.InventorySource._meta.app_label, model=orm.InventorySource._meta.module_name) for inventory_source in orm.InventorySource.objects.order_by('pk'): d = self._get_dict_from_common_model(inventory_source) - d['polymorphic_ctype_id'] = new_ctype.pk + d.update({ + 'polymorphic_ctype_id': new_ctype.pk, + 'source': inventory_source.source, + 'source_path': inventory_source.source_path, + 'source_vars': inventory_source.source_vars, + 'credential_id': inventory_source.credential_id, + 'source_regions': inventory_source.source_regions, + 'overwrite': inventory_source.overwrite, + 'overwrite_vars': inventory_source.overwrite_vars, + 'update_on_launch': inventory_source.update_on_launch, + 'inventory_id': inventory_source.inventory_id, + 'group_id': inventory_source.group_id, + }) new_inventory_source, created = orm.InventorySourceNew.objects.get_or_create(old_pk=inventory_source.pk, defaults=d) # Copy InventoryUpdate old to new. new_ctype = orm['contenttypes.ContentType'].objects.get(app_label=orm.InventoryUpdate._meta.app_label, model=orm.InventoryUpdate._meta.module_name) for inventory_update in orm.InventoryUpdate.objects.order_by('pk'): + inventory_source = inventory_update.inventory_source d = self._get_dict_from_common_task_model(inventory_update) - d['inventory_source_id'] = orm.InventorySourceNew.objects.get(old_pk=inventory_update.inventory_source_id).pk - d['polymorphic_ctype_id'] = new_ctype.pk + d.update({ + 'polymorphic_ctype_id': new_ctype.pk, + 'source': inventory_source.source, + 'source_path': inventory_source.source_path, + 'source_vars': inventory_source.source_vars, + 'credential_id': inventory_source.credential_id, + 'source_regions': inventory_source.source_regions, + 'overwrite': inventory_source.overwrite, + 'overwrite_vars': inventory_source.overwrite_vars, + 'inventory_source_id': orm.InventorySourceNew.objects.get(old_pk=inventory_update.inventory_source_id).pk, + 'license_error': inventory_update.license_error, + }) new_inventory_update, created = orm.InventoryUpdateNew.objects.get_or_create(old_pk=inventory_update.pk, defaults=d) # Update InventorySource last run. @@ -115,6 +162,9 @@ class Migration(DataMigration): new_inventory_source.current_job = orm.InventoryUpdateNew.objects.get(old_pk=inventory_source.current_update_id) if inventory_source.last_update: new_inventory_source.last_job = orm.InventoryUpdateNew.objects.get(old_pk=inventory_source.last_update_id) + new_inventory_source.last_job_failed = inventory_source.last_update_failed + new_inventory_source.last_job_run = inventory_source.last_updated + new_inventory_source.status = inventory_source.status new_inventory_source.save() # Update Group inventory_sources. @@ -133,20 +183,57 @@ class Migration(DataMigration): new_ctype = orm['contenttypes.ContentType'].objects.get(app_label=orm.JobTemplate._meta.app_label, model=orm.JobTemplate._meta.module_name) for job_template in orm.JobTemplate.objects.order_by('pk'): d = self._get_dict_from_common_model(job_template) + d.update({ + 'polymorphic_ctype_id': new_ctype.pk, + 'job_type': job_template.job_type, + 'inventory_id': job_template.inventory_id, + 'playbook': job_template.playbook, + 'credential_id': job_template.credential_id, + 'cloud_credential_id': job_template.cloud_credential_id, + 'forks': job_template.forks, + 'limit': job_template.limit, + 'extra_vars': job_template.extra_vars, + 'job_tags': job_template.job_tags, + 'host_config_key': job_template.host_config_key, + }) if job_template.project: d['project_id'] = orm.ProjectNew.objects.get(old_pk=job_template.project_id).pk - d['polymorphic_ctype_id'] = new_ctype.pk new_job_template, created = orm.JobTemplateNew.objects.get_or_create(old_pk=job_template.pk, defaults=d) # Copy Job old to new. new_ctype = orm['contenttypes.ContentType'].objects.get(app_label=orm.Job._meta.app_label, model=orm.Job._meta.module_name) for job in orm.Job.objects.order_by('pk'): d = self._get_dict_from_common_task_model(job) + d.update({ + 'polymorphic_ctype_id': new_ctype.pk, + 'job_type': job_template.job_type, + 'inventory_id': job_template.inventory_id, + 'playbook': job_template.playbook, + 'credential_id': job_template.credential_id, + 'cloud_credential_id': job_template.cloud_credential_id, + 'forks': job_template.forks, + 'limit': job_template.limit, + 'extra_vars': job_template.extra_vars, + 'job_tags': job_template.job_tags, + }) if job.project: d['project_id'] = orm.ProjectNew.objects.get(old_pk=job.project_id).pk - d['polymorphic_ctype_id'] = new_ctype.pk + if job.job_template: + d['job_template_id'] = orm.JobTemplateNew.objects.get(old_pk=job.job_template_id).pk new_job, created = orm.JobNew.objects.get_or_create(old_pk=job.pk, defaults=d) + # Update JobTemplate last run. + for new_job_template in orm.JobTemplateNew.objects.order_by('pk'): + try: + new_last_job = new_job_template.jobs.order_by('-pk')[0] + new_job_template.last_job = new_last_job + new_job_template.last_job_failed = new_last_job.failed + new_job_template.last_job_run = new_last_job.finished + new_job_template.status = 'failed' if new_last_job.failed else 'successful' + except IndexError: + new_job_template.status = 'never updated' + new_inventory_source.save() + # Update JobHostSummary job. for job_host_summary in orm.JobHostSummary.objects.order_by('pk'): new_job = orm.JobNew.objects.get(old_pk=job_host_summary.job_id) @@ -192,6 +279,7 @@ class Migration(DataMigration): "Write your backwards methods here." # FIXME: Would like to have this, but not required. + raise NotImplementedError() models = { u'auth.group': { diff --git a/awx/main/migrations/0037_v148_changes.py b/awx/main/migrations/0037_v148_changes.py index 40cb63e232..fb8a90b956 100644 --- a/awx/main/migrations/0037_v148_changes.py +++ b/awx/main/migrations/0037_v148_changes.py @@ -7,7 +7,7 @@ from django.db import models class Migration(SchemaMigration): ''' - Rename tables to be consistent with model names. + Rename tables/columns to be consistent with model/field names. ''' def forwards(self, orm): @@ -19,41 +19,60 @@ class Migration(SchemaMigration): db.rename_table(u'main_jobnew', 'main_job') db.rename_table(db.shorten_name(u'main_team_new_projects'), db.shorten_name(u'main_team_projects')) + db.rename_column(db.shorten_name(u'main_team_projects'), 'projectnew_id', 'project_id') db.rename_table(db.shorten_name(u'main_organization_new_projects'), db.shorten_name(u'main_organization_projects')) + db.rename_column(db.shorten_name(u'main_organization_projects'), 'projectnew_id', 'project_id') db.rename_column(u'main_permission', 'new_project_id', 'project_id') db.rename_column(u'main_host', 'new_last_job_id', 'last_job_id') db.rename_table(db.shorten_name(u'main_host_new_inventory_sources'), db.shorten_name(u'main_host_inventory_sources')) + db.rename_column(db.shorten_name(u'main_host_inventory_sources'), 'inventorysourcenew_id', 'inventorysource_id') db.rename_table(db.shorten_name(u'main_group_new_inventory_sources'), db.shorten_name(u'main_group_inventory_sources')) + db.rename_column(db.shorten_name(u'main_group_inventory_sources'), 'inventorysourcenew_id', 'inventorysource_id') db.rename_column(u'main_jobhostsummary', 'new_job_id', 'job_id') db.rename_column(u'main_jobevent', 'new_job_id', 'job_id') db.rename_table(db.shorten_name(u'main_activitystream_new_project'), db.shorten_name(u'main_activitystream_project')) + db.rename_column(db.shorten_name(u'main_activitystream_project'), 'projectnew_id', 'project_id') db.rename_table(db.shorten_name(u'main_activitystream_new_project_update'), db.shorten_name(u'main_activitystream_project_update')) + db.rename_column(db.shorten_name(u'main_activitystream_project_update'), 'projectupdatenew_id', 'projectupdate_id') db.rename_table(db.shorten_name(u'main_activitystream_new_inventory_source'), db.shorten_name(u'main_activitystream_inventory_source')) + db.rename_column(db.shorten_name(u'main_activitystream_inventory_source'), 'inventorysourcenew_id', 'inventorysource_id') db.rename_table(db.shorten_name(u'main_activitystream_new_inventory_update'), db.shorten_name(u'main_activitystream_inventory_update')) + db.rename_column(db.shorten_name(u'main_activitystream_inventory_update'), 'inventoryupdatenew_id', 'inventoryupdate_id') db.rename_table(db.shorten_name(u'main_activitystream_new_job_template'), db.shorten_name(u'main_activitystream_job_template')) + db.rename_column(db.shorten_name(u'main_activitystream_job_template'), 'jobtemplatenew_id', 'jobtemplate_id') db.rename_table(db.shorten_name(u'main_activitystream_new_job'), db.shorten_name(u'main_activitystream_job')) + db.rename_column(db.shorten_name(u'main_activitystream_job'), 'jobnew_id', 'job_id') def backwards(self, orm): + db.rename_column(db.shorten_name(u'main_activitystream_job'), 'job_id', 'jobnew_id') db.rename_table(db.shorten_name(u'main_activitystream_job'), db.shorten_name(u'main_activitystream_new_job')) + db.rename_column(db.shorten_name(u'main_activitystream_job_template'), 'jobtemplate_id', 'jobtemplatenew_id') db.rename_table(db.shorten_name(u'main_activitystream_job_template'), db.shorten_name(u'main_activitystream_new_job_template')) + db.rename_column(db.shorten_name(u'main_activitystream_inventory_update'), 'inventoryupdate_id', 'inventoryupdatenew_id') db.rename_table(db.shorten_name(u'main_activitystream_inventory_update'), db.shorten_name(u'main_activitystream_new_inventory_update')) + db.rename_column(db.shorten_name(u'main_activitystream_inventory_source'), 'inventorysource_id', 'inventorysourcenew_id') db.rename_table(db.shorten_name(u'main_activitystream_inventory_source'), db.shorten_name(u'main_activitystream_new_inventory_source')) + db.rename_column(db.shorten_name(u'main_activitystream_project_update'), 'projectupdate_id', 'projectupdatenew_id') db.rename_table(db.shorten_name(u'main_activitystream_project_update'), db.shorten_name(u'main_activitystream_new_project_update')) + db.rename_column(db.shorten_name(u'main_activitystream_project'), 'project_id', 'projectnew_id') db.rename_table(db.shorten_name(u'main_activitystream_project'), db.shorten_name(u'main_activitystream_new_project')) db.rename_column(u'main_jobevent', 'job_id', 'new_job_id') db.rename_column(u'main_jobhostsummary', 'job_id', 'new_job_id') - + db.rename_column(db.shorten_name(u'main_group_inventory_sources'), 'inventorysource_id', 'inventorysourcenew_id') db.rename_table(db.shorten_name(u'main_group_inventory_sources'), db.shorten_name(u'main_group_new_inventory_sources')) + db.rename_column(db.shorten_name(u'main_host_inventory_sources'), 'inventorysource_id', 'inventorysourcenew_id') db.rename_table(db.shorten_name(u'main_host_inventory_sources'), db.shorten_name(u'main_host_new_inventory_sources')) db.rename_column(u'main_host', 'last_job_id', 'new_last_job_id') db.rename_column(u'main_permission', 'project_id', 'new_project_id') + db.rename_column(db.shorten_name(u'main_organization_projects'), 'project_id', 'projectnew_id') db.rename_table(db.shorten_name(u'main_organization_projects'), db.shorten_name(u'main_organization_new_projects')) + db.rename_column(db.shorten_name(u'main_team_projects'), 'project_id', 'projectnew_id') db.rename_table(db.shorten_name(u'main_team_projects'), db.shorten_name(u'main_team_new_projects')) db.rename_table(u'main_job', 'main_jobnew') diff --git a/awx/main/models/activity_stream.py b/awx/main/models/activity_stream.py index d25b2220fc..65f1818748 100644 --- a/awx/main/models/activity_stream.py +++ b/awx/main/models/activity_stream.py @@ -58,12 +58,12 @@ class ActivityStreamBase(models.Model): # For compatibility with Django 1.4.x, attempt to handle any calls to # save that pass update_fields. try: - super(ActivityStream, self).save(*args, **kwargs) + super(ActivityStreamBase, self).save(*args, **kwargs) except TypeError: if 'update_fields' not in kwargs: raise kwargs.pop('update_fields') - super(ActivityStream, self).save(*args, **kwargs) + super(ActivityStreamBase, self).save(*args, **kwargs) if getattr(settings, 'UNIFIED_JOBS_STEP') == 0: diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py index 99c59a87ef..956050b28d 100644 --- a/awx/main/models/inventory.py +++ b/awx/main/models/inventory.py @@ -133,7 +133,8 @@ class Inventory(CommonModel): active_groups = self.groups.filter(active=True) failed_groups = active_groups.filter(has_active_failures=True) active_inventory_sources = self.inventory_sources.filter(active=True, source__in=CLOUD_INVENTORY_SOURCES) - failed_inventory_sources = active_inventory_sources.filter(last_update_failed=True) + #failed_inventory_sources = active_inventory_sources.filter(last_update_failed=True) + failed_inventory_sources = active_inventory_sources.filter(last_job_failed=True) computed_fields = { 'has_active_failures': bool(failed_hosts.count()), 'total_hosts': active_hosts.count(), @@ -233,7 +234,7 @@ class HostBase(CommonModelNameNotUnique): When marking hosts inactive, remove all associations to related inventory sources. ''' - super(Host, self).mark_inactive(save=save) + super(HostBase, self).mark_inactive(save=save) self.inventory_sources.clear() def update_computed_fields(self, update_inventory=True, update_groups=True): @@ -457,7 +458,7 @@ class GroupBase(CommonModelNameNotUnique): groups/hosts/inventory_sources. ''' def mark_actual(): - super(Group, self).mark_inactive(save=save) + super(GroupBase, self).mark_inactive(save=save) self.inventory_source.mark_inactive(save=save) self.inventory_sources.clear() self.parents.clear() @@ -674,20 +675,6 @@ class InventorySourceOptions(BaseModel): help_text=_('Overwrite local variables from remote inventory source.'), ) - -class InventorySourceBase(InventorySourceOptions): - - class Meta: - abstract = True - app_label = 'main' - - update_on_launch = models.BooleanField( - default=False, - ) - update_cache_timeout = models.PositiveIntegerField( - default=0, - ) - @classmethod def get_ec2_region_choices(cls): ec2_region_names = getattr(settings, 'EC2_REGION_NAMES', {}) @@ -758,57 +745,46 @@ class InventorySourceBase(InventorySourceOptions): ', '.join(invalid_regions))) return ','.join(regions) + +class InventorySourceBase(InventorySourceOptions): + + class Meta: + abstract = True + app_label = 'main' + + update_on_launch = models.BooleanField( + default=False, + ) + update_cache_timeout = models.PositiveIntegerField( + default=0, + ) + + +class InventorySourceBaseMethods(object): + def save(self, *args, **kwargs): - new_instance = not bool(self.pk) # If update_fields has been specified, add our field names to it, # if it hasn't been specified, then we're just doing a normal save. update_fields = kwargs.get('update_fields', []) - # Update status and last_updated fields. - updated_fields = self.set_status_and_last_updated(save=False) - for field in updated_fields: - if field not in update_fields: - update_fields.append(field) # Update inventory from group (if available). if self.group and not self.inventory: self.inventory = self.group.inventory if 'inventory' not in update_fields: update_fields.append('inventory') + # Set name automatically. + if not self.name: + self.name = 'inventory_source %s' % now() + if 'name' not in update_fields: + update_fields.append('name') # Do the actual save. - super(InventorySource, self).save(*args, **kwargs) + super(InventorySourceBaseMethods, self).save(*args, **kwargs) source_vars_dict = VarsDictProperty('source_vars') - def set_status_and_last_updated(self, save=True): - # Determine current status. - if self.source: - if self.current_update: - status = 'updating' - elif not self.last_update: - status = 'never updated' - elif self.last_update_failed: - status = 'failed' - else: - status = 'successful' - else: - status = 'none' - # Determine current last_updated timestamp. - last_updated = None - if self.source and self.last_update: - last_updated = self.last_update.modified - # Update values if changed. - update_fields = [] - if self.status != status: - self.status = status - update_fields.append('status') - if self.last_updated != last_updated: - self.last_updated = last_updated - update_fields.append('last_updated') - if save and update_fields: - self.save(update_fields=update_fields) - return update_fields + def get_absolute_url(self): + return reverse('api:inventory_source_detail', args=(self.pk,)) - @property - def can_update(self): + def _can_update(self): # FIXME: Prevent update when another one is active! return bool(self.source) @@ -827,13 +803,10 @@ class InventorySourceBase(InventorySourceOptions): inventory_update.signal_start(**kwargs) return inventory_update - def get_absolute_url(self): - return reverse('api:inventory_source_detail', args=(self.pk,)) - if getattr(settings, 'UNIFIED_JOBS_STEP') == 0: - class InventorySource(PrimordialModel, InventorySourceBase): + class InventorySource(InventorySourceBaseMethods, PrimordialModel, InventorySourceBase): INVENTORY_SOURCE_STATUS_CHOICES = [ ('none', _('No External Source')), @@ -893,7 +866,7 @@ if getattr(settings, 'UNIFIED_JOBS_STEP') == 0: if getattr(settings, 'UNIFIED_JOBS_STEP') in (0, 1): - class InventorySourceNew(UnifiedJobTemplate, InventorySourceBase): + class InventorySourceNew(InventorySourceBaseMethods, UnifiedJobTemplate, InventorySourceBase): class Meta: app_label = 'main' @@ -923,7 +896,7 @@ if getattr(settings, 'UNIFIED_JOBS_STEP') == 1: if getattr(settings, 'UNIFIED_JOBS_STEP') == 2: - class InventorySource(UnifiedJobTemplate, InventorySourceBase): + class InventorySource(InventorySourceBaseMethods, UnifiedJobTemplate, InventorySourceBase): class Meta: app_label = 'main' @@ -959,6 +932,9 @@ class InventoryUpdateBase(InventorySourceOptions): editable=False, ) + +class InventoryUpdateBaseMethods(object): + def save(self, *args, **kwargs): update_fields = kwargs.get('update_fields', []) if bool('license' in self.result_stdout and @@ -966,7 +942,7 @@ class InventoryUpdateBase(InventorySourceOptions): self.license_error = True if 'license_error' not in update_fields: update_fields.append('license_error') - super(InventoryUpdate, self).save(*args, **kwargs) + super(InventoryUpdateBaseMethods, self).save(*args, **kwargs) def _get_parent_instance(self): return self.inventory_source @@ -1008,7 +984,7 @@ class InventoryUpdateBase(InventorySourceOptions): if getattr(settings, 'UNIFIED_JOBS_STEP') == 0: - class InventoryUpdate(CommonTask, InventoryUpdateBase): + class InventoryUpdate(InventoryUpdateBaseMethods, CommonTask, InventoryUpdateBase): class Meta: app_label = 'main' @@ -1022,7 +998,7 @@ if getattr(settings, 'UNIFIED_JOBS_STEP') == 0: if getattr(settings, 'UNIFIED_JOBS_STEP') in (0, 1): - class InventoryUpdateNew(UnifiedJob, InventoryUpdateBase): + class InventoryUpdateNew(InventoryUpdateBaseMethods, UnifiedJob, InventoryUpdateBase): class Meta: app_label = 'main' @@ -1043,7 +1019,7 @@ if getattr(settings, 'UNIFIED_JOBS_STEP') == 1: if getattr(settings, 'UNIFIED_JOBS_STEP') == 2: - class InventoryUpdate(UnifiedJob, InventoryUpdateBase): + class InventoryUpdate(InventoryUpdateBaseMethods, UnifiedJob, InventoryUpdateBase): class Meta: app_label = 'main' diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index 76b24ff932..7b705bfb64 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -32,6 +32,9 @@ from django.utils.timezone import now, make_aware, get_default_timezone # Django-JSONField from jsonfield import JSONField +# Django-Polymorphic +from polymorphic import PolymorphicModel + # AWX from awx.main.models.base import * from awx.main.models.unified_jobs import * @@ -142,6 +145,7 @@ class JobTemplateBase(JobOptions): default='', ) +class JobTemplateBaseMethods(object): def create_job(self, **kwargs): ''' @@ -185,7 +189,7 @@ class JobTemplateBase(JobOptions): if getattr(settings, 'UNIFIED_JOBS_STEP') == 0: - class JobTemplate(CommonModel, JobTemplateBase): + class JobTemplate(JobTemplateBaseMethods, CommonModel, JobTemplateBase): class Meta: app_label = 'main' @@ -199,7 +203,7 @@ if getattr(settings, 'UNIFIED_JOBS_STEP') == 0: if getattr(settings, 'UNIFIED_JOBS_STEP') in (0, 1): - class JobTemplateNew(UnifiedJobTemplate, JobTemplateBase): + class JobTemplateNew(JobTemplateBaseMethods, UnifiedJobTemplate, JobTemplateBase): class Meta: app_label = 'main' @@ -221,7 +225,8 @@ if getattr(settings, 'UNIFIED_JOBS_STEP') == 1: if getattr(settings, 'UNIFIED_JOBS_STEP') == 2: - class JobTemplate(UnifiedJobTemplate, JobTemplateBase): + #class JobTemplate(JobTemplateBase, UnifiedJobTemplate): + class JobTemplate(JobTemplateBaseMethods, UnifiedJobTemplate, JobTemplateBase): class Meta: app_label = 'main' @@ -253,6 +258,8 @@ class JobBase(JobOptions): through='JobHostSummary', ) +class JobBaseMethods(object): + def get_absolute_url(self): return reverse('api:job_detail', args=(self.pk,)) @@ -405,7 +412,7 @@ class JobBase(JobOptions): if getattr(settings, 'UNIFIED_JOBS_STEP') == 0: - class Job(CommonTask, JobBase): + class Job(JobBaseMethods, CommonTask, JobBase): LAUNCH_TYPE_CHOICES = [ ('manual', _('Manual')), @@ -439,7 +446,7 @@ if getattr(settings, 'UNIFIED_JOBS_STEP') == 0: if getattr(settings, 'UNIFIED_JOBS_STEP') in (0, 1): - class JobNew(UnifiedJob, JobBase): + class JobNew(JobBaseMethods, UnifiedJob, JobBase): class Meta: app_label = 'main' @@ -468,7 +475,8 @@ if getattr(settings, 'UNIFIED_JOBS_STEP') == 1: if getattr(settings, 'UNIFIED_JOBS_STEP') == 2: - class Job(UnifiedJob, JobBase): + #class Job(JobBase, UnifiedJob): + class Job(JobBaseMethods, UnifiedJob, JobBase): class Meta: app_label = 'main' @@ -535,7 +543,7 @@ class JobHostSummaryBase(CreatedModifiedModel): update_fields = kwargs.get('update_fields', []) self.failed = bool(self.dark or self.failures) update_fields.append('failed') - super(JobHostSummary, self).save(*args, **kwargs) + super(JobHostSummaryBase, self).save(*args, **kwargs) self.update_host_last_job_summary() def update_host_last_job_summary(self): @@ -887,7 +895,7 @@ class JobEventBase(CreatedModifiedModel): self.parent = self._find_parent() if 'parent' not in update_fields: update_fields.append('parent') - super(JobEvent, self).save(*args, **kwargs) + super(JobEventBase, self).save(*args, **kwargs) if post_process and not from_parent_update: self.update_parent_failed_and_changed() # FIXME: The update_hosts() call (and its queries) are the current diff --git a/awx/main/models/projects.py b/awx/main/models/projects.py index 70152175b1..dce3103ba9 100644 --- a/awx/main/models/projects.py +++ b/awx/main/models/projects.py @@ -175,6 +175,9 @@ class ProjectBase(ProjectOptions): default=0, ) + +class ProjectBaseMethods(object): + def save(self, *args, **kwargs): new_instance = not bool(self.pk) # If update_fields has been specified, add our field names to it, @@ -182,7 +185,7 @@ class ProjectBase(ProjectOptions): update_fields = kwargs.get('update_fields', []) # Check if scm_type or scm_url changes. if self.pk: - project_before = Project.objects.get(pk=self.pk) + project_before = self.__class__.objects.get(pk=self.pk) if project_before.scm_type != self.scm_type or project_before.scm_url != self.scm_url: self.scm_delete_on_next_update = True if 'scm_delete_on_next_update' not in update_fields: @@ -193,13 +196,8 @@ class ProjectBase(ProjectOptions): self.local_path = u'_%d__%s' % (self.pk, slug_name) if 'local_path' not in update_fields: update_fields.append('local_path') - # Update status and last_updated fields. - updated_fields = self.set_status_and_last_updated(save=False) - for field in updated_fields: - if field not in update_fields: - update_fields.append(field) # Do the actual save. - super(Project, self).save(*args, **kwargs) + super(ProjectBaseMethods, self).save(*args, **kwargs) if new_instance: update_fields=[] # Generate local_path for SCM after initial save (so we have a PK). @@ -211,50 +209,37 @@ class ProjectBase(ProjectOptions): if new_instance and self.scm_type: self.update() - def set_status_and_last_updated(self, save=True): - # Determine current status. + def _get_current_status(self): if self.scm_type: if self.current_update: - status = 'updating' - elif not self.last_update: - status = 'never updated' - elif self.last_update_failed: - status = 'failed' + return 'updating' + elif not self.last_job: + return 'never updated' + elif self.last_job_failed: + return 'failed' elif not self.get_project_path(): - status = 'missing' + return 'missing' else: - status = 'successful' + return 'successful' elif not self.get_project_path(): - status = 'missing' + return 'missing' else: - status = 'ok' - # Determine current last_updated timestamp. - last_updated = None - if self.scm_type and self.last_update: - last_updated = self.last_update.modified + return 'ok' + + def _get_last_job_run(self): + if self.scm_type and self.last_job: + return self.last_job.finished else: project_path = self.get_project_path() if project_path: try: mtime = os.path.getmtime(project_path) dt = datetime.datetime.fromtimestamp(mtime) - last_updated = make_aware(dt, get_default_timezone()) + return make_aware(dt, get_default_timezone()) except os.error: pass - # Update values if changed. - update_fields = [] - if self.status != status: - self.status = status - update_fields.append('status') - if self.last_updated != last_updated: - self.last_updated = last_updated - update_fields.append('last_updated') - if save and update_fields: - self.save(update_fields=update_fields) - return update_fields - @property - def can_update(self): + def _can_update(self): # FIXME: Prevent update when another one is active! return bool(self.scm_type)# and not self.current_update) @@ -319,7 +304,7 @@ class ProjectBase(ProjectOptions): if getattr(settings, 'UNIFIED_JOBS_STEP') == 0: - class Project(CommonModel, ProjectBase): + class Project(ProjectBaseMethods, CommonModel, ProjectBase): PROJECT_STATUS_CHOICES = [ ('ok', 'OK'), @@ -366,7 +351,7 @@ if getattr(settings, 'UNIFIED_JOBS_STEP') == 0: if getattr(settings, 'UNIFIED_JOBS_STEP') in (0, 1): - class ProjectNew(UnifiedJobTemplate, ProjectBase): + class ProjectNew(ProjectBaseMethods, UnifiedJobTemplate, ProjectBase): class Meta: app_label = 'main' @@ -380,7 +365,7 @@ if getattr(settings, 'UNIFIED_JOBS_STEP') == 1: if getattr(settings, 'UNIFIED_JOBS_STEP') == 2: - class Project(UnifiedJobTemplate, ProjectBase): + class Project(ProjectBaseMethods, UnifiedJobTemplate, ProjectBase): class Meta: app_label = 'main' @@ -395,12 +380,15 @@ class ProjectUpdateBase(ProjectOptions): app_label = 'main' abstract = True - def get_absolute_url(self): - return reverse('api:project_update_detail', args=(self.pk,)) + +class ProjectUpdateBaseMethods(object): def _get_parent_instance(self): return self.project + def get_absolute_url(self): + return reverse('api:project_update_detail', args=(self.pk,)) + def _get_task_class(self): from awx.main.tasks import RunProjectUpdate return RunProjectUpdate @@ -436,25 +424,25 @@ class ProjectUpdateBase(ProjectOptions): parent_instance = self._get_parent_instance() if parent_instance: if self.status in ('pending', 'waiting', 'running'): - if parent_instance.current_update != self: - parent_instance.current_update = self - parent_instance.save(update_fields=['current_update']) + if parent_instance.current_job != self: + parent_instance.current_job = self + parent_instance.save(update_fields=['current_job']) elif self.status in ('successful', 'failed', 'error', 'canceled'): - if parent_instance.current_update == self: - parent_instance.current_update = None - parent_instance.last_update = self - parent_instance.last_update_failed = self.failed + if parent_instance.current_job == self: + parent_instance.current_job = None + parent_instance.last_job = self + parent_instance.last_job_failed = self.failed if not self.failed and parent_instance.scm_delete_on_next_update: parent_instance.scm_delete_on_next_update = False - parent_instance.save(update_fields=['current_update', - 'last_update', - 'last_update_failed', + parent_instance.save(update_fields=['current_job', + 'last_job', + 'last_job_failed', 'scm_delete_on_next_update']) if getattr(settings, 'UNIFIED_JOBS_STEP') == 0: - class ProjectUpdate(CommonTask, ProjectUpdateBase): + class ProjectUpdate(ProjectUpdateBaseMethods, CommonTask, ProjectUpdateBase): class Meta: app_label = 'main' @@ -468,7 +456,7 @@ if getattr(settings, 'UNIFIED_JOBS_STEP') == 0: if getattr(settings, 'UNIFIED_JOBS_STEP') in (0, 1): - class ProjectUpdateNew(UnifiedJob, ProjectUpdateBase): + class ProjectUpdateNew(ProjectUpdateBaseMethods, UnifiedJob, ProjectUpdateBase): class Meta: app_label = 'main' @@ -489,7 +477,7 @@ if getattr(settings, 'UNIFIED_JOBS_STEP') == 1: if getattr(settings, 'UNIFIED_JOBS_STEP') == 2: - class ProjectUpdate(UnifiedJob, ProjectUpdateBase): + class ProjectUpdate(ProjectUpdateBaseMethods, UnifiedJob, ProjectUpdateBase): class Meta: app_label = 'main' diff --git a/awx/main/models/schedules.py b/awx/main/models/schedules.py index 44a9a969fa..06da1ecdfe 100644 --- a/awx/main/models/schedules.py +++ b/awx/main/models/schedules.py @@ -40,3 +40,6 @@ class Schedule(CommonModel): rrule = models.CharField( max_length=255, ) + + def save(self, *args, **kwargs): + super(Schedule, self).save(*args, **kwargs) diff --git a/awx/main/models/unified_jobs.py b/awx/main/models/unified_jobs.py index 9673910e31..c26ef64ba7 100644 --- a/awx/main/models/unified_jobs.py +++ b/awx/main/models/unified_jobs.py @@ -108,8 +108,85 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique): default='ok', editable=False, ) - - # FIXME: Include code common to Project/InventorySource/JobTemplate + + @property + def current_update(self): + return self.current_job + + @property + def last_update(self): + return self.last_job + + @property + def last_update_failed(self): + return self.last_job_failed + + @property + def last_updated(self): + return self.last_job_run + + def save(self, *args, **kwargs): + # If update_fields has been specified, add our field names to it, + # if it hasn't been specified, then we're just doing a normal save. + update_fields = kwargs.get('update_fields', []) + # Update status and last_updated fields. + updated_fields = self._set_status_and_last_job_run(save=False) + for field in updated_fields: + if field not in update_fields: + update_fields.append(field) + # Do the actual save. + super(UnifiedJobTemplate, self).save(*args, **kwargs) + + def _get_current_status(self): + # Override in subclasses as needed. + if self.current_job: + return 'updating' + elif not self.last_job: + return 'never updated' + elif self.last_job_failed: + return 'failed' + else: + return 'successful' + + def _get_last_job_run(self): + # Override in subclasses as needed. + if self.last_job: + return self.last_job.finished + + def _set_status_and_last_job_run(self, save=True): + status = self._get_current_status() + last_job_run = self._get_last_job_run() + # Update values if changed. + update_fields = [] + if self.status != status: + self.status = status + update_fields.append('status') + if self.last_job_run != last_job_run: + self.last_job_run = last_job_run + update_fields.append('last_job_run') + if save and update_fields: + self.save(update_fields=update_fields) + return update_fields + + def _can_update(self): + # Override in subclasses as needed. + return False + + @property + def can_update(self): + return self._can_update() + + def update_signature(self, **kwargs): + raise NotImplementedError # Implement in subclass. + + def update(self, **kwargs): + raise NotImplementedError # Implement in subclass. + + def _get_child_queryset(self): + pass + + def _create_child_instance(self, **kwargs): + pass class UnifiedJob(PolymorphicModel, PrimordialModel): @@ -156,7 +233,6 @@ class UnifiedJob(PolymorphicModel, PrimordialModel): editable=False, related_name='%(class)s_blocked_by+', ) - cancel_flag = models.BooleanField( blank=True, default=False, @@ -229,4 +305,176 @@ class UnifiedJob(PolymorphicModel, PrimordialModel): default='', editable=False, ) - # FIXME: Add methods from CommonTask. + + def __unicode__(self): + return u'%s-%s-%s' % (self.created, self.id, self.status) + + def _get_parent_instance(self): + return self.job_template + + def _update_parent_instance(self): + parent_instance = self._get_parent_instance() + if parent_instance: + if self.status in ('pending', 'waiting', 'running'): + if parent_instance.current_job != self: + parent_instance.current_job = self + parent_instance.save(update_fields=['current_job']) + elif self.status in ('successful', 'failed', 'error', 'canceled'): + if parent_instance.current_job == self: + parent_instance.current_job = None + parent_instance.last_job = self + parent_instance.last_job_failed = self.failed + parent_instance.save(update_fields=['current_job', + 'last_job', + 'last_job_failed']) + + def save(self, *args, **kwargs): + # If update_fields has been specified, add our field names to it, + # if it hasn't been specified, then we're just doing a normal save. + update_fields = kwargs.get('update_fields', []) + # Get status before save... + status_before = self.status or 'new' + if self.pk: + self_before = self.__class__.objects.get(pk=self.pk) + if self_before.status != self.status: + status_before = self_before.status + failed = bool(self.status in ('failed', 'error', 'canceled')) + if self.failed != failed: + self.failed = failed + if 'failed' not in update_fields: + update_fields.append('failed') + if self.status == 'running' and not self.started: + self.started = now() + if 'started' not in update_fields: + update_fields.append('started') + if self.status in ('successful', 'failed', 'error', 'canceled') and not self.finished: + self.finished = now() + if 'finished' not in update_fields: + update_fields.append('finished') + if self.started and self.finished and not self.elapsed: + td = self.finished - self.started + elapsed = (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / (10**6 * 1.0) + else: + elapsed = 0.0 + if self.elapsed != elapsed: + self.elapsed = elapsed + if 'elapsed' not in update_fields: + update_fields.append('elapsed') + super(UnifiedJob, self).save(*args, **kwargs) + # If status changed, update parent instance.... + if self.status != status_before: + self._update_parent_instance() + + def delete(self): + if self.result_stdout_file != "": + try: + os.remove(self.result_stdout_file) + except Exception, e: + pass + super(UnifiedJob, self).delete() + + @property + def result_stdout(self): + if self.result_stdout_file != "": + if not os.path.exists(self.result_stdout_file): + return "stdout capture is missing" + stdout_fd = open(self.result_stdout_file, "r") + output = stdout_fd.read() + stdout_fd.close() + return output + return self.result_stdout_text + + @property + def celery_task(self): + try: + if self.celery_task_id: + return TaskMeta.objects.get(task_id=self.celery_task_id) + except TaskMeta.DoesNotExist: + pass + + @property + def can_start(self): + return bool(self.status == 'new') + + def _get_task_class(self): + raise NotImplementedError + + def _get_passwords_needed_to_start(self): + return [] + + def start_signature(self, **kwargs): + from awx.main.tasks import handle_work_error + + task_class = self._get_task_class() + if not self.can_start: + return False + needed = self._get_passwords_needed_to_start() + opts = dict([(field, kwargs.get(field, '')) for field in needed]) + if not all(opts.values()): + return False + self.status = 'pending' + self.save(update_fields=['status']) + transaction.commit() + task_actual = task_class().si(self.pk, **opts) + return task_actual + + def start(self, **kwargs): + task_actual = self.start_signature(**kwargs) + # TODO: Callback for status + task_result = task_actual.delay() + # Reload instance from database so we don't clobber results from task + # (mainly from tests when using Django 1.4.x). + instance = self.__class__.objects.get(pk=self.pk) + # The TaskMeta instance in the database isn't created until the worker + # starts processing the task, so we can only store the task ID here. + instance.celery_task_id = task_result.task_id + instance.save(update_fields=['celery_task_id']) + return True + + @property + def can_cancel(self): + return bool(self.status in ('pending', 'waiting', 'running')) + + def _force_cancel(self): + # Update the status to 'canceled' if we can detect that the job + # really isn't running (i.e. celery has crashed or forcefully + # killed the worker). + task_statuses = ('STARTED', 'SUCCESS', 'FAILED', 'RETRY', 'REVOKED') + try: + taskmeta = self.celery_task + if not taskmeta or taskmeta.status not in task_statuses: + return + from celery import current_app + i = current_app.control.inspect() + for v in (i.active() or {}).values(): + if taskmeta.task_id in [x['id'] for x in v]: + return + for v in (i.reserved() or {}).values(): + if taskmeta.task_id in [x['id'] for x in v]: + return + for v in (i.revoked() or {}).values(): + if taskmeta.task_id in [x['id'] for x in v]: + return + for v in (i.scheduled() or {}).values(): + if taskmeta.task_id in [x['id'] for x in v]: + return + instance = self.__class__.objects.get(pk=self.pk) + if instance.can_cancel: + instance.status = 'canceled' + update_fields = ['status'] + if not instance.result_traceback: + instance.result_traceback = 'Forced cancel' + update_fields.append('result_traceback') + instance.save(update_fields=update_fields) + except: # FIXME: Log this exception! + if settings.DEBUG: + raise + + def cancel(self): + if self.can_cancel: + if not self.cancel_flag: + self.cancel_flag = True + self.save(update_fields=['cancel_flag']) + if settings.BROKER_URL.startswith('amqp://'): + self._force_cancel() + return self.cancel_flag diff --git a/awx/main/tests/base.py b/awx/main/tests/base.py index d98df261e5..2628a6b1ea 100644 --- a/awx/main/tests/base.py +++ b/awx/main/tests/base.py @@ -374,7 +374,16 @@ class BaseTestMixin(object): self.check_list_ids(response, qs, check_order) if fields: for obj in response['results']: - self.assertTrue(set(obj.keys()) <= set(fields)) + returned_fields = set(obj.keys()) + expected_fields = set(fields) + msg = '' + not_expected = returned_fields - expected_fields + if not_expected: + msg += 'fields %s not expected ' % ', '.join(not_expected) + not_returned = expected_fields - returned_fields + if not_returned: + msg += 'fields %s not returned ' % ', '.join(not_returned) + self.assertTrue(set(obj.keys()) <= set(fields), msg) def start_taskmanager(self, command_port): self.taskmanager_process = Process(target=run_taskmanager, diff --git a/awx/main/tests/jobs.py b/awx/main/tests/jobs.py index a6a810acd5..e6cbf68807 100644 --- a/awx/main/tests/jobs.py +++ b/awx/main/tests/jobs.py @@ -456,7 +456,7 @@ class BaseJobTestMixin(BaseTestMixin): class JobTemplateTest(BaseJobTestMixin, django.test.TestCase): - JOB_TEMPLATE_FIELDS = ('id', 'url', 'related', 'summary_fields', 'created', + JOB_TEMPLATE_FIELDS = ('id', 'type', 'url', 'related', 'summary_fields', 'created', 'modified', 'name', 'description', 'job_type', 'inventory', 'project', 'playbook', 'credential', 'cloud_credential', 'forks', 'limit', 'verbosity', diff --git a/awx/main/tests/projects.py b/awx/main/tests/projects.py index 5ffd7b8017..cb7e2184fd 100644 --- a/awx/main/tests/projects.py +++ b/awx/main/tests/projects.py @@ -1610,7 +1610,7 @@ class ProjectUpdatesTest(BaseTransactionTest): self.assertEqual(job.status, 'new') self.assertFalse(job.passwords_needed_to_start) self.assertTrue(job.start()) - self.assertEqual(job.status, 'pending') + self.assertTrue(job.status in ('pending', 'waiting'), job.status) job = Job.objects.get(pk=job.pk) self.assertTrue(job.status in ('successful', 'failed'), job.result_stdout + job.result_traceback) @@ -1624,7 +1624,7 @@ class ProjectUpdatesTest(BaseTransactionTest): self.assertEqual(job.status, 'new') self.assertFalse(job.passwords_needed_to_start) self.assertTrue(job.start()) - self.assertEqual(job.status, 'pending') + self.assertTrue(job.status in ('pending', 'waiting'), job.status) job = Job.objects.get(pk=job.pk) # FIXME: Not quite sure why the project update still returns successful # in this case? diff --git a/awx/settings/local_settings.py.example b/awx/settings/local_settings.py.example index 5200ee29f4..24182ab30f 100644 --- a/awx/settings/local_settings.py.example +++ b/awx/settings/local_settings.py.example @@ -269,8 +269,8 @@ TEST_GIT_USERNAME = '' TEST_GIT_PASSWORD = '' TEST_GIT_KEY_DATA = TEST_SSH_KEY_DATA TEST_GIT_PUBLIC_HTTPS = 'https://github.com/ansible/ansible.github.com.git' -TEST_GIT_PRIVATE_HTTPS = 'https://github.com/ansible/ansible-doc.git' -TEST_GIT_PRIVATE_SSH = 'git@github.com:ansible/ansible-doc.git' +TEST_GIT_PRIVATE_HTTPS = 'https://github.com/ansible/product-docs.git' +TEST_GIT_PRIVATE_SSH = 'git@github.com:ansible/product-docs.git' TEST_HG_USERNAME = '' TEST_HG_PASSWORD = '' @@ -282,7 +282,7 @@ TEST_HG_PRIVATE_SSH = '' TEST_SVN_USERNAME = '' TEST_SVN_PASSWORD = '' TEST_SVN_PUBLIC_HTTPS = 'https://github.com/ansible/ansible.github.com' -TEST_SVN_PRIVATE_HTTPS = 'https://github.com/ansible/ansible-doc' +TEST_SVN_PRIVATE_HTTPS = 'https://github.com/ansible/product-docs' # To test repo access via SSH login to localhost. import getpass