K8S Profile modification by CDS
[demo.git] / heat / vFW_CNF_CDS / templates / cba / Scripts / kotlin / KotlinK8sProfileUpload.kt
1 /*
2  * Copyright © 2019 Orange
3  *
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
17 package org.onap.ccsdk.cds.blueprintsprocessor.services.execution.scripts
18
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
46 import java.io.File
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
55
56 open class K8sProfileUpload : AbstractScriptComponentFunction() {
57
58     private val log = LoggerFactory.getLogger(K8sProfileUpload::class.java)!!
59
60     override fun getName(): String {
61         return "K8sProfileUpload"
62     }
63
64     override suspend fun processNB(executionRequest: ExecutionServiceInput) {
65         log.info("executing K8s Profile Upload script")
66
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()
70
71         val prefixList: ArrayList<String> = getTemplatePrefixList(executionRequest)
72
73         for (prefix in prefixList) {
74             if (prefix.toLowerCase().equals("vnf")) {
75                 log.info("For vnf-level resource-assignment, profile upload is not performed")
76                 continue
77             }
78
79             val assignmentParams = getDynamicProperties("assignment-params")
80             val payloadObject = JacksonUtils.jsonNode(assignmentParams.get(prefix).asText()) as ObjectNode
81
82             log.info("Uploading K8S profile for template prefix $prefix")
83
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")
88
89             val api = K8sApi(k8sApiUsername, k8sApiPassword, baseK8sApiUrl, vfModuleModelInvariantUuid, vfModuleModelUuid)
90
91             if (!api.hasDefinition()) {
92                 throw BluePrintProcessorException("K8s RB Definition (${vfModuleModelInvariantUuid}/${vfModuleModelUuid}) not found ")
93             }
94
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")
98             }
99             if (k8sRbProfileName.equals("default") and api.hasProfile(k8sRbProfileName)) {
100                 log.info("Using default profile - skipping upload")
101             } else {
102                 if (api.hasProfile(k8sRbProfileName)) {
103                     log.info("Profile Already Existing - skipping upload")
104                 } else {
105                     val profileFilePath: Path = prepareProfileFile(k8sRbProfileName)
106
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)
114
115                     log.info("K8s Profile Upload Completed")
116                 }
117             }
118         }
119     }
120
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}")
126
127         val profileFile = profileFilePath.toFile()
128
129         if (!profileFile.exists())
130             throw BluePrintProcessorException("K8s Profile template file ${profileFilePath} does not exists")
131
132         val tempMainPath: File = createTempDir("k8s-profile-", "")
133         val tempProfilePath: File = createTempDir("${k8sRbProfileName}-", "", tempMainPath)
134         log.info("Decompressing profile to ${tempProfilePath.toString()}")
135
136         val decompressedProfile: File = BluePrintArchiveUtils.deCompress(profileFilePath.toFile(),
137                 "${tempProfilePath.toString()}", ArchiveType.TarGz)
138
139         log.info("${profileFilePath.toString()} decompression completed")
140
141         //Here we can add extra files inside the archive
142         profileFilePath = Paths.get(tempMainPath.toString().plus(File.separator).plus("${k8sRbProfileName}.tar.gz"))
143
144         if (!BluePrintArchiveUtils.compress(decompressedProfile, profileFilePath.toFile(),
145                         ArchiveType.TarGz)) {
146             throw BluePrintProcessorException("Profile compression has failed")
147         }
148
149         log.info("${profileFilePath.toString()} compression completed")
150
151         return profileFilePath
152     }
153
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())
158         return result
159     }
160
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()
165             }
166         }
167         return ""
168     }
169
170     override suspend fun recoverNB(runtimeException: RuntimeException, executionRequest: ExecutionServiceInput) {
171         log.info("Executing Recovery")
172         bluePrintRuntimeService.getBluePrintError().addError("${runtimeException.message}")
173     }
174
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
178
179         init {
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
190
191             this.service = UploadFileRestClientService(basicAuthRestClientProperties)
192         }
193
194         fun hasDefinition(): Boolean {
195             try {
196                 val result: BlueprintWebClientService.WebClientResponse<String> = service.exchangeResource(HttpMethod.GET.name, "", "")
197                 print(result)
198                 if (result.status >= 200 && result.status < 300)
199                     return true
200                 else
201                     return false
202             } catch (e: Exception) {
203                 log.info("Caught exception trying to get k8s rb definition")
204                 throw BluePrintProcessorException("${e.message}")
205             }
206         }
207
208         fun hasProfile(profileName: String): Boolean {
209             try {
210                 val result: BlueprintWebClientService.WebClientResponse<String> = service.exchangeResource(HttpMethod.GET.name,
211                         "/profile/$profileName", "")
212                 if (result.status >= 200 && result.status < 300)
213                     return true
214                 else {
215                     print(result)
216                     return false
217                 }
218             } catch (e: Exception) {
219                 log.info("Caught exception trying to get k8s rb profile")
220                 throw BluePrintProcessorException("${e.message}")
221             }
222         }
223
224         fun createProfile(profile: K8sProfile) {
225             val objectMapper = ObjectMapper()
226             val profileJsonString: String = objectMapper.writeValueAsString(profile)
227             try {
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)
232                 }
233             } catch (e: Exception) {
234                 log.info("Caught exception trying to create k8s rb profile ${profile.profileName}")
235                 throw BluePrintProcessorException("${e.message}")
236             }
237         }
238
239         fun uploadProfileContent(profile: K8sProfile, filePath: Path) {
240             try {
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)
245                 }
246             } catch (e: Exception) {
247                 log.info("Caught exception trying to upload k8s rb profile ${profile.profileName}")
248                 throw BluePrintProcessorException("${e.message}")
249             }
250         }
251     }
252 }
253
254 class UploadFileRestClientService(
255         private val restClientProperties:
256         BasicAuthRestClientProperties
257 ) : BlueprintWebClientService {
258
259     override fun defaultHeaders(): Map<String, String> {
260
261         val encodedCredentials = setBasicAuth(
262                 restClientProperties.username,
263                 restClientProperties.password
264         )
265         return mapOf(
266                 HttpHeaders.CONTENT_TYPE to MediaType.APPLICATION_JSON_VALUE,
267                 HttpHeaders.ACCEPT to MediaType.APPLICATION_JSON_VALUE,
268                 HttpHeaders.AUTHORIZATION to "Basic $encodedCredentials"
269         )
270     }
271
272     override fun host(uri: String): String {
273         return restClientProperties.url + uri
274     }
275
276     override fun convertToBasicHeaders(headers: Map<String, String>):
277             Array<BasicHeader> {
278         val customHeaders: MutableMap<String, String> = headers.toMutableMap()
279         // inject additionalHeaders
280         customHeaders.putAll(verifyAdditionalHeaders(restClientProperties))
281
282         if (!headers.containsKey(HttpHeaders.AUTHORIZATION)) {
283             val encodedCredentials = setBasicAuth(
284                     restClientProperties.username,
285                     restClientProperties.password
286             )
287             customHeaders[HttpHeaders.AUTHORIZATION] =
288                     "Basic $encodedCredentials"
289         }
290         return super.convertToBasicHeaders(customHeaders)
291     }
292
293     private fun setBasicAuth(username: String, password: String): String {
294         val credentialsString = "$username:$password"
295         return Base64.getEncoder().encodeToString(
296                 credentialsString.toByteArray(Charset.defaultCharset())
297         )
298     }
299
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)
307         }
308     }
309
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)
318     }
319 }
320
321 class K8sProfile {
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"
330
331     override fun toString(): String {
332         return "$rbName:$rbVersion:$profileName"
333     }
334
335     override fun equals(other: Any?): Boolean {
336         if (this === other) return true
337         if (javaClass != other?.javaClass) return false
338         return true
339     }
340
341     override fun hashCode(): Int {
342         return javaClass.hashCode()
343     }
344 }