-
Notifications
You must be signed in to change notification settings - Fork 26
/
JobResultPersisterService.groovy
236 lines (210 loc) · 11.5 KB
/
JobResultPersisterService.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
package de.iteratec.osm.measurement.environment.wptserver
import de.iteratec.osm.measurement.environment.WebPageTestServer
import de.iteratec.osm.measurement.schedule.Job
import de.iteratec.osm.measurement.schedule.JobDaoService
import de.iteratec.osm.result.JobResult
import de.iteratec.osm.result.JobResultStatus
import de.iteratec.osm.result.WptStatus
import de.iteratec.osm.util.PerformanceLoggingService
import grails.async.Promise
import grails.gorm.transactions.Transactional
import org.hibernate.StaleObjectStateException
import org.springframework.transaction.annotation.Propagation
import java.util.concurrent.locks.ReentrantLock
import static de.iteratec.osm.util.PerformanceLoggingService.LogLevel.DEBUG
import static grails.async.Promises.task
interface iResultListener {
String getListenerName()
void listenToResult(WptResultXml resultXml, WebPageTestServer wptserver, long jobId)
boolean callListenerAsync()
}
@Transactional
class JobResultPersisterService {
JobDaoService jobDaoService
PerformanceLoggingService performanceLoggingService
private final ReentrantLock lock = new ReentrantLock()
protected List<iResultListener> resultListeners = new ArrayList<iResultListener>()
void addResultListener(iResultListener listener) {
this.resultListeners.add(listener)
}
/**
* Saves a JobResult with the given parameters and no date to indicate that the
* specified Job/test is running and that this is not the result of a finished
* test execution.
*/
JobResult persistUnfinishedJobResult(Job job, String testId, JobResultStatus jobResultStatus, String description = '') {
JobResult result = JobResult.findByJobAndTestId(job, testId)
WptStatus wptStatus = result ? result.wptStatus : WptStatus.UNKNOWN
return persistUnfinishedJobResult(job, testId, jobResultStatus, wptStatus, description)
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
JobResult persistUnfinishedJobResult(Job job, String testId, JobResultStatus jobResultStatus, WptStatus wptStatus, String description = '') {
JobResult result = testId ? JobResult.findByJobAndTestId(job, testId) : null
if (!result) {
result = persistNewUnfinishedJobResult(job, testId, jobResultStatus, wptStatus, description)
} else if (wptStatus != result.wptStatus || jobResultStatus != result.jobResultStatus) {
updateAndPersistJobResult(result, testId, jobResultStatus, wptStatus, description)
}
return result
}
JobResultStatus handleWptResult(WptResultXml resultXml, String testId, Job job) {
JobResultStatus jobResultStatus = determineJobResultStatusFromWptResult(resultXml, testId, job)
log.debug("Jobrun ${testId} has status ${jobResultStatus}")
if (jobResultStatus.isTerminated()) {
performanceLoggingService.logExecutionTime(DEBUG, "Persisting finished jobrun ${testId} of job ${job.id}.", 1) {
processFinishedJobResult(resultXml, jobResultStatus, job)
}
} else {
performanceLoggingService.logExecutionTime(DEBUG, "Polling jobrun ${testId} of job ${job.id}: updating jobresult.", 1) {
persistUnfinishedJobResult(job, testId, jobResultStatus, resultXml.wptStatus, "Polling job run.")
}
}
return jobResultStatus
}
private JobResultStatus determineJobResultStatusFromWptResult(WptResultXml resultXml, String testId, Job job) {
WptStatus wptStatus = resultXml.wptStatus
log.info("WptStatus of ${testId}: ${wptStatus.toString()} (${wptStatus.wptStatusCode})")
log.info("resultXml.hasRuns()=${resultXml.hasRuns()}")
log.info("resultXml.runCount=${resultXml.hasRuns() ? resultXml.runCount : null}")
if (wptStatus.isFailed()) {
return JobResultStatus.FAILED
} else if (wptStatus.isSuccess() && resultXml.hasRuns()) {
JobResult jobResult = JobResult.findByJobAndTestId(job, testId)
if (!jobResult) {
log.error("There is no job result for finished job id ${job.id} and test id ${testId}!")
return JobResultStatus.FAILED
}
int numValidResults = resultXml.countValidResults(jobResult.jobConfigRuns, jobResult.expectedSteps, jobResult.firstViewOnly)
int numExpectedResults = jobResult.jobConfigRuns * jobResult.expectedSteps * (jobResult.firstViewOnly ? 1 : 2)
if (numValidResults < 1) {
return JobResultStatus.FAILED
}
return numValidResults < numExpectedResults ? JobResultStatus.INCOMPLETE : JobResultStatus.SUCCESS
}
if (wptStatus == WptStatus.IN_PROGRESS) {
return JobResultStatus.RUNNING
}
return JobResultStatus.WAITING // keep polling
}
private processFinishedJobResult(WptResultXml resultXml, JobResultStatus jobResultStatus, Job job) {
try {
lock.lockInterruptibly()
persistFinishedJobResult(resultXml, jobResultStatus, job.id)
if (jobResultStatus.hasResults()) {
invokeResultPersisters(resultXml, job.location.wptServer, job.id)
}
} finally {
lock.unlock()
}
}
private invokeResultPersisters(WptResultXml resultXml, WebPageTestServer wptServer, long jobId) {
this.resultListeners.each { listener ->
log.info("calling listener ${listener.listenerName} for job id ${jobId}")
if (listener.callListenerAsync()) {
Promise p = task {
JobResult.withNewSession {
listener.listenToResult(resultXml, wptServer, jobId)
}
}
p.onError { Throwable err -> log.error("${listener.getListenerName()} failed persisting results", err) }
p.onComplete { log.info("${listener.getListenerName()} successfully returned from async task") }
} else {
listener.listenToResult(resultXml, wptServer, jobId)
}
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
private void persistFinishedJobResult(WptResultXml resultXml, JobResultStatus jobResultStatus, long jobId) throws OsmResultPersistanceException {
performanceLoggingService.logExecutionTime(DEBUG, "persist JobResult for job ${resultXml.getLabel()}, test ${resultXml.getTestId()}...", 4) {
String testId = resultXml.getTestId()
log.debug("test-ID for which results should get persisted now=${testId}, jobResultStatus=${jobResultStatus}")
if (testId == null) {
throw new OsmResultPersistanceException("No test id in result xml file from wpt server!")
}
log.debug("Deleting pending JobResults and create finished ...")
Job job = jobDaoService.getJob(jobId)
deleteUnfinishedJobResults(job, testId)
JobResult jobResult = JobResult.findByJobAndTestId(job, testId)
if (!jobResult) {
persistNewFinishedJobResult(job, testId, jobResultStatus, resultXml)
} else {
updateJobResult(jobResult, jobResultStatus, resultXml)
}
}
}
private void updateJobResult(JobResult jobResult, JobResultStatus jobResultStatus, WptResultXml resultXml) {
jobResult.testAgent = resultXml.getTestAgent()
jobResult.wptVersion = resultXml.version.toString()
updateAndPersistJobResult(jobResult, resultXml.testId, jobResultStatus, resultXml.wptStatus, resultXml.getStatusText())
}
private void updateAndPersistJobResult(JobResult result, String testId, JobResultStatus jobResultStatus, WptStatus wptStatus, String description = '') {
log.debug("Updating status of existing JobResult: Job ${result.job.id}, test-id=${testId}")
result.jobResultStatus = jobResultStatus
result.description = description
result.wptStatus = wptStatus
try {
result.save(failOnError: true, flush: true)
} catch (StaleObjectStateException staleObjectStateException) {
String logMessage = "Updating status of existing JobResult: Job ${result.job.id}, test-id=${testId}" +
"\n\t-> jobResultStatus of result couldn't get updated from ${result.jobResultStatus}->${jobResultStatus}" +
"\n\t-> wptStatus of result couldn't get updated from ${result.wptStatus}->${wptStatus}"
log.error(logMessage, staleObjectStateException)
}
}
private JobResult persistNewUnfinishedJobResult(Job job, String testId, JobResultStatus jobResultStatus, WptStatus wptStatus, String description) {
JobResult result = new JobResult(
job: job,
date: new Date(),
testId: testId ?: UUID.randomUUID() as String,
description: description,
jobConfigLabel: job.label,
jobConfigRuns: job.runs,
expectedSteps: job.script.measuredEventsCount,
firstViewOnly: job.firstViewOnly,
wptServerLabel: job.location.wptServer.label,
wptServerBaseurl: job.location.wptServer.baseUrl,
locationLabel: job.location.label,
locationLocation: job.location.location,
locationBrowser: job.location.browser.name,
locationUniqueIdentifierForServer: job.location.uniqueIdentifierForServer,
jobGroupName: job.jobGroup.name,
wptStatus: wptStatus,
jobResultStatus: jobResultStatus)
log.debug("Persisting of unfinished result: Job ${job.label}, test-id=${testId} -> persisting new JobResult=${result}")
return result.save(failOnError: true, flush: true)
}
private void deleteUnfinishedJobResults(Job job, String testId) {
JobResult.findAllByJobAndTestIdAndJobResultStatusInList(job, testId, [JobResultStatus.WAITING, JobResultStatus.RUNNING]).each {
it.delete(failOnError: true, flush: true)
}
}
private void persistNewFinishedJobResult(Job job, String testId, JobResultStatus jobResultStatus, WptResultXml resultXml) {
log.debug("persisting new JobResult ${testId}")
Date testCompletion = resultXml.getCompletionDate() ?: new Date()
JobResult result = new JobResult(
job: job,
date: testCompletion,
testId: testId,
wptStatus: resultXml.getWptStatus(),
jobResultStatus: jobResultStatus,
jobConfigLabel: job.label,
jobConfigRuns: job.runs,
expectedSteps: job.script.measuredEventsCount,
firstViewOnly: job.firstViewOnly,
wptServerLabel: job.location.wptServer.label,
wptServerBaseurl: job.location.wptServer.baseUrl,
locationLabel: job.location.label,
locationLocation: job.location.location,
locationUniqueIdentifierForServer: job.location.uniqueIdentifierForServer,
locationBrowser: job.location.browser.name,
jobGroupName: job.jobGroup.name,
testAgent: resultXml.getTestAgent(),
wptVersion: resultXml.version.toString(),
)
//new 'feature' of grails 2.3: empty strings get converted to null in map-constructors
result.setDescription(resultXml.getStatusText())
result.save(failOnError: true, flush: true)
job.lastRun = testCompletion
job.merge(failOnError: true)
}
}