Skip to content

Commit

Permalink
feat(agent): add labels for Harvester exit uploads (#1050) (#1056)
Browse files Browse the repository at this point in the history
(cherry picked from commit be8aa98)

Co-authored-by: Andrew Azores <me@andrewazor.es>
  • Loading branch information
mergify[bot] and andrewazores authored Feb 15, 2025
1 parent 995677c commit 518d4bf
Show file tree
Hide file tree
Showing 4 changed files with 227 additions and 24 deletions.
17 changes: 10 additions & 7 deletions internal/controllers/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,16 @@ const (
TargetNamespaceCRNamespaceLabel = targetNamespaceCRLabelPrefix + "namespace"

// Labels for agent auto-configuration
agentLabelPrefix = "cryostat.io/"
AgentLabelCryostatName = agentLabelPrefix + "name"
AgentLabelCryostatNamespace = agentLabelPrefix + "namespace"
AgentLabelCallbackPort = agentLabelPrefix + "callback-port"
AgentLabelContainer = agentLabelPrefix + "container"
AgentLabelReadOnly = agentLabelPrefix + "read-only"
AgentLabelJavaOptionsVar = agentLabelPrefix + "java-options-var"
agentLabelPrefix = "cryostat.io/"
AgentLabelCryostatName = agentLabelPrefix + "name"
AgentLabelCryostatNamespace = agentLabelPrefix + "namespace"
AgentLabelCallbackPort = agentLabelPrefix + "callback-port"
AgentLabelContainer = agentLabelPrefix + "container"
AgentLabelReadOnly = agentLabelPrefix + "read-only"
AgentLabelJavaOptionsVar = agentLabelPrefix + "java-options-var"
AgentLabelHarvesterTemplate = agentLabelPrefix + "harvester-template"
AgentLabelHarvesterExitMaxAge = agentLabelPrefix + "harvester-exit-max-age"
AgentLabelHarvesterExitMaxSize = agentLabelPrefix + "harvester-exit-max-size"

CryostatCATLSCommonName = "cryostat-ca-cert-manager"
CryostatTLSCommonName = "cryostat"
Expand Down
82 changes: 75 additions & 7 deletions internal/webhooks/agent/pod_defaulter.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"fmt"
"slices"
"strconv"
"time"

operatorv1beta2 "github.com/cryostatio/cryostat-operator/api/v1beta2"
"github.com/cryostatio/cryostat-operator/internal/controllers/common"
Expand Down Expand Up @@ -47,13 +48,17 @@ type podMutator struct {
var _ admission.CustomDefaulter = &podMutator{}

const (
agentArg = "-javaagent:/tmp/cryostat-agent/cryostat-agent-shaded.jar"
podNameEnvVar = "CRYOSTAT_AGENT_POD_NAME"
podIPEnvVar = "CRYOSTAT_AGENT_POD_IP"
agentMaxSizeBytes = "50Mi"
agentInitCpuRequest = "10m"
agentInitMemoryRequest = "32Mi"
defaultJavaOptsVar = "JAVA_TOOL_OPTIONS"
agentArg = "-javaagent:/tmp/cryostat-agent/cryostat-agent-shaded.jar"
podNameEnvVar = "CRYOSTAT_AGENT_POD_NAME"
podIPEnvVar = "CRYOSTAT_AGENT_POD_IP"
agentMaxSizeBytes = "50Mi"
agentInitCpuRequest = "10m"
agentInitMemoryRequest = "32Mi"
defaultJavaOptsVar = "JAVA_TOOL_OPTIONS"
defaultHarvesterExitMaxAge = int32(30000)
kib = int32(1024)
mib = 1024 * kib
defaultHarvesterExitMaxSize = 20 * mib
)

// Default optionally mutates a pod to inject the Cryostat agent
Expand Down Expand Up @@ -108,6 +113,16 @@ func (r *podMutator) Default(ctx context.Context, obj runtime.Object) error {
return err
}

harvesterTemplate := getHarvesterTemplate(pod.Labels)
harvesterExitMaxAge, err := getHarvesterExitMaxAge(pod.Labels)
if err != nil {
return err
}
harvesterExitMaxSize, err := getHarvesterExitMaxSize(pod.Labels)
if err != nil {
return err
}

// Add init container
nonRoot := true
imageTag := r.getImageTag()
Expand Down Expand Up @@ -185,6 +200,23 @@ func (r *podMutator) Default(ctx context.Context, obj runtime.Object) error {
},
)

if len(harvesterTemplate) > 0 {
container.Env = append(container.Env,
corev1.EnvVar{
Name: "CRYOSTAT_AGENT_HARVESTER_TEMPLATE",
Value: harvesterTemplate,
},
corev1.EnvVar{
Name: "CRYOSTAT_AGENT_HARVESTER_EXIT_MAX_AGE_MS",
Value: strconv.Itoa(int(*harvesterExitMaxAge)),
},
corev1.EnvVar{
Name: "CRYOSTAT_AGENT_HARVESTER_EXIT_MAX_SIZE_B",
Value: strconv.Itoa(int(*harvesterExitMaxSize)),
},
)
}

// Append a port for the callback server
container.Ports = append(container.Ports, corev1.ContainerPort{
Name: constants.AgentCallbackPortName,
Expand Down Expand Up @@ -344,6 +376,42 @@ func getJavaOptionsVar(labels map[string]string) string {
return result
}

func getHarvesterTemplate(labels map[string]string) string {
result := ""
value, pres := labels[constants.AgentLabelHarvesterTemplate]
if pres {
result = value
}
return result
}

func getHarvesterExitMaxAge(labels map[string]string) (*int32, error) {
value := defaultHarvesterExitMaxAge
age, pres := labels[constants.AgentLabelHarvesterExitMaxAge]
if pres {
// Parse the label value into an int32 and return an error if invalid
parsed, err := time.ParseDuration(age)
if err != nil {
return nil, fmt.Errorf("invalid label value for \"%s\": %s", constants.AgentLabelHarvesterExitMaxAge, err.Error())
}
value = int32(parsed.Milliseconds())
}
return &value, nil
}

func getHarvesterExitMaxSize(labels map[string]string) (*int32, error) {
value := defaultHarvesterExitMaxSize
size, pres := labels[constants.AgentLabelHarvesterExitMaxSize]
if pres {
parsed, err := resource.ParseQuantity(size)
if err != nil {
return nil, fmt.Errorf("invalid label value for \"%s\": %s", constants.AgentLabelHarvesterExitMaxSize, err.Error())
}
value = int32(parsed.Value())
}
return &value, nil
}

func getResourceRequirements(cr *model.CryostatInstance) *corev1.ResourceRequirements {
resources := &corev1.ResourceRequirements{}
if cr.Spec.AgentOptions != nil {
Expand Down
58 changes: 58 additions & 0 deletions internal/webhooks/agent/pod_defaulter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,64 @@ var _ = Describe("PodDefaulter", func() {
ExpectPod()
})

Context("with harvester enabled", func() {
Context("with default exit settings", func() {
BeforeEach(func() {
t.objs = append(t.objs, t.NewCryostat().Object)
originalPod = t.NewPodHarvesterTemplate()
expectedPod = t.NewMutatedPodHarvesterTemplate()
})

ExpectPod()
})

Context("with exit age setting", func() {
Context("that is valid", func() {
BeforeEach(func() {
t.objs = append(t.objs, t.NewCryostat().Object)
originalPod = t.NewPodHarvesterTemplateAge()
expectedPod = t.NewMutatedPodHarvesterTemplateAge()
})

ExpectPod()
})

Context("that is invalid", func() {
BeforeEach(func() {
t.objs = append(t.objs, t.NewCryostat().Object)
originalPod = t.NewPodHarvesterTemplateInvalidAge()
// Should fail
expectedPod = originalPod
})

ExpectPod()
})
})

Context("with exit size setting", func() {
Context("that is valid", func() {
BeforeEach(func() {
t.objs = append(t.objs, t.NewCryostat().Object)
originalPod = t.NewPodHarvesterTemplateSize()
expectedPod = t.NewMutatedPodHarvesterTemplateSize()
})

ExpectPod()
})

Context("that is invalid", func() {
BeforeEach(func() {
t.objs = append(t.objs, t.NewCryostat().Object)
originalPod = t.NewPodHarvesterTemplateInvalidSize()
// Should fail
expectedPod = originalPod
})

ExpectPod()
})
})
})

Context("with a custom resource requirements", func() {
Context("that are valid", func() {
BeforeEach(func() {
Expand Down
94 changes: 84 additions & 10 deletions internal/webhooks/agent/test/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,17 +188,50 @@ func (r *AgentWebhookTestResources) NewPodJavaOptsVar() *corev1.Pod {
return pod
}

func (r *AgentWebhookTestResources) NewPodHarvesterTemplate() *corev1.Pod {
pod := r.NewPod()
pod.Labels["cryostat.io/harvester-template"] = "default.jfc"
return pod
}

func (r *AgentWebhookTestResources) NewPodHarvesterTemplateAge() *corev1.Pod {
pod := r.NewPod()
pod.Labels["cryostat.io/harvester-exit-max-age"] = "10s"
return pod
}

func (r *AgentWebhookTestResources) NewPodHarvesterTemplateInvalidAge() *corev1.Pod {
pod := r.NewPod()
pod.Labels["cryostat.io/harvester-exit-max-age"] = "tenseconds"
return pod
}

func (r *AgentWebhookTestResources) NewPodHarvesterTemplateSize() *corev1.Pod {
pod := r.NewPod()
pod.Labels["cryostat.io/harvester-exit-max-size"] = "10Mi"
return pod
}

func (r *AgentWebhookTestResources) NewPodHarvesterTemplateInvalidSize() *corev1.Pod {
pod := r.NewPod()
pod.Labels["cryostat.io/harvester-exit-max-size"] = "tenmib"
return pod
}

type mutatedPodOptions struct {
javaOptionsName string
javaOptionsValue string
namespace string
image string
pullPolicy corev1.PullPolicy
gatewayPort int32
callbackPort int32
writeAccess *bool
scheme string
resources *corev1.ResourceRequirements
javaOptionsName string
javaOptionsValue string
namespace string
image string
pullPolicy corev1.PullPolicy
gatewayPort int32
callbackPort int32
writeAccess *bool
harvesterTemplate string
harvesterExitAge int32
harvesterExitSize int32
scheme string
resources *corev1.ResourceRequirements
// Function to produce mutated container array
containersFunc func(*AgentWebhookTestResources, *mutatedPodOptions) []corev1.Container
}
Expand All @@ -219,6 +252,12 @@ func (r *AgentWebhookTestResources) setDefaultMutatedPodOptions(options *mutated
if options.callbackPort == 0 {
options.callbackPort = 9977
}
if options.harvesterExitAge == 0 {
options.harvesterExitAge = 30000
}
if options.harvesterExitSize == 0 {
options.harvesterExitSize = 20971520
}
if options.writeAccess == nil {
options.writeAccess = &[]bool{true}[0]
}
Expand Down Expand Up @@ -308,6 +347,24 @@ func (r *AgentWebhookTestResources) NewMutatedPodJavaOptsVarLabel() *corev1.Pod
})
}

func (r *AgentWebhookTestResources) NewMutatedPodHarvesterTemplate() *corev1.Pod {
return r.newMutatedPod(&mutatedPodOptions{
harvesterTemplate: "default.jfc",
})
}

func (r *AgentWebhookTestResources) NewMutatedPodHarvesterTemplateAge() *corev1.Pod {
return r.newMutatedPod(&mutatedPodOptions{
harvesterExitAge: 10000,
})
}

func (r *AgentWebhookTestResources) NewMutatedPodHarvesterTemplateSize() *corev1.Pod {
return r.newMutatedPod(&mutatedPodOptions{
harvesterExitSize: 123456,
})
}

func (r *AgentWebhookTestResources) NewMutatedPodResources() *corev1.Pod {
return r.newMutatedPod(&mutatedPodOptions{
resources: &corev1.ResourceRequirements{
Expand Down Expand Up @@ -581,6 +638,23 @@ func (r *AgentWebhookTestResources) newMutatedContainer(original *corev1.Contain
}
container.Env = append(container.Env, callbackEnvs...)

if len(options.harvesterTemplate) > 0 {
container.Env = append(container.Env,
corev1.EnvVar{
Name: "CRYOSTAT_AGENT_HARVESTER_TEMPLATE",
Value: options.harvesterTemplate,
},
corev1.EnvVar{
Name: "CRYOSTAT_AGENT_HARVESTER_EXIT_MAX_AGE_MS",
Value: strconv.Itoa(int(options.harvesterExitAge)),
},
corev1.EnvVar{
Name: "CRYOSTAT_AGENT_HARVESTER_EXIT_MAX_SIZE_B",
Value: strconv.Itoa(int(options.harvesterExitSize)),
},
)
}

return container
}

Expand Down

0 comments on commit 518d4bf

Please sign in to comment.