5G Core Network Service CBA package upload
[ccsdk/cds.git] / components / model-catalog / blueprint-model / service-blueprint / 5GC_Simulator_CNF_CDS / Scripts / kotlin / KotlinK8sProfileUpload.kt
1 /*
2  * Copyright © 2019 Orange
3  * Author: Malinconico Aniello Paolo <aniellopaolo.malinconico@guest.telecomitalia.it>
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. Creating override_values")
69                 val assignmentParams = getDynamicProperties("assignment-params")
70                 val payloadObject = JacksonUtils.jsonNode(assignmentParams.get(prefix).asText()) as ObjectNode
71                 createOverrideVaues(payloadObject)
72                 continue
73             }
74             val assignmentParams = getDynamicProperties("assignment-params")
75             val payloadObject = JacksonUtils.jsonNode(assignmentParams.get(prefix).asText()) as ObjectNode
76
77             log.info("Uploading K8S profile for template prefix $prefix")
78
79             val vfModuleModelInvariantUuid: String = getResolvedParameter(payloadObject, "vf-module-model-invariant-uuid")
80             val vfModuleModelUuid: String = getResolvedParameter(payloadObject, "vf-module-model-version")
81             val k8sRbProfileName: String = getResolvedParameter(payloadObject, "k8s-rb-profile-name")
82             val k8sRbProfileNamespace: String = getResolvedParameter(payloadObject, "k8s-rb-profile-namespace")
83             val vnfId: String = getResolvedParameter(payloadObject, "vnf-id")
84             log.info("******vnfID************   $vnfId")
85             log.info("k8sRbProfileName $k8sRbProfileName")
86
87             // Extract supportedNssai
88             val api = K8sApi(k8sApiUsername, k8sApiPassword, baseK8sApiUrl, vfModuleModelInvariantUuid, vfModuleModelUuid)
89
90             if (!api.hasDefinition()) {
91                 throw BluePrintProcessorException("K8s RB Definition ($vfModuleModelInvariantUuid/$vfModuleModelUuid) 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                     val profileFilePath: Path = prepareProfileFile(k8sRbProfileName, vnfId)
105
106                     var profile = K8sProfile()
107                     profile.profileName = k8sRbProfileName
108                     profile.rbName = vfModuleModelInvariantUuid
109                     profile.rbVersion = vfModuleModelUuid
110                     profile.namespace = k8sRbProfileNamespace
111                     api.createProfile(profile)
112                     api.uploadProfileContent(profile, profileFilePath)
113
114                     log.info("K8s Profile Upload Completed")
115                 }
116             }
117         }
118     }
119
120     fun prepareProfileFile(k8sRbProfileName: String, vnfId: String): Path {
121         val bluePrintContext = bluePrintRuntimeService.bluePrintContext()
122         val bluePrintBasePath: String = bluePrintContext.rootPath
123         var profileFilePath: Path = Paths.get(bluePrintBasePath.plus(File.separator).plus("Templates").plus(File.separator).plus("k8s-profiles").plus(File.separator).plus("template-profile.tar.gz"))
124         log.info("Reading K8s profile file: $profileFilePath")
125
126         val profileFile = profileFilePath.toFile()
127
128         if (!profileFile.exists())
129             throw BluePrintProcessorException("K8s Profile template file $profileFilePath does not exists")
130
131         val tempMainPath: File = createTempDir("k8s-profile-", "")
132         val tempProfilePath: File = createTempDir("$k8sRbProfileName-", "", tempMainPath)
133         log.info("Decompressing profile to $tempProfilePath")
134
135         val decompressedProfile: File = BluePrintArchiveUtils.deCompress(
136             profileFilePath.toFile(),
137             "$tempProfilePath",
138             ArchiveType.TarGz
139         )
140
141         log.info("$profileFilePath decompression completed")
142         val tempOverrideValuesPath: String = "/tmp/k8s-profile-" + vnfId
143
144         // Here we are copying the tmp override_values file (created at vnf level) into the profile, before uploading it. This function is performed only at vfmodule level
145
146         log.info("Modification of override.yaml file ")
147         val tmpOverrideFile = tempOverrideValuesPath.toString().plus(File.separator).plus("override_values.yaml")
148         val destOverrideFile = tempProfilePath.toString().plus(File.separator).plus("override_values.yaml")
149         log.info("destination override file $destOverrideFile")
150         File(tmpOverrideFile).copyTo(File(destOverrideFile), true)
151         profileFilePath = Paths.get(tempMainPath.toString().plus(File.separator).plus("template-profile.tar.gz"))
152
153         if (!BluePrintArchiveUtils.compress(decompressedProfile, profileFilePath.toFile(), ArchiveType.TarGz)) {
154             throw BluePrintProcessorException("Profile compression has failed")
155         }
156         log.info("$profileFilePath compression completed")
157         return profileFilePath
158     }
159
160     fun getTemplatePrefixList(executionRequest: ExecutionServiceInput): ArrayList<String> {
161         val result = ArrayList<String>()
162         for (prefix in executionRequest.payload.get("resource-assignment-request").get("template-prefix").elements())
163             result.add(prefix.asText())
164         return result
165     }
166
167     fun createOverrideVaues(payloadObject: ObjectNode): String {
168         // Extract supportedNssai
169         val supportedNssaiMap = LinkedHashMap<String, Any>()
170         val snssai: String = getResolvedParameter(payloadObject, "config.supportedNssai.sNssai.snssai")
171         log.info("snssa1 $snssai")
172         supportedNssaiMap.put("snssai", snssai)
173
174         val bluePrintContext = bluePrintRuntimeService.bluePrintContext()
175         val bluePrintBasePath: String = bluePrintContext.rootPath
176         val vnfId: String = getResolvedParameter(payloadObject, "vnf-id")
177         val destPath: String = "/tmp/k8s-profile-" + vnfId
178         log.info("*********vnfID***** $vnfId")
179
180         var profileFilePath: Path = Paths.get(bluePrintBasePath.plus(File.separator).plus("Templates").plus(File.separator).plus("k8s-profiles").plus(File.separator).plus("template-profile.tar.gz"))
181         log.info("Reading K8s profile file: $profileFilePath")
182         val profileFile = profileFilePath.toFile()
183
184         if (!profileFile.exists())
185             throw BluePrintProcessorException("K8s Profile template file $profileFilePath does not exists")
186
187         val success = File(destPath).mkdirs()
188         log.info("Decompressing profile to $destPath")
189
190         val decompressedProfile: File = BluePrintArchiveUtils.deCompress(
191             profileFilePath.toFile(),
192             "$destPath",
193             ArchiveType.TarGz
194         )
195
196         log.info("$profileFilePath decompression completed")
197
198         // Here we update override.yaml file
199         val manifestFileName = destPath.plus(File.separator).plus("override_values.yaml")
200         log.info("Modification of override.yaml file at $manifestFileName")
201         var finalManifest = ""
202         File(manifestFileName).bufferedReader().use { inr ->
203             val manifestYaml = Yaml()
204             val manifestObject: Map<String, Any> = manifestYaml.load(inr)
205
206             for ((k, v) in manifestObject) {
207                 log.info("manifestObject: $k, $v")
208             }
209
210             log.info("Uploaded YAML object")
211
212             val configFiles: MutableMap<String, Any> = manifestObject.get("config") as MutableMap<String, Any>
213             log.info("Uploaded config YAML object")
214
215             for ((k, v) in configFiles) {
216                 log.info("configFiles: $k, $v")
217             }
218
219             val supportedNssai: MutableMap<String, Any> = configFiles.get("supportedNssai") as MutableMap<String, Any>
220             log.info("Uploaded  supportedNssai YAML object")
221
222             for ((k, v) in supportedNssai) {
223                 log.info("supportedNssai: $k, $v")
224             }
225
226             val sNssai: MutableMap<String, Any> = supportedNssai.get("sNssai") as MutableMap<String, Any>
227             log.info("Uploaded  sNssai YAML object")
228
229             for ((k, v) in sNssai) {
230                 log.info("sNssai: $k, $v")
231             }
232
233             for ((k, v) in supportedNssaiMap) {
234                 log.info("supportedNssaiMap: $k, $v")
235                 sNssai.put(k, v)
236             }
237
238             finalManifest = manifestYaml.dump(manifestObject)
239         }
240
241         File(manifestFileName).bufferedWriter().use { out -> out.write(finalManifest) }
242         log.info("Modified K8s profile manifest file")
243         log.info(finalManifest)
244         log.info("Modification of profile completed")
245         return ""
246     }
247
248     fun getResolvedParameter(payload: ObjectNode, keyName: String): String {
249         for (node in payload.get("resource-accumulator-resolved-data").elements()) {
250             if (node.get("param-name").asText().equals(keyName)) {
251                 return node.get("param-value").asText()
252             }
253         }
254         return ""
255     }
256
257     fun getResolvedParameterbyCapabilityData(payload: ObjectNode, keyName: String): String {
258         for (node in payload.get("capability-data").elements()) {
259             log.info("node: $node")
260             if (node.get("capability-name").asText().equals("unresolved-composite-data")) {
261                 log.info("inside")
262                 for (d in node.get("key-mapping")) {
263                     log.info("d: $d")
264                     for (value in d.get("output-key-mapping")) {
265                         if (value.get("resource-name").asText().equals(keyName)) {
266                             log.info("value: $value")
267                             return value.get("resource-value").asText()
268                         }
269                     }
270                 }
271             }
272         }
273         return ""
274     }
275
276     override suspend fun recoverNB(runtimeException: RuntimeException, executionRequest: ExecutionServiceInput) {
277         log.info("Executing Recovery")
278         bluePrintRuntimeService.getBluePrintError().addError("${runtimeException.message}")
279     }
280
281     inner class K8sApi(
282         val username: String,
283         val password: String,
284         val baseUrl: String,
285         val definition: String,
286         val definitionVersion: String
287     ) {
288         private val service: UploadFileRestClientService // BasicAuthRestClientService
289
290         init {
291             var mapOfHeaders = hashMapOf<String, String>()
292             mapOfHeaders.put("Accept", "application/json")
293             mapOfHeaders.put("Content-Type", "application/json")
294             mapOfHeaders.put("cache-control", " no-cache")
295             mapOfHeaders.put("Accept", "application/json")
296             var basicAuthRestClientProperties: BasicAuthRestClientProperties = BasicAuthRestClientProperties()
297             basicAuthRestClientProperties.username = username
298             basicAuthRestClientProperties.password = password
299             basicAuthRestClientProperties.url = "$baseUrl/v1/rb/definition/$definition/$definitionVersion"
300             basicAuthRestClientProperties.additionalHeaders = mapOfHeaders
301
302             this.service = UploadFileRestClientService(basicAuthRestClientProperties)
303         }
304
305         fun hasDefinition(): Boolean {
306             try {
307                 val result: BlueprintWebClientService.WebClientResponse<String> = service.exchangeResource(HttpMethod.GET.name, "", "")
308                 print(result)
309                 if (result.status >= 200 && result.status < 300)
310                     return true
311                 else
312                     return false
313             } catch (e: Exception) {
314                 log.info("Caught exception trying to get k8s rb definition")
315                 throw BluePrintProcessorException("${e.message}")
316             }
317         }
318
319         fun hasProfile(profileName: String): Boolean {
320             try {
321                 val result: BlueprintWebClientService.WebClientResponse<String> = service.exchangeResource(
322                     HttpMethod.GET.name,
323                     "/profile/$profileName",
324                     ""
325                 )
326                 if (result.status >= 200 && result.status < 300)
327                     return true
328                 else {
329                     print(result)
330                     return false
331                 }
332             } catch (e: Exception) {
333                 log.info("Caught exception trying to get k8s rb profile")
334                 throw BluePrintProcessorException("${e.message}")
335             }
336         }
337
338         fun createProfile(profile: K8sProfile) {
339             val objectMapper = ObjectMapper()
340             val profileJsonString: String = objectMapper.writeValueAsString(profile)
341             try {
342                 val result: BlueprintWebClientService.WebClientResponse<String> = service.exchangeResource(
343                     HttpMethod.POST.name,
344                     "/profile",
345                     profileJsonString
346                 )
347                 if (result.status < 200 || result.status >= 300) {
348                     throw Exception(result.body)
349                 }
350             } catch (e: Exception) {
351                 log.info("Caught exception trying to create k8s rb profile ${profile.profileName}")
352                 throw BluePrintProcessorException("${e.message}")
353             }
354         }
355
356         fun uploadProfileContent(profile: K8sProfile, filePath: Path) {
357             try {
358                 val result: BlueprintWebClientService.WebClientResponse<String> = service.uploadBinaryFile(
359                     "/profile/${profile.profileName}/content",
360                     filePath
361                 )
362                 if (result.status < 200 || result.status >= 300) {
363                     throw Exception(result.body)
364                 }
365             } catch (e: Exception) {
366                 log.info("Caught exception trying to upload k8s rb profile ${profile.profileName}")
367                 throw BluePrintProcessorException("${e.message}")
368             }
369         }
370     }
371 }
372
373 class UploadFileRestClientService(
374     private val restClientProperties:
375         BasicAuthRestClientProperties
376 ) : BlueprintWebClientService {
377
378     override fun defaultHeaders(): Map<String, String> {
379
380         val encodedCredentials = setBasicAuth(
381             restClientProperties.username,
382             restClientProperties.password
383         )
384         return mapOf(
385             HttpHeaders.CONTENT_TYPE to MediaType.APPLICATION_JSON_VALUE,
386             HttpHeaders.ACCEPT to MediaType.APPLICATION_JSON_VALUE,
387             HttpHeaders.AUTHORIZATION to "Basic $encodedCredentials"
388         )
389     }
390
391     override fun host(uri: String): String {
392         return restClientProperties.url + uri
393     }
394
395     override fun convertToBasicHeaders(headers: Map<String, String>):
396         Array<BasicHeader> {
397             val customHeaders: MutableMap<String, String> = headers.toMutableMap()
398             // inject additionalHeaders
399             customHeaders.putAll(verifyAdditionalHeaders(restClientProperties))
400
401             if (!headers.containsKey(HttpHeaders.AUTHORIZATION)) {
402                 val encodedCredentials = setBasicAuth(
403                     restClientProperties.username,
404                     restClientProperties.password
405                 )
406                 customHeaders[HttpHeaders.AUTHORIZATION] =
407                     "Basic $encodedCredentials"
408             }
409             return super.convertToBasicHeaders(customHeaders)
410         }
411
412     private fun setBasicAuth(username: String, password: String): String {
413         val credentialsString = "$username:$password"
414         return Base64.getEncoder().encodeToString(
415             credentialsString.toByteArray(Charset.defaultCharset())
416         )
417     }
418
419     @Throws(IOException::class, ClientProtocolException::class)
420     private fun performHttpCall(httpUriRequest: HttpUriRequest): BlueprintWebClientService.WebClientResponse<String> {
421         val httpResponse = httpClient().execute(httpUriRequest)
422         val statusCode = httpResponse.statusLine.statusCode
423         httpResponse.entity.content.use {
424             val body = IOUtils.toString(it, Charset.defaultCharset())
425             return BlueprintWebClientService.WebClientResponse(statusCode, body)
426         }
427     }
428
429     fun uploadBinaryFile(path: String, filePath: Path): BlueprintWebClientService.WebClientResponse<String> {
430         val convertedHeaders: Array<BasicHeader> = convertToBasicHeaders(defaultHeaders())
431         val httpPost = HttpPost(host(path))
432         val entity = EntityBuilder.create().setBinary(Files.readAllBytes(filePath)).build()
433         httpPost.setEntity(entity)
434         RestLoggerService.httpInvoking(convertedHeaders)
435         httpPost.setHeaders(convertedHeaders)
436         return performHttpCall(httpPost)
437     }
438 }
439
440 class K8sProfile {
441     @get:JsonProperty("rb-name")
442     var rbName: String? = null
443     @get:JsonProperty("rb-version")
444     var rbVersion: String? = null
445     @get:JsonProperty("profile-name")
446     var profileName: String? = null
447     @get:JsonProperty("namespace")
448     var namespace: String? = "default"
449
450     override fun toString(): String {
451         return "$rbName:$rbVersion:$profileName"
452     }
453
454     override fun equals(other: Any?): Boolean {
455         if (this === other) return true
456         if (javaClass != other?.javaClass) return false
457         return true
458     }
459
460     override fun hashCode(): Int {
461         return javaClass.hashCode()
462     }
463 }