Update INFO.yaml
[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.util.LinkedHashMap
46 import java.io.IOException
47 import java.io.File
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
57
58 open class K8sProfileUpload : AbstractScriptComponentFunction() {
59
60     private val log = LoggerFactory.getLogger(K8sProfileUpload::class.java)!!
61
62     override fun getName(): String {
63         return "K8sProfileUpload"
64     }
65
66     override suspend fun processNB(executionRequest: ExecutionServiceInput) {
67         log.info("executing K8s Profile Upload script")
68
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()
72
73         val prefixList: ArrayList<String> = getTemplatePrefixList(executionRequest)
74
75         for (prefix in prefixList) {
76             if (prefix.toLowerCase().equals("vnf")) {
77                 log.info("For vnf-level resource-assignment, profile upload is not performed")
78                 continue
79             }
80
81             val assignmentParams = getDynamicProperties("assignment-params")
82             val payloadObject = JacksonUtils.jsonNode(assignmentParams.get(prefix).asText()) as ObjectNode
83
84             log.info("Uploading K8S profile for template prefix $prefix")
85
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")
90
91             val api = K8sApi(k8sApiUsername, k8sApiPassword, baseK8sApiUrl, vfModuleModelInvariantUuid, vfModuleModelUuid)
92
93             if (!api.hasDefinition()) {
94                 throw BluePrintProcessorException("K8s RB Definition (${vfModuleModelInvariantUuid}/${vfModuleModelUuid}) not found ")
95             }
96
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")
100             }
101             if (k8sRbProfileName.equals("default") and api.hasProfile(k8sRbProfileName)) {
102                 log.info("Using default profile - skipping upload")
103             } else {
104                 if (api.hasProfile(k8sRbProfileName)) {
105                     log.info("Profile Already Existing - skipping upload")
106                 } else {
107                     val profileFilePath: Path = prepareProfileFile(k8sRbProfileName, prefix.equals("vpkg"))
108
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)
116
117                     log.info("K8s Profile Upload Completed")
118                 }
119             }
120         }
121     }
122
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}")
128
129         val profileFile = profileFilePath.toFile()
130
131         if (!profileFile.exists())
132             throw BluePrintProcessorException("K8s Profile template file ${profileFilePath} does not exists")
133
134         val tempMainPath: File = createTempDir("k8s-profile-", "")
135         val tempProfilePath: File = createTempDir("${k8sRbProfileName}-", "", tempMainPath)
136         log.info("Decompressing profile to ${tempProfilePath.toString()}")
137
138         val decompressedProfile: File = BluePrintArchiveUtils.deCompress(profileFilePath.toFile(),
139                 "${tempProfilePath.toString()}", ArchiveType.TarGz)
140
141         log.info("${profileFilePath.toString()} decompression completed")
142
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")
149
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)
169                 }
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")
174             }
175         }
176
177         profileFilePath = Paths.get(tempMainPath.toString().plus(File.separator).plus("${k8sRbProfileName}.tar.gz"))
178
179         if (!BluePrintArchiveUtils.compress(decompressedProfile, profileFilePath.toFile(),
180                         ArchiveType.TarGz)) {
181             throw BluePrintProcessorException("Profile compression has failed")
182         }
183
184         log.info("${profileFilePath.toString()} compression completed")
185
186         return profileFilePath
187     }
188
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())
193         return result
194     }
195
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()
200             }
201         }
202         return ""
203     }
204
205     override suspend fun recoverNB(runtimeException: RuntimeException, executionRequest: ExecutionServiceInput) {
206         log.info("Executing Recovery")
207         bluePrintRuntimeService.getBluePrintError().addError("${runtimeException.message}")
208     }
209
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
213
214         init {
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
225
226             this.service = UploadFileRestClientService(basicAuthRestClientProperties)
227         }
228
229         fun hasDefinition(): Boolean {
230             try {
231                 val result: BlueprintWebClientService.WebClientResponse<String> = service.exchangeResource(HttpMethod.GET.name, "", "")
232                 print(result)
233                 if (result.status >= 200 && result.status < 300)
234                     return true
235                 else
236                     return false
237             } catch (e: Exception) {
238                 log.info("Caught exception trying to get k8s rb definition")
239                 throw BluePrintProcessorException("${e.message}")
240             }
241         }
242
243         fun hasProfile(profileName: String): Boolean {
244             try {
245                 val result: BlueprintWebClientService.WebClientResponse<String> = service.exchangeResource(HttpMethod.GET.name,
246                         "/profile/$profileName", "")
247                 if (result.status >= 200 && result.status < 300)
248                     return true
249                 else {
250                     print(result)
251                     return false
252                 }
253             } catch (e: Exception) {
254                 log.info("Caught exception trying to get k8s rb profile")
255                 throw BluePrintProcessorException("${e.message}")
256             }
257         }
258
259         fun createProfile(profile: K8sProfile) {
260             val objectMapper = ObjectMapper()
261             val profileJsonString: String = objectMapper.writeValueAsString(profile)
262             try {
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)
267                 }
268             } catch (e: Exception) {
269                 log.info("Caught exception trying to create k8s rb profile ${profile.profileName}")
270                 throw BluePrintProcessorException("${e.message}")
271             }
272         }
273
274         fun uploadProfileContent(profile: K8sProfile, filePath: Path) {
275             try {
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)
280                 }
281             } catch (e: Exception) {
282                 log.info("Caught exception trying to upload k8s rb profile ${profile.profileName}")
283                 throw BluePrintProcessorException("${e.message}")
284             }
285         }
286     }
287 }
288
289 class UploadFileRestClientService(
290         private val restClientProperties:
291         BasicAuthRestClientProperties
292 ) : BlueprintWebClientService {
293
294     override fun defaultHeaders(): Map<String, String> {
295
296         val encodedCredentials = setBasicAuth(
297                 restClientProperties.username,
298                 restClientProperties.password
299         )
300         return mapOf(
301                 HttpHeaders.CONTENT_TYPE to MediaType.APPLICATION_JSON_VALUE,
302                 HttpHeaders.ACCEPT to MediaType.APPLICATION_JSON_VALUE,
303                 HttpHeaders.AUTHORIZATION to "Basic $encodedCredentials"
304         )
305     }
306
307     override fun host(uri: String): String {
308         return restClientProperties.url + uri
309     }
310
311     override fun convertToBasicHeaders(headers: Map<String, String>):
312             Array<BasicHeader> {
313         val customHeaders: MutableMap<String, String> = headers.toMutableMap()
314         // inject additionalHeaders
315         customHeaders.putAll(verifyAdditionalHeaders(restClientProperties))
316
317         if (!headers.containsKey(HttpHeaders.AUTHORIZATION)) {
318             val encodedCredentials = setBasicAuth(
319                     restClientProperties.username,
320                     restClientProperties.password
321             )
322             customHeaders[HttpHeaders.AUTHORIZATION] =
323                     "Basic $encodedCredentials"
324         }
325         return super.convertToBasicHeaders(customHeaders)
326     }
327
328     private fun setBasicAuth(username: String, password: String): String {
329         val credentialsString = "$username:$password"
330         return Base64.getEncoder().encodeToString(
331                 credentialsString.toByteArray(Charset.defaultCharset())
332         )
333     }
334
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)
342         }
343     }
344
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)
353     }
354 }
355
356 class K8sProfile {
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"
365
366     override fun toString(): String {
367         return "$rbName:$rbVersion:$profileName"
368     }
369
370     override fun equals(other: Any?): Boolean {
371         if (this === other) return true
372         if (javaClass != other?.javaClass) return false
373         return true
374     }
375
376     override fun hashCode(): Int {
377         return javaClass.hashCode()
378     }
379 }