Merge SW Upgrade Blueprint into PNF_AAI and create one UAT BP for PNF
[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 properties = requestPayloadActionProperty(executionRequest.actionIdentifiers.actionName + "-properties")!!.get(0)
49         val model= SoftwareUpgradeModel(getDynamicProperties("resolution-key").asText(),
50             BluePrintDependencyService.restClientService(RESTCONF_SERVER_IDENTIFIER),
51             properties.get("pnf-id").textValue(), properties.get("target-software-version").textValue(),
52             Action.getEnumFromActionName(executionRequest.actionIdentifiers.actionName))
53
54         log.info("Blueprint invoked for ${model.resolutionKey} for SW Upgrade : " +
55             "${model.action} for sw version ${model.targetSwVersion} on pnf: ${model.deviceId}")
56
57         try {
58             val mountPayload = contentFromResolvedArtifactNB("mount-node")
59             log.debug("Mount Payload : $mountPayload")
60             restconfMountDevice(model.client, model.deviceId, mountPayload, mutableMapOf("Content-Type" to "application/json"))
61
62             when (model.action) {
63                 Action.PRE_CHECK -> processPrecheck(model)
64                 Action.DOWNLOAD_NE_SW -> processDownloadNeSw(model)
65                 Action.ACTIVATE_NE_SW -> processActivateNeSw(model)
66                 Action.POST_CHECK -> processPostcheck(model)
67                 Action.CANCEL -> processCancel(model)
68             }
69
70         } catch (err: Exception) {
71             log.error("an error occurred while configuring device {}", err)
72         } finally {
73             restconfUnMountDevice(model.client, model.deviceId, "")
74         }
75     }
76
77     private suspend fun processPrecheck(model: SoftwareUpgradeModel) {
78         log.debug("In PNF SW upgrade : processPreCheck")
79         //Log the current configuration for the subtree
80         val payloadObject = getCurrentConfig(model)
81         log.debug("Current sw version on pnf : ${payloadObject.get("software-upgrade")?.get("upgrade-package")?.get(0)?.get("software-version")?.asText()}")
82         log.info("PNF is Healthy!")
83     }
84
85     private suspend fun processDownloadNeSw(model: SoftwareUpgradeModel) {
86         log.debug("In PNF SW upgrade : processDownloadNeSw")
87         //Check if there is existing config for the targeted software version
88
89         var downloadConfigPayload: String
90         if (checkIfSwReadyToPerformAction(Action.PRE_CHECK, model)) {
91             downloadConfigPayload = contentFromResolvedArtifactNB("configure")
92             downloadConfigPayload =downloadConfigPayload.replace("%id%", model.yangId)
93         }
94         else {
95             downloadConfigPayload = contentFromResolvedArtifactNB("download-ne-sw")
96             model.yangId=model.targetSwVersion
97         }
98         downloadConfigPayload = downloadConfigPayload.replace("%actionName%", Action.DOWNLOAD_NE_SW.name)
99         log.info("Config Payload to start download : $downloadConfigPayload")
100
101         //Apply configlet
102         restconfApplyDeviceConfig(model.client, model.deviceId, CONFIGLET_RESOURCE_PATH, downloadConfigPayload,
103             mutableMapOf("Content-Type" to "application/yang.patch+json"))
104
105         //Poll PNF for Download action's progress
106         checkExecution(model)
107     }
108
109     private suspend fun processActivateNeSw(model: SoftwareUpgradeModel) {
110         log.debug("In PNF SW upgrade : processActivateNeSw")
111         //Check if the software is downloaded and ready to be activated
112         if (checkIfSwReadyToPerformAction(Action.DOWNLOAD_NE_SW, model)) {
113             var activateConfigPayload: String = contentFromResolvedArtifactNB("configure")
114             activateConfigPayload = activateConfigPayload.replace("%actionName%", Action.ACTIVATE_NE_SW.name)
115             log.info("Config Payload to start activate : $activateConfigPayload")
116             //Apply configlet
117             restconfApplyDeviceConfig(model.client, model.deviceId, CONFIGLET_RESOURCE_PATH, activateConfigPayload,
118                 mutableMapOf("Content-Type" to "application/yang.patch+json"))
119
120             //Poll PNF for Activate action's progress
121             checkExecution(model)
122         } else {
123             throw BluePrintRetryException("Software Download not completed for device(${model.deviceId}) to activate sw version: ${model.targetSwVersion}")
124         }
125     }
126
127     private suspend fun processPostcheck(model: SoftwareUpgradeModel) {
128         log.info("In PNF SW upgrade : processPostcheck")
129         //Log the current configuration for the subtree
130         if (checkIfSwReadyToPerformAction(Action.POST_CHECK, model)) {
131             log.info("PNF is healthy post activation!")
132         }
133     }
134
135     private fun processCancel(model :SoftwareUpgradeModel) {
136         //This is for future implementation of cancel step during software upgrade
137         log.info("In PNF SW upgrade : processCancel")
138     }
139
140     private suspend fun getCurrentConfig(model: SoftwareUpgradeModel) : ObjectNode{
141         val currentConfig: BlueprintWebClientService.WebClientResponse<String> = restconfDeviceConfig(model.client, model.deviceId, CONFIGLET_RESOURCE_PATH)
142         return JacksonUtils.jsonNode(currentConfig.body) as ObjectNode
143     }
144     private suspend fun checkExecution(model: SoftwareUpgradeModel) {
145         val checkExecutionBlock: suspend (Int) -> String = {
146             val result = restconfDeviceConfig(model.client, model.deviceId, TARGET_SOFTWARE_PATH.plus(model.yangId))
147             if (result.body.contains(model.action.completionStatus)) {
148                 log.info("${model.action.name} is complete")
149                 result.body
150             } else {
151                 throw BluePrintRetryException("Waiting for device(${model.deviceId}) to activate sw version ${model.targetSwVersion}")
152             }
153         }
154         model.client.retry<String>(10, 0, 1000, checkExecutionBlock)
155
156     }
157
158     private suspend fun checkIfSwReadyToPerformAction(action : Action, model: SoftwareUpgradeModel): Boolean {
159         val configBody = getCurrentConfig(model)
160         configBody.get("software-upgrade")?.get("upgrade-package")?.iterator()?.forEach { item ->
161             if (model.targetSwVersion == item.get("software-version")?.asText() &&
162                 action.completionStatus == item?.get("current-status")?.asText()) {
163                 model.yangId= item.get("id").textValue()
164                 return true
165             }
166         }
167         return false
168     }
169
170     override suspend fun recoverNB(runtimeException: RuntimeException, executionRequest: ExecutionServiceInput) {
171         log.info("Recover function called!")
172         log.info("Execution request : $executionRequest")
173         log.error("Exception", runtimeException)
174     }
175 }
176
177 enum class Action(val actionName: String, val completionStatus: String) {
178     PRE_CHECK("precheck", "INITIALIZED"),
179     DOWNLOAD_NE_SW("downloadNeSw", "DOWNLOAD_COMPLETED"),
180     ACTIVATE_NE_SW("activateNeSw", "ACTIVATION_COMPLETED"),
181     POST_CHECK("postcheck", "ACTIVATION_COMPLETED"),
182     CANCEL("cancel", "CANCELLED")
183     ;
184     companion object{
185         fun getEnumFromActionName(name: String): Action {
186             for(value in values()){
187                 if (value.actionName==name) return value
188             }
189             throw BluePrintException("Invalid Action sent to CDS")
190         }
191     }
192 }
193
194 data class SoftwareUpgradeModel(val resolutionKey: String, val client: BlueprintWebClientService, val deviceId: String,
195                                 val targetSwVersion: String, val action: Action, var yangId: String = "")