From 25481aba1caf035d775aeec2ee7bbdc30e498103 Mon Sep 17 00:00:00 2001 From: Luke Reed Date: Fri, 22 Oct 2021 13:46:32 -0400 Subject: [PATCH] Support multi controller (frontend) (#376) --- e2e/tests/99_cleanup.yaml | 1 + pkg/dashboard/templates/namespace.gohtml | 22 +- pkg/summary/constants_test.go | 252 +++++++++++++++++++++-- pkg/summary/options.go | 2 + pkg/summary/summary.go | 110 ++++++---- pkg/summary/summary_test.go | 44 +++- pkg/utils/utils.go | 2 +- 7 files changed, 351 insertions(+), 82 deletions(-) diff --git a/e2e/tests/99_cleanup.yaml b/e2e/tests/99_cleanup.yaml index 5fe6dfdd..a91308a8 100644 --- a/e2e/tests/99_cleanup.yaml +++ b/e2e/tests/99_cleanup.yaml @@ -10,3 +10,4 @@ testcases: kubectl delete ns metrics-server helm -n vpa delete vpa kubectl delete ns vpa + kubectl delete ns statefulset-demo diff --git a/pkg/dashboard/templates/namespace.gohtml b/pkg/dashboard/templates/namespace.gohtml index 4919be09..a7070f2f 100644 --- a/pkg/dashboard/templates/namespace.gohtml +++ b/pkg/dashboard/templates/namespace.gohtml @@ -1,11 +1,11 @@ {{define "namespace"}} -{{ $foundFirstDeployment := false }} +{{ $foundFirstWorkload := false }}

- Namespace {{ $.Namespace }} @@ -20,29 +20,29 @@

- {{ if lt (len $.Deployments) 1 }} + {{ if lt (len $.Workloads) 1 }}
-

No deployments found in this namespace.

+

No workloads found in this namespace.

{{ else }} - {{ range $deployment := $.Deployments }} + {{ range $workload := $.Workloads }}

- Deployment{{ $workload.ControllerType }} - {{ $deployment.DeploymentName }} + {{ $workload.ControllerName }}

- {{ range $cName, $cSummary := $deployment.Containers }} + {{ range $cName, $cSummary := $workload.Containers }}

diff --git a/pkg/summary/constants_test.go b/pkg/summary/constants_test.go index 7cef1612..78f65bd2 100644 --- a/pkg/summary/constants_test.go +++ b/pkg/summary/constants_test.go @@ -15,11 +15,11 @@ package summary import ( - appsv1 "k8s.io/api/apps/v1" autoscalingv1 "k8s.io/api/autoscaling/v1" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" vpav1 "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1" "github.com/fairwindsops/goldilocks/pkg/utils" @@ -73,10 +73,56 @@ var testVPABasic = &vpav1.VerticalPodAutoscaler{ }, }, } -var testDeploymentBasic = &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-basic", - Namespace: "testing", + +var testDeploymentBasicUnstructured = &unstructured.Unstructured{ + Object: map[string]interface{}{ + "kind": "Deployment", + "apiVersion": "apps/v1", + "metadata": map[string]interface{}{ + "name": "test-basic", + "namespace": "testing", + }, + "spec": map[string]interface{}{}, + }, +} + +var testDeploymentBasicReplicaSetUnstructured = &unstructured.Unstructured{ + Object: map[string]interface{}{ + "kind": "ReplicaSet", + "apiVersion": "apps/v1", + "metadata": map[string]interface{}{ + "ownerReferences": []interface{}{ + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "controller": true, + "name": "test-basic", + }, + }, + "name": "test-basic-0123456789", + "namespace": "testing", + }, + "spec": map[string]interface{}{}, + }, +} + +var testDeploymentBasicPodUnstructured = &unstructured.Unstructured{ + Object: map[string]interface{}{ + "kind": "Pod", + "apiVersion": "v1", + "metadata": map[string]interface{}{ + "ownerReferences": []interface{}{ + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "ReplicaSet", + "controller": true, + "name": "test-basic-0123456789", + }, + }, + "name": "test-basic-0123456789-01234", + "namespace": "testing", + }, + "spec": map[string]interface{}{}, }, } @@ -131,20 +177,30 @@ var testVPAWithReco = &vpav1.VerticalPodAutoscaler{ }, } -var testDeploymentWithReco = &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-vpa-with-reco", - Namespace: "testing", - }, - Spec: appsv1.DeploymentSpec{ - Template: v1.PodTemplateSpec{ - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Name: "container", - Resources: v1.ResourceRequirements{ - Limits: targetResources, - Requests: targetResources, +var testDeploymentWithRecoUnstructured = &unstructured.Unstructured{ + Object: map[string]interface{}{ + "kind": "Deployment", + "apiVersion": "apps/v1", + "metadata": map[string]interface{}{ + "name": "test-vpa-with-reco", + "namespace": "testing", + }, + "spec": map[string]interface{}{ + "template": map[string]interface{}{ + "spec": map[string]interface{}{ + "containers": []interface{}{ + map[string]interface{}{ + "name": "container", + "resources": map[string]interface{}{ + "limits": map[string]interface{}{ + "cpu": "100m", + "memory": "100Mi", + }, + "requests": map[string]interface{}{ + "cpu": "100m", + "memory": "100Mi", + }, + }, }, }, }, @@ -153,19 +209,171 @@ var testDeploymentWithReco = &appsv1.Deployment{ }, } +var testDeploymentWithRecoReplicaSetUnstructured = &unstructured.Unstructured{ + Object: map[string]interface{}{ + "kind": "ReplicaSet", + "apiVersion": "apps/v1", + "metadata": map[string]interface{}{ + "ownerReferences": []interface{}{ + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "controller": true, + "name": "test-vpa-with-reco", + }, + }, + "name": "test-vpa-with-reco-0123456789", + "namespace": "testing", + }, + "spec": map[string]interface{}{}, + }, +} + +var testDeploymentWithRecoPodUnstructured = &unstructured.Unstructured{ + Object: map[string]interface{}{ + "kind": "Pod", + "apiVersion": "v1", + "metadata": map[string]interface{}{ + "ownerReferences": []interface{}{ + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "ReplicaSet", + "controller": true, + "name": "test-vpa-with-reco-0123456789", + }, + }, + "name": "test-vpa-with-reco-0123456789-01234", + "namespace": "testing", + }, + "spec": map[string]interface{}{}, + }, +} + // The summary of these objects var testSummary = Summary{ Namespaces: map[string]namespaceSummary{ "testing": { Namespace: "testing", - Deployments: map[string]deploymentSummary{ + Workloads: map[string]workloadSummary{ "test-basic": { - DeploymentName: "test-basic", + ControllerName: "test-basic", + ControllerType: "Deployment", Containers: map[string]containerSummary{}, }, "test-vpa-with-reco": { - DeploymentName: "test-vpa-with-reco", + ControllerName: "test-vpa-with-reco", + ControllerType: "Deployment", + Containers: map[string]containerSummary{ + "container": { + ContainerName: "container", + LowerBound: lowerBound, + UpperBound: upperBound, + Target: targetResources, + Limits: targetResources, + Requests: targetResources, + }, + }, + }, + }, + }, + }, +} + +// DaemonSet test and VPA + +var testDaemonSettWithRecoUnstructured = &unstructured.Unstructured{ + Object: map[string]interface{}{ + "kind": "DaemonSet", + "apiVersion": "apps/v1", + "metadata": map[string]interface{}{ + "name": "test-ds-with-reco", + "namespace": "testing-daemonset", + }, + "spec": map[string]interface{}{ + "template": map[string]interface{}{ + "spec": map[string]interface{}{ + "containers": []interface{}{ + map[string]interface{}{ + "name": "container", + "resources": map[string]interface{}{ + "limits": map[string]interface{}{ + "cpu": "100m", + "memory": "100Mi", + }, + "requests": map[string]interface{}{ + "cpu": "100m", + "memory": "100Mi", + }, + }, + }, + }, + }, + }, + }, + }, +} + +var testDaemonSetWithRecoPodUnstructured = &unstructured.Unstructured{ + Object: map[string]interface{}{ + "kind": "Pod", + "apiVersion": "v1", + "metadata": map[string]interface{}{ + "ownerReferences": []interface{}{ + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "DaemonSet", + "controller": true, + "name": "test-ds-with-reco", + }, + }, + "name": "test-ds-with-reco-01234", + "namespace": "testing-daemonset", + }, + "spec": map[string]interface{}{}, + }, +} + +var testDaemonSetVPAWithReco = &vpav1.VerticalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ds-with-reco", + Namespace: "testing-daemonset", + Labels: utils.VPALabels, + }, + Spec: vpav1.VerticalPodAutoscalerSpec{ + TargetRef: &autoscalingv1.CrossVersionObjectReference{ + APIVersion: "apps/v1", + Kind: "DaemonSet", + Name: "test-ds-with-reco", + }, + UpdatePolicy: &vpav1.PodUpdatePolicy{ + UpdateMode: &updateMode, + }, + }, + Status: vpav1.VerticalPodAutoscalerStatus{ + Recommendation: &vpav1.RecommendedPodResources{ + ContainerRecommendations: []vpav1.RecommendedContainerResources{ + { + ContainerName: "container", + Target: targetResources, + UpperBound: upperBound, + LowerBound: lowerBound, + }, + }, + }, + }, +} + +// The summary of the daemonset + +var testSummaryDaemonSet = Summary{ + Namespaces: map[string]namespaceSummary{ + "testing-daemonset": { + Namespace: "testing-daemonset", + Workloads: map[string]workloadSummary{ + "test-ds-with-reco": { + ControllerName: "test-ds-with-reco", + ControllerType: "DaemonSet", Containers: map[string]containerSummary{ "container": { ContainerName: "container", diff --git a/pkg/summary/options.go b/pkg/summary/options.go index be5cce10..e8812a58 100644 --- a/pkg/summary/options.go +++ b/pkg/summary/options.go @@ -12,6 +12,7 @@ type Option func(*options) type options struct { kubeClient *kube.ClientInstance vpaClient *kube.VPAClientInstance + dynamicClient *kube.DynamicClientInstance namespace string vpaLabels map[string]string excludedContainers sets.String @@ -22,6 +23,7 @@ func defaultOptions() *options { return &options{ kubeClient: kube.GetInstance(), vpaClient: kube.GetVPAInstance(), + dynamicClient: kube.GetDynamicInstance(), namespace: namespaceAllNamespaces, vpaLabels: utils.VPALabels, excludedContainers: sets.NewString(), diff --git a/pkg/summary/summary.go b/pkg/summary/summary.go index 49322e1e..f0b44a81 100644 --- a/pkg/summary/summary.go +++ b/pkg/summary/summary.go @@ -18,10 +18,12 @@ import ( "context" "strings" - appsv1 "k8s.io/api/apps/v1" + controllerUtils "github.com/fairwindsops/controller-utils/pkg/controller" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" vpav1 "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1" "k8s.io/klog" @@ -33,18 +35,19 @@ const ( namespaceAllNamespaces = "" ) -// Summary is for storing a summary of recommendation data by namespace/deployment/container +// Summary is for storing a summary of recommendation data by namespace/controller type/container type Summary struct { Namespaces map[string]namespaceSummary } type namespaceSummary struct { - Namespace string `json:"namespace"` - Deployments map[string]deploymentSummary `json:"deployments"` + Namespace string `json:"namespace"` + Workloads map[string]workloadSummary `json:"workloads"` } -type deploymentSummary struct { - DeploymentName string `json:"deploymentName"` +type workloadSummary struct { + ControllerName string `json:"controllerName"` + ControllerType string `json:"controllerType"` Containers map[string]containerSummary `json:"containers"` } @@ -67,8 +70,8 @@ type Summarizer struct { // cached list of vpas vpas []vpav1.VerticalPodAutoscaler - // cached map of deploy/vpa name -> deployment - deploymentForVPANamed map[string]*appsv1.Deployment + // cached map of vpa name -> workload + workloadForVPANamed map[string]*controllerUtils.Workload } // NewSummarizer returns a Summarizer for all goldilocks managed VPAs in all Namespaces @@ -104,13 +107,13 @@ func (s Summarizer) GetSummary() (Summary, error) { // then add that namespace by default to the blank summary if s.namespace != namespaceAllNamespaces { summary.Namespaces[s.namespace] = namespaceSummary{ - Namespace: s.namespace, - Deployments: map[string]deploymentSummary{}, + Namespace: s.namespace, + Workloads: map[string]workloadSummary{}, } } - // cached vpas and deployments - if s.vpas == nil || s.deploymentForVPANamed == nil { + // cached vpas and workloads + if s.vpas == nil || s.workloadForVPANamed == nil { err := s.Update() if err != nil { return summary, err @@ -132,53 +135,70 @@ func (s Summarizer) GetSummary() (Summary, error) { nsSummary = val } else { nsSummary = namespaceSummary{ - Namespace: namespace, - Deployments: map[string]deploymentSummary{}, + Namespace: namespace, + Workloads: map[string]workloadSummary{}, } summary.Namespaces[namespace] = nsSummary } - // VPA.Name := Deployment.Name, as that's how goldilocks works - dSummary := deploymentSummary{ - DeploymentName: vpa.Name, + wSummary := workloadSummary{ + ControllerName: vpa.Name, + ControllerType: vpa.Spec.TargetRef.Kind, Containers: map[string]containerSummary{}, } - deployment, ok := s.deploymentForVPANamed[vpa.Name] + workload, ok := s.workloadForVPANamed[vpa.Name] if !ok { - klog.Errorf("no matching Deployment found for VPA/%s", vpa.Name) + klog.Errorf("no matching Workloads found for VPA/%s", vpa.Name) continue } if vpa.Status.Recommendation == nil { - klog.V(2).Infof("Empty status on %v", dSummary.DeploymentName) - nsSummary.Deployments[dSummary.DeploymentName] = dSummary + klog.V(2).Infof("Empty status on %v", wSummary.ControllerName) + nsSummary.Workloads[wSummary.ControllerName] = wSummary summary.Namespaces[nsSummary.Namespace] = nsSummary continue } if len(vpa.Status.Recommendation.ContainerRecommendations) <= 0 { - klog.V(2).Infof("No container recommendations found in the %v vpa.", dSummary.DeploymentName) - nsSummary.Deployments[dSummary.DeploymentName] = dSummary + klog.V(2).Infof("No container recommendations found in the %v vpa.", wSummary.ControllerName) + nsSummary.Workloads[wSummary.ControllerName] = wSummary summary.Namespaces[nsSummary.Namespace] = nsSummary continue } - // get the full set of excluded containers for this Deployment + // get the full set of excluded containers for this workload excludedContainers := sets.NewString().Union(s.excludedContainers) - if val, exists := deployment.GetAnnotations()[utils.DeploymentExcludeContainersAnnotation]; exists { + if val, exists := workload.TopController.GetAnnotations()[utils.WorkloadExcludeContainersAnnotation]; exists { excludedContainers.Insert(strings.Split(val, ",")...) } CONTAINER_REC_LOOP: for _, containerRecommendation := range vpa.Status.Recommendation.ContainerRecommendations { if excludedContainers.Has(containerRecommendation.ContainerName) { - klog.V(2).Infof("Excluding container Deployment/%s/%s", dSummary.DeploymentName, containerRecommendation.ContainerName) + klog.V(2).Infof("Excluding container %s/%s/%s", wSummary.ControllerType, wSummary.ControllerName, containerRecommendation.ContainerName) continue CONTAINER_REC_LOOP } var cSummary containerSummary - for _, c := range deployment.Spec.Template.Spec.Containers { - // find the matching container on the deployment + workloadPodSpecUnstructured, workloadPodSpecFound, err := unstructured.NestedMap(workload.TopController.UnstructuredContent(), "spec", "template", "spec") + if err != nil { + klog.Errorf("unable to parse spec.template.spec from unstructured workload. Namespace: '%s', Kind: '%s', Name: '%s'", workload.TopController.GetNamespace(), workload.TopController.GetKind(), workload.TopController.GetName()) + continue CONTAINER_REC_LOOP + } + if !workloadPodSpecFound { + klog.Errorf("no spec.template.spec field from unstructured workload. Namespace: '%s', Kind: '%s', Name: '%s'", workload.TopController.GetNamespace(), workload.TopController.GetKind(), workload.TopController.GetName()) + continue CONTAINER_REC_LOOP + } + + var workloadPodSpec corev1.PodSpec + err = runtime.DefaultUnstructuredConverter.FromUnstructured(workloadPodSpecUnstructured, &workloadPodSpec) + if err != nil { + klog.Errorf("unable to convert unstructured pod spec to PodSpec struct. Namespace: '%s', Kind: '%s', Name: '%s'", workload.TopController.GetNamespace(), workload.TopController.GetKind(), workload.TopController.GetName()) + continue CONTAINER_REC_LOOP + } + + for _, c := range workloadPodSpec.Containers { + // find the matching container on the workload if c.Name == containerRecommendation.ContainerName { cSummary = containerSummary{ ContainerName: containerRecommendation.ContainerName, @@ -189,21 +209,21 @@ func (s Summarizer) GetSummary() (Summary, error) { Limits: utils.FormatResourceList(c.Resources.Limits), Requests: utils.FormatResourceList(c.Resources.Requests), } - klog.V(6).Infof("Resources for Deployment/%s/%s: Requests: %v Limits: %v", dSummary.DeploymentName, c.Name, cSummary.Requests, cSummary.Limits) - dSummary.Containers[cSummary.ContainerName] = cSummary + klog.V(6).Infof("Resources for %s/%s/%s: Requests: %v Limits: %v", wSummary.ControllerType, wSummary.ControllerName, c.Name, cSummary.Requests, cSummary.Limits) + wSummary.Containers[cSummary.ContainerName] = cSummary continue CONTAINER_REC_LOOP } } } // update summary maps - nsSummary.Deployments[dSummary.DeploymentName] = dSummary + nsSummary.Workloads[wSummary.ControllerName] = wSummary summary.Namespaces[nsSummary.Namespace] = nsSummary } return summary, nil } -// Update the set of VPAs and Deployments that the Summarizer uses for creating a summary +// Update the set of VPAs and Workloads that the Summarizer uses for creating a summary func (s *Summarizer) Update() error { err := s.updateVPAs() if err != nil { @@ -211,7 +231,7 @@ func (s *Summarizer) Update() error { return err } - err = s.updateDeployments() + err = s.updateWorkloads() if err != nil { klog.Error(err.Error()) return err @@ -251,33 +271,33 @@ func getVPAListOptionsForLabels(vpaLabels map[string]string) metav1.ListOptions } } -func (s *Summarizer) updateDeployments() error { +func (s *Summarizer) updateWorkloads() error { nsLog := s.namespace if s.namespace == namespaceAllNamespaces { nsLog = "all namespaces" } - klog.V(3).Infof("Looking for Deployments in %s", nsLog) - deployments, err := s.listDeployments(metav1.ListOptions{}) + klog.V(3).Infof("Looking for Workloads in %s", nsLog) + workloads, err := s.listWorkloads() if err != nil { return err } - klog.V(10).Infof("Found deployments: %v", deployments) + klog.V(10).Infof("Found workloads in namespace '%s': %v", s.namespace, workloads) - // map the deployment.name -> &deployment for easy vpa lookup (since vpa.Name == deployment.Name for matching vpas/deployments) - s.deploymentForVPANamed = map[string]*appsv1.Deployment{} - for _, d := range deployments { - d := d - s.deploymentForVPANamed[d.Name] = &d + // map the workload.name -> &controllerUtils.Workload{} for easy vpa lookup (since vpa.Name == workload.Name) + s.workloadForVPANamed = map[string]*controllerUtils.Workload{} + for _, w := range workloads { + w := w + s.workloadForVPANamed[w.TopController.GetName()] = &w } return nil } -func (s Summarizer) listDeployments(listOptions metav1.ListOptions) ([]appsv1.Deployment, error) { - deployments, err := s.kubeClient.Client.AppsV1().Deployments(s.namespace).List(context.TODO(), listOptions) +func (s Summarizer) listWorkloads() ([]controllerUtils.Workload, error) { + workloads, err := controllerUtils.GetAllTopControllers(context.TODO(), s.dynamicClient.Client, s.dynamicClient.RESTMapper, s.namespace) if err != nil { return nil, err } - return deployments.Items, nil + return workloads, nil } diff --git a/pkg/summary/summary_test.go b/pkg/summary/summary_test.go index 853279f8..df47e230 100644 --- a/pkg/summary/summary_test.go +++ b/pkg/summary/summary_test.go @@ -21,24 +21,38 @@ import ( "github.com/fairwindsops/goldilocks/pkg/kube" "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" ) -func TestSummarizer(t *testing.T) { +func Test_Summarizer(t *testing.T) { kubeClientVPA := kube.GetMockVPAClient() kubeClient := kube.GetMockClient() + dynamicClient := kube.GetMockDynamicClient() summarizer := NewSummarizer() summarizer.kubeClient = kubeClient summarizer.vpaClient = kubeClientVPA + summarizer.dynamicClient = dynamicClient - _, _ = kubeClient.Client.AppsV1().Deployments("testing").Create(context.TODO(), testDeploymentBasic, metav1.CreateOptions{}) + // _, _ = dynamicClient.Client.Resource(schema.GroupVersionResource{Group: "", Version: "v1", Resource: "namespaces"}).Create(context.TODO(), nsLabeledTrueUnstructured, metav1.CreateOptions{}) + _, err := dynamicClient.Client.Resource(schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}).Namespace("testing").Create(context.TODO(), testDeploymentBasicUnstructured, metav1.CreateOptions{}) + assert.NoError(t, err) + _, err = dynamicClient.Client.Resource(schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "replicasets"}).Namespace("testing").Create(context.TODO(), testDeploymentBasicReplicaSetUnstructured, metav1.CreateOptions{}) + assert.NoError(t, err) + _, err = dynamicClient.Client.Resource(schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}).Namespace("testing").Create(context.TODO(), testDeploymentBasicPodUnstructured, metav1.CreateOptions{}) + assert.NoError(t, err) _, errOk := kubeClientVPA.Client.AutoscalingV1().VerticalPodAutoscalers("testing").Create(context.TODO(), testVPABasic, metav1.CreateOptions{}) assert.NoError(t, errOk) _, errOk2 := kubeClientVPA.Client.AutoscalingV1().VerticalPodAutoscalers("testing").Create(context.TODO(), testVPANoLabels, metav1.CreateOptions{}) assert.NoError(t, errOk2) - _, _ = kubeClient.Client.AppsV1().Deployments("testing").Create(context.TODO(), testDeploymentWithReco, metav1.CreateOptions{}) + _, err = dynamicClient.Client.Resource(schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}).Namespace("testing").Create(context.TODO(), testDeploymentWithRecoUnstructured, metav1.CreateOptions{}) + assert.NoError(t, err) + _, err = dynamicClient.Client.Resource(schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "replicasets"}).Namespace("testing").Create(context.TODO(), testDeploymentWithRecoReplicaSetUnstructured, metav1.CreateOptions{}) + assert.NoError(t, err) + _, err = dynamicClient.Client.Resource(schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}).Namespace("testing").Create(context.TODO(), testDeploymentWithRecoPodUnstructured, metav1.CreateOptions{}) + assert.NoError(t, err) _, errOk3 := kubeClientVPA.Client.AutoscalingV1().VerticalPodAutoscalers("testing").Create(context.TODO(), testVPAWithReco, metav1.CreateOptions{}) assert.NoError(t, errOk3) @@ -47,3 +61,27 @@ func TestSummarizer(t *testing.T) { assert.EqualValues(t, testSummary, got) } + +func Test_Summarizer_Daemonset(t *testing.T) { + kubeClientVPA := kube.GetMockVPAClient() + kubeClient := kube.GetMockClient() + dynamicClient := kube.GetMockDynamicClient() + + summarizer := NewSummarizer() + summarizer.kubeClient = kubeClient + summarizer.vpaClient = kubeClientVPA + summarizer.dynamicClient = dynamicClient + + // _, _ = dynamicClient.Client.Resource(schema.GroupVersionResource{Group: "", Version: "v1", Resource: "namespaces"}).Create(context.TODO(), nsLabeledTrueUnstructured, metav1.CreateOptions{}) + _, err := dynamicClient.Client.Resource(schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "daemonsets"}).Namespace("testing-daemonset").Create(context.TODO(), testDaemonSettWithRecoUnstructured, metav1.CreateOptions{}) + assert.NoError(t, err) + _, err = dynamicClient.Client.Resource(schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}).Namespace("testing-daemonset").Create(context.TODO(), testDaemonSetWithRecoPodUnstructured, metav1.CreateOptions{}) + assert.NoError(t, err) + _, errOk := kubeClientVPA.Client.AutoscalingV1().VerticalPodAutoscalers("testing-daemonset").Create(context.TODO(), testDaemonSetVPAWithReco, metav1.CreateOptions{}) + assert.NoError(t, errOk) + + got, err := summarizer.GetSummary() + assert.NoError(t, err) + + assert.EqualValues(t, testSummaryDaemonSet, got) +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 32679fb3..5d9ef56c 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -27,7 +27,7 @@ var ( // VpaUpdateModeKey is the label used to indicate the vpa update mode. VpaUpdateModeKey = LabelOrAnnotationBase + "/" + "vpa-update-mode" // DeploymentExcludeContainersAnnotation is the label used to exclude container names from being reported. - DeploymentExcludeContainersAnnotation = LabelOrAnnotationBase + "/" + "exclude-containers" + WorkloadExcludeContainersAnnotation = LabelOrAnnotationBase + "/" + "exclude-containers" // VpaResourcePolicyAnnotation is the annotation use to define the json configuration of PodResourcePolicy section of a vpa VpaResourcePolicyAnnotation = LabelOrAnnotationBase + "/" + "vpa-resource-policy" )