Renaming Files having BluePrint to have Blueprint
[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 package cba.pnf.swug
20
21 import com.fasterxml.jackson.databind.node.ObjectNode
22 import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput
23 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.contentFromResolvedArtifactNB
24 import org.onap.ccsdk.cds.blueprintsprocessor.functions.restconf.executor.restconfApplyDeviceConfig
25 import org.onap.ccsdk.cds.blueprintsprocessor.functions.restconf.executor.restconfDeviceConfig
26 import org.onap.ccsdk.cds.blueprintsprocessor.functions.restconf.executor.restconfMountDevice
27 import org.onap.ccsdk.cds.blueprintsprocessor.functions.restconf.executor.restconfUnMountDevice
28 import org.onap.ccsdk.cds.blueprintsprocessor.rest.restClientService
29 import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService
30 import org.onap.ccsdk.cds.blueprintsprocessor.services.execution.AbstractScriptComponentFunction
31 import org.onap.ccsdk.cds.controllerblueprints.core.BlueprintException
32 import org.onap.ccsdk.cds.controllerblueprints.core.BlueprintRetryException
33 import org.onap.ccsdk.cds.controllerblueprints.core.logger
34 import org.onap.ccsdk.cds.controllerblueprints.core.service.BlueprintDependencyService
35 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
36
37 class RestconfSoftwareUpgrade : AbstractScriptComponentFunction() {
38
39     private val RESTCONF_SERVER_IDENTIFIER = "sdnc"
40     private val CONFIGLET_RESOURCE_PATH = "yang-ext:mount/pnf-sw-upgrade:software-upgrade"
41     private val log = logger(AbstractScriptComponentFunction::class.java)
42     private val TARGET_SOFTWARE_PATH = "$CONFIGLET_RESOURCE_PATH/upgrade-package/"
43
44     override suspend fun processNB(executionRequest: ExecutionServiceInput) {
45
46         // Extract request properties
47         val model = validatedPayload(executionRequest)
48
49         log.info(
50             "Blueprint invoked for ${model.resolutionKey} for SW Upgrade : " +
51                 "${model.action} for sw version ${model.targetSwVersion} on pnf: ${model.deviceId}"
52         )
53
54         try {
55             val mountPayload = contentFromResolvedArtifactNB("mount-node")
56             log.debug("Mount Payload : $mountPayload")
57             restconfMountDevice(model.client, model.deviceId, mountPayload, mutableMapOf("Content-Type" to "application/json"))
58
59             when (model.action) {
60                 Action.PRE_CHECK -> processPreCheck(model)
61                 Action.DOWNLOAD_NE_SW -> processDownloadNESw(model)
62                 Action.ACTIVATE_NE_SW -> processActivateNESw(model)
63                 Action.POST_CHECK -> processPostCheck(model)
64                 Action.CANCEL -> processCancel(model)
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         ) {
78             return SoftwareUpgradeModel(
79                 getDynamicProperties("resolution-key").asText(),
80                 BlueprintDependencyService.restClientService(RESTCONF_SERVER_IDENTIFIER),
81                 properties.get("pnf-id").textValue(), properties.get("target-software-version").textValue(),
82                 Action.getEnumFromActionName(executionRequest.actionIdentifiers.actionName)
83             )
84         } else {
85             throw BlueprintException("Invalid parameters sent to CDS. Request parameters pnf-id or target-software-version missing")
86         }
87     }
88
89     private suspend fun processPreCheck(model: SoftwareUpgradeModel) {
90         log.debug("In PNF SW upgrade : processPreCheck")
91         // Log the current configuration for the subtree
92         val payloadObject = getCurrentConfig(model)
93         log.debug(
94             "Current sw version on pnf : ${
95                 payloadObject.get("software-upgrade")?.get("upgrade-package")?.get(0)?.get("software-version")?.asText()
96             }"
97         )
98         log.info("PNF is Healthy!")
99     }
100
101     private suspend fun processDownloadNESw(model: SoftwareUpgradeModel) {
102         log.debug("In PNF SW upgrade : processDownloadNESw")
103         // Check if there is existing config for the targeted software version
104
105         var downloadConfigPayload: String
106         if (checkIfSwReadyToPerformAction(Action.PRE_CHECK, model)) {
107             downloadConfigPayload = contentFromResolvedArtifactNB("configure")
108             downloadConfigPayload = downloadConfigPayload.replace("%id%", model.yangId)
109         } else {
110             downloadConfigPayload = contentFromResolvedArtifactNB("download-ne-sw")
111             model.yangId = model.targetSwVersion
112         }
113         downloadConfigPayload = downloadConfigPayload.replace("%actionName%", Action.DOWNLOAD_NE_SW.name)
114         log.info("Config Payload to start download : $downloadConfigPayload")
115
116         // Apply configlet
117         restconfApplyDeviceConfig(
118             model.client, model.deviceId, CONFIGLET_RESOURCE_PATH, downloadConfigPayload,
119             mutableMapOf("Content-Type" to "application/yang.patch+json")
120         )
121
122         // Poll PNF for Download action's progress
123         checkExecution(model)
124     }
125
126     private suspend fun processActivateNESw(model: SoftwareUpgradeModel) {
127         log.debug("In PNF SW upgrade : processActivateNESw")
128         // Check if the software is downloaded and ready to be activated
129         if (checkIfSwReadyToPerformAction(Action.DOWNLOAD_NE_SW, model)) {
130             var activateConfigPayload: String = contentFromResolvedArtifactNB("configure")
131             activateConfigPayload = activateConfigPayload.replace("%actionName%", Action.ACTIVATE_NE_SW.name)
132             activateConfigPayload = activateConfigPayload.replace("%id%", model.yangId)
133             log.info("Config Payload to start activate : $activateConfigPayload")
134             // Apply configlet
135             restconfApplyDeviceConfig(
136                 model.client, model.deviceId, CONFIGLET_RESOURCE_PATH, activateConfigPayload,
137                 mutableMapOf("Content-Type" to "application/yang.patch+json")
138             )
139
140             // Poll PNF for Activate action's progress
141             checkExecution(model)
142         } else {
143             throw BlueprintRetryException("Software Download not completed for device(${model.deviceId}) to activate sw version: ${model.targetSwVersion}")
144         }
145     }
146
147     private suspend fun processPostCheck(model: SoftwareUpgradeModel) {
148         log.info("In PNF SW upgrade : processPostCheck")
149         // Log the current configuration for the subtree
150         if (checkIfSwReadyToPerformAction(Action.POST_CHECK, model)) {
151             log.info("PNF is healthy post activation!")
152         }
153     }
154
155     private fun processCancel(model: SoftwareUpgradeModel) {
156         // This is for future implementation of cancel step during software upgrade
157         log.info("In PNF SW upgrade : processCancel")
158     }
159
160     private suspend fun getCurrentConfig(model: SoftwareUpgradeModel): ObjectNode {
161         val currentConfig: BlueprintWebClientService.WebClientResponse<String> =
162             restconfDeviceConfig(model.client, model.deviceId, CONFIGLET_RESOURCE_PATH)
163         return JacksonUtils.jsonNode(currentConfig.body) as ObjectNode
164     }
165
166     private suspend fun checkExecution(model: SoftwareUpgradeModel) {
167         val checkExecutionBlock: suspend (Int) -> String = {
168             val result = restconfDeviceConfig(model.client, model.deviceId, TARGET_SOFTWARE_PATH.plus(model.yangId))
169             if (result.body.contains(model.action.completionStatus)) {
170                 log.info("${model.action.name} is complete")
171                 result.body
172             } else {
173                 throw BlueprintRetryException("Waiting for device(${model.deviceId}) to activate sw version ${model.targetSwVersion}")
174             }
175         }
176         model.client.retry<String>(10, 0, 1000, checkExecutionBlock)
177     }
178
179     private suspend fun checkIfSwReadyToPerformAction(action: Action, model: SoftwareUpgradeModel): Boolean {
180         val configBody = getCurrentConfig(model)
181         configBody.get("software-upgrade")?.get("upgrade-package")?.iterator()?.forEach { item ->
182             if (model.targetSwVersion == item.get("software-version")?.asText() &&
183                 action.completionStatus == item?.get("current-status")?.asText()
184             ) {
185                 model.yangId = item.get("id").textValue()
186                 return true
187             }
188         }
189         return false
190     }
191
192     override suspend fun recoverNB(runtimeException: RuntimeException, executionRequest: ExecutionServiceInput) {
193         log.info("Recover function called!")
194         log.info("Execution request : $executionRequest")
195         log.error("Exception", runtimeException)
196     }
197 }
198
199 enum class Action(val actionName: String, val completionStatus: String) {
200     PRE_CHECK("preCheck", "INITIALIZED"),
201     DOWNLOAD_NE_SW("downloadNESw", "DOWNLOAD_COMPLETED"),
202     ACTIVATE_NE_SW("activateNESw", "ACTIVATION_COMPLETED"),
203     POST_CHECK("postCheck", "ACTIVATION_COMPLETED"),
204     CANCEL("cancel", "CANCELLED")
205     ;
206
207     companion object {
208
209         fun getEnumFromActionName(name: String): Action {
210             for (value in values()) {
211                 if (value.actionName == name) return value
212             }
213             throw BlueprintException("Invalid Action sent to CDS")
214         }
215     }
216 }
217
218 data class SoftwareUpgradeModel(
219     val resolutionKey: String,
220     val client: BlueprintWebClientService,
221     val deviceId: String,
222     val targetSwVersion: String,
223     val action: Action,
224     var yangId: String = ""
225 )