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.
17 package org.onap.ccsdk.cds.blueprintsprocessor.services.execution.scripts
19 import com.fasterxml.jackson.databind.node.ObjectNode
20 import com.fasterxml.jackson.databind.ObjectMapper
21 import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput
22 import org.onap.ccsdk.cds.blueprintsprocessor.rest.BasicAuthRestClientProperties
23 import org.onap.ccsdk.cds.blueprintsprocessor.rest.RestClientProperties
24 import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BasicAuthRestClientService
25 import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService
26 import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.RestLoggerService
27 import org.onap.ccsdk.cds.blueprintsprocessor.services.execution.AbstractScriptComponentFunction
28 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
29 import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintArchiveUtils
30 import org.onap.ccsdk.cds.controllerblueprints.core.utils.ArchiveType
31 import org.apache.commons.io.IOUtils
32 import org.apache.commons.io.FilenameUtils
33 import org.apache.http.client.entity.EntityBuilder
34 import org.apache.http.entity.ContentType
35 import org.apache.http.message.BasicHeader
36 import org.apache.http.client.methods.HttpPost
37 import org.apache.http.client.methods.HttpUriRequest
38 import org.apache.http.client.ClientProtocolException
39 import org.slf4j.LoggerFactory
40 import org.springframework.http.HttpMethod
41 import org.springframework.web.client.RestTemplate
42 import com.fasterxml.jackson.annotation.JsonIgnore
43 import com.fasterxml.jackson.annotation.JsonProperty
44 import java.util.ArrayList
45 import java.util.LinkedHashMap
46 import java.io.IOException
48 import java.nio.file.Files
49 import java.nio.file.Paths
50 import java.nio.file.Path
51 import org.springframework.http.HttpHeaders
52 import org.springframework.http.MediaType
53 import java.nio.charset.Charset
54 import java.util.Base64
55 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException
56 import org.yaml.snakeyaml.Yaml
58 open class K8sProfileUpload : AbstractScriptComponentFunction() {
60 private val log = LoggerFactory.getLogger(K8sProfileUpload::class.java)!!
62 override fun getName(): String {
63 return "K8sProfileUpload"
66 override suspend fun processNB(executionRequest: ExecutionServiceInput) {
67 log.info("executing K8s Profile Upload script")
69 val baseK8sApiUrl = getDynamicProperties("api-access").get("url").asText()
70 val k8sApiUsername = getDynamicProperties("api-access").get("username").asText()
71 val k8sApiPassword = getDynamicProperties("api-access").get("password").asText()
73 val prefixList: ArrayList<String> = getTemplatePrefixList(executionRequest)
75 for (prefix in prefixList) {
76 if (prefix.toLowerCase().equals("vnf")) {
77 log.info("For vnf-level resource-assignment, profile upload is not performed")
81 val assignmentParams = getDynamicProperties("assignment-params")
82 val payloadObject = JacksonUtils.jsonNode(assignmentParams.get(prefix).asText()) as ObjectNode
84 log.info("Uploading K8S profile for template prefix $prefix")
86 val vfModuleModelInvariantUuid: String = getResolvedParameter(payloadObject, "vf-module-model-invariant-uuid")
87 val vfModuleModelUuid: String = getResolvedParameter(payloadObject, "vf-module-model-version")
88 val k8sRbProfileName: String = getResolvedParameter(payloadObject, "k8s-rb-profile-name")
89 val k8sRbProfileNamespace: String = getResolvedParameter(payloadObject, "k8s-rb-profile-namespace")
91 val api = K8sApi(k8sApiUsername, k8sApiPassword, baseK8sApiUrl, vfModuleModelInvariantUuid, vfModuleModelUuid)
93 if (!api.hasDefinition()) {
94 throw BluePrintProcessorException("K8s RB Definition (${vfModuleModelInvariantUuid}/${vfModuleModelUuid}) not found ")
97 log.info("k8s-rb-profile-name: $k8sRbProfileName")
98 if (k8sRbProfileName.equals("")) {
99 throw BluePrintProcessorException("K8s rb profile name is empty! Either define profile name to use or choose default")
101 if (k8sRbProfileName.equals("default") and api.hasProfile(k8sRbProfileName)) {
102 log.info("Using default profile - skipping upload")
104 if (api.hasProfile(k8sRbProfileName)) {
105 log.info("Profile Already Existing - skipping upload")
107 val profileFilePath: Path = prepareProfileFile(k8sRbProfileName, prefix.equals("vpkg"))
109 var profile = K8sProfile()
110 profile.profileName = k8sRbProfileName
111 profile.rbName = vfModuleModelInvariantUuid
112 profile.rbVersion = vfModuleModelUuid
113 profile.namespace = k8sRbProfileNamespace
114 api.createProfile(profile)
115 api.uploadProfileContent(profile, profileFilePath)
117 log.info("K8s Profile Upload Completed")
123 fun prepareProfileFile(k8sRbProfileName: String, profileModificationAllowed: Boolean): Path {
124 val bluePrintContext = bluePrintRuntimeService.bluePrintContext()
125 val bluePrintBasePath: String = bluePrintContext.rootPath
126 var profileFilePath: Path = Paths.get(bluePrintBasePath.plus(File.separator).plus("Templates").plus(File.separator).plus("k8s-profiles").plus(File.separator).plus("${k8sRbProfileName}.tar.gz"))
127 log.info("Reading K8s profile file: ${profileFilePath}")
129 val profileFile = profileFilePath.toFile()
131 if (!profileFile.exists())
132 throw BluePrintProcessorException("K8s Profile template file ${profileFilePath} does not exists")
134 val tempMainPath: File = createTempDir("k8s-profile-", "")
135 val tempProfilePath: File = createTempDir("${k8sRbProfileName}-", "", tempMainPath)
136 log.info("Decompressing profile to ${tempProfilePath.toString()}")
138 val decompressedProfile: File = BluePrintArchiveUtils.deCompress(profileFilePath.toFile(),
139 "${tempProfilePath.toString()}", ArchiveType.TarGz)
141 log.info("${profileFilePath.toString()} decompression completed")
143 if (profileModificationAllowed) {
144 //Here we can add extra files inside the archive
145 val profileModificationDecisionData = getDynamicProperties("profile-modification-decision-data")
146 log.info("Profile modification decision data: ${profileModificationDecisionData}")
147 if (profileModificationDecisionData != null && profileModificationDecisionData.asText().toInt() > 0) {
148 log.info("Modification of profile content")
150 val profileArtifacts = getDynamicProperties("profile-artifacts")
151 val sshServiceFileContent = profileArtifacts.get("ssh-service").asText()
152 val sshServiceFileName = "ssh-service.yaml"
153 val serviceFilePath = tempProfilePath.toString().plus(File.separator).plus(sshServiceFileName)
154 File(serviceFilePath).bufferedWriter().use { out -> out.write(sshServiceFileContent) }
155 val manifestFileName = tempProfilePath.toString().plus(File.separator).plus("manifest.yaml")
156 var finalManifest = ""
157 File(manifestFileName).bufferedReader().use { inr ->
158 val manifestYaml = Yaml()
159 val manifestObject: Map<String, Any> = manifestYaml.load(inr)
160 val typeObject: MutableMap<String, Any> = manifestObject.get("type") as MutableMap<String, Any>
161 if (!typeObject.containsKey("configresource"))
162 typeObject.put("configresource", ArrayList<LinkedHashMap<String, Any>>())
163 val configFiles: MutableList<LinkedHashMap<String, Any>> = typeObject.get("configresource") as MutableList<LinkedHashMap<String, Any>>
164 val sshConfigFile = LinkedHashMap<String, Any>()
165 sshConfigFile.put("filepath", sshServiceFileName)
166 sshConfigFile.put("chartpath", "vpkg/templates/${sshServiceFileName}")
167 configFiles.add(sshConfigFile)
168 finalManifest = manifestYaml.dump(manifestObject)
170 File(manifestFileName).bufferedWriter().use { out -> out.write(finalManifest) }
171 log.info("Modified K8s profile manifest file")
172 log.info(finalManifest)
173 log.info("Modification of profile completed")
177 profileFilePath = Paths.get(tempMainPath.toString().plus(File.separator).plus("${k8sRbProfileName}.tar.gz"))
179 if (!BluePrintArchiveUtils.compress(decompressedProfile, profileFilePath.toFile(),
180 ArchiveType.TarGz)) {
181 throw BluePrintProcessorException("Profile compression has failed")
184 log.info("${profileFilePath.toString()} compression completed")
186 return profileFilePath
189 fun getTemplatePrefixList(executionRequest: ExecutionServiceInput): ArrayList<String> {
190 val result = ArrayList<String>()
191 for (prefix in executionRequest.payload.get("resource-assignment-request").get("template-prefix").elements())
192 result.add(prefix.asText())
196 fun getResolvedParameter(payload: ObjectNode, keyName: String): String {
197 for (node in payload.get("resource-accumulator-resolved-data").elements()) {
198 if (node.get("param-name").asText().equals(keyName)) {
199 return node.get("param-value").asText()
205 override suspend fun recoverNB(runtimeException: RuntimeException, executionRequest: ExecutionServiceInput) {
206 log.info("Executing Recovery")
207 bluePrintRuntimeService.getBluePrintError().addError("${runtimeException.message}")
210 inner class K8sApi(val username: String, val password: String, val baseUrl: String, val definition: String,
211 val definitionVersion: String) {
212 private val service: UploadFileRestClientService //BasicAuthRestClientService
215 var mapOfHeaders = hashMapOf<String, String>()
216 mapOfHeaders.put("Accept", "application/json")
217 mapOfHeaders.put("Content-Type", "application/json")
218 mapOfHeaders.put("cache-control", " no-cache")
219 mapOfHeaders.put("Accept", "application/json")
220 var basicAuthRestClientProperties: BasicAuthRestClientProperties = BasicAuthRestClientProperties()
221 basicAuthRestClientProperties.username = username
222 basicAuthRestClientProperties.password = password
223 basicAuthRestClientProperties.url = "$baseUrl/v1/rb/definition/${definition}/${definitionVersion}"
224 basicAuthRestClientProperties.additionalHeaders = mapOfHeaders
226 this.service = UploadFileRestClientService(basicAuthRestClientProperties)
229 fun hasDefinition(): Boolean {
231 val result: BlueprintWebClientService.WebClientResponse<String> = service.exchangeResource(HttpMethod.GET.name, "", "")
233 if (result.status >= 200 && result.status < 300)
237 } catch (e: Exception) {
238 log.info("Caught exception trying to get k8s rb definition")
239 throw BluePrintProcessorException("${e.message}")
243 fun hasProfile(profileName: String): Boolean {
245 val result: BlueprintWebClientService.WebClientResponse<String> = service.exchangeResource(HttpMethod.GET.name,
246 "/profile/$profileName", "")
247 if (result.status >= 200 && result.status < 300)
253 } catch (e: Exception) {
254 log.info("Caught exception trying to get k8s rb profile")
255 throw BluePrintProcessorException("${e.message}")
259 fun createProfile(profile: K8sProfile) {
260 val objectMapper = ObjectMapper()
261 val profileJsonString: String = objectMapper.writeValueAsString(profile)
263 val result: BlueprintWebClientService.WebClientResponse<String> = service.exchangeResource(HttpMethod.POST.name,
264 "/profile", profileJsonString)
265 if (result.status < 200 || result.status >= 300) {
266 throw Exception(result.body)
268 } catch (e: Exception) {
269 log.info("Caught exception trying to create k8s rb profile ${profile.profileName}")
270 throw BluePrintProcessorException("${e.message}")
274 fun uploadProfileContent(profile: K8sProfile, filePath: Path) {
276 val result: BlueprintWebClientService.WebClientResponse<String> = service.uploadBinaryFile(
277 "/profile/${profile.profileName}/content", filePath)
278 if (result.status < 200 || result.status >= 300) {
279 throw Exception(result.body)
281 } catch (e: Exception) {
282 log.info("Caught exception trying to upload k8s rb profile ${profile.profileName}")
283 throw BluePrintProcessorException("${e.message}")
289 class UploadFileRestClientService(
290 private val restClientProperties:
291 BasicAuthRestClientProperties
292 ) : BlueprintWebClientService {
294 override fun defaultHeaders(): Map<String, String> {
296 val encodedCredentials = setBasicAuth(
297 restClientProperties.username,
298 restClientProperties.password
301 HttpHeaders.CONTENT_TYPE to MediaType.APPLICATION_JSON_VALUE,
302 HttpHeaders.ACCEPT to MediaType.APPLICATION_JSON_VALUE,
303 HttpHeaders.AUTHORIZATION to "Basic $encodedCredentials"
307 override fun host(uri: String): String {
308 return restClientProperties.url + uri
311 override fun convertToBasicHeaders(headers: Map<String, String>):
313 val customHeaders: MutableMap<String, String> = headers.toMutableMap()
314 // inject additionalHeaders
315 customHeaders.putAll(verifyAdditionalHeaders(restClientProperties))
317 if (!headers.containsKey(HttpHeaders.AUTHORIZATION)) {
318 val encodedCredentials = setBasicAuth(
319 restClientProperties.username,
320 restClientProperties.password
322 customHeaders[HttpHeaders.AUTHORIZATION] =
323 "Basic $encodedCredentials"
325 return super.convertToBasicHeaders(customHeaders)
328 private fun setBasicAuth(username: String, password: String): String {
329 val credentialsString = "$username:$password"
330 return Base64.getEncoder().encodeToString(
331 credentialsString.toByteArray(Charset.defaultCharset())
335 @Throws(IOException::class, ClientProtocolException::class)
336 private fun performHttpCall(httpUriRequest: HttpUriRequest): BlueprintWebClientService.WebClientResponse<String> {
337 val httpResponse = httpClient().execute(httpUriRequest)
338 val statusCode = httpResponse.statusLine.statusCode
339 httpResponse.entity.content.use {
340 val body = IOUtils.toString(it, Charset.defaultCharset())
341 return BlueprintWebClientService.WebClientResponse(statusCode, body)
345 fun uploadBinaryFile(path: String, filePath: Path): BlueprintWebClientService.WebClientResponse<String> {
346 val convertedHeaders: Array<BasicHeader> = convertToBasicHeaders(defaultHeaders())
347 val httpPost = HttpPost(host(path))
348 val entity = EntityBuilder.create().setBinary(Files.readAllBytes(filePath)).build()
349 httpPost.setEntity(entity)
350 RestLoggerService.httpInvoking(convertedHeaders)
351 httpPost.setHeaders(convertedHeaders)
352 return performHttpCall(httpPost)
357 @get:JsonProperty("rb-name")
358 var rbName: String? = null
359 @get:JsonProperty("rb-version")
360 var rbVersion: String? = null
361 @get:JsonProperty("profile-name")
362 var profileName: String? = null
363 @get:JsonProperty("namespace")
364 var namespace: String? = "default"
366 override fun toString(): String {
367 return "$rbName:$rbVersion:$profileName"
370 override fun equals(other: Any?): Boolean {
371 if (this === other) return true
372 if (javaClass != other?.javaClass) return false
376 override fun hashCode(): Int {
377 return javaClass.hashCode()