2 * Copyright © 2019 Orange
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.")
72 val assignmentParams = getDynamicProperties("assignment-params")
73 val payloadObject = JacksonUtils.jsonNode(assignmentParams.get(prefix).asText()) as ObjectNode
75 log.info("Uploading K8S profile for template prefix $prefix")
77 val vfModuleModelInvariantUuid: String = getResolvedParameter(payloadObject, "vf-module-model-invariant-uuid")
78 val vfModuleId: String = getResolvedParameter(payloadObject, "vf-module-id")
79 val vfModuleModelUuid: 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")
84 log.info("******vfModuleID************ $vfModuleId")
85 log.info("k8sRbProfileName $k8sRbProfileName")
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 createOverrideValues(payloadObject, vfModuleId, prefix)
105 val profileFilePath: Path = prepareProfileFile(k8sRbProfileName, vfModuleId, prefix)
107 var profile = K8sProfile()
108 profile.profileName = k8sRbProfileName
109 profile.rbName = vfModuleModelInvariantUuid
110 profile.rbVersion = vfModuleModelUuid
111 profile.namespace = k8sRbProfileNamespace
112 profile.releaseName = releaseName
113 api.createProfile(profile)
114 api.uploadProfileContent(profile, profileFilePath)
116 log.info("K8s Profile Upload Completed")
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")
128 val profileFile = profileFilePath.toFile()
130 if (!profileFile.exists())
131 throw BluePrintProcessorException("K8s Profile template file $profileFilePath does not exists")
133 val tempMainPath: File = createTempDir("k8s-profile-", "")
134 val tempProfilePath: File = createTempDir("$k8sRbProfileName-", "", tempMainPath)
135 log.info("Decompressing profile to $tempProfilePath")
137 val decompressedProfile: File = BluePrintArchiveUtils.deCompress(
138 profileFilePath.toFile(),
143 log.info("$profileFilePath decompression completed")
144 val destPath: String = "/tmp/k8s-profile-" + vfModuleId
146 // Here we update override.yaml file
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"))
156 if (!BluePrintArchiveUtils.compress(decompressedProfile, profileFilePath.toFile(), ArchiveType.TarGz)) {
157 throw BluePrintProcessorException("Profile compression has failed")
159 log.info("$profileFilePath compression completed")
160 return profileFilePath
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())
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)
178 val bluePrintContext = bluePrintRuntimeService.bluePrintContext()
179 val bluePrintBasePath: String = bluePrintContext.rootPath
180 val destPath: String = "/tmp/k8s-profile-" + vfModuleId
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()
186 if (!profileFile.exists())
187 throw BluePrintProcessorException("K8s Profile template file $profileFilePath does not exists")
189 val success = File(destPath).mkdirs()
190 log.info("Decompressing profile to $destPath")
192 val decompressedProfile: File = BluePrintArchiveUtils.deCompress(
193 profileFilePath.toFile(),
198 log.info("$profileFilePath decompression completed")
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)
208 for ((k, v) in manifestObject) {
209 log.info("manifestObject: $k, $v")
212 log.info("Uploaded YAML object")
214 val configFiles: MutableMap<String, Any> = manifestObject.get("config") as MutableMap<String, Any>
215 log.info("Uploaded config YAML object")
217 for ((k, v) in configFiles) {
218 log.info("configFiles: $k, $v")
221 val supportedNssai: MutableMap<String, Any> = configFiles.get("supportedNssai") as MutableMap<String, Any>
222 log.info("Uploaded supportedNssai YAML object")
224 for ((k, v) in supportedNssai) {
225 log.info("supportedNssai: $k, $v")
228 val sNssai: MutableMap<String, Any> = supportedNssai.get("sNssai") as MutableMap<String, Any>
229 log.info("Uploaded sNssai YAML object")
231 for ((k, v) in sNssai) {
232 log.info("sNssai: $k, $v")
235 for ((k, v) in supportedNssaiMap) {
236 log.info("supportedNssaiMap: $k, $v")
240 finalManifest = manifestYaml.dump(manifestObject)
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")
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()
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")) {
266 for (d in node.get("key-mapping")) {
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()
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!!)
288 val username: String,
289 val password: String,
291 val definition: String,
292 val definitionVersion: String
294 private val service: UploadFileRestClientService // BasicAuthRestClientService
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
308 this.service = UploadFileRestClientService(basicAuthRestClientProperties)
311 fun hasDefinition(): Boolean {
313 val result: BlueprintWebClientService.WebClientResponse<String> = service.exchangeResource(HttpMethod.GET.name, "", "")
315 if (result.status >= 200 && result.status < 300)
319 } catch (e: Exception) {
320 log.info("Caught exception trying to get k8s rb definition")
321 throw BluePrintProcessorException("${e.message}")
325 fun hasProfile(profileName: String): Boolean {
327 val result: BlueprintWebClientService.WebClientResponse<String> = service.exchangeResource(
329 "/profile/$profileName",
332 if (result.status >= 200 && result.status < 300)
338 } catch (e: Exception) {
339 log.info("Caught exception trying to get k8s rb profile")
340 throw BluePrintProcessorException("${e.message}")
344 fun createProfile(profile: K8sProfile) {
345 val objectMapper = ObjectMapper()
346 val profileJsonString: String = objectMapper.writeValueAsString(profile)
348 val result: BlueprintWebClientService.WebClientResponse<String> = service.exchangeResource(
349 HttpMethod.POST.name,
353 if (result.status < 200 || result.status >= 300) {
354 throw Exception(result.body)
356 } catch (e: Exception) {
357 log.info("Caught exception trying to create k8s rb profile ${profile.profileName}")
358 throw BluePrintProcessorException("${e.message}")
362 fun uploadProfileContent(profile: K8sProfile, filePath: Path) {
364 val result: BlueprintWebClientService.WebClientResponse<String> = service.uploadBinaryFile(
365 "/profile/${profile.profileName}/content",
368 if (result.status < 200 || result.status >= 300) {
369 throw Exception(result.body)
371 } catch (e: Exception) {
372 log.info("Caught exception trying to upload k8s rb profile ${profile.profileName}")
373 throw BluePrintProcessorException("${e.message}")
379 class UploadFileRestClientService(
380 private val restClientProperties:
381 BasicAuthRestClientProperties
382 ) : BlueprintWebClientService {
384 override fun defaultHeaders(): Map<String, String> {
386 val encodedCredentials = setBasicAuth(
387 restClientProperties.username,
388 restClientProperties.password
391 HttpHeaders.CONTENT_TYPE to MediaType.APPLICATION_JSON_VALUE,
392 HttpHeaders.ACCEPT to MediaType.APPLICATION_JSON_VALUE,
393 HttpHeaders.AUTHORIZATION to "Basic $encodedCredentials"
397 override fun host(uri: String): String {
398 return restClientProperties.url + uri
401 override fun convertToBasicHeaders(headers: Map<String, String>):
403 val customHeaders: MutableMap<String, String> = headers.toMutableMap()
404 // inject additionalHeaders
405 customHeaders.putAll(verifyAdditionalHeaders(restClientProperties))
407 if (!headers.containsKey(HttpHeaders.AUTHORIZATION)) {
408 val encodedCredentials = setBasicAuth(
409 restClientProperties.username,
410 restClientProperties.password
412 customHeaders[HttpHeaders.AUTHORIZATION] =
413 "Basic $encodedCredentials"
415 return super.convertToBasicHeaders(customHeaders)
418 private fun setBasicAuth(username: String, password: String): String {
419 val credentialsString = "$username:$password"
420 return Base64.getEncoder().encodeToString(
421 credentialsString.toByteArray(Charset.defaultCharset())
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)
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)
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
458 override fun toString(): String {
459 return "$rbName:$rbVersion:$profileName:$releaseName"
462 override fun equals(other: Any?): Boolean {
463 if (this === other) return true
464 if (javaClass != other?.javaClass) return false
468 override fun hashCode(): Int {
469 return javaClass.hashCode()