From 4b4c42b5a66367be992035ff6e18227d4c4a96d1 Mon Sep 17 00:00:00 2001 From: oscar Date: Thu, 11 Apr 2024 12:26:43 -0400 Subject: [PATCH] Remove Execution usages of GormExecReportDataProvider --- core/build.gradle | 2 +- .../rundeck/data/execution/RdExecution.groovy | 1 + .../data/report/SaveReportRequestImpl.groovy | 63 +++++++------- .../controllers/ReportsController.groovy | 19 ++-- .../domain/rundeck/ExecReport.groovy | 8 +- .../domain/rundeck/Execution.groovy | 48 +++++++++++ .../quartzjobs/ExecutionsCleanUp.groovy | 2 +- .../migrations/core/BaseReportSpi.groovy | 24 ++++++ .../rundeck/services/ExecutionService.groovy | 20 +++-- .../rundeck/services/ProjectService.groovy | 10 ++- .../rundeck/services/ReportService.groovy | 77 ++++++++++------- .../groovy/ReportServiceTests.groovy | 58 ++++++------- .../GormExecReportDataProvider.groovy | 86 +++++++------------ ...GormReferencedExecutionDataProvider.groovy | 5 ++ .../GormExecReportDataProviderSpec.groovy | 45 ++++++---- .../services/ProjectServiceSpec.groovy | 5 +- 16 files changed, 286 insertions(+), 187 deletions(-) diff --git a/core/build.gradle b/core/build.gradle index 9900b3da891..d5943f84579 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -56,7 +56,7 @@ dependencies { api project(":rundeck-authz:rundeck-authz-api") api project(":rundeck-authz:rundeck-authz-core") api project(":rundeck-authz:rundeck-authz-yaml") - api "org.rundeck:rundeck-data-models:1.0.5" + api "org.rundeck:rundeck-data-models:1.0.6" api ('com.google.guava:guava:32.0.1-jre') { exclude group:'org.codehaus.mojo', module: 'animal-sniffer-annotations' diff --git a/grails-rundeck-data-shared/src/main/groovy/rundeck/data/execution/RdExecution.groovy b/grails-rundeck-data-shared/src/main/groovy/rundeck/data/execution/RdExecution.groovy index 4738ee80f6d..23a79a09dff 100644 --- a/grails-rundeck-data-shared/src/main/groovy/rundeck/data/execution/RdExecution.groovy +++ b/grails-rundeck-data-shared/src/main/groovy/rundeck/data/execution/RdExecution.groovy @@ -77,4 +77,5 @@ class RdExecution implements ExecutionData, Validateable { boolean statusSucceeded(){ return getExecutionState()== ExecutionConstants.EXECUTION_SUCCEEDED } + } diff --git a/grails-rundeck-data-shared/src/main/groovy/rundeck/data/report/SaveReportRequestImpl.groovy b/grails-rundeck-data-shared/src/main/groovy/rundeck/data/report/SaveReportRequestImpl.groovy index 1d43f51c35f..94c58c0d533 100644 --- a/grails-rundeck-data-shared/src/main/groovy/rundeck/data/report/SaveReportRequestImpl.groovy +++ b/grails-rundeck-data-shared/src/main/groovy/rundeck/data/report/SaveReportRequestImpl.groovy @@ -7,28 +7,30 @@ import java.lang.reflect.Field; @Slf4j class SaveReportRequestImpl implements SaveReportRequest { - Long executionId; - Date dateStarted; - String jobId; - String reportId; - Boolean adhocExecution; - String succeededNodeList; - String failedNodeList; - String filterApplied; - String project; - String abortedByUser; - String author; - String title; - String status; - String node; - String message; - Date dateCompleted; - String adhocScript; - String tags; + Long executionId + Date dateStarted + String jobId + String reportId + Boolean adhocExecution + String succeededNodeList + String failedNodeList + String filterApplied + String project + String abortedByUser + String author + String title + String status + String node + String message + Date dateCompleted + String adhocScript + String tags + String jobUuid + String executionUuid static void buildFromMap(SaveReportRequestImpl obj, Map data) { for (String key : data.keySet()) { - Field field; + Field field try { if(key == "ctxProject") { obj.project = (String)data.get("ctxProject"); @@ -41,17 +43,17 @@ class SaveReportRequestImpl implements SaveReportRequest { if(field != null){ if(key == "dateStarted" | key == "dateCompleted"){ if (!(data.get("dateCompleted") instanceof Date)) { - obj.dateCompleted = new Date(); + obj.dateCompleted = new Date() } else { - obj.dateCompleted = (Date)data.get("dateCompleted"); + obj.dateCompleted = (Date)data.get("dateCompleted") } if (data.get("dateStarted") instanceof Date) { - obj.dateStarted = (Date)data.get("dateStarted"); + obj.dateStarted = (Date)data.get("dateStarted") } }else if(key == "executionId"){ - obj.executionId = new Long((Integer)data.get(key)); + obj.executionId = new Long((Integer)data.get(key)) }else if(key == "adhocExecution"){ - obj.adhocExecution = Boolean.parseBoolean(String.valueOf(data.get(key))); + obj.adhocExecution = Boolean.parseBoolean(String.valueOf(data.get(key))) }else{ obj[key] = data.get(key) } @@ -60,18 +62,19 @@ class SaveReportRequestImpl implements SaveReportRequest { } catch (NoSuchFieldException nsfe) { if(!DEPRECATED_FIELD_NAMES.contains(key)) { - log.info("Report builder found unknown field: " + key); + log.info("Report builder found unknown field: " + key) } } catch (IllegalAccessException iex) { - log.warn("Unable to set field: "+key+" illegal access"); + log.warn("Unable to set field: "+key+" illegal access") } } } static SaveReportRequestImpl fromMap(Map data) { - SaveReportRequestImpl SaveReportRequest = new SaveReportRequestImpl(); - buildFromMap(SaveReportRequest, data); - return SaveReportRequest; + SaveReportRequestImpl SaveReportRequest = new SaveReportRequestImpl() + buildFromMap(SaveReportRequest, data) + return SaveReportRequest } - public static final List DEPRECATED_FIELD_NAMES = Arrays.asList("ctxProject","jcJobId","jcExecId","actionType","ctxType","ctxName","ctxCommand","ctxController","maprefUri"); + public static final List DEPRECATED_FIELD_NAMES = Arrays.asList("ctxProject","jcJobId","jcExecId","actionType","ctxType","ctxName","ctxCommand","ctxController","maprefUri") + } \ No newline at end of file diff --git a/rundeckapp/grails-app/controllers/rundeck/controllers/ReportsController.groovy b/rundeckapp/grails-app/controllers/rundeck/controllers/ReportsController.groovy index 7e7248b3acc..8f502e62dbb 100644 --- a/rundeckapp/grails-app/controllers/rundeck/controllers/ReportsController.groovy +++ b/rundeckapp/grails-app/controllers/rundeck/controllers/ReportsController.groovy @@ -298,21 +298,23 @@ class ReportsController extends ControllerBase{ results.reports=results?.reports.collect{ def map=it.toMap() map.duration= (it.dateCompleted ?: new Date()).time - it.dateStarted.time - if(map.executionId){ + if(map.executionUuid){ //nb:response data type expects string - map.executionId= map.executionId.toString() try { - map.execution = Execution.get(map.executionId).toMap() - map.executionHref = createLink(controller: 'execution', action: 'show', absolute: false, id: map.executionId, params: [project: (map?.project != null)? map.project : params.project]) - } catch (Exception e) { + map.execution = Execution.findByUuid(map.executionUuid)?.toMap() + map.executionId= map.execution.id.toString() + map.executionHref = createLink(controller: 'execution', action: 'show', absolute: false, id: map.execution.id, params: [project: (map?.project != null)? map.project : params.project]) + } catch (Exception e) { + log.debug("Error getting Execution: " + e.message) } - } + } + map.jobName= map.remove('reportId') - if(map.jcJobId){ + if(map.jobUuid){ map.jobId= map.remove('jcJobId') try { - def job = ScheduledExecution.get(Long.parseLong(map.jobId)) + def job = ScheduledExecution.findByUuid(map.jobUuid) map.jobId=job?.extid map.jobDeleted = job==null map['jobPermalink']= createLink( @@ -325,6 +327,7 @@ class ReportsController extends ControllerBase{ map.jobName=job?.jobName map.jobGroup=job?.groupPath }catch(Exception e){ + log.debug("Error getting job: "+e.message) } if(map.execution?.argString){ map.execution.jobArguments= OptionsParserUtil.parseOptsFromString(map.execution.argString) diff --git a/rundeckapp/grails-app/domain/rundeck/ExecReport.groovy b/rundeckapp/grails-app/domain/rundeck/ExecReport.groovy index 21885c892ea..15c2397285b 100644 --- a/rundeckapp/grails-app/domain/rundeck/ExecReport.groovy +++ b/rundeckapp/grails-app/domain/rundeck/ExecReport.groovy @@ -35,6 +35,7 @@ class ExecReport extends BaseReport implements RdExecReport{ String failedNodeList String filterApplied String jobUuid + String executionUuid static mapping = { adhocScript type: 'text' @@ -61,6 +62,7 @@ class ExecReport extends BaseReport implements RdExecReport{ failedNodeList(nullable:true,blank:true) filterApplied(nullable:true,blank:true) jobUuid(nullable:true) + executionUuid(nullable:true) } @@ -73,7 +75,8 @@ class ExecReport extends BaseReport implements RdExecReport{ 'succeededNodeList', 'failedNodeList', 'filterApplied', - 'jobUuid' + 'jobUuid', + 'executionUuid' ] def Map toMap(){ def map = this.properties.subMap(exportProps) @@ -135,7 +138,8 @@ class ExecReport extends BaseReport implements RdExecReport{ failedNodeList: failedList, succeededNodeList: succeededList, filterApplied: exec.filter, - jobUuid: exec.scheduledExecution?.uuid + jobUuid: exec.scheduledExecution?.uuid, + executionUuid: exec.uuid ]) } static ExecReport fromMap(Map map) { diff --git a/rundeckapp/grails-app/domain/rundeck/Execution.groovy b/rundeckapp/grails-app/domain/rundeck/Execution.groovy index 21b8f209a67..4af14689f9d 100644 --- a/rundeckapp/grails-app/domain/rundeck/Execution.groovy +++ b/rundeckapp/grails-app/domain/rundeck/Execution.groovy @@ -27,8 +27,10 @@ import com.fasterxml.jackson.core.JsonParseException import grails.gorm.DetachedCriteria import org.rundeck.app.data.model.v1.execution.ExecutionData import org.rundeck.app.data.model.v1.execution.ExecutionDataSummary +import org.rundeck.app.data.model.v1.report.dto.SaveReportRequest import rundeck.data.execution.RdExecutionDataSummary import rundeck.data.job.RdNodeConfig +import rundeck.data.report.SaveReportRequestImpl import rundeck.data.validation.shared.SharedExecutionConstraints import rundeck.data.validation.shared.SharedNodeConfigConstraints import rundeck.data.validation.shared.SharedProjectNameConstraints @@ -565,5 +567,51 @@ class Execution extends ExecutionContext implements EmbeddedJsonData, ExecutionD serverNodeUUID: this.serverNodeUUID ) } + + SaveReportRequest toSaveReportRequest() { + def failedCount = failedNodeList ? failedNodeList.split(',').size() : 0 + def successCount = succeededNodeList ? succeededNodeList.split(',').size() : 0; + def failedList = failedNodeList ? failedNodeList : '' + def succeededList = succeededNodeList ? succeededNodeList : ''; + def totalCount = failedCount + successCount; + def adhocScript = null + if ( + null == scheduledExecution + && workflow.commands + && workflow.commands.size() == 1 + && workflow.commands[0] instanceof CommandExec + ) { + adhocScript = workflow.commands[0].adhocRemoteString + } + def summary = "[${workflow.commands ? workflow.commands.size() : 0} steps]" + def issuccess = statusSucceeded() + def iscancelled = cancelled + def istimedout = timedOut + def ismissed = status == "missed" + def status = issuccess ? "succeed" : iscancelled ? "cancel" : willRetry ? "retry" : istimedout ? + "timedout" : ismissed ? "missed" : "fail" + return new SaveReportRequestImpl( + executionId: id, + jobId: scheduledExecution?.id, + adhocExecution: null == scheduledExecution, + adhocScript: adhocScript, + abortedByUser: iscancelled ? abortedby ?: user : null, + node: "${successCount}/${failedCount}/${totalCount}", + title: adhocScript ? adhocScript : summary, + status: status, + project: project, + reportId: scheduledExecution ? (scheduledExecution.groupPath ? scheduledExecution.generateFullName() : scheduledExecution.jobName) : 'adhoc', + author: user, + message: (issuccess ? 'Job completed successfully' : iscancelled ? ('Job killed by: ' + (abortedby ?: user)) : ismissed ? "Job missed execution at: ${dateStarted}" : 'Job failed'), + dateStarted: dateStarted, + dateCompleted: dateCompleted, + failedNodeList: failedList, + succeededNodeList: succeededList, + filterApplied: filter, + jobUuid: scheduledExecution?.uuid, + executionUuid: uuid) + } + + } diff --git a/rundeckapp/grails-app/jobs/rundeck/quartzjobs/ExecutionsCleanUp.groovy b/rundeckapp/grails-app/jobs/rundeck/quartzjobs/ExecutionsCleanUp.groovy index e88af76ac49..f3dcc5b0345 100644 --- a/rundeckapp/grails-app/jobs/rundeck/quartzjobs/ExecutionsCleanUp.groovy +++ b/rundeckapp/grails-app/jobs/rundeck/quartzjobs/ExecutionsCleanUp.groovy @@ -91,7 +91,7 @@ class ExecutionsCleanUp implements InterruptableJob { referencedExecutionDataProvider.deleteByExecutionId(e.id) //delete all reports - reportService.deleteByExecutionId(e.id) + reportService.deleteByExecutionUuid(e.uuid) def executionFiles = logFileStorageService.getExecutionFiles(e, [], false) List files = [] diff --git a/rundeckapp/grails-app/migrations/core/BaseReportSpi.groovy b/rundeckapp/grails-app/migrations/core/BaseReportSpi.groovy index 728cccec932..24b24c88ed0 100644 --- a/rundeckapp/grails-app/migrations/core/BaseReportSpi.groovy +++ b/rundeckapp/grails-app/migrations/core/BaseReportSpi.groovy @@ -25,4 +25,28 @@ databaseChangeLog = { " inner join execution ON execution.scheduled_execution_id = scheduled_execution.id\n" + " WHERE base_report.execution_id = execution.id)") } + + changeSet(author: "rundeckdev", id: "add-execution-uuid-to-base-report") { + preConditions(onFail: "MARK_RAN") { + not { + columnExists(tableName: "base_report", columnName: 'execution_uuid') + } + } + addColumn(tableName: "base_report") { + column(name: 'execution_uuid', type: '${varchar255.type}') + } + } + + changeSet(author: "rundeckdev", id: "populate-base-report-execution-uuid") { + preConditions(onFail: "MARK_RAN") { + tableExists(tableName: "base_report") + tableExists(tableName: "execution") + } + sql("update base_report\n" + + "set execution_uuid =\n" + + " (select execution.uuid\n" + + " from execution\n" + + " WHERE base_report.execution_id = execution.id)") + } + } \ No newline at end of file diff --git a/rundeckapp/grails-app/services/rundeck/services/ExecutionService.groovy b/rundeckapp/grails-app/services/rundeck/services/ExecutionService.groovy index 3d1b5fb05b1..da6de138d2d 100644 --- a/rundeckapp/grails-app/services/rundeck/services/ExecutionService.groovy +++ b/rundeckapp/grails-app/services/rundeck/services/ExecutionService.groovy @@ -918,7 +918,7 @@ class ExecutionService implements ApplicationContextAware, StepExecutor, NodeSte public logExecution(uri,project,user,issuccess,statusString,execId,Date startDate=null, jobExecId=null, jobName=null, jobSummary=null,iscancelled=false,istimedout=false,willretry=false, nodesummary=null, - abortedby=null, succeededNodeList=null, failedNodeList=null, filter=null){ + abortedby=null, succeededNodeList=null, failedNodeList=null, filter=null, executionUuid=null, jobUuid=null){ SaveReportRequestImpl saveReportRequest = new SaveReportRequestImpl() def internalLog = LoggerFactory.getLogger("ExecutionService") @@ -931,6 +931,9 @@ class ExecutionService implements ApplicationContextAware, StepExecutor, NodeSte if(execId){ saveReportRequest.executionId=execId } + if(executionUuid){ + saveReportRequest.executionUuid=executionUuid + } if(startDate){ saveReportRequest.dateStarted=startDate } @@ -961,6 +964,9 @@ class ExecutionService implements ApplicationContextAware, StepExecutor, NodeSte }else if(iscancelled){ saveReportRequest.abortedByUser=user } + if(jobUuid){ + saveReportRequest.jobUuid = jobUuid + } saveReportRequest.author=user saveReportRequest.title= jobSummary?jobSummary:"Rundeck Job Execution" @@ -981,7 +987,7 @@ class ExecutionService implements ApplicationContextAware, StepExecutor, NodeSte saveReportRequest.dateCompleted=new Date() def result=reportService.reportExecutionResult(saveReportRequest) if(result.error){ - log.error("Failed to create report: "+result.report.errors.allErrors.collect{it.toString()}).join("; ") + log.error("Failed to create report: "+result.errors) } } @@ -2030,7 +2036,7 @@ class ExecutionService implements ApplicationContextAware, StepExecutor, NodeSte } referencedExecutionDataProvider.deleteByExecutionId(e.id) //delete all reports - execReportDataProvider.deleteAllByExecutionId(e.id) + execReportDataProvider.deleteAllByExecutionUuid(e.uuid) List files = [] def execs = [] @@ -3186,10 +3192,12 @@ class ExecutionService implements ApplicationContextAware, StepExecutor, NodeSte } def jobname="adhoc" def jobid=null + def jobUuid=null def summary= summarizeJob(scheduledExecution, execution) if (scheduledExecution) { jobname = scheduledExecution.groupPath ? scheduledExecution.generateFullName() : scheduledExecution.jobName jobid = scheduledExecution.id + jobUuid = scheduledExecution.uuid } if(execSaved) { //summarize node success @@ -3226,7 +3234,9 @@ class ExecutionService implements ApplicationContextAware, StepExecutor, NodeSte execution.abortedby, execution.succeededNodeList, execution.failedNodeList, - execution.filter + execution.filter, + execution.uuid, + jobUuid ) logExecutionLog4j(execution, "finish", execution.user) @@ -4450,7 +4460,7 @@ class ExecutionService implements ApplicationContextAware, StepExecutor, NodeSte missed.status = 'missed' missed.save() - execReportDataProvider.createReportFromExecution(missed.id) + execReportDataProvider.saveReport(missed.toSaveReportRequest()) if(scheduledExecution.notifications) { AuthContext authContext = rundeckAuthContextProcessor. diff --git a/rundeckapp/grails-app/services/rundeck/services/ProjectService.groovy b/rundeckapp/grails-app/services/rundeck/services/ProjectService.groovy index ddf393289a1..472c6e231fe 100644 --- a/rundeckapp/grails-app/services/rundeck/services/ProjectService.groovy +++ b/rundeckapp/grails-app/services/rundeck/services/ProjectService.groovy @@ -924,7 +924,7 @@ class ProjectService implements InitializingBean, ExecutionFileProducer, EventPu } } execs = Execution.findAllByProjectAndIdInList(projectName, execIds) - reports = execReportDataProvider.findAllByProjectAndExecutionIdInList(projectName, execIds) + reports = execReportDataProvider.findAllByProjectAndExecutionUuidInList(projectName, execs.collect{it.uuid}) } else if (isExportExecutions) { execs = Execution.findAllByProject(projectName) reports = execReportDataProvider.findAllByProject(projectName) @@ -958,6 +958,7 @@ class ProjectService implements InitializingBean, ExecutionFileProducer, EventPu dir('reports/') { reports.each { RdExecReport report -> exportHistoryReport zip, report, "report-${report.id}.xml" + def a = report.toMap() listener?.inc('export', 1) } } @@ -1668,7 +1669,12 @@ class ProjectService implements InitializingBean, ExecutionFileProducer, EventPu } //generate reports for executions without matching reports execids.each { eid -> - def saveReportResponse = execReportDataProvider.createReportFromExecution(eid) + def execution = Execution.get(eid) + if(!execution) { + log.error("Execution not found with id: ${eid}") + return + } + def saveReportResponse = execReportDataProvider.saveReport(execution.toSaveReportRequest()) if (!saveReportResponse.isSaved) { log.error("Unable to save generated report: ${saveReportResponse.errors} (execution ${eid})") return diff --git a/rundeckapp/grails-app/services/rundeck/services/ReportService.groovy b/rundeckapp/grails-app/services/rundeck/services/ReportService.groovy index 1f361af6db9..364b61542d0 100644 --- a/rundeckapp/grails-app/services/rundeck/services/ReportService.groovy +++ b/rundeckapp/grails-app/services/rundeck/services/ReportService.groovy @@ -25,6 +25,8 @@ import grails.gorm.DetachedCriteria import grails.gorm.transactions.Transactional import org.rundeck.app.authorization.AppAuthContextEvaluator import org.rundeck.app.data.model.v1.query.RdExecQuery +import org.rundeck.app.data.model.v1.report.dto.SaveReportResponse +import org.rundeck.app.data.providers.v1.execution.ReferencedExecutionDataProvider import rundeck.data.report.SaveReportRequestImpl import rundeck.data.report.SaveReportResponseImpl import org.rundeck.app.data.providers.v1.report.ExecReportDataProvider @@ -41,6 +43,7 @@ class ReportService { AppAuthContextEvaluator rundeckAuthContextEvaluator ConfigurationService configurationService ExecReportDataProvider execReportDataProvider + ReferencedExecutionDataProvider referencedExecutionDataProvider static final String GRANTED_VIEW_HISTORY_JOBS = "granted_view_history_jobs" static final String DENIED_VIEW_HISTORY_JOBS = "rejected_view_history_jobs" @@ -55,7 +58,7 @@ class ReportService { if(!saveReportRequest.message){ saveReportRequest.message="[no message]" } - SaveReportResponseImpl saveReportResponse = execReportDataProvider.saveReport(saveReportRequest) + SaveReportResponse saveReportResponse = execReportDataProvider.saveReport(saveReportRequest) //TODO: authorize event creation? @@ -64,13 +67,13 @@ class ReportService { // rep.errors.allErrors.each { // System.err.println(it) // } - return [error:true,report:saveReportResponse.report] + return [error:true,report:saveReportResponse.report,errors:saveReportResponse.errors] }else{ return [success:true] } } - def public finishquery(ExecQuery query,def params, Map model){ + def public finishquery(ExecQuery query,def params, Map model){ if(!params.max){ params.max=grailsApplication.config.getProperty("reportservice.pagination.default",Integer.class, 20) @@ -159,41 +162,41 @@ class ReportService { paginateParams.remove(it) } - Integer defaultMax = grailsApplication.config.getProperty("reportservice.pagination.default",Integer.class, 20) + Integer defaultMax = grailsApplication.config.getProperty("reportservice.pagination.default",Integer.class, 20) - def tmod=[max: query?.max?query.max:defaultMax, - offset:query?.offset?query.offset:0, - paginateParams:paginateParams, - displayParams:displayParams] + def tmod=[max: query?.max?query.max:defaultMax, + offset:query?.offset?query.offset:0, + paginateParams:paginateParams, + displayParams:displayParams] model.putAll(tmod) return model } private def getStartsWithFilters() { return [ - //job filter repurposed for reportId - job: 'reportId', + //job filter repurposed for reportId + job: 'reportId', ] } private def getTxtFilters() { def txtfilters = [ - obj: 'ctxName', - user: 'author', - abortedBy: 'abortedByUser', - node: 'node', - message: 'message', - title: 'title', - tags: 'tags', + obj: 'ctxName', + user: 'author', + abortedBy: 'abortedByUser', + node: 'node', + message: 'message', + title: 'title', + tags: 'tags', ] return txtfilters } private def getEqFilters() { def eqfilters = [ - stat: 'status', - reportId: 'reportId', - jobId:'jobId', - proj: 'project', + stat: 'status', + reportId: 'reportId', + jobId:'jobId', + proj: 'project', ] return eqfilters } @@ -433,8 +436,16 @@ class ReportService { filters.putAll(txtfilters) filters.putAll(eqfilters) - def seId = se?.id?: null - def runlist = execReportDataProvider.getExecutionReports(query, isJobs, seId) + def seUuid = se?.uuid?: null + + //TO DO: Move to execution provider when possible + def execUuids = [] + if (query.execProjects && seUuid) { + execUuids = referencedExecutionDataProvider.getExecutionUuidsByJobUuid(seUuid) + } + + + def runlist = execReportDataProvider.getExecutionReports(query, isJobs, seUuid, execUuids) def executions=[] def lastDate = -1 @@ -444,18 +455,18 @@ class ReportService { lastDate = it.dateCompleted.time } } - def total = execReportDataProvider.countExecutionReportsWithTransaction(query, isJobs, seId) + def total = execReportDataProvider.countExecutionReportsWithTransaction(query, isJobs, seUuid) filters.putAll(specialfilters) return [ - query:query, - reports:executions, - total: total, - lastDate: lastDate, - _filters:filters - ] - } + query:query, + reports:executions, + total: total, + lastDate: lastDate, + _filters:filters + ] + } /** * Sorts jobs according to user permission @@ -517,8 +528,8 @@ class ReportService { return rundeckAuthContextEvaluator.authorizeProjectResources(authContext,resHS, constraints, project) } - def deleteByExecutionId(Long id){ - execReportDataProvider.deleteAllByExecutionId(id) + def deleteByExecutionUuid(String uuid){ + execReportDataProvider.deleteAllByExecutionUuid(uuid) } private boolean isOracleDatasource(){ def dataSource = applicationContext.getBean('dataSource', DataSource) diff --git a/rundeckapp/src/integration-test/groovy/ReportServiceTests.groovy b/rundeckapp/src/integration-test/groovy/ReportServiceTests.groovy index 14fbe7c6b03..e22658a9d2a 100644 --- a/rundeckapp/src/integration-test/groovy/ReportServiceTests.groovy +++ b/rundeckapp/src/integration-test/groovy/ReportServiceTests.groovy @@ -47,15 +47,15 @@ class ReportServiceTests extends GroovyTestCase { void testGetExecReportsReportIdFilter(){ def r1, r2, r3 - r1 = proto(reportId: 'blah', executionId: '123') + r1 = proto(reportId: 'blah', executionId: '123', executionUuid: 'uuid1') assert r1.validate() assert null != r1.save(flush: true) assert 'blah' == r1.reportId assertNotNull(r1.id) - r2 = proto(reportId: 'blah2', executionId: '124') + r2 = proto(reportId: 'blah2', executionId: '124', executionUuid: 'uuid2') assert r2.validate() assert null != r2.save(flush: true) - r3 = proto(reportId: 'blah3', executionId: '125') + r3 = proto(reportId: 'blah3', executionId: '125', executionUuid: 'uuid3') assert r3.validate() println r3.save(flush: true) @@ -79,18 +79,18 @@ class ReportServiceTests extends GroovyTestCase { void testGetExecNodeFilterReportIdFilter(){ def r1,r2,r3, r4 - r1=proto(reportId:'blah', executionId: '123', succeededNodeList:'test') + r1=proto(reportId:'blah', executionId: '123', succeededNodeList:'test', executionUuid: 'uuid1') assert r1.validate() assert null!=r1.save(flush: true) assert 'blah'==r1.reportId assertNotNull(r1.id) - r2 = proto(reportId: 'blah2', executionId: '124', failedNodeList:'test') + r2 = proto(reportId: 'blah2', executionId: '124', failedNodeList:'test', executionUuid: 'uuid2') assert r2.validate() assert null != r2.save(flush: true) - r3 = proto(reportId: 'blah3', executionId: '125', filterApplied:'tags: monkey') + r3 = proto(reportId: 'blah3', executionId: '125', filterApplied:'tags: monkey', executionUuid: 'uuid3') assert r3.validate() println r3.save(flush: true) - r4 = proto(reportId: 'blah4', executionId: '126', filterApplied:'.*',succeededNodeList:'test') + r4 = proto(reportId: 'blah4', executionId: '126', filterApplied:'.*',succeededNodeList:'test', executionUuid: 'uuid4') assert r4.validate() println r4.save(flush: true) @@ -121,15 +121,15 @@ class ReportServiceTests extends GroovyTestCase { void testGetExecReportsProjFilterIsExact(){ def r1,r2,r3 - r1=proto(reportId:'blah', executionId: '123', project:'abc') + r1=proto(reportId:'blah', executionId: '123', project:'abc', executionUuid: 'uuid1') assert r1.validate() assert null!=r1.save(flush: true) assert 'blah'==r1.reportId assertNotNull(r1.id) - r2 = proto(reportId: 'blah2', executionId: '124', project: 'abc') + r2 = proto(reportId: 'blah2', executionId: '124', project: 'abc', executionUuid: 'uuid2') assert r2.validate() assert null != r2.save(flush: true) - r3 = proto(reportId: 'blah3', executionId: '125', project: 'abcdef') + r3 = proto(reportId: 'blah3', executionId: '125', project: 'abcdef', executionUuid: 'uuid3') assert r3.validate() println r3.save(flush: true) @@ -152,13 +152,13 @@ class ReportServiceTests extends GroovyTestCase { } @Test void testGetExecReportsJobListFilter(){ - def r1 = proto(reportId: 'group/name', executionId: '1') + def r1 = proto(reportId: 'group/name', executionId: '1', executionUuid: 'uuid1') assert null != r1.save(flush: true) - def r2 = proto(reportId: 'group/name2', executionId: '2') + def r2 = proto(reportId: 'group/name2', executionId: '2', executionUuid: 'uuid2') assert null != r2.save(flush: true) - def r3 = proto(reportId: 'group/name3', executionId: '3') + def r3 = proto(reportId: 'group/name3', executionId: '3', executionUuid: 'uuid3') assert null != r3.save(flush: true) - def r4 = proto(reportId: 'group/monkey', executionId: '4') + def r4 = proto(reportId: 'group/monkey', executionId: '4', executionUuid: 'uuid4') assert null != r4.save(flush: true) assertQueryResult([jobListFilter: ['group/name']], [r1]) @@ -169,14 +169,14 @@ class ReportServiceTests extends GroovyTestCase { } @Test void testGetExecReportsStatusStringVariations(){ - def r1 = proto(reportId: 'group/name', executionId: '1', status: 'failed', actionType: 'failed') + def r1 = proto(reportId: 'group/name', executionId: '1', status: 'failed', actionType: 'failed', executionUuid: 'uuid1') assert null != r1.save(flush: true) - def r2 = proto(reportId: 'group/name2', executionId: '2', status: 'fail', actionType: 'fail') + def r2 = proto(reportId: 'group/name2', executionId: '2', status: 'fail', actionType: 'fail', executionUuid: 'uuid2') assert null != r2.save(flush: true) - def r3 = proto(reportId: 'group/name2', executionId: '3', status: 'succeed', actionType: 'succeed') + def r3 = proto(reportId: 'group/name2', executionId: '3', status: 'succeed', actionType: 'succeed', executionUuid: 'uuid3') assert null != r3.save(flush: true) - def r4 = proto(reportId: 'group/name2', executionId: '4', status: 'succeeded', actionType: 'succeeded') + def r4 = proto(reportId: 'group/name2', executionId: '4', status: 'succeeded', actionType: 'succeeded', executionUuid: 'uuid4') assert null != r4.save(flush: true) assertQueryResult([statFilter: 'fail'], [r1, r2]) @@ -185,11 +185,11 @@ class ReportServiceTests extends GroovyTestCase { } @Test void testGetCombinedReportsExcludeJobListFilter(){ - def r1 = proto(reportId: 'group/name', executionId: '1') + def r1 = proto(reportId: 'group/name', executionId: '1', executionUuid: 'uuid1') assert null != r1.save(flush: true) - def r2 = proto(reportId: 'group/name2', executionId: '2') + def r2 = proto(reportId: 'group/name2', executionId: '2', executionUuid: 'uuid2') assert null != r2.save(flush: true) - def r3 = proto(reportId: 'group/name3', executionId: '3') + def r3 = proto(reportId: 'group/name3', executionId: '3', executionUuid: 'uuid3') assert null != r3.save(flush: true) assertQueryResult([excludeJobListFilter: ['group/name']], [r2, r3]) @@ -209,9 +209,9 @@ class ReportServiceTests extends GroovyTestCase { assert null != se.save(flush: true) assert null != se2.save(flush: true) - def r1 = proto(reportId: 'group/name', jobId: se.id,project: 'one') + def r1 = proto(reportId: 'group/name', jobId: se.id,project: 'one', executionUuid: 'uuid1') assert null != r1.save(flush: true) - def r2 = proto(reportId: 'group/name2', jobId: se2.id,project: 'one') + def r2 = proto(reportId: 'group/name2', jobId: se2.id,project: 'one', executionUuid: 'uuid2') assert null != r2.save(flush: true) sessionFactory.currentSession.flush() @@ -238,15 +238,15 @@ class ReportServiceTests extends GroovyTestCase { void testGetExecReportsFailedStat(){ def r1,r2,r3 - r1 = proto(reportId: 'blah', executionId: '123', status: 'fail') + r1 = proto(reportId: 'blah', executionId: '123', status: 'fail', executionUuid: 'uuid1') assert r1.validate() assert null != r1.save(flush: true) assert 'blah' == r1.reportId assertNotNull(r1.id) - r2 = proto(reportId: 'blah2', executionId: '124', status: 'fail') + r2 = proto(reportId: 'blah2', executionId: '124', status: 'fail', executionUuid: 'uuid2') assert r2.validate() assert null != r2.save(flush: true) - r3 = proto(reportId: 'blah3', executionId: '125', status: 'succes') + r3 = proto(reportId: 'blah3', executionId: '125', status: 'succes', executionUuid: 'uuid3') assert r3.validate() println r3.save(flush: true) @@ -271,15 +271,15 @@ class ReportServiceTests extends GroovyTestCase { void testGetExecReportsKilledStat(){ def r1,r2,r3 - r1=proto(reportId:'blah', executionId: '123', status: 'fail', abortedByUser: 'admin') + r1=proto(reportId:'blah', executionId: '123', status: 'fail', abortedByUser: 'admin', executionUuid: 'uuid1') assert r1.validate() assert null!=r1.save(flush: true) assert 'blah'==r1.reportId assertNotNull(r1.id) - r2 = proto(reportId: 'blah2', executionId: '124',status: 'fail') + r2 = proto(reportId: 'blah2', executionId: '124',status: 'fail', executionUuid: 'uuid2') assert r2.validate() assert null != r2.save(flush: true) - r3 = proto(reportId: 'blah3', executionId: '125',status: 'succes') + r3 = proto(reportId: 'blah3', executionId: '125',status: 'succes', executionUuid: 'uuid3') assert r3.validate() println r3.save(flush: true) diff --git a/rundeckapp/src/main/groovy/org/rundeck/app/data/providers/GormExecReportDataProvider.groovy b/rundeckapp/src/main/groovy/org/rundeck/app/data/providers/GormExecReportDataProvider.groovy index 34a4506e7b4..6525b28870c 100644 --- a/rundeckapp/src/main/groovy/org/rundeck/app/data/providers/GormExecReportDataProvider.groovy +++ b/rundeckapp/src/main/groovy/org/rundeck/app/data/providers/GormExecReportDataProvider.groovy @@ -8,21 +8,18 @@ import org.rundeck.app.data.model.v1.query.RdExecQuery import org.rundeck.app.data.model.v1.report.RdExecReport import org.rundeck.app.data.model.v1.report.dto.SaveReportRequest import org.rundeck.app.data.model.v1.report.dto.SaveReportResponse -import rundeck.data.report.SaveReportResponseImpl; +import rundeck.data.report.SaveReportResponseImpl import org.rundeck.app.data.providers.v1.report.ExecReportDataProvider import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.ApplicationContext import org.springframework.context.MessageSource import org.springframework.transaction.TransactionStatus -import rundeck.BaseReport; +import rundeck.BaseReport import rundeck.ExecReport -import rundeck.Execution import rundeck.ReferencedExecution import rundeck.ScheduledExecution import rundeck.services.ConfigurationService - -import javax.persistence.EntityNotFoundException -import javax.sql.DataSource; +import javax.sql.DataSource @CompileStatic(TypeCheckingMode.SKIP) class GormExecReportDataProvider implements ExecReportDataProvider { @@ -38,31 +35,9 @@ class GormExecReportDataProvider implements ExecReportDataProvider { return ExecReport.get(id) } - @Override - SaveReportResponse createReportFromExecution(Long id) { - Execution execution = Execution.get(id) - if(!execution) throw new EntityNotFoundException("Execution not found with id: ${id}") - createReportFromExecution(execution) - } - - @Override - SaveReportResponse createReportFromExecution(String uuid) { - Execution execution = Execution.findByUuid(uuid) - if(!execution) throw new EntityNotFoundException("Execution not found with uuid: ${uuid}") - createReportFromExecution(execution) - } - - SaveReportResponse createReportFromExecution(Execution execution) { - ExecReport execReport = ExecReport.fromExec(execution) - boolean isUpdated = execReport.save(flush: true) - String errors = execReport.errors.hasErrors() ? execReport.errors.allErrors.collect { messageSource.getMessage(it,null) }.join(",") : null - return new SaveReportResponseImpl(report: execReport, isSaved: isUpdated, errors: errors) - } - @Override SaveReportResponse saveReport(SaveReportRequest saveReportRequest) { ExecReport execReport = new ExecReport() - Execution execution = Execution.get(saveReportRequest.executionId) execReport.executionId = saveReportRequest.executionId execReport.jobId = saveReportRequest.jobId execReport.adhocExecution = saveReportRequest.adhocExecution @@ -82,15 +57,17 @@ class GormExecReportDataProvider implements ExecReportDataProvider { execReport.message = saveReportRequest.message execReport.dateStarted = saveReportRequest.dateStarted execReport.dateCompleted = saveReportRequest.dateCompleted - execReport.jobUuid = execution.scheduledExecution?.uuid + execReport.jobUuid = saveReportRequest.jobUuid + execReport.executionUuid = saveReportRequest.executionUuid boolean isUpdated = execReport.save(flush: true) - String errors = execReport.errors.hasErrors() ? execReport.errors.allErrors.collect { messageSource.getMessage(it,null) }.join(",") : null + String errors = execReport.errors.hasErrors() ? execReport.errors.allErrors.collect { + messageSource.getMessage(it,null) }.join(",") : null return new SaveReportResponseImpl(report: execReport, isSaved: isUpdated, errors: errors) } @Override List findAllByProject(String projectName) { - return BaseReport.findAllByProject(projectName) + return ExecReport.findAllByProject(projectName) } @Override @@ -98,12 +75,8 @@ class GormExecReportDataProvider implements ExecReportDataProvider { return ExecReport.findAllByStatus(status) } @Override - List findAllByExecutionId(Long id) { - return ExecReport.findAllByExecutionId(id) - } - @Override - List findAllByProjectAndExecutionIdInList(String projectName, List execIds) { - return ExecReport.findAllByProjectAndExecutionIdInList(projectName, execIds) + List findAllByProjectAndExecutionUuidInList(String projectName, List execUuids) { + return ExecReport.findAllByProjectAndExecutionUuidInList(projectName, execUuids) } @Override @@ -119,10 +92,10 @@ class GormExecReportDataProvider implements ExecReportDataProvider { } @Override - int countExecutionReportsWithTransaction(RdExecQuery query, boolean isJobs, Long jobId) { + int countExecutionReportsWithTransaction(RdExecQuery query, boolean isJobs, String jobId) { return ExecReport.withTransaction { ExecReport.createCriteria().count { - applyExecutionCriteria(query, delegate, isJobs, jobId) + applyExecutionCriteria(query, delegate, isJobs, jobId, []) } } } @@ -147,7 +120,7 @@ class GormExecReportDataProvider implements ExecReportDataProvider { } @Override - List getExecutionReports(RdExecQuery query, boolean isJobs, Long jobId) { + List getExecutionReports(RdExecQuery query, boolean isJobs, String jobId, List execUuids) { def eqfilters = [ stat: 'status', reportId: 'reportId', @@ -166,7 +139,7 @@ class GormExecReportDataProvider implements ExecReportDataProvider { filters.putAll(txtfilters) filters.putAll(eqfilters) - return ExecReport.createCriteria().list { + return ExecReport.createCriteria().list { if (query?.max) { maxResults(query?.max.toInteger()) @@ -176,7 +149,7 @@ class GormExecReportDataProvider implements ExecReportDataProvider { if (query?.offset) { firstResult(query.offset.toInteger()) } - applyExecutionCriteria(query, delegate,isJobs, jobId) + applyExecutionCriteria(query, delegate,isJobs, jobId, execUuids) if (query && query.sortBy && filters[query.sortBy]) { order(filters[query.sortBy], query.sortOrder == 'ascending' ? 'asc' : 'desc') @@ -205,13 +178,13 @@ class GormExecReportDataProvider implements ExecReportDataProvider { } @Override - void deleteAllByExecutionId(Long executionId) { - ExecReport.findAllByExecutionId(executionId).each { rpt -> + void deleteAllByExecutionUuid(String executionUuid) { + ExecReport.findAllByExecutionUuid(executionUuid).each { rpt -> rpt.delete() } } - def applyExecutionCriteria(RdExecQuery query, delegate, boolean isJobs=true, Long seId=null){ + def applyExecutionCriteria(RdExecQuery query, delegate, boolean isJobs=true, String seId=null, List execUuids=[]){ def eqfilters = [ stat: 'status', reportId: 'reportId', @@ -277,20 +250,21 @@ class GormExecReportDataProvider implements ExecReportDataProvider { } if (query.execProjects && seId) { - String jobUuid = ScheduledExecution.get(seId).uuid or { - exists(new DetachedCriteria(ReferencedExecution, "re").build { - projections { property 're.execution.id' } - eq('re.jobUuid', jobUuid) - eqProperty('re.execution.id', 'this.executionId') - List execProjectsPartitioned = Lists.partition(query.execProjects, 1000) - or{ - for(def partition : execProjectsPartitioned){ - 'in'('this.project', partition) + if(execUuids && execUuids.size() > 0){ + and{ + 'in'('executionUuid', execUuids) + List execProjectsPartitioned = Lists.partition(query.execProjects, 1000) + or{ + for(def partition : execProjectsPartitioned){ + 'in'('project', partition) + } } } - }) - eq('jobId', String.valueOf(seId)) + } + + + eq('jobUuid', seId) and{ jobfilters.each { key, val -> if (query["${key}Filter"] == 'null') { diff --git a/rundeckapp/src/main/groovy/org/rundeck/app/data/providers/GormReferencedExecutionDataProvider.groovy b/rundeckapp/src/main/groovy/org/rundeck/app/data/providers/GormReferencedExecutionDataProvider.groovy index dc06810fc6f..b95800ba267 100644 --- a/rundeckapp/src/main/groovy/org/rundeck/app/data/providers/GormReferencedExecutionDataProvider.groovy +++ b/rundeckapp/src/main/groovy/org/rundeck/app/data/providers/GormReferencedExecutionDataProvider.groovy @@ -39,6 +39,11 @@ class GormReferencedExecutionDataProvider implements ReferencedExecutionDataProv return ReferencedExecution.executionProjectList(jobUuid, max) } + @Override + List getExecutionUuidsByJobUuid(String jobUuid) { + return ReferencedExecution.findAllByJobUuid(jobUuid).collect{ it.execution.scheduledExecution.uuid } + } + @Override void deleteByExecutionId(Long id) { def execution = Execution.findById(id) diff --git a/rundeckapp/src/test/groovy/org/rundeck/app/data/providers/GormExecReportDataProviderSpec.groovy b/rundeckapp/src/test/groovy/org/rundeck/app/data/providers/GormExecReportDataProviderSpec.groovy index c1456f30f64..36244784cc5 100644 --- a/rundeckapp/src/test/groovy/org/rundeck/app/data/providers/GormExecReportDataProviderSpec.groovy +++ b/rundeckapp/src/test/groovy/org/rundeck/app/data/providers/GormExecReportDataProviderSpec.groovy @@ -1,12 +1,14 @@ package org.rundeck.app.data.providers import grails.testing.gorm.DataTest +import org.springframework.context.MessageSource import rundeck.CommandExec import rundeck.ExecReport import rundeck.Execution import rundeck.JobExec import rundeck.PluginStep import rundeck.Workflow +import rundeck.data.report.SaveReportRequestImpl import spock.lang.Specification import testhelper.TestDomainFactory @@ -17,15 +19,16 @@ class GormExecReportDataProviderSpec extends Specification implements DataTest { def setupSpec() { mockDomains(Execution, ExecReport, Workflow, CommandExec, PluginStep, JobExec) + } - def "CreateReportFromExecutionId"() { + def "CreateReportFromExecution"() { given: String uuid = UUID.randomUUID().toString() Execution e = TestDomainFactory.createExecution(uuid: uuid, status: 'succeeded', dateCompleted: new Date()) when: - def actual = provider.createReportFromExecution(e.id) + def actual = provider.saveReport(e.toSaveReportRequest()) def created = provider.get(actual.report.id) then: @@ -34,31 +37,35 @@ class GormExecReportDataProviderSpec extends Specification implements DataTest { created } - def "CreateReportFromExecutionUuid"() { - given: - String uuid = UUID.randomUUID().toString() - Execution e = TestDomainFactory.createExecution(uuid: uuid, status: 'succeeded', dateCompleted: new Date()) - + def "CreateReportFromWithEmptyRequestShouldReturnError"() { when: - def actual = provider.createReportFromExecution(e.uuid) - def created = provider.get(actual.report.id) + + provider.messageSource = Mock(MessageSource) { + getMessage(_,_) >> "Error saving report" + } + + def response = provider.saveReport(new SaveReportRequestImpl()) then: - actual.isSaved - !actual.errors - created + !response.isSaved + response.errors != null + } - def "CreateReportFromExecutionShouldThrowErrorIfExceptionDoesNotExist"() { + def "ShouldDeleteExecReportsByExecutionUuid"() { + given: + String uuid = UUID.randomUUID().toString() + Execution e = TestDomainFactory.createExecution(uuid: uuid, status: 'succeeded', dateCompleted: new Date()) + def report = provider.saveReport(e.toSaveReportRequest()) + when: - provider.createReportFromExecution(execId) + def created = provider.get(report.report.id) + provider.deleteAllByExecutionUuid(report.report.executionUuid) + def deleted = provider.get(report.report.id) then: - thrown(EntityNotFoundException) + created + deleted == null - where: - execId | _ - -1011L | 'Execution with id -1011 does not exist' - "some-uuid" | 'Execution with uuid some-uuid does not exist' } } diff --git a/rundeckapp/src/test/groovy/rundeck/services/ProjectServiceSpec.groovy b/rundeckapp/src/test/groovy/rundeck/services/ProjectServiceSpec.groovy index bb259d0ac1c..834d5e868bf 100644 --- a/rundeckapp/src/test/groovy/rundeck/services/ProjectServiceSpec.groovy +++ b/rundeckapp/src/test/groovy/rundeck/services/ProjectServiceSpec.groovy @@ -1333,7 +1333,6 @@ class ProjectServiceSpec extends Specification implements ServiceUnitTest test-job-uuid + uuid ''' /** * uses deprecated jcExecId @@ -2607,6 +2607,7 @@ class ProjectServiceSpec extends Specification implements ServiceUnitTest1970-01-01T01:00:00Z 123 test-job-uuid + uuid @@ -2650,6 +2651,7 @@ class ProjectServiceSpec extends Specification implements ServiceUnitTest