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