K8s Profile Upload
[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.functions.resource.resolution.storedContentFromResolvedArtifactNB
23 import org.onap.ccsdk.cds.blueprintsprocessor.rest.BasicAuthRestClientProperties
24 import org.onap.ccsdk.cds.blueprintsprocessor.rest.RestClientProperties
25 import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BasicAuthRestClientService
26 import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService
27 import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.RestLoggerService
28 import org.onap.ccsdk.cds.blueprintsprocessor.services.execution.AbstractScriptComponentFunction
29 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
30 import org.apache.commons.io.IOUtils
31 import org.apache.commons.io.FilenameUtils
32 import org.apache.http.client.entity.EntityBuilder
33 import org.apache.http.entity.ContentType
34 import org.apache.http.message.BasicHeader
35 import org.apache.http.client.methods.HttpPost
36 import org.apache.http.client.methods.HttpUriRequest
37 import org.apache.http.client.ClientProtocolException
38 import org.slf4j.LoggerFactory
39 import org.springframework.http.HttpMethod
40 import org.springframework.web.client.RestTemplate
41 import com.fasterxml.jackson.annotation.JsonIgnore
42 import com.fasterxml.jackson.annotation.JsonProperty
43 import java.util.ArrayList
44 import java.io.IOException
45 import java.io.File
46 import java.nio.file.Files
47 import java.nio.file.Paths
48 import java.nio.file.Path
49 import org.springframework.http.HttpHeaders
50 import org.springframework.http.MediaType
51 import java.nio.charset.Charset
52 import java.util.Base64
53 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException
54
55 import java.io.BufferedInputStream
56 import java.io.FileInputStream
57 import java.io.FileOutputStream
58 import java.util.zip.GZIPInputStream
59
60 open class K8sProfileUpload : AbstractScriptComponentFunction() {
61
62     private val log = LoggerFactory.getLogger(K8sProfileUpload::class.java)!!
63
64     override fun getName(): String {
65         return "K8sProfileUpload"
66     }
67
68     override suspend fun processNB(executionRequest: ExecutionServiceInput) {
69         log.info("executing K8s Profile Upload script")
70         val resolution_key = getDynamicProperties("resolution-key").asText()
71         log.info("resolution_key: $resolution_key")
72
73         val baseK8sApiUrl = getDynamicProperties("api-access").get("url").asText()
74         val k8sApiUsername = getDynamicProperties("api-access").get("username").asText()
75         val k8sApiPassword = getDynamicProperties("api-access").get("password").asText()
76
77         val prefixList: ArrayList<String> = getTemplatePrefixList(executionRequest)
78
79         for (prefix in prefixList) {
80             if (prefix.toLowerCase().equals("vnf"))
81                 continue
82
83             val payload = storedContentFromResolvedArtifactNB(resolution_key, prefix)
84             log.info("Uploading K8S profile for template prefix $prefix")
85
86             val payloadObject = JacksonUtils.jsonNode(payload) as ObjectNode
87
88             val vfModuleModelInvariantUuid: String = getResolvedParameter(payloadObject, "vf_module_model_invariant_uuid")
89             val vfModuleModelUuid: String = getResolvedParameter(payloadObject, "vf_module_model_version")
90             val k8sRbProfileName: String = getResolvedParameter(payloadObject, "k8s-rb-profile-name")
91             val k8sRbProfileNamespace: String = getResolvedParameter(payloadObject, "k8s-rb-profile-namespace")
92
93             val api = K8sApi(k8sApiUsername, k8sApiPassword, baseK8sApiUrl, vfModuleModelInvariantUuid, vfModuleModelUuid)
94
95             if (!api.hasDefinition()) {
96                 throw BluePrintProcessorException("K8s RB Definition (${vfModuleModelInvariantUuid}/${vfModuleModelUuid}) not found ")
97             }
98
99             log.info("k8s-rb-profile-name: $k8sRbProfileName")
100             if (k8sRbProfileName.equals("")) {
101                 log.info("Profile Name Not Defined - skipping upload")
102             } else {
103                 if (api.hasProfile(k8sRbProfileName)) {
104                     log.info("Profile Already Existing - skipping upload")
105                 } else {
106                     profileFilePath = prepareProfileFile(k8sRbProfileName)
107
108                     var profile = K8sProfile()
109                     profile.profileName = k8sRbProfileName
110                     profile.rbName = vfModuleModelInvariantUuid
111                     profile.rbVersion = vfModuleModelUuid
112                     profile.namespace = k8sRbProfileNamespace
113                     api.createProfile(profile)
114                     api.uploadProfileContent(profile, profileFilePath)
115
116                     log.info("K8s Profile Upload Completed")
117                 }
118             }
119         }
120     }
121
122     fun prepareProfileFile(k8sRbProfileName: 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("${k8sRbProfileName}.tar.gz"))
126         log.info("Reading K8s profile file: ${profileFilePath}")
127
128         val profileFile = profileFilePath.toFile()
129
130         if (!profileFile.exists())
131             throw BluePrintProcessorException("K8s Profile template file ${profileFilePath} does not exists")
132
133         return profileFilePath
134     }
135
136     fun getTemplatePrefixList(executionRequest: ExecutionServiceInput): ArrayList<String> {
137         val result = ArrayList<String>()
138         for (prefix in executionRequest.payload.get("resource-assignment-request").get("template-prefix").elements())
139             result.add(prefix.asText())
140         return result
141     }
142
143     fun getResolvedParameter(payload: ObjectNode, keyName: String): String {
144         for (node in payload.get("resource-accumulator-resolved-data").elements()) {
145             if (node.get("param-name").asText().equals(keyName)) {
146                 return node.get("param-value").asText()
147             }
148         }
149         return ""
150     }
151
152     override suspend fun recoverNB(runtimeException: RuntimeException, executionRequest: ExecutionServiceInput) {
153         log.info("Executing Recovery")
154         bluePrintRuntimeService.getBluePrintError().addError("${runtimeException.message}")
155     }
156
157     inner class K8sApi(val username: String, val password: String, val baseUrl: String, val definition: String,
158                        val definitionVersion: String) {
159         private val service: UploadFileRestClientService //BasicAuthRestClientService
160
161         init {
162             var mapOfHeaders = hashMapOf<String, String>()
163             mapOfHeaders.put("Accept", "application/json")
164             mapOfHeaders.put("Content-Type", "application/json")
165             mapOfHeaders.put("cache-control", " no-cache")
166             mapOfHeaders.put("Accept", "application/json")
167             var basicAuthRestClientProperties: BasicAuthRestClientProperties = BasicAuthRestClientProperties()
168             basicAuthRestClientProperties.username = username
169             basicAuthRestClientProperties.password = password
170             basicAuthRestClientProperties.url = "$baseUrl/api/multicloud-k8s/v1/v1/rb/definition/${definition}/${definitionVersion}"
171             basicAuthRestClientProperties.additionalHeaders = mapOfHeaders
172
173             this.service = UploadFileRestClientService(basicAuthRestClientProperties)
174         }
175
176         fun hasDefinition(): Boolean {
177             try {
178                 val result: BlueprintWebClientService.WebClientResponse<String> = service.exchangeResource(HttpMethod.GET.name, "", "")
179                 print(result)
180                 if (result.status >= 200 && result.status < 300)
181                     return true
182                 else
183                     return false
184             } catch (e: Exception) {
185                 log.info("Caught exception trying to get k8s rb definition")
186                 throw BluePrintProcessorException("${e.message}")
187             }
188         }
189
190         fun hasProfile(profileName: String): Boolean {
191             try {
192                 val result: BlueprintWebClientService.WebClientResponse<String> = service.exchangeResource(HttpMethod.GET.name,
193                         "/profile/$profileName", "")
194                 if (result.status >= 200 && result.status < 300)
195                     return true
196                 else {
197                     print(result)
198                     return false
199                 }
200             } catch (e: Exception) {
201                 log.info("Caught exception trying to get k8s rb profile")
202                 throw BluePrintProcessorException("${e.message}")
203             }
204         }
205
206         fun createProfile(profile: K8sProfile) {
207             val objectMapper = ObjectMapper()
208             val profileJsonString: String = objectMapper.writeValueAsString(profile)
209             try {
210                 val result: BlueprintWebClientService.WebClientResponse<String> = service.exchangeResource(HttpMethod.POST.name,
211                         "/profile", profileJsonString)
212                 if (result.status < 200 || result.status >= 300) {
213                     throw Exception(result.body)
214                 }
215             } catch (e: Exception) {
216                 log.info("Caught exception trying to create k8s rb profile ${profile.profileName}")
217                 throw BluePrintProcessorException("${e.message}")
218             }
219         }
220
221         fun uploadProfileContent(profile: K8sProfile, filePath: Path) {
222             try {
223                 val result: BlueprintWebClientService.WebClientResponse<String> = service.uploadBinaryFile(
224                         "/profile/${profile.profileName}/content", filePath)
225                 if (result.status < 200 || result.status >= 300) {
226                     throw Exception(result.body)
227                 }
228             } catch (e: Exception) {
229                 log.info("Caught exception trying to upload k8s rb profile ${profile.profileName}")
230                 throw BluePrintProcessorException("${e.message}")
231             }
232         }
233     }
234 }
235
236 class UploadFileRestClientService(
237         private val restClientProperties:
238         BasicAuthRestClientProperties
239 ) : BlueprintWebClientService {
240
241     override fun defaultHeaders(): Map<String, String> {
242
243         val encodedCredentials = setBasicAuth(
244                 restClientProperties.username,
245                 restClientProperties.password
246         )
247         return mapOf(
248                 HttpHeaders.CONTENT_TYPE to MediaType.APPLICATION_JSON_VALUE,
249                 HttpHeaders.ACCEPT to MediaType.APPLICATION_JSON_VALUE,
250                 HttpHeaders.AUTHORIZATION to "Basic $encodedCredentials"
251         )
252     }
253
254     override fun host(uri: String): String {
255         return restClientProperties.url + uri
256     }
257
258     override fun convertToBasicHeaders(headers: Map<String, String>):
259             Array<BasicHeader> {
260         val customHeaders: MutableMap<String, String> = headers.toMutableMap()
261         // inject additionalHeaders
262         customHeaders.putAll(verifyAdditionalHeaders(restClientProperties))
263
264         if (!headers.containsKey(HttpHeaders.AUTHORIZATION)) {
265             val encodedCredentials = setBasicAuth(
266                     restClientProperties.username,
267                     restClientProperties.password
268             )
269             customHeaders[HttpHeaders.AUTHORIZATION] =
270                     "Basic $encodedCredentials"
271         }
272         return super.convertToBasicHeaders(customHeaders)
273     }
274
275     private fun setBasicAuth(username: String, password: String): String {
276         val credentialsString = "$username:$password"
277         return Base64.getEncoder().encodeToString(
278                 credentialsString.toByteArray(Charset.defaultCharset())
279         )
280     }
281
282     @Throws(IOException::class, ClientProtocolException::class)
283     private fun performHttpCall(httpUriRequest: HttpUriRequest): BlueprintWebClientService.WebClientResponse<String> {
284         val httpResponse = httpClient().execute(httpUriRequest)
285         val statusCode = httpResponse.statusLine.statusCode
286         httpResponse.entity.content.use {
287             val body = IOUtils.toString(it, Charset.defaultCharset())
288             return BlueprintWebClientService.WebClientResponse(statusCode, body)
289         }
290     }
291
292     fun uploadBinaryFile(path: String, filePath: Path): BlueprintWebClientService.WebClientResponse<String> {
293         val convertedHeaders: Array<BasicHeader> = convertToBasicHeaders(defaultHeaders())
294         val httpPost = HttpPost(host(path))
295         val entity = EntityBuilder.create().setBinary(Files.readAllBytes(filePath)).build()
296         httpPost.setEntity(entity)
297         RestLoggerService.httpInvoking(convertedHeaders)
298         httpPost.setHeaders(convertedHeaders)
299         return performHttpCall(httpPost)
300     }
301 }
302
303 class K8sProfile {
304     @get:JsonProperty("rb-name")
305     var rbName: String? = null
306     @get:JsonProperty("rb-version")
307     var rbVersion: String? = null
308     @get:JsonProperty("profile-name")
309     var profileName: String? = null
310     @get:JsonProperty("namespace")
311     var namespace: String? = "default"
312
313     override fun toString(): String {
314         return "$rbName:$rbVersion:$profileName"
315     }
316
317     override fun equals(other: Any?): Boolean {
318         if (this === other) return true
319         if (javaClass != other?.javaClass) return false
320         return true
321     }
322
323     override fun hashCode(): Int {
324         return javaClass.hashCode()
325     }
326 }