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