diff --git a/reports-scheduler/build.gradle b/reports-scheduler/build.gradle index 0bff8af1..00400f19 100644 --- a/reports-scheduler/build.gradle +++ b/reports-scheduler/build.gradle @@ -99,9 +99,6 @@ ext { isSnapshot = "true" == System.getProperty("build.snapshot", "true") } -group = "com.amazon.opendistroforelasticsearch" -version = "${opendistroVersion}.0" - if (isSnapshot) { version += "-SNAPSHOT" } @@ -115,6 +112,7 @@ plugins.withId('org.jetbrains.kotlin.jvm') { } allprojects { + group = "com.amazon.opendistroforelasticsearch" version = "${opendistroVersion}.0" plugins.withId('java') { @@ -127,7 +125,8 @@ dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib:${kotlin_version}" compile "org.jetbrains.kotlin:kotlin-stdlib-common:${kotlin_version}" compile "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9" - compileOnly "${group}:opendistro-job-scheduler-spi:1.10.1.0" + compile "${group}:common-utils:${version}" + compileOnly "${group}:opendistro-job-scheduler-spi:${version}" compile group: 'com.google.guava', name: 'guava', version: '15.0' testImplementation( 'org.assertj:assertj-core:3.16.1', diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/action/ReportDefinitionAction.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/action/ReportDefinitionAction.kt deleted file mode 100644 index 8db128fe..00000000 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/action/ReportDefinitionAction.kt +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * - */ - -package com.amazon.opendistroforelasticsearch.reportsscheduler.action - -import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.LOG_PREFIX -import com.amazon.opendistroforelasticsearch.reportsscheduler.index.IndexManager -import com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportDefinition -import com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportDefinitionDetails -import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.ID_FIELD -import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.REPORT_DEFINITION_LIST_FIELD -import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_TEXT_FIELD -import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.common.xcontent.ToXContent -import org.elasticsearch.common.xcontent.XContentType -import org.elasticsearch.rest.BytesRestResponse -import org.elasticsearch.rest.RestChannel -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestStatus -import java.time.Instant - -/** - * Report definitions index operation actions. - */ -internal class ReportDefinitionAction( - private val request: RestRequest, - private val client: NodeClient, - private val restChannel: RestChannel -) { - - companion object { - private val log by logger(ReportDefinitionAction::class.java) - private const val TEMP_OWNER_ID = "ownerId" // TODO get this from request - } - - /** - * Create new ReportDefinition - */ - fun create() { - log.info("$LOG_PREFIX:ReportDefinition-create") - val response = restChannel.newBuilder(XContentType.JSON, false).startObject() - var restStatus = RestStatus.OK // Default to success - val currentTime = Instant.now() - val contentParser = request.contentParser() - contentParser.nextToken() - val reportDefinition = ReportDefinition.parse(contentParser) - val reportDefinitionDetails = ReportDefinitionDetails("ignore", - currentTime, - currentTime, - TEMP_OWNER_ID, // TODO update with actual requester ID - reportDefinition - ) - val docId = IndexManager.createReportDefinition(reportDefinitionDetails) - if (docId == null) { - response.field(STATUS_TEXT_FIELD, "Report Definition Creation failed") - restStatus = RestStatus.INTERNAL_SERVER_ERROR - } else { - response.field(ID_FIELD, docId) - } - response.endObject() - restChannel.sendResponse(BytesRestResponse(restStatus, response)) - } - - /** - * Update ReportDefinition - * @param reportDefinitionId ReportDefinition id - */ - fun update(reportDefinitionId: String) { - log.info("$LOG_PREFIX:ReportDefinition-update $reportDefinitionId") - val response = restChannel.newBuilder(XContentType.JSON, false).startObject() - var restStatus = RestStatus.OK // Default to success - val currentReportDefinitionDetails = IndexManager.getReportDefinition(reportDefinitionId) - if (currentReportDefinitionDetails == null) { // TODO verify actual requester ID - restStatus = RestStatus.NOT_FOUND - response.field(STATUS_TEXT_FIELD, "Report Definition $reportDefinitionId not found") - } else { - val currentTime = Instant.now() - val contentParser = request.contentParser() - contentParser.nextToken() - val reportDefinition = ReportDefinition.parse(contentParser) - val reportDefinitionDetails = ReportDefinitionDetails(reportDefinitionId, - currentTime, - currentReportDefinitionDetails.createdTime, - currentReportDefinitionDetails.ownerId, - reportDefinition - ) - val isUpdated = IndexManager.updateReportDefinition(reportDefinitionId, reportDefinitionDetails) - if (isUpdated) { - response.field(ID_FIELD, reportDefinitionId) - } else { - response.field(STATUS_TEXT_FIELD, "Report Definition Update failed") - restStatus = RestStatus.INTERNAL_SERVER_ERROR - } - } - response.endObject() - restChannel.sendResponse(BytesRestResponse(restStatus, response)) - } - - /** - * Get ReportDefinition info - * @param reportDefinitionId ReportDefinition id - */ - fun info(reportDefinitionId: String) { - log.info("$LOG_PREFIX:ReportDefinition-info $reportDefinitionId") - val response = restChannel.newBuilder(XContentType.JSON, false) - var restStatus = RestStatus.OK // Default to success - val reportDefinitionDetails = IndexManager.getReportDefinition(reportDefinitionId) - if (reportDefinitionDetails == null) { // TODO verify actual requester ID - restStatus = RestStatus.NOT_FOUND - response.startObject() - .field(STATUS_TEXT_FIELD, "Report Definition $reportDefinitionId not found") - .endObject() - } else { - reportDefinitionDetails.toXContent(response, ToXContent.EMPTY_PARAMS, true) - } - restChannel.sendResponse(BytesRestResponse(restStatus, response)) - } - - /** - * Delete ReportDefinition - * @param reportDefinitionId ReportDefinition id - */ - fun delete(reportDefinitionId: String) { - log.info("$LOG_PREFIX:ReportDefinition-delete $reportDefinitionId") - val response = restChannel.newBuilder(XContentType.JSON, false).startObject() - var restStatus = RestStatus.OK // Default to success - val reportDefinitionDetails = IndexManager.getReportDefinition(reportDefinitionId) - if (reportDefinitionDetails == null) { // TODO verify actual requester ID - restStatus = RestStatus.NOT_FOUND - response.startObject() - .field(STATUS_TEXT_FIELD, "Report Definition $reportDefinitionId not found") - .endObject() - } else { - val isDeleted = IndexManager.deleteReportDefinition(reportDefinitionId) - if (!isDeleted) { - restStatus = RestStatus.REQUEST_TIMEOUT - response.field(STATUS_TEXT_FIELD, "Report Definition $reportDefinitionId delete failed") - } else { - response.field(STATUS_TEXT_FIELD, "Report Definition $reportDefinitionId deleted") - } - } - response.endObject() - restChannel.sendResponse(BytesRestResponse(restStatus, response)) - } - - /** - * Get all ReportDefinition for current user - */ - fun getAll(from: Int) { - log.info("$LOG_PREFIX:ReportDefinition-getAll") - val response = restChannel.newBuilder(XContentType.JSON, false).startObject() - var restStatus = RestStatus.OK // Default to success - // TODO verify actual requester ID - val reportDefinitionsList = IndexManager.getAllReportDefinitions(TEMP_OWNER_ID, from) - if (reportDefinitionsList.isEmpty()) { - restStatus = RestStatus.NOT_FOUND - response.field(STATUS_TEXT_FIELD, "No Report Definitions found") - } else { - response.startArray(REPORT_DEFINITION_LIST_FIELD) - reportDefinitionsList.forEach { it.toXContent(response, ToXContent.EMPTY_PARAMS, true) } - response.endArray() - } - response.endObject() - restChannel.sendResponse(BytesRestResponse(restStatus, response)) - } -} diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/action/ReportDefinitionActions.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/action/ReportDefinitionActions.kt new file mode 100644 index 00000000..3531cfc4 --- /dev/null +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/action/ReportDefinitionActions.kt @@ -0,0 +1,164 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.action + +import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.LOG_PREFIX +import com.amazon.opendistroforelasticsearch.reportsscheduler.index.IndexManager +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.CreateReportDefinitionRequest +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.CreateReportDefinitionResponse +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.DeleteReportDefinitionRequest +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.DeleteReportDefinitionResponse +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.GetAllReportDefinitionsRequest +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.GetAllReportDefinitionsResponse +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.GetReportDefinitionRequest +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.GetReportDefinitionsResponse +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportDefinitionDetails +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.UpdateReportDefinitionRequest +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.UpdateReportDefinitionResponse +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger +import org.elasticsearch.rest.RestStatus +import java.time.Instant + +/** + * Report definitions index operation actions. + */ +internal object ReportDefinitionActions { + private val log by logger(ReportDefinitionActions::class.java) + private const val TEMP_ROLE_ID = "roleId" // TODO get this from request + + /** + * Create new ReportDefinition + * @param request [CreateReportDefinitionRequest] object + * @return [CreateReportDefinitionResponse] + */ + fun create(request: CreateReportDefinitionRequest): CreateReportDefinitionResponse { + log.info("$LOG_PREFIX:ReportDefinition-create") + val currentTime = Instant.now() + val reportDefinitionDetails = ReportDefinitionDetails("ignore", + currentTime, + currentTime, + listOf(TEMP_ROLE_ID), // TODO update with actual requester ID + request.reportDefinition + ) + val docId = IndexManager.createReportDefinition(reportDefinitionDetails) + return if (docId == null) { + CreateReportDefinitionResponse(RestStatus.INTERNAL_SERVER_ERROR, + "Report Definition Creation failed", + null) + } else { + CreateReportDefinitionResponse(RestStatus.OK, + null, + docId) + } + } + + /** + * Update ReportDefinition + * @param request [UpdateReportDefinitionRequest] object + * @return [UpdateReportDefinitionResponse] + */ + fun update(request: UpdateReportDefinitionRequest): UpdateReportDefinitionResponse { + log.info("$LOG_PREFIX:ReportDefinition-update ${request.reportDefinitionId}") + val currentReportDefinitionDetails = IndexManager.getReportDefinition(request.reportDefinitionId) + return if (currentReportDefinitionDetails == null) { // TODO verify actual requester ID + UpdateReportDefinitionResponse(RestStatus.NOT_FOUND, + "Report Definition ${request.reportDefinitionId} not found", + null) + } else { + val currentTime = Instant.now() + val reportDefinitionDetails = ReportDefinitionDetails(request.reportDefinitionId, + currentTime, + currentReportDefinitionDetails.createdTime, + currentReportDefinitionDetails.roles, + request.reportDefinition + ) + val isUpdated = IndexManager.updateReportDefinition(request.reportDefinitionId, reportDefinitionDetails) + if (isUpdated) { + UpdateReportDefinitionResponse(RestStatus.OK, + null, + request.reportDefinitionId) + } else { + UpdateReportDefinitionResponse(RestStatus.INTERNAL_SERVER_ERROR, + "Report Definition Update failed", + null) + } + } + } + + /** + * Get ReportDefinition info + * @param request [GetReportDefinitionRequest] object + * @return [GetReportDefinitionsResponse] + */ + fun info(request: GetReportDefinitionRequest): GetReportDefinitionsResponse { + log.info("$LOG_PREFIX:ReportDefinition-info ${request.reportDefinitionId}") + val reportDefinitionDetails = IndexManager.getReportDefinition(request.reportDefinitionId) + return if (reportDefinitionDetails == null) { // TODO verify actual requester ID + GetReportDefinitionsResponse(RestStatus.NOT_FOUND, + "Report Definition ${request.reportDefinitionId} not found", + null) + } else { + GetReportDefinitionsResponse(RestStatus.OK, + null, + reportDefinitionDetails) + } + } + + /** + * Delete ReportDefinition + * @param request [DeleteReportDefinitionRequest] object + * @return [DeleteReportDefinitionResponse] + */ + fun delete(request: DeleteReportDefinitionRequest): DeleteReportDefinitionResponse { + log.info("$LOG_PREFIX:ReportDefinition-delete ${request.reportDefinitionId}") + val reportDefinitionDetails = IndexManager.getReportDefinition(request.reportDefinitionId) + return if (reportDefinitionDetails == null) { // TODO verify actual requester ID + DeleteReportDefinitionResponse(RestStatus.NOT_FOUND, + "Report Definition ${request.reportDefinitionId} not found", + null) + } else { + val isDeleted = IndexManager.deleteReportDefinition(request.reportDefinitionId) + if (!isDeleted) { + DeleteReportDefinitionResponse(RestStatus.REQUEST_TIMEOUT, + "Report Definition ${request.reportDefinitionId} delete failed", + null) + } else { + DeleteReportDefinitionResponse(RestStatus.OK, + null, + request.reportDefinitionId) + } + } + } + + /** + * Get all ReportDefinitions + * @param request [GetAllReportDefinitionsRequest] object + * @return [GetAllReportDefinitionsResponse] + */ + fun getAll(request: GetAllReportDefinitionsRequest): GetAllReportDefinitionsResponse { + log.info("$LOG_PREFIX:ReportDefinition-getAll ${request.fromIndex}") + // TODO verify actual requester ID + val reportDefinitionsList = IndexManager.getAllReportDefinitions(listOf(TEMP_ROLE_ID), request.fromIndex) + return if (reportDefinitionsList.isEmpty()) { + GetAllReportDefinitionsResponse(RestStatus.NOT_FOUND, + "No Report Definitions found", + null) + } else { + GetAllReportDefinitionsResponse(RestStatus.OK, null, reportDefinitionsList) + } + } +} diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/action/ReportInstanceAction.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/action/ReportInstanceAction.kt deleted file mode 100644 index 9bc16349..00000000 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/action/ReportInstanceAction.kt +++ /dev/null @@ -1,295 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * - */ - -package com.amazon.opendistroforelasticsearch.reportsscheduler.action - -import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.LOG_PREFIX -import com.amazon.opendistroforelasticsearch.reportsscheduler.index.IndexManager -import com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportDefinitionDetails -import com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportInstance -import com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportInstance.State -import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.BEGIN_TIME_FIELD -import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.END_TIME_FIELD -import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.ID_FIELD -import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.IN_CONTEXT_DOWNLOAD_URL_FIELD -import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.REPORT_DEFINITION_DETAILS_FIELD -import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.REPORT_INSTANCE_FIELD -import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.REPORT_INSTANCE_LIST_FIELD -import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.RETRY_AFTER_FIELD -import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_FIELD -import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_TEXT_FIELD -import com.amazon.opendistroforelasticsearch.reportsscheduler.settings.PluginSettings -import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.common.xcontent.ToXContent -import org.elasticsearch.common.xcontent.XContentParser -import org.elasticsearch.common.xcontent.XContentParserUtils -import org.elasticsearch.common.xcontent.XContentType -import org.elasticsearch.rest.BytesRestResponse -import org.elasticsearch.rest.RestChannel -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestStatus -import java.time.Instant -import kotlin.random.Random - -/** - * Report instances index operation actions. - */ -internal class ReportInstanceAction( - private val request: RestRequest, - private val client: NodeClient, - private val restChannel: RestChannel -) { - - companion object { - private val log by logger(ReportInstanceAction::class.java) - private const val TEMP_OWNER_ID = "ownerId" - } - - /** - * Create a new on-demand report from in-context menu. - */ - fun createOnDemand() { - log.info("$LOG_PREFIX:ReportInstance-createOnDemand") - val response = restChannel.newBuilder(XContentType.JSON, false) - var restStatus = RestStatus.OK // Default to success - val currentTime = Instant.now() - val parser = request.contentParser() - parser.nextToken() - var beginTime: Instant? = null - var endTime: Instant? = null - var currentState: State = State.Success - var currentStateDescription: String? = null - var inContextDownloadUrlPath: String? = null - var reportDefinitionDetails: ReportDefinitionDetails? = null - XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation) - while (XContentParser.Token.END_OBJECT != parser.nextToken()) { - val fieldName = parser.currentName() - parser.nextToken() - when (fieldName) { - IN_CONTEXT_DOWNLOAD_URL_FIELD -> inContextDownloadUrlPath = parser.text() - BEGIN_TIME_FIELD -> beginTime = Instant.ofEpochMilli(parser.longValue()) - END_TIME_FIELD -> endTime = Instant.ofEpochMilli(parser.longValue()) - STATUS_FIELD -> currentState = State.valueOf(parser.text()) - STATUS_TEXT_FIELD -> currentStateDescription = parser.text() - REPORT_DEFINITION_DETAILS_FIELD -> reportDefinitionDetails = ReportDefinitionDetails.parse(parser, TEMP_OWNER_ID) - else -> { - parser.skipChildren() - log.info("$LOG_PREFIX:createOnDemand Skipping Unknown field $fieldName") - } - } - } - beginTime ?: throw IllegalArgumentException("$BEGIN_TIME_FIELD field absent") - endTime ?: throw IllegalArgumentException("$END_TIME_FIELD field absent") - val reportInstance = ReportInstance("ignore", - currentTime, - currentTime, - beginTime, - endTime, - TEMP_OWNER_ID, // TODO validate userId actual requester ID - reportDefinitionDetails, - currentState, - currentStateDescription, - inContextDownloadUrlPath) - val docId = IndexManager.createReportInstance(reportInstance) - if (docId == null) { - response.startObject() - .field(STATUS_TEXT_FIELD, "Report Instance Creation failed") - .endObject() - restStatus = RestStatus.INTERNAL_SERVER_ERROR - } else { - val reportInstanceCopy = reportInstance.copy(id = docId) - reportInstanceCopy.toXContent(response, ToXContent.EMPTY_PARAMS, true) - } - restChannel.sendResponse(BytesRestResponse(restStatus, response)) - } - - /** - * Create on-demand report from report definition - * @param reportDefinitionId ReportDefinition id - */ - fun createOnDemandFromDefinition(reportDefinitionId: String) { - log.info("$LOG_PREFIX:ReportInstance-createOnDemandFromDefinition") - val response = restChannel.newBuilder(XContentType.JSON, false) - var restStatus = RestStatus.OK // Default to success - val currentTime = Instant.now() - val reportDefinitionDetails = IndexManager.getReportDefinition(reportDefinitionId) - if (reportDefinitionDetails == null) { // TODO verify actual requester ID - restStatus = RestStatus.NOT_FOUND - response.startObject() - .field(STATUS_TEXT_FIELD, "Report Definition $reportDefinitionId not found") - .endObject() - } else { - val beginTime: Instant = currentTime.minus(reportDefinitionDetails.reportDefinition.format.duration) - val endTime: Instant = currentTime - val currentState: State = State.Executing - val reportInstance = ReportInstance("ignore", - currentTime, - currentTime, - beginTime, - endTime, - reportDefinitionDetails.ownerId, - reportDefinitionDetails, - currentState) - val docId = IndexManager.createReportInstance(reportInstance) - if (docId == null) { - response.startObject() - .field(STATUS_TEXT_FIELD, "Report Instance Creation failed") - .endObject() - restStatus = RestStatus.INTERNAL_SERVER_ERROR - } else { - val reportInstanceCopy = reportInstance.copy(id = docId) - reportInstanceCopy.toXContent(response, ToXContent.EMPTY_PARAMS, true) - } - } - restChannel.sendResponse(BytesRestResponse(restStatus, response)) - } - - /** - * Update status of existing report instance - * @param reportInstanceId ReportInstance id - */ - fun update(reportInstanceId: String) { - log.info("$LOG_PREFIX:ReportInstance-update $reportInstanceId") - val response = restChannel.newBuilder(XContentType.JSON, false).startObject() - var restStatus = RestStatus.OK // Default to success - val currentReportInstance = IndexManager.getReportInstance(reportInstanceId) - if (currentReportInstance == null) { // TODO verify actual requester ID - restStatus = RestStatus.NOT_FOUND - response.field(STATUS_TEXT_FIELD, "Report Instance $reportInstanceId not found") - } else { - val currentTime = Instant.now() - val parser = request.contentParser() - parser.nextToken() - var state: State? = null - var stateDescription: String? = null - XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation) - while (XContentParser.Token.END_OBJECT != parser.nextToken()) { - val fieldName = parser.currentName() - parser.nextToken() - when (fieldName) { - STATUS_FIELD -> state = State.valueOf(parser.text()) - STATUS_TEXT_FIELD -> stateDescription = parser.text() - else -> { - parser.skipChildren() - log.info("$LOG_PREFIX:updateReportInstanceStatus Skipping Unknown field $fieldName") - } - } - } - if (state == null || state == State.Scheduled) { // Don't allow changing status to Scheduled - throw IllegalArgumentException("$STATUS_FIELD field not valid") - } - val updatedReportInstance = currentReportInstance.copy(updatedTime = currentTime, - currentState = state, - currentStateDescription = stateDescription) - val isUpdated = IndexManager.updateReportInstance(updatedReportInstance) - if (isUpdated) { - response.field(ID_FIELD, reportInstanceId) - } else { - response.field(STATUS_TEXT_FIELD, "Report Instance state update failed") - restStatus = RestStatus.INTERNAL_SERVER_ERROR - } - } - response.endObject() - restChannel.sendResponse(BytesRestResponse(restStatus, response)) - } - - /** - * Get information of existing report instance - * @param reportInstanceId ReportInstance id - */ - fun info(reportInstanceId: String) { - log.info("$LOG_PREFIX:ReportInstance-info $reportInstanceId") - val response = restChannel.newBuilder(XContentType.JSON, false) - var restStatus = RestStatus.OK // Default to success - val reportInstance = IndexManager.getReportInstance(reportInstanceId) - if (reportInstance == null) { // TODO verify actual requester ID - restStatus = RestStatus.NOT_FOUND - response.startObject() - .field(STATUS_TEXT_FIELD, "Report Instance $reportInstanceId not found") - .endObject() - } else { - reportInstance.toXContent(response, ToXContent.EMPTY_PARAMS, true) - } - restChannel.sendResponse(BytesRestResponse(restStatus, response)) - } - - /** - * Get information of all report instance for given user - */ - fun getAll(from: Int) { - log.info("$LOG_PREFIX:ReportInstance-getAll") - val response = restChannel.newBuilder(XContentType.JSON, false).startObject() - var restStatus = RestStatus.OK // Default to success - // TODO verify actual requester ID - val reportInstanceList = IndexManager.getAllReportInstances(TEMP_OWNER_ID, from) - if (reportInstanceList.isEmpty()) { - restStatus = RestStatus.NOT_FOUND - response.field(STATUS_TEXT_FIELD, "No Report Definitions found") - } else { - response.startArray(REPORT_INSTANCE_LIST_FIELD) - reportInstanceList.forEach { it.toXContent(response, ToXContent.EMPTY_PARAMS, true) } - response.endArray() - } - response.endObject() - restChannel.sendResponse(BytesRestResponse(restStatus, response)) - } - - fun poll() { - log.info("$LOG_PREFIX:ReportInstance-poll") - val response = restChannel.newBuilder(XContentType.JSON, false).startObject() - val restStatus: RestStatus - val currentTime = Instant.now() - // TODO verify actual requester ID to be kibana background task - val reportInstances = IndexManager.getPendingReportInstances() - if (reportInstances.isEmpty()) { - restStatus = RestStatus.MULTI_STATUS - response.field(RETRY_AFTER_FIELD, getRetryAfterTime()) - response.field(STATUS_TEXT_FIELD, "No Scheduled Report Instance found") - } else { - // Shuffle list so that when multiple requests are made, chances of lock conflict is less - reportInstances.shuffle() - /* - If the shuffling is perfect random then there is high probability that first item locking is successful - even when there are many parallel requests. i.e. say there are x jobs and y parallel requests. - then x out of y jobs can lock first item and rest cannot lock any jobs. However shuffle may not be perfect - hence checking first few jobs for locking. - */ - val lockedJob = reportInstances.subList(0, PluginSettings.maxLockRetries).find { - val updatedInstance = it.copy(reportInstance = it.reportInstance.copy( - updatedTime = currentTime, - currentState = State.Executing - )) - IndexManager.updateReportInstanceDoc(updatedInstance) - } - if (lockedJob == null) { - restStatus = RestStatus.MULTI_STATUS - response.field(RETRY_AFTER_FIELD, PluginSettings.minPollingDurationSeconds) - response.field(STATUS_TEXT_FIELD, "Could not get lock. try after sometime") - } else { - restStatus = RestStatus.OK - response.field(REPORT_INSTANCE_FIELD) - lockedJob.reportInstance.toXContent(response, ToXContent.EMPTY_PARAMS, true) - } - } - response.endObject() - restChannel.sendResponse(BytesRestResponse(restStatus, response)) - } - - private fun getRetryAfterTime(): Int { - return Random.nextInt(PluginSettings.minPollingDurationSeconds, PluginSettings.maxPollingDurationSeconds) - } -} diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/action/ReportInstanceActions.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/action/ReportInstanceActions.kt new file mode 100644 index 00000000..12b6a27b --- /dev/null +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/action/ReportInstanceActions.kt @@ -0,0 +1,228 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.action + +import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.LOG_PREFIX +import com.amazon.opendistroforelasticsearch.reportsscheduler.index.IndexManager +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.GetAllReportInstancesRequest +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.GetAllReportInstancesResponse +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.GetReportInstanceRequest +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.GetReportInstanceResponse +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.InContextReportCreateRequest +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.InContextReportCreateResponse +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.OnDemandReportCreateRequest +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.OnDemandReportCreateResponse +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.PollReportInstanceResponse +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportInstance +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportInstance.Status +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.UpdateReportInstanceStatusRequest +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.UpdateReportInstanceStatusResponse +import com.amazon.opendistroforelasticsearch.reportsscheduler.settings.PluginSettings +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger +import org.elasticsearch.rest.RestStatus +import java.time.Instant +import kotlin.random.Random + +/** + * Report instances index operation actions. + */ +internal object ReportInstanceActions { + private val log by logger(ReportInstanceActions::class.java) + private const val TEMP_ROLE_ID = "roleId" // TODO get this from request + + /** + * Create a new on-demand report from in-context menu. + * @param request [InContextReportCreateRequest] object + * @return [InContextReportCreateResponse] + */ + fun createOnDemand(request: InContextReportCreateRequest): InContextReportCreateResponse { + log.info("$LOG_PREFIX:ReportInstance-createOnDemand") + val currentTime = Instant.now() + val reportInstance = ReportInstance("ignore", + currentTime, + currentTime, + request.beginTime, + request.endTime, + listOf(TEMP_ROLE_ID), + request.reportDefinitionDetails, + request.status, + request.statusText, + request.inContextDownloadUrlPath) + val docId = IndexManager.createReportInstance(reportInstance) + return if (docId == null) { + InContextReportCreateResponse(RestStatus.INTERNAL_SERVER_ERROR, + "Report Instance Creation failed", + null) + } else { + val reportInstanceCopy = reportInstance.copy(id = docId) + InContextReportCreateResponse(RestStatus.OK, + null, + reportInstanceCopy) + } + } + + /** + * Create on-demand report from report definition + * @param request [OnDemandReportCreateRequest] object + * @return [OnDemandReportCreateResponse] + */ + fun createOnDemandFromDefinition(request: OnDemandReportCreateRequest): OnDemandReportCreateResponse { + log.info("$LOG_PREFIX:ReportInstance-createOnDemandFromDefinition ${request.reportDefinitionId}") + val currentTime = Instant.now() + val reportDefinitionDetails = IndexManager.getReportDefinition(request.reportDefinitionId) + return if (reportDefinitionDetails == null) { // TODO verify actual requester ID + OnDemandReportCreateResponse(RestStatus.INTERNAL_SERVER_ERROR, + "Report Definition ${request.reportDefinitionId} not found", + null) + } else { + val beginTime: Instant = currentTime.minus(reportDefinitionDetails.reportDefinition.format.duration) + val endTime: Instant = currentTime + val currentStatus: Status = Status.Executing + val reportInstance = ReportInstance("ignore", + currentTime, + currentTime, + beginTime, + endTime, + reportDefinitionDetails.roles, + reportDefinitionDetails, + currentStatus) + val docId = IndexManager.createReportInstance(reportInstance) + if (docId == null) { + OnDemandReportCreateResponse(RestStatus.INTERNAL_SERVER_ERROR, + "Report Instance Creation failed", + null) + } else { + val reportInstanceCopy = reportInstance.copy(id = docId) + OnDemandReportCreateResponse(RestStatus.OK, + null, + reportInstanceCopy) + } + } + } + + /** + * Update status of existing report instance + * @param request [UpdateReportInstanceStatusRequest] object + * @return [UpdateReportInstanceStatusResponse] + */ + fun update(request: UpdateReportInstanceStatusRequest): UpdateReportInstanceStatusResponse { + log.info("$LOG_PREFIX:ReportInstance-update ${request.reportInstanceId}") + val currentReportInstance = IndexManager.getReportInstance(request.reportInstanceId) + return if (currentReportInstance == null) { // TODO verify actual requester ID + UpdateReportInstanceStatusResponse(RestStatus.NOT_FOUND, + "Report Instance not found", + request.reportInstanceId) + } else if (request.status == Status.Scheduled) { // Don't allow changing status to Scheduled + UpdateReportInstanceStatusResponse(RestStatus.NOT_FOUND, + "Status cannot be updated to ${Status.Scheduled}", + request.reportInstanceId) + } else { + val currentTime = Instant.now() + val updatedReportInstance = currentReportInstance.copy(updatedTime = currentTime, + status = request.status, + statusText = request.statusText) + val isUpdated = IndexManager.updateReportInstance(updatedReportInstance) + if (isUpdated) { + UpdateReportInstanceStatusResponse(RestStatus.OK, + null, + request.reportInstanceId) + } else { + UpdateReportInstanceStatusResponse(RestStatus.INTERNAL_SERVER_ERROR, + "Report Instance state update failed", + request.reportInstanceId) + } + } + } + + /** + * Get information of existing report instance + * @param request [GetReportInstanceRequest] object + * @return [GetReportInstanceResponse] + */ + fun info(request: GetReportInstanceRequest): GetReportInstanceResponse { + log.info("$LOG_PREFIX:ReportInstance-info ${request.reportInstanceId}") + val reportInstance = IndexManager.getReportInstance(request.reportInstanceId) + return if (reportInstance == null) { // TODO verify actual requester ID + GetReportInstanceResponse(RestStatus.NOT_FOUND, + "Report Instance ${request.reportInstanceId} not found", + null) + } else { + GetReportInstanceResponse(RestStatus.OK, + null, + reportInstance) + } + } + + /** + * Get information of all report instances + * @param request [GetAllReportInstancesRequest] object + * @return [GetAllReportInstancesResponse] + */ + fun getAll(request: GetAllReportInstancesRequest): GetAllReportInstancesResponse { + log.info("$LOG_PREFIX:ReportInstance-getAll ${request.fromIndex}") + // TODO verify actual requester ID + val reportInstanceList = IndexManager.getAllReportInstances(listOf(TEMP_ROLE_ID), request.fromIndex) + return if (reportInstanceList.isEmpty()) { + GetAllReportInstancesResponse(RestStatus.NOT_FOUND, + "No Report Instances found", + null) + } else { + GetAllReportInstancesResponse(RestStatus.OK, null, reportInstanceList) + } + } + + fun poll(): PollReportInstanceResponse { + log.info("$LOG_PREFIX:ReportInstance-poll") + val currentTime = Instant.now() + // TODO verify actual requester ID to be kibana background task + val reportInstances = IndexManager.getPendingReportInstances() + return if (reportInstances.isEmpty()) { + PollReportInstanceResponse(RestStatus.MULTI_STATUS, + "No Scheduled Report Instance found", + getRetryAfterTime(), + null) + } else { + // Shuffle list so that when multiple requests are made, chances of lock conflict is less + reportInstances.shuffle() + /* + If the shuffling is perfect random then there is high probability that first item locking is successful + even when there are many parallel requests. i.e. say there are x jobs and y parallel requests. + then x out of y jobs can lock first item and rest cannot lock any jobs. However shuffle may not be perfect + hence checking first few jobs for locking. + */ + val lockedJob = reportInstances.subList(0, PluginSettings.maxLockRetries).find { + val updatedInstance = it.copy(reportInstance = it.reportInstance.copy( + updatedTime = currentTime, + status = Status.Executing + )) + IndexManager.updateReportInstanceDoc(updatedInstance) + } + if (lockedJob == null) { + PollReportInstanceResponse(RestStatus.MULTI_STATUS, + "Could not get lock. try after sometime", + PluginSettings.minPollingDurationSeconds, + null) + } else { + PollReportInstanceResponse(RestStatus.OK, null, 0, lockedJob.reportInstance) + } + } + } + + private fun getRetryAfterTime(): Int { + return Random.nextInt(PluginSettings.minPollingDurationSeconds, PluginSettings.maxPollingDurationSeconds) + } +} diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/EmptyReportDefinitionsIndex.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/EmptyReportDefinitionsIndex.kt index f63c9769..d55fd4c0 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/EmptyReportDefinitionsIndex.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/EmptyReportDefinitionsIndex.kt @@ -22,20 +22,6 @@ import com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportDefini * Empty implementation of the ReportDefinitionsIndex which responds with IllegalStateException all operations. */ internal object EmptyReportDefinitionsIndex : IReportDefinitionsIndex { - /** - * {@inheritDoc} - */ - override fun createIndex() { - notInitializedError() - } - - /** - * {@inheritDoc} - */ - override fun isIndexExists(): Boolean { - notInitializedError() - } - /** * {@inheritDoc} */ @@ -53,7 +39,7 @@ internal object EmptyReportDefinitionsIndex : IReportDefinitionsIndex { /** * {@inheritDoc} */ - override fun getAllReportDefinitions(ownerId: String, from: Int): List { + override fun getAllReportDefinitions(roles: List, from: Int): List { notInitializedError() } diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/EmptyReportInstancesIndex.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/EmptyReportInstancesIndex.kt index 5deb4c07..412d4989 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/EmptyReportInstancesIndex.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/EmptyReportInstancesIndex.kt @@ -23,20 +23,6 @@ import com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportInstan * Empty implementation of the ReportInstancesIndex which responds with IllegalStateException all operations. */ internal object EmptyReportInstancesIndex : IReportInstancesIndex { - /** - * {@inheritDoc} - */ - override fun createIndex() { - notInitializedError() - } - - /** - * {@inheritDoc} - */ - override fun isIndexExists(): Boolean { - notInitializedError() - } - /** * {@inheritDoc} */ @@ -54,7 +40,7 @@ internal object EmptyReportInstancesIndex : IReportInstancesIndex { /** * {@inheritDoc} */ - override fun getAllReportInstances(ownerId: String, from: Int): List { + override fun getAllReportInstances(roles: List, from: Int): List { notInitializedError() } diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/IReportDefinitionsIndex.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/IReportDefinitionsIndex.kt index 90e20f65..95b282ba 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/IReportDefinitionsIndex.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/IReportDefinitionsIndex.kt @@ -22,17 +22,6 @@ import com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportDefini * Interface for Report definition index operations. */ internal interface IReportDefinitionsIndex { - /** - * Create index using the mapping and settings defined in resource - */ - fun createIndex() - - /** - * Check if the index is created and available. - * @return true if index is available, false otherwise - */ - fun isIndexExists(): Boolean - /** * create a new doc for reportDefinitionDetails * @param reportDefinitionDetails the Report definition details @@ -49,12 +38,12 @@ internal interface IReportDefinitionsIndex { fun getReportDefinition(id: String): ReportDefinitionDetails? /** - * Query index for report definition for given ownerId - * @param ownerId the owner ID + * Query index for report definition for given roles + * @param roles the list of roles to search reports for. * @param from the paginated start index * @return list of Report definition details */ - fun getAllReportDefinitions(ownerId: String, from: Int = 0): List + fun getAllReportDefinitions(roles: List, from: Int = 0): List /** * update Report definition details for given id diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/IReportInstancesIndex.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/IReportInstancesIndex.kt index decfba99..dfaf7606 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/IReportInstancesIndex.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/IReportInstancesIndex.kt @@ -23,17 +23,6 @@ import com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportInstan * Interface for Report instance index operations. */ internal interface IReportInstancesIndex { - /** - * Create index using the mapping and settings defined in resource - */ - fun createIndex() - - /** - * Check if the index is created and available. - * @return true if index is available, false otherwise - */ - fun isIndexExists(): Boolean - /** * create a new doc for reportInstance * @param reportInstance the report instance @@ -50,12 +39,12 @@ internal interface IReportInstancesIndex { fun getReportInstance(id: String): ReportInstance? /** - * Query index for report instance for given ownerId - * @param ownerId the owner ID + * Query index for report instance for given roles + * @param roles the list of roles to search reports for. * @param from the paginated start index * @return list of Report instance details */ - fun getAllReportInstances(ownerId: String, from: Int = 0): List + fun getAllReportInstances(roles: List, from: Int = 0): List /** * update Report instance details for given id diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/IndexManager.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/IndexManager.kt index 900702c3..8a874c04 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/IndexManager.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/IndexManager.kt @@ -16,141 +16,39 @@ package com.amazon.opendistroforelasticsearch.reportsscheduler.index -import com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportDefinitionDetails -import com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportInstance -import com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportInstanceDoc import org.elasticsearch.client.Client import org.elasticsearch.cluster.service.ClusterService /** * The object class which provides abstraction over all index operations */ -@Suppress("TooManyFunctions") -internal object IndexManager { - private var reportInstancesIndex: IReportInstancesIndex = EmptyReportInstancesIndex - private var reportDefinitionsIndex: IReportDefinitionsIndex = EmptyReportDefinitionsIndex +internal object IndexManager : IReportInstancesIndex by IndexContainer.reportInstancesIndex, + IReportDefinitionsIndex by IndexContainer.reportDefinitionsIndex { /** - * Initialize the class - * @param client The ES client - * @param clusterService The ES cluster service - */ - fun initialize(client: Client, clusterService: ClusterService) { - this.reportInstancesIndex = ReportInstancesIndex(client, clusterService) - this.reportDefinitionsIndex = ReportDefinitionsIndex(client, clusterService) - } - - /** - * create a new doc for reportDefinitionDetails - * @param reportDefinitionDetails the Report definition details - * @return ReportDefinition.id if successful, null otherwise - * @throws java.util.concurrent.ExecutionException with a cause - */ - fun createReportDefinition(reportDefinitionDetails: ReportDefinitionDetails): String? { - reportDefinitionsIndex.createIndex() - return reportDefinitionsIndex.createReportDefinition(reportDefinitionDetails) - } - - /** - * Query index for report definition ID - * @param id the id for the document - * @return Report definition details on success, null otherwise + * The object class which contains all index operations */ - fun getReportDefinition(id: String): ReportDefinitionDetails? { - reportDefinitionsIndex.createIndex() - return reportDefinitionsIndex.getReportDefinition(id) - } - - /** - * Query index for report definition for given ownerId - * @param ownerId the owner ID - * @param from the paginated start index - * @return list of Report definition details - */ - fun getAllReportDefinitions(ownerId: String, from: Int = 0): List { - reportDefinitionsIndex.createIndex() - return reportDefinitionsIndex.getAllReportDefinitions(ownerId, from) - } - - /** - * update Report definition details for given id - * @param id the id for the document - * @param reportDefinitionDetails the Report definition details data - * @return true if successful, false otherwise - */ - fun updateReportDefinition(id: String, reportDefinitionDetails: ReportDefinitionDetails): Boolean { - reportDefinitionsIndex.createIndex() - return reportDefinitionsIndex.updateReportDefinition(id, reportDefinitionDetails) - } + private object IndexContainer { + var reportInstancesIndex: IReportInstancesIndex = EmptyReportInstancesIndex + var reportDefinitionsIndex: IReportDefinitionsIndex = EmptyReportDefinitionsIndex - /** - * delete Report definition details for given id - * @param id the id for the document - * @return true if successful, false otherwise - */ - fun deleteReportDefinition(id: String): Boolean { - reportDefinitionsIndex.createIndex() - return reportDefinitionsIndex.deleteReportDefinition(id) + /** + * Initialize the class + * @param client The ES client + * @param clusterService The ES cluster service + */ + fun initialize(client: Client, clusterService: ClusterService) { + reportInstancesIndex = ReportInstancesIndex(client, clusterService) + reportDefinitionsIndex = ReportDefinitionsIndex(client, clusterService) + } } /** - * create a new doc for reportInstance - * @param reportInstance the report instance - * @return ReportInstance.id if successful, null otherwise - * @throws java.util.concurrent.ExecutionException with a cause - */ - fun createReportInstance(reportInstance: ReportInstance): String? { - reportInstancesIndex.createIndex() - return reportInstancesIndex.createReportInstance(reportInstance) - } - - /** - * Query index for report instance ID - * @param id the id for the document - * @return Report instance details on success, null otherwise - */ - fun getReportInstance(id: String): ReportInstance? { - reportInstancesIndex.createIndex() - return reportInstancesIndex.getReportInstance(id) - } - - /** - * Query index for report instance for given ownerId - * @param ownerId the owner ID - * @param from the paginated start index - * @return list of Report instance details - */ - fun getAllReportInstances(ownerId: String, from: Int = 0): List { - reportInstancesIndex.createIndex() - return reportInstancesIndex.getAllReportInstances(ownerId, from) - } - - /** - * update Report instance details for given id - * @param reportInstance the Report instance details data - * @return true if successful, false otherwise - */ - fun updateReportInstance(reportInstance: ReportInstance): Boolean { - reportInstancesIndex.createIndex() - return reportInstancesIndex.updateReportInstance(reportInstance) - } - - /** - * update Report instance details for given id - * @param reportInstanceDoc the Report instance details doc data - * @return true if successful, false otherwise - */ - fun updateReportInstanceDoc(reportInstanceDoc: ReportInstanceDoc): Boolean { - reportInstancesIndex.createIndex() - return reportInstancesIndex.updateReportInstanceDoc(reportInstanceDoc) - } - - /** - * Get pending report instances - * @return ReportInstanceDoc list + * Initialize the class + * @param client The ES client + * @param clusterService The ES cluster service */ - fun getPendingReportInstances(): MutableList { - reportInstancesIndex.createIndex() - return reportInstancesIndex.getPendingReportInstances() + fun initialize(client: Client, clusterService: ClusterService) { + IndexContainer.initialize(client, clusterService) } } diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/ReportDefinitionsIndex.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/ReportDefinitionsIndex.kt index 3019cff3..64dae981 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/ReportDefinitionsIndex.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/ReportDefinitionsIndex.kt @@ -18,7 +18,7 @@ package com.amazon.opendistroforelasticsearch.reportsscheduler.index import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.LOG_PREFIX import com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportDefinitionDetails -import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.OWNER_ID_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.ROLE_LIST_FIELD import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.UPDATED_TIME_FIELD import com.amazon.opendistroforelasticsearch.reportsscheduler.settings.PluginSettings import com.amazon.opendistroforelasticsearch.reportsscheduler.util.SecureIndexClient @@ -61,10 +61,10 @@ internal class ReportDefinitionsIndex(client: Client, private val clusterService } /** - * {@inheritDoc} + * Create index using the mapping and settings defined in resource */ @Suppress("TooGenericExceptionCaught") - override fun createIndex() { + private fun createIndex() { if (!isIndexExists()) { val classLoader = ReportDefinitionsIndex::class.java.classLoader val indexMappingSource = classLoader.getResource(REPORT_DEFINITIONS_MAPPING_FILE_NAME)?.readText()!! @@ -89,9 +89,10 @@ internal class ReportDefinitionsIndex(client: Client, private val clusterService } /** - * {@inheritDoc} + * Check if the index is created and available. + * @return true if index is available, false otherwise */ - override fun isIndexExists(): Boolean { + private fun isIndexExists(): Boolean { val clusterState = clusterService.state() return clusterState.routingTable.hasIndex(REPORT_DEFINITIONS_INDEX_NAME) } @@ -100,6 +101,7 @@ internal class ReportDefinitionsIndex(client: Client, private val clusterService * {@inheritDoc} */ override fun createReportDefinition(reportDefinitionDetails: ReportDefinitionDetails): String? { + createIndex() val indexRequest = IndexRequest(REPORT_DEFINITIONS_INDEX_NAME) .source(reportDefinitionDetails.toXContent(false)) .create(true) @@ -117,6 +119,7 @@ internal class ReportDefinitionsIndex(client: Client, private val clusterService * {@inheritDoc} */ override fun getReportDefinition(id: String): ReportDefinitionDetails? { + createIndex() val getRequest = GetRequest(REPORT_DEFINITIONS_INDEX_NAME).id(id) val actionFuture = client.get(getRequest) val response = actionFuture.actionGet(PluginSettings.operationTimeoutMs) @@ -135,8 +138,9 @@ internal class ReportDefinitionsIndex(client: Client, private val clusterService /** * {@inheritDoc} */ - override fun getAllReportDefinitions(ownerId: String, from: Int): List { - val query = QueryBuilders.matchQuery(OWNER_ID_FIELD, ownerId) + override fun getAllReportDefinitions(roles: List, from: Int): List { + createIndex() + val query = QueryBuilders.termsQuery(ROLE_LIST_FIELD, roles) val sourceBuilder = SearchSourceBuilder() .timeout(TimeValue(PluginSettings.operationTimeoutMs, TimeUnit.MILLISECONDS)) .sort(UPDATED_TIME_FIELD) @@ -163,6 +167,7 @@ internal class ReportDefinitionsIndex(client: Client, private val clusterService * {@inheritDoc} */ override fun updateReportDefinition(id: String, reportDefinitionDetails: ReportDefinitionDetails): Boolean { + createIndex() val updateRequest = UpdateRequest() .index(REPORT_DEFINITIONS_INDEX_NAME) .id(id) @@ -180,6 +185,7 @@ internal class ReportDefinitionsIndex(client: Client, private val clusterService * {@inheritDoc} */ override fun deleteReportDefinition(id: String): Boolean { + createIndex() val deleteRequest = DeleteRequest() .index(REPORT_DEFINITIONS_INDEX_NAME) .id(id) diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/ReportInstancesIndex.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/ReportInstancesIndex.kt index d61e0a11..1b037117 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/ReportInstancesIndex.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/ReportInstancesIndex.kt @@ -18,11 +18,11 @@ package com.amazon.opendistroforelasticsearch.reportsscheduler.index import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.LOG_PREFIX import com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportInstance -import com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportInstance.State +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportInstance.Status import com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportInstanceDoc +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.ROLE_LIST_FIELD import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_FIELD import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.UPDATED_TIME_FIELD -import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.USER_ID_FIELD import com.amazon.opendistroforelasticsearch.reportsscheduler.settings.PluginSettings import com.amazon.opendistroforelasticsearch.reportsscheduler.util.SecureIndexClient import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger @@ -65,10 +65,10 @@ internal class ReportInstancesIndex(client: Client, private val clusterService: } /** - * {@inheritDoc} + * Create index using the mapping and settings defined in resource */ @Suppress("TooGenericExceptionCaught") - override fun createIndex() { + private fun createIndex() { if (!isIndexExists()) { val classLoader = ReportInstancesIndex::class.java.classLoader val indexMappingSource = classLoader.getResource(REPORT_INSTANCES_MAPPING_FILE_NAME)?.readText()!! @@ -93,9 +93,10 @@ internal class ReportInstancesIndex(client: Client, private val clusterService: } /** - * {@inheritDoc} + * Check if the index is created and available. + * @return true if index is available, false otherwise */ - override fun isIndexExists(): Boolean { + private fun isIndexExists(): Boolean { val clusterState = clusterService.state() return clusterState.routingTable.hasIndex(REPORT_INSTANCES_INDEX_NAME) } @@ -104,6 +105,7 @@ internal class ReportInstancesIndex(client: Client, private val clusterService: * {@inheritDoc} */ override fun createReportInstance(reportInstance: ReportInstance): String? { + createIndex() val indexRequest = IndexRequest(REPORT_INSTANCES_INDEX_NAME) .source(reportInstance.toXContent(false)) .create(true) @@ -121,6 +123,7 @@ internal class ReportInstancesIndex(client: Client, private val clusterService: * {@inheritDoc} */ override fun getReportInstance(id: String): ReportInstance? { + createIndex() val getRequest = GetRequest(REPORT_INSTANCES_INDEX_NAME).id(id) val actionFuture = client.get(getRequest) val response = actionFuture.actionGet(PluginSettings.operationTimeoutMs) @@ -139,8 +142,9 @@ internal class ReportInstancesIndex(client: Client, private val clusterService: /** * {@inheritDoc} */ - override fun getAllReportInstances(ownerId: String, from: Int): List { - val query = QueryBuilders.matchQuery(USER_ID_FIELD, ownerId) + override fun getAllReportInstances(roles: List, from: Int): List { + createIndex() + val query = QueryBuilders.termsQuery(ROLE_LIST_FIELD, roles) val sourceBuilder = SearchSourceBuilder() .timeout(TimeValue(PluginSettings.operationTimeoutMs, TimeUnit.MILLISECONDS)) .sort(UPDATED_TIME_FIELD) @@ -167,6 +171,7 @@ internal class ReportInstancesIndex(client: Client, private val clusterService: * {@inheritDoc} */ override fun updateReportInstance(reportInstance: ReportInstance): Boolean { + createIndex() val updateRequest = UpdateRequest() .index(REPORT_INSTANCES_INDEX_NAME) .id(reportInstance.id) @@ -184,6 +189,7 @@ internal class ReportInstancesIndex(client: Client, private val clusterService: * {@inheritDoc} */ override fun updateReportInstanceDoc(reportInstanceDoc: ReportInstanceDoc): Boolean { + createIndex() val updateRequest = UpdateRequest() .index(REPORT_INSTANCES_INDEX_NAME) .id(reportInstanceDoc.reportInstance.id) @@ -203,6 +209,7 @@ internal class ReportInstancesIndex(client: Client, private val clusterService: * {@inheritDoc} */ override fun deleteReportInstance(id: String): Boolean { + createIndex() val deleteRequest = DeleteRequest() .index(REPORT_INSTANCES_INDEX_NAME) .id(id) @@ -218,9 +225,10 @@ internal class ReportInstancesIndex(client: Client, private val clusterService: * {@inheritDoc} */ override fun getPendingReportInstances(): MutableList { + createIndex() val query = QueryBuilders.termsQuery(STATUS_FIELD, - State.Scheduled.name, - State.Executing.name + Status.Scheduled.name, + Status.Executing.name ) val sourceBuilder = SearchSourceBuilder() .timeout(TimeValue(PluginSettings.operationTimeoutMs, TimeUnit.MILLISECONDS)) @@ -241,7 +249,7 @@ internal class ReportInstancesIndex(client: Client, private val clusterService: it.sourceAsString) parser.nextToken() val reportInstance = ReportInstance.parse(parser, it.id) - if (reportInstance.currentState == State.Scheduled || // If still in Scheduled state + if (reportInstance.status == Status.Scheduled || // If still in Scheduled state reportInstance.updatedTime.isBefore(refTime)) { // or when timeout happened mutableList.add(ReportInstanceDoc(reportInstance, it.seqNo, it.primaryTerm)) } diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/CreateReportDefinitionRequest.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/CreateReportDefinitionRequest.kt new file mode 100644 index 00000000..71c3d115 --- /dev/null +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/CreateReportDefinitionRequest.kt @@ -0,0 +1,87 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.model + +import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.LOG_PREFIX +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.REPORT_DEFINITION_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger +import org.elasticsearch.common.xcontent.ToXContent +import org.elasticsearch.common.xcontent.ToXContentObject +import org.elasticsearch.common.xcontent.XContentBuilder +import org.elasticsearch.common.xcontent.XContentFactory +import org.elasticsearch.common.xcontent.XContentParser +import org.elasticsearch.common.xcontent.XContentParser.Token +import org.elasticsearch.common.xcontent.XContentParserUtils + +/** + * Report Definition-create request. + *
 JSON format
+ * {@code
+ * {
+ *   "reportDefinition":{
+ *      // refer [com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportDefinition]
+ *   }
+ * }
+ * }
+ */ +internal data class CreateReportDefinitionRequest( + val reportDefinition: ReportDefinition +) : ToXContentObject { + companion object { + private val log by logger(CreateReportDefinitionRequest::class.java) + + /** + * Parse the data from parser and create [CreateReportDefinitionRequest] object + * @param parser data referenced at parser + * @return created [CreateReportDefinitionRequest] object + */ + fun parse(parser: XContentParser): CreateReportDefinitionRequest { + var reportDefinition: ReportDefinition? = null + XContentParserUtils.ensureExpectedToken(Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation) + while (Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + REPORT_DEFINITION_FIELD -> reportDefinition = ReportDefinition.parse(parser) + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:Skipping Unknown field $fieldName") + } + } + } + reportDefinition ?: throw IllegalArgumentException("$REPORT_DEFINITION_FIELD field absent") + return CreateReportDefinitionRequest(reportDefinition) + } + } + + /** + * create XContentBuilder from this object using [XContentFactory.jsonBuilder()] + * @return created XContentBuilder object + */ + fun toXContent(): XContentBuilder? { + return toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return builder!!.startObject() + .field(REPORT_DEFINITION_FIELD, reportDefinition) + .endObject() + } +} diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/CreateReportDefinitionResponse.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/CreateReportDefinitionResponse.kt new file mode 100644 index 00000000..89a29b39 --- /dev/null +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/CreateReportDefinitionResponse.kt @@ -0,0 +1,106 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.model + +import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.LOG_PREFIX +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.REPORT_DEFINITION_ID_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_TEXT_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.fieldIfNotNull +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger +import org.elasticsearch.common.xcontent.ToXContent +import org.elasticsearch.common.xcontent.XContentBuilder +import org.elasticsearch.common.xcontent.XContentFactory +import org.elasticsearch.common.xcontent.XContentParser +import org.elasticsearch.common.xcontent.XContentParser.Token +import org.elasticsearch.common.xcontent.XContentParserUtils +import org.elasticsearch.rest.RestStatus + +/** + * Report Definition-create response. + *
 JSON format
+ * {@code
+ * // On Success
+ * {
+ *   "reportDefinitionId":"reportDefinitionId"
+ * }
+ * // On Failure
+ * {
+ *   "status":500,
+ *   "statusText":"Report Definition Creation failed"
+ * }
+ * }
+ */ +internal data class CreateReportDefinitionResponse( + override val restStatus: RestStatus, + override val restStatusText: String?, + val reportDefinitionId: String? +) : IRestResponse { + companion object { + private val log by logger(CreateReportDefinitionResponse::class.java) + + /** + * Parse the data from parser and create [CreateReportDefinitionResponse] object + * @param parser data referenced at parser + * @return created [CreateReportDefinitionResponse] object + */ + fun parse(parser: XContentParser): CreateReportDefinitionResponse { + var restStatus: RestStatus = RestStatus.OK + var statusText: String? = null + var reportDefinitionId: String? = null + XContentParserUtils.ensureExpectedToken(Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation) + while (Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + STATUS_FIELD -> restStatus = RestStatus.fromCode(parser.intValue()) + STATUS_TEXT_FIELD -> statusText = parser.text() + REPORT_DEFINITION_ID_FIELD -> reportDefinitionId = parser.text() + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:Skipping Unknown field $fieldName") + } + } + } + return CreateReportDefinitionResponse(restStatus, statusText, reportDefinitionId) + } + } + + /** + * create XContentBuilder from this object using [XContentFactory.jsonBuilder()] + * @return created XContentBuilder object + */ + override fun toXContent(): XContentBuilder { + return toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return if (reportDefinitionId != null) { + builder!!.startObject() + .field(REPORT_DEFINITION_ID_FIELD, reportDefinitionId) + .endObject() + } else { + builder!!.startObject() + .field(STATUS_FIELD, restStatus) + .fieldIfNotNull(STATUS_TEXT_FIELD, restStatusText) + .endObject() + } + } +} diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/DeleteReportDefinitionRequest.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/DeleteReportDefinitionRequest.kt new file mode 100644 index 00000000..76f63ce4 --- /dev/null +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/DeleteReportDefinitionRequest.kt @@ -0,0 +1,87 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.model + +import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.LOG_PREFIX +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.REPORT_DEFINITION_ID_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger +import org.elasticsearch.common.xcontent.ToXContent +import org.elasticsearch.common.xcontent.ToXContentObject +import org.elasticsearch.common.xcontent.XContentBuilder +import org.elasticsearch.common.xcontent.XContentFactory +import org.elasticsearch.common.xcontent.XContentParser +import org.elasticsearch.common.xcontent.XContentParser.Token +import org.elasticsearch.common.xcontent.XContentParserUtils + +/** + * Report Definition-delete request. + * reportDefinitionId is from request query params + *
 JSON format
+ * {@code
+ * {
+ *   "reportDefinitionId":"reportDefinitionId"
+ * }
+ * }
+ */ +internal data class DeleteReportDefinitionRequest( + val reportDefinitionId: String +) : ToXContentObject { + companion object { + private val log by logger(DeleteReportDefinitionRequest::class.java) + + /** + * Parse the data from parser and create [DeleteReportDefinitionRequest] object + * @param parser data referenced at parser + * @param useReportDefinitionId use this id if not available in the json + * @return created [DeleteReportDefinitionRequest] object + */ + fun parse(parser: XContentParser, useReportDefinitionId: String? = null): DeleteReportDefinitionRequest { + var reportDefinitionId: String? = useReportDefinitionId + XContentParserUtils.ensureExpectedToken(Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation) + while (Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + REPORT_DEFINITION_ID_FIELD -> reportDefinitionId = parser.text() + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:Skipping Unknown field $fieldName") + } + } + } + reportDefinitionId ?: throw IllegalArgumentException("$REPORT_DEFINITION_ID_FIELD field absent") + return DeleteReportDefinitionRequest(reportDefinitionId) + } + } + + /** + * create XContentBuilder from this object using [XContentFactory.jsonBuilder()] + * @return created XContentBuilder object + */ + fun toXContent(): XContentBuilder? { + return toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return builder!!.startObject() + .field(REPORT_DEFINITION_ID_FIELD, reportDefinitionId) + .endObject() + } +} diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/DeleteReportDefinitionResponse.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/DeleteReportDefinitionResponse.kt new file mode 100644 index 00000000..bb03db74 --- /dev/null +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/DeleteReportDefinitionResponse.kt @@ -0,0 +1,106 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.model + +import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.LOG_PREFIX +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.REPORT_DEFINITION_ID_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_TEXT_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.fieldIfNotNull +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger +import org.elasticsearch.common.xcontent.ToXContent +import org.elasticsearch.common.xcontent.XContentBuilder +import org.elasticsearch.common.xcontent.XContentFactory +import org.elasticsearch.common.xcontent.XContentParser +import org.elasticsearch.common.xcontent.XContentParser.Token +import org.elasticsearch.common.xcontent.XContentParserUtils +import org.elasticsearch.rest.RestStatus + +/** + * Report Definition-delete response. + *
 JSON format
+ * {@code
+ * // On Success
+ * {
+ *   "reportDefinitionId":"reportDefinitionId"
+ * }
+ * // On Failure
+ * {
+ *   "status":500,
+ *   "statusText":"Report Definition Delete failed"
+ * }
+ * }
+ */ +internal data class DeleteReportDefinitionResponse( + override val restStatus: RestStatus, + override val restStatusText: String?, + val reportDefinitionId: String? +) : IRestResponse { + companion object { + private val log by logger(DeleteReportDefinitionResponse::class.java) + + /** + * Parse the data from parser and create [DeleteReportDefinitionResponse] object + * @param parser data referenced at parser + * @return created [DeleteReportDefinitionResponse] object + */ + fun parse(parser: XContentParser): DeleteReportDefinitionResponse { + var restStatus: RestStatus = RestStatus.OK + var statusText: String? = null + var reportDefinitionId: String? = null + XContentParserUtils.ensureExpectedToken(Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation) + while (Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + STATUS_FIELD -> restStatus = RestStatus.fromCode(parser.intValue()) + STATUS_TEXT_FIELD -> statusText = parser.text() + REPORT_DEFINITION_ID_FIELD -> reportDefinitionId = parser.text() + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:Skipping Unknown field $fieldName") + } + } + } + return DeleteReportDefinitionResponse(restStatus, statusText, reportDefinitionId) + } + } + + /** + * create XContentBuilder from this object using [XContentFactory.jsonBuilder()] + * @return created XContentBuilder object + */ + override fun toXContent(): XContentBuilder { + return toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return if (reportDefinitionId != null) { + builder!!.startObject() + .field(REPORT_DEFINITION_ID_FIELD, reportDefinitionId) + .endObject() + } else { + builder!!.startObject() + .field(STATUS_FIELD, restStatus) + .fieldIfNotNull(STATUS_TEXT_FIELD, restStatusText) + .endObject() + } + } +} diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/GetAllReportDefinitionsRequest.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/GetAllReportDefinitionsRequest.kt new file mode 100644 index 00000000..51a2003c --- /dev/null +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/GetAllReportDefinitionsRequest.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.model + +import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.LOG_PREFIX +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.FROM_INDEX_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger +import org.elasticsearch.common.xcontent.ToXContent +import org.elasticsearch.common.xcontent.ToXContentObject +import org.elasticsearch.common.xcontent.XContentBuilder +import org.elasticsearch.common.xcontent.XContentFactory +import org.elasticsearch.common.xcontent.XContentParser +import org.elasticsearch.common.xcontent.XContentParser.Token +import org.elasticsearch.common.xcontent.XContentParserUtils + +/** + * Get All report definitions info request + * Data object created from GET request query params + *
 JSON format
+ * {@code
+ * {
+ *   "fromIndex":100
+ * }
+ * }
+ */ +internal data class GetAllReportDefinitionsRequest( + val fromIndex: Int +) : ToXContentObject { + companion object { + private val log by logger(GetAllReportDefinitionsRequest::class.java) + + /** + * Parse the data from parser and create [GetAllReportDefinitionsRequest] object + * @param parser data referenced at parser + * @return created [GetAllReportDefinitionsRequest] object + */ + fun parse(parser: XContentParser): GetAllReportDefinitionsRequest { + var reportInstanceId = 0 + XContentParserUtils.ensureExpectedToken(Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation) + while (Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + FROM_INDEX_FIELD -> reportInstanceId = parser.intValue() + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:Skipping Unknown field $fieldName") + } + } + } + return GetAllReportDefinitionsRequest(reportInstanceId) + } + } + + /** + * create XContentBuilder from this object using [XContentFactory.jsonBuilder()] + * @return created XContentBuilder object + */ + fun toXContent(): XContentBuilder? { + return toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return builder!!.startObject() + .field(FROM_INDEX_FIELD, fromIndex) + .endObject() + } +} diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/GetAllReportDefinitionsResponse.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/GetAllReportDefinitionsResponse.kt new file mode 100644 index 00000000..57d8de23 --- /dev/null +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/GetAllReportDefinitionsResponse.kt @@ -0,0 +1,122 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.model + +import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.LOG_PREFIX +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.REPORT_DEFINITION_LIST_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_TEXT_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.fieldIfNotNull +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger +import org.elasticsearch.common.xcontent.ToXContent +import org.elasticsearch.common.xcontent.XContentBuilder +import org.elasticsearch.common.xcontent.XContentFactory +import org.elasticsearch.common.xcontent.XContentParser +import org.elasticsearch.common.xcontent.XContentParser.Token +import org.elasticsearch.common.xcontent.XContentParserUtils +import org.elasticsearch.rest.RestStatus + +/** + * Get all report instances info response. + *
 JSON format
+ * {@code
+ * // On Success
+ * {
+ *   "reportDefinitionDetailsList":[
+ *      // refer [com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportDefinitionDetails]
+ *   ]
+ * }
+ * // On Failure
+ * {
+ *   "status":404,
+ *   "statusText":"No Report Definitions found"
+ * }
+ * }
+ */ +internal data class GetAllReportDefinitionsResponse( + override val restStatus: RestStatus, + override val restStatusText: String?, + val reportDefinitionList: List? +) : IRestResponse { + companion object { + private val log by logger(GetAllReportDefinitionsResponse::class.java) + + /** + * Parse the data from parser and create [GetAllReportDefinitionsResponse] object + * @param parser data referenced at parser + * @return created [GetAllReportDefinitionsResponse] object + */ + fun parse(parser: XContentParser): GetAllReportDefinitionsResponse { + var restStatus: RestStatus = RestStatus.OK + var statusText: String? = null + var reportInstances: List? = null + XContentParserUtils.ensureExpectedToken(Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation) + while (Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + STATUS_FIELD -> restStatus = RestStatus.fromCode(parser.intValue()) + STATUS_TEXT_FIELD -> statusText = parser.text() + REPORT_DEFINITION_LIST_FIELD -> reportInstances = parseReportDefinitionList(parser) + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:Skipping Unknown field $fieldName") + } + } + } + return GetAllReportDefinitionsResponse(restStatus, statusText, reportInstances) + } + + /** + * Parse the report definition list from parser + * @param parser data referenced at parser + * @return created list of ReportDefinitionDetails + */ + private fun parseReportDefinitionList(parser: XContentParser): List { + val retList: MutableList = mutableListOf() + XContentParserUtils.ensureExpectedToken(Token.START_ARRAY, parser.currentToken(), parser::getTokenLocation) + while (parser.nextToken() != Token.END_ARRAY) { + retList.add(ReportDefinitionDetails.parse(parser)) + } + return retList + } + } + + /** + * {@inheritDoc} + */ + override fun toXContent(): XContentBuilder { + return toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return if (reportDefinitionList != null) { + builder!!.startObject() + .startArray(REPORT_DEFINITION_LIST_FIELD) + reportDefinitionList.forEach { it.toXContent(builder, ToXContent.EMPTY_PARAMS, true) } + builder.endArray().endObject() + } else { + builder!!.startObject() + .field(STATUS_FIELD, restStatus) + .fieldIfNotNull(STATUS_TEXT_FIELD, restStatusText) + .endObject() + } + } +} diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/GetAllReportInstancesRequest.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/GetAllReportInstancesRequest.kt new file mode 100644 index 00000000..1451a2ab --- /dev/null +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/GetAllReportInstancesRequest.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.model + +import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.LOG_PREFIX +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.FROM_INDEX_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger +import org.elasticsearch.common.xcontent.ToXContent +import org.elasticsearch.common.xcontent.ToXContentObject +import org.elasticsearch.common.xcontent.XContentBuilder +import org.elasticsearch.common.xcontent.XContentFactory +import org.elasticsearch.common.xcontent.XContentParser +import org.elasticsearch.common.xcontent.XContentParser.Token +import org.elasticsearch.common.xcontent.XContentParserUtils + +/** + * Get All report instances info request + * Data object created from GET request query params + *
 JSON format
+ * {@code
+ * {
+ *   "fromIndex":100
+ * }
+ * }
+ */ +internal data class GetAllReportInstancesRequest( + val fromIndex: Int +) : ToXContentObject { + companion object { + private val log by logger(GetAllReportInstancesRequest::class.java) + + /** + * Parse the data from parser and create [GetAllReportInstancesRequest] object + * @param parser data referenced at parser + * @return created [GetAllReportInstancesRequest] object + */ + fun parse(parser: XContentParser): GetAllReportInstancesRequest { + var reportInstanceId = 0 + XContentParserUtils.ensureExpectedToken(Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation) + while (Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + FROM_INDEX_FIELD -> reportInstanceId = parser.intValue() + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:Skipping Unknown field $fieldName") + } + } + } + return GetAllReportInstancesRequest(reportInstanceId) + } + } + + /** + * create XContentBuilder from this object using [XContentFactory.jsonBuilder()] + * @return created XContentBuilder object + */ + fun toXContent(): XContentBuilder? { + return toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return builder!!.startObject() + .field(FROM_INDEX_FIELD, fromIndex) + .endObject() + } +} diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/GetAllReportInstancesResponse.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/GetAllReportInstancesResponse.kt new file mode 100644 index 00000000..b8705c45 --- /dev/null +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/GetAllReportInstancesResponse.kt @@ -0,0 +1,122 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.model + +import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.LOG_PREFIX +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.REPORT_INSTANCE_LIST_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_TEXT_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.fieldIfNotNull +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger +import org.elasticsearch.common.xcontent.ToXContent +import org.elasticsearch.common.xcontent.XContentBuilder +import org.elasticsearch.common.xcontent.XContentFactory +import org.elasticsearch.common.xcontent.XContentParser +import org.elasticsearch.common.xcontent.XContentParser.Token +import org.elasticsearch.common.xcontent.XContentParserUtils +import org.elasticsearch.rest.RestStatus + +/** + * Get all report instances info response. + *
 JSON format
+ * {@code
+ * // On Success
+ * {
+ *   "reportInstanceList":[
+ *      // refer [com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportInstance]
+ *   ]
+ * }
+ * // On Failure
+ * {
+ *   "status":404,
+ *   "statusText":"No Report Definitions found"
+ * }
+ * }
+ */ +internal data class GetAllReportInstancesResponse( + override val restStatus: RestStatus, + override val restStatusText: String?, + val reportInstanceList: List? +) : IRestResponse { + companion object { + private val log by logger(GetAllReportInstancesResponse::class.java) + + /** + * Parse the data from parser and create [GetAllReportInstancesResponse] object + * @param parser data referenced at parser + * @return created [GetAllReportInstancesResponse] object + */ + fun parse(parser: XContentParser): GetAllReportInstancesResponse { + var restStatus: RestStatus = RestStatus.OK + var statusText: String? = null + var reportInstanceList: List? = null + XContentParserUtils.ensureExpectedToken(Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation) + while (Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + STATUS_FIELD -> restStatus = RestStatus.fromCode(parser.intValue()) + STATUS_TEXT_FIELD -> statusText = parser.text() + REPORT_INSTANCE_LIST_FIELD -> reportInstanceList = parseReportInstanceList(parser) + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:Skipping Unknown field $fieldName") + } + } + } + return GetAllReportInstancesResponse(restStatus, statusText, reportInstanceList) + } + + /** + * Parse the report instance list from parser + * @param parser data referenced at parser + * @return created list of ReportInstance + */ + private fun parseReportInstanceList(parser: XContentParser): List { + val retList: MutableList = mutableListOf() + XContentParserUtils.ensureExpectedToken(Token.START_ARRAY, parser.currentToken(), parser::getTokenLocation) + while (parser.nextToken() != Token.END_ARRAY) { + retList.add(ReportInstance.parse(parser)) + } + return retList + } + } + + /** + * {@inheritDoc} + */ + override fun toXContent(): XContentBuilder { + return toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return if (reportInstanceList != null) { + builder!!.startObject() + .startArray(REPORT_INSTANCE_LIST_FIELD) + reportInstanceList.forEach { it.toXContent(builder, ToXContent.EMPTY_PARAMS, true) } + builder.endArray().endObject() + } else { + builder!!.startObject() + .field(STATUS_FIELD, restStatus) + .fieldIfNotNull(STATUS_TEXT_FIELD, restStatusText) + .endObject() + } + } +} diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/GetReportDefinitionRequest.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/GetReportDefinitionRequest.kt new file mode 100644 index 00000000..a8fb2e0d --- /dev/null +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/GetReportDefinitionRequest.kt @@ -0,0 +1,87 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.model + +import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.LOG_PREFIX +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.REPORT_DEFINITION_ID_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger +import org.elasticsearch.common.xcontent.ToXContent +import org.elasticsearch.common.xcontent.ToXContentObject +import org.elasticsearch.common.xcontent.XContentBuilder +import org.elasticsearch.common.xcontent.XContentFactory +import org.elasticsearch.common.xcontent.XContentParser +import org.elasticsearch.common.xcontent.XContentParser.Token +import org.elasticsearch.common.xcontent.XContentParserUtils + +/** + * Report Definition-get request. + * reportDefinitionId is from request query params + *
 JSON format
+ * {@code
+ * {
+ *   "reportDefinitionId":"reportDefinitionId"
+ * }
+ * }
+ */ +internal data class GetReportDefinitionRequest( + val reportDefinitionId: String +) : ToXContentObject { + companion object { + private val log by logger(GetReportDefinitionRequest::class.java) + + /** + * Parse the data from parser and create [GetReportDefinitionRequest] object + * @param parser data referenced at parser + * @param useReportDefinitionId use this id if not available in the json + * @return created [GetReportDefinitionRequest] object + */ + fun parse(parser: XContentParser, useReportDefinitionId: String? = null): GetReportDefinitionRequest { + var reportDefinitionId: String? = useReportDefinitionId + XContentParserUtils.ensureExpectedToken(Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation) + while (Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + REPORT_DEFINITION_ID_FIELD -> reportDefinitionId = parser.text() + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:Skipping Unknown field $fieldName") + } + } + } + reportDefinitionId ?: throw IllegalArgumentException("$REPORT_DEFINITION_ID_FIELD field absent") + return GetReportDefinitionRequest(reportDefinitionId) + } + } + + /** + * create XContentBuilder from this object using [XContentFactory.jsonBuilder()] + * @return created XContentBuilder object + */ + fun toXContent(): XContentBuilder? { + return toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return builder!!.startObject() + .field(REPORT_DEFINITION_ID_FIELD, reportDefinitionId) + .endObject() + } +} diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/GetReportDefinitionsResponse.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/GetReportDefinitionsResponse.kt new file mode 100644 index 00000000..955a3dca --- /dev/null +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/GetReportDefinitionsResponse.kt @@ -0,0 +1,107 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.model + +import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.LOG_PREFIX +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.REPORT_DEFINITION_DETAILS_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_TEXT_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.fieldIfNotNull +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger +import org.elasticsearch.common.xcontent.ToXContent +import org.elasticsearch.common.xcontent.XContentBuilder +import org.elasticsearch.common.xcontent.XContentFactory +import org.elasticsearch.common.xcontent.XContentParser +import org.elasticsearch.common.xcontent.XContentParser.Token +import org.elasticsearch.common.xcontent.XContentParserUtils +import org.elasticsearch.rest.RestStatus + +/** + * Get report Definition info response. + *
 JSON format
+ * {@code
+ * // On Success
+ * {
+ *   "reportDefinitionDetails":{
+ *      // refer [com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportDefinitionDetails]
+ *   }
+ * }
+ * // On Failure
+ * {
+ *   "status":404,
+ *   "statusText":"No Report Definitions found"
+ * }
+ * }
+ */ +internal data class GetReportDefinitionsResponse( + override val restStatus: RestStatus, + override val restStatusText: String?, + val reportDefinition: ReportDefinitionDetails? +) : IRestResponse { + companion object { + private val log by logger(GetReportDefinitionsResponse::class.java) + + /** + * Parse the data from parser and create [GetReportDefinitionsResponse] object + * @param parser data referenced at parser + * @return created [GetReportDefinitionsResponse] object + */ + fun parse(parser: XContentParser): GetReportDefinitionsResponse { + var restStatus: RestStatus = RestStatus.OK + var statusText: String? = null + var reportDefinition: ReportDefinitionDetails? = null + XContentParserUtils.ensureExpectedToken(Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation) + while (Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + STATUS_FIELD -> restStatus = RestStatus.fromCode(parser.intValue()) + STATUS_TEXT_FIELD -> statusText = parser.text() + REPORT_DEFINITION_DETAILS_FIELD -> reportDefinition = ReportDefinitionDetails.parse(parser) + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:Skipping Unknown field $fieldName") + } + } + } + return GetReportDefinitionsResponse(restStatus, statusText, reportDefinition) + } + } + + /** + * {@inheritDoc} + */ + override fun toXContent(): XContentBuilder { + return toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return if (reportDefinition != null) { + builder!!.startObject() + .field(REPORT_DEFINITION_DETAILS_FIELD, reportDefinition) + .endObject() + } else { + builder!!.startObject() + .field(STATUS_FIELD, restStatus) + .fieldIfNotNull(STATUS_TEXT_FIELD, restStatusText) + .endObject() + } + } +} diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/GetReportInstanceRequest.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/GetReportInstanceRequest.kt new file mode 100644 index 00000000..e3942d24 --- /dev/null +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/GetReportInstanceRequest.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.model + +import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.LOG_PREFIX +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.REPORT_INSTANCE_ID_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger +import org.elasticsearch.common.xcontent.ToXContent +import org.elasticsearch.common.xcontent.ToXContentObject +import org.elasticsearch.common.xcontent.XContentBuilder +import org.elasticsearch.common.xcontent.XContentFactory +import org.elasticsearch.common.xcontent.XContentParser +import org.elasticsearch.common.xcontent.XContentParser.Token +import org.elasticsearch.common.xcontent.XContentParserUtils + +/** + * Get report instance info request + * Data object created from GET request query params + *
 JSON format
+ * {@code
+ * {
+ *   "reportInstanceId":"reportInstanceId"
+ * }
+ * }
+ */ +internal data class GetReportInstanceRequest( + val reportInstanceId: String +) : ToXContentObject { + companion object { + private val log by logger(GetReportInstanceRequest::class.java) + + /** + * Parse the data from parser and create [GetReportInstanceRequest] object + * @param parser data referenced at parser + * @return created [GetReportInstanceRequest] object + */ + fun parse(parser: XContentParser, useReportInstanceId: String? = null): GetReportInstanceRequest { + var reportInstanceId: String? = useReportInstanceId + XContentParserUtils.ensureExpectedToken(Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation) + while (Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + REPORT_INSTANCE_ID_FIELD -> reportInstanceId = parser.text() + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:Skipping Unknown field $fieldName") + } + } + } + reportInstanceId ?: throw IllegalArgumentException("$REPORT_INSTANCE_ID_FIELD field absent") + return GetReportInstanceRequest(reportInstanceId) + } + } + + /** + * create XContentBuilder from this object using [XContentFactory.jsonBuilder()] + * @return created XContentBuilder object + */ + fun toXContent(): XContentBuilder? { + return toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return builder!!.startObject() + .field(REPORT_INSTANCE_ID_FIELD, reportInstanceId) + .endObject() + } +} diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/GetReportInstanceResponse.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/GetReportInstanceResponse.kt new file mode 100644 index 00000000..6a891f36 --- /dev/null +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/GetReportInstanceResponse.kt @@ -0,0 +1,108 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.model + +import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.LOG_PREFIX +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.REPORT_INSTANCE_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_TEXT_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.fieldIfNotNull +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger +import org.elasticsearch.common.xcontent.ToXContent +import org.elasticsearch.common.xcontent.XContentBuilder +import org.elasticsearch.common.xcontent.XContentFactory +import org.elasticsearch.common.xcontent.XContentParser +import org.elasticsearch.common.xcontent.XContentParser.Token +import org.elasticsearch.common.xcontent.XContentParserUtils +import org.elasticsearch.rest.RestStatus + +/** + * Get report instance info response. + *
 JSON format
+ * {@code
+ * // On Success
+ * {
+ *   "reportInstance":{
+ *      // refer [com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportInstance]
+ *   }
+ * }
+ * // On Failure
+ * {
+ *   "status":404,
+ *   "statusText":"Report Instance not found"
+ * }
+ * }
+ */ +internal data class GetReportInstanceResponse( + override val restStatus: RestStatus, + override val restStatusText: String?, + val reportInstance: ReportInstance? +) : IRestResponse { + companion object { + private val log by logger(GetReportInstanceResponse::class.java) + + /** + * Parse the data from parser and create [GetReportInstanceResponse] object + * @param parser data referenced at parser + * @return created [GetReportInstanceResponse] object + */ + fun parse(parser: XContentParser): GetReportInstanceResponse { + var restStatus: RestStatus = RestStatus.OK + var statusText: String? = null + var reportInstance: ReportInstance? = null + XContentParserUtils.ensureExpectedToken(Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation) + while (Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + STATUS_FIELD -> restStatus = RestStatus.fromCode(parser.intValue()) + STATUS_TEXT_FIELD -> statusText = parser.text() + REPORT_INSTANCE_FIELD -> reportInstance = ReportInstance.parse(parser) + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:Skipping Unknown field $fieldName") + } + } + } + return GetReportInstanceResponse(restStatus, statusText, reportInstance) + } + } + + /** + * {@inheritDoc} + */ + override fun toXContent(): XContentBuilder { + return toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return if (reportInstance != null) { + builder!!.startObject() + .field(REPORT_INSTANCE_FIELD) + reportInstance.toXContent(builder, ToXContent.EMPTY_PARAMS, true) + builder.endObject() + } else { + builder!!.startObject() + .field(STATUS_FIELD, restStatus) + .fieldIfNotNull(STATUS_TEXT_FIELD, restStatusText) + .endObject() + } + } +} diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/IRestResponse.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/IRestResponse.kt new file mode 100644 index 00000000..14540ca5 --- /dev/null +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/IRestResponse.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.model + +import org.elasticsearch.common.xcontent.ToXContentObject +import org.elasticsearch.common.xcontent.XContentBuilder +import org.elasticsearch.rest.RestStatus + +/** + * Interface for REST response. + */ +interface IRestResponse : ToXContentObject { + val restStatus: RestStatus + val restStatusText: String? + + /** + * create XContentBuilder from this object using [XContentFactory.jsonBuilder()] + * @return created XContentBuilder object + */ + fun toXContent(): XContentBuilder +} diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/InContextReportCreateRequest.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/InContextReportCreateRequest.kt new file mode 100644 index 00000000..2a3cde84 --- /dev/null +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/InContextReportCreateRequest.kt @@ -0,0 +1,130 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.model + +import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.LOG_PREFIX +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportInstance.Status +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.BEGIN_TIME_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.END_TIME_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.IN_CONTEXT_DOWNLOAD_URL_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.REPORT_DEFINITION_DETAILS_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_TEXT_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.fieldIfNotNull +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger +import org.elasticsearch.common.xcontent.ToXContent +import org.elasticsearch.common.xcontent.ToXContentObject +import org.elasticsearch.common.xcontent.XContentBuilder +import org.elasticsearch.common.xcontent.XContentFactory +import org.elasticsearch.common.xcontent.XContentParser +import org.elasticsearch.common.xcontent.XContentParser.Token +import org.elasticsearch.common.xcontent.XContentParserUtils +import java.time.Instant + +/** + * Report-create request from in-context menu. + *
 JSON format
+ * {@code
+ * {
+ *   "beginTimeMs":1603506908773,
+ *   "endTimeMs":1603506908773,
+ *   "reportDefinitionDetails":{
+ *      // refer [com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportDefinitionDetails]
+ *   },
+ *   "status":"Success", // Scheduled, Executing, Success, Failed
+ *   "statusText":"Success",
+ *   "inContextDownloadUrlPath":"/app/dashboard#view/dashboard-id"
+ * }
+ * }
+ */ +internal data class InContextReportCreateRequest( + val beginTime: Instant, + val endTime: Instant, + val reportDefinitionDetails: ReportDefinitionDetails?, + val status: Status, + val statusText: String? = null, + val inContextDownloadUrlPath: String? = null +) : ToXContentObject { + companion object { + private val log by logger(InContextReportCreateRequest::class.java) + + /** + * Parse the data from parser and create [InContextReportCreateRequest] object + * @param parser data referenced at parser + * @return created [InContextReportCreateRequest] object + */ + fun parse(parser: XContentParser): InContextReportCreateRequest { + var beginTime: Instant? = null + var endTime: Instant? = null + var reportDefinitionDetails: ReportDefinitionDetails? = null + var status: Status? = null + var statusText: String? = null + var inContextDownloadUrlPath: String? = null + XContentParserUtils.ensureExpectedToken(Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation) + while (Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + BEGIN_TIME_FIELD -> beginTime = Instant.ofEpochMilli(parser.longValue()) + END_TIME_FIELD -> endTime = Instant.ofEpochMilli(parser.longValue()) + REPORT_DEFINITION_DETAILS_FIELD -> reportDefinitionDetails = ReportDefinitionDetails.parse(parser) + STATUS_FIELD -> status = Status.valueOf(parser.text()) + STATUS_TEXT_FIELD -> statusText = parser.text() + IN_CONTEXT_DOWNLOAD_URL_FIELD -> inContextDownloadUrlPath = parser.text() + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:InContextReportCreateRequest Skipping Unknown field $fieldName") + } + } + } + beginTime ?: throw IllegalArgumentException("$BEGIN_TIME_FIELD field absent") + endTime ?: throw IllegalArgumentException("$END_TIME_FIELD field absent") + status ?: throw IllegalArgumentException("$STATUS_FIELD field absent") + return InContextReportCreateRequest(beginTime, + endTime, + reportDefinitionDetails, + status, + statusText, + inContextDownloadUrlPath) + } + } + + /** + * create XContentBuilder from this object using [XContentFactory.jsonBuilder()] + * @return created XContentBuilder object + */ + fun toXContent(): XContentBuilder? { + return toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!!.startObject() + .field(BEGIN_TIME_FIELD, beginTime.toEpochMilli()) + .field(END_TIME_FIELD, endTime.toEpochMilli()) + if (reportDefinitionDetails != null) { + builder.field(REPORT_DEFINITION_DETAILS_FIELD) + reportDefinitionDetails.toXContent(builder, ToXContent.EMPTY_PARAMS, true) + } + return builder.field(STATUS_FIELD, status.name) + .fieldIfNotNull(STATUS_TEXT_FIELD, statusText) + .fieldIfNotNull(IN_CONTEXT_DOWNLOAD_URL_FIELD, inContextDownloadUrlPath) + .endObject() + } +} diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/InContextReportCreateResponse.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/InContextReportCreateResponse.kt new file mode 100644 index 00000000..b8df4c9e --- /dev/null +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/InContextReportCreateResponse.kt @@ -0,0 +1,109 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.model + +import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.LOG_PREFIX +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.REPORT_INSTANCE_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_TEXT_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.fieldIfNotNull +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger +import org.elasticsearch.common.xcontent.ToXContent +import org.elasticsearch.common.xcontent.XContentBuilder +import org.elasticsearch.common.xcontent.XContentFactory +import org.elasticsearch.common.xcontent.XContentParser +import org.elasticsearch.common.xcontent.XContentParser.Token +import org.elasticsearch.common.xcontent.XContentParserUtils +import org.elasticsearch.rest.RestStatus + +/** + * Report-create response for in-context menu. + *
 JSON format
+ * {@code
+ * // On Success
+ * {
+ *   "reportInstance":{
+ *      // refer [com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportInstance]
+ *   }
+ * }
+ * // On Failure
+ * {
+ *   "status":500,
+ *   "statusText":"Report Instance Creation failed"
+ * }
+ * }
+ */ +internal data class InContextReportCreateResponse( + override val restStatus: RestStatus, + override val restStatusText: String?, + val reportInstance: ReportInstance? +) : IRestResponse { + companion object { + private val log by logger(InContextReportCreateResponse::class.java) + + /** + * Parse the data from parser and create [InContextReportCreateResponse] object + * @param parser data referenced at parser + * @return created [InContextReportCreateResponse] object + */ + fun parse(parser: XContentParser): InContextReportCreateResponse { + var restStatus: RestStatus = RestStatus.OK + var statusText: String? = null + var reportInstance: ReportInstance? = null + XContentParserUtils.ensureExpectedToken(Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation) + while (Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + STATUS_FIELD -> restStatus = RestStatus.fromCode(parser.intValue()) + STATUS_TEXT_FIELD -> statusText = parser.text() + REPORT_INSTANCE_FIELD -> reportInstance = ReportInstance.parse(parser) + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:Skipping Unknown field $fieldName") + } + } + } + return InContextReportCreateResponse(restStatus, statusText, reportInstance) + } + } + + /** + * create XContentBuilder from this object using [XContentFactory.jsonBuilder()] + * @return created XContentBuilder object + */ + override fun toXContent(): XContentBuilder { + return toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return if (reportInstance != null) { + builder!!.startObject() + .field(REPORT_INSTANCE_FIELD) + reportInstance.toXContent(builder, ToXContent.EMPTY_PARAMS, true) + builder.endObject() + } else { + builder!!.startObject() + .field(STATUS_FIELD, restStatus) + .fieldIfNotNull(STATUS_TEXT_FIELD, restStatusText) + .endObject() + } + } +} diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/OnDemandReportCreateRequest.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/OnDemandReportCreateRequest.kt new file mode 100644 index 00000000..7b630d4c --- /dev/null +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/OnDemandReportCreateRequest.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.model + +import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.LOG_PREFIX +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.REPORT_DEFINITION_ID_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger +import org.elasticsearch.common.xcontent.ToXContent +import org.elasticsearch.common.xcontent.ToXContentObject +import org.elasticsearch.common.xcontent.XContentBuilder +import org.elasticsearch.common.xcontent.XContentFactory +import org.elasticsearch.common.xcontent.XContentParser +import org.elasticsearch.common.xcontent.XContentParser.Token +import org.elasticsearch.common.xcontent.XContentParserUtils + +/** + * Report-create request from on-demand report definition. + * reportDefinitionId field is from request query params + *
 JSON format
+ * {@code
+ * {
+ *   "reportDefinitionId":"reportDefinitionId"
+ * }
+ * }
+ */ +internal data class OnDemandReportCreateRequest( + val reportDefinitionId: String +) : ToXContentObject { + companion object { + private val log by logger(OnDemandReportCreateRequest::class.java) + + /** + * Parse the data from parser and create [OnDemandReportCreateRequest] object + * @param parser data referenced at parser + * @return created [OnDemandReportCreateRequest] object + */ + fun parse(parser: XContentParser, useReportDefinitionId: String? = null): OnDemandReportCreateRequest { + var reportDefinitionId: String? = useReportDefinitionId + XContentParserUtils.ensureExpectedToken(Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation) + while (Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + REPORT_DEFINITION_ID_FIELD -> reportDefinitionId = parser.text() + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:Skipping Unknown field $fieldName") + } + } + } + reportDefinitionId ?: throw IllegalArgumentException("$REPORT_DEFINITION_ID_FIELD field absent") + return OnDemandReportCreateRequest(reportDefinitionId) + } + } + + /** + * create XContentBuilder from this object using [XContentFactory.jsonBuilder()] + * @return created XContentBuilder object + */ + fun toXContent(): XContentBuilder? { + return toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return builder!!.startObject() + .field(REPORT_DEFINITION_ID_FIELD, reportDefinitionId) + .endObject() + } +} diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/OnDemandReportCreateResponse.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/OnDemandReportCreateResponse.kt new file mode 100644 index 00000000..c114b869 --- /dev/null +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/OnDemandReportCreateResponse.kt @@ -0,0 +1,108 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.model + +import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.LOG_PREFIX +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.REPORT_INSTANCE_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_TEXT_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.fieldIfNotNull +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger +import org.elasticsearch.common.xcontent.ToXContent +import org.elasticsearch.common.xcontent.XContentBuilder +import org.elasticsearch.common.xcontent.XContentFactory +import org.elasticsearch.common.xcontent.XContentParser +import org.elasticsearch.common.xcontent.XContentParser.Token +import org.elasticsearch.common.xcontent.XContentParserUtils +import org.elasticsearch.rest.RestStatus + +/** + * Report-create response for on-demand report definition. + *
 JSON format
+ * {@code
+ * // On Success
+ * {
+ *   "reportInstance":{
+ *      // refer [com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportInstance]
+ *   }
+ * }
+ * // On Failure
+ * {
+ *   "status":500,
+ *   "statusText":"Report Instance Creation failed"
+ * }
+ * }
+ */ +internal data class OnDemandReportCreateResponse( + override val restStatus: RestStatus, + override val restStatusText: String?, + val reportInstance: ReportInstance? +) : IRestResponse { + companion object { + private val log by logger(OnDemandReportCreateResponse::class.java) + + /** + * Parse the data from parser and create [OnDemandReportCreateResponse] object + * @param parser data referenced at parser + * @return created [OnDemandReportCreateResponse] object + */ + fun parse(parser: XContentParser): OnDemandReportCreateResponse { + var restStatus: RestStatus = RestStatus.OK + var statusText: String? = null + var reportInstance: ReportInstance? = null + XContentParserUtils.ensureExpectedToken(Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation) + while (Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + STATUS_FIELD -> restStatus = RestStatus.fromCode(parser.intValue()) + STATUS_TEXT_FIELD -> statusText = parser.text() + REPORT_INSTANCE_FIELD -> reportInstance = ReportInstance.parse(parser) + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:Skipping Unknown field $fieldName") + } + } + } + return OnDemandReportCreateResponse(restStatus, statusText, reportInstance) + } + } + + /** + * {@inheritDoc} + */ + override fun toXContent(): XContentBuilder { + return toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return if (reportInstance != null) { + builder!!.startObject() + .field(REPORT_INSTANCE_FIELD) + reportInstance.toXContent(builder, ToXContent.EMPTY_PARAMS, true) + builder.endObject() + } else { + builder!!.startObject() + .field(STATUS_FIELD, restStatus) + .fieldIfNotNull(STATUS_TEXT_FIELD, restStatusText) + .endObject() + } + } +} diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/PollReportInstanceResponse.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/PollReportInstanceResponse.kt new file mode 100644 index 00000000..43d056c8 --- /dev/null +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/PollReportInstanceResponse.kt @@ -0,0 +1,115 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.model + +import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.LOG_PREFIX +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.REPORT_INSTANCE_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.RETRY_AFTER_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_TEXT_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.settings.PluginSettings +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.fieldIfNotNull +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger +import org.elasticsearch.common.xcontent.ToXContent +import org.elasticsearch.common.xcontent.XContentBuilder +import org.elasticsearch.common.xcontent.XContentFactory +import org.elasticsearch.common.xcontent.XContentParser +import org.elasticsearch.common.xcontent.XContentParser.Token +import org.elasticsearch.common.xcontent.XContentParserUtils +import org.elasticsearch.rest.RestStatus + +/** + * Poll report instance info response. + *
 JSON format
+ * {@code
+ * // On Success
+ * {
+ *   "reportInstance":{
+ *      // refer [com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportInstance]
+ *   }
+ * }
+ * // On Failure
+ * {
+ *   "status":207,
+ *   "statusText":"No Scheduled Report Instance found",
+ *   "retryAfter":300
+ * }
+ * }
+ */ +internal data class PollReportInstanceResponse( + override val restStatus: RestStatus, + override val restStatusText: String?, + val retryAfter: Int, + val reportInstance: ReportInstance? +) : IRestResponse { + companion object { + private val log by logger(PollReportInstanceResponse::class.java) + + /** + * Parse the data from parser and create [PollReportInstanceResponse] object + * @param parser data referenced at parser + * @return created [PollReportInstanceResponse] object + */ + fun parse(parser: XContentParser): PollReportInstanceResponse { + var restStatus: RestStatus = RestStatus.OK + var statusText: String? = null + var retryAfter: Int = PluginSettings.minPollingDurationSeconds + var reportInstance: ReportInstance? = null + XContentParserUtils.ensureExpectedToken(Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation) + while (Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + STATUS_FIELD -> restStatus = RestStatus.fromCode(parser.intValue()) + STATUS_TEXT_FIELD -> statusText = parser.text() + RETRY_AFTER_FIELD -> retryAfter = parser.intValue() + REPORT_INSTANCE_FIELD -> reportInstance = ReportInstance.parse(parser) + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:Skipping Unknown field $fieldName") + } + } + } + return PollReportInstanceResponse(restStatus, statusText, retryAfter, reportInstance) + } + } + + /** + * {@inheritDoc} + */ + override fun toXContent(): XContentBuilder { + return toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return if (reportInstance != null) { + builder!!.startObject() + .field(REPORT_INSTANCE_FIELD) + reportInstance.toXContent(builder, ToXContent.EMPTY_PARAMS, true) + builder.endObject() + } else { + builder!!.startObject() + .field(STATUS_FIELD, restStatus) + .fieldIfNotNull(STATUS_TEXT_FIELD, restStatusText) + .field(RETRY_AFTER_FIELD, retryAfter) + .endObject() + } + } +} diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/ReportDefinition.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/ReportDefinition.kt index a5493f4f..c6e90cbd 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/ReportDefinition.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/ReportDefinition.kt @@ -31,7 +31,49 @@ import java.time.Duration /** * Report definition main data class. + *
 JSON format
+ * {@code
+ * {
+ *   "name":"name",
+ *   "isEnabled":true,
+ *   "source":{
+ *      "description":"description",
+ *      "type":"Dashboard", // Dashboard, Visualization, SavedSearch
+ *      "id":"id"
+ *   },
+ *   "format":{
+ *       "duration":"PT1H",
+ *       "fileFormat":"Pdf", // Pdf, Png, Csv
+ *       "limit":1000, // optional
+ *       "header":"optional header",
+ *       "footer":"optional footer"
+ *   },
+ *   "trigger":{
+ *       "triggerType":"OnDemand", // Download, OnDemand, CronSchedule, IntervalSchedule
+ *       "schedule":{ // required when triggerType is CronSchedule or IntervalSchedule
+ *           "cron":{ // required when triggerType is CronSchedule
+ *               "expression":"0 * * * *",
+ *               "timezone":"PST"
+ *           },
+ *           "interval":{ // required when triggerType is IntervalSchedule
+ *               "start_time":1603506908773,
+ *               "period":10",
+ *               "unit":"Minutes"
+ *           }
+ *       }
+ *   },
+ *   "delivery":{ // optional
+ *       "recipients":["banantha@amazon.com"],
+ *       "deliveryFormat":"LinkOnly", // LinkOnly, Attachment, Embedded
+ *       "title":"title",
+ *       "textDescription":"textDescription",
+ *       "htmlDescription":"optional htmlDescription",
+ *       "channelIds":["optional_channelIds"]
+ *   }
+ * }
+ * }
*/ + internal data class ReportDefinition( val name: String, val isEnabled: Boolean, diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/ReportDefinitionDetails.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/ReportDefinitionDetails.kt index e18e203a..78a44303 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/ReportDefinitionDetails.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/ReportDefinitionDetails.kt @@ -22,11 +22,12 @@ import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPl import com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportDefinition.TriggerType import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.CREATED_TIME_FIELD import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.ID_FIELD -import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.OWNER_ID_FIELD import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.REPORT_DEFINITION_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.ROLE_LIST_FIELD import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.UPDATED_TIME_FIELD import com.amazon.opendistroforelasticsearch.reportsscheduler.settings.PluginSettings import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.stringList import org.elasticsearch.common.xcontent.ToXContent import org.elasticsearch.common.xcontent.XContentBuilder import org.elasticsearch.common.xcontent.XContentFactory @@ -36,12 +37,24 @@ import java.time.Instant /** * Wrapper data class over ReportDefinition to add metadata + *
 JSON format
+ * {@code
+ * {
+ *   "id":"id",
+ *   "lastUpdatedTimeMs":1603506908773,
+ *   "createdTimeMs":1603506908773,
+ *   "roles":["sample_role"]
+ *   "reportDefinition":{
+ *      // refer [com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportDefinition]
+ *   }
+ * }
+ * }
*/ internal data class ReportDefinitionDetails( val id: String, val updatedTime: Instant, val createdTime: Instant, - val ownerId: String, + val roles: List, val reportDefinition: ReportDefinition ) : ScheduledJobParameter { internal companion object { @@ -50,13 +63,14 @@ internal data class ReportDefinitionDetails( /** * Parse the data from parser and create ReportDefinitionDetails object * @param parser data referenced at parser + * @param useId use this id if not available in the json * @return created ReportDefinitionDetails object */ fun parse(parser: XContentParser, useId: String? = null): ReportDefinitionDetails { var id: String? = useId var updatedTime: Instant? = null var createdTime: Instant? = null - var createdBy: String? = null + var roles: List = listOf() var reportDefinition: ReportDefinition? = null XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation) while (XContentParser.Token.END_OBJECT != parser.nextToken()) { @@ -66,7 +80,7 @@ internal data class ReportDefinitionDetails( ID_FIELD -> id = parser.text() UPDATED_TIME_FIELD -> updatedTime = Instant.ofEpochMilli(parser.longValue()) CREATED_TIME_FIELD -> createdTime = Instant.ofEpochMilli(parser.longValue()) - OWNER_ID_FIELD -> createdBy = parser.text() + ROLE_LIST_FIELD -> roles = parser.stringList() REPORT_DEFINITION_FIELD -> reportDefinition = ReportDefinition.parse(parser) else -> { parser.skipChildren() @@ -77,12 +91,11 @@ internal data class ReportDefinitionDetails( id ?: throw IllegalArgumentException("$ID_FIELD field absent") updatedTime ?: throw IllegalArgumentException("$UPDATED_TIME_FIELD field absent") createdTime ?: throw IllegalArgumentException("$CREATED_TIME_FIELD field absent") - createdBy ?: throw IllegalArgumentException("$OWNER_ID_FIELD field absent") reportDefinition ?: throw IllegalArgumentException("$REPORT_DEFINITION_FIELD field absent") return ReportDefinitionDetails(id, updatedTime, createdTime, - createdBy, + roles, reportDefinition) } } @@ -106,7 +119,7 @@ internal data class ReportDefinitionDetails( } builder.field(UPDATED_TIME_FIELD, updatedTime.toEpochMilli()) .field(CREATED_TIME_FIELD, createdTime.toEpochMilli()) - .field(OWNER_ID_FIELD, ownerId) + .field(ROLE_LIST_FIELD, roles) builder.field(REPORT_DEFINITION_FIELD) reportDefinition.toXContent(builder, ToXContent.EMPTY_PARAMS) builder.endObject() diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/ReportInstance.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/ReportInstance.kt index 86cf4ad1..50f54034 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/ReportInstance.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/ReportInstance.kt @@ -23,11 +23,12 @@ import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.Plugin import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.ID_FIELD import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.IN_CONTEXT_DOWNLOAD_URL_FIELD import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.REPORT_DEFINITION_DETAILS_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.ROLE_LIST_FIELD import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_FIELD import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_TEXT_FIELD import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.UPDATED_TIME_FIELD -import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.USER_ID_FIELD import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.stringList import org.elasticsearch.common.xcontent.ToXContent import org.elasticsearch.common.xcontent.ToXContentObject import org.elasticsearch.common.xcontent.XContentBuilder @@ -38,6 +39,23 @@ import java.time.Instant /** * Report instance main data class contains ReportDefinitionDetails. + *
 JSON format
+ * {@code
+ * {
+ *   "id":"id",
+ *   "lastUpdatedTimeMs":1603506908773,
+ *   "createdTimeMs":1603506908773,
+ *   "beginTimeMs":1603506908773,
+ *   "endTimeMs":1603506908773,
+ *   "roles":["sample_role"]
+ *   "reportDefinitionDetails":{
+ *      // refer [com.amazon.opendistroforelasticsearch.reportsscheduler.model.reportDefinitionDetails]
+ *   },
+ *   "status":"Success", // Scheduled, Executing, Success, Failed
+ *   "statusText":"Success",
+ *   "inContextDownloadUrlPath":"/app/dashboard#view/dashboard-id",
+ * }
+ * }
*/ internal data class ReportInstance( val id: String, @@ -45,13 +63,13 @@ internal data class ReportInstance( val createdTime: Instant, val beginTime: Instant, val endTime: Instant, - val userId: String, + val roles: List, val reportDefinitionDetails: ReportDefinitionDetails?, - val currentState: State, - val currentStateDescription: String? = null, + val status: Status, + val statusText: String? = null, val inContextDownloadUrlPath: String? = null ) : ToXContentObject { - internal enum class State { Scheduled, Executing, Success, Failed } + internal enum class Status { Scheduled, Executing, Success, Failed } companion object { private val log by logger(ReportInstance::class.java) @@ -67,11 +85,11 @@ internal data class ReportInstance( var createdTime: Instant? = null var beginTime: Instant? = null var endTime: Instant? = null - var userId: String? = null - var currentState: State? = null - var currentStateDescription: String? = null - var inContextDownloadUrlPath: String? = null + var roles: List = listOf() var reportDefinitionDetails: ReportDefinitionDetails? = null + var status: Status? = null + var statusText: String? = null + var inContextDownloadUrlPath: String? = null XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation) while (XContentParser.Token.END_OBJECT != parser.nextToken()) { val fieldName = parser.currentName() @@ -80,13 +98,13 @@ internal data class ReportInstance( ID_FIELD -> id = parser.text() UPDATED_TIME_FIELD -> updatedTime = Instant.ofEpochMilli(parser.longValue()) CREATED_TIME_FIELD -> createdTime = Instant.ofEpochMilli(parser.longValue()) - IN_CONTEXT_DOWNLOAD_URL_FIELD -> inContextDownloadUrlPath = parser.text() BEGIN_TIME_FIELD -> beginTime = Instant.ofEpochMilli(parser.longValue()) END_TIME_FIELD -> endTime = Instant.ofEpochMilli(parser.longValue()) - USER_ID_FIELD -> userId = parser.text() - STATUS_FIELD -> currentState = State.valueOf(parser.text()) - STATUS_TEXT_FIELD -> currentStateDescription = parser.text() + ROLE_LIST_FIELD -> roles = parser.stringList() REPORT_DEFINITION_DETAILS_FIELD -> reportDefinitionDetails = ReportDefinitionDetails.parse(parser) + STATUS_FIELD -> status = Status.valueOf(parser.text()) + STATUS_TEXT_FIELD -> statusText = parser.text() + IN_CONTEXT_DOWNLOAD_URL_FIELD -> inContextDownloadUrlPath = parser.text() else -> { parser.skipChildren() log.info("$LOG_PREFIX:ReportInstance Skipping Unknown field $fieldName") @@ -98,17 +116,16 @@ internal data class ReportInstance( createdTime ?: throw IllegalArgumentException("$CREATED_TIME_FIELD field absent") beginTime ?: throw IllegalArgumentException("$BEGIN_TIME_FIELD field absent") endTime ?: throw IllegalArgumentException("$END_TIME_FIELD field absent") - userId ?: throw IllegalArgumentException("$USER_ID_FIELD field absent") - currentState ?: throw IllegalArgumentException("$STATUS_FIELD field absent") + status ?: throw IllegalArgumentException("$STATUS_FIELD field absent") return ReportInstance(id, updatedTime, createdTime, beginTime, endTime, - userId, + roles, reportDefinitionDetails, - currentState, - currentStateDescription, + status, + statusText, inContextDownloadUrlPath) } } @@ -141,18 +158,18 @@ internal data class ReportInstance( .field(CREATED_TIME_FIELD, createdTime.toEpochMilli()) .field(BEGIN_TIME_FIELD, beginTime.toEpochMilli()) .field(END_TIME_FIELD, endTime.toEpochMilli()) - .field(USER_ID_FIELD, userId) - .field(STATUS_FIELD, currentState.name) - if (currentStateDescription != null) { - builder.field(STATUS_TEXT_FIELD, currentStateDescription) - } - if (inContextDownloadUrlPath != null) { - builder.field(IN_CONTEXT_DOWNLOAD_URL_FIELD, inContextDownloadUrlPath) - } + .field(ROLE_LIST_FIELD, roles) if (reportDefinitionDetails != null) { builder.field(REPORT_DEFINITION_DETAILS_FIELD) reportDefinitionDetails.toXContent(builder, ToXContent.EMPTY_PARAMS, true) } + builder.field(STATUS_FIELD, status.name) + if (statusText != null) { + builder.field(STATUS_TEXT_FIELD, statusText) + } + if (inContextDownloadUrlPath != null) { + builder.field(IN_CONTEXT_DOWNLOAD_URL_FIELD, inContextDownloadUrlPath) + } builder.endObject() return builder } diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/RestErrorResponse.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/RestErrorResponse.kt new file mode 100644 index 00000000..b69f6674 --- /dev/null +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/RestErrorResponse.kt @@ -0,0 +1,91 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.model + +import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.LOG_PREFIX +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_TEXT_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.fieldIfNotNull +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger +import org.elasticsearch.common.xcontent.ToXContent +import org.elasticsearch.common.xcontent.XContentBuilder +import org.elasticsearch.common.xcontent.XContentFactory +import org.elasticsearch.common.xcontent.XContentParser +import org.elasticsearch.common.xcontent.XContentParser.Token +import org.elasticsearch.common.xcontent.XContentParserUtils +import org.elasticsearch.rest.RestStatus + +/** + * Common error response for REST APIs. + *
 JSON format
+ * {@code
+ * {
+ *   "status":405,
+ *   "statusText":"Method not allowed",
+ * }
+ * }
+ */ +internal data class RestErrorResponse( + override val restStatus: RestStatus, + override val restStatusText: String? +) : IRestResponse { + companion object { + private val log by logger(RestErrorResponse::class.java) + + /** + * Parse the data from parser and create [RestErrorResponse] object + * @param parser data referenced at parser + * @return created [RestErrorResponse] object + */ + fun parse(parser: XContentParser): RestErrorResponse { + var restStatus: RestStatus? = null + var statusText: String? = null + XContentParserUtils.ensureExpectedToken(Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation) + while (Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + STATUS_FIELD -> restStatus = RestStatus.fromCode(parser.intValue()) + STATUS_TEXT_FIELD -> statusText = parser.text() + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:Skipping Unknown field $fieldName") + } + } + } + restStatus ?: throw IllegalArgumentException("$STATUS_FIELD field absent") + return RestErrorResponse(restStatus, statusText) + } + } + + /** + * {@inheritDoc} + */ + override fun toXContent(): XContentBuilder { + return toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return builder!!.startObject() + .field(STATUS_FIELD, restStatus) + .fieldIfNotNull(STATUS_TEXT_FIELD, restStatusText) + .endObject() + } +} diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/UpdateReportDefinitionRequest.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/UpdateReportDefinitionRequest.kt new file mode 100644 index 00000000..57951610 --- /dev/null +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/UpdateReportDefinitionRequest.kt @@ -0,0 +1,96 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.model + +import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.LOG_PREFIX +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.REPORT_DEFINITION_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.REPORT_DEFINITION_ID_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger +import org.elasticsearch.common.xcontent.ToXContent +import org.elasticsearch.common.xcontent.ToXContentObject +import org.elasticsearch.common.xcontent.XContentBuilder +import org.elasticsearch.common.xcontent.XContentFactory +import org.elasticsearch.common.xcontent.XContentParser +import org.elasticsearch.common.xcontent.XContentParser.Token +import org.elasticsearch.common.xcontent.XContentParserUtils + +/** + * Report Definition-update request. + * reportDefinitionId is from request query params + *
 JSON format
+ * {@code
+ * {
+ *   "reportDefinitionId":"reportDefinitionId",
+ *   "reportDefinition":{
+ *      // refer [com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportDefinition]
+ *   }
+ * }
+ * }
+ */ +internal data class UpdateReportDefinitionRequest( + val reportDefinitionId: String, + val reportDefinition: ReportDefinition +) : ToXContentObject { + companion object { + private val log by logger(UpdateReportDefinitionRequest::class.java) + + /** + * Parse the data from parser and create [UpdateReportDefinitionRequest] object + * @param parser data referenced at parser + * @param useReportDefinitionId use this id if not available in the json + * @return created [UpdateReportDefinitionRequest] object + */ + fun parse(parser: XContentParser, useReportDefinitionId: String? = null): UpdateReportDefinitionRequest { + var reportDefinitionId: String? = useReportDefinitionId + var reportDefinition: ReportDefinition? = null + XContentParserUtils.ensureExpectedToken(Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation) + while (Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + REPORT_DEFINITION_ID_FIELD -> reportDefinitionId = parser.text() + REPORT_DEFINITION_FIELD -> reportDefinition = ReportDefinition.parse(parser) + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:Skipping Unknown field $fieldName") + } + } + } + reportDefinitionId ?: throw IllegalArgumentException("$REPORT_DEFINITION_ID_FIELD field absent") + reportDefinition ?: throw IllegalArgumentException("$REPORT_DEFINITION_FIELD field absent") + return UpdateReportDefinitionRequest(reportDefinitionId, reportDefinition) + } + } + + /** + * create XContentBuilder from this object using [XContentFactory.jsonBuilder()] + * @return created XContentBuilder object + */ + fun toXContent(): XContentBuilder? { + return toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return builder!!.startObject() + .field(REPORT_DEFINITION_ID_FIELD, reportDefinitionId) + .field(REPORT_DEFINITION_FIELD, reportDefinition) + .endObject() + } +} diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/UpdateReportDefinitionResponse.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/UpdateReportDefinitionResponse.kt new file mode 100644 index 00000000..6958888d --- /dev/null +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/UpdateReportDefinitionResponse.kt @@ -0,0 +1,106 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.model + +import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.LOG_PREFIX +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.REPORT_DEFINITION_ID_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_TEXT_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.fieldIfNotNull +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger +import org.elasticsearch.common.xcontent.ToXContent +import org.elasticsearch.common.xcontent.XContentBuilder +import org.elasticsearch.common.xcontent.XContentFactory +import org.elasticsearch.common.xcontent.XContentParser +import org.elasticsearch.common.xcontent.XContentParser.Token +import org.elasticsearch.common.xcontent.XContentParserUtils +import org.elasticsearch.rest.RestStatus + +/** + * Report Definition-update response. + *
 JSON format
+ * {@code
+ * // On Success
+ * {
+ *   "reportDefinitionId":"reportDefinitionId"
+ * }
+ * // On Failure
+ * {
+ *   "status":500,
+ *   "statusText":"Report Definition Update failed"
+ * }
+ * }
+ */ +internal data class UpdateReportDefinitionResponse( + override val restStatus: RestStatus, + override val restStatusText: String?, + val reportDefinitionId: String? +) : IRestResponse { + companion object { + private val log by logger(UpdateReportDefinitionResponse::class.java) + + /** + * Parse the data from parser and create [UpdateReportDefinitionResponse] object + * @param parser data referenced at parser + * @return created [UpdateReportDefinitionResponse] object + */ + fun parse(parser: XContentParser): UpdateReportDefinitionResponse { + var restStatus: RestStatus = RestStatus.OK + var statusText: String? = null + var reportDefinitionId: String? = null + XContentParserUtils.ensureExpectedToken(Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation) + while (Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + STATUS_FIELD -> restStatus = RestStatus.fromCode(parser.intValue()) + STATUS_TEXT_FIELD -> statusText = parser.text() + REPORT_DEFINITION_ID_FIELD -> reportDefinitionId = parser.text() + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:Skipping Unknown field $fieldName") + } + } + } + return UpdateReportDefinitionResponse(restStatus, statusText, reportDefinitionId) + } + } + + /** + * create XContentBuilder from this object using [XContentFactory.jsonBuilder()] + * @return created XContentBuilder object + */ + override fun toXContent(): XContentBuilder { + return toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return if (reportDefinitionId != null) { + builder!!.startObject() + .field(REPORT_DEFINITION_ID_FIELD, reportDefinitionId) + .endObject() + } else { + builder!!.startObject() + .field(STATUS_FIELD, restStatus) + .fieldIfNotNull(STATUS_TEXT_FIELD, restStatusText) + .endObject() + } + } +} diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/UpdateReportInstanceStatusRequest.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/UpdateReportInstanceStatusRequest.kt new file mode 100644 index 00000000..ef0b5d32 --- /dev/null +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/UpdateReportInstanceStatusRequest.kt @@ -0,0 +1,100 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.model + +import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.LOG_PREFIX +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportInstance.Status +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.REPORT_INSTANCE_ID_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_TEXT_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.fieldIfNotNull +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger +import org.elasticsearch.common.xcontent.ToXContent +import org.elasticsearch.common.xcontent.ToXContentObject +import org.elasticsearch.common.xcontent.XContentBuilder +import org.elasticsearch.common.xcontent.XContentFactory +import org.elasticsearch.common.xcontent.XContentParser +import org.elasticsearch.common.xcontent.XContentParser.Token +import org.elasticsearch.common.xcontent.XContentParserUtils + +/** + * Update report instance status request + *
 JSON format
+ * {@code
+ * {
+ *   "reportInstanceId":"reportInstanceId"
+ *   "status":"Success", // refer [Status]
+ *   "statusText":"Operation completed",
+ * }
+ * }
+ */ +internal data class UpdateReportInstanceStatusRequest( + val reportInstanceId: String, + var status: Status, + var statusText: String? = null +) : ToXContentObject { + companion object { + private val log by logger(UpdateReportInstanceStatusRequest::class.java) + + /** + * Parse the data from parser and create [UpdateReportInstanceStatusRequest] object + * @param parser data referenced at parser + * @return created [UpdateReportInstanceStatusRequest] object + */ + fun parse(parser: XContentParser, useReportInstanceId: String? = null): UpdateReportInstanceStatusRequest { + var reportInstanceId: String? = useReportInstanceId + var status: Status? = null + var statusText: String? = null + XContentParserUtils.ensureExpectedToken(Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation) + while (Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + REPORT_INSTANCE_ID_FIELD -> reportInstanceId = parser.text() + STATUS_FIELD -> status = Status.valueOf(parser.text()) + STATUS_TEXT_FIELD -> statusText = parser.text() + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:Skipping Unknown field $fieldName") + } + } + } + reportInstanceId ?: throw IllegalArgumentException("$REPORT_INSTANCE_ID_FIELD field absent") + status ?: throw IllegalArgumentException("$STATUS_FIELD field absent") + return UpdateReportInstanceStatusRequest(reportInstanceId, status, statusText) + } + } + + /** + * create XContentBuilder from this object using [XContentFactory.jsonBuilder()] + * @return created XContentBuilder object + */ + fun toXContent(): XContentBuilder? { + return toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return builder!!.startObject() + .field(REPORT_INSTANCE_ID_FIELD, reportInstanceId) + .field(STATUS_FIELD, status) + .fieldIfNotNull(STATUS_TEXT_FIELD, statusText) + .endObject() + } +} diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/UpdateReportInstanceStatusResponse.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/UpdateReportInstanceStatusResponse.kt new file mode 100644 index 00000000..42cb7c18 --- /dev/null +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/UpdateReportInstanceStatusResponse.kt @@ -0,0 +1,101 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.model + +import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.LOG_PREFIX +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.REPORT_INSTANCE_ID_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.PluginRestHandler.Companion.STATUS_TEXT_FIELD +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.fieldIfNotNull +import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger +import org.elasticsearch.common.xcontent.ToXContent +import org.elasticsearch.common.xcontent.XContentBuilder +import org.elasticsearch.common.xcontent.XContentFactory +import org.elasticsearch.common.xcontent.XContentParser +import org.elasticsearch.common.xcontent.XContentParser.Token +import org.elasticsearch.common.xcontent.XContentParserUtils +import org.elasticsearch.rest.RestStatus + +/** + * Update report instance status response. + *
 JSON format
+ * {@code
+ * // On Success
+ * {
+ *   "reportInstanceId":"reportInstanceId"
+ * }
+ * // On Failure
+ * {
+ *   "status":404,
+ *   "statusText":"Report Instance not found"
+ * }
+ * }
+ */ +internal data class UpdateReportInstanceStatusResponse( + override val restStatus: RestStatus, + override val restStatusText: String?, + val reportInstanceId: String? +) : IRestResponse { + companion object { + private val log by logger(UpdateReportInstanceStatusResponse::class.java) + + /** + * Parse the data from parser and create [UpdateReportInstanceStatusResponse] object + * @param parser data referenced at parser + * @return created [UpdateReportInstanceStatusResponse] object + */ + fun parse(parser: XContentParser): UpdateReportInstanceStatusResponse { + var restStatus: RestStatus = RestStatus.OK + var statusText: String? = null + var reportInstanceId: String? = null + XContentParserUtils.ensureExpectedToken(Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation) + while (Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + STATUS_FIELD -> restStatus = RestStatus.fromCode(parser.intValue()) + STATUS_TEXT_FIELD -> statusText = parser.text() + REPORT_INSTANCE_ID_FIELD -> reportInstanceId = parser.text() + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:Skipping Unknown field $fieldName") + } + } + } + return UpdateReportInstanceStatusResponse(restStatus, statusText, reportInstanceId) + } + } + + /** + * create XContentBuilder from this object using [XContentFactory.jsonBuilder()] + * @return created XContentBuilder object + */ + override fun toXContent(): XContentBuilder { + return toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return builder!!.startObject() + .field(STATUS_FIELD, restStatus) + .fieldIfNotNull(STATUS_TEXT_FIELD, restStatusText) + .fieldIfNotNull(REPORT_INSTANCE_ID_FIELD, reportInstanceId) + .endObject() + } +} diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/OnDemandReportRestHandler.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/OnDemandReportRestHandler.kt index db9a92d7..3d78d8dd 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/OnDemandReportRestHandler.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/OnDemandReportRestHandler.kt @@ -16,9 +16,12 @@ package com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.BASE_REPORTS_URI -import com.amazon.opendistroforelasticsearch.reportsscheduler.action.ReportInstanceAction +import com.amazon.opendistroforelasticsearch.reportsscheduler.action.ReportInstanceActions +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.IRestResponse +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.InContextReportCreateRequest +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.OnDemandReportCreateRequest +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.RestErrorResponse import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.rest.BytesRestResponse import org.elasticsearch.rest.RestChannel import org.elasticsearch.rest.RestHandler.Route import org.elasticsearch.rest.RestRequest @@ -28,7 +31,7 @@ import org.elasticsearch.rest.RestStatus /** * Rest handler for creating on-demand report instances. - * This handler uses [ReportInstanceAction]. + * This handler uses [ReportInstanceActions]. */ internal class OnDemandReportRestHandler : PluginRestHandler() { companion object { @@ -48,11 +51,20 @@ internal class OnDemandReportRestHandler : PluginRestHandler() { */ override fun routes(): List { return listOf( - // create a new report instance from provided definition - // POST ON_DEMAND_REPORT_URL + /** + * Create a new report instance from provided definition + * Request URL: PUT ON_DEMAND_REPORT_URL + * Request body: Ref [com.amazon.opendistroforelasticsearch.reportsscheduler.model.InContextReportCreateRequest] + * Response body: Ref [com.amazon.opendistroforelasticsearch.reportsscheduler.model.InContextReportCreateResponse] + */ Route(PUT, ON_DEMAND_REPORT_URL), - // create a new report from definition and return instance - // GET ON_DEMAND_REPORT_URL?id= + + /** + * Create a new report from definition and return instance + * Request URL: POST ON_DEMAND_REPORT_URL?id= + * Request body: Ref [com.amazon.opendistroforelasticsearch.reportsscheduler.model.OnDemandReportCreateRequest] + * Response body: Ref [com.amazon.opendistroforelasticsearch.reportsscheduler.model.OnDemandReportCreateResponse] + */ Route(POST, ON_DEMAND_REPORT_URL) ) } @@ -67,15 +79,16 @@ internal class OnDemandReportRestHandler : PluginRestHandler() { /** * {@inheritDoc} */ - override fun executeRequest(request: RestRequest, client: NodeClient, channel: RestChannel) { - val handler = ReportInstanceAction(request, client, channel) - when (request.method()) { - PUT -> handler.createOnDemand() + override fun executeRequest(request: RestRequest, client: NodeClient, channel: RestChannel): IRestResponse { + val parser = request.contentParser() + parser.nextToken() + return when (request.method()) { + PUT -> ReportInstanceActions.createOnDemand(InContextReportCreateRequest.parse(parser)) POST -> { val reportDefinitionId = request.param(ID_FIELD) ?: throw IllegalArgumentException("Must specify id") - handler.createOnDemandFromDefinition(reportDefinitionId) + ReportInstanceActions.createOnDemandFromDefinition(OnDemandReportCreateRequest.parse(parser, reportDefinitionId)) } - else -> channel.sendResponse(BytesRestResponse(RestStatus.METHOD_NOT_ALLOWED, "${request.method()} is not allowed")) + else -> RestErrorResponse(RestStatus.METHOD_NOT_ALLOWED, "${request.method()} is not allowed") } } } diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/PluginRestHandler.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/PluginRestHandler.kt index 2198696d..8838a646 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/PluginRestHandler.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/PluginRestHandler.kt @@ -16,11 +16,14 @@ package com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.LOG_PREFIX +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.IRestResponse import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.elasticsearch.client.node.NodeClient +import org.elasticsearch.common.xcontent.ToXContent +import org.elasticsearch.common.xcontent.XContentType import org.elasticsearch.rest.BaseRestHandler import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer import org.elasticsearch.rest.BytesRestResponse @@ -39,15 +42,16 @@ internal abstract class PluginRestHandler : BaseRestHandler() { const val STATUS_TEXT_FIELD = "statusText" const val UPDATED_TIME_FIELD = "lastUpdatedTimeMs" const val CREATED_TIME_FIELD = "createdTimeMs" - const val OWNER_ID_FIELD = "ownerId" - const val USER_ID_FIELD = "userId" + const val ROLE_LIST_FIELD = "roles" const val REPORT_DEFINITION_LIST_FIELD = "reportDefinitionDetailsList" const val REPORT_INSTANCE_LIST_FIELD = "reportInstanceList" const val REPORT_INSTANCE_FIELD = "reportInstance" + const val REPORT_INSTANCE_ID_FIELD = "reportDefinitionId" const val IN_CONTEXT_DOWNLOAD_URL_FIELD = "inContextDownloadUrlPath" const val BEGIN_TIME_FIELD = "beginTimeMs" const val END_TIME_FIELD = "endTimeMs" const val REPORT_DEFINITION_FIELD = "reportDefinition" + const val REPORT_DEFINITION_ID_FIELD = "reportDefinitionId" const val REPORT_DEFINITION_DETAILS_FIELD = "reportDefinitionDetails" const val FROM_INDEX_FIELD = "fromIndex" const val RETRY_AFTER_FIELD = "retryAfter" @@ -73,7 +77,10 @@ internal abstract class PluginRestHandler : BaseRestHandler() { private fun executeRequestInScope(request: RestRequest, client: NodeClient, channel: RestChannel) { scope.launch { try { - executeRequest(request, client, channel) + val response = executeRequest(request, client, channel) + val contentBuilder = channel.newBuilder(XContentType.JSON, false) + response.toXContent(contentBuilder, ToXContent.EMPTY_PARAMS) + channel.sendResponse(BytesRestResponse(response.restStatus, contentBuilder)) } catch (exception: IllegalArgumentException) { log.warn("executeRequestInScope:", exception) channel.sendResponse(BytesRestResponse(RestStatus.BAD_REQUEST, exception.message)) @@ -96,5 +103,5 @@ internal abstract class PluginRestHandler : BaseRestHandler() { * @param client client for executing actions on the local node * @param channel Rest channel to send response to */ - abstract fun executeRequest(request: RestRequest, client: NodeClient, channel: RestChannel) + abstract fun executeRequest(request: RestRequest, client: NodeClient, channel: RestChannel): IRestResponse } diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportDefinitionListRestHandler.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportDefinitionListRestHandler.kt index 27588531..e5c74caf 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportDefinitionListRestHandler.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportDefinitionListRestHandler.kt @@ -16,9 +16,11 @@ package com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.BASE_REPORTS_URI -import com.amazon.opendistroforelasticsearch.reportsscheduler.action.ReportDefinitionAction +import com.amazon.opendistroforelasticsearch.reportsscheduler.action.ReportDefinitionActions +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.GetAllReportDefinitionsRequest +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.IRestResponse +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.RestErrorResponse import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.rest.BytesRestResponse import org.elasticsearch.rest.RestChannel import org.elasticsearch.rest.RestHandler.Route import org.elasticsearch.rest.RestRequest @@ -27,7 +29,7 @@ import org.elasticsearch.rest.RestStatus /** * Rest handler for getting list of report definitions. - * This handler uses [ReportDefinitionAction]. + * This handler uses [ReportDefinitionActions]. */ internal class ReportDefinitionListRestHandler : PluginRestHandler() { companion object { @@ -47,8 +49,12 @@ internal class ReportDefinitionListRestHandler : PluginRestHandler() { */ override fun routes(): List { return listOf( - // Get all report definitions (from optional fromIndex) - // GET LIST_REPORT_DEFINITIONS_URL[?fromIndex=1000] + /** + * Get all report definitions (from optional fromIndex) + * Request URL: GET LIST_REPORT_DEFINITIONS_URL[?fromIndex=1000] + * Request body: None + * Response body: Ref [com.amazon.opendistroforelasticsearch.reportsscheduler.model.GetAllReportDefinitionsResponse] + */ Route(GET, LIST_REPORT_DEFINITIONS_URL) ) } @@ -63,12 +69,11 @@ internal class ReportDefinitionListRestHandler : PluginRestHandler() { /** * {@inheritDoc} */ - override fun executeRequest(request: RestRequest, client: NodeClient, channel: RestChannel) { + override fun executeRequest(request: RestRequest, client: NodeClient, channel: RestChannel): IRestResponse { val from = request.param(FROM_INDEX_FIELD)?.toIntOrNull() ?: 0 - val handler = ReportDefinitionAction(request, client, channel) - when (request.method()) { - GET -> handler.getAll(from) - else -> channel.sendResponse(BytesRestResponse(RestStatus.METHOD_NOT_ALLOWED, "${request.method()} is not allowed")) + return when (request.method()) { + GET -> ReportDefinitionActions.getAll(GetAllReportDefinitionsRequest(from)) + else -> RestErrorResponse(RestStatus.METHOD_NOT_ALLOWED, "${request.method()} is not allowed") } } } diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportDefinitionRestHandler.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportDefinitionRestHandler.kt index 519cce89..be9c0285 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportDefinitionRestHandler.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportDefinitionRestHandler.kt @@ -16,9 +16,15 @@ package com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.BASE_REPORTS_URI -import com.amazon.opendistroforelasticsearch.reportsscheduler.action.ReportDefinitionAction +import com.amazon.opendistroforelasticsearch.reportsscheduler.action.ReportDefinitionActions +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.CreateReportDefinitionRequest +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.DeleteReportDefinitionRequest +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.GetReportDefinitionRequest +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.IRestResponse +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.RestErrorResponse +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.UpdateReportDefinitionRequest import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.rest.BytesRestResponse +import org.elasticsearch.common.xcontent.XContentParser import org.elasticsearch.rest.RestChannel import org.elasticsearch.rest.RestHandler.Route import org.elasticsearch.rest.RestRequest @@ -30,7 +36,7 @@ import org.elasticsearch.rest.RestStatus /** * Rest handler for report definitions lifecycle management. - * This handler uses [ReportDefinitionAction]. + * This handler uses [ReportDefinitionActions]. */ internal class ReportDefinitionRestHandler : PluginRestHandler() { companion object { @@ -50,17 +56,33 @@ internal class ReportDefinitionRestHandler : PluginRestHandler() { */ override fun routes(): List { return listOf( - // create a new report definition - // POST REPORT_DEFINITION_URL + /** + * Create a new report definition + * Request URL: POST REPORT_DEFINITION_URL + * Request body: Ref [com.amazon.opendistroforelasticsearch.reportsscheduler.model.CreateReportDefinitionRequest] + * Response body: Ref [com.amazon.opendistroforelasticsearch.reportsscheduler.model.CreateReportDefinitionResponse] + */ Route(POST, REPORT_DEFINITION_URL), - // update report definition - // PUT REPORT_DEFINITION_URL?id= + /** + * Update report definition + * Request URL: PUT REPORT_DEFINITION_URL?id= + * Request body: Ref [com.amazon.opendistroforelasticsearch.reportsscheduler.model.UpdateReportDefinitionRequest] + * Response body: Ref [com.amazon.opendistroforelasticsearch.reportsscheduler.model.UpdateReportDefinitionResponse] + */ Route(PUT, REPORT_DEFINITION_URL), - // get a report definition - // GET REPORT_DEFINITION_URL?id= + /** + * Get a report definition + * Request URL: GET REPORT_DEFINITION_URL?id= + * Request body: Ref [com.amazon.opendistroforelasticsearch.reportsscheduler.model.GetReportDefinitionRequest] + * Response body: Ref [com.amazon.opendistroforelasticsearch.reportsscheduler.model.GetReportDefinitionsResponse] + */ Route(GET, REPORT_DEFINITION_URL), - // delete report definition - // DELETE REPORT_DEFINITION_URL?id= + /** + * Delete report definition + * Request URL: DELETE REPORT_DEFINITION_URL?id= + * Request body: Ref [com.amazon.opendistroforelasticsearch.reportsscheduler.model.DeleteReportDefinitionRequest] + * Response body: Ref [com.amazon.opendistroforelasticsearch.reportsscheduler.model.DeleteReportDefinitionResponse] + */ Route(DELETE, REPORT_DEFINITION_URL) ) } @@ -75,14 +97,19 @@ internal class ReportDefinitionRestHandler : PluginRestHandler() { /** * {@inheritDoc} */ - override fun executeRequest(request: RestRequest, client: NodeClient, channel: RestChannel) { - val handler = ReportDefinitionAction(request, client, channel) - when (request.method()) { - POST -> handler.create() - PUT -> handler.update(request.param(ID_FIELD)) - GET -> handler.info(request.param(ID_FIELD)) - DELETE -> handler.delete(request.param(ID_FIELD)) - else -> channel.sendResponse(BytesRestResponse(RestStatus.METHOD_NOT_ALLOWED, "${request.method()} is not allowed")) + override fun executeRequest(request: RestRequest, client: NodeClient, channel: RestChannel): IRestResponse { + return when (request.method()) { + POST -> ReportDefinitionActions.create(CreateReportDefinitionRequest.parse(contentParser(request))) + PUT -> ReportDefinitionActions.update(UpdateReportDefinitionRequest.parse(contentParser(request), request.param(ID_FIELD))) + GET -> ReportDefinitionActions.info(GetReportDefinitionRequest(request.param(ID_FIELD))) + DELETE -> ReportDefinitionActions.delete(DeleteReportDefinitionRequest(request.param(ID_FIELD))) + else -> RestErrorResponse(RestStatus.METHOD_NOT_ALLOWED, "${request.method()} is not allowed") } } + + private fun contentParser(request: RestRequest): XContentParser { + val parser = request.contentParser() + parser.nextToken() + return parser + } } diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportInstanceListRestHandler.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportInstanceListRestHandler.kt index 6e9712da..f98bd746 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportInstanceListRestHandler.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportInstanceListRestHandler.kt @@ -16,9 +16,11 @@ package com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.BASE_REPORTS_URI -import com.amazon.opendistroforelasticsearch.reportsscheduler.action.ReportInstanceAction +import com.amazon.opendistroforelasticsearch.reportsscheduler.action.ReportInstanceActions +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.GetAllReportInstancesRequest +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.IRestResponse +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.RestErrorResponse import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.rest.BytesRestResponse import org.elasticsearch.rest.RestChannel import org.elasticsearch.rest.RestHandler.Route import org.elasticsearch.rest.RestRequest @@ -27,7 +29,7 @@ import org.elasticsearch.rest.RestStatus /** * Rest handler for getting list of report instances. - * This handler uses [ReportInstanceAction]. + * This handler uses [ReportInstanceActions]. */ internal class ReportInstanceListRestHandler : PluginRestHandler() { companion object { @@ -47,8 +49,12 @@ internal class ReportInstanceListRestHandler : PluginRestHandler() { */ override fun routes(): List { return listOf( - // Get all report instances (from optional fromIndex) - // GET LIST_REPORT_INSTANCES_URL[?fromIndex=1000] + /** + * Get all report instances (from optional fromIndex) + * Request URL: GET LIST_REPORT_INSTANCES_URL[?fromIndex=1000] + * Request body: None + * Response body: Ref [com.amazon.opendistroforelasticsearch.reportsscheduler.model.GetAllReportInstancesResponse] + */ Route(GET, LIST_REPORT_INSTANCES_URL) ) } @@ -63,12 +69,11 @@ internal class ReportInstanceListRestHandler : PluginRestHandler() { /** * {@inheritDoc} */ - override fun executeRequest(request: RestRequest, client: NodeClient, channel: RestChannel) { + override fun executeRequest(request: RestRequest, client: NodeClient, channel: RestChannel): IRestResponse { val from = request.param(FROM_INDEX_FIELD)?.toIntOrNull() ?: 0 - val handler = ReportInstanceAction(request, client, channel) - when (request.method()) { - GET -> handler.getAll(from) - else -> channel.sendResponse(BytesRestResponse(RestStatus.METHOD_NOT_ALLOWED, "${request.method()} is not allowed")) + return when (request.method()) { + GET -> ReportInstanceActions.getAll(GetAllReportInstancesRequest(from)) + else -> RestErrorResponse(RestStatus.METHOD_NOT_ALLOWED, "${request.method()} is not allowed") } } } diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportInstancePollRestHandler.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportInstancePollRestHandler.kt index bad2ee08..82b30414 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportInstancePollRestHandler.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportInstancePollRestHandler.kt @@ -16,9 +16,10 @@ package com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.BASE_REPORTS_URI -import com.amazon.opendistroforelasticsearch.reportsscheduler.action.ReportInstanceAction +import com.amazon.opendistroforelasticsearch.reportsscheduler.action.ReportInstanceActions +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.IRestResponse +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.RestErrorResponse import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.rest.BytesRestResponse import org.elasticsearch.rest.RestChannel import org.elasticsearch.rest.RestHandler.Route import org.elasticsearch.rest.RestRequest @@ -27,7 +28,7 @@ import org.elasticsearch.rest.RestStatus /** * Rest handler for getting list of report instances. - * This handler uses [ReportInstanceAction]. + * This handler uses [ReportInstanceActions]. */ internal class ReportInstancePollRestHandler : PluginRestHandler() { companion object { @@ -47,8 +48,12 @@ internal class ReportInstancePollRestHandler : PluginRestHandler() { */ override fun routes(): List { return listOf( - // Poll report instances for pending job - // GET POLL_REPORT_INSTANCE_URL + /** + * Poll report instances for pending job + * Request URL: GET POLL_REPORT_INSTANCE_URL + * Request body: None + * Response body: Ref [com.amazon.opendistroforelasticsearch.reportsscheduler.model.PollReportInstanceResponse] + */ Route(GET, POLL_REPORT_INSTANCE_URL) ) } @@ -63,11 +68,10 @@ internal class ReportInstancePollRestHandler : PluginRestHandler() { /** * {@inheritDoc} */ - override fun executeRequest(request: RestRequest, client: NodeClient, channel: RestChannel) { - val handler = ReportInstanceAction(request, client, channel) - when (request.method()) { - GET -> handler.poll() - else -> channel.sendResponse(BytesRestResponse(RestStatus.METHOD_NOT_ALLOWED, "${request.method()} is not allowed")) + override fun executeRequest(request: RestRequest, client: NodeClient, channel: RestChannel): IRestResponse { + return when (request.method()) { + GET -> ReportInstanceActions.poll() + else -> RestErrorResponse(RestStatus.METHOD_NOT_ALLOWED, "${request.method()} is not allowed") } } } diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportInstanceRestHandler.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportInstanceRestHandler.kt index e7f5469f..c368164c 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportInstanceRestHandler.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportInstanceRestHandler.kt @@ -16,9 +16,12 @@ package com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.BASE_REPORTS_URI -import com.amazon.opendistroforelasticsearch.reportsscheduler.action.ReportInstanceAction +import com.amazon.opendistroforelasticsearch.reportsscheduler.action.ReportInstanceActions +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.GetReportInstanceRequest +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.IRestResponse +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.RestErrorResponse +import com.amazon.opendistroforelasticsearch.reportsscheduler.model.UpdateReportInstanceStatusRequest import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.rest.BytesRestResponse import org.elasticsearch.rest.RestChannel import org.elasticsearch.rest.RestHandler.Route import org.elasticsearch.rest.RestRequest @@ -28,7 +31,7 @@ import org.elasticsearch.rest.RestStatus /** * Rest handler for report instances lifecycle management. - * This handler uses [ReportInstanceAction]. + * This handler uses [ReportInstanceActions]. */ internal class ReportInstanceRestHandler : PluginRestHandler() { companion object { @@ -48,11 +51,19 @@ internal class ReportInstanceRestHandler : PluginRestHandler() { */ override fun routes(): List { return listOf( - // update report instance (only modifiable fields to be present) - // POST REPORT_INSTANCE_URL?id= + /** + * Update report instance status + * Request URL: POST REPORT_INSTANCE_URL?id= + * Request body: Ref [com.amazon.opendistroforelasticsearch.reportsscheduler.model.UpdateReportInstanceStatusRequest] + * Response body: Ref [com.amazon.opendistroforelasticsearch.reportsscheduler.model.UpdateReportInstanceStatusResponse] + */ Route(POST, REPORT_INSTANCE_URL), - // get a report instance information - // GET REPORT_INSTANCE_URL?id= + /** + * Get a report instance information + * Request URL: GET REPORT_INSTANCE_URL?id= + * Request body: None + * Response body: Ref [com.amazon.opendistroforelasticsearch.reportsscheduler.model.GetReportInstanceResponse] + */ Route(GET, REPORT_INSTANCE_URL) ) } @@ -67,13 +78,16 @@ internal class ReportInstanceRestHandler : PluginRestHandler() { /** * {@inheritDoc} */ - override fun executeRequest(request: RestRequest, client: NodeClient, channel: RestChannel) { + override fun executeRequest(request: RestRequest, client: NodeClient, channel: RestChannel): IRestResponse { val reportInstanceId = request.param(ID_FIELD) ?: throw IllegalArgumentException("Must specify id") - val handler = ReportInstanceAction(request, client, channel) - when (request.method()) { - POST -> handler.update(reportInstanceId) - GET -> handler.info(reportInstanceId) - else -> channel.sendResponse(BytesRestResponse(RestStatus.METHOD_NOT_ALLOWED, "${request.method()} is not allowed")) + return when (request.method()) { + POST -> { + val parser = request.contentParser() + parser.nextToken() + ReportInstanceActions.update(UpdateReportInstanceStatusRequest.parse(parser, reportInstanceId)) + } + GET -> ReportInstanceActions.info(GetReportInstanceRequest(reportInstanceId)) + else -> RestErrorResponse(RestStatus.METHOD_NOT_ALLOWED, "${request.method()} is not allowed") } } } diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/scheduler/ReportDefinitionJobRunner.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/scheduler/ReportDefinitionJobRunner.kt index be42ac37..5a97c179 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/scheduler/ReportDefinitionJobRunner.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/scheduler/ReportDefinitionJobRunner.kt @@ -32,6 +32,7 @@ import java.time.Instant internal object ReportDefinitionJobRunner : ScheduledJobRunner { private val log by logger(ReportDefinitionJobRunner::class.java) private val scope: CoroutineScope = CoroutineScope(Dispatchers.IO) + private const val TEMP_ROLE_ID = "roleId" // TODO get this from request override fun runJob(job: ScheduledJobParameter, context: JobExecutionContext) { if (job !is ReportDefinitionDetails) { @@ -48,9 +49,9 @@ internal object ReportDefinitionJobRunner : ScheduledJobRunner { currentTime, beginTime, endTime, - reportDefinitionDetails.ownerId, + listOf(TEMP_ROLE_ID), reportDefinitionDetails, - ReportInstance.State.Scheduled) + ReportInstance.Status.Scheduled) val id = IndexManager.createReportInstance(reportInstance) if (id == null) { log.warn("$LOG_PREFIX:runJob-job creation failed for $reportInstance") diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/util/Helpers.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/util/Helpers.kt index 59f3e167..bb317739 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/util/Helpers.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/util/Helpers.kt @@ -18,18 +18,37 @@ package com.amazon.opendistroforelasticsearch.reportsscheduler.util import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.Logger +import org.elasticsearch.common.xcontent.ToXContent +import org.elasticsearch.common.xcontent.ToXContentObject +import org.elasticsearch.common.xcontent.XContentBuilder import org.elasticsearch.common.xcontent.XContentParser +import org.elasticsearch.common.xcontent.XContentParser.Token import org.elasticsearch.common.xcontent.XContentParserUtils internal fun XContentParser.stringList(): List { val retList: MutableList = mutableListOf() - XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_ARRAY, currentToken(), this::getTokenLocation) - while (nextToken() != XContentParser.Token.END_ARRAY) { + XContentParserUtils.ensureExpectedToken(Token.START_ARRAY, currentToken(), this::getTokenLocation) + while (nextToken() != Token.END_ARRAY) { retList.add(text()) } return retList } -fun logger(forClass: Class): Lazy { +internal fun logger(forClass: Class): Lazy { return lazy { LogManager.getLogger(forClass) } } + +internal fun XContentBuilder.fieldIfNotNull(name: String, value: Any?): XContentBuilder { + if (value != null) { + this.field(name, value) + } + return this +} + +internal fun XContentBuilder.objectIfNotNull(name: String, xContentObject: ToXContentObject?): XContentBuilder { + if (xContentObject != null) { + this.field(name) + xContentObject.toXContent(this, ToXContent.EMPTY_PARAMS) + } + return this +} diff --git a/reports-scheduler/src/main/resources/report-definitions-mapping.yml b/reports-scheduler/src/main/resources/report-definitions-mapping.yml index 414c140e..0f08fc96 100644 --- a/reports-scheduler/src/main/resources/report-definitions-mapping.yml +++ b/reports-scheduler/src/main/resources/report-definitions-mapping.yml @@ -16,7 +16,7 @@ ## # Schema file for the report definitions index -# Since we only search based on "ownerId", sort on lastUpdatedTimeMs & createdTimeMs, +# Since we only search based on "roles", sort on lastUpdatedTimeMs & createdTimeMs, # other fields are not used in mapping to avoid index on those fields. # Also "dynamic" is set to "false" so that other fields can be added. dynamic: false @@ -27,7 +27,7 @@ properties: createdTimeMs: type: date format: epoch_millis - ownerId: + roles: # Array of role name type: keyword reportDefinition: type: object diff --git a/reports-scheduler/src/main/resources/report-instances-mapping.yml b/reports-scheduler/src/main/resources/report-instances-mapping.yml index 34ad50cf..df7d16d3 100644 --- a/reports-scheduler/src/main/resources/report-instances-mapping.yml +++ b/reports-scheduler/src/main/resources/report-instances-mapping.yml @@ -16,7 +16,7 @@ ## # Schema file for the report instances index -# Since we only search based on "userId" & currentState, sort on lastUpdatedTimeMs & createdTimeMs, +# Since we only search based on "roles" & currentState, sort on lastUpdatedTimeMs & createdTimeMs, # other fields are not used in mapping to avoid index on those fields. # Also "dynamic" is set to "false" so that other fields can be added. dynamic: false @@ -27,7 +27,7 @@ properties: createdTimeMs: type: date format: epoch_millis - userId: + roles: # Array of role name type: keyword status: type: keyword