Merge pull request #2444 from jakemcdermott/fix-2334

add host_status_counts to adhoc commands; fix status display for adhoc commands
This commit is contained in:
Jake McDermott
2018-07-09 12:08:25 -04:00
committed by GitHub
4 changed files with 77 additions and 49 deletions

View File

@@ -9,7 +9,7 @@ import operator
import re import re
import six import six
import urllib import urllib
from collections import defaultdict, OrderedDict from collections import OrderedDict
from datetime import timedelta from datetime import timedelta
# OAuth2 # OAuth2
@@ -1477,23 +1477,11 @@ class ProjectUpdateDetailSerializer(ProjectUpdateSerializer):
def get_host_status_counts(self, obj): def get_host_status_counts(self, obj):
try: try:
event_data = obj.project_update_events.only('event_data').get(event='playbook_on_stats').event_data counts = obj.project_update_events.only('event_data').get(event='playbook_on_stats').get_host_status_counts()
except ProjectUpdateEvent.DoesNotExist: except ProjectUpdateEvent.DoesNotExist:
event_data = {} counts = {}
host_status = {} return counts
host_status_keys = ['skipped', 'ok', 'changed', 'failures', 'dark']
for key in host_status_keys:
for host in event_data.get(key, {}):
host_status[host] = key
host_status_counts = defaultdict(lambda: 0)
for value in host_status.values():
host_status_counts[value] += 1
return host_status_counts
class ProjectUpdateListSerializer(ProjectUpdateSerializer, UnifiedJobListSerializer): class ProjectUpdateListSerializer(ProjectUpdateSerializer, UnifiedJobListSerializer):
@@ -3274,23 +3262,11 @@ class JobDetailSerializer(JobSerializer):
def get_host_status_counts(self, obj): def get_host_status_counts(self, obj):
try: try:
event_data = obj.job_events.only('event_data').get(event='playbook_on_stats').event_data counts = obj.job_events.only('event_data').get(event='playbook_on_stats').get_host_status_counts()
except JobEvent.DoesNotExist: except JobEvent.DoesNotExist:
event_data = {} counts = {}
host_status = {} return counts
host_status_keys = ['skipped', 'ok', 'changed', 'failures', 'dark']
for key in host_status_keys:
for host in event_data.get(key, {}):
host_status[host] = key
host_status_counts = defaultdict(lambda: 0)
for value in host_status.values():
host_status_counts[value] += 1
return host_status_counts
class JobCancelSerializer(BaseSerializer): class JobCancelSerializer(BaseSerializer):
@@ -3470,6 +3446,25 @@ class AdHocCommandSerializer(UnifiedJobSerializer):
return vars_validate_or_raise(value) return vars_validate_or_raise(value)
class AdHocCommandDetailSerializer(AdHocCommandSerializer):
host_status_counts = serializers.SerializerMethodField(
help_text=_('A count of hosts uniquely assigned to each status.'),
)
class Meta:
model = AdHocCommand
fields = ('*', 'host_status_counts',)
def get_host_status_counts(self, obj):
try:
counts = obj.ad_hoc_command_events.only('event_data').get(event='playbook_on_stats').get_host_status_counts()
except AdHocCommandEvent.DoesNotExist:
counts = {}
return counts
class AdHocCommandCancelSerializer(AdHocCommandSerializer): class AdHocCommandCancelSerializer(AdHocCommandSerializer):
can_cancel = serializers.BooleanField(read_only=True) can_cancel = serializers.BooleanField(read_only=True)

View File

@@ -4569,7 +4569,7 @@ class HostAdHocCommandsList(AdHocCommandList, SubListCreateAPIView):
class AdHocCommandDetail(UnifiedJobDeletionMixin, RetrieveDestroyAPIView): class AdHocCommandDetail(UnifiedJobDeletionMixin, RetrieveDestroyAPIView):
model = AdHocCommand model = AdHocCommand
serializer_class = AdHocCommandSerializer serializer_class = AdHocCommandDetailSerializer
class AdHocCommandCancel(RetrieveAPIView): class AdHocCommandCancel(RetrieveAPIView):

View File

@@ -1,5 +1,6 @@
import datetime import datetime
import logging import logging
from collections import defaultdict
from django.conf import settings from django.conf import settings
from django.db import models, DatabaseError from django.db import models, DatabaseError
@@ -39,6 +40,21 @@ def sanitize_event_keys(kwargs, valid_keys):
kwargs[key] = Truncator(kwargs[key]).chars(1024) kwargs[key] = Truncator(kwargs[key]).chars(1024)
def create_host_status_counts(event_data):
host_status = {}
host_status_keys = ['skipped', 'ok', 'changed', 'failures', 'dark']
for key in host_status_keys:
for host in event_data.get(key, {}):
host_status[host] = key
host_status_counts = defaultdict(lambda: 0)
for value in host_status.values():
host_status_counts[value] += 1
return dict(host_status_counts)
class BasePlaybookEvent(CreatedModifiedModel): class BasePlaybookEvent(CreatedModifiedModel):
''' '''
@@ -194,6 +210,9 @@ class BasePlaybookEvent(CreatedModifiedModel):
def event_level(self): def event_level(self):
return self.LEVEL_FOR_EVENT.get(self.event, 0) return self.LEVEL_FOR_EVENT.get(self.event, 0)
def get_host_status_counts(self):
return create_host_status_counts(getattr(self, 'event_data', {}))
def get_event_display2(self): def get_event_display2(self):
msg = self.get_event_display() msg = self.get_event_display()
if self.event == 'playbook_on_play_start': if self.event == 'playbook_on_play_start':
@@ -588,6 +607,9 @@ class BaseCommandEvent(CreatedModifiedModel):
''' '''
return self.event return self.event
def get_host_status_counts(self):
return create_host_status_counts(getattr(self, 'event_data', {}))
class AdHocCommandEvent(BaseCommandEvent): class AdHocCommandEvent(BaseCommandEvent):

View File

@@ -7,6 +7,7 @@ const TASK_START = 'playbook_on_task_start';
const HOST_STATUS_KEYS = ['dark', 'failures', 'changed', 'ok', 'skipped']; const HOST_STATUS_KEYS = ['dark', 'failures', 'changed', 'ok', 'skipped'];
const COMPLETE = ['successful', 'failed']; const COMPLETE = ['successful', 'failed'];
const INCOMPLETE = ['canceled', 'error']; const INCOMPLETE = ['canceled', 'error'];
const UNSUCCESSFUL = ['failed'].concat(INCOMPLETE);
const FINISHED = COMPLETE.concat(INCOMPLETE); const FINISHED = COMPLETE.concat(INCOMPLETE);
function JobStatusService (moment, message) { function JobStatusService (moment, message) {
@@ -41,18 +42,17 @@ function JobStatusService (moment, message) {
}, },
}; };
if (model.get('type') === 'job' || model.get('type') === 'project_update') { if (model.has('host_status_counts')) {
if (model.has('playbook_counts')) { this.setHostStatusCounts(model.get('host_status_counts'));
this.setPlaybookCounts(model.get('playbook_counts'));
}
if (model.has('host_status_counts')) {
this.setHostStatusCounts(model.get('host_status_counts'));
}
} else { } else {
const hostStatusCounts = this.createHostStatusCounts(this.state.status); const hostStatusCounts = this.createHostStatusCounts(this.state.status);
this.setHostStatusCounts(hostStatusCounts); this.setHostStatusCounts(hostStatusCounts);
}
if (model.has('playbook_counts')) {
this.setPlaybookCounts(model.get('playbook_counts'));
} else {
this.setPlaybookCounts({ task_count: 1, play_count: 1 }); this.setPlaybookCounts({ task_count: 1, play_count: 1 });
} }
@@ -61,12 +61,12 @@ function JobStatusService (moment, message) {
}; };
this.createHostStatusCounts = status => { this.createHostStatusCounts = status => {
if (_.includes(COMPLETE, status)) { if (UNSUCCESSFUL.includes(status)) {
return { ok: 1 }; return { failures: 1 };
} }
if (_.includes(INCOMPLETE, status)) { if (COMPLETE.includes(status)) {
return { failures: 1 }; return { ok: 1 };
} }
return null; return null;
@@ -130,14 +130,25 @@ function JobStatusService (moment, message) {
}; };
this.isExpectingStatsEvent = () => (this.jobType === 'job') || this.isExpectingStatsEvent = () => (this.jobType === 'job') ||
(this.jobType === 'project_update'); (this.jobType === 'project_update') ||
(this.jobType === 'ad_hoc_command');
this.updateStats = () => { this.updateStats = () => {
this.updateHostCounts(); this.updateHostCounts();
if (this.statsEvent) { if (this.statsEvent) {
this.setFinished(this.statsEvent.created); this.setFinished(this.statsEvent.created);
this.setJobStatus(this.statsEvent.failed ? 'failed' : 'successful');
const failures = _.get(this.statsEvent, ['event_data', 'failures'], {});
const dark = _.get(this.statsEvent, ['event_data', 'dark'], {});
if (this.statsEvent.failed ||
Object.keys(failures).length > 0 ||
Object.keys(dark).length > 0) {
this.setJobStatus('failed');
} else {
this.setJobStatus('successful');
}
} }
}; };
@@ -173,9 +184,9 @@ function JobStatusService (moment, message) {
this.setJobStatus = status => { this.setJobStatus = status => {
const isExpectingStats = this.isExpectingStatsEvent(); const isExpectingStats = this.isExpectingStatsEvent();
const isIncomplete = _.includes(INCOMPLETE, status); const isIncomplete = INCOMPLETE.includes(status);
const isFinished = _.includes(FINISHED, status); const isFinished = FINISHED.includes(status);
const isAlreadyFinished = _.includes(FINISHED, this.state.status); const isAlreadyFinished = FINISHED.includes(this.state.status);
if (isAlreadyFinished) { if (isAlreadyFinished) {
return; return;