7f3a05be8a09501689c128738ae647f55d0cc4e7
[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 import com.fasterxml.jackson.module.kotlin.convertValue
24 import org.onap.portalsdk.core.logging.logic.EELFLoggerDelegate
25 import org.onap.vid.changeManagement.RequestDetailsWrapper
26 import org.onap.vid.job.Job
27 import org.onap.vid.job.Job.JobStatus
28 import org.onap.vid.job.JobAdapter
29 import org.onap.vid.job.JobCommand
30 import org.onap.vid.job.NextCommand
31 import org.onap.vid.job.impl.JobSharedData
32 import org.onap.vid.model.Action
33 import org.onap.vid.model.RequestReferencesContainer
34 import org.onap.vid.model.serviceInstantiation.BaseResource
35 import org.onap.vid.mso.RestMsoImplementation
36 import org.onap.vid.utils.JACKSON_OBJECT_MAPPER
37 import org.onap.vid.utils.getEnumFromMapOfStrings
38 import org.springframework.http.HttpMethod
39 import java.util.*
40
41 const val INTERNAL_STATE = "internalState"
42 const val ACTION_PHASE = "actionPhase"
43 const val CHILD_JOBS = "childJobs"
44 const val MSO_RESOURCE_ID = "msoResourceIds"
45 const val CUMULATIVE_STATUS = "cumulativeStatus"
46
47 enum class InternalState constructor(val immediate:Boolean=false) {
48     INITIAL,
49     CREATING_CHILDREN(true),
50     WATCHING,
51     DELETE_MYSELF,
52     CREATE_MYSELF,
53     IN_PROGRESS,
54     TERMINAL
55 }
56
57 data class NextInternalState(val nextActionPhase: Action, val nextInternalState: InternalState)
58
59
60 data class MsoRestCallPlan(
61         val httpMethod: HttpMethod,
62         val path: String,
63         val payload: Optional<RequestDetailsWrapper<out Any>>,
64         val userId: Optional<String>,
65         val actionDescription: String
66 )
67
68 abstract class ResourceCommand(
69         protected val restMso: RestMsoImplementation,
70         protected val inProgressStatusService: InProgressStatusService,
71         protected val msoResultHandlerService: MsoResultHandlerService,
72         protected val watchChildrenJobsBL: WatchChildrenJobsBL
73 ) : CommandBase(), JobCommand {
74
75     companion object {
76         private val Logger = EELFLoggerDelegate.getLogger(ResourceCommand::class.java)
77     }
78
79     abstract fun createChildren():JobStatus
80
81     abstract fun planCreateMyselfRestCall(commandParentData: CommandParentData, request: JobAdapter.AsyncJobRequest, userId: String): MsoRestCallPlan
82
83     abstract fun planDeleteMyselfRestCall(commandParentData: CommandParentData, request: JobAdapter.AsyncJobRequest, userId: String): MsoRestCallPlan
84
85     private val commandByInternalState: Map<InternalState, () -> JobStatus> = hashMapOf(
86             Pair(InternalState.CREATING_CHILDREN, ::createChildren),
87             Pair(InternalState.WATCHING, ::watchChildren),
88             Pair(InternalState.CREATE_MYSELF, ::createMyself),
89             Pair(InternalState.DELETE_MYSELF, ::deleteMyself),
90             Pair(InternalState.IN_PROGRESS, ::inProgress)
91     )
92
93     private lateinit var internalState:InternalState
94     protected lateinit var actionPhase: Action
95     private var commandParentData: CommandParentData = CommandParentData()
96     private var msoResourceIds: MsoResourceIds = EMPTY_MSO_RESOURCE_ID
97     protected var childJobs:List<String> = emptyList()
98     private lateinit var cumulativeStatus:JobStatus
99
100
101     override fun call(): NextCommand {
102         var jobStatus:JobStatus = invokeCommand()
103         jobStatus = comulateStatusAndUpdatePropertyIfFinal(jobStatus)
104
105         Logger.debug("command for job ${sharedData.jobUuid} invoked and finished with jobStatus $jobStatus")
106         if (shallStopJob(jobStatus)) {
107             onFinal(jobStatus)
108             return NextCommand(jobStatus)
109         }
110
111         val (nextActionPhase, nextInternalState) = calcNextInternalState(jobStatus, internalState, actionPhase)
112         Logger.debug("next state for job ${sharedData.jobUuid} is $nextInternalState")
113         actionPhase = nextActionPhase
114         internalState = nextInternalState
115
116         if (internalState==InternalState.TERMINAL) {
117             onFinal(jobStatus)
118             return NextCommand(jobStatus)
119         }
120
121         jobStatus = getExternalInProgressStatus()
122         Logger.debug("next status for job ${sharedData.jobUuid} is $jobStatus")
123 //        if (internalState.immediate) return call() //shortcut instead of execute another command
124         return NextCommand(jobStatus, this)
125     }
126
127     //we want to stop in faliures, except for service witn no action, since service with no action trigger 2 phases (delete and create)
128     protected fun shallStopJob(jobStatus: JobStatus) =
129             jobStatus.isFailure && !(isServiceCommand() && getActionType()==Action.None)
130
131     //this method is used to expose the job status after successful completion of current state
132     //should be override by subclass (like ServiceCommand) that need to return other default job status
133     protected open fun getExternalInProgressStatus() = JobStatus.RESOURCE_IN_PROGRESS
134
135     private fun invokeCommand(): JobStatus {
136         return commandByInternalState.getOrDefault (internalState, ::throwIllegalState).invoke()
137     }
138
139     private fun throwIllegalState():JobStatus {
140              throw IllegalStateException("can't find action for pashe $actionPhase and state $internalState")
141     }
142
143     private fun calcNextInternalState(jobStatus: JobStatus, internalState: InternalState, actionPhase: Action): NextInternalState {
144
145         val nextInternalState = when (actionPhase) {
146             Action.Delete -> calcNextStateDeletePhase(jobStatus, internalState)
147             Action.Create -> calcNextStateCreatePhase(jobStatus, internalState)
148             else -> InternalState.TERMINAL
149         }
150
151         if (nextInternalState == InternalState.TERMINAL
152                 && actionPhase == Action.Delete
153                 && isServiceCommand()) {
154             // Loop over to "Create" phase
155             return NextInternalState(Action.Create, InternalState.INITIAL)
156         }
157
158         return NextInternalState(actionPhase, nextInternalState)
159
160     }
161
162     //no need to refer to failed (final) states here
163     //This method is called only for non final states or COMPLETED
164     protected fun calcNextStateDeletePhase(jobStatus: JobStatus, internalState: InternalState): InternalState {
165         return when (internalState) {
166
167             InternalState.CREATING_CHILDREN -> InternalState.WATCHING
168
169             InternalState.WATCHING -> {
170                 when {
171                     !jobStatus.isFinal -> InternalState.WATCHING
172                     isNeedToDeleteMyself() -> InternalState.DELETE_MYSELF
173                     else -> InternalState.TERMINAL
174                 }
175             }
176
177             InternalState.DELETE_MYSELF -> InternalState.IN_PROGRESS
178
179             InternalState.IN_PROGRESS -> {
180                 if (jobStatus == Job.JobStatus.COMPLETED) InternalState.TERMINAL else InternalState.IN_PROGRESS
181             }
182
183             else -> InternalState.TERMINAL
184         }
185     }
186
187     protected fun calcNextStateCreatePhase(jobStatus: JobStatus, internalState: InternalState): InternalState {
188         return when (internalState) {
189
190             InternalState.CREATE_MYSELF -> InternalState.IN_PROGRESS
191
192             InternalState.IN_PROGRESS -> {
193                 if (jobStatus == Job.JobStatus.COMPLETED) InternalState.CREATING_CHILDREN else InternalState.IN_PROGRESS
194             }
195
196             InternalState.CREATING_CHILDREN -> InternalState.WATCHING
197
198             InternalState.WATCHING -> {
199                 when {
200                     !jobStatus.isFinal -> InternalState.WATCHING
201                     else -> InternalState.TERMINAL
202                 }
203             }
204
205
206             else -> InternalState.TERMINAL
207         }
208     }
209
210     override fun getData(): Map<String, Any?> {
211         return mapOf(
212                 ACTION_PHASE to actionPhase,
213                 INTERNAL_STATE to internalState,
214                 MSO_RESOURCE_ID to msoResourceIds,
215                 CHILD_JOBS to childJobs,
216                 CUMULATIVE_STATUS to cumulativeStatus
217         )
218     }
219
220     override fun init(sharedData: JobSharedData, commandData: Map<String, Any>): ResourceCommand {
221         init(sharedData)
222         val resourceIdsRaw:Any? = commandData[MSO_RESOURCE_ID]
223         commandParentData.initParentData(commandData)
224         msoResourceIds =
225                 if (resourceIdsRaw != null) JACKSON_OBJECT_MAPPER.convertValue(resourceIdsRaw)
226                 else EMPTY_MSO_RESOURCE_ID
227
228         childJobs = JACKSON_OBJECT_MAPPER.convertValue(commandData.getOrDefault(CHILD_JOBS, emptyList<String>()))
229         cumulativeStatus = getEnumFromMapOfStrings(commandData, CUMULATIVE_STATUS, JobStatus.COMPLETED_WITH_NO_ACTION)
230         actionPhase = getEnumFromMapOfStrings(commandData, ACTION_PHASE, Action.Delete)
231         internalState = calcInitialState(commandData, actionPhase)
232         return this
233     }
234
235     private fun calcInitialState(commandData: Map<String, Any>, phase: Action):InternalState {
236         val status:InternalState = getEnumFromMapOfStrings(commandData, INTERNAL_STATE, InternalState.INITIAL)
237         if (status == InternalState.INITIAL) {
238             onInitial(phase)
239             return when (phase) {
240                 Action.Delete -> InternalState.CREATING_CHILDREN
241                 Action.Create -> if (isNeedToCreateMyself()) InternalState.CREATE_MYSELF else InternalState.CREATING_CHILDREN
242                 else -> throw IllegalStateException("state $internalState is not supported yet")
243             }
244         }
245         return status
246     }
247
248     //command may override it in order to do something while init state
249     protected open fun onInitial(phase: Action) {
250         //do nothing
251     }
252
253     //command may override it in order to do something while final status
254     protected open fun onFinal(jobStatus: JobStatus) {
255         //do nothing
256     }
257
258     protected open fun getRequest(): BaseResource {
259         return sharedData.request as BaseResource
260     }
261
262     protected open fun getActionType(): Action {
263         return getRequest().action
264     }
265
266     protected open fun isServiceCommand(): Boolean = false
267
268     protected open fun isNeedToDeleteMyself(): Boolean = getActionType() == Action.Delete
269
270     protected open fun isNeedToCreateMyself(): Boolean = getActionType() == Action.Create
271
272     protected open fun inProgress(): Job.JobStatus {
273         val requestId:String = msoResourceIds.requestId;
274         return try {
275             val jobStatus = inProgressStatusService.call(getExpiryChecker(), sharedData, requestId)
276             handleInProgressStatus(jobStatus)
277         } catch (e: javax.ws.rs.ProcessingException) {
278             // Retry when we can't connect MSO during getStatus
279             Logger.error(EELFLoggerDelegate.errorLogger, "Cannot get orchestration status for {}, will retry: {}", requestId, e, e)
280             Job.JobStatus.IN_PROGRESS;
281         } catch (e: InProgressStatusService.BadResponseFromMso) {
282             inProgressStatusService.handleFailedMsoResponse(sharedData.jobUuid, requestId, e.msoResponse)
283             Job.JobStatus.IN_PROGRESS
284         } catch (e: RuntimeException) {
285             Logger.error(EELFLoggerDelegate.errorLogger, "Cannot get orchestration status for {}, stopping: {}", requestId, e, e)
286             Job.JobStatus.STOPPED
287         }
288     }
289
290     fun createMyself(): Job.JobStatus {
291         val createMyselfCommand = planCreateMyselfRestCall(commandParentData, sharedData.request, sharedData.userId)
292
293         return executeAndHandleMsoInstanceRequest(createMyselfCommand)
294     }
295
296     fun deleteMyself(): Job.JobStatus {
297         val deleteMyselfCommand = planDeleteMyselfRestCall(commandParentData, sharedData.request, sharedData.userId)
298
299         return executeAndHandleMsoInstanceRequest(deleteMyselfCommand)
300     }
301
302     private fun executeAndHandleMsoInstanceRequest(restCallPlan: MsoRestCallPlan): JobStatus {
303         val msoResponse = restMso.restCall(
304                 restCallPlan.httpMethod,
305                 RequestReferencesContainer::class.java,
306                 restCallPlan.payload.orElse(null),
307                 restCallPlan.path,
308                 restCallPlan.userId
309         )
310
311         val msoResult = if (isServiceCommand()) {
312             msoResultHandlerService.handleRootResponse(sharedData.jobUuid, msoResponse)
313         } else {
314             msoResultHandlerService.handleResponse(msoResponse, restCallPlan.actionDescription)
315         }
316
317         this.msoResourceIds = msoResult.msoResourceIds
318         return msoResult.jobStatus
319     }
320
321     protected open fun getExpiryChecker(): ExpiryChecker = ExpiryChecker {false}
322
323     protected open fun handleInProgressStatus(jobStatus: JobStatus): JobStatus {
324         return  if (jobStatus == Job.JobStatus.PAUSE) Job.JobStatus.IN_PROGRESS else jobStatus
325     }
326
327     protected open fun watchChildren():JobStatus {
328         return watchChildrenJobsBL.retrieveChildrenJobsStatus(childJobs)
329     }
330
331     private fun comulateStatusAndUpdatePropertyIfFinal(internalStateStatus: JobStatus): JobStatus {
332         val status = watchChildrenJobsBL.cumulateJobStatus(internalStateStatus, cumulativeStatus)
333
334         //we want to update cumulativeStatus only for final status
335         if (status.isFinal) {
336             cumulativeStatus = status;
337         }
338
339         return status
340     }
341 }
342
343
344