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