-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[clusteragent/autoscaling] Add local fallback fields to DatadogPodAutoscalerInternal
#30776
base: main
Are you sure you want to change the base?
Changes from 9 commits
2346774
bf45051
c54a91e
0ca33ba
2b491ac
1957a34
f1305ff
3e72df5
70ed026
3afc059
5757b59
92263a1
fffced9
b69bcfb
8424eb9
5b04388
767066e
5dd8695
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,40 @@ | ||||||
// Unless explicitly stated otherwise all files in this repository are licensed | ||||||
// under the Apache License Version 2.0. | ||||||
// This product includes software developed at Datadog (https://www.datadoghq.com/). | ||||||
// Copyright 2024-present Datadog, Inc. | ||||||
|
||||||
//go:build kubeapiserver | ||||||
|
||||||
package model | ||||||
|
||||||
import "encoding/json" | ||||||
|
||||||
// exported for testing purposes | ||||||
const ( | ||||||
AnnotationsURLKey = "autoscaling.datadoghq.com/url" | ||||||
AnnotationsFallbackURLKey = "autoscaling.datadoghq.com/fallback-url" | ||||||
AnnotationsSettingsKey = "autoscaling.datadoghq.com/settings" | ||||||
) | ||||||
|
||||||
// Annotations represents the relevant annotations on a DatadogPodAutoscaler object | ||||||
type Annotations struct { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think the fact that the info comes from annotations is important, the info itself is important. I would see it as a That could directly be a field in DPAInt |
||||||
Endpoint string | ||||||
FallbackEndpoint string | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we want to support another endpoint for fallback (for now). |
||||||
Settings map[string]string | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I think we can keep the types (int, string, etc.) so that the recommender receives typed info. This is what the JSON/Proto allows: https://github.com/DataDog/agent-payload/blob/master/jsonschema/WorkloadRecommendationsRequest.json#L51-L74 |
||||||
} | ||||||
|
||||||
// ParseAnnotations extracts the relevant autoscaling annotations from a kubernetes annotation map | ||||||
func ParseAnnotations(annotations map[string]string) Annotations { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure the function needs to be exported. It should be call within the package and filled directly by the |
||||||
annotation := Annotations{ | ||||||
Endpoint: annotations[AnnotationsURLKey], | ||||||
FallbackEndpoint: annotations[AnnotationsFallbackURLKey], | ||||||
} | ||||||
|
||||||
settings := map[string]string{} | ||||||
err := json.Unmarshal([]byte(annotations[AnnotationsSettingsKey]), &settings) | ||||||
if err == nil && len(settings) > 0 { | ||||||
annotation.Settings = settings | ||||||
} | ||||||
|
||||||
return annotation | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
// Unless explicitly stated otherwise all files in this repository are licensed | ||
// under the Apache License Version 2.0. | ||
// This product includes software developed at Datadog (https://www.datadoghq.com/). | ||
// Copyright 2024-present Datadog, Inc. | ||
|
||
//go:build kubeapiserver && test | ||
|
||
package model | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestParseAnnotation(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
annotations map[string]string | ||
expected Annotations | ||
}{ | ||
{ | ||
name: "Empty annotations", | ||
annotations: map[string]string{}, | ||
expected: Annotations{}, | ||
}, | ||
{ | ||
name: "URL annotation", | ||
annotations: map[string]string{ | ||
AnnotationsURLKey: "localhost:8080/test", | ||
}, | ||
expected: Annotations{ | ||
Endpoint: "localhost:8080/test", | ||
}, | ||
}, | ||
{ | ||
name: "Fallback annotation", | ||
annotations: map[string]string{ | ||
AnnotationsURLKey: "localhost:8080/test", | ||
AnnotationsFallbackURLKey: "localhost:8080/fallback", | ||
}, | ||
expected: Annotations{ | ||
Endpoint: "localhost:8080/test", | ||
FallbackEndpoint: "localhost:8080/fallback", | ||
}, | ||
}, | ||
{ | ||
name: "Settings annotation", | ||
annotations: map[string]string{ | ||
AnnotationsURLKey: "localhost:8080/test", | ||
AnnotationsFallbackURLKey: "localhost:8080/fallback", | ||
AnnotationsSettingsKey: `{"key": "value"}`, | ||
}, | ||
expected: Annotations{ | ||
Endpoint: "localhost:8080/test", | ||
FallbackEndpoint: "localhost:8080/fallback", | ||
Settings: map[string]string{ | ||
"key": "value", | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
parsedAnnotation := ParseAnnotations(tt.annotations) | ||
assert.Equal(t, tt.expected, parsedAnnotation) | ||
}) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -38,6 +38,9 @@ type PodAutoscalerInternal struct { | |||||
// name is the name of the PodAutoscaler | ||||||
name string | ||||||
|
||||||
// annotations are the annotations of the PodAutoscaler | ||||||
annotations Annotations | ||||||
|
||||||
// creationTimestamp is the time when the kubernetes object was created | ||||||
// creationTimestamp is stored in .DatadogPodAutoscaler.CreationTimestamp | ||||||
creationTimestamp time.Time | ||||||
|
@@ -53,9 +56,15 @@ type PodAutoscalerInternal struct { | |||||
// (only if owner == remote) | ||||||
settingsTimestamp time.Time | ||||||
|
||||||
// scalingValues represents the current target scaling values (retrieved from RC) | ||||||
// scalingValues represents the active scaling values that should be used | ||||||
scalingValues ScalingValues | ||||||
|
||||||
// mainScalingValues represents the scaling values retrieved from the product | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
mainScalingValues ScalingValues | ||||||
|
||||||
// fallbackScalingValues represents the scaling values retrieved from the fallback | ||||||
fallbackScalingValues ScalingValues | ||||||
|
||||||
// horizontalLastActions is the last horizontal action successfully taken | ||||||
horizontalLastActions []datadoghq.DatadogPodAutoscalerHorizontalAction | ||||||
|
||||||
|
@@ -100,8 +109,9 @@ type PodAutoscalerInternal struct { | |||||
// NewPodAutoscalerInternal creates a new PodAutoscalerInternal from a Kubernetes CR | ||||||
func NewPodAutoscalerInternal(podAutoscaler *datadoghq.DatadogPodAutoscaler) PodAutoscalerInternal { | ||||||
pai := PodAutoscalerInternal{ | ||||||
namespace: podAutoscaler.Namespace, | ||||||
name: podAutoscaler.Name, | ||||||
namespace: podAutoscaler.Namespace, | ||||||
name: podAutoscaler.Name, | ||||||
annotations: ParseAnnotations(podAutoscaler.Annotations), | ||||||
} | ||||||
pai.UpdateFromPodAutoscaler(podAutoscaler) | ||||||
pai.UpdateFromStatus(&podAutoscaler.Status) | ||||||
|
@@ -128,6 +138,7 @@ func NewPodAutoscalerFromSettings(ns, name string, podAutoscalerSpec *datadoghq. | |||||
func (p *PodAutoscalerInternal) UpdateFromPodAutoscaler(podAutoscaler *datadoghq.DatadogPodAutoscaler) { | ||||||
p.creationTimestamp = podAutoscaler.CreationTimestamp.Time | ||||||
p.generation = podAutoscaler.Generation | ||||||
p.annotations = ParseAnnotations(podAutoscaler.Annotations) | ||||||
p.spec = podAutoscaler.Spec.DeepCopy() | ||||||
// Reset the target GVK as it might have changed | ||||||
// Resolving the target GVK is done in the controller sync to ensure proper sync and error handling | ||||||
|
@@ -148,16 +159,36 @@ func (p *PodAutoscalerInternal) UpdateFromSettings(podAutoscalerSpec *datadoghq. | |||||
p.horizontalEventsRetention = getHorizontalEventsRetention(podAutoscalerSpec.Policy, longestScalingRulePeriodAllowed) | ||||||
} | ||||||
|
||||||
// UpdateFromValues updates the PodAutoscalerInternal from a new scaling values | ||||||
// UpdateFromValues updates the PodAutoscalerInternal scaling values | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ideally we would need to refine this API so that it's not complete freedom from controller 🤔 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could this validation (ensuring that |
||||||
func (p *PodAutoscalerInternal) UpdateFromValues(scalingValues ScalingValues) { | ||||||
p.scalingValues = scalingValues | ||||||
} | ||||||
|
||||||
// UpdateFromMainValues updates the PodAutoscalerInternal from new main scaling values | ||||||
func (p *PodAutoscalerInternal) UpdateFromMainValues(mainScalingValues ScalingValues) { | ||||||
p.mainScalingValues = mainScalingValues | ||||||
} | ||||||
|
||||||
// UpdateFromLocalValues updates the PodAutoscalerInternal from new local scaling values | ||||||
func (p *PodAutoscalerInternal) UpdateFromLocalValues(fallbackScalingValues ScalingValues) { | ||||||
p.fallbackScalingValues = fallbackScalingValues | ||||||
} | ||||||
|
||||||
// RemoveValues clears autoscaling values data from the PodAutoscalerInternal as we stopped autoscaling | ||||||
func (p *PodAutoscalerInternal) RemoveValues() { | ||||||
p.scalingValues = ScalingValues{} | ||||||
} | ||||||
|
||||||
// RemoveMainValues clears main autoscaling values data from the PodAutoscalerInternal as we stopped autoscaling | ||||||
func (p *PodAutoscalerInternal) RemoveMainValues() { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any difference with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, no difference, mostly added to stay consistent with the |
||||||
p.mainScalingValues = ScalingValues{} | ||||||
} | ||||||
|
||||||
// RemoveLocalValues clears local autoscaling values data from the PodAutoscalerInternal as we stopped autoscaling | ||||||
func (p *PodAutoscalerInternal) RemoveLocalValues() { | ||||||
p.fallbackScalingValues = ScalingValues{} | ||||||
} | ||||||
|
||||||
// UpdateFromHorizontalAction updates the PodAutoscalerInternal from a new horizontal action | ||||||
func (p *PodAutoscalerInternal) UpdateFromHorizontalAction(action *datadoghq.DatadogPodAutoscalerHorizontalAction, err error) { | ||||||
if err != nil { | ||||||
|
@@ -295,6 +326,11 @@ func (p *PodAutoscalerInternal) Name() string { | |||||
return p.name | ||||||
} | ||||||
|
||||||
// Annotations returns the annotations on the PodAutoscaler | ||||||
func (p *PodAutoscalerInternal) Annotations() Annotations { | ||||||
return p.annotations | ||||||
} | ||||||
|
||||||
// ID returns the functional identifier of the PodAutoscaler | ||||||
func (p *PodAutoscalerInternal) ID() string { | ||||||
return p.namespace + "/" + p.name | ||||||
|
@@ -320,11 +356,21 @@ func (p *PodAutoscalerInternal) CreationTimestamp() time.Time { | |||||
return p.creationTimestamp | ||||||
} | ||||||
|
||||||
// ScalingValues returns the scaling values of the PodAutoscaler | ||||||
// ScalingValues returns a pointer to the active scaling values of the PodAutoscaler | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not a pointer |
||||||
func (p *PodAutoscalerInternal) ScalingValues() ScalingValues { | ||||||
return p.scalingValues | ||||||
} | ||||||
|
||||||
// MainScalingValues returns the main scaling values of the PodAutoscaler | ||||||
func (p *PodAutoscalerInternal) MainScalingValues() ScalingValues { | ||||||
return p.mainScalingValues | ||||||
} | ||||||
|
||||||
// FallbackScalingValues returns the fallback scaling values of the PodAutoscaler | ||||||
func (p *PodAutoscalerInternal) FallbackScalingValues() ScalingValues { | ||||||
return p.fallbackScalingValues | ||||||
} | ||||||
|
||||||
// HorizontalLastActions returns the last horizontal actions taken | ||||||
func (p *PodAutoscalerInternal) HorizontalLastActions() []datadoghq.DatadogPodAutoscalerHorizontalAction { | ||||||
return p.horizontalLastActions | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
// Unless explicitly stated otherwise all files in this repository are licensed | ||
// under the Apache License Version 2.0. | ||
// This product includes software developed at Datadog (https://www.datadoghq.com/). | ||
// Copyright 2024-present Datadog, Inc. | ||
|
||
//go:build kubeapiserver | ||
|
||
package model | ||
|
||
import ( | ||
"time" | ||
|
||
"k8s.io/apimachinery/pkg/api/resource" | ||
|
||
datadoghq "github.com/DataDog/datadog-operator/apis/datadoghq/v1alpha1" | ||
) | ||
|
||
// ScalingValues represents the scaling values (horizontal and vertical) for a target | ||
type ScalingValues struct { | ||
// HorizontalError refers to an error encountered by Datadog while computing the horizontal scaling values | ||
HorizontalError error | ||
Horizontal *HorizontalScalingValues | ||
|
||
// VerticalError refers to an error encountered by Datadog while computing the vertical scaling values | ||
VerticalError error | ||
Vertical *VerticalScalingValues | ||
|
||
// Error refers to a general error encountered by Datadog while computing the scaling values | ||
Error error | ||
} | ||
|
||
// HorizontalScalingValues holds the horizontal scaling values for a target | ||
type HorizontalScalingValues struct { | ||
// Source is the source of the value | ||
Source datadoghq.DatadogPodAutoscalerValueSource | ||
|
||
// Timestamp is the time at which the data was generated | ||
Timestamp time.Time | ||
|
||
// Replicas is the desired number of replicas for the target | ||
Replicas int32 | ||
} | ||
|
||
// VerticalScalingValues holds the vertical scaling values for a target | ||
type VerticalScalingValues struct { | ||
// Source is the source of the value | ||
Source datadoghq.DatadogPodAutoscalerValueSource | ||
|
||
// Timestamp is the time at which the data was generated | ||
Timestamp time.Time | ||
|
||
// ResourcesHash is the hash of containerResources | ||
ResourcesHash string | ||
|
||
// ContainerResources holds the resources for a container | ||
ContainerResources []datadoghq.DatadogPodAutoscalerContainerResources | ||
} | ||
|
||
// SumCPUMemoryRequests sums the CPU and memory requests of all containers | ||
func (v *VerticalScalingValues) SumCPUMemoryRequests() (cpu, memory resource.Quantity) { | ||
for _, container := range v.ContainerResources { | ||
cpuReq := container.Requests.Cpu() | ||
if cpuReq != nil { | ||
cpu.Add(*cpuReq) | ||
} | ||
|
||
memoryReq := container.Requests.Memory() | ||
if memoryReq != nil { | ||
memory.Add(*memoryReq) | ||
} | ||
} | ||
|
||
return | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As you already have a JSON for settings, it can simplified as a single JSON object: