Skip to content

Commit

Permalink
[features] migrate OOM kill (#479)
Browse files Browse the repository at this point in the history
* [features] migrate oom kill
  • Loading branch information
celenechang committed May 10, 2022
1 parent 27d2ea8 commit bb0d4f1
Show file tree
Hide file tree
Showing 8 changed files with 298 additions and 10 deletions.
6 changes: 6 additions & 0 deletions apis/datadoghq/common/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,10 @@ const (
ConfigVolumeName = "config"
ConfigVolumePath = "/etc/datadog-agent"
KubeStateMetricCoreVolumeName = "ksm-core-config"
ModulesVolumeName = "modules"
// same path on host and container
ModulesVolumePath = "/lib/modules"
SrcVolumeName = "src"
// same path on host and container
SrcVolumePath = "/usr/src"
)
1 change: 1 addition & 0 deletions apis/datadoghq/common/envvar.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ const (
DDIgnoreAutoConf = "DD_IGNORE_AUTOCONF"
DDKubeStateMetricsCoreEnabled = "DD_KUBE_STATE_METRICS_CORE_ENABLED"
DDKubeStateMetricsCoreConfigMap = "DD_KUBE_STATE_METRICS_CORE_CONFIGMAP_NAME"
DDEnableOOMKillEnvVar = "DD_SYSTEM_PROBE_CONFIG_ENABLE_OOM_KILL"
)
4 changes: 3 additions & 1 deletion controllers/datadogagent/feature/ids.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const (
KubernetesStateCoreIDType IDType = iota
// OrchestratorExplorerIDType Orchestrator Explorer feature.
OrchestratorExplorerIDType
// DummyIDType Dummt feature.
// OOMKillIDType OOM Kill check feature
OOMKillIDType
// DummyIDType Dummy feature.
DummyIDType
)
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ func (f *ksmFeature) Configure(dda *v2alpha1.DatadogAgent) feature.RequiredCompo
}

return feature.RequiredComponents{
ClusterAgent: feature.RequiredComponent{Required: &f.enable},
ClusterCheckRunner: feature.RequiredComponent{Required: &f.clusterChecksEnabled},
ClusterAgent: feature.RequiredComponent{IsRequired: &f.enable},
ClusterCheckRunner: feature.RequiredComponent{IsRequired: &f.clusterChecksEnabled},
}
}

Expand Down Expand Up @@ -112,8 +112,8 @@ func (f *ksmFeature) ConfigureV1(dda *v1alpha1.DatadogAgent) feature.RequiredCom
}

return feature.RequiredComponents{
ClusterAgent: feature.RequiredComponent{Required: &f.enable},
ClusterCheckRunner: feature.RequiredComponent{Required: &f.clusterChecksEnabled},
ClusterAgent: feature.RequiredComponent{IsRequired: &f.enable},
ClusterCheckRunner: feature.RequiredComponent{IsRequired: &f.clusterChecksEnabled},
}
}

Expand Down
102 changes: 102 additions & 0 deletions controllers/datadogagent/feature/oom_kill/feature.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// 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 2016-present Datadog, Inc.

package oomkill

import (
corev1 "k8s.io/api/core/v1"

"github.com/DataDog/datadog-operator/apis/datadoghq/v1alpha1"
"github.com/DataDog/datadog-operator/apis/datadoghq/v2alpha1"
apiutils "github.com/DataDog/datadog-operator/apis/utils"

apicommon "github.com/DataDog/datadog-operator/apis/datadoghq/common"
apicommonv1 "github.com/DataDog/datadog-operator/apis/datadoghq/common/v1"
"github.com/DataDog/datadog-operator/controllers/datadogagent/feature"
"github.com/DataDog/datadog-operator/controllers/datadogagent/object/volume"
)

func init() {
err := feature.Register(feature.OOMKillIDType, buildOOMKillFeature)
if err != nil {
panic(err)
}
}

func buildOOMKillFeature(options *feature.Options) feature.Feature {
oomKillFeat := &oomKillFeature{}

return oomKillFeat
}

type oomKillFeature struct {
enable bool
}

// Configure is used to configure the feature from a v2alpha1.DatadogAgent instance.
func (f *oomKillFeature) Configure(dda *v2alpha1.DatadogAgent) (reqComp feature.RequiredComponents) {
if dda.Spec.Features.OOMKill != nil && apiutils.BoolValue(dda.Spec.Features.OOMKill.Enabled) {
f.enable = true
reqComp.Agent = feature.RequiredComponent{
IsRequired: &f.enable,
Containers: []apicommonv1.AgentContainerName{apicommonv1.CoreAgentContainerName, apicommonv1.SystemProbeContainerName},
}
}

return reqComp
}

// ConfigureV1 use to configure the feature from a v1alpha1.DatadogAgent instance.
func (f *oomKillFeature) ConfigureV1(dda *v1alpha1.DatadogAgent) (reqComp feature.RequiredComponents) {
if dda.Spec.Agent.SystemProbe != nil && *dda.Spec.Agent.SystemProbe.EnableOOMKill {
f.enable = true
reqComp.Agent = feature.RequiredComponent{
IsRequired: &f.enable,
Containers: []apicommonv1.AgentContainerName{apicommonv1.CoreAgentContainerName, apicommonv1.SystemProbeContainerName},
}
}

return reqComp
}

// ManageDependencies allows a feature to manage its dependencies.
// Feature's dependencies should be added in the store.
func (f *oomKillFeature) ManageDependencies(managers feature.ResourceManagers) error {
return nil
}

// ManageClusterAgent allows a feature to configure the ClusterAgent's corev1.PodTemplateSpec
// It should do nothing if the feature doesn't need to configure it.
func (f *oomKillFeature) ManageClusterAgent(managers feature.PodTemplateManagers) error {
return nil
}

// ManageNodeAgent allows a feature to configure the Node Agent's corev1.PodTemplateSpec
// It should do nothing if the feature doesn't need to configure it.
func (f *oomKillFeature) ManageNodeAgent(managers feature.PodTemplateManagers) error {
// modules volume mount
modulesVol, modulesVolMount := volume.GetVolumes(apicommon.ModulesVolumeName, apicommon.ModulesVolumePath, apicommon.ModulesVolumePath)
managers.Volume().AddVolumeToContainer(&modulesVol, &modulesVolMount, apicommonv1.SystemProbeContainerName)

// src volume mount
srcVol, srcVolMount := volume.GetVolumes(apicommon.SrcVolumeName, apicommon.SrcVolumePath, apicommon.SrcVolumePath)
managers.Volume().AddVolumeToContainer(&srcVol, &srcVolMount, apicommonv1.SystemProbeContainerName)

enableEnvVar := &corev1.EnvVar{
Name: apicommon.DDEnableOOMKillEnvVar,
Value: "true",
}

managers.EnvVar().AddEnvVarToContainer(apicommonv1.CoreAgentContainerName, enableEnvVar)
managers.EnvVar().AddEnvVarToContainer(apicommonv1.SystemProbeContainerName, enableEnvVar)

return nil
}

// ManageClusterChecksRunner allows a feature to configure the ClusterChecksRunner's corev1.PodTemplateSpec
// It should do nothing if the feature doesn't need to configure it.
func (f *oomKillFeature) ManageClusterChecksRunner(managers feature.PodTemplateManagers) error {
return nil
}
155 changes: 155 additions & 0 deletions controllers/datadogagent/feature/oom_kill/feature_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// 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 2016-present Datadog, Inc.

package oomkill

import (
"testing"

apicommon "github.com/DataDog/datadog-operator/apis/datadoghq/common"
apicommonv1 "github.com/DataDog/datadog-operator/apis/datadoghq/common/v1"
"github.com/DataDog/datadog-operator/apis/datadoghq/v1alpha1"
"github.com/DataDog/datadog-operator/apis/datadoghq/v2alpha1"
apiutils "github.com/DataDog/datadog-operator/apis/utils"
"github.com/DataDog/datadog-operator/controllers/datadogagent/feature"
"github.com/DataDog/datadog-operator/controllers/datadogagent/feature/fake"
"github.com/DataDog/datadog-operator/controllers/datadogagent/feature/test"

"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
)

func createEmptyFakeManager(t testing.TB) feature.PodTemplateManagers {
mgr := fake.NewPodTemplateManagers(t)
return mgr
}

func Test_oomKillFeature_Configure(t *testing.T) {
ddav1OOMKillDisabled := v1alpha1.DatadogAgent{
Spec: v1alpha1.DatadogAgentSpec{
Agent: v1alpha1.DatadogAgentSpecAgentSpec{
SystemProbe: &v1alpha1.SystemProbeSpec{
EnableOOMKill: apiutils.NewBoolPointer(false),
},
},
},
}

ddav1OOMKillEnabled := ddav1OOMKillDisabled.DeepCopy()
{
ddav1OOMKillEnabled.Spec.Agent.SystemProbe.EnableOOMKill = apiutils.NewBoolPointer(true)
}

ddav2OOMKillDisabled := v2alpha1.DatadogAgent{
Spec: v2alpha1.DatadogAgentSpec{
Features: &v2alpha1.DatadogFeatures{
OOMKill: &v2alpha1.OOMKillFeatureConfig{
Enabled: apiutils.NewBoolPointer(false),
},
},
},
}
ddav2OOMKillEnabled := ddav2OOMKillDisabled.DeepCopy()
{
ddav2OOMKillEnabled.Spec.Features.OOMKill.Enabled = apiutils.NewBoolPointer(true)
}

oomKillAgentNodeWantFunc := func(t testing.TB, mgrInterface feature.PodTemplateManagers) {
mgr := mgrInterface.(*fake.PodTemplateManagers)

// check volume mounts
wantVolumeMounts := []corev1.VolumeMount{
{
Name: apicommon.ModulesVolumeName,
MountPath: apicommon.ModulesVolumePath,
ReadOnly: true,
},
{
Name: apicommon.SrcVolumeName,
MountPath: apicommon.SrcVolumePath,
ReadOnly: true,
},
}

systemProbeVolumeMounts := mgr.VolumeMgr.VolumeMountByC[apicommonv1.SystemProbeContainerName]
assert.True(t, apiutils.IsEqualStruct(systemProbeVolumeMounts, wantVolumeMounts), "System Probe volume mounts \ndiff = %s", cmp.Diff(systemProbeVolumeMounts, wantVolumeMounts))

// check volumes
wantVolumes := []corev1.Volume{
{
Name: apicommon.ModulesVolumeName,
VolumeSource: corev1.VolumeSource{
HostPath: &corev1.HostPathVolumeSource{
Path: apicommon.ModulesVolumePath,
},
},
},
{
Name: apicommon.SrcVolumeName,
VolumeSource: corev1.VolumeSource{
HostPath: &corev1.HostPathVolumeSource{
Path: apicommon.SrcVolumePath,
},
},
},
}

volumes := mgr.VolumeMgr.Volumes
assert.True(t, apiutils.IsEqualStruct(volumes, wantVolumes), "Volumes \ndiff = %s", cmp.Diff(volumes, wantVolumes))

// check env vars
wantEnvVars := []*corev1.EnvVar{
{
Name: apicommon.DDEnableOOMKillEnvVar,
Value: "true",
},
}
agentEnvVars := mgr.EnvVarMgr.EnvVarsByC[apicommonv1.CoreAgentContainerName]
assert.True(t, apiutils.IsEqualStruct(agentEnvVars, wantEnvVars), "Agent envvars \ndiff = %s", cmp.Diff(agentEnvVars, wantEnvVars))

systemProbeEnvVars := mgr.EnvVarMgr.EnvVarsByC[apicommonv1.SystemProbeContainerName]
assert.True(t, apiutils.IsEqualStruct(systemProbeEnvVars, wantEnvVars), "System Probe envvars \ndiff = %s", cmp.Diff(systemProbeEnvVars, wantEnvVars))
}

tests := test.FeatureTestSuite{
///////////////////////////
// v1alpha1.DatadogAgent //
///////////////////////////
{
Name: "v1alpha1 oom kill not enabled",
DDAv1: ddav1OOMKillDisabled.DeepCopy(),
WantConfigure: false,
},
{
Name: "v1alpha1 oom kill enabled",
DDAv1: ddav1OOMKillEnabled,
WantConfigure: true,
Agent: &test.ComponentTest{
CreateFunc: createEmptyFakeManager,
WantFunc: oomKillAgentNodeWantFunc,
},
},
///////////////////////////
// v2alpha1.DatadogAgent //
///////////////////////////
{
Name: "v2alpha1 oom kill not enabled",
DDAv2: ddav2OOMKillDisabled.DeepCopy(),
WantConfigure: false,
},
{
Name: "v2alpha1 oom kill enabled",
DDAv2: ddav2OOMKillEnabled,
WantConfigure: true,
Agent: &test.ComponentTest{
CreateFunc: createEmptyFakeManager,
WantFunc: oomKillAgentNodeWantFunc,
},
},
}

tests.Run(t, buildOOMKillFeature)
}
10 changes: 5 additions & 5 deletions controllers/datadogagent/feature/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,21 @@ func (rc *RequiredComponents) Merge(in *RequiredComponents) *RequiredComponents
// * false: the corresponding component needs to ne disabled for this feature.
// * nil: the feature doesn't need the corresponding component.
type RequiredComponent struct {
Required *bool
RequiredContainers []apicommonv1.AgentContainerName
IsRequired *bool
Containers []apicommonv1.AgentContainerName
}

// IsEnabled return true if the Feature need the current RequiredComponent
func (rc *RequiredComponent) IsEnabled() bool {
return apiutils.BoolValue(rc.Required) || len(rc.RequiredContainers) > 0
return apiutils.BoolValue(rc.IsRequired) || len(rc.Containers) > 0
}

// Merge use to merge 2 RequiredComponents
// merge priority: false > true > nil
// *
func (rc *RequiredComponent) Merge(in *RequiredComponent) *RequiredComponent {
rc.Required = merge(rc.Required, in.Required)
rc.RequiredContainers = mergeSlices(rc.RequiredContainers, in.RequiredContainers)
rc.IsRequired = merge(rc.IsRequired, in.IsRequired)
rc.Containers = mergeSlices(rc.Containers, in.Containers)
return rc
}

Expand Down
22 changes: 22 additions & 0 deletions controllers/datadogagent/object/volume/volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,28 @@ import (
apicommonv1 "github.com/DataDog/datadog-operator/apis/datadoghq/common/v1"
)

// GetVolumes creates a corev1.Volume and corev1.VolumeMount corresponding to a host path.
func GetVolumes(volumeName, hostPath, mountPath string) (corev1.Volume, corev1.VolumeMount) {
var volume corev1.Volume
var volumeMount corev1.VolumeMount

volume = corev1.Volume{
Name: volumeName,
VolumeSource: corev1.VolumeSource{
HostPath: &corev1.HostPathVolumeSource{
Path: hostPath,
},
},
}
volumeMount = corev1.VolumeMount{
Name: volumeName,
MountPath: mountPath,
ReadOnly: true,
}

return volume, volumeMount
}

// GetCustomConfigSpecVolumes use to generate the corev1.Volume and corev1.VolumeMount corresponding to a CustomConfig.
func GetCustomConfigSpecVolumes(customConfig *apicommonv1.CustomConfig, volumeName, defaultCMName, configFolder string) (corev1.Volume, corev1.VolumeMount) {
var volume corev1.Volume
Expand Down

0 comments on commit bb0d4f1

Please sign in to comment.