0540efe800db03776bec9d4e2fccdaa9c6968f3c
[ccsdk/cds.git] / components / model-catalog / blueprint-model / uat-blueprints / PNF_CDS_RESTCONF / Scripts / kotlin / RestconfSoftwareUpgrade.kt
1 /*
2 * ============LICENSE_START=======================================================
3 *  Copyright (C) 2020 Nordix Foundation.
4 * ================================================================================
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *      http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 * ============LICENSE_END=========================================================
17  */
18
19
20 package cba.pnf.swug
21
22 import com.fasterxml.jackson.databind.node.ObjectNode
23 import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput
24 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.contentFromResolvedArtifactNB
25 import org.onap.ccsdk.cds.blueprintsprocessor.functions.restconf.executor.restconfApplyDeviceConfig
26 import org.onap.ccsdk.cds.blueprintsprocessor.functions.restconf.executor.restconfDeviceConfig
27 import org.onap.ccsdk.cds.blueprintsprocessor.functions.restconf.executor.restconfMountDevice
28 import org.onap.ccsdk.cds.blueprintsprocessor.functions.restconf.executor.restconfUnMountDevice
29 import org.onap.ccsdk.cds.blueprintsprocessor.rest.restClientService
30 import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService
31 import org.onap.ccsdk.cds.blueprintsprocessor.services.execution.AbstractScriptComponentFunction
32 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintException
33 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintRetryException
34 import org.onap.ccsdk.cds.controllerblueprints.core.logger
35 import org.onap.ccsdk.cds.controllerblueprints.core.service.BluePrintDependencyService
36 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
37
38 class RestconfSoftwareUpgrade : AbstractScriptComponentFunction() {
39
40     private val RESTCONF_SERVER_IDENTIFIER = "sdnc"
41     private val CONFIGLET_RESOURCE_PATH = "yang-ext:mount/pnf-sw-upgrade:software-upgrade"
42     private val log = logger(AbstractScriptComponentFunction::class.java)
43     private val TARGET_SOFTWARE_PATH = "$CONFIGLET_RESOURCE_PATH/upgrade-package/"
44
45     override suspend fun processNB(executionRequest: ExecutionServiceInput) {
46
47         // Extract request properties
48         val model= validatedPayload(executionRequest)
49
50         log.info("Blueprint invoked for ${model.resolutionKey} for SW Upgrade : " +
51             "${model.action} for sw version ${model.targetSwVersion} on pnf: ${model.deviceId}")
52
53         try {
54             val mountPayload = contentFromResolvedArtifactNB("mount-node")
55             log.debug("Mount Payload : $mountPayload")
56             restconfMountDevice(model.client, model.deviceId, mountPayload, mutableMapOf("Content-Type" to "application/json"))
57
58             when (model.action) {
59                 Action.PRE_CHECK -> processPreCheck(model)
60                 Action.DOWNLOAD_NE_SW -> processDownloadNESw(model)
61                 Action.ACTIVATE_NE_SW -> processActivateNESw(model)
62                 Action.POST_CHECK -> processPostCheck(model)
63                 Action.CANCEL -> processCancel(model)
64             }
65
66         } catch (err: Exception) {
67             log.error("an error occurred while configuring device {}", err)
68         } finally {
69             restconfUnMountDevice(model.client, model.deviceId, "")
70         }
71     }
72
73     private fun validatedPayload(executionRequest: ExecutionServiceInput): SoftwareUpgradeModel {
74         val properties = requestPayloadActionProperty(executionRequest.actionIdentifiers.actionName + "-properties")!!.get(0)
75         if(!properties?.get("pnf-id")?.textValue().isNullOrEmpty() &&
76             !properties?.get("target-software-version")?.textValue().isNullOrEmpty()) {
77             return SoftwareUpgradeModel(getDynamicProperties("resolution-key").asText(),
78                 BluePrintDependencyService.restClientService(RESTCONF_SERVER_IDENTIFIER),
79                 properties.get("pnf-id").textValue(), properties.get("target-software-version").textValue(),
80                 Action.getEnumFromActionName(executionRequest.actionIdentifiers.actionName))
81         }else{
82             throw BluePrintException("Invalid parameters sent to CDS. Request parameters pnf-id or target-software-version missing")
83         }
84     }
85
86     private suspend fun processPreCheck(model: SoftwareUpgradeModel) {
87         log.debug("In PNF SW upgrade : processPreCheck")
88         //Log the current configuration for the subtree
89         val payloadObject = getCurrentConfig(model)
90         log.debug("Current sw version on pnf : ${payloadObject.get("software-upgrade")?.get("upgrade-package")?.get(0)?.get("software-version")?.asText()}")
91         log.info("PNF is Healthy!")
92     }
93
94     private suspend fun processDownloadNESw(model: SoftwareUpgradeModel) {
95         log.debug("In PNF SW upgrade : processDownloadNESw")
96         //Check if there is existing config for the targeted software version
97
98         var downloadConfigPayload: String
99         if (checkIfSwReadyToPerformAction(Action.PRE_CHECK, model)) {
100             downloadConfigPayload = contentFromResolvedArtifactNB("configure")
101             downloadConfigPayload =downloadConfigPayload.replace("%id%", model.yangId)
102         }
103         else {
104             downloadConfigPayload = contentFromResolvedArtifactNB("download-ne-sw")
105             model.yangId=model.targetSwVersion
106         }
107         downloadConfigPayload = downloadConfigPayload.replace("%actionName%", Action.DOWNLOAD_NE_SW.name)
108         log.info("Config Payload to start download : $downloadConfigPayload")
109
110         //Apply configlet
111         restconfApplyDeviceConfig(model.client, model.deviceId, CONFIGLET_RESOURCE_PATH, downloadConfigPayload,
112             mutableMapOf("Content-Type" to "application/yang.patch+json"))
113
114         //Poll PNF for Download action's progress
115         checkExecution(model)
116     }
117
118     private suspend fun processActivateNESw(model: SoftwareUpgradeModel) {
119         log.debug("In PNF SW upgrade : processActivateNESw")
120         //Check if the software is downloaded and ready to be activated
121         if (checkIfSwReadyToPerformAction(Action.DOWNLOAD_NE_SW, model)) {
122             var activateConfigPayload: String = contentFromResolvedArtifactNB("configure")
123             activateConfigPayload = activateConfigPayload.replace("%actionName%", Action.ACTIVATE_NE_SW.name)
124             activateConfigPayload = activateConfigPayload.replace("%id%", model.yangId)
125             log.info("Config Payload to start activate : $activateConfigPayload")
126             //Apply configlet
127             restconfApplyDeviceConfig(model.client, model.deviceId, CONFIGLET_RESOURCE_PATH, activateConfigPayload,
128                 mutableMapOf("Content-Type" to "application/yang.patch+json"))
129
130             //Poll PNF for Activate action's progress
131             checkExecution(model)
132         } else {
133             throw BluePrintRetryException("Software Download not completed for device(${model.deviceId}) to activate sw version: ${model.targetSwVersion}")
134         }
135     }
136
137     private suspend fun processPostCheck(model: SoftwareUpgradeModel) {
138         log.info("In PNF SW upgrade : processPostCheck")
139         //Log the current configuration for the subtree
140         if (checkIfSwReadyToPerformAction(Action.POST_CHECK, model)) {
141             log.info("PNF is healthy post activation!")
142         }
143     }
144
145     private fun processCancel(model :SoftwareUpgradeModel) {
146         //This is for future implementation of cancel step during software upgrade
147         log.info("In PNF SW upgrade : processCancel")
148     }
149
150     private suspend fun getCurrentConfig(model: SoftwareUpgradeModel) : ObjectNode{
151         val currentConfig: BlueprintWebClientService.WebClientResponse<String> = restconfDeviceConfig(model.client, model.deviceId, CONFIGLET_RESOURCE_PATH)
152         return JacksonUtils.jsonNode(currentConfig.body) as ObjectNode
153     }
154     private suspend fun checkExecution(model: SoftwareUpgradeModel) {
155         val checkExecutionBlock: suspend (Int) -> String = {
156             val result = restconfDeviceConfig(model.client, model.deviceId, TARGET_SOFTWARE_PATH.plus(model.yangId))
157             if (result.body.contains(model.action.completionStatus)) {
158                 log.info("${model.action.name} is complete")
159                 result.body
160             } else {
161                 throw BluePrintRetryException("Waiting for device(${model.deviceId}) to activate sw version ${model.targetSwVersion}")
162             }
163         }
164         model.client.retry<String>(10, 0, 1000, checkExecutionBlock)
165
166     }
167
168     private suspend fun checkIfSwReadyToPerformAction(action : Action, model: SoftwareUpgradeModel): Boolean {
169         val configBody = getCurrentConfig(model)
170         configBody.get("software-upgrade")?.get("upgrade-package")?.iterator()?.forEach { item ->
171             if (model.targetSwVersion == item.get("software-version")?.asText() &&
172                 action.completionStatus == item?.get("current-status")?.asText()) {
173                 model.yangId= item.get("id").textValue()
174                 return true
175             }
176         }
177         return false
178     }
179
180     override suspend fun recoverNB(runtimeException: RuntimeException, executionRequest: ExecutionServiceInput) {
181         log.info("Recover function called!")
182         log.info("Execution request : $executionRequest")
183         log.error("Exception", runtimeException)
184     }
185 }
186
187 enum class Action(val actionName: String, val completionStatus: String) {
188     PRE_CHECK("preCheck", "INITIALIZED"),
189     DOWNLOAD_NE_SW("downloadNESw", "DOWNLOAD_COMPLETED"),
190     ACTIVATE_NE_SW("activateNESw", "ACTIVATION_COMPLETED"),
191     POST_CHECK("postCheck", "ACTIVATION_COMPLETED"),
192     CANCEL("cancel", "CANCELLED")
193     ;
194     companion object{
195         fun getEnumFromActionName(name: String): Action {
196             for(value in values()){
197                 if (value.actionName==name) return value
198             }
199             throw BluePrintException("Invalid Action sent to CDS")
200         }
201     }
202 }
203
204 data class SoftwareUpgradeModel(val resolutionKey: String, val client: BlueprintWebClientService, val deviceId: String,
205                                 val targetSwVersion: String, val action: Action, var yangId: String = "")