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.io.IOException
47 import java.nio.file.Files
48 import java.nio.file.Paths
49 import java.nio.file.Path
50 import org.springframework.http.HttpHeaders
51 import org.springframework.http.MediaType
52 import java.nio.charset.Charset
53 import java.util.Base64
54 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException
56 open class K8sProfileUpload : AbstractScriptComponentFunction() {
58 private val log = LoggerFactory.getLogger(K8sProfileUpload::class.java)!!
60 override fun getName(): String {
61 return "K8sProfileUpload"
64 override suspend fun processNB(executionRequest: ExecutionServiceInput) {
65 log.info("executing K8s Profile Upload script")
67 val baseK8sApiUrl = getDynamicProperties("api-access").get("url").asText()
68 val k8sApiUsername = getDynamicProperties("api-access").get("username").asText()
69 val k8sApiPassword = getDynamicProperties("api-access").get("password").asText()
71 val prefixList: ArrayList<String> = getTemplatePrefixList(executionRequest)
73 for (prefix in prefixList) {
74 if (prefix.toLowerCase().equals("vnf")) {
75 log.info("For vnf-level resource-assignment, profile upload is not performed")
79 val assignmentParams = getDynamicProperties("assignment-params")
80 val payloadObject = JacksonUtils.jsonNode(assignmentParams.get(prefix).asText()) as ObjectNode
82 log.info("Uploading K8S profile for template prefix $prefix")
84 val vfModuleModelInvariantUuid: String = getResolvedParameter(payloadObject, "vf-module-model-invariant-uuid")
85 val vfModuleModelUuid: String = getResolvedParameter(payloadObject, "vf-module-model-version")
86 val k8sRbProfileName: String = getResolvedParameter(payloadObject, "k8s-rb-profile-name")
87 val k8sRbProfileNamespace: String = getResolvedParameter(payloadObject, "k8s-rb-profile-namespace")
89 val api = K8sApi(k8sApiUsername, k8sApiPassword, baseK8sApiUrl, vfModuleModelInvariantUuid, vfModuleModelUuid)
91 if (!api.hasDefinition()) {
92 throw BluePrintProcessorException("K8s RB Definition (${vfModuleModelInvariantUuid}/${vfModuleModelUuid}) not found ")
95 log.info("k8s-rb-profile-name: $k8sRbProfileName")
96 if (k8sRbProfileName.equals("")) {
97 throw BluePrintProcessorException("K8s rb profile name is empty! Either define profile name to use or choose default")
99 if (k8sRbProfileName.equals("default") and api.hasProfile(k8sRbProfileName)) {
100 log.info("Using default profile - skipping upload")
102 if (api.hasProfile(k8sRbProfileName)) {
103 log.info("Profile Already Existing - skipping upload")
105 val profileFilePath: Path = prepareProfileFile(k8sRbProfileName)
107 var profile = K8sProfile()
108 profile.profileName = k8sRbProfileName
109 profile.rbName = vfModuleModelInvariantUuid
110 profile.rbVersion = vfModuleModelUuid
111 profile.namespace = k8sRbProfileNamespace
112 api.createProfile(profile)
113 api.uploadProfileContent(profile, profileFilePath)
115 log.info("K8s Profile Upload Completed")
121 fun prepareProfileFile(k8sRbProfileName: String): Path {
122 val bluePrintContext = bluePrintRuntimeService.bluePrintContext()
123 val bluePrintBasePath: String = bluePrintContext.rootPath
124 var profileFilePath: Path = Paths.get(bluePrintBasePath.plus(File.separator).plus("Templates").plus(File.separator).plus("k8s-profiles").plus(File.separator).plus("${k8sRbProfileName}.tar.gz"))
125 log.info("Reading K8s profile file: ${profileFilePath}")
127 val profileFile = profileFilePath.toFile()
129 if (!profileFile.exists())
130 throw BluePrintProcessorException("K8s Profile template file ${profileFilePath} does not exists")
132 val tempMainPath: File = createTempDir("k8s-profile-", "")
133 val tempProfilePath: File = createTempDir("${k8sRbProfileName}-", "", tempMainPath)
134 log.info("Decompressing profile to ${tempProfilePath.toString()}")
136 val decompressedProfile: File = BluePrintArchiveUtils.deCompress(profileFilePath.toFile(),
137 "${tempProfilePath.toString()}", ArchiveType.TarGz)
139 log.info("${profileFilePath.toString()} decompression completed")
141 //Here we can add extra files inside the archive
142 profileFilePath = Paths.get(tempMainPath.toString().plus(File.separator).plus("${k8sRbProfileName}.tar.gz"))
144 if (!BluePrintArchiveUtils.compress(decompressedProfile, profileFilePath.toFile(),
145 ArchiveType.TarGz)) {
146 throw BluePrintProcessorException("Profile compression has failed")
149 log.info("${profileFilePath.toString()} compression completed")
151 return profileFilePath
154 fun getTemplatePrefixList(executionRequest: ExecutionServiceInput): ArrayList<String> {
155 val result = ArrayList<String>()
156 for (prefix in executionRequest.payload.get("resource-assignment-request").get("template-prefix").elements())
157 result.add(prefix.asText())
161 fun getResolvedParameter(payload: ObjectNode, keyName: String): String {
162 for (node in payload.get("resource-accumulator-resolved-data").elements()) {
163 if (node.get("param-name").asText().equals(keyName)) {
164 return node.get("param-value").asText()
170 override suspend fun recoverNB(runtimeException: RuntimeException, executionRequest: ExecutionServiceInput) {
171 log.info("Executing Recovery")
172 bluePrintRuntimeService.getBluePrintError().addError("${runtimeException.message}")
175 inner class K8sApi(val username: String, val password: String, val baseUrl: String, val definition: String,
176 val definitionVersion: String) {
177 private val service: UploadFileRestClientService //BasicAuthRestClientService
180 var mapOfHeaders = hashMapOf<String, String>()
181 mapOfHeaders.put("Accept", "application/json")
182 mapOfHeaders.put("Content-Type", "application/json")
183 mapOfHeaders.put("cache-control", " no-cache")
184 mapOfHeaders.put("Accept", "application/json")
185 var basicAuthRestClientProperties: BasicAuthRestClientProperties = BasicAuthRestClientProperties()
186 basicAuthRestClientProperties.username = username
187 basicAuthRestClientProperties.password = password
188 basicAuthRestClientProperties.url = "$baseUrl/v1/rb/definition/${definition}/${definitionVersion}"
189 basicAuthRestClientProperties.additionalHeaders = mapOfHeaders
191 this.service = UploadFileRestClientService(basicAuthRestClientProperties)
194 fun hasDefinition(): Boolean {
196 val result: BlueprintWebClientService.WebClientResponse<String> = service.exchangeResource(HttpMethod.GET.name, "", "")
198 if (result.status >= 200 && result.status < 300)
202 } catch (e: Exception) {
203 log.info("Caught exception trying to get k8s rb definition")
204 throw BluePrintProcessorException("${e.message}")
208 fun hasProfile(profileName: String): Boolean {
210 val result: BlueprintWebClientService.WebClientResponse<String> = service.exchangeResource(HttpMethod.GET.name,
211 "/profile/$profileName", "")
212 if (result.status >= 200 && result.status < 300)
218 } catch (e: Exception) {
219 log.info("Caught exception trying to get k8s rb profile")
220 throw BluePrintProcessorException("${e.message}")
224 fun createProfile(profile: K8sProfile) {
225 val objectMapper = ObjectMapper()
226 val profileJsonString: String = objectMapper.writeValueAsString(profile)
228 val result: BlueprintWebClientService.WebClientResponse<String> = service.exchangeResource(HttpMethod.POST.name,
229 "/profile", profileJsonString)
230 if (result.status < 200 || result.status >= 300) {
231 throw Exception(result.body)
233 } catch (e: Exception) {
234 log.info("Caught exception trying to create k8s rb profile ${profile.profileName}")
235 throw BluePrintProcessorException("${e.message}")
239 fun uploadProfileContent(profile: K8sProfile, filePath: Path) {
241 val result: BlueprintWebClientService.WebClientResponse<String> = service.uploadBinaryFile(
242 "/profile/${profile.profileName}/content", filePath)
243 if (result.status < 200 || result.status >= 300) {
244 throw Exception(result.body)
246 } catch (e: Exception) {
247 log.info("Caught exception trying to upload k8s rb profile ${profile.profileName}")
248 throw BluePrintProcessorException("${e.message}")
254 class UploadFileRestClientService(
255 private val restClientProperties:
256 BasicAuthRestClientProperties
257 ) : BlueprintWebClientService {
259 override fun defaultHeaders(): Map<String, String> {
261 val encodedCredentials = setBasicAuth(
262 restClientProperties.username,
263 restClientProperties.password
266 HttpHeaders.CONTENT_TYPE to MediaType.APPLICATION_JSON_VALUE,
267 HttpHeaders.ACCEPT to MediaType.APPLICATION_JSON_VALUE,
268 HttpHeaders.AUTHORIZATION to "Basic $encodedCredentials"
272 override fun host(uri: String): String {
273 return restClientProperties.url + uri
276 override fun convertToBasicHeaders(headers: Map<String, String>):
278 val customHeaders: MutableMap<String, String> = headers.toMutableMap()
279 // inject additionalHeaders
280 customHeaders.putAll(verifyAdditionalHeaders(restClientProperties))
282 if (!headers.containsKey(HttpHeaders.AUTHORIZATION)) {
283 val encodedCredentials = setBasicAuth(
284 restClientProperties.username,
285 restClientProperties.password
287 customHeaders[HttpHeaders.AUTHORIZATION] =
288 "Basic $encodedCredentials"
290 return super.convertToBasicHeaders(customHeaders)
293 private fun setBasicAuth(username: String, password: String): String {
294 val credentialsString = "$username:$password"
295 return Base64.getEncoder().encodeToString(
296 credentialsString.toByteArray(Charset.defaultCharset())
300 @Throws(IOException::class, ClientProtocolException::class)
301 private fun performHttpCall(httpUriRequest: HttpUriRequest): BlueprintWebClientService.WebClientResponse<String> {
302 val httpResponse = httpClient().execute(httpUriRequest)
303 val statusCode = httpResponse.statusLine.statusCode
304 httpResponse.entity.content.use {
305 val body = IOUtils.toString(it, Charset.defaultCharset())
306 return BlueprintWebClientService.WebClientResponse(statusCode, body)
310 fun uploadBinaryFile(path: String, filePath: Path): BlueprintWebClientService.WebClientResponse<String> {
311 val convertedHeaders: Array<BasicHeader> = convertToBasicHeaders(defaultHeaders())
312 val httpPost = HttpPost(host(path))
313 val entity = EntityBuilder.create().setBinary(Files.readAllBytes(filePath)).build()
314 httpPost.setEntity(entity)
315 RestLoggerService.httpInvoking(convertedHeaders)
316 httpPost.setHeaders(convertedHeaders)
317 return performHttpCall(httpPost)
322 @get:JsonProperty("rb-name")
323 var rbName: String? = null
324 @get:JsonProperty("rb-version")
325 var rbVersion: String? = null
326 @get:JsonProperty("profile-name")
327 var profileName: String? = null
328 @get:JsonProperty("namespace")
329 var namespace: String? = "default"
331 override fun toString(): String {
332 return "$rbName:$rbVersion:$profileName"
335 override fun equals(other: Any?): Boolean {
336 if (this === other) return true
337 if (javaClass != other?.javaClass) return false
341 override fun hashCode(): Int {
342 return javaClass.hashCode()