Skip to content

Commit

Permalink
feat: Add API endpoint for auto duration policy in boost
Browse files Browse the repository at this point in the history
- Added `apiEndpoint` field to `AutoDurationPolicy` struct in `startupcpuboost_types.go`
- Modified `auto.go` in `internal/boost/duration` to use the `apiEndpoint` for making API calls to get duration predictions
- Updated `startupcpuboost.go` to map the `apiEndpoint` from the `AutoDurationPolicy` spec

Related to google#59
  • Loading branch information
CharinduThisara committed Oct 1, 2024
1 parent bddddd5 commit dfa39ec
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 35 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ spec:
containerPolicies:
- containerName: spring-rest-jpa
autoPolicy:
metric: "auto"
apiEndpoint: "http://exampleUrl:examplePort"
```

### [Boost duration] fixed time
Expand Down Expand Up @@ -227,7 +227,7 @@ Define the POD condition, the resource boost effect will last for the predicted
spec:
durationPolicy:
autoPolicy:
metric: "auto"
apiEndpoint: "http://exampleUrl:examplePort"
```

## Configuration
Expand Down
4 changes: 2 additions & 2 deletions api/v1alpha1/startupcpuboost_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ type PodConditionDurationPolicy struct {
// AutoDurationPolicy defines the autoPolicy based duration policy
type AutoDurationPolicy struct {
// Metric specifies the metric to be used for automatic adjustment
Value string `json:"metric"`
ApiEndpoint string `json:"apiEndpoint,omitempty"`
}

// DurationPolicy defines the policy used to determine the duration
Expand Down Expand Up @@ -94,7 +94,7 @@ type PercentageIncrease struct {
// CPU resources based on certain metrics or conditions
type AutoResourcePolicy struct {
// Metric specifies the metric to be used for automatic adjustment
Value string `json:"metric"`
ApiEndpoint string `json:"apiEndpoint,omitempty"`
}

// ContainerPolicy defines the policy used to determine the target
Expand Down
45 changes: 27 additions & 18 deletions internal/boost/duration/auto.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,38 @@
package duration

import (
"context"
"bytes"
"encoding/json"
"net/http"
"time"

ctrl "sigs.k8s.io/controller-runtime"
v1 "k8s.io/api/core/v1"
)

const (
AutoDurationPolicyName = "AutoDuration"
)

type AutoDurationPolicy struct {
apiEndpoint string
}

func (p *AutoDurationPolicy) Name() string {
return AutoDurationPolicyName
}

// Valid returns true if the pod is still within the duration
func (p *AutoDurationPolicy) Valid(pod *v1.Pod) bool {
now := time.Now()
duration, err := p.GetDuration(pod)

if err != nil {
return false
}

return pod.CreationTimestamp.Add(duration).After(now)
}

type DurationPrediction struct {
Duration string `json:"duration"`
}
Expand All @@ -37,40 +57,29 @@ func NewAutoDurationPolicy(apiEndpoint string) *AutoDurationPolicy {
}
}

func (p *AutoDurationPolicy) GetDuration(ctx context.Context) (time.Duration, error) {
prediction, err := p.getPrediction(ctx)
func (p *AutoDurationPolicy) GetDuration(pod *v1.Pod) (time.Duration, error) {
prediction, err := p.getPrediction(pod)
if err != nil {
return 0, err
}
return time.ParseDuration(prediction.Duration)
}

func (p *AutoDurationPolicy) getPrediction(ctx context.Context) (*DurationPrediction, error) {
log := ctrl.LoggerFrom(ctx).WithName("auto-duration-policy").WithValues("apiEndpoint", p.apiEndpoint)
client := &http.Client{Timeout: 10 * time.Second}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, p.apiEndpoint, nil)
func (p *AutoDurationPolicy) getPrediction(pod *v1.Pod) (*DurationPrediction, error) {
podData, err := json.Marshal(pod)
if err != nil {
log.Error(err, "failed to create request")
return nil, err
}

resp, err := client.Do(req)
resp, err := http.Post(p.apiEndpoint+"/duration", "application/json", bytes.NewBuffer(podData))
if err != nil {
log.Error(err, "failed to call API")
return nil, err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
log.Error(err, "unexpected status code from API", "statusCode", resp.StatusCode)
return nil, err
}

var prediction DurationPrediction
if err := json.NewDecoder(resp.Body).Decode(&prediction); err != nil {
log.Error(err, "failed to decode API response")
return nil, err
}

return &prediction, nil
}
35 changes: 22 additions & 13 deletions internal/boost/resource/auto.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
package resource

import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"time"

"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -66,10 +68,12 @@ func (p *AutoPolicy) NewResources(ctx context.Context, container *corev1.Contain

cpuRequests, err := apiResource.ParseQuantity(prediction.CPURequests)
if err != nil {
log.Error(err, "failed to parse CPU requests")
return nil
}
cpuLimits, err := apiResource.ParseQuantity(prediction.CPULimits)
if err != nil {
log.Error(err, "failed to parse CPU limits")
return nil
}

Expand All @@ -96,30 +100,35 @@ func (p *AutoPolicy) setResource(resource corev1.ResourceName, resources corev1.
}

func (p *AutoPolicy) getPrediction(ctx context.Context) (*ResourcePrediction, error) {
log := ctrl.LoggerFrom(ctx).WithName("auto-cpu-policy").WithValues("apiEndpoint", p.apiEndpoint)
client := &http.Client{Timeout: 10 * time.Second}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, p.apiEndpoint, nil)
pod := ctx.Value("pod").(*corev1.Pod)
if pod == nil {
return nil, errors.New("pod information is missing in context")
}

reqBody, err := json.Marshal(pod)
if err != nil {
return nil, fmt.Errorf("failed to marshal pod information: %w", err)
}

req, err := http.NewRequestWithContext(ctx, http.MethodPost, p.apiEndpoint+"cpu", bytes.NewBuffer(reqBody))
if err != nil {
log.Error(err, "failed to create request")
return nil, err
return nil, fmt.Errorf("failed to create new request: %w", err)
}
req.Header.Set("Content-Type", "application/json")

resp, err := client.Do(req)
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Error(err, "failed to call API")
return nil, err
return nil, fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
log.Error(err, "unexpected status code from API", "statusCode", resp.StatusCode)
return nil, err
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}

var prediction ResourcePrediction
if err := json.NewDecoder(resp.Body).Decode(&prediction); err != nil {
log.Error(err, "failed to decode API response")
return nil, err
return nil, fmt.Errorf("failed to decode response: %w", err)
}

return &prediction, nil
Expand Down
7 changes: 7 additions & 0 deletions internal/boost/startupcpuboost.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,9 @@ func mapDurationPolicy(policiesSpec autoscaling.DurationPolicy) map[string]durat
if condPolicy := policiesSpec.PodCondition; condPolicy != nil {
policies[duration.PodConditionPolicyName] = duration.NewPodConditionPolicy(condPolicy.Type, condPolicy.Status)
}
if autoPolicy := policiesSpec.AutoPolicy; autoPolicy != nil {
policies[duration.AutoDurationPolicyName] = duration.NewAutoDurationPolicy(autoPolicy.ApiEndpoint)
}
return policies
}

Expand All @@ -318,6 +321,10 @@ func mapResourcePolicy(spec autoscaling.ResourcePolicy) (map[string]resource.Con
policy = resource.NewPercentageContainerPolicy(percIncrease.Value)
cnt++
}
if autoPolicy := policySpec.AutoPolicy; autoPolicy != nil {
policy = resource.NewAutoPolicy(autoPolicy.ApiEndpoint)
cnt++
}
if cnt != 1 {
errs = append(errs, fmt.Errorf("invalid number of resource policies fo container %s; must be one", policySpec.ContainerName))
continue
Expand Down

0 comments on commit dfa39ec

Please sign in to comment.