Updated for Jakarta Release
[ccsdk/cds.git] / components / model-catalog / blueprint-model / service-blueprint / 5GC_Simulator_CNF_CDS / Scripts / kotlin / KotlinK8sProfileUpload.kt
1 /*
2  * Copyright © 2019 Orange
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.onap.ccsdk.cds.blueprintsprocessor.services.execution.scripts
17
18 import com.fasterxml.jackson.annotation.JsonProperty
19 import com.fasterxml.jackson.databind.ObjectMapper
20 import com.fasterxml.jackson.databind.node.ObjectNode
21 import java.io.File
22 import java.io.IOException
23 import java.nio.charset.Charset
24 import java.nio.file.Files
25 import java.nio.file.Path
26 import java.nio.file.Paths
27 import java.util.ArrayList
28 import java.util.Base64
29 import java.util.LinkedHashMap
30 import org.apache.commons.io.IOUtils
31 import org.apache.http.client.ClientProtocolException
32 import org.apache.http.client.entity.EntityBuilder
33 import org.apache.http.client.methods.HttpPost
34 import org.apache.http.client.methods.HttpUriRequest
35 import org.apache.http.message.BasicHeader
36 import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput
37 import org.onap.ccsdk.cds.blueprintsprocessor.rest.BasicAuthRestClientProperties
38 import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService
39 import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.RestLoggerService
40 import org.onap.ccsdk.cds.blueprintsprocessor.services.execution.AbstractScriptComponentFunction
41 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException
42 import org.onap.ccsdk.cds.controllerblueprints.core.utils.ArchiveType
43 import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintArchiveUtils
44 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
45 import org.slf4j.LoggerFactory
46 import org.springframework.http.HttpHeaders
47 import org.springframework.http.HttpMethod
48 import org.springframework.http.MediaType
49 import org.yaml.snakeyaml.Yaml
50
51 open class K8sProfileUpload : AbstractScriptComponentFunction() {
52
53     private val log = LoggerFactory.getLogger(K8sProfileUpload::class.java)!!
54
55     override fun getName(): String {
56         return "K8sProfileUpload"
57     }
58
59     override suspend fun processNB(executionRequest: ExecutionServiceInput) {
60         log.info("executing K8s Profile Upload script")
61
62         val baseK8sApiUrl = getDynamicProperties("api-access").get("url").asText()
63         val k8sApiUsername = getDynamicProperties("api-access").get("username").asText()
64         val k8sApiPassword = getDynamicProperties("api-access").get("password").asText()
65         val prefixList: ArrayList<String> = getTemplatePrefixList(executionRequest)
66         for (prefix in prefixList) {
67             if (prefix.toLowerCase().equals("vnf")) {
68                 log.info("For vnf-level resource-assignment, profile is not performed.")
69                 
70                 continue
71             }
72             val assignmentParams = getDynamicProperties("assignment-params")
73             val payloadObject = JacksonUtils.jsonNode(assignmentParams.get(prefix).asText()) as ObjectNode
74
75             log.info("Uploading K8S profile for template prefix $prefix")
76
77             val vfModuleModelInvariantUuid: String = getResolvedParameter(payloadObject, "vf-module-model-invariant-uuid")
78                          val vfModuleId: String = getResolvedParameter(payloadObject, "vf-module-id")
79             val vfModuleModelCustUuid: String = getResolvedParameter(payloadObject, "vf-module-model-customization-uuid")
80             val k8sRbProfileName: String = getResolvedParameter(payloadObject, "k8s-rb-profile-name")
81             val k8sRbProfileNamespace: String = getResolvedParameter(payloadObject, "k8s-rb-profile-namespace")
82                         val releaseName:String = getResolvedParameter(payloadObject, "k8s-rb-instance-release-name")
83             
84             log.info("******vfModuleID************   $vfModuleId")
85             log.info("k8sRbProfileName $k8sRbProfileName")
86
87
88             val api = K8sApi(k8sApiUsername, k8sApiPassword, baseK8sApiUrl, vfModuleModelInvariantUuid, vfModuleModelCustUuid)
89
90             if (!api.hasDefinition()) {
91                 throw BluePrintProcessorException("K8s RB Definition ($vfModuleModelInvariantUuid/$vfModuleModelCustUuid) not found ")
92             }
93
94             log.info("k8s-rb-profile-name: $k8sRbProfileName")
95             if (k8sRbProfileName.equals("")) {
96                 throw BluePrintProcessorException("K8s rb profile name is empty! Either define profile name to use or choose default")
97             }
98             if (k8sRbProfileName.equals("default") and api.hasProfile(k8sRbProfileName)) {
99                 log.info("Using default profile - skipping upload")
100             } else {
101                 if (api.hasProfile(k8sRbProfileName)) {
102                     log.info("Profile Already Existing - skipping upload")
103                 } else {
104                                     createOverrideValues(payloadObject,  vfModuleId, prefix)
105                     val profileFilePath: Path = prepareProfileFile(k8sRbProfileName, vfModuleId, prefix)
106                 
107                     var profile = K8sProfile()
108                     profile.profileName = k8sRbProfileName
109                     profile.rbName = vfModuleModelInvariantUuid
110                     profile.rbVersion = vfModuleModelCustUuid
111                     profile.namespace = k8sRbProfileNamespace
112                                         profile.releaseName = releaseName
113                     api.createProfile(profile)
114                     api.uploadProfileContent(profile, profileFilePath)
115
116                     log.info("K8s Profile Upload Completed")
117                 }
118             }
119         }
120     }
121
122     fun prepareProfileFile(k8sRbProfileName: String, vfModuleId: String, prefix: String): Path {
123        val bluePrintContext = bluePrintRuntimeService.bluePrintContext()
124         val bluePrintBasePath: String = bluePrintContext.rootPath
125        var profileFilePath: Path = Paths.get(bluePrintBasePath.plus(File.separator).plus("Templates").plus(File.separator).plus("k8s-profiles").plus(File.separator).plus("$prefix-profile.tar.gz"))
126         log.info("Reading K8s profile file: $profileFilePath")
127
128         val profileFile = profileFilePath.toFile()
129
130         if (!profileFile.exists())
131             throw BluePrintProcessorException("K8s Profile template file $profileFilePath does not exists")
132
133         val tempMainPath: File = createTempDir("k8s-profile-", "")
134         val tempProfilePath: File = createTempDir("$k8sRbProfileName-", "", tempMainPath)
135         log.info("Decompressing profile to $tempProfilePath")
136
137         val decompressedProfile: File = BluePrintArchiveUtils.deCompress(
138             profileFilePath.toFile(),
139             "$tempProfilePath",
140             ArchiveType.TarGz
141         )
142
143         log.info("$profileFilePath decompression completed")
144         val destPath: String = "/tmp/k8s-profile-" + vfModuleId
145
146         // Here we update override.yaml file
147
148         log.info("Modification of override.yaml file ")
149         val manifestFileName = destPath.toString().plus(File.separator).plus("override_values.yaml")
150         val destOverrideFile = tempProfilePath.toString().plus(File.separator).plus("override_values.yaml")
151         log.info("destination override file $destOverrideFile")
152         File(manifestFileName).copyTo(File(destOverrideFile), true)
153         profileFilePath = Paths.get(tempMainPath.toString().plus(File.separator).plus("$prefix-profile.tar.gz"))
154
155
156         if (!BluePrintArchiveUtils.compress(decompressedProfile, profileFilePath.toFile(), ArchiveType.TarGz)) {
157             throw BluePrintProcessorException("Profile compression has failed")
158         }
159         log.info("$profileFilePath compression completed")
160         return profileFilePath
161     }
162
163     fun getTemplatePrefixList(executionRequest: ExecutionServiceInput): ArrayList<String> {
164         val result = ArrayList<String>()
165         for (prefix in executionRequest.payload.get("resource-assignment-request").get("template-prefix").elements())
166             result.add(prefix.asText())
167         return result
168     }
169
170
171      fun createOverrideValues(payloadObject: ObjectNode,  vfModuleId: String, prefix:String): String {
172          // Extract supportedNssai
173         val supportedNssaiMap = LinkedHashMap<String, Any>()
174         val snssai: String = getResolvedParameter(payloadObject, "config.supportedNssai.sNssai.snssai")
175         log.info("snssa1 $snssai")
176         supportedNssaiMap.put("snssai", snssai)
177
178         val bluePrintContext = bluePrintRuntimeService.bluePrintContext()
179         val bluePrintBasePath: String = bluePrintContext.rootPath
180         val destPath: String = "/tmp/k8s-profile-" + vfModuleId
181         
182         var profileFilePath: Path = Paths.get(bluePrintBasePath.plus(File.separator).plus("Templates").plus(File.separator).plus("k8s-profiles").plus(File.separator).plus("$prefix-profile.tar.gz"))
183         log.info("Reading K8s profile file: $profileFilePath")
184         val profileFile = profileFilePath.toFile()
185
186         if (!profileFile.exists())
187             throw BluePrintProcessorException("K8s Profile template file $profileFilePath does not exists")
188
189         val success = File(destPath).mkdirs()
190         log.info("Decompressing profile to $destPath")
191
192         val decompressedProfile: File = BluePrintArchiveUtils.deCompress(
193             profileFilePath.toFile(),
194             "$destPath",
195             ArchiveType.TarGz
196         )
197
198         log.info("$profileFilePath decompression completed")
199
200         // Here we update override.yaml file
201         val manifestFileName = destPath.plus(File.separator).plus("override_values.yaml")
202         log.info("Modification of override.yaml file at $manifestFileName")
203         var finalManifest = ""
204         File(manifestFileName).bufferedReader().use { inr ->
205             val manifestYaml = Yaml()
206             val manifestObject: Map<String, Any> = manifestYaml.load(inr)
207
208             for ((k, v) in manifestObject) {
209                 log.info("manifestObject: $k, $v")
210             }
211
212             log.info("Uploaded YAML object")
213
214             val configFiles: MutableMap<String, Any> = manifestObject.get("config") as MutableMap<String, Any>
215             log.info("Uploaded config YAML object")
216
217             for ((k, v) in configFiles) {
218                 log.info("configFiles: $k, $v")
219             }
220
221             val supportedNssai: MutableMap<String, Any> = configFiles.get("supportedNssai") as MutableMap<String, Any>
222             log.info("Uploaded  supportedNssai YAML object")
223
224             for ((k, v) in supportedNssai) {
225                 log.info("supportedNssai: $k, $v")
226             }
227
228             val sNssai: MutableMap<String, Any> = supportedNssai.get("sNssai") as MutableMap<String, Any>
229             log.info("Uploaded  sNssai YAML object")
230
231             for ((k, v) in sNssai) {
232                 log.info("sNssai: $k, $v")
233             }
234
235             for ((k, v) in supportedNssaiMap) {
236                 log.info("supportedNssaiMap: $k, $v")
237                 sNssai.put(k, v)
238             }
239
240             finalManifest = manifestYaml.dump(manifestObject)
241         }
242
243         File(manifestFileName).bufferedWriter().use { out -> out.write(finalManifest) }
244         log.info("Modified K8s profile manifest file")
245         log.info(finalManifest)
246         log.info("Modification of profile completed")
247         return ""
248     }
249
250
251
252     fun getResolvedParameter(payload: ObjectNode, keyName: String): String {
253         for (node in payload.get("resource-accumulator-resolved-data").elements()) {
254             if (node.get("param-name").asText().equals(keyName)) {
255                 return node.get("param-value").asText()
256             }
257         }
258         return ""
259     }
260
261     fun getResolvedParameterbyCapabilityData(payload: ObjectNode, keyName: String): String {
262         for (node in payload.get("capability-data").elements()) {
263             log.info("node: $node")
264             if (node.get("capability-name").asText().equals("unresolved-composite-data")) {
265                 log.info("inside")
266                 for (d in node.get("key-mapping")) {
267                     log.info("d: $d")
268                     for (value in d.get("output-key-mapping")) {
269                         if (value.get("resource-name").asText().equals(keyName)) {
270                             log.info("value: $value")
271                             return value.get("resource-value").asText()
272                         }
273                     }
274                 }
275             }
276         }
277         return ""
278     }
279
280     override suspend fun recoverNB(runtimeException: RuntimeException, executionRequest: ExecutionServiceInput) {
281         log.info("Recover function called!")
282         log.info("Execution request : $executionRequest")
283         log.error("Exception", runtimeException)
284         addError(runtimeException.message!!)
285     }
286
287     inner class K8sApi(
288         val username: String,
289         val password: String,
290         val baseUrl: String,
291         val definition: String,
292         val definitionVersion: String
293     ) {
294         private val service: UploadFileRestClientService // BasicAuthRestClientService
295
296         init {
297             var mapOfHeaders = hashMapOf<String, String>()
298             mapOfHeaders.put("Accept", "application/json")
299             mapOfHeaders.put("Content-Type", "application/json")
300             mapOfHeaders.put("cache-control", " no-cache")
301             mapOfHeaders.put("Accept", "application/json")
302             var basicAuthRestClientProperties: BasicAuthRestClientProperties = BasicAuthRestClientProperties()
303             basicAuthRestClientProperties.username = username
304             basicAuthRestClientProperties.password = password
305             basicAuthRestClientProperties.url = "$baseUrl/v1/rb/definition/$definition/$definitionVersion"
306             basicAuthRestClientProperties.additionalHeaders = mapOfHeaders
307
308             this.service = UploadFileRestClientService(basicAuthRestClientProperties)
309         }
310
311         fun hasDefinition(): Boolean {
312             try {
313                 val result: BlueprintWebClientService.WebClientResponse<String> = service.exchangeResource(HttpMethod.GET.name, "", "")
314                 print(result)
315                 if (result.status >= 200 && result.status < 300)
316                     return true
317                 else
318                     return false
319             } catch (e: Exception) {
320                 log.info("Caught exception trying to get k8s rb definition")
321                 throw BluePrintProcessorException("${e.message}")
322             }
323         }
324
325         fun hasProfile(profileName: String): Boolean {
326             try {
327                 val result: BlueprintWebClientService.WebClientResponse<String> = service.exchangeResource(
328                     HttpMethod.GET.name,
329                     "/profile/$profileName",
330                     ""
331                 )
332                 if (result.status >= 200 && result.status < 300)
333                     return true
334                 else {
335                     print(result)
336                     return false
337                 }
338             } catch (e: Exception) {
339                 log.info("Caught exception trying to get k8s rb profile")
340                 throw BluePrintProcessorException("${e.message}")
341             }
342         }
343
344         fun createProfile(profile: K8sProfile) {
345             val objectMapper = ObjectMapper()
346             val profileJsonString: String = objectMapper.writeValueAsString(profile)
347             try {
348                 val result: BlueprintWebClientService.WebClientResponse<String> = service.exchangeResource(
349                     HttpMethod.POST.name,
350                     "/profile",
351                     profileJsonString
352                 )
353                 if (result.status < 200 || result.status >= 300) {
354                     throw Exception(result.body)
355                 }
356             } catch (e: Exception) {
357                 log.info("Caught exception trying to create k8s rb profile ${profile.profileName}")
358                 throw BluePrintProcessorException("${e.message}")
359             }
360         }
361
362         fun uploadProfileContent(profile: K8sProfile, filePath: Path) {
363             try {
364                 val result: BlueprintWebClientService.WebClientResponse<String> = service.uploadBinaryFile(
365                     "/profile/${profile.profileName}/content",
366                     filePath
367                 )
368                 if (result.status < 200 || result.status >= 300) {
369                     throw Exception(result.body)
370                 }
371             } catch (e: Exception) {
372                 log.info("Caught exception trying to upload k8s rb profile ${profile.profileName}")
373                 throw BluePrintProcessorException("${e.message}")
374             }
375         }
376     }
377 }
378
379 class UploadFileRestClientService(
380     private val restClientProperties:
381         BasicAuthRestClientProperties
382 ) : BlueprintWebClientService {
383
384     override fun defaultHeaders(): Map<String, String> {
385
386         val encodedCredentials = setBasicAuth(
387             restClientProperties.username,
388             restClientProperties.password
389         )
390         return mapOf(
391             HttpHeaders.CONTENT_TYPE to MediaType.APPLICATION_JSON_VALUE,
392             HttpHeaders.ACCEPT to MediaType.APPLICATION_JSON_VALUE,
393             HttpHeaders.AUTHORIZATION to "Basic $encodedCredentials"
394         )
395     }
396
397     override fun host(uri: String): String {
398         return restClientProperties.url + uri
399     }
400
401     override fun convertToBasicHeaders(headers: Map<String, String>):
402         Array<BasicHeader> {
403             val customHeaders: MutableMap<String, String> = headers.toMutableMap()
404             // inject additionalHeaders
405             customHeaders.putAll(verifyAdditionalHeaders(restClientProperties))
406
407             if (!headers.containsKey(HttpHeaders.AUTHORIZATION)) {
408                 val encodedCredentials = setBasicAuth(
409                     restClientProperties.username,
410                     restClientProperties.password
411                 )
412                 customHeaders[HttpHeaders.AUTHORIZATION] =
413                     "Basic $encodedCredentials"
414             }
415             return super.convertToBasicHeaders(customHeaders)
416         }
417
418     private fun setBasicAuth(username: String, password: String): String {
419         val credentialsString = "$username:$password"
420         return Base64.getEncoder().encodeToString(
421             credentialsString.toByteArray(Charset.defaultCharset())
422         )
423     }
424
425     @Throws(IOException::class, ClientProtocolException::class)
426     private fun performHttpCall(httpUriRequest: HttpUriRequest): BlueprintWebClientService.WebClientResponse<String> {
427         val httpResponse = httpClient().execute(httpUriRequest)
428         val statusCode = httpResponse.statusLine.statusCode
429         httpResponse.entity.content.use {
430             val body = IOUtils.toString(it, Charset.defaultCharset())
431             return BlueprintWebClientService.WebClientResponse(statusCode, body)
432         }
433     }
434
435     fun uploadBinaryFile(path: String, filePath: Path): BlueprintWebClientService.WebClientResponse<String> {
436         val convertedHeaders: Array<BasicHeader> = convertToBasicHeaders(defaultHeaders())
437         val httpPost = HttpPost(host(path))
438         val entity = EntityBuilder.create().setBinary(Files.readAllBytes(filePath)).build()
439         httpPost.setEntity(entity)
440         RestLoggerService.httpInvoking(convertedHeaders)
441         httpPost.setHeaders(convertedHeaders)
442         return performHttpCall(httpPost)
443     }
444 }
445
446 class K8sProfile {
447     @get:JsonProperty("rb-name")
448     var rbName: String? = null
449     @get:JsonProperty("rb-version")
450     var rbVersion: String? = null
451     @get:JsonProperty("profile-name")
452     var profileName: String? = null
453     @get:JsonProperty("namespace")
454     var namespace: String? = "default"
455         @get:JsonProperty("releaseName")
456     var releaseName: String? = null
457
458     override fun toString(): String {
459         return "$rbName:$rbVersion:$profileName:$releaseName"
460     }
461
462     override fun equals(other: Any?): Boolean {
463         if (this === other) return true
464         if (javaClass != other?.javaClass) return false
465         return true
466     }
467
468     override fun hashCode(): Int {
469         return javaClass.hashCode()
470     }
471 }