From 645c6a331cd00043fcf9f567f5f261a9db070918 Mon Sep 17 00:00:00 2001 From: Eric Multanen Date: Wed, 12 Aug 2020 15:33:12 -0700 Subject: Enhance the status query API This patch enhances the status query API. - The ResourceBundleState CRD is modified to just use the k8s Pod structure instead of a customized struct. - Status queries can either present results showing the rsync status of the composite app and resources or from information received from the cluster via the ResourceBundleState CR - Query parameters are provided to the API call to customize the query and response - Support for querying status of cluster network intents is added Issue-ID: MULTICLOUD-1042 Signed-off-by: Eric Multanen Change-Id: Icca4cdd901e2f2b446414fade256fc24d87594cd --- src/orchestrator/pkg/status/status_helper.go | 482 +++++++++++++++++++++++++++ 1 file changed, 482 insertions(+) create mode 100644 src/orchestrator/pkg/status/status_helper.go (limited to 'src/orchestrator/pkg/status/status_helper.go') diff --git a/src/orchestrator/pkg/status/status_helper.go b/src/orchestrator/pkg/status/status_helper.go new file mode 100644 index 00000000..a791493e --- /dev/null +++ b/src/orchestrator/pkg/status/status_helper.go @@ -0,0 +1,482 @@ +/* + * Copyright 2020 Intel Corporation, Inc + * + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package status + +import ( + "encoding/json" + "fmt" + "strings" + + rb "github.com/onap/multicloud-k8s/src/monitor/pkg/apis/k8splugin/v1alpha1" + "github.com/onap/multicloud-k8s/src/monitor/pkg/generated/clientset/versioned/scheme" + "github.com/onap/multicloud-k8s/src/orchestrator/pkg/appcontext" + log "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/logutils" + "github.com/onap/multicloud-k8s/src/orchestrator/pkg/resourcestatus" + "github.com/onap/multicloud-k8s/src/orchestrator/pkg/state" + pkgerrors "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" +) + +// decodeYAML reads a YAMl []byte to extract the Kubernetes object definition +func decodeYAML(y []byte, into runtime.Object) (runtime.Object, error) { + decode := scheme.Codecs.UniversalDeserializer().Decode + obj, _, err := decode(y, nil, into) + if err != nil { + return nil, pkgerrors.Wrap(err, "Deserialize YAML error") + } + + return obj, nil +} + +func getUnstruct(y []byte) (unstructured.Unstructured, error) { + //Decode the yaml file to create a runtime.Object + unstruct := unstructured.Unstructured{} + //Ignore the returned obj as we expect the data in unstruct + _, err := decodeYAML(y, &unstruct) + if err != nil { + log.Info(":: Error decoding YAML ::", log.Fields{"object": y, "error": err}) + return unstructured.Unstructured{}, pkgerrors.Wrap(err, "Decode object error") + } + + return unstruct, nil +} + +// GetClusterResources takes in a ResourceBundleStatus CR and resturns a list of ResourceStatus elments +func GetClusterResources(rbData rb.ResourceBundleStatus, qOutput string, qResources []string, + resourceList *[]ResourceStatus, cnts map[string]int) (int, error) { + + count := 0 + + for _, p := range rbData.PodStatuses { + if !keepResource(p.Name, qResources) { + continue + } + r := ResourceStatus{} + r.Name = p.Name + r.Gvk = (&p.TypeMeta).GroupVersionKind() + if qOutput == "detail" { + r.Detail = p + } + *resourceList = append(*resourceList, r) + count++ + cnt := cnts["Present"] + cnts["Present"] = cnt + 1 + } + + for _, s := range rbData.ServiceStatuses { + if !keepResource(s.Name, qResources) { + continue + } + r := ResourceStatus{} + r.Name = s.Name + r.Gvk = (&s.TypeMeta).GroupVersionKind() + if qOutput == "detail" { + r.Detail = s + } + *resourceList = append(*resourceList, r) + count++ + cnt := cnts["Present"] + cnts["Present"] = cnt + 1 + } + + for _, d := range rbData.DeploymentStatuses { + if !keepResource(d.Name, qResources) { + continue + } + r := ResourceStatus{} + r.Name = d.Name + r.Gvk = (&d.TypeMeta).GroupVersionKind() + if qOutput == "detail" { + r.Detail = d + } + *resourceList = append(*resourceList, r) + count++ + cnt := cnts["Present"] + cnts["Present"] = cnt + 1 + } + + for _, c := range rbData.ConfigMapStatuses { + if !keepResource(c.Name, qResources) { + continue + } + r := ResourceStatus{} + r.Name = c.Name + r.Gvk = (&c.TypeMeta).GroupVersionKind() + if qOutput == "detail" { + r.Detail = c + } + *resourceList = append(*resourceList, r) + count++ + cnt := cnts["Present"] + cnts["Present"] = cnt + 1 + } + + for _, s := range rbData.SecretStatuses { + if !keepResource(s.Name, qResources) { + continue + } + r := ResourceStatus{} + r.Name = s.Name + r.Gvk = (&s.TypeMeta).GroupVersionKind() + if qOutput == "detail" { + r.Detail = s + } + *resourceList = append(*resourceList, r) + count++ + cnt := cnts["Present"] + cnts["Present"] = cnt + 1 + } + + for _, d := range rbData.DaemonSetStatuses { + if !keepResource(d.Name, qResources) { + continue + } + r := ResourceStatus{} + r.Name = d.Name + r.Gvk = (&d.TypeMeta).GroupVersionKind() + if qOutput == "detail" { + r.Detail = d + } + *resourceList = append(*resourceList, r) + count++ + cnt := cnts["Present"] + cnts["Present"] = cnt + 1 + } + + for _, i := range rbData.IngressStatuses { + if !keepResource(i.Name, qResources) { + continue + } + r := ResourceStatus{} + r.Name = i.Name + r.Gvk = (&i.TypeMeta).GroupVersionKind() + if qOutput == "detail" { + r.Detail = i + } + *resourceList = append(*resourceList, r) + count++ + cnt := cnts["Present"] + cnts["Present"] = cnt + 1 + } + + for _, j := range rbData.JobStatuses { + if !keepResource(j.Name, qResources) { + continue + } + r := ResourceStatus{} + r.Name = j.Name + r.Gvk = (&j.TypeMeta).GroupVersionKind() + if qOutput == "detail" { + r.Detail = j + } + *resourceList = append(*resourceList, r) + count++ + cnt := cnts["Present"] + cnts["Present"] = cnt + 1 + } + + for _, s := range rbData.StatefulSetStatuses { + if !keepResource(s.Name, qResources) { + continue + } + r := ResourceStatus{} + r.Name = s.Name + r.Gvk = (&s.TypeMeta).GroupVersionKind() + if qOutput == "detail" { + r.Detail = s + } + *resourceList = append(*resourceList, r) + count++ + cnt := cnts["Present"] + cnts["Present"] = cnt + 1 + } + + return count, nil +} + +// isResourceHandle takes a cluster handle and determines if the other handle parameter is a resource handle for this cluster +// handle. It does this by verifying that the cluster handle is a prefix of the handle and that the remainder of the handle +// is a value that matches to a resource format: "resource/+/" +// Example cluster handle: +// /context/6385596659306465421/app/network-intents/cluster/vfw-cluster-provider+edge01/ +// Example resource handle: +// /context/6385596659306465421/app/network-intents/cluster/vfw-cluster-provider+edge01/resource/emco-private-net+ProviderNetwork/ +func isResourceHandle(ch, h interface{}) bool { + clusterHandle := fmt.Sprintf("%v", ch) + handle := fmt.Sprintf("%v", h) + diff := strings.Split(handle, clusterHandle) + + if len(diff) != 2 && diff[0] != "" { + return false + } + + parts := strings.Split(diff[1], "/") + + if len(parts) == 3 && + parts[0] == "resource" && + len(strings.Split(parts[1], "+")) == 2 && + parts[2] == "" { + return true + } else { + return false + } +} + +// keepResource keeps a resource if the filter list is empty or if the resource is part of the list +func keepResource(r string, rList []string) bool { + if len(rList) == 0 { + return true + } + for _, res := range rList { + if r == res { + return true + } + } + return false +} + +// GetAppContextResources collects the resource status of all resources in an AppContext subject to the filter parameters +func GetAppContextResources(ac appcontext.AppContext, ch interface{}, qOutput string, qResources []string, resourceList *[]ResourceStatus, statusCnts map[string]int) (int, error) { + count := 0 + + // Get all Resources for the Cluster + hs, err := ac.GetAllHandles(ch) + if err != nil { + log.Info(":: Error getting all handles ::", log.Fields{"handles": ch, "error": err}) + return 0, err + } + + for _, h := range hs { + // skip any handles that are not resource handles + if !isResourceHandle(ch, h) { + continue + } + + // Get Resource from AppContext + res, err := ac.GetValue(h) + if err != nil { + log.Info(":: Error getting resource value ::", log.Fields{"Handle": h}) + continue + } + + // Get Resource Status from AppContext + sh, err := ac.GetLevelHandle(h, "status") + if err != nil { + log.Info(":: No status handle for resource ::", log.Fields{"Handle": h}) + continue + } + s, err := ac.GetValue(sh) + if err != nil { + log.Info(":: Error getting resource status value ::", log.Fields{"Handle": sh}) + continue + } + rstatus := resourcestatus.ResourceStatus{} + js, err := json.Marshal(s) + if err != nil { + log.Info(":: Non-JSON status data for resource ::", log.Fields{"Handle": sh, "Value": s}) + continue + } + err = json.Unmarshal(js, &rstatus) + if err != nil { + log.Info(":: Invalid status data for resource ::", log.Fields{"Handle": sh, "Value": s}) + continue + } + + // Get the unstructured object + unstruct, err := getUnstruct([]byte(res.(string))) + if err != nil { + log.Info(":: Error getting GVK ::", log.Fields{"Resource": res, "error": err}) + continue + } + if !keepResource(unstruct.GetName(), qResources) { + continue + } + + // Make and fill out a ResourceStatus structure + r := ResourceStatus{} + r.Gvk = unstruct.GroupVersionKind() + r.Name = unstruct.GetName() + if qOutput == "detail" { + r.Detail = unstruct.Object + } + r.RsyncStatus = fmt.Sprintf("%v", rstatus.Status) + *resourceList = append(*resourceList, r) + cnt := statusCnts[rstatus.Status] + statusCnts[rstatus.Status] = cnt + 1 + count++ + } + + return count, nil +} + +// PrepareStatusResult takes in a resource stateInfo object, the list of apps and the query parameters. +// It then fills out the StatusResult structure appropriately from information in the AppContext +func PrepareStatusResult(stateInfo state.StateInfo, apps []string, qInstance, qType, qOutput string, qApps, qClusters, qResources []string) (StatusResult, error) { + + var currentCtxId string + if qInstance != "" { + currentCtxId = qInstance + } else { + currentCtxId = state.GetLastContextIdFromStateInfo(stateInfo) + } + ac, err := state.GetAppContextFromId(currentCtxId) + if err != nil { + return StatusResult{}, pkgerrors.Wrap(err, "AppContext for status query not found") + } + + // get the appcontext status value + h, err := ac.GetCompositeAppHandle() + if err != nil { + return StatusResult{}, pkgerrors.Wrap(err, "AppContext handle not found") + } + sh, err := ac.GetLevelHandle(h, "status") + if err != nil { + return StatusResult{}, pkgerrors.Wrap(err, "AppContext status handle not found") + } + statusVal, err := ac.GetValue(sh) + if err != nil { + return StatusResult{}, pkgerrors.Wrap(err, "AppContext status value not found") + } + acStatus := appcontext.AppContextStatus{} + js, err := json.Marshal(statusVal) + if err != nil { + return StatusResult{}, pkgerrors.Wrap(err, "Invalid AppContext status value format") + } + err = json.Unmarshal(js, &acStatus) + if err != nil { + return StatusResult{}, pkgerrors.Wrap(err, "Invalid AppContext status value format") + } + + statusResult := StatusResult{} + + statusResult.Apps = make([]AppStatus, 0) + statusResult.State = stateInfo + statusResult.Status = acStatus.Status + + rsyncStatusCnts := make(map[string]int) + clusterStatusCnts := make(map[string]int) + // Loop through each app and get the status data for each cluster in the app + for _, app := range apps { + appCount := 0 + if len(qApps) > 0 { + found := false + for _, a := range qApps { + if a == app { + found = true + break + } + } + if !found { + continue + } + } + // Get the clusters in the appcontext for this app + clusters, err := ac.GetClusterNames(app) + if err != nil { + continue + } + var appStatus AppStatus + appStatus.Name = app + appStatus.Clusters = make([]ClusterStatus, 0) + + for _, cluster := range clusters { + clusterCount := 0 + if len(qClusters) > 0 { + found := false + for _, c := range qClusters { + if c == cluster { + found = true + break + } + } + if !found { + continue + } + } + + var clusterStatus ClusterStatus + pc := strings.Split(cluster, "+") + clusterStatus.ClusterProvider = pc[0] + clusterStatus.Cluster = pc[1] + + if qType == "cluster" { + csh, err := ac.GetClusterStatusHandle(app, cluster) + if err != nil { + log.Info(":: No cluster status handle for cluster, app ::", + log.Fields{"Cluster": cluster, "AppName": app, "Error": err}) + continue + } + clusterRbValue, err := ac.GetValue(csh) + if err != nil { + log.Info(":: No cluster status value for cluster, app ::", + log.Fields{"Cluster": cluster, "AppName": app, "Error": err}) + continue + } + var rbValue rb.ResourceBundleStatus + err = json.Unmarshal([]byte(clusterRbValue.(string)), &rbValue) + if err != nil { + log.Info(":: Error unmarshaling cluster status value for cluster, app ::", + log.Fields{"Cluster": cluster, "AppName": app, "Error": err}) + continue + } + + clusterStatus.Resources = make([]ResourceStatus, 0) + cnt, err := GetClusterResources(rbValue, qOutput, qResources, &clusterStatus.Resources, clusterStatusCnts) + if err != nil { + log.Info(":: Error gathering cluster resources for cluster, app ::", + log.Fields{"Cluster": cluster, "AppName": app, "Error": err}) + continue + } + appCount += cnt + clusterCount += cnt + } else if qType == "rsync" { + ch, err := ac.GetClusterHandle(app, cluster) + if err != nil { + log.Info(":: No handle for cluster, app ::", + log.Fields{"Cluster": cluster, "AppName": app, "Error": err}) + continue + } + + /* code to get status for resources from AppContext */ + clusterStatus.Resources = make([]ResourceStatus, 0) + cnt, err := GetAppContextResources(ac, ch, qOutput, qResources, &clusterStatus.Resources, rsyncStatusCnts) + if err != nil { + log.Info(":: Error gathering appcontext resources for cluster, app ::", + log.Fields{"Cluster": cluster, "AppName": app, "Error": err}) + continue + } + appCount += cnt + clusterCount += cnt + } else { + log.Info(":: Invalid status type ::", log.Fields{"Status Type": qType}) + continue + } + + if clusterCount > 0 { + appStatus.Clusters = append(appStatus.Clusters, clusterStatus) + } + } + if appCount > 0 && qOutput != "summary" { + statusResult.Apps = append(statusResult.Apps, appStatus) + } + } + statusResult.RsyncStatus = rsyncStatusCnts + statusResult.ClusterStatus = clusterStatusCnts + + return statusResult, nil +} -- cgit 1.2.3-korg