Add vnf definition APIs
[multicloud/k8s.git] / src / k8splugin / api / handler.go
1 /*
2 Copyright 2018 Intel Corporation.
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6     http://www.apache.org/licenses/LICENSE-2.0
7 Unless required by applicable law or agreed to in writing, software
8 distributed under the License is distributed on an "AS IS" BASIS,
9 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 See the License for the specific language governing permissions and
11 limitations under the License.
12 */
13
14 package api
15
16 import (
17         "encoding/json"
18         "errors"
19         "log"
20         "net/http"
21         "os"
22         "strings"
23
24         "github.com/gorilla/mux"
25         pkgerrors "github.com/pkg/errors"
26         "k8s.io/client-go/kubernetes"
27
28         "k8splugin/csar"
29         "k8splugin/db"
30         "k8splugin/krd"
31 )
32
33 // GetVNFClient retrieves the client used to communicate with a Kubernetes Cluster
34 var GetVNFClient = func(kubeConfigPath string) (kubernetes.Clientset, error) {
35         client, err := krd.GetKubeClient(kubeConfigPath)
36         if err != nil {
37                 return client, err
38         }
39         return client, nil
40 }
41
42 func validateBody(body interface{}) error {
43         switch b := body.(type) {
44         case CreateVnfRequest:
45                 if b.CloudRegionID == "" {
46                         werr := pkgerrors.Wrap(errors.New("Invalid/Missing CloudRegionID in POST request"), "CreateVnfRequest bad request")
47                         return werr
48                 }
49                 if b.CsarID == "" {
50                         werr := pkgerrors.Wrap(errors.New("Invalid/Missing CsarID in POST request"), "CreateVnfRequest bad request")
51                         return werr
52                 }
53                 if strings.Contains(b.CloudRegionID, "|") || strings.Contains(b.Namespace, "|") {
54                         werr := pkgerrors.Wrap(errors.New("Character \"|\" not allowed in CSAR ID"), "CreateVnfRequest bad request")
55                         return werr
56                 }
57         case UpdateVnfRequest:
58                 if b.CloudRegionID == "" || b.CsarID == "" {
59                         werr := pkgerrors.Wrap(errors.New("Invalid/Missing Data in PUT request"), "UpdateVnfRequest bad request")
60                         return werr
61                 }
62         }
63         return nil
64 }
65
66 // CreateHandler is the POST method creates a new VNF instance resource.
67 func CreateHandler(w http.ResponseWriter, r *http.Request) {
68         var resource CreateVnfRequest
69
70         if r.Body == nil {
71                 http.Error(w, "Body empty", http.StatusBadRequest)
72                 return
73         }
74
75         err := json.NewDecoder(r.Body).Decode(&resource)
76         if err != nil {
77                 http.Error(w, err.Error(), http.StatusUnprocessableEntity)
78                 return
79         }
80
81         err = validateBody(resource)
82         if err != nil {
83                 http.Error(w, err.Error(), http.StatusUnprocessableEntity)
84                 return
85         }
86
87         // (TODO): Read kubeconfig for specific Cloud Region from local file system
88         // if present or download it from AAI
89         // err := DownloadKubeConfigFromAAI(resource.CloudRegionID, os.Getenv("KUBE_CONFIG_DIR")
90         kubeclient, err := GetVNFClient(os.Getenv("KUBE_CONFIG_DIR") + "/" + resource.CloudRegionID)
91         if err != nil {
92                 http.Error(w, err.Error(), http.StatusInternalServerError)
93                 return
94         }
95
96         /*
97                 uuid,
98                 {
99                         "deployment": ["cloud1-default-uuid-sisedeploy1", "cloud1-default-uuid-sisedeploy2", ... ]
100                         "service": ["cloud1-default-uuid-sisesvc1", "cloud1-default-uuid-sisesvc2", ... ]
101                 },
102                 nil
103         */
104         externalVNFID, resourceNameMap, err := csar.CreateVNF(resource.CsarID, resource.CloudRegionID, resource.Namespace, &kubeclient)
105         if err != nil {
106                 werr := pkgerrors.Wrap(err, "Read Kubernetes Data information error")
107                 http.Error(w, werr.Error(), http.StatusInternalServerError)
108                 return
109         }
110
111         // cloud1-default-uuid
112         internalVNFID := resource.CloudRegionID + "-" + resource.Namespace + "-" + externalVNFID
113
114         // Persist in AAI database.
115         log.Printf("Cloud Region ID: %s, Namespace: %s, VNF ID: %s ", resource.CloudRegionID, resource.Namespace, externalVNFID)
116
117         // TODO: Uncomment when annotations are done
118         // krd.AddNetworkAnnotationsToPod(kubeData, resource.Networks)
119
120         // "{"deployment":<>,"service":<>}"
121         serializedResourceNameMap, err := db.Serialize(resourceNameMap)
122         if err != nil {
123                 werr := pkgerrors.Wrap(err, "Create VNF deployment JSON Marshalling error")
124                 http.Error(w, werr.Error(), http.StatusInternalServerError)
125                 return
126         }
127
128         // key: cloud1-default-uuid
129         // value: "{"deployment":<>,"service":<>}"
130         err = db.DBconn.CreateEntry(internalVNFID, serializedResourceNameMap)
131         if err != nil {
132                 werr := pkgerrors.Wrap(err, "Create VNF deployment DB error")
133                 http.Error(w, werr.Error(), http.StatusInternalServerError)
134                 return
135         }
136
137         resp := CreateVnfResponse{
138                 VNFID:         externalVNFID,
139                 CloudRegionID: resource.CloudRegionID,
140                 Namespace:     resource.Namespace,
141                 VNFComponents: resourceNameMap,
142         }
143
144         w.Header().Set("Content-Type", "application/json")
145         w.WriteHeader(http.StatusCreated)
146         json.NewEncoder(w).Encode(resp)
147 }
148
149 // ListHandler the existing VNF instances created in a given Kubernetes cluster
150 func ListHandler(w http.ResponseWriter, r *http.Request) {
151         vars := mux.Vars(r)
152
153         cloudRegionID := vars["cloudRegionID"]
154         namespace := vars["namespace"]
155         prefix := cloudRegionID + "-" + namespace
156
157         internalVNFIDs, err := db.DBconn.ReadAll(prefix)
158         if err != nil {
159                 werr := pkgerrors.Wrap(err, "Get VNF list error")
160                 http.Error(w, werr.Error(), http.StatusInternalServerError)
161                 return
162         }
163
164         if len(internalVNFIDs) == 0 {
165                 w.WriteHeader(http.StatusNotFound)
166                 return
167         }
168
169         // TODO: There is an edge case where if namespace is passed but is missing some characters
170         // trailing, it will print the result with those excluding characters. This is because of
171         // the way I am trimming the Prefix. This fix is needed.
172
173         var editedList []string
174
175         for _, id := range internalVNFIDs {
176                 if len(id) > 0 {
177                         editedList = append(editedList, strings.TrimPrefix(id, prefix)[1:])
178                 }
179         }
180
181         if len(editedList) == 0 {
182                 editedList = append(editedList, "")
183         }
184
185         resp := ListVnfsResponse{
186                 VNFs: editedList,
187         }
188
189         w.Header().Set("Content-Type", "application/json")
190         w.WriteHeader(http.StatusOK)
191         json.NewEncoder(w).Encode(resp)
192 }
193
194 // DeleteHandler method terminates an individual VNF instance.
195 func DeleteHandler(w http.ResponseWriter, r *http.Request) {
196         vars := mux.Vars(r)
197
198         cloudRegionID := vars["cloudRegionID"] // cloud1
199         namespace := vars["namespace"]         // default
200         externalVNFID := vars["externalVNFID"] // uuid
201
202         // cloud1-default-uuid
203         internalVNFID := cloudRegionID + "-" + namespace + "-" + externalVNFID
204
205         // (TODO): Read kubeconfig for specific Cloud Region from local file system
206         // if present or download it from AAI
207         // err := DownloadKubeConfigFromAAI(resource.CloudRegionID, os.Getenv("KUBE_CONFIG_DIR")
208         kubeclient, err := GetVNFClient(os.Getenv("KUBE_CONFIG_DIR") + "/" + cloudRegionID)
209         if err != nil {
210                 http.Error(w, err.Error(), http.StatusInternalServerError)
211                 return
212         }
213
214         // key: cloud1-default-uuid
215         // value: "{"deployment":<>,"service":<>}"
216         serializedResourceNameMap, found, err := db.DBconn.ReadEntry(internalVNFID)
217         if err != nil {
218                 http.Error(w, err.Error(), http.StatusInternalServerError)
219                 return
220         }
221
222         if found == false {
223                 w.WriteHeader(http.StatusNotFound)
224                 return
225         }
226
227         /*
228                 {
229                         "deployment": ["cloud1-default-uuid-sisedeploy1", "cloud1-default-uuid-sisedeploy2", ... ]
230                         "service": ["cloud1-default-uuid-sisesvc1", "cloud1-default-uuid-sisesvc2", ... ]
231                 },
232         */
233         deserializedResourceNameMap := make(map[string][]string)
234         err = db.DeSerialize(serializedResourceNameMap, &deserializedResourceNameMap)
235         if err != nil {
236                 werr := pkgerrors.Wrap(err, "Delete VNF error")
237                 http.Error(w, werr.Error(), http.StatusInternalServerError)
238                 return
239         }
240
241         err = csar.DestroyVNF(deserializedResourceNameMap, namespace, &kubeclient)
242         if err != nil {
243                 werr := pkgerrors.Wrap(err, "Delete VNF error")
244                 http.Error(w, werr.Error(), http.StatusInternalServerError)
245                 return
246         }
247
248         err = db.DBconn.DeleteEntry(internalVNFID)
249         if err != nil {
250                 werr := pkgerrors.Wrap(err, "Delete VNF error")
251                 http.Error(w, werr.Error(), http.StatusInternalServerError)
252                 return
253         }
254
255         w.Header().Set("Content-Type", "application/json")
256         w.WriteHeader(http.StatusAccepted)
257 }
258
259 // // UpdateHandler method to update a VNF instance.
260 // func UpdateHandler(w http.ResponseWriter, r *http.Request) {
261 //      vars := mux.Vars(r)
262 //      id := vars["vnfInstanceId"]
263
264 //      var resource UpdateVnfRequest
265
266 //      if r.Body == nil {
267 //              http.Error(w, "Body empty", http.StatusBadRequest)
268 //              return
269 //      }
270
271 //      err := json.NewDecoder(r.Body).Decode(&resource)
272 //      if err != nil {
273 //              http.Error(w, err.Error(), http.StatusUnprocessableEntity)
274 //              return
275 //      }
276
277 //      err = validateBody(resource)
278 //      if err != nil {
279 //              http.Error(w, err.Error(), http.StatusUnprocessableEntity)
280 //              return
281 //      }
282
283 //      kubeData, err := utils.ReadCSARFromFileSystem(resource.CsarID)
284
285 //      if kubeData.Deployment == nil {
286 //              werr := pkgerrors.Wrap(err, "Update VNF deployment error")
287 //              http.Error(w, werr.Error(), http.StatusInternalServerError)
288 //              return
289 //      }
290 //      kubeData.Deployment.SetUID(types.UID(id))
291
292 //      if err != nil {
293 //              werr := pkgerrors.Wrap(err, "Update VNF deployment information error")
294 //              http.Error(w, werr.Error(), http.StatusInternalServerError)
295 //              return
296 //      }
297
298 //      // (TODO): Read kubeconfig for specific Cloud Region from local file system
299 //      // if present or download it from AAI
300 //      s, err := NewVNFInstanceService("../kubeconfig/config")
301 //      if err != nil {
302 //              http.Error(w, err.Error(), http.StatusInternalServerError)
303 //              return
304 //      }
305
306 //      err = s.Client.UpdateDeployment(kubeData.Deployment, resource.Namespace)
307 //      if err != nil {
308 //              werr := pkgerrors.Wrap(err, "Update VNF error")
309
310 //              http.Error(w, werr.Error(), http.StatusInternalServerError)
311 //              return
312 //      }
313
314 //      resp := UpdateVnfResponse{
315 //              DeploymentID: id,
316 //      }
317
318 //      w.Header().Set("Content-Type", "application/json")
319 //      w.WriteHeader(http.StatusCreated)
320
321 //      err = json.NewEncoder(w).Encode(resp)
322 //      if err != nil {
323 //              werr := pkgerrors.Wrap(err, "Parsing output of new VNF error")
324 //              http.Error(w, werr.Error(), http.StatusInternalServerError)
325 //      }
326 // }
327
328 // GetHandler retrieves information about a VNF instance by reading an individual VNF instance resource.
329 func GetHandler(w http.ResponseWriter, r *http.Request) {
330         vars := mux.Vars(r)
331
332         cloudRegionID := vars["cloudRegionID"] // cloud1
333         namespace := vars["namespace"]         // default
334         externalVNFID := vars["externalVNFID"] // uuid
335
336         // cloud1-default-uuid
337         internalVNFID := cloudRegionID + "-" + namespace + "-" + externalVNFID
338
339         // key: cloud1-default-uuid
340         // value: "{"deployment":<>,"service":<>}"
341         serializedResourceNameMap, found, err := db.DBconn.ReadEntry(internalVNFID)
342         if err != nil {
343                 http.Error(w, err.Error(), http.StatusInternalServerError)
344                 return
345         }
346
347         if found == false {
348                 w.WriteHeader(http.StatusNotFound)
349                 return
350         }
351
352         /*
353                 {
354                         "deployment": ["cloud1-default-uuid-sisedeploy1", "cloud1-default-uuid-sisedeploy2", ... ]
355                         "service": ["cloud1-default-uuid-sisesvc1", "cloud1-default-uuid-sisesvc2", ... ]
356                 },
357         */
358         deserializedResourceNameMap := make(map[string][]string)
359         err = db.DeSerialize(serializedResourceNameMap, &deserializedResourceNameMap)
360         if err != nil {
361                 werr := pkgerrors.Wrap(err, "Get VNF error")
362                 http.Error(w, werr.Error(), http.StatusInternalServerError)
363                 return
364         }
365
366         resp := GetVnfResponse{
367                 VNFID:         externalVNFID,
368                 CloudRegionID: cloudRegionID,
369                 Namespace:     namespace,
370                 VNFComponents: deserializedResourceNameMap,
371         }
372
373         w.Header().Set("Content-Type", "application/json")
374         w.WriteHeader(http.StatusOK)
375         json.NewEncoder(w).Encode(resp)
376 }