Uniform formatting and volume as hostpath
[ealt-edge.git] / mecm / mepm / applcm / broker / pkg / handlers / handlersImpl.go
1 /*
2  * Copyright 2020 Huawei Technologies Co., Ltd.
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 package handlers
17
18 import (
19         "archive/zip"
20         "broker/pkg/handlers/adapter/dbAdapter"
21         "broker/pkg/handlers/adapter/pluginAdapter"
22         "broker/pkg/handlers/model"
23         "bytes"
24         "encoding/json"
25         "fmt"
26         "io"
27         "io/ioutil"
28         "net/http"
29         "os"
30         "path/filepath"
31         "strings"
32
33         "github.com/buger/jsonparser"
34         "github.com/ghodss/yaml"
35         "github.com/google/uuid"
36         "github.com/gorilla/mux"
37         "github.com/sirupsen/logrus"
38         "google.golang.org/grpc/codes"
39         "google.golang.org/grpc/status"
40 )
41
42 // Handler of REST APIs
43 type HandlerImpl struct {
44         logger    *logrus.Logger
45         dbAdapter *dbAdapter.DbAdapter
46 }
47
48 // Creates handler implementation
49 func newHandlerImpl(logger *logrus.Logger) (impl HandlerImpl) {
50         impl.logger = logger
51         impl.dbAdapter = dbAdapter.NewDbAdapter(logger)
52         impl.dbAdapter.CreateDatabase()
53         return
54 }
55
56 // Uploads package
57 func (impl *HandlerImpl) UploadPackage(w http.ResponseWriter, r *http.Request) {
58
59         file, header, err := r.FormFile("file")
60         defer file.Close()
61         if err != nil {
62                 respondError(w, http.StatusBadRequest, err.Error())
63                 return
64         }
65
66         buf := bytes.NewBuffer(nil)
67         if _, err := io.Copy(buf, file); err != nil {
68                 respondError(w, http.StatusBadRequest, err.Error())
69                 return
70         }
71
72         var packageName = ""
73         f := strings.Split(header.Filename, ".")
74         if len(f) > 0 {
75                 packageName = f[0]
76         }
77         impl.logger.Infof(packageName)
78
79         pkgPath := PackageFolderPath + header.Filename
80         newFile, err := os.Create(pkgPath)
81         if err != nil {
82                 respondError(w, http.StatusInternalServerError, err.Error())
83                 return
84         }
85
86         defer newFile.Close()
87         if _, err := newFile.Write(buf.Bytes()); err != nil {
88                 respondError(w, http.StatusInternalServerError, err.Error())
89                 return
90         }
91
92         /* Unzip package to decode appDescriptor */
93         impl.openPackage(w, pkgPath)
94
95         var yamlFile = PackageFolderPath + packageName + "/Definitions/" + "MainServiceTemplate.yaml"
96         appPkgInfo := impl.decodeApplicationDescriptor(w, yamlFile)
97         appPkgInfo.AppPackage = header.Filename
98         appPkgInfo.OnboardingState = "ONBOARDED"
99
100         impl.logger.Infof("Application package info from package")
101         defer r.Body.Close()
102
103         impl.dbAdapter.InsertAppPackageInfo(appPkgInfo)
104
105         /*http.StatusOK*/
106         respondJSON(w, http.StatusCreated, appPkgInfo)
107 }
108
109 // Opens package
110 func (impl *HandlerImpl) openPackage(w http.ResponseWriter, packagePath string) {
111         zipReader, _ := zip.OpenReader(packagePath)
112         for _, file := range zipReader.Reader.File {
113
114                 zippedFile, err := file.Open()
115                 if err != nil {
116                         respondError(w, http.StatusBadRequest, err.Error())
117                 }
118                 defer zippedFile.Close()
119
120                 targetDir := PackageFolderPath + "/"
121                 extractedFilePath := filepath.Join(
122                         targetDir,
123                         file.Name,
124                 )
125
126                 if file.FileInfo().IsDir() {
127                         os.MkdirAll(extractedFilePath, file.Mode())
128                 } else {
129                         outputFile, err := os.OpenFile(
130                                 extractedFilePath,
131                                 os.O_WRONLY|os.O_CREATE|os.O_TRUNC,
132                                 file.Mode(),
133                         )
134                         if err != nil {
135                                 respondError(w, http.StatusBadRequest, err.Error())
136                         }
137                         defer outputFile.Close()
138
139                         _, err = io.Copy(outputFile, zippedFile)
140                         if err != nil {
141                                 respondError(w, http.StatusBadRequest, err.Error())
142                         }
143                 }
144         }
145 }
146
147 // Decodes application descriptor
148 func (impl *HandlerImpl) decodeApplicationDescriptor(w http.ResponseWriter, serviceTemplate string) model.AppPackageInfo {
149
150         yamlFile, err := ioutil.ReadFile(serviceTemplate)
151         if err != nil {
152                 respondError(w, http.StatusBadRequest, err.Error())
153         }
154
155         jsondata, err := yaml.YAMLToJSON(yamlFile)
156         if err != nil {
157                 respondError(w, http.StatusBadRequest, err.Error())
158         }
159
160         appDId, _, _, _ := jsonparser.Get(jsondata, "topology_template", "node_templates", "face_recognition", "properties", "appDId")
161         appProvider, _, _, _ := jsonparser.Get(jsondata, "topology_template", "node_templates", "face_recognition", "properties", "appProvider")
162         appInfoName, _, _, _ := jsonparser.Get(jsondata, "topology_template", "node_templates", "face_recognition", "properties", "appInfoName")
163         appSoftVersion, _, _, _ := jsonparser.Get(jsondata, "topology_template", "node_templates", "face_recognition", "properties", "appSoftVersion")
164         appDVersion, _, _, _ := jsonparser.Get(jsondata, "topology_template", "node_templates", "face_recognition", "properties", "appDVersion")
165         deployType, _, _, _ := jsonparser.Get(jsondata, "topology_template", "node_templates", "face_recognition", "properties", "type")
166
167         appPkgInfo := model.AppPackageInfo{
168                 ID:                 string(appDId),
169                 AppDID:             string(appDId),
170                 AppProvider:        string(appProvider),
171                 AppName:            string(appInfoName),
172                 AppSoftwareVersion: string(appSoftVersion),
173                 AppDVersion:        string(appDVersion),
174                 DeployType:         string(deployType),
175         }
176
177         //return appPackageInfo
178         return appPkgInfo
179 }
180
181 // Query application package information
182 func (impl *HandlerImpl) QueryAppPackageInfo(w http.ResponseWriter, r *http.Request) {
183         params := mux.Vars(r)
184         appPkgId := params["appPkgId"]
185         appPkgInfo := impl.dbAdapter.GetAppPackageInfo(appPkgId)
186         if appPkgInfo.ID == "" {
187                 respondJSON(w, http.StatusNotFound, "ID not exist")
188                 return
189         }
190         respondJSON(w, http.StatusAccepted, json.NewEncoder(w).Encode(appPkgInfo))
191 }
192
193 // Deletes application package
194 func (impl *HandlerImpl) DeleteAppPackage(w http.ResponseWriter, r *http.Request) {
195         params := mux.Vars(r)
196         appPkgId := params["appPkgId"]
197         appPackageInfo := impl.dbAdapter.GetAppPackageInfo(appPkgId)
198         if appPackageInfo.ID == "" {
199                 respondJSON(w, http.StatusNotFound, "ID not exist")
200                 return
201         }
202         impl.dbAdapter.DeleteAppPackageInfo(appPkgId)
203
204         deletePackage := PackageFolderPath + appPackageInfo.AppPackage
205
206         /* Delete ZIP*/
207         os.Remove(deletePackage)
208         f := strings.Split(appPackageInfo.AppPackage, ".")
209         if len(f) > 0 {
210                 packageName := f[0]
211                 /*Delete unzipped*/
212                 os.Remove(packageName)
213         }
214         respondJSON(w, http.StatusAccepted, json.NewEncoder(w).Encode(""))
215 }
216
217 // Creates application instance
218 func (impl *HandlerImpl) CreateAppInstance(w http.ResponseWriter, r *http.Request) {
219         var req model.CreateApplicationReq
220         err := json.NewDecoder(r.Body).Decode(&req)
221         if err != nil {
222                 respondError(w, http.StatusInternalServerError, err.Error())
223                 return
224         }
225
226         appPkgInfo := impl.dbAdapter.GetAppPackageInfo(req.AppDID)
227         if appPkgInfo.ID == "" {
228                 respondJSON(w, http.StatusNotFound, "ID not exist")
229                 return
230         }
231         impl.logger.Infof("Query appPkg Info:", appPkgInfo)
232
233         appInstanceId, err := uuid.NewUUID()
234         if err != nil {
235                 respondError(w, http.StatusInternalServerError, err.Error())
236         }
237
238         appInstanceInfo := model.AppInstanceInfo{
239
240                 ID:                     appInstanceId.String(),
241                 AppInstanceName:        req.AppInstancename,
242                 AppInstanceDescription: req.AppInstanceDescriptor,
243                 AppDID:                 req.AppDID,
244                 AppProvider:            appPkgInfo.AppProvider,
245                 AppName:                appPkgInfo.AppName,
246                 AppSoftVersion:         appPkgInfo.AppSoftwareVersion,
247                 AppDVersion:            appPkgInfo.AppDVersion,
248                 AppPkgID:               appPkgInfo.AppDID,
249                 InstantiationState:     "NOT_INSTANTIATED",
250         }
251         impl.dbAdapter.InsertAppInstanceInfo(appInstanceInfo)
252         impl.logger.Infof("CreateAppInstance:", req)
253         /*http.StatusOK*/
254         respondJSON(w, http.StatusCreated, json.NewEncoder(w).Encode(appInstanceInfo))
255 }
256
257 // Instantiates application instance
258 func (impl *HandlerImpl) InstantiateAppInstance(w http.ResponseWriter, r *http.Request) {
259         var req model.InstantiateApplicationReq
260         err := json.NewDecoder(r.Body).Decode(&req)
261         if err != nil {
262                 respondError(w, http.StatusInternalServerError, err.Error())
263                 return
264         }
265
266         params := mux.Vars(r)
267         appInstanceId := params["appInstanceId"]
268
269         appInstanceInfo := impl.dbAdapter.GetAppInstanceInfo(appInstanceId)
270         appPackageInfo := impl.dbAdapter.GetAppPackageInfo(appInstanceInfo.AppDID)
271         if appInstanceInfo.ID == "" || appPackageInfo.ID == "" {
272                 respondJSON(w, http.StatusNotFound, "ID not exist")
273                 return
274         }
275
276         if appInstanceInfo.InstantiationState == "INSTANTIATED" {
277                 respondError(w, http.StatusInternalServerError, "Application already instantiated")
278                 return
279         }
280
281         //remove extension
282         var packageName = ""
283         f := strings.Split(appPackageInfo.AppPackage, ".")
284         if len(f) > 0 {
285                 packageName = f[0]
286         }
287         impl.logger.Infof(packageName)
288
289         var artifact string
290         var pluginInfo string
291
292         switch appPackageInfo.DeployType {
293         case "helm":
294                 pkgPath := PackageFolderPath + packageName + PackageArtifactPath + "Charts"
295                 artifact = impl.getDeploymentArtifact(pkgPath, ".tar")
296                 if artifact == "" {
297                         respondError(w, http.StatusInternalServerError, "artifact not available in application package")
298                         return
299                 }
300                 pluginInfo = "helmplugin" + ":" + os.Getenv("HELM_PLUGIN_PORT")
301                 impl.logger.Infof("Plugin Info ", pluginInfo)
302         case "kubernetes":
303                 pkgPath := PackageFolderPath + packageName + PackageArtifactPath + "Kubernetes"
304                 artifact = impl.getDeploymentArtifact(pkgPath, "*.yaml")
305                 if artifact == "" {
306                         respondError(w, http.StatusInternalServerError, "artifact not available in application package")
307                         return
308                 }
309                 pluginInfo = "kubernetes.plugin" + ":" + os.Getenv("KUBERNETES_PLUGIN_PORT")
310         default:
311                 respondError(w, http.StatusInternalServerError, "Deployment type not supported")
312                 return
313         }
314         impl.logger.Infof("Artifact to deploy:", artifact)
315
316         adapter := pluginAdapter.NewPluginAdapter(pluginInfo, impl.logger)
317         workloadId, err, resStatus := adapter.Instantiate(pluginInfo, req.SelectedMECHostInfo.HostID, artifact)
318         if err != nil {
319                 st, ok := status.FromError(err)
320                 if ok && st.Code() == codes.InvalidArgument {
321                         respondError(w, http.StatusBadRequest, err.Error())
322                         return
323                 } else {
324                         respondError(w, http.StatusInternalServerError, err.Error())
325                 }
326         }
327
328         if resStatus == "Failure" {
329                 respondError(w, http.StatusInternalServerError, err.Error())
330         }
331
332         impl.dbAdapter.UpdateAppInstanceInfoInstStatusHostAndWorkloadId(appInstanceId, "INSTANTIATED", req.SelectedMECHostInfo.HostID, workloadId)
333         respondJSON(w, http.StatusAccepted, json.NewEncoder(w).Encode(workloadId))
334 }
335
336 // Gets deployment artifact
337 func (impl *HandlerImpl) getDeploymentArtifact(dir string, ext string) string {
338         d, err := os.Open(dir)
339         if err != nil {
340                 impl.logger.Infof("Error: ", err)
341                 return ""
342         }
343         defer d.Close()
344
345         files, err := d.Readdir(-1)
346         if err != nil {
347                 impl.logger.Infof("Error: ", err)
348                 return ""
349         }
350
351         impl.logger.Infof("Directory to read " + dir)
352
353         for _, file := range files {
354                 if file.Mode().IsRegular() {
355                         if filepath.Ext(file.Name()) == ext || filepath.Ext(file.Name()) == ".gz" {
356                                 impl.logger.Infof(file.Name())
357                                 impl.logger.Infof(dir + "/" + file.Name())
358                                 return dir + "/" + file.Name()
359                         }
360                 }
361         }
362         return ""
363 }
364
365 // Queries application instance information
366 func (impl *HandlerImpl) QueryAppInstanceInfo(w http.ResponseWriter, r *http.Request) {
367
368         params := mux.Vars(r)
369         appInstanceId := params["appInstanceId"]
370
371         appInstanceInfo := impl.dbAdapter.GetAppInstanceInfo(appInstanceId)
372         appPackageInfo := impl.dbAdapter.GetAppPackageInfo(appInstanceInfo.AppDID)
373         if appInstanceInfo.ID == "" || appPackageInfo.ID == "" {
374                 respondJSON(w, http.StatusNotFound, "ID not exist")
375                 return
376         }
377         var instantiatedAppState string
378         if appInstanceInfo.InstantiationState == "INSTANTIATED" {
379
380                 var pluginInfo string
381
382                 switch appPackageInfo.DeployType {
383                 case "helm":
384                         pluginInfo = "helmplugin" + ":" + os.Getenv("HELM_PLUGIN_PORT")
385                 case "kubernetes":
386                         pluginInfo = "kubernetes.plugin" + ":" + os.Getenv("KUBERNETES_PLUGIN_PORT")
387                 default:
388                         respondError(w, http.StatusInternalServerError, "Deployment type not supported")
389                         return
390                 }
391
392                 adapter := pluginAdapter.NewPluginAdapter(pluginInfo, impl.logger)
393                 state, err := adapter.Query(pluginInfo, appInstanceInfo.Host, appInstanceInfo.WorkloadID)
394                 if err != nil {
395                         respondError(w, http.StatusInternalServerError, err.Error())
396                         return
397                 }
398                 instantiatedAppState = state
399         }
400         appInstanceInfo.InstantiatedAppState = instantiatedAppState
401
402         respondJSON(w, http.StatusCreated, json.NewEncoder(w).Encode(appInstanceInfo))
403 }
404
405 // Queries application lcm operation status
406 func (impl *HandlerImpl) QueryAppLcmOperationStatus(w http.ResponseWriter, r *http.Request) {
407         var req model.QueryApplicationLCMOperStatusReq
408         err := json.NewDecoder(r.Body).Decode(&req)
409         if err != nil {
410                 respondError(w, http.StatusInternalServerError, err.Error())
411                 return
412         }
413
414         fmt.Fprintf(w, "QueryApplicationLCMOperStatus: %+v", req)
415 }
416
417 // Terminates application instance
418 func (impl *HandlerImpl) TerminateAppInstance(w http.ResponseWriter, r *http.Request) {
419         impl.logger.Infof("TerminateAppInstance...")
420         params := mux.Vars(r)
421         appInstanceId := params["appInstanceId"]
422
423         appInstanceInfo := impl.dbAdapter.GetAppInstanceInfo(appInstanceId)
424         appPackageInfo := impl.dbAdapter.GetAppPackageInfo(appInstanceInfo.AppDID)
425         if appInstanceInfo.ID == "" || appPackageInfo.ID == "" {
426                 respondJSON(w, http.StatusNotFound, "ID not exist")
427                 return
428         }
429
430         if appInstanceInfo.InstantiationState == "NOT_INSTANTIATED" {
431                 respondError(w, http.StatusNotAcceptable, "instantiationState: NOT_INSTANTIATED")
432                 return
433         }
434
435         var pluginInfo string
436         switch appPackageInfo.DeployType {
437         case "helm":
438                 pluginInfo = "helmplugin" + ":" + os.Getenv("HELM_PLUGIN_PORT")
439         case "kubernetes":
440                 pluginInfo = "kubernetes.plugin" + ":" + os.Getenv("KUBERNETES_PLUGIN_PORT")
441         default:
442                 respondError(w, http.StatusInternalServerError, "Deployment type not supported")
443                 return
444         }
445
446         adapter := pluginAdapter.NewPluginAdapter(pluginInfo, impl.logger)
447         _, err := adapter.Terminate(pluginInfo, appInstanceInfo.Host, appInstanceInfo.WorkloadID)
448         if err != nil {
449                 respondError(w, http.StatusInternalServerError, err.Error())
450                 return
451         }
452         impl.dbAdapter.UpdateAppInstanceInfoInstStatusAndWorkload(appInstanceId, "NOT_INSTANTIATED", "")
453
454         respondJSON(w, http.StatusAccepted, json.NewEncoder(w).Encode(""))
455 }
456
457 // Deletes application instance identifier
458 func (impl *HandlerImpl) DeleteAppInstanceIdentifier(w http.ResponseWriter, r *http.Request) {
459         impl.logger.Infof("DeleteAppInstanceIdentifier:")
460         params := mux.Vars(r)
461         appInstanceId := params["appInstanceId"]
462
463         impl.dbAdapter.DeleteAppInstanceInfo(appInstanceId)
464         respondJSON(w, http.StatusOK, json.NewEncoder(w).Encode(""))
465 }
466
467 // It makes the JSON
468 func respondJSON(w http.ResponseWriter, status int, payload interface{}) {
469         response, err := json.Marshal(payload)
470         if err != nil {
471                 w.WriteHeader(http.StatusInternalServerError)
472                 w.Write([]byte(err.Error()))
473                 return
474         }
475         w.Header().Set("Content-Type", "application/json")
476         w.WriteHeader(status)
477         w.Write([]byte(response))
478 }
479
480 // RespondError makes the error response with payload as json format
481 func respondError(w http.ResponseWriter, code int, message string) {
482         respondJSON(w, code, map[string]string{"error": message})
483 }