Provide Query API for CNF Instances 18/118418/4
authorKonrad Bańka <k.banka@samsung.com>
Wed, 24 Feb 2021 17:28:56 +0000 (18:28 +0100)
committerKonrad Bańka <k.banka@samsung.com>
Wed, 24 Feb 2021 20:43:07 +0000 (21:43 +0100)
Query API doesn't directly use Status API code, in order to allow for
querying derived resources that might not be typically returned by
Status API like replicasets for deployment.

Issue-ID: MULTICLOUD-1305
Signed-off-by: Konrad Bańka <k.banka@samsung.com>
Change-Id: If15adce23845880f3e6771cc8eab78a78ab13517

src/k8splugin/api/api.go
src/k8splugin/api/instancehandler.go
src/k8splugin/internal/app/client.go
src/k8splugin/internal/app/instance.go

index c836fc6..e55d833 100644 (file)
@@ -1,5 +1,6 @@
 /*
 Copyright 2018 Intel Corporation.
+Copyright © 2021 Samsung Electronics
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
@@ -47,6 +48,11 @@ func NewRouter(defClient rb.DefinitionManager,
 
        instRouter.HandleFunc("/instance/{instID}", instHandler.getHandler).Methods("GET")
        instRouter.HandleFunc("/instance/{instID}/status", instHandler.statusHandler).Methods("GET")
+       instRouter.HandleFunc("/instance/{instID}/query", instHandler.queryHandler).
+               Queries("ApiVersion", "{ApiVersion}",
+                       "Kind", "{Kind}",
+                       "Name", "{Name}",
+                       "Labels", "{Labels}").Methods("GET")
        instRouter.HandleFunc("/instance/{instID}", instHandler.deleteHandler).Methods("DELETE")
        // (TODO): Fix update method
        // instRouter.HandleFunc("/{vnfInstanceId}", UpdateHandler).Methods("PUT")
index b043742..b56a8e1 100644 (file)
@@ -1,5 +1,6 @@
 /*
 Copyright 2018 Intel Corporation.
+Copyright © 2021 Samsung Electronics
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
@@ -171,6 +172,51 @@ func (i instanceHandler) statusHandler(w http.ResponseWriter, r *http.Request) {
        }
 }
 
+// queryHandler retrieves information about specified resources for instance
+func (i instanceHandler) queryHandler(w http.ResponseWriter, r *http.Request) {
+       vars := mux.Vars(r)
+       id := vars["instID"]
+       apiVersion := r.FormValue("ApiVersion")
+       kind := r.FormValue("Kind")
+       name := r.FormValue("Name")
+       labels := r.FormValue("Labels")
+       if apiVersion == "" {
+               http.Error(w, "Missing apiVersion mandatory parameter", http.StatusBadRequest)
+               return
+       }
+       if kind == "" {
+               http.Error(w, "Missing kind mandatory parameter", http.StatusBadRequest)
+               return
+       }
+       if name == "" && labels == "" {
+               http.Error(w, "Name or Labels parameter must be provided", http.StatusBadRequest)
+               return
+       }
+       resp, err := i.client.Query(id, apiVersion, kind, name, labels)
+       if err != nil {
+               log.Error("Error getting Query results", log.Fields{
+                       "error":      err,
+                       "id":         id,
+                       "apiVersion": apiVersion,
+                       "kind":       kind,
+                       "name":       name,
+                       "labels":     labels,
+               })
+               http.Error(w, err.Error(), http.StatusInternalServerError)
+       }
+       w.Header().Set("Content-Type", "application/json")
+       w.WriteHeader(http.StatusOK)
+       err = json.NewEncoder(w).Encode(resp)
+       if err != nil {
+               log.Error("Error Marshaling Response", log.Fields{
+                       "error":    err,
+                       "response": resp,
+               })
+               http.Error(w, err.Error(), http.StatusInternalServerError)
+               return
+       }
+}
+
 // listHandler retrieves information about an instance via the ID
 func (i instanceHandler) listHandler(w http.ResponseWriter, r *http.Request) {
 
index 6762d1b..f0edf8c 100644 (file)
@@ -1,6 +1,6 @@
 /*
 Copyright 2018 Intel Corporation.
-Copyright © 2020 Samsung Electronics
+Copyright © 2021 Samsung Electronics
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -90,6 +90,39 @@ func (k *KubernetesClient) getPodsByLabel(namespace string) ([]ResourceStatus, e
        return resp, nil
 }
 
+func (k *KubernetesClient) queryResources(apiVersion, kind, labelSelector, namespace string) ([]ResourceStatus, error) {
+       dynClient := k.GetDynamicClient()
+       mapper := k.GetMapper()
+       gvk := schema.FromAPIVersionAndKind(apiVersion, kind)
+       mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
+       if err != nil {
+               return nil, pkgerrors.Wrap(err, "Preparing mapper based on GVK")
+       }
+
+       gvr := mapping.Resource
+       opts := metav1.ListOptions{
+               LabelSelector: labelSelector,
+       }
+       var unstrList *unstructured.UnstructuredList
+       switch mapping.Scope.Name() {
+       case meta.RESTScopeNameNamespace:
+               unstrList, err = dynClient.Resource(gvr).Namespace(namespace).List(opts)
+       case meta.RESTScopeNameRoot:
+               unstrList, err = dynClient.Resource(gvr).List(opts)
+       default:
+               return nil, pkgerrors.New("Got an unknown RESTScopeName for mapping: " + gvk.String())
+       }
+       if err != nil {
+               return nil, pkgerrors.Wrap(err, "Querying for resources")
+       }
+
+       resp := make([]ResourceStatus, len(unstrList.Items))
+       for _, unstr := range unstrList.Items {
+               resp = append(resp, ResourceStatus{unstr.GetName(), gvk, unstr})
+       }
+       return resp, nil
+}
+
 // getResourcesStatus yields status of given generic resource
 func (k *KubernetesClient) getResourceStatus(res helm.KubernetesResource, namespace string) (ResourceStatus, error) {
        dynClient := k.GetDynamicClient()
index a6e213c..e789664 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright 2018 Intel Corporation, Inc
- * Copyright © 2020 Samsung Electronics
+ * Copyright © 2021 Samsung Electronics
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -74,6 +74,7 @@ type InstanceManager interface {
        Create(i InstanceRequest) (InstanceResponse, error)
        Get(id string) (InstanceResponse, error)
        Status(id string) (InstanceStatus, error)
+       Query(id, apiVersion, kind, name, labels string) (InstanceStatus, error)
        List(rbname, rbversion, profilename string) ([]InstanceMiniResponse, error)
        Find(rbName string, ver string, profile string, labelKeys map[string]string) ([]InstanceMiniResponse, error)
        Delete(id string) error
@@ -198,6 +199,68 @@ func (v *InstanceClient) Get(id string) (InstanceResponse, error) {
        return InstanceResponse{}, pkgerrors.New("Error getting Instance")
 }
 
+// Query returns state of instance's filtered resources
+func (v *InstanceClient) Query(id, apiVersion, kind, name, labels string) (InstanceStatus, error) {
+
+       //Read the status from the DB
+       key := InstanceKey{
+               ID: id,
+       }
+       value, err := db.DBconn.Read(v.storeName, key, v.tagInst)
+       if err != nil {
+               return InstanceStatus{}, pkgerrors.Wrap(err, "Get Instance")
+       }
+       if value == nil { //value is a byte array
+               return InstanceStatus{}, pkgerrors.New("Status is not available")
+       }
+       resResp := InstanceResponse{}
+       err = db.DBconn.Unmarshal(value, &resResp)
+       if err != nil {
+               return InstanceStatus{}, pkgerrors.Wrap(err, "Unmarshaling Instance Value")
+       }
+
+       k8sClient := KubernetesClient{}
+       err = k8sClient.init(resResp.Request.CloudRegion, id)
+       if err != nil {
+               return InstanceStatus{}, pkgerrors.Wrap(err, "Getting CloudRegion Information")
+       }
+
+       var resourcesStatus []ResourceStatus
+       if labels != "" {
+               resList, err := k8sClient.queryResources(apiVersion, kind, labels, resResp.Namespace)
+               if err != nil {
+                       return InstanceStatus{}, pkgerrors.Wrap(err, "Querying Resources")
+               }
+               // If user specifies both label and name, we want to pick up only single resource from these matching label
+               if name != "" {
+                       //Assigning 0-length, because we may actually not find matching name
+                       resourcesStatus = make([]ResourceStatus, 0)
+                       for _, res := range resList {
+                               if res.Name == name {
+                                       resourcesStatus = append(resourcesStatus, res)
+                                       break
+                               }
+                       }
+               } else {
+                       resourcesStatus = resList
+               }
+       } else if name != "" {
+               resIdentifier := helm.KubernetesResource{}
+               res, err := k8sClient.getResourceStatus(resIdentifier, resResp.Namespace)
+               if err != nil {
+                       return InstanceStatus{}, pkgerrors.Wrap(err, "Querying Resource")
+               }
+               resourcesStatus = []ResourceStatus{res}
+       }
+
+       resp := InstanceStatus{
+               Request:         resResp.Request,
+               ResourceCount:   int32(len(resourcesStatus)),
+               ResourcesStatus: resourcesStatus,
+       }
+       return resp, nil
+}
+
 // Status returns the status for the instance
 func (v *InstanceClient) Status(id string) (InstanceStatus, error) {