Skip to content
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

Upgrade horizontal scaling information (Keda) #633

Merged
merged 25 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
2776423
initialize kedaClient
Richard87 May 31, 2024
de280c2
fix hpa metrics
Richard87 May 31, 2024
6580691
log request errors as warnings
Richard87 May 31, 2024
9f12907
insert HPA for controller test
Richard87 May 31, 2024
2a82aac
remove unused constant
Richard87 May 31, 2024
c4008ce
add client request logging
Richard87 Jun 7, 2024
c037a94
add client request logging (simplified)
Richard87 Jun 7, 2024
1782c7c
add client request logging (simplified)
Richard87 Jun 7, 2024
416eac7
add client request logging (simplified)
Richard87 Jun 7, 2024
a34ed44
refactor GetComponentsForDeployment
Richard87 Jun 10, 2024
aaae0cd
test responses from scaledobjects and hpa
Richard87 Jun 12, 2024
d387e15
test responses from scaledobjects and hpa
Richard87 Jun 13, 2024
343ff63
Merge branch 'master' into update-operator-with-keda-support
Richard87 Jun 13, 2024
0c55bd4
fix unused import
Richard87 Jun 13, 2024
26a99a4
update swaggerui
Richard87 Jun 13, 2024
b100445
remove unused function
Richard87 Jun 13, 2024
b2db348
Update api/utils/logs/roundtrip_logger.go
Richard87 Jun 13, 2024
9f43d5d
debugging
Richard87 Jun 14, 2024
1e97f25
handle client err seperatley from request error
Richard87 Jun 14, 2024
3ee06de
move getComponentStateFromSpec to environments package
Richard87 Jun 14, 2024
8ef0897
use kubequery where possible, remove unsuded fields
Richard87 Jun 14, 2024
53d1e3c
cleanup deprecated fields, add lint ignore
Richard87 Jun 14, 2024
f48554f
use same status code field as gin middleware
Richard87 Jun 14, 2024
e1f44e5
Update api/deployments/component_handler.go
Richard87 Jun 14, 2024
b6507f3
Update api/deployments/component_handler.go
Richard87 Jun 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/applications/get_applications_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ func getComponentsForActiveDeploymentsInEnvironments(ctx context.Context, deploy

envName := env.Name
g.Go(func() error {
componentModels, err := deploy.GetComponentsForDeployment(ctx, appName, deployment)
componentModels, err := deploy.GetComponentsForDeployment(ctx, appName, deployment.Name, deployment.Environment)
if err == nil {
chanData <- &ChannelData{key: envName, components: componentModels}
}
Expand Down
248 changes: 207 additions & 41 deletions api/deployments/component_controller_test.go

Large diffs are not rendered by default.

147 changes: 50 additions & 97 deletions api/deployments/component_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@ package deployments

import (
"context"
"fmt"
"strings"

deploymentModels "github.com/equinor/radix-api/api/deployments/models"
"github.com/equinor/radix-api/api/kubequery"
"github.com/equinor/radix-api/api/utils"
"github.com/equinor/radix-api/api/models"
"github.com/equinor/radix-api/api/utils/event"
"github.com/equinor/radix-api/api/utils/horizontalscaling"
"github.com/equinor/radix-api/api/utils/labelselector"
radixutils "github.com/equinor/radix-common/utils"
"github.com/equinor/radix-common/utils/slice"
Expand All @@ -18,7 +16,8 @@ import (
"github.com/equinor/radix-operator/pkg/apis/kube"
v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1"
crdUtils "github.com/equinor/radix-operator/pkg/apis/utils"
v2 "k8s.io/api/autoscaling/v2"
"github.com/kedacore/keda/v2/apis/keda/v1alpha1"
autoscalingv2 "k8s.io/api/autoscaling/v2"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
Expand All @@ -27,124 +26,69 @@ import (
)

// GetComponentsForDeployment Gets a list of components for a given deployment
func (deploy *deployHandler) GetComponentsForDeployment(ctx context.Context, appName string, deployment *deploymentModels.DeploymentSummary) ([]*deploymentModels.Component, error) {
envNs := crdUtils.GetEnvironmentNamespace(appName, deployment.Environment)
rd, err := deploy.accounts.UserAccount.RadixClient.RadixV1().RadixDeployments(envNs).Get(ctx, deployment.Name, metav1.GetOptions{})
func (deploy *deployHandler) GetComponentsForDeployment(ctx context.Context, appName, deploymentName, envName string) ([]*deploymentModels.Component, error) {
rd, err := kubequery.GetRadixDeploymentByName(ctx, deploy.accounts.UserAccount.RadixClient, appName, envName, deploymentName)
if err != nil {
return nil, err
}

ra, _ := deploy.accounts.UserAccount.RadixClient.RadixV1().RadixApplications(crdUtils.GetAppNamespace(appName)).Get(ctx, appName, metav1.GetOptions{})
var components []*deploymentModels.Component

for _, component := range rd.Spec.Components {
componentModel, err := deploy.getComponent(ctx, &component, ra, rd, deployment)
if err != nil {
return nil, err
}
components = append(components, componentModel)
ra, err := kubequery.GetRadixApplication(ctx, deploy.accounts.UserAccount.RadixClient, appName)
if err != nil {
return nil, err
}

for _, component := range rd.Spec.Jobs {
componentModel, err := deploy.getComponent(ctx, &component, ra, rd, deployment)
if err != nil {
return nil, err
}
components = append(components, componentModel)
deploymentList, err := kubequery.GetDeploymentsForEnvironment(ctx, deploy.accounts.UserAccount.Client, appName, envName)
if err != nil {
return nil, err
}

return components, nil
}

// GetComponentsForDeploymentName handler for GetDeployments
func (deploy *deployHandler) GetComponentsForDeploymentName(ctx context.Context, appName, deploymentName string) ([]*deploymentModels.Component, error) {
deployments, err := deploy.GetDeploymentsForApplication(ctx, appName)
podList, err := kubequery.GetPodsForEnvironmentComponents(ctx, deploy.accounts.UserAccount.Client, appName, envName)
if err != nil {
return nil, err
}

for _, depl := range deployments {
if strings.EqualFold(depl.Name, deploymentName) {
return deploy.GetComponentsForDeployment(ctx, appName, depl)
}
hpas, err := kubequery.GetHorizontalPodAutoscalersForEnvironment(ctx, deploy.accounts.UserAccount.Client, appName, envName)
if err != nil {
return nil, err
}

return nil, deploymentModels.NonExistingDeployment(nil, deploymentName)
}

func (deploy *deployHandler) getComponent(ctx context.Context, component v1.RadixCommonDeployComponent, ra *v1.RadixApplication, rd *v1.RadixDeployment, deployment *deploymentModels.DeploymentSummary) (*deploymentModels.Component, error) {
envNs := crdUtils.GetEnvironmentNamespace(ra.Name, deployment.Environment)

// TODO: Add interface for RA + EnvConfig
environmentConfig := utils.GetComponentEnvironmentConfig(ra, deployment.Environment, component.GetName())

deploymentComponent, err := GetComponentStateFromSpec(ctx, deploy.accounts.UserAccount.Client, ra.Name, deployment, rd.Status, environmentConfig, component)
scaledObjects, err := kubequery.GetScaledObjectsForEnvironment(ctx, deploy.accounts.UserAccount.KedaClient, appName, envName)
if err != nil {
return nil, err
}

if component.GetType() == v1.RadixComponentTypeComponent {
hpaSummary, err := deploy.getHpaSummary(ctx, component, ra.Name, envNs)
if err != nil {
return nil, err
}
deploymentComponent.HorizontalScalingSummary = hpaSummary
noJobPayloadReq, err := labels.NewRequirement(kube.RadixSecretTypeLabel, selection.NotEquals, []string{string(kube.RadixSecretJobPayload)})
if err != nil {
return nil, err
}
return deploymentComponent, nil
}

func (deploy *deployHandler) getHpaSummary(ctx context.Context, component v1.RadixCommonDeployComponent, appName, envNs string) (*deploymentModels.HorizontalScalingSummary, error) {
selector := labelselector.ForComponent(appName, component.GetName()).String()
hpas, err := deploy.accounts.UserAccount.Client.AutoscalingV2().HorizontalPodAutoscalers(envNs).List(ctx, metav1.ListOptions{LabelSelector: selector})
secretList, err := kubequery.GetSecretsForEnvironment(ctx, deploy.accounts.UserAccount.Client, appName, envName, *noJobPayloadReq)
if err != nil {
return nil, err
}
if len(hpas.Items) == 0 {
return nil, nil
eventList, err := kubequery.GetEventsForEnvironment(ctx, deploy.accounts.UserAccount.Client, appName, envName)
if err != nil {
return nil, err
}
if len(hpas.Items) > 1 {
return nil, fmt.Errorf("found more than 1 HPA for component %s", component.GetName())
certs, err := kubequery.GetCertificatesForEnvironment(ctx, deploy.accounts.UserAccount.CertManagerClient, appName, envName)
Richard87 marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, err
}
hpa := &hpas.Items[0]

minReplicas := int32(1)
if hpa.Spec.MinReplicas != nil {
minReplicas = *hpa.Spec.MinReplicas
certRequests, err := kubequery.GetCertificateRequestsForEnvironment(ctx, deploy.accounts.UserAccount.CertManagerClient, appName, envName)
Richard87 marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, err
}
maxReplicas := hpa.Spec.MaxReplicas

currentCpuUtil, targetCpuUtil := getHpaMetrics(hpa, corev1.ResourceCPU)
currentMemoryUtil, targetMemoryUtil := getHpaMetrics(hpa, corev1.ResourceMemory)

hpaSummary := deploymentModels.HorizontalScalingSummary{
MinReplicas: minReplicas,
MaxReplicas: maxReplicas,
CurrentCPUUtilizationPercentage: currentCpuUtil,
TargetCPUUtilizationPercentage: targetCpuUtil,
CurrentMemoryUtilizationPercentage: currentMemoryUtil,
TargetMemoryUtilizationPercentage: targetMemoryUtil,
}
return &hpaSummary, nil
return models.BuildComponents(ra, rd, deploymentList, podList, hpas, secretList, eventList, certs, certRequests, nil, scaledObjects), nil
}

func getHpaMetrics(hpa *v2.HorizontalPodAutoscaler, resourceName corev1.ResourceName) (*int32, *int32) {
currentResourceUtil := getHpaCurrentMetric(hpa, resourceName)
// find resource utilization target
var targetResourceUtil *int32
targetResourceMetric := horizontalscaling.GetHpaMetric(hpa, resourceName)
if targetResourceMetric != nil {
targetResourceUtil = targetResourceMetric.Resource.Target.AverageUtilization
// GetComponentsForDeploymentName handler for GetDeployments
func (deploy *deployHandler) GetComponentsForDeploymentName(ctx context.Context, appName, deploymentName string) ([]*deploymentModels.Component, error) {
deployments, err := deploy.GetDeploymentsForApplication(ctx, appName)
if err != nil {
return nil, err
}
return currentResourceUtil, targetResourceUtil
}

func getHpaCurrentMetric(hpa *v2.HorizontalPodAutoscaler, resourceName corev1.ResourceName) *int32 {
for _, metric := range hpa.Status.CurrentMetrics {
if metric.Resource != nil && metric.Resource.Name == resourceName {
return metric.Resource.Current.AverageUtilization
for _, depl := range deployments {
if strings.EqualFold(depl.Name, deploymentName) {
return deploy.GetComponentsForDeployment(ctx, appName, depl.Name, depl.Environment)
}
}
return nil

return nil, deploymentModels.NonExistingDeployment(nil, deploymentName)
}

// GetComponentStateFromSpec Returns a component with the current state
Expand All @@ -155,12 +99,16 @@ func GetComponentStateFromSpec(
deployment *deploymentModels.DeploymentSummary,
deploymentStatus v1.RadixDeployStatus,
environmentConfig v1.RadixCommonEnvironmentConfig,
component v1.RadixCommonDeployComponent) (*deploymentModels.Component, error) {
component v1.RadixCommonDeployComponent,
nilsgstrabo marked this conversation as resolved.
Show resolved Hide resolved
hpas []autoscalingv2.HorizontalPodAutoscaler,
scaledObjects []v1alpha1.ScaledObject,
) (*deploymentModels.Component, error) {

var componentPodNames []string
var environmentVariables map[string]string
var replicaSummaryList []deploymentModels.ReplicaSummary
var auxResource deploymentModels.AuxiliaryResource
var horizontalScalingSummary *deploymentModels.HorizontalScalingSummary

envNs := crdUtils.GetEnvironmentNamespace(appName, deployment.Environment)
status := deploymentModels.ConsistentComponent
Expand Down Expand Up @@ -200,13 +148,18 @@ func GetComponentStateFromSpec(
componentBuilder.WithNotifications(jobComponent.Notifications)
}

if component.GetType() == v1.RadixComponentTypeComponent {
horizontalScalingSummary = models.GetHpaSummary(appName, component.GetName(), hpas, scaledObjects)
}

return componentBuilder.
WithComponent(component).
WithStatus(status).
WithPodNames(componentPodNames).
WithReplicaSummaryList(replicaSummaryList).
WithRadixEnvironmentVariables(environmentVariables).
WithAuxiliaryResource(auxResource).
WithHorizontalScalingSummary(horizontalScalingSummary).
BuildComponent()
}

Expand Down
4 changes: 2 additions & 2 deletions api/deployments/deployment_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type DeployHandler interface {
GetDeploymentWithName(ctx context.Context, appName, deploymentName string) (*deploymentModels.Deployment, error)
GetDeploymentsForApplicationEnvironment(ctx context.Context, appName, environment string, latest bool) ([]*deploymentModels.DeploymentSummary, error)
GetComponentsForDeploymentName(ctx context.Context, appName, deploymentID string) ([]*deploymentModels.Component, error)
GetComponentsForDeployment(ctx context.Context, appName string, deployment *deploymentModels.DeploymentSummary) ([]*deploymentModels.Component, error)
GetComponentsForDeployment(ctx context.Context, appName, deploymentName, envName string) ([]*deploymentModels.Component, error)
GetLatestDeploymentForApplicationEnvironment(ctx context.Context, appName, environment string) (*deploymentModels.DeploymentSummary, error)
GetDeploymentsForPipelineJob(context.Context, string, string) ([]*deploymentModels.DeploymentSummary, error)
GetJobComponentDeployments(context.Context, string, string, string) ([]*deploymentModels.DeploymentItem, error)
Expand Down Expand Up @@ -171,7 +171,7 @@ func (deploy *deployHandler) GetDeploymentWithName(ctx context.Context, appName,
return nil, err
}

components, err := deploy.GetComponentsForDeployment(ctx, appName, deploymentSummary)
components, err := deploy.GetComponentsForDeployment(ctx, appName, deploymentName, deploymentSummary.Environment)
if err != nil {
return nil, err
}
Expand Down
8 changes: 4 additions & 4 deletions api/deployments/mock/deployment_handler_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 39 additions & 0 deletions api/deployments/models/component_deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -422,31 +422,70 @@ type HorizontalScalingSummary struct {
// example: 5
MaxReplicas int32 `json:"maxReplicas"`

// CooldownPeriod in seconds. From radixconfig.yaml
//
// required: false
// example: 300
CooldownPeriod int32 `json:"cooldownPeriod"`

// PollingInterval in seconds. From radixconfig.yaml
//
// required: false
// example: 30
PollingInterval int32 `json:"pollingInterval"`

// Triggers lists status of all triggers found in radixconfig.yaml
//
// required: false
// example: 30
Triggers []HorizontalScalingSummaryTriggerStatus `json:"triggers"`

// Component current average CPU utilization over all pods, represented as a percentage of requested CPU
//
// required: false
// example: 70
// deprecated: use Triggers instead. Will be removed from Radix API 2025-01-01.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The deprecated "annotation" is not propagated to swagger.json (if that was the intention), nor does it flag it as deprecated to the linters.
Applies to the next three field

CurrentCPUUtilizationPercentage *int32 `json:"currentCPUUtilizationPercentage"`

// Component target average CPU utilization over all pods
//
// required: false
// example: 80
// deprecated: use Triggers instead. Will be removed from Radix API 2025-01-01.
TargetCPUUtilizationPercentage *int32 `json:"targetCPUUtilizationPercentage"`

// Component current average memory utilization over all pods, represented as a percentage of requested memory
//
// required: false
// example: 80
// deprecated: use Triggers instead. Will be removed from Radix API 2025-01-01.
CurrentMemoryUtilizationPercentage *int32 `json:"currentMemoryUtilizationPercentage"`

// Component target average memory utilization over all pods
//
// required: false
// example: 80
// deprecated: use Triggers instead. Will be removed from Radix API 2025-01-01.
TargetMemoryUtilizationPercentage *int32 `json:"targetMemoryUtilizationPercentage"`
}

type HorizontalScalingSummaryTriggerStatus struct {
// Name of trigger
Name string `json:"name"`

// CurrentUtilization is the last measured utilization
CurrentUtilization string `json:"current_utilization"`

// TargetUtilization is the average target across replicas
TargetUtilization string `json:"target_utilization"`

// Type of trigger
Type string `json:"type"`

// Error contains short description if trigger have problems
Error string `json:"error,omitempty"`
}

// Node Defines node attributes, where pod should be scheduled
type Node struct {
// Gpu Holds lists of node GPU types, with dashed types to exclude
Expand Down
2 changes: 1 addition & 1 deletion api/environments/component_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func (eh EnvironmentHandler) RestartComponentAuxiliaryResource(ctx context.Conte
return err
}

componentsDto, err := eh.deployHandler.GetComponentsForDeployment(ctx, appName, deploySummary)
componentsDto, err := eh.deployHandler.GetComponentsForDeployment(ctx, appName, deploySummary.Name, envName)
if err != nil {
return err
}
Expand Down
18 changes: 15 additions & 3 deletions api/environments/environment_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,10 @@ func (eh EnvironmentHandler) GetEnvironment(ctx context.Context, appName, envNam
if err != nil {
return nil, err
}

scaledObjects, err := kubequery.GetScaledObjectsForEnvironment(ctx, eh.accounts.UserAccount.KedaClient, appName, envName)
if err != nil {
return nil, err
}
noJobPayloadReq, err := labels.NewRequirement(kube.RadixSecretTypeLabel, selection.NotEquals, []string{string(kube.RadixSecretJobPayload)})
if err != nil {
return nil, err
Expand All @@ -216,7 +219,7 @@ func (eh EnvironmentHandler) GetEnvironment(ctx context.Context, appName, envNam
return nil, err
}

env := apimodels.BuildEnvironment(rr, ra, re, rdList, rjList, deploymentList, componentPodList, hpaList, secretList, secretProviderClassList, eventList, certs, certRequests, eh.tlsValidator)
env := apimodels.BuildEnvironment(rr, ra, re, rdList, rjList, deploymentList, componentPodList, hpaList, secretList, secretProviderClassList, eventList, certs, certRequests, eh.tlsValidator, scaledObjects)
return env, nil
}

Expand Down Expand Up @@ -449,12 +452,21 @@ func (eh EnvironmentHandler) getRadixCommonComponentUpdater(ctx context.Context,
updater = &radixDeployJobComponentUpdater{base: baseUpdater}
}

hpas, err := eh.getHPAsInEnvironment(ctx, appName, envName)
nilsgstrabo marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, err
}
scalers, err := eh.getScaledObjectsInEnvironment(ctx, appName, envName)
nilsgstrabo marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, err
}

baseUpdater.componentIndex = componentIndex
baseUpdater.componentToPatch = componentToPatch

ra, _ := eh.getRadixApplicationInAppNamespace(ctx, appName)
baseUpdater.environmentConfig = utils.GetComponentEnvironmentConfig(ra, envName, componentName)
baseUpdater.componentState, err = deployments.GetComponentStateFromSpec(ctx, eh.client, appName, deploymentSummary, rd.Status, baseUpdater.environmentConfig, componentToPatch)
baseUpdater.componentState, err = deployments.GetComponentStateFromSpec(ctx, eh.client, appName, deploymentSummary, rd.Status, baseUpdater.environmentConfig, componentToPatch, hpas, scalers)
if err != nil {
return nil, err
}
Expand Down
Loading
Loading