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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package org.onap.ccsdk.cds.blueprintsprocessor.services.execution.scripts
18 import com.fasterxml.jackson.annotation.JsonProperty
19 import com.fasterxml.jackson.databind.ObjectMapper
20 import com.fasterxml.jackson.databind.node.ObjectNode
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
51 open class K8sProfileUpload : AbstractScriptComponentFunction() {
53 private val log = LoggerFactory.getLogger(K8sProfileUpload::class.java)!!
55 override fun getName(): String {
56 return "K8sProfileUpload"
59 override suspend fun processNB(executionRequest: ExecutionServiceInput) {
60 log.info("executing K8s Profile Upload script")
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)
74 val assignmentParams = getDynamicProperties("assignment-params")
75 val payloadObject = JacksonUtils.jsonNode(assignmentParams.get(prefix).asText()) as ObjectNode
77 log.info("Uploading K8S profile for template prefix $prefix")
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")
87 // Extract supportedNssai
88 val api = K8sApi(k8sApiUsername, k8sApiPassword, baseK8sApiUrl, vfModuleModelInvariantUuid, vfModuleModelUuid)
90 if (!api.hasDefinition()) {
91 throw BluePrintProcessorException("K8s RB Definition ($vfModuleModelInvariantUuid/$vfModuleModelUuid) not found ")
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")
98 if (k8sRbProfileName.equals("default") and api.hasProfile(k8sRbProfileName)) {
99 log.info("Using default profile - skipping upload")
101 if (api.hasProfile(k8sRbProfileName)) {
102 log.info("Profile Already Existing - skipping upload")
104 val profileFilePath: Path = prepareProfileFile(k8sRbProfileName, vnfId)
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)
114 log.info("K8s Profile Upload Completed")
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")
126 val profileFile = profileFilePath.toFile()
128 if (!profileFile.exists())
129 throw BluePrintProcessorException("K8s Profile template file $profileFilePath does not exists")
131 val tempMainPath: File = createTempDir("k8s-profile-", "")
132 val tempProfilePath: File = createTempDir("$k8sRbProfileName-", "", tempMainPath)
133 log.info("Decompressing profile to $tempProfilePath")
135 val decompressedProfile: File = BluePrintArchiveUtils.deCompress(
136 profileFilePath.toFile(),
141 log.info("$profileFilePath decompression completed")
142 val tempOverrideValuesPath: String = "/tmp/k8s-profile-" + vnfId
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
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"))
153 if (!BluePrintArchiveUtils.compress(decompressedProfile, profileFilePath.toFile(), ArchiveType.TarGz)) {
154 throw BluePrintProcessorException("Profile compression has failed")
156 log.info("$profileFilePath compression completed")
157 return profileFilePath
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())
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)
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")
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()
184 if (!profileFile.exists())
185 throw BluePrintProcessorException("K8s Profile template file $profileFilePath does not exists")
187 val success = File(destPath).mkdirs()
188 log.info("Decompressing profile to $destPath")
190 val decompressedProfile: File = BluePrintArchiveUtils.deCompress(
191 profileFilePath.toFile(),
196 log.info("$profileFilePath decompression completed")
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)
206 for ((k, v) in manifestObject) {
207 log.info("manifestObject: $k, $v")
210 log.info("Uploaded YAML object")
212 val configFiles: MutableMap<String, Any> = manifestObject.get("config") as MutableMap<String, Any>
213 log.info("Uploaded config YAML object")
215 for ((k, v) in configFiles) {
216 log.info("configFiles: $k, $v")
219 val supportedNssai: MutableMap<String, Any> = configFiles.get("supportedNssai") as MutableMap<String, Any>
220 log.info("Uploaded supportedNssai YAML object")
222 for ((k, v) in supportedNssai) {
223 log.info("supportedNssai: $k, $v")
226 val sNssai: MutableMap<String, Any> = supportedNssai.get("sNssai") as MutableMap<String, Any>
227 log.info("Uploaded sNssai YAML object")
229 for ((k, v) in sNssai) {
230 log.info("sNssai: $k, $v")
233 for ((k, v) in supportedNssaiMap) {
234 log.info("supportedNssaiMap: $k, $v")
238 finalManifest = manifestYaml.dump(manifestObject)
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")
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()
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")) {
262 for (d in node.get("key-mapping")) {
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()
276 override suspend fun recoverNB(runtimeException: RuntimeException, executionRequest: ExecutionServiceInput) {
277 log.info("Executing Recovery")
278 bluePrintRuntimeService.getBluePrintError().addError("${runtimeException.message}")
282 val username: String,
283 val password: String,
285 val definition: String,
286 val definitionVersion: String
288 private val service: UploadFileRestClientService // BasicAuthRestClientService
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
302 this.service = UploadFileRestClientService(basicAuthRestClientProperties)
305 fun hasDefinition(): Boolean {
307 val result: BlueprintWebClientService.WebClientResponse<String> = service.exchangeResource(HttpMethod.GET.name, "", "")
309 if (result.status >= 200 && result.status < 300)
313 } catch (e: Exception) {
314 log.info("Caught exception trying to get k8s rb definition")
315 throw BluePrintProcessorException("${e.message}")
319 fun hasProfile(profileName: String): Boolean {
321 val result: BlueprintWebClientService.WebClientResponse<String> = service.exchangeResource(
323 "/profile/$profileName",
326 if (result.status >= 200 && result.status < 300)
332 } catch (e: Exception) {
333 log.info("Caught exception trying to get k8s rb profile")
334 throw BluePrintProcessorException("${e.message}")
338 fun createProfile(profile: K8sProfile) {
339 val objectMapper = ObjectMapper()
340 val profileJsonString: String = objectMapper.writeValueAsString(profile)
342 val result: BlueprintWebClientService.WebClientResponse<String> = service.exchangeResource(
343 HttpMethod.POST.name,
347 if (result.status < 200 || result.status >= 300) {
348 throw Exception(result.body)
350 } catch (e: Exception) {
351 log.info("Caught exception trying to create k8s rb profile ${profile.profileName}")
352 throw BluePrintProcessorException("${e.message}")
356 fun uploadProfileContent(profile: K8sProfile, filePath: Path) {
358 val result: BlueprintWebClientService.WebClientResponse<String> = service.uploadBinaryFile(
359 "/profile/${profile.profileName}/content",
362 if (result.status < 200 || result.status >= 300) {
363 throw Exception(result.body)
365 } catch (e: Exception) {
366 log.info("Caught exception trying to upload k8s rb profile ${profile.profileName}")
367 throw BluePrintProcessorException("${e.message}")
373 class UploadFileRestClientService(
374 private val restClientProperties:
375 BasicAuthRestClientProperties
376 ) : BlueprintWebClientService {
378 override fun defaultHeaders(): Map<String, String> {
380 val encodedCredentials = setBasicAuth(
381 restClientProperties.username,
382 restClientProperties.password
385 HttpHeaders.CONTENT_TYPE to MediaType.APPLICATION_JSON_VALUE,
386 HttpHeaders.ACCEPT to MediaType.APPLICATION_JSON_VALUE,
387 HttpHeaders.AUTHORIZATION to "Basic $encodedCredentials"
391 override fun host(uri: String): String {
392 return restClientProperties.url + uri
395 override fun convertToBasicHeaders(headers: Map<String, String>):
397 val customHeaders: MutableMap<String, String> = headers.toMutableMap()
398 // inject additionalHeaders
399 customHeaders.putAll(verifyAdditionalHeaders(restClientProperties))
401 if (!headers.containsKey(HttpHeaders.AUTHORIZATION)) {
402 val encodedCredentials = setBasicAuth(
403 restClientProperties.username,
404 restClientProperties.password
406 customHeaders[HttpHeaders.AUTHORIZATION] =
407 "Basic $encodedCredentials"
409 return super.convertToBasicHeaders(customHeaders)
412 private fun setBasicAuth(username: String, password: String): String {
413 val credentialsString = "$username:$password"
414 return Base64.getEncoder().encodeToString(
415 credentialsString.toByteArray(Charset.defaultCharset())
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)
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)
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"
450 override fun toString(): String {
451 return "$rbName:$rbVersion:$profileName"
454 override fun equals(other: Any?): Boolean {
455 if (this === other) return true
456 if (javaClass != other?.javaClass) return false
460 override fun hashCode(): Int {
461 return javaClass.hashCode()