aboutsummaryrefslogtreecommitdiffstats
path: root/src/k8splugin/internal/app
diff options
context:
space:
mode:
authorKonrad Bańka <k.banka@samsung.com>2020-09-25 16:35:02 +0200
committerKonrad Bańka <k.banka@samsung.com>2020-09-30 12:56:34 +0200
commitb5ccaabd6c3b06286cc845bfb910fc2bd1ab6419 (patch)
tree3d8922fc3e7fa444a27d2b0fb81c220819f8c637 /src/k8splugin/internal/app
parent603a68284970205fa95dec67d4f9b88ae99e8d2c (diff)
Fix Status API to actually provide instance status
Provide information about instance resources and Pods inside status response. Issue-ID: MULTICLOUD-1177 Signed-off-by: Konrad Bańka <k.banka@samsung.com> Change-Id: Iee6fd56120d091dddfa6b6d0e4aa7eb36d40e888
Diffstat (limited to 'src/k8splugin/internal/app')
-rw-r--r--src/k8splugin/internal/app/client.go181
-rw-r--r--src/k8splugin/internal/app/instance.go81
-rw-r--r--src/k8splugin/internal/app/instance_test.go122
3 files changed, 186 insertions, 198 deletions
diff --git a/src/k8splugin/internal/app/client.go b/src/k8splugin/internal/app/client.go
index ed606444..6762d1bc 100644
--- a/src/k8splugin/internal/app/client.go
+++ b/src/k8splugin/internal/app/client.go
@@ -1,5 +1,7 @@
/*
Copyright 2018 Intel Corporation.
+Copyright © 2020 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
@@ -18,6 +20,7 @@ import (
"strings"
"time"
+ "github.com/onap/multicloud-k8s/src/k8splugin/internal/config"
"github.com/onap/multicloud-k8s/src/k8splugin/internal/connection"
"github.com/onap/multicloud-k8s/src/k8splugin/internal/helm"
log "github.com/onap/multicloud-k8s/src/k8splugin/internal/logutils"
@@ -25,6 +28,9 @@ import (
pkgerrors "github.com/pkg/errors"
"k8s.io/apimachinery/pkg/api/meta"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+ "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery/cached/disk"
"k8s.io/client-go/dynamic"
@@ -43,6 +49,79 @@ type KubernetesClient struct {
instanceID string
}
+// ResourceStatus holds Resource Runtime Data
+type ResourceStatus struct {
+ Name string `json:"name"`
+ GVK schema.GroupVersionKind `json:"GVK"`
+ Status unstructured.Unstructured `json:"status"`
+}
+
+// getPodsByLabel yields status of all pods under given instance ID
+func (k *KubernetesClient) getPodsByLabel(namespace string) ([]ResourceStatus, error) {
+ client := k.GetStandardClient().CoreV1().Pods(namespace)
+ listOpts := metav1.ListOptions{
+ LabelSelector: config.GetConfiguration().KubernetesLabelName + "=" + k.instanceID,
+ }
+ podList, err := client.List(listOpts)
+ if err != nil {
+ return nil, pkgerrors.Wrap(err, "Retrieving PodList from cluster")
+ }
+ resp := make([]ResourceStatus, 0, len(podList.Items))
+ cumulatedErrorMsg := make([]string, 0)
+ for _, pod := range podList.Items {
+ podContent, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&pod)
+ if err != nil {
+ cumulatedErrorMsg = append(cumulatedErrorMsg, err.Error())
+ continue
+ }
+ var unstrPod unstructured.Unstructured
+ unstrPod.SetUnstructuredContent(podContent)
+ podStatus := ResourceStatus{
+ Name: unstrPod.GetName(),
+ GVK: schema.FromAPIVersionAndKind("v1", "Pod"),
+ Status: unstrPod,
+ }
+ resp = append(resp, podStatus)
+ }
+ if len(cumulatedErrorMsg) != 0 {
+ return resp, pkgerrors.New("Converting podContent to unstruct error:\n" +
+ strings.Join(cumulatedErrorMsg, "\n"))
+ }
+ return resp, nil
+}
+
+// getResourcesStatus yields status of given generic resource
+func (k *KubernetesClient) getResourceStatus(res helm.KubernetesResource, namespace string) (ResourceStatus, error) {
+ dynClient := k.GetDynamicClient()
+ mapper := k.GetMapper()
+ mapping, err := mapper.RESTMapping(schema.GroupKind{
+ Group: res.GVK.Group,
+ Kind: res.GVK.Kind,
+ }, res.GVK.Version)
+ if err != nil {
+ return ResourceStatus{},
+ pkgerrors.Wrap(err, "Preparing mapper based on GVK")
+ }
+
+ gvr := mapping.Resource
+ opts := metav1.GetOptions{}
+ var unstruct *unstructured.Unstructured
+ switch mapping.Scope.Name() {
+ case meta.RESTScopeNameNamespace:
+ unstruct, err = dynClient.Resource(gvr).Namespace(namespace).Get(res.Name, opts)
+ case meta.RESTScopeNameRoot:
+ unstruct, err = dynClient.Resource(gvr).Get(res.Name, opts)
+ default:
+ return ResourceStatus{}, pkgerrors.New("Got an unknown RESTSCopeName for mapping: " + res.GVK.String())
+ }
+
+ if err != nil {
+ return ResourceStatus{}, pkgerrors.Wrap(err, "Getting object status")
+ }
+
+ return ResourceStatus{unstruct.GetName(), res.GVK, *unstruct}, nil
+}
+
// getKubeConfig uses the connectivity client to get the kubeconfig based on the name
// of the cloudregion. This is written out to a file.
func (k *KubernetesClient) getKubeConfig(cloudregion string) (string, error) {
@@ -182,40 +261,40 @@ func (k *KubernetesClient) createKind(resTempl helm.KubernetesResourceTemplate,
}
func (k *KubernetesClient) updateKind(resTempl helm.KubernetesResourceTemplate,
- namespace string) (helm.KubernetesResource, error) {
-
- if _, err := os.Stat(resTempl.FilePath); os.IsNotExist(err) {
- return helm.KubernetesResource{}, pkgerrors.New("File " + resTempl.FilePath + "does not exists")
- }
-
- log.Info("Processing Kubernetes Resource", log.Fields{
- "filepath": resTempl.FilePath,
- })
-
- pluginImpl, err := plugin.GetPluginByKind(resTempl.GVK.Kind)
- if err != nil {
- return helm.KubernetesResource{}, pkgerrors.Wrap(err, "Error loading plugin")
- }
-
- updatedResourceName, err := pluginImpl.Update(resTempl.FilePath, namespace, k)
- if err != nil {
- log.Error("Error Updating Resource", log.Fields{
- "error": err,
- "gvk": resTempl.GVK,
- "filepath": resTempl.FilePath,
- })
- return helm.KubernetesResource{}, pkgerrors.Wrap(err, "Error in plugin "+resTempl.GVK.Kind+" plugin")
- }
-
- log.Info("Updated Kubernetes Resource", log.Fields{
- "resource": updatedResourceName,
- "gvk": resTempl.GVK,
- })
-
- return helm.KubernetesResource{
- GVK: resTempl.GVK,
- Name: updatedResourceName,
- }, nil
+ namespace string) (helm.KubernetesResource, error) {
+
+ if _, err := os.Stat(resTempl.FilePath); os.IsNotExist(err) {
+ return helm.KubernetesResource{}, pkgerrors.New("File " + resTempl.FilePath + "does not exists")
+ }
+
+ log.Info("Processing Kubernetes Resource", log.Fields{
+ "filepath": resTempl.FilePath,
+ })
+
+ pluginImpl, err := plugin.GetPluginByKind(resTempl.GVK.Kind)
+ if err != nil {
+ return helm.KubernetesResource{}, pkgerrors.Wrap(err, "Error loading plugin")
+ }
+
+ updatedResourceName, err := pluginImpl.Update(resTempl.FilePath, namespace, k)
+ if err != nil {
+ log.Error("Error Updating Resource", log.Fields{
+ "error": err,
+ "gvk": resTempl.GVK,
+ "filepath": resTempl.FilePath,
+ })
+ return helm.KubernetesResource{}, pkgerrors.Wrap(err, "Error in plugin "+resTempl.GVK.Kind+" plugin")
+ }
+
+ log.Info("Updated Kubernetes Resource", log.Fields{
+ "resource": updatedResourceName,
+ "gvk": resTempl.GVK,
+ })
+
+ return helm.KubernetesResource{
+ GVK: resTempl.GVK,
+ Name: updatedResourceName,
+ }, nil
}
func (k *KubernetesClient) createResources(sortedTemplates []helm.KubernetesResourceTemplate,
@@ -239,23 +318,23 @@ func (k *KubernetesClient) createResources(sortedTemplates []helm.KubernetesReso
}
func (k *KubernetesClient) updateResources(sortedTemplates []helm.KubernetesResourceTemplate,
- namespace string) ([]helm.KubernetesResource, error) {
-
- err := k.ensureNamespace(namespace)
- if err != nil {
- return nil, pkgerrors.Wrap(err, "Creating Namespace")
- }
-
- var updatedResources []helm.KubernetesResource
- for _, resTempl := range sortedTemplates {
- resUpdated, err := k.updateKind(resTempl, namespace)
- if err != nil {
- return nil, pkgerrors.Wrapf(err, "Error updating kind: %+v", resTempl.GVK)
- }
- updatedResources = append(updatedResources, resUpdated)
- }
-
- return updatedResources, nil
+ namespace string) ([]helm.KubernetesResource, error) {
+
+ err := k.ensureNamespace(namespace)
+ if err != nil {
+ return nil, pkgerrors.Wrap(err, "Creating Namespace")
+ }
+
+ var updatedResources []helm.KubernetesResource
+ for _, resTempl := range sortedTemplates {
+ resUpdated, err := k.updateKind(resTempl, namespace)
+ if err != nil {
+ return nil, pkgerrors.Wrapf(err, "Error updating kind: %+v", resTempl.GVK)
+ }
+ updatedResources = append(updatedResources, resUpdated)
+ }
+
+ return updatedResources, nil
}
func (k *KubernetesClient) deleteKind(resource helm.KubernetesResource, namespace string) error {
diff --git a/src/k8splugin/internal/app/instance.go b/src/k8splugin/internal/app/instance.go
index 220c82da..a6e213c1 100644
--- a/src/k8splugin/internal/app/instance.go
+++ b/src/k8splugin/internal/app/instance.go
@@ -1,5 +1,6 @@
/*
* Copyright 2018 Intel Corporation, Inc
+ * Copyright © 2020 Samsung Electronics
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +20,7 @@ package app
import (
"encoding/json"
"log"
+ "strings"
"github.com/onap/multicloud-k8s/src/k8splugin/internal/db"
"github.com/onap/multicloud-k8s/src/k8splugin/internal/helm"
@@ -26,7 +28,6 @@ import (
"github.com/onap/multicloud-k8s/src/k8splugin/internal/rb"
pkgerrors "github.com/pkg/errors"
- corev1 "k8s.io/api/core/v1"
)
// InstanceRequest contains the parameters needed for instantiation
@@ -60,22 +61,12 @@ type InstanceMiniResponse struct {
Namespace string `json:"namespace"`
}
-// PodStatus defines the observed state of ResourceBundleState
-type PodStatus struct {
- Name string `json:"name"`
- Namespace string `json:"namespace"`
- Ready bool `json:"ready"`
- Status corev1.PodStatus `json:"status,omitempty"`
- IPAddresses []string `json:"ipaddresses"`
-}
-
// InstanceStatus is what is returned when status is queried for an instance
type InstanceStatus struct {
Request InstanceRequest `json:"request"`
Ready bool `json:"ready"`
ResourceCount int32 `json:"resourceCount"`
- PodStatuses []PodStatus `json:"podStatuses"`
- ServiceStatuses []corev1.Service `json:"serviceStatuses"`
+ ResourcesStatus []ResourceStatus `json:"resourcesStatus"`
}
// InstanceManager is an interface exposes the instantiation functionality
@@ -107,18 +98,16 @@ func (dk InstanceKey) String() string {
// InstanceClient implements the InstanceManager interface
// It will also be used to maintain some localized state
type InstanceClient struct {
- storeName string
- tagInst string
- tagInstStatus string
+ storeName string
+ tagInst string
}
// NewInstanceClient returns an instance of the InstanceClient
// which implements the InstanceManager
func NewInstanceClient() *InstanceClient {
return &InstanceClient{
- storeName: "rbdef",
- tagInst: "instance",
- tagInstStatus: "instanceStatus",
+ storeName: "rbdef",
+ tagInst: "instance",
}
}
@@ -217,22 +206,64 @@ func (v *InstanceClient) Status(id string) (InstanceStatus, error) {
ID: id,
}
- value, err := db.DBconn.Read(v.storeName, key, v.tagInstStatus)
+ value, err := db.DBconn.Read(v.storeName, key, v.tagInst)
if err != nil {
return InstanceStatus{}, pkgerrors.Wrap(err, "Get Instance")
}
//value is a byte array
- if value != nil {
- resp := InstanceStatus{}
- err = db.DBconn.Unmarshal(value, &resp)
+ if value == nil {
+ 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")
+ }
+
+ cumulatedErrorMsg := make([]string, 0)
+ podsStatus, err := k8sClient.getPodsByLabel(resResp.Namespace)
+ if err != nil {
+ cumulatedErrorMsg = append(cumulatedErrorMsg, err.Error())
+ }
+
+ generalStatus := make([]ResourceStatus, 0, len(resResp.Resources))
+Main:
+ for _, resource := range resResp.Resources {
+ for _, pod := range podsStatus {
+ if resource.GVK == pod.GVK && resource.Name == pod.Name {
+ continue Main //Don't double check pods if someone decided to define pod explicitly in helm chart
+ }
+ }
+ status, err := k8sClient.getResourceStatus(resource, resResp.Namespace)
if err != nil {
- return InstanceStatus{}, pkgerrors.Wrap(err, "Unmarshaling Instance Value")
+ cumulatedErrorMsg = append(cumulatedErrorMsg, err.Error())
+ } else {
+ generalStatus = append(generalStatus, status)
}
- return resp, nil
+ }
+ resp := InstanceStatus{
+ Request: resResp.Request,
+ ResourceCount: int32(len(generalStatus) + len(podsStatus)),
+ Ready: false, //FIXME To determine readiness, some parsing of status fields is necessary
+ ResourcesStatus: append(generalStatus, podsStatus...),
}
- return InstanceStatus{}, pkgerrors.New("Status is not available")
+ if len(cumulatedErrorMsg) != 0 {
+ err = pkgerrors.New("Getting Resources Status:\n" +
+ strings.Join(cumulatedErrorMsg, "\n"))
+ return resp, err
+ }
+ //TODO Filter response content by requested verbosity (brief, ...)?
+
+ return resp, nil
}
// List returns the instance for corresponding ID
diff --git a/src/k8splugin/internal/app/instance_test.go b/src/k8splugin/internal/app/instance_test.go
index 1b84b449..b79cf388 100644
--- a/src/k8splugin/internal/app/instance_test.go
+++ b/src/k8splugin/internal/app/instance_test.go
@@ -318,128 +318,6 @@ func TestInstanceGet(t *testing.T) {
})
}
-func TestInstanceStatus(t *testing.T) {
- oldkrdPluginData := utils.LoadedPlugins
-
- defer func() {
- utils.LoadedPlugins = oldkrdPluginData
- }()
-
- err := LoadMockPlugins(utils.LoadedPlugins)
- if err != nil {
- t.Fatalf("LoadMockPlugins returned an error (%s)", err)
- }
-
- t.Run("Successfully Get Instance Status", func(t *testing.T) {
- db.DBconn = &db.MockDB{
- Items: map[string]map[string][]byte{
- InstanceKey{ID: "HaKpys8e"}.String(): {
- "instanceStatus": []byte(
- `{
- "request": {
- "profile-name":"profile1",
- "rb-name":"test-rbdef",
- "rb-version":"v1",
- "cloud-region":"region1"
- },
- "ready": true,
- "resourceCount": 2,
- "podStatuses": [
- {
- "name": "test-pod1",
- "namespace": "default",
- "ready": true,
- "ipaddresses": ["192.168.1.1", "192.168.2.1"]
- },
- {
- "name": "test-pod2",
- "namespace": "default",
- "ready": true,
- "ipaddresses": ["192.168.4.1", "192.168.5.1"]
- }
- ]
- }`),
- },
- },
- }
-
- expected := InstanceStatus{
- Request: InstanceRequest{
- RBName: "test-rbdef",
- RBVersion: "v1",
- ProfileName: "profile1",
- CloudRegion: "region1",
- },
- Ready: true,
- ResourceCount: 2,
- PodStatuses: []PodStatus{
- {
- Name: "test-pod1",
- Namespace: "default",
- Ready: true,
- IPAddresses: []string{"192.168.1.1", "192.168.2.1"},
- },
- {
- Name: "test-pod2",
- Namespace: "default",
- Ready: true,
- IPAddresses: []string{"192.168.4.1", "192.168.5.1"},
- },
- },
- }
- ic := NewInstanceClient()
- id := "HaKpys8e"
- data, err := ic.Status(id)
- if err != nil {
- t.Fatalf("TestInstanceStatus returned an error (%s)", err)
- }
- if !reflect.DeepEqual(expected, data) {
- t.Fatalf("TestInstanceStatus returned:\n result=%v\n expected=%v",
- data, expected)
- }
- })
-
- t.Run("Get non-existing Instance", func(t *testing.T) {
- db.DBconn = &db.MockDB{
- Items: map[string]map[string][]byte{
- InstanceKey{ID: "HaKpys8e"}.String(): {
- "instanceStatus": []byte(
- `{
- "request": {
- "profile-name":"profile1",
- "rb-name":"test-rbdef",
- "rb-version":"v1",
- "cloud-region":"region1"
- },
- "ready": true,
- "resourceCount": 2,
- "podStatuses": [
- {
- "name": "test-pod1",
- "namespace": "default",
- "ready": true,
- "ipaddresses": ["192.168.1.1", "192.168.2.1"]
- },
- {
- "name": "test-pod2",
- "namespace": "default",
- "ready": true,
- "ipaddresses": ["192.168.4.1", "192.168.5.1"]
- }
- ]
- }`),
- },
- },
- }
-
- ic := NewInstanceClient()
- _, err := ic.Get("non-existing")
- if err == nil {
- t.Fatal("Expected error, got pass", err)
- }
- })
-}
-
func TestInstanceFind(t *testing.T) {
oldkrdPluginData := utils.LoadedPlugins