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

Validate collisions of logmodules and OneAgents in the validation webhook #3801

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
34 changes: 23 additions & 11 deletions pkg/api/v1beta1/dynakube/validation/oneagent.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strings"

"github.com/Dynatrace/dynatrace-operator/pkg/api/v1beta1/dynakube" //nolint:staticcheck
dynakubev1beta3 "github.com/Dynatrace/dynatrace-operator/pkg/api/v1beta3/dynakube"
"github.com/Dynatrace/dynatrace-operator/pkg/util/kubeobjects/env"
"golang.org/x/mod/semver"
"k8s.io/apimachinery/pkg/labels"
Expand All @@ -17,14 +18,18 @@ const (
`
errorImageFieldSetWithoutCSIFlag = `The DynaKube's specification tries to enable ApplicationMonitoring mode and get the respective image, but the CSI driver is not enabled.`

errorNodeSelectorConflict = `The DynaKube's specification tries to specify a nodeSelector conflicts with an another Dynakube's nodeSelector, which is not supported.
The conflicting Dynakube: %s
`
errorNodeSelectorConflict = `The DynaKube specification attempts to deploy a %s, which conflicts with another DynaKube's %s. Only one Agent per node is supported.
Copy link
Collaborator

Choose a reason for hiding this comment

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

this does not give the information anymore, that the nodeSelector is the problem.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That was kind of on purpose, because the nodeSelector is not really true. It also pops up if no nodeSelectors are specified and 2 dynakubes are colliding.

So I would rather state that the dynakubes are colliding and which type of agent is the problem. So WDYF, should we still change it back? 🤔

Copy link
Collaborator

Choose a reason for hiding this comment

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

only way to fix it is by specifying a node selector, so that should be in the message somewhere

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated

Conflicting DynaKube: %s`
0sewa0 marked this conversation as resolved.
Show resolved Hide resolved

errorVolumeStorageReadOnlyModeConflict = `The DynaKube's specification specifies a read-only host file system and OneAgent has volume storage enabled.`

warningOneAgentInstallerEnvVars = `Environment variables ONEAGENT_INSTALLER_SCRIPT_URL and ONEAGENT_INSTALLER_TOKEN are only relevant for an unsupported image type. Please make sure you are using a supported image.`

warningHostGroupConflict = `DynaKube's specification sets the host group using --set-host-group parameter. Instead, specify the new spec.oneagent.hostGroup field. If you use both settings, the new field precedes the parameter.`

oneAgentComponentName = "OneAgent"

logModuleComponentName = "LogModule"
)

func conflictingOneAgentConfiguration(_ context.Context, _ *Validator, dk *dynakube.DynaKube) string {
Expand Down Expand Up @@ -59,26 +64,33 @@ func conflictingNodeSelector(ctx context.Context, dv *Validator, dk *dynakube.Dy
return ""
}

validDynakubes := &dynakube.DynaKubeList{}
validDynakubes := &dynakubev1beta3.DynaKubeList{}
if err := dv.apiReader.List(ctx, validDynakubes, &client.ListOptions{Namespace: dk.Namespace}); err != nil {
log.Info("error occurred while listing dynakubes", "err", err.Error())

return ""
}

oneAgentNodeSelector := dk.NodeSelector()

for _, item := range validDynakubes.Items {
if !item.NeedsOneAgent() {
if item.Name == dk.Name {
continue
}

nodeSelectorMap := dk.NodeSelector()
validNodeSelectorMap := item.NodeSelector()
if item.NeedsOneAgent() {
if hasConflictingMatchLabels(oneAgentNodeSelector, item.OneAgentNodeSelector()) {
log.Info("requested dynakube has conflicting OneAgent nodeSelector", "name", dk.Name, "namespace", dk.Namespace)

return fmt.Sprintf(errorNodeSelectorConflict, oneAgentComponentName, oneAgentComponentName, item.Name)
}
}

if item.Name != dk.Name {
if hasConflictingMatchLabels(nodeSelectorMap, validNodeSelectorMap) {
log.Info("requested dynakube has conflicting nodeSelector", "name", dk.Name, "namespace", dk.Namespace)
if item.NeedsLogModule() {
if hasConflictingMatchLabels(oneAgentNodeSelector, item.LogModuleNodeSelector()) {
log.Info("requested dynakube has conflicting LogModule nodeSelector", "name", dk.Name, "namespace", dk.Namespace)

return fmt.Sprintf(errorNodeSelectorConflict, item.Name)
return fmt.Sprintf(errorNodeSelectorConflict, oneAgentComponentName, logModuleComponentName, item.Name)
}
}
}
Expand Down
80 changes: 72 additions & 8 deletions pkg/api/v1beta1/dynakube/validation/oneagent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"testing"

"github.com/Dynatrace/dynatrace-operator/pkg/api/v1beta1/dynakube" //nolint:staticcheck
dynakubev1beta3 "github.com/Dynatrace/dynatrace-operator/pkg/api/v1beta3/dynakube"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
Expand Down Expand Up @@ -95,6 +96,21 @@ func TestConflictingNodeSelector(t *testing.T) {
},
}
}
newCloudNativeV1Beta3Dynakube := func(name string, annotations map[string]string, nodeSelectorValue string) *dynakubev1beta3.DynaKube {
dk := newCloudNativeDynakube(name, annotations, nodeSelectorValue)
dkv3 := &dynakubev1beta3.DynaKube{}
dkv3.ObjectMeta = dk.ObjectMeta
dkv3.Spec.APIURL = dk.Spec.APIURL
dkv3.Spec.OneAgent.CloudNativeFullStack = &dynakubev1beta3.CloudNativeFullStackSpec{
HostInjectSpec: dynakubev1beta3.HostInjectSpec{
NodeSelector: map[string]string{
"node": nodeSelectorValue,
},
},
}

return dkv3
}

t.Run(`valid dynakube specs`, func(t *testing.T) {
assertAllowedWithoutWarnings(t,
Expand Down Expand Up @@ -160,7 +176,24 @@ func TestConflictingNodeSelector(t *testing.T) {
},
},
}, &defaultCSIDaemonSet)

assertAllowedWithoutWarnings(t, newCloudNativeDynakube("dk1", map[string]string{}, "1"),
&dynakubev1beta3.DynaKube{
ObjectMeta: defaultDynakubeObjectMeta,
Spec: dynakubev1beta3.DynaKubeSpec{
APIURL: testApiUrl,
LogModule: dynakubev1beta3.LogModuleSpec{
Enabled: true,
},
Templates: dynakubev1beta3.TemplatesSpec{
LogModule: dynakubev1beta3.LogModuleTemplateSpec{
NodeSelector: map[string]string{"node": "12"},
},
},
},
}, &defaultCSIDaemonSet)
})

t.Run(`valid dynakube specs with multitenant hostMonitoring`, func(t *testing.T) {
assertAllowedWithWarnings(t, 0,
newCloudNativeDynakube("dk1", map[string]string{
Expand All @@ -182,7 +215,7 @@ func TestConflictingNodeSelector(t *testing.T) {
})
t.Run(`invalid dynakube specs`, func(t *testing.T) {
assertDenied(t,
[]string{fmt.Sprintf(errorNodeSelectorConflict, "conflicting-dk")},
[]string{fmt.Sprintf(errorNodeSelectorConflict, oneAgentComponentName, oneAgentComponentName, "conflicting-dk")},
&dynakube.DynaKube{
ObjectMeta: defaultDynakubeObjectMeta,
Spec: dynakube.DynaKubeSpec{
Expand All @@ -198,15 +231,15 @@ func TestConflictingNodeSelector(t *testing.T) {
},
},
},
&dynakube.DynaKube{
&dynakubev1beta3.DynaKube{
ObjectMeta: metav1.ObjectMeta{
Name: "conflicting-dk",
Namespace: testNamespace,
},
Spec: dynakube.DynaKubeSpec{
Spec: dynakubev1beta3.DynaKubeSpec{
APIURL: testApiUrl,
OneAgent: dynakube.OneAgentSpec{
HostMonitoring: &dynakube.HostInjectSpec{
OneAgent: dynakubev1beta3.OneAgentSpec{
HostMonitoring: &dynakubev1beta3.HostInjectSpec{
NodeSelector: map[string]string{
"node": "1",
},
Expand All @@ -220,7 +253,7 @@ func TestConflictingNodeSelector(t *testing.T) {
newCloudNativeDynakube("dk1", map[string]string{
dynakube.AnnotationFeatureMultipleOsAgentsOnNode: "false",
}, "1"),
newCloudNativeDynakube("dk2", map[string]string{
newCloudNativeV1Beta3Dynakube("dk2", map[string]string{
dynakube.AnnotationFeatureMultipleOsAgentsOnNode: "true",
}, "1"),
&defaultCSIDaemonSet)
Expand All @@ -229,16 +262,47 @@ func TestConflictingNodeSelector(t *testing.T) {
newCloudNativeDynakube("dk1", map[string]string{
dynakube.AnnotationFeatureMultipleOsAgentsOnNode: "false",
}, "1"),
newCloudNativeDynakube("dk2", map[string]string{
newCloudNativeV1Beta3Dynakube("dk2", map[string]string{
dynakube.AnnotationFeatureMultipleOsAgentsOnNode: "false",
}, "1"),
&defaultCSIDaemonSet)

assertDenied(t, nil,
newCloudNativeDynakube("dk1", map[string]string{}, "1"),
newCloudNativeDynakube("dk2", map[string]string{}, "1"),
newCloudNativeV1Beta3Dynakube("dk2", map[string]string{}, "1"),
&defaultCSIDaemonSet)
})

t.Run(`invalid dynakube specs with existing log module`, func(t *testing.T) {
assertDenied(t, []string{fmt.Sprintf(errorNodeSelectorConflict, oneAgentComponentName, logModuleComponentName, testName)},
newCloudNativeDynakube("dk1", map[string]string{}, "1"),
&dynakubev1beta3.DynaKube{
ObjectMeta: defaultDynakubeObjectMeta,
Spec: dynakubev1beta3.DynaKubeSpec{
APIURL: testApiUrl,
LogModule: dynakubev1beta3.LogModuleSpec{
Enabled: true,
},
},
}, &defaultCSIDaemonSet)

assertDenied(t, []string{fmt.Sprintf(errorNodeSelectorConflict, oneAgentComponentName, logModuleComponentName, testName)},
newCloudNativeDynakube("dk1", map[string]string{}, "1"),
&dynakubev1beta3.DynaKube{
ObjectMeta: defaultDynakubeObjectMeta,
Spec: dynakubev1beta3.DynaKubeSpec{
APIURL: testApiUrl,
LogModule: dynakubev1beta3.LogModuleSpec{
Enabled: true,
},
Templates: dynakubev1beta3.TemplatesSpec{
LogModule: dynakubev1beta3.LogModuleTemplateSpec{
NodeSelector: map[string]string{"node": "1"},
},
},
},
}, &defaultCSIDaemonSet)
})
}

func TestImageFieldSetWithoutCSIFlag(t *testing.T) {
Expand Down
14 changes: 7 additions & 7 deletions pkg/api/v1beta1/dynakube/validation/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ func TestDynakubeValidator_Handle(t *testing.T) {
errorConflictingNamespaceSelector,
fmt.Sprintf(errorDuplicateActiveGateCapability, dynakube.KubeMonCapability.DisplayName),
fmt.Sprintf(errorInvalidActiveGateCapability, "me dumb"),
fmt.Sprintf(errorNodeSelectorConflict, "conflict2")},
fmt.Sprintf(errorNodeSelectorConflict, oneAgentComponentName, oneAgentComponentName, "conflict2")},
&dynakube.DynaKube{
ObjectMeta: metav1.ObjectMeta{
Name: testName,
Expand Down Expand Up @@ -156,15 +156,15 @@ func TestDynakubeValidator_Handle(t *testing.T) {
},
},
},
&dynakube.DynaKube{
&v1beta3.DynaKube{
ObjectMeta: metav1.ObjectMeta{
Name: "conflict2",
Namespace: testNamespace,
},
Spec: dynakube.DynaKubeSpec{
Spec: v1beta3.DynaKubeSpec{
APIURL: testApiUrl,
OneAgent: dynakube.OneAgentSpec{
HostMonitoring: &dynakube.HostInjectSpec{},
OneAgent: v1beta3.OneAgentSpec{
HostMonitoring: &v1beta3.HostInjectSpec{},
},
},
}, &dummyNamespace, &dummyNamespace2)
Expand All @@ -175,8 +175,8 @@ func assertDenied(t *testing.T, errMessages []string, dk *dynakube.DynaKube, oth
_, err := runValidators(dk, other...)
require.Error(t, err)

for _, errMsg := range errMessages {
assert.Contains(t, err.Error(), errMsg)
for i, errMsg := range errMessages {
assert.Contains(t, err.Error(), errMsg, "error nr %d", i)
}
}

Expand Down
34 changes: 23 additions & 11 deletions pkg/api/v1beta2/dynakube/validation/oneagent.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strings"

"github.com/Dynatrace/dynatrace-operator/pkg/api/v1beta2/dynakube" //nolint:staticcheck
dynakubev1beta3 "github.com/Dynatrace/dynatrace-operator/pkg/api/v1beta3/dynakube"
"github.com/Dynatrace/dynatrace-operator/pkg/util/kubeobjects/env"
"golang.org/x/mod/semver"
"k8s.io/apimachinery/pkg/labels"
Expand All @@ -17,14 +18,18 @@ const (
`
errorImageFieldSetWithoutCSIFlag = `The DynaKube's specification tries to enable ApplicationMonitoring mode and get the respective image, but the CSI driver is not enabled.`

errorNodeSelectorConflict = `The DynaKube's specification tries to specify a nodeSelector conflicts with an another Dynakube's nodeSelector, which is not supported.
The conflicting Dynakube: %s
`
errorNodeSelectorConflict = `The DynaKube specification attempts to deploy a %s, which conflicts with another DynaKube's %s. Only one Agent per node is supported.
Conflicting DynaKube: %s`

errorVolumeStorageReadOnlyModeConflict = `The DynaKube's specification specifies a read-only host file system and OneAgent has volume storage enabled.`

warningOneAgentInstallerEnvVars = `Environment variables ONEAGENT_INSTALLER_SCRIPT_URL and ONEAGENT_INSTALLER_TOKEN are only relevant for an unsupported image type. Please make sure you are using a supported image.`

warningHostGroupConflict = `DynaKube's specification sets the host group using --set-host-group parameter. Instead, specify the new spec.oneagent.hostGroup field. If you use both settings, the new field precedes the parameter.`

oneAgentComponentName = "OneAgent"

logModuleComponentName = "LogModule"
)

func conflictingOneAgentConfiguration(_ context.Context, _ *Validator, dk *dynakube.DynaKube) string {
Expand Down Expand Up @@ -59,26 +64,33 @@ func conflictingNodeSelector(ctx context.Context, dv *Validator, dk *dynakube.Dy
return ""
}

validDynakubes := &dynakube.DynaKubeList{}
validDynakubes := &dynakubev1beta3.DynaKubeList{}
if err := dv.apiReader.List(ctx, validDynakubes, &client.ListOptions{Namespace: dk.Namespace}); err != nil {
log.Info("error occurred while listing dynakubes", "err", err.Error())

return ""
}

oneAgentNodeSelector := dk.NodeSelector()

for _, item := range validDynakubes.Items {
if !item.NeedsOneAgent() {
if item.Name == dk.Name {
continue
}

nodeSelectorMap := dk.NodeSelector()
validNodeSelectorMap := item.NodeSelector()
if item.NeedsOneAgent() {
if hasConflictingMatchLabels(oneAgentNodeSelector, item.OneAgentNodeSelector()) {
log.Info("requested dynakube has conflicting OneAgent nodeSelector", "name", dk.Name, "namespace", dk.Namespace)

return fmt.Sprintf(errorNodeSelectorConflict, oneAgentComponentName, oneAgentComponentName, item.Name)
}
}

if item.Name != dk.Name {
if hasConflictingMatchLabels(nodeSelectorMap, validNodeSelectorMap) {
log.Info("requested dynakube has conflicting nodeSelector", "name", dk.Name, "namespace", dk.Namespace)
if item.NeedsLogModule() {
if hasConflictingMatchLabels(oneAgentNodeSelector, item.LogModuleNodeSelector()) {
log.Info("requested dynakube has conflicting LogModule nodeSelector", "name", dk.Name, "namespace", dk.Namespace)

return fmt.Sprintf(errorNodeSelectorConflict, item.Name)
return fmt.Sprintf(errorNodeSelectorConflict, oneAgentComponentName, logModuleComponentName, item.Name)
}
}
}
Expand Down
Loading
Loading