2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017 - 2019 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ============LICENSE_END=========================================================
21 package org.onap.vid.job.command
24 import com.fasterxml.jackson.module.kotlin.convertValue
25 import org.apache.commons.lang3.ObjectUtils.defaultIfNull
26 import org.onap.portalsdk.core.logging.logic.EELFLoggerDelegate
27 import org.onap.vid.changeManagement.RequestDetailsWrapper
28 import org.onap.vid.exceptions.AbortingException
29 import org.onap.vid.exceptions.TryAgainException
30 import org.onap.vid.job.*
31 import org.onap.vid.job.Job.JobStatus
32 import org.onap.vid.job.impl.JobSharedData
33 import org.onap.vid.model.Action
34 import org.onap.vid.model.RequestReferencesContainer
35 import org.onap.vid.model.serviceInstantiation.BaseResource
36 import org.onap.vid.model.serviceInstantiation.BaseResource.PauseInstantiation.afterCompletion
37 import org.onap.vid.mso.RestMsoImplementation
38 import org.onap.vid.mso.model.ModelInfo
39 import org.onap.vid.properties.Features
40 import org.onap.vid.utils.JACKSON_OBJECT_MAPPER
41 import org.onap.vid.utils.getEnumFromMapOfStrings
42 import org.springframework.http.HttpMethod
43 import org.togglz.core.manager.FeatureManager
47 const val INTERNAL_STATE = "internalState"
48 const val ACTION_PHASE = "actionPhase"
49 const val CHILD_JOBS = "childJobs"
50 const val MSO_RESOURCE_ID = "msoResourceIds"
51 const val CUMULATIVE_STATUS = "cumulativeStatus"
53 enum class InternalState constructor(val immediate:Boolean=false) {
55 CREATING_CHILDREN(true),
65 data class NextInternalState(val nextActionPhase: Action, val nextInternalState: InternalState)
68 data class MsoRestCallPlan(
69 val httpMethod: HttpMethod,
71 val payload: Optional<RequestDetailsWrapper<out Any>>,
72 val userId: Optional<String>,
73 val actionDescription: String
76 abstract class ResourceCommand(
77 protected val restMso: RestMsoImplementation,
78 protected val inProgressStatusService: InProgressStatusService,
79 protected val msoResultHandlerService: MsoResultHandlerService,
80 protected val watchChildrenJobsBL: WatchChildrenJobsBL,
81 private val jobsBrokerService: JobsBrokerService,
82 private val jobAdapter: JobAdapter,
83 private val featureManager: FeatureManager
84 ) : CommandBase(), JobCommand {
87 private val Logger = EELFLoggerDelegate.getLogger(ResourceCommand::class.java)
90 abstract fun createChildren():JobStatus
92 abstract fun planCreateMyselfRestCall(commandParentData: CommandParentData, request: JobAdapter.AsyncJobRequest, userId: String, testApi: String?): MsoRestCallPlan
94 abstract fun planDeleteMyselfRestCall(commandParentData: CommandParentData, request: JobAdapter.AsyncJobRequest, userId: String): MsoRestCallPlan
96 private val commandByInternalState: Map<InternalState, () -> JobStatus> = hashMapOf(
97 Pair(InternalState.CREATING_CHILDREN, ::createChildren),
98 Pair(InternalState.WATCHING, ::watchChildren),
99 Pair(InternalState.CREATE_MYSELF, ::createMyself),
100 Pair(InternalState.RESUME_MYSELF, ::resumeMyself),
101 Pair(InternalState.DELETE_MYSELF, ::deleteMyself),
102 Pair(InternalState.IN_PROGRESS, ::inProgress),
103 Pair(InternalState.REPLACE_MYSELF, ::replaceMyself)
106 private lateinit var internalState:InternalState
107 protected lateinit var actionPhase: Action
108 protected var commandParentData: CommandParentData = CommandParentData()
109 protected var msoResourceIds: MsoResourceIds = EMPTY_MSO_RESOURCE_ID
110 protected var childJobs:List<String> = emptyList()
111 private lateinit var cumulativeStatus:JobStatus
114 override fun call(): NextCommand {
115 var jobStatus:JobStatus = if (internalState!=InternalState.TERMINAL) invokeCommand() else cumulativeStatus
116 jobStatus = comulateStatusAndUpdatePropertyIfFinal(jobStatus)
119 Logger.debug(EELFLoggerDelegate.debugLogger, "job: ${this.javaClass.simpleName} ${sharedData.jobUuid} $actionPhase ${getActionType()} $internalState $jobStatus $childJobs")
120 } catch (e:Exception) { /* do nothing. Just failed to log...*/}
122 if (shallStopJob(jobStatus)) {
124 return NextCommand(jobStatus)
127 val (nextActionPhase, nextInternalState) = calcNextInternalState(jobStatus, internalState, actionPhase)
128 Logger.debug(EELFLoggerDelegate.debugLogger, "next state for job ${sharedData.jobUuid} is $nextInternalState")
129 actionPhase = nextActionPhase
130 internalState = nextInternalState
132 if (internalState==InternalState.TERMINAL) {
134 return NextCommand(jobStatus)
137 jobStatus = getExternalInProgressStatus()
138 Logger.debug(EELFLoggerDelegate.debugLogger, "next status for job ${sharedData.jobUuid} is $jobStatus")
139 // if (internalState.immediate) return call() //shortcut instead of execute another command
140 return NextCommand(jobStatus, this)
143 //we want to stop in faliures, except for service witn no action, since service with no action trigger 2 phases (delete and create)
144 protected fun shallStopJob(jobStatus: JobStatus) =
145 jobStatus.isFailure && !(isServiceCommand() && getActionType()==Action.None)
147 //this method is used to expose the job status after successful completion of current state
148 //should be override by subclass (like ServiceCommand) that need to return other default job status
149 protected open fun getExternalInProgressStatus() = JobStatus.RESOURCE_IN_PROGRESS
151 private fun invokeCommand(): JobStatus {
153 commandByInternalState.getOrDefault(internalState, ::throwIllegalState).invoke()
155 catch (exception: TryAgainException) {
156 Logger.warn("caught TryAgainException. Set job status to IN_PROGRESS")
157 JobStatus.IN_PROGRESS
159 catch (exception: AbortingException) {
160 Logger.error("caught AbortingException. Set job status to FAILED")
165 private fun throwIllegalState():JobStatus {
166 throw IllegalStateException("can't find action for pashe $actionPhase and state $internalState")
169 private fun calcNextInternalState(jobStatus: JobStatus, internalState: InternalState, actionPhase: Action): NextInternalState {
171 val nextInternalState = when (actionPhase) {
172 Action.Delete -> calcNextStateDeletePhase(jobStatus, internalState)
173 Action.Create -> calcNextStateCreatePhase(jobStatus, internalState)
174 else -> InternalState.TERMINAL
177 if (nextInternalState == InternalState.TERMINAL
178 && actionPhase == Action.Delete
179 && isServiceCommand()) {
180 // Loop over to "Create" phase
181 return NextInternalState(Action.Create, InternalState.INITIAL)
184 return NextInternalState(actionPhase, nextInternalState)
188 //no need to refer to failed (final) states here
189 //This method is called only for non final states or COMPLETED
190 protected fun calcNextStateDeletePhase(jobStatus: JobStatus, internalState: InternalState): InternalState {
191 return when (internalState) {
193 InternalState.CREATING_CHILDREN -> InternalState.WATCHING
195 InternalState.WATCHING -> {
197 !jobStatus.isFinal -> InternalState.WATCHING
198 isNeedToDeleteMyself() -> InternalState.DELETE_MYSELF
199 else -> InternalState.TERMINAL
203 InternalState.DELETE_MYSELF -> InternalState.IN_PROGRESS
205 InternalState.IN_PROGRESS -> {
206 if (jobStatus == JobStatus.COMPLETED) InternalState.TERMINAL else InternalState.IN_PROGRESS
209 else -> InternalState.TERMINAL
213 protected fun calcNextStateCreatePhase(jobStatus: JobStatus, internalState: InternalState): InternalState {
214 return when (internalState) {
216 InternalState.CREATE_MYSELF -> when (jobStatus) {
217 JobStatus.IN_PROGRESS -> InternalState.CREATE_MYSELF
218 else -> InternalState.IN_PROGRESS
221 InternalState.RESUME_MYSELF -> when (jobStatus) {
222 JobStatus.IN_PROGRESS -> InternalState.RESUME_MYSELF
223 else -> InternalState.IN_PROGRESS
226 InternalState.REPLACE_MYSELF -> when (jobStatus) {
227 JobStatus.IN_PROGRESS -> InternalState.REPLACE_MYSELF
228 else -> InternalState.IN_PROGRESS
231 InternalState.IN_PROGRESS -> {
233 jobStatus != JobStatus.COMPLETED -> InternalState.IN_PROGRESS
234 isDescendantHasAction(Action.Create) -> InternalState.CREATING_CHILDREN
235 isDescendantHasAction(Action.Upgrade) -> InternalState.CREATING_CHILDREN
236 else -> InternalState.TERMINAL
240 InternalState.CREATING_CHILDREN -> InternalState.WATCHING
242 InternalState.WATCHING -> {
244 !jobStatus.isFinal -> InternalState.WATCHING
245 else -> InternalState.TERMINAL
249 else -> InternalState.TERMINAL
253 override fun getData(): Map<String, Any?> {
255 ACTION_PHASE to actionPhase,
256 INTERNAL_STATE to internalState,
257 MSO_RESOURCE_ID to msoResourceIds,
258 CHILD_JOBS to childJobs,
259 CUMULATIVE_STATUS to cumulativeStatus
260 ) + commandParentData.parentData
263 override fun init(sharedData: JobSharedData, commandData: Map<String, Any>): ResourceCommand {
265 val resourceIdsRaw:Any? = commandData[MSO_RESOURCE_ID]
266 commandParentData.initParentData(commandData)
268 if (resourceIdsRaw != null) JACKSON_OBJECT_MAPPER.convertValue(resourceIdsRaw)
269 else EMPTY_MSO_RESOURCE_ID
271 childJobs = JACKSON_OBJECT_MAPPER.convertValue(commandData.getOrDefault(CHILD_JOBS, emptyList<String>()))
272 cumulativeStatus = getEnumFromMapOfStrings(commandData, CUMULATIVE_STATUS, JobStatus.COMPLETED_WITH_NO_ACTION)
273 actionPhase = getEnumFromMapOfStrings(commandData, ACTION_PHASE, Action.Delete)
274 internalState = calcInitialState(commandData, actionPhase)
278 fun calcInitialState(commandData: Map<String, Any>, phase: Action):InternalState {
279 val status:InternalState = getEnumFromMapOfStrings(commandData, INTERNAL_STATE, InternalState.INITIAL)
280 if (status == InternalState.INITIAL) {
282 return when (phase) {
283 Action.Delete -> when {
284 isDescendantHasAction(phase) -> InternalState.CREATING_CHILDREN
285 isNeedToDeleteMyself() -> InternalState.DELETE_MYSELF
286 else -> InternalState.TERMINAL
288 Action.Create -> when {
289 isNeedToCreateMyself() -> InternalState.CREATE_MYSELF
290 isNeedToResumeMySelf() -> InternalState.RESUME_MYSELF
291 isNeedToReplaceMySelf() -> InternalState.REPLACE_MYSELF
292 isDescendantHasAction(phase) -> InternalState.CREATING_CHILDREN
293 isDescendantHasAction(Action.Upgrade) -> InternalState.CREATING_CHILDREN
294 else -> InternalState.TERMINAL
296 else -> throw IllegalStateException("state $internalState is not supported yet")
302 //command may override it in order to do something while init state
303 protected open fun onInitial(phase: Action) {
307 //command may override it in order to do something while final status
308 protected open fun onFinal(jobStatus: JobStatus) {
312 protected open fun getRequest(): BaseResource {
313 return sharedData.request as BaseResource
316 protected open fun getActionType(): Action {
317 return getRequest().action
320 protected open fun isServiceCommand(): Boolean = false
322 protected open fun isNeedToDeleteMyself(): Boolean = getActionType() == Action.Delete
324 protected open fun isNeedToCreateMyself(): Boolean = getActionType() == Action.Create
326 protected open fun isNeedToResumeMySelf(): Boolean = getActionType() == Action.Resume
328 protected open fun isNeedToReplaceMySelf(): Boolean = false
330 protected open fun inProgress(): JobStatus {
331 val requestId:String = msoResourceIds.requestId;
333 val jobStatus = inProgressStatusService.call(getExpiryChecker(), sharedData, requestId)
334 handleInProgressStatus(jobStatus)
335 } catch (e: javax.ws.rs.ProcessingException) {
336 // Retry when we can't connect MSO during getStatus
337 Logger.error("Cannot get orchestration status for {}, will retry: {}", requestId, e, e)
338 JobStatus.IN_PROGRESS;
339 } catch (e: InProgressStatusService.BadResponseFromMso) {
340 inProgressStatusService.handleFailedMsoResponse(sharedData.jobUuid, requestId, e.msoResponse)
341 JobStatus.IN_PROGRESS
342 } catch (e: RuntimeException) {
343 Logger.error("Cannot get orchestration status for {}, stopping: {}", requestId, e, e)
348 fun createMyself(): JobStatus {
349 val createMyselfCommand = planCreateMyselfRestCall(commandParentData, sharedData.request, sharedData.userId, sharedData.testApi)
350 return executeAndHandleMsoInstanceRequest(createMyselfCommand)
353 protected open fun resumeMyself(): JobStatus {
354 throw NotImplementedError("Resume is not implemented for this command " + this.javaClass)
357 protected open fun replaceMyself(): JobStatus {
358 throw NotImplementedError("Replace is not implemented for this command " + this.javaClass)
361 fun deleteMyself(): JobStatus {
362 val deleteMyselfCommand = planDeleteMyselfRestCall(commandParentData, sharedData.request, sharedData.userId)
363 return executeAndHandleMsoInstanceRequest(deleteMyselfCommand)
366 protected fun executeAndHandleMsoInstanceRequest(restCallPlan: MsoRestCallPlan): JobStatus {
367 val msoResponse = restMso.restCall(
368 restCallPlan.httpMethod,
369 RequestReferencesContainer::class.java,
370 restCallPlan.payload.orElse(null),
375 val msoResult = if (isServiceCommand()) {
376 msoResultHandlerService.handleRootResponse(sharedData, msoResponse)
378 msoResultHandlerService.handleResponse(sharedData, msoResponse, restCallPlan.actionDescription)
381 this.msoResourceIds = msoResult.msoResourceIds
382 return msoResult.jobStatus
385 protected open fun getExpiryChecker(): ExpiryChecker = ExpiryChecker {false}
387 protected open fun handleInProgressStatus(jobStatus: JobStatus): JobStatus {
388 return if (jobStatus == JobStatus.PAUSE) JobStatus.IN_PROGRESS else jobStatus
391 protected open fun watchChildren():JobStatus {
392 return watchChildrenJobsBL.retrieveChildrenJobsStatus(childJobs)
395 protected fun comulateStatusAndUpdatePropertyIfFinal(internalStateStatus: JobStatus): JobStatus {
396 val status = watchChildrenJobsBL.cumulateJobStatus(internalStateStatus, cumulativeStatus)
398 //we want to update cumulativeStatus only for final status
399 if (status.isFinal) {
400 cumulativeStatus = status;
406 protected fun buildDataForChild(request: BaseResource, actionPhase: Action): Map<String, Any> {
407 addMyselfToChildrenData(commandParentData, request)
408 commandParentData.setActionPhase(actionPhase)
409 return commandParentData.parentData
412 protected fun serviceModelInfoFromRequest(): ModelInfo = commandParentData.getModelInfo(CommandParentData.CommandDataKey.SERVICE_MODEL_INFO)
413 protected fun serviceInstanceIdFromRequest(): String = commandParentData.getInstanceId(CommandParentData.CommandDataKey.SERVICE_INSTANCE_ID)
415 protected open fun addMyselfToChildrenData(commandParentData: CommandParentData, request: BaseResource) {
416 // Nothing by default
419 protected open fun isDescendantHasAction(phase:Action):Boolean = isDescendantHasAction(getRequest(), phase, true )
423 fun isDescendantHasAction(request: BaseResource, phase: Action, isFirstLevel:Boolean=true): Boolean {
424 if (!isFirstLevel && request.action == phase) {
428 return request.children.map {this.isDescendantHasAction(it, phase, false)}.any {it}
431 protected fun getActualInstanceId(request: BaseResource):String =
432 if (getActionType() == Action.Create) msoResourceIds.instanceId else request.instanceId
435 protected fun pushChildrenJobsToBroker(children:Collection<BaseResource>,
436 dataForChild: Map<String, Any>,
437 jobType: JobType?=null): List<String> {
438 return setPositionWhereIsMissing(children)
439 .map { jobAdapter.createChildJob(jobType ?: it.first.jobType, it.first, sharedData, dataForChild, it.second) }
440 .map { jobsBrokerService.add(it) }
441 .map { it.toString() }
444 protected fun setPositionWhereIsMissing(children: Collection<BaseResource>): List<Pair<BaseResource, Int>> {
445 var orderingPosition = children.map{ defaultIfNull(it.position, 0) }.max() ?: 0
447 .map {Pair(it, it.position ?: ++orderingPosition)}