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