Don't use EELFLoggerDelegate.errorLogger in Async jobs
[vid.git] / vid-app-common / src / main / java / org / onap / vid / job / command / ResourceCommand.kt
1 /*-
2  * ============LICENSE_START=======================================================
3  * VID
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
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
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=========================================================
19  */
20
21 package org.onap.vid.job.command
22
23
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.mso.RestMsoImplementation
37 import org.onap.vid.mso.model.ModelInfo
38 import org.onap.vid.utils.JACKSON_OBJECT_MAPPER
39 import org.onap.vid.utils.getEnumFromMapOfStrings
40 import org.springframework.http.HttpMethod
41 import java.util.*
42
43
44 const val INTERNAL_STATE = "internalState"
45 const val ACTION_PHASE = "actionPhase"
46 const val CHILD_JOBS = "childJobs"
47 const val MSO_RESOURCE_ID = "msoResourceIds"
48 const val CUMULATIVE_STATUS = "cumulativeStatus"
49
50 enum class InternalState constructor(val immediate:Boolean=false) {
51     INITIAL,
52     CREATING_CHILDREN(true),
53     WATCHING,
54     DELETE_MYSELF,
55     CREATE_MYSELF,
56     IN_PROGRESS,
57     TERMINAL,
58     RESUME_MYSELF,
59     REPLACE_MYSELF,
60 }
61
62 data class NextInternalState(val nextActionPhase: Action, val nextInternalState: InternalState)
63
64
65 data class MsoRestCallPlan(
66         val httpMethod: HttpMethod,
67         val path: String,
68         val payload: Optional<RequestDetailsWrapper<out Any>>,
69         val userId: Optional<String>,
70         val actionDescription: String
71 )
72
73 abstract class ResourceCommand(
74         protected val restMso: RestMsoImplementation,
75         protected val inProgressStatusService: InProgressStatusService,
76         protected val msoResultHandlerService: MsoResultHandlerService,
77         protected val watchChildrenJobsBL: WatchChildrenJobsBL,
78         private val jobsBrokerService: JobsBrokerService,
79         private val jobAdapter: JobAdapter
80         ) : CommandBase(), JobCommand {
81
82     companion object {
83         private val Logger = EELFLoggerDelegate.getLogger(ResourceCommand::class.java)
84     }
85
86     abstract fun createChildren():JobStatus
87
88     abstract fun planCreateMyselfRestCall(commandParentData: CommandParentData, request: JobAdapter.AsyncJobRequest, userId: String, testApi: String?): MsoRestCallPlan
89
90     abstract fun planDeleteMyselfRestCall(commandParentData: CommandParentData, request: JobAdapter.AsyncJobRequest, userId: String): MsoRestCallPlan
91
92     private val commandByInternalState: Map<InternalState, () -> JobStatus> = hashMapOf(
93             Pair(InternalState.CREATING_CHILDREN, ::createChildren),
94             Pair(InternalState.WATCHING, ::watchChildren),
95             Pair(InternalState.CREATE_MYSELF, ::createMyself),
96             Pair(InternalState.RESUME_MYSELF, ::resumeMyself),
97             Pair(InternalState.DELETE_MYSELF, ::deleteMyself),
98             Pair(InternalState.IN_PROGRESS, ::inProgress),
99             Pair(InternalState.REPLACE_MYSELF, ::replaceMyself)
100     )
101
102     private lateinit var internalState:InternalState
103     protected lateinit var actionPhase: Action
104     protected var commandParentData: CommandParentData = CommandParentData()
105     protected var msoResourceIds: MsoResourceIds = EMPTY_MSO_RESOURCE_ID
106     protected var childJobs:List<String> = emptyList()
107     private lateinit var cumulativeStatus:JobStatus
108
109
110     override fun call(): NextCommand {
111         var jobStatus:JobStatus = if (internalState!=InternalState.TERMINAL) invokeCommand() else cumulativeStatus
112         jobStatus = comulateStatusAndUpdatePropertyIfFinal(jobStatus)
113
114         try {
115             Logger.debug(EELFLoggerDelegate.debugLogger, "job: ${this.javaClass.simpleName} ${sharedData.jobUuid} $actionPhase ${getActionType()} $internalState $jobStatus $childJobs")
116         } catch (e:Exception) { /* do nothing. Just failed to log...*/}
117
118         if (shallStopJob(jobStatus)) {
119             onFinal(jobStatus)
120             return NextCommand(jobStatus)
121         }
122
123         val (nextActionPhase, nextInternalState) = calcNextInternalState(jobStatus, internalState, actionPhase)
124         Logger.debug(EELFLoggerDelegate.debugLogger, "next state for job ${sharedData.jobUuid} is $nextInternalState")
125         actionPhase = nextActionPhase
126         internalState = nextInternalState
127
128         if (internalState==InternalState.TERMINAL) {
129             onFinal(jobStatus)
130             return NextCommand(jobStatus)
131         }
132
133         jobStatus = getExternalInProgressStatus()
134         Logger.debug(EELFLoggerDelegate.debugLogger, "next status for job ${sharedData.jobUuid} is $jobStatus")
135 //        if (internalState.immediate) return call() //shortcut instead of execute another command
136         return NextCommand(jobStatus, this)
137     }
138
139     //we want to stop in faliures, except for service witn no action, since service with no action trigger 2 phases (delete and create)
140     protected fun shallStopJob(jobStatus: JobStatus) =
141             jobStatus.isFailure && !(isServiceCommand() && getActionType()==Action.None)
142
143     //this method is used to expose the job status after successful completion of current state
144     //should be override by subclass (like ServiceCommand) that need to return other default job status
145     protected open fun getExternalInProgressStatus() = JobStatus.RESOURCE_IN_PROGRESS
146
147     private fun invokeCommand(): JobStatus {
148         return try {
149             commandByInternalState.getOrDefault(internalState, ::throwIllegalState).invoke()
150         }
151         catch (exception: TryAgainException) {
152             Logger.warn("caught TryAgainException. Set job status to IN_PROGRESS")
153             JobStatus.IN_PROGRESS
154         }
155         catch (exception: AbortingException) {
156             Logger.error("caught AbortingException. Set job status to FAILED")
157             JobStatus.FAILED;
158         }
159     }
160
161     private fun throwIllegalState():JobStatus {
162              throw IllegalStateException("can't find action for pashe $actionPhase and state $internalState")
163     }
164
165     private fun calcNextInternalState(jobStatus: JobStatus, internalState: InternalState, actionPhase: Action): NextInternalState {
166
167         val nextInternalState = when (actionPhase) {
168             Action.Delete -> calcNextStateDeletePhase(jobStatus, internalState)
169             Action.Create -> calcNextStateCreatePhase(jobStatus, internalState)
170             else -> InternalState.TERMINAL
171         }
172
173         if (nextInternalState == InternalState.TERMINAL
174                 && actionPhase == Action.Delete
175                 && isServiceCommand()) {
176             // Loop over to "Create" phase
177             return NextInternalState(Action.Create, InternalState.INITIAL)
178         }
179
180         return NextInternalState(actionPhase, nextInternalState)
181
182     }
183
184     //no need to refer to failed (final) states here
185     //This method is called only for non final states or COMPLETED
186     protected fun calcNextStateDeletePhase(jobStatus: JobStatus, internalState: InternalState): InternalState {
187         return when (internalState) {
188
189             InternalState.CREATING_CHILDREN -> InternalState.WATCHING
190
191             InternalState.WATCHING -> {
192                 when {
193                     !jobStatus.isFinal -> InternalState.WATCHING
194                     isNeedToDeleteMyself() -> InternalState.DELETE_MYSELF
195                     else -> InternalState.TERMINAL
196                 }
197             }
198
199             InternalState.DELETE_MYSELF -> InternalState.IN_PROGRESS
200
201             InternalState.IN_PROGRESS -> {
202                 if (jobStatus == JobStatus.COMPLETED) InternalState.TERMINAL else InternalState.IN_PROGRESS
203             }
204
205             else -> InternalState.TERMINAL
206         }
207     }
208
209     protected fun calcNextStateCreatePhase(jobStatus: JobStatus, internalState: InternalState): InternalState {
210         return when (internalState) {
211
212             InternalState.CREATE_MYSELF -> when (jobStatus) {
213                 JobStatus.IN_PROGRESS -> InternalState.CREATE_MYSELF
214                 else -> InternalState.IN_PROGRESS
215             }
216
217             InternalState.RESUME_MYSELF -> when (jobStatus) {
218                 JobStatus.IN_PROGRESS -> InternalState.RESUME_MYSELF
219                 else -> InternalState.IN_PROGRESS
220             }
221
222             InternalState.REPLACE_MYSELF -> when (jobStatus) {
223                 JobStatus.IN_PROGRESS -> InternalState.REPLACE_MYSELF
224                 else -> InternalState.IN_PROGRESS
225             }
226
227             InternalState.IN_PROGRESS -> {
228                 when {
229                     jobStatus != JobStatus.COMPLETED -> InternalState.IN_PROGRESS
230                     isDescendantHasAction(Action.Create) -> InternalState.CREATING_CHILDREN
231                     isDescendantHasAction(Action.Upgrade) -> InternalState.CREATING_CHILDREN
232                     else -> InternalState.TERMINAL
233                 }
234             }
235
236             InternalState.CREATING_CHILDREN -> InternalState.WATCHING
237
238             InternalState.WATCHING -> {
239                 when {
240                     !jobStatus.isFinal -> InternalState.WATCHING
241                     else -> InternalState.TERMINAL
242                 }
243             }
244
245             else -> InternalState.TERMINAL
246         }
247     }
248
249     override fun getData(): Map<String, Any?> {
250         return mapOf(
251                 ACTION_PHASE to actionPhase,
252                 INTERNAL_STATE to internalState,
253                 MSO_RESOURCE_ID to msoResourceIds,
254                 CHILD_JOBS to childJobs,
255                 CUMULATIVE_STATUS to cumulativeStatus
256         ) + commandParentData.parentData
257     }
258
259     override fun init(sharedData: JobSharedData, commandData: Map<String, Any>): ResourceCommand {
260         init(sharedData)
261         val resourceIdsRaw:Any? = commandData[MSO_RESOURCE_ID]
262         commandParentData.initParentData(commandData)
263         msoResourceIds =
264                 if (resourceIdsRaw != null) JACKSON_OBJECT_MAPPER.convertValue(resourceIdsRaw)
265                 else EMPTY_MSO_RESOURCE_ID
266
267         childJobs = JACKSON_OBJECT_MAPPER.convertValue(commandData.getOrDefault(CHILD_JOBS, emptyList<String>()))
268         cumulativeStatus = getEnumFromMapOfStrings(commandData, CUMULATIVE_STATUS, JobStatus.COMPLETED_WITH_NO_ACTION)
269         actionPhase = getEnumFromMapOfStrings(commandData, ACTION_PHASE, Action.Delete)
270         internalState = calcInitialState(commandData, actionPhase)
271         return this
272     }
273
274     fun calcInitialState(commandData: Map<String, Any>, phase: Action):InternalState {
275         val status:InternalState = getEnumFromMapOfStrings(commandData, INTERNAL_STATE, InternalState.INITIAL)
276         if (status == InternalState.INITIAL) {
277             onInitial(phase)
278             return when (phase) {
279                 Action.Delete -> when {
280                     isDescendantHasAction(phase) -> InternalState.CREATING_CHILDREN
281                     isNeedToDeleteMyself() -> InternalState.DELETE_MYSELF
282                     else -> InternalState.TERMINAL
283                 }
284                 Action.Create -> when {
285                     isNeedToCreateMyself() -> InternalState.CREATE_MYSELF
286                     isNeedToResumeMySelf() -> InternalState.RESUME_MYSELF
287                     isNeedToReplaceMySelf() -> InternalState.REPLACE_MYSELF
288                     isDescendantHasAction(phase) -> InternalState.CREATING_CHILDREN
289                     isDescendantHasAction(Action.Upgrade) -> InternalState.CREATING_CHILDREN
290                     else -> InternalState.TERMINAL
291                 }
292                 else -> throw IllegalStateException("state $internalState is not supported yet")
293             }
294         }
295         return status
296     }
297
298     //command may override it in order to do something while init state
299     protected open fun onInitial(phase: Action) {
300         //do nothing
301     }
302
303     //command may override it in order to do something while final status
304     protected open fun onFinal(jobStatus: JobStatus) {
305         //do nothing
306     }
307
308     protected open fun getRequest(): BaseResource {
309         return sharedData.request as BaseResource
310     }
311
312     protected open fun getActionType(): Action {
313         return getRequest().action
314     }
315
316     protected open fun isServiceCommand(): Boolean = false
317
318     protected open fun isNeedToDeleteMyself(): Boolean = getActionType() == Action.Delete
319
320     protected open fun isNeedToCreateMyself(): Boolean = getActionType() == Action.Create
321
322     protected open fun isNeedToResumeMySelf(): Boolean = getActionType() == Action.Resume
323
324     protected open fun isNeedToReplaceMySelf(): Boolean = false
325
326     protected open fun inProgress(): JobStatus {
327         val requestId:String = msoResourceIds.requestId;
328         return try {
329             val jobStatus = inProgressStatusService.call(getExpiryChecker(), sharedData, requestId)
330             handleInProgressStatus(jobStatus)
331         } catch (e: javax.ws.rs.ProcessingException) {
332             // Retry when we can't connect MSO during getStatus
333             Logger.error("Cannot get orchestration status for {}, will retry: {}", requestId, e, e)
334             JobStatus.IN_PROGRESS;
335         } catch (e: InProgressStatusService.BadResponseFromMso) {
336             inProgressStatusService.handleFailedMsoResponse(sharedData.jobUuid, requestId, e.msoResponse)
337             JobStatus.IN_PROGRESS
338         } catch (e: RuntimeException) {
339             Logger.error("Cannot get orchestration status for {}, stopping: {}", requestId, e, e)
340             JobStatus.STOPPED
341         }
342     }
343
344     fun createMyself(): JobStatus {
345         val createMyselfCommand = planCreateMyselfRestCall(commandParentData, sharedData.request, sharedData.userId, sharedData.testApi)
346         return executeAndHandleMsoInstanceRequest(createMyselfCommand)
347     }
348
349     protected open fun resumeMyself(): JobStatus {
350         throw NotImplementedError("Resume is not implemented for this command " + this.javaClass)
351     }
352
353     protected open fun replaceMyself(): JobStatus {
354         throw NotImplementedError("Replace is not implemented for this command " + this.javaClass)
355     }
356
357     fun deleteMyself(): JobStatus {
358         val deleteMyselfCommand = planDeleteMyselfRestCall(commandParentData, sharedData.request, sharedData.userId)
359         return executeAndHandleMsoInstanceRequest(deleteMyselfCommand)
360     }
361
362     protected fun executeAndHandleMsoInstanceRequest(restCallPlan: MsoRestCallPlan): JobStatus {
363         val msoResponse = restMso.restCall(
364                 restCallPlan.httpMethod,
365                 RequestReferencesContainer::class.java,
366                 restCallPlan.payload.orElse(null),
367                 restCallPlan.path,
368                 restCallPlan.userId
369         )
370
371         val msoResult = if (isServiceCommand()) {
372             msoResultHandlerService.handleRootResponse(sharedData, msoResponse)
373         } else {
374             msoResultHandlerService.handleResponse(sharedData, msoResponse, restCallPlan.actionDescription)
375         }
376
377         this.msoResourceIds = msoResult.msoResourceIds
378         return msoResult.jobStatus
379     }
380
381     protected open fun getExpiryChecker(): ExpiryChecker = ExpiryChecker {false}
382
383     protected open fun handleInProgressStatus(jobStatus: JobStatus): JobStatus {
384         return if (jobStatus == JobStatus.PAUSE) JobStatus.IN_PROGRESS else jobStatus
385     }
386
387     protected open fun watchChildren():JobStatus {
388         return watchChildrenJobsBL.retrieveChildrenJobsStatus(childJobs)
389     }
390
391     protected fun comulateStatusAndUpdatePropertyIfFinal(internalStateStatus: JobStatus): JobStatus {
392         val status = watchChildrenJobsBL.cumulateJobStatus(internalStateStatus, cumulativeStatus)
393
394         //we want to update cumulativeStatus only for final status
395         if (status.isFinal) {
396             cumulativeStatus = status;
397         }
398
399         return status
400     }
401
402     protected fun buildDataForChild(request: BaseResource, actionPhase: Action): Map<String, Any> {
403         addMyselfToChildrenData(commandParentData, request)
404         commandParentData.setActionPhase(actionPhase)
405         return commandParentData.parentData
406     }
407
408     protected fun serviceModelInfoFromRequest(): ModelInfo = commandParentData.getModelInfo(CommandParentData.CommandDataKey.SERVICE_MODEL_INFO)
409     protected fun serviceInstanceIdFromRequest(): String = commandParentData.getInstanceId(CommandParentData.CommandDataKey.SERVICE_INSTANCE_ID)
410
411     protected open fun addMyselfToChildrenData(commandParentData: CommandParentData, request: BaseResource) {
412         // Nothing by default
413     }
414
415     protected open fun isDescendantHasAction(phase:Action):Boolean = isDescendantHasAction(getRequest(), phase, true )
416
417
418     @JvmOverloads
419     fun isDescendantHasAction(request: BaseResource, phase: Action, isFirstLevel:Boolean=true): Boolean {
420         if (!isFirstLevel && request.action == phase) {
421             return true;
422         }
423
424         return request.children.map {this.isDescendantHasAction(it, phase, false)}.any {it}
425     }
426
427     protected fun getActualInstanceId(request: BaseResource):String =
428             if (getActionType() == Action.Create) msoResourceIds.instanceId else request.instanceId
429
430
431     protected fun pushChildrenJobsToBroker(children:Collection<BaseResource>,
432                                            dataForChild: Map<String, Any>,
433                                            jobType: JobType?=null): List<String> {
434         return  setPositionWhereIsMissing(children)
435                 .map { jobAdapter.createChildJob(jobType ?: it.first.jobType, it.first, sharedData, dataForChild, it.second) }
436                 .map { jobsBrokerService.add(it) }
437                 .map { it.toString() }
438     }
439
440     protected fun setPositionWhereIsMissing(children: Collection<BaseResource>): List<Pair<BaseResource, Int>> {
441         var orderingPosition = children.map{ defaultIfNull(it.position, 0) }.max() ?: 0
442         return  children
443                 .map {Pair(it, it.position ?: ++orderingPosition)}
444     }
445 }
446
447
448