Skip to content

Commit

Permalink
Create EEC/StatsD config map with runtimeProperties to be mounted in …
Browse files Browse the repository at this point in the history
…EEC container (corresponds to feature flags but OneAgent-like)
  • Loading branch information
toszr committed Feb 18, 2022
1 parent e051fa8 commit ad78aec
Show file tree
Hide file tree
Showing 8 changed files with 317 additions and 27 deletions.
41 changes: 24 additions & 17 deletions src/controllers/activegate/capability/capability.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,12 @@ var activeGateCapabilities = map[dynatracev1beta1.CapabilityDisplayName]baseFunc
}

type Configuration struct {
SetDnsEntryPoint bool
SetReadinessPort bool
SetCommunicationPort bool
CreateService bool
ServiceAccountOwner string
SetDnsEntryPoint bool
SetReadinessPort bool
SetCommunicationPort bool
CreateService bool
CreateEecRuntimeConfig bool
ServiceAccountOwner string
}

type Capability interface {
Expand Down Expand Up @@ -169,6 +170,9 @@ func NewMultiCapability(dk *dynatracev1beta1.DynaKube) *MultiCapability {
if !mc.CreateService {
mc.CreateService = capGen.CreateService
}
if !mc.CreateEecRuntimeConfig {
mc.CreateEecRuntimeConfig = capGen.CreateEecRuntimeConfig
}
if !mc.SetCommunicationPort {
mc.SetCommunicationPort = capGen.SetCommunicationPort
}
Expand Down Expand Up @@ -258,10 +262,11 @@ func routingBase() *capabilityBase {
shortName: dynatracev1beta1.RoutingCapability.ShortName,
argName: dynatracev1beta1.RoutingCapability.ArgumentName,
Configuration: Configuration{
SetDnsEntryPoint: true,
SetReadinessPort: true,
SetCommunicationPort: true,
CreateService: true,
SetDnsEntryPoint: true,
SetReadinessPort: true,
SetCommunicationPort: true,
CreateService: true,
CreateEecRuntimeConfig: false,
},
}
return &c
Expand All @@ -272,10 +277,11 @@ func metricsIngestBase() *capabilityBase {
shortName: dynatracev1beta1.MetricsIngestCapability.ShortName,
argName: dynatracev1beta1.MetricsIngestCapability.ArgumentName,
Configuration: Configuration{
SetDnsEntryPoint: true,
SetReadinessPort: true,
SetCommunicationPort: true,
CreateService: true,
SetDnsEntryPoint: true,
SetReadinessPort: true,
SetCommunicationPort: true,
CreateService: true,
CreateEecRuntimeConfig: false,
},
}
return &c
Expand All @@ -300,10 +306,11 @@ func statsdIngestBase() *capabilityBase {
shortName: dynatracev1beta1.StatsdIngestCapability.ShortName,
argName: dynatracev1beta1.StatsdIngestCapability.ArgumentName,
Configuration: Configuration{
SetDnsEntryPoint: true,
SetReadinessPort: true,
SetCommunicationPort: true,
CreateService: true,
SetDnsEntryPoint: true,
SetReadinessPort: true,
SetCommunicationPort: true,
CreateService: true,
CreateEecRuntimeConfig: true,
},
}
return &c
Expand Down
22 changes: 12 additions & 10 deletions src/controllers/activegate/capability/capability_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -427,11 +427,12 @@ func TestNewMultiCapability(t *testing.T) {
argName: dynatracev1beta1.StatsdIngestCapability.ArgumentName,
properties: props,
Configuration: Configuration{
SetDnsEntryPoint: true,
SetReadinessPort: true,
SetCommunicationPort: true,
CreateService: true,
ServiceAccountOwner: "",
SetDnsEntryPoint: true,
SetReadinessPort: true,
SetCommunicationPort: true,
CreateService: true,
CreateEecRuntimeConfig: true,
ServiceAccountOwner: "",
},
},
},
Expand Down Expand Up @@ -519,11 +520,12 @@ func TestNewMultiCapability(t *testing.T) {
","),
properties: props,
Configuration: Configuration{
SetDnsEntryPoint: true,
SetReadinessPort: true,
SetCommunicationPort: true,
CreateService: true,
ServiceAccountOwner: "kubernetes-monitoring",
SetDnsEntryPoint: true,
SetReadinessPort: true,
SetCommunicationPort: true,
CreateService: true,
CreateEecRuntimeConfig: true,
ServiceAccountOwner: "kubernetes-monitoring",
},
initContainersTemplates: []v1.Container{
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package capability

import (
"encoding/json"
"strconv"
"strings"

dynatracev1beta1 "github.com/Dynatrace/dynatrace-operator/src/api/v1beta1"
"github.com/Dynatrace/dynatrace-operator/src/controllers/activegate/reconciler/statefulset"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const extensionsRuntimeProperties = dynatracev1beta1.InternalFlagPrefix + "extensions."

func getExtensionsFlagsFromAnnotations(instance *dynatracev1beta1.DynaKube) map[string]string {
extensionsFlags := make(map[string]string)
for flag, val := range dynatracev1beta1.GetInternalFlags(instance) {
if strings.HasPrefix(flag, extensionsRuntimeProperties) {
runtimeProp := strings.TrimPrefix(flag, extensionsRuntimeProperties)
extensionsFlags[runtimeProp] = val
}
}
return extensionsFlags
}

func buildEecRuntimeConfig(instance *dynatracev1beta1.DynaKube) map[string]interface{} {
booleanMap := make(map[string]bool)
stringMap := make(map[string]string)
longMap := make(map[string]int64)

for runtimeProp, val := range getExtensionsFlagsFromAnnotations(instance) {
if parsedLongInt, err := strconv.ParseInt(val, 10, 64); err == nil {
longMap[runtimeProp] = parsedLongInt
} else if parsedBool, err := strconv.ParseBool(val); err == nil {
booleanMap[runtimeProp] = parsedBool
} else {
stringMap[runtimeProp] = val
}
}

return map[string]interface{}{
"revision": 1,
"booleanMap": booleanMap,
"stringMap": stringMap,
"longMap": longMap,
}
}

func buildEecRuntimeConfigJson(instance *dynatracev1beta1.DynaKube) (string, error) {
runtimeConfiguration, err := json.Marshal(buildEecRuntimeConfig(instance))
if err != nil {
log.Error(err, "problem serializing map with runtime properties")
return "", err
}
return string(runtimeConfiguration), nil
}

func CreateEecConfigMap(instance *dynatracev1beta1.DynaKube, feature string) *corev1.ConfigMap {
if !instance.NeedsStatsd() {
return nil
}

eecRuntimeConfigurationJson, err := buildEecRuntimeConfigJson(instance)
if err != nil {
log.Error(err, "failed to build EEC runtime configuration JSON")
return nil
}

return &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: statefulset.BuildEecConfigMapName(instance.Name, feature),
Namespace: instance.Namespace,
},
Data: map[string]string{
"runtimeConfiguration": eecRuntimeConfigurationJson,
},
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package capability

import (
"encoding/json"
"testing"

dynatracev1beta1 "github.com/Dynatrace/dynatrace-operator/src/api/v1beta1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const testApiUrl = "https://demo.dev.dynatracelabs.com/api"

func testBuildDynaKubeWithAnnotations(instanceName string, statsdEnabled bool, annotations map[string]string) *dynatracev1beta1.DynaKube {
var capabilities []dynatracev1beta1.CapabilityDisplayName
if statsdEnabled {
capabilities = append(capabilities, dynatracev1beta1.StatsdIngestCapability.DisplayName)
}

return &dynatracev1beta1.DynaKube{
ObjectMeta: metav1.ObjectMeta{
Name: instanceName,
Namespace: "dynatrace",
Annotations: annotations,
},
Spec: dynatracev1beta1.DynaKubeSpec{
APIURL: testApiUrl,
ActiveGate: dynatracev1beta1.ActiveGateSpec{
Capabilities: capabilities,
},
},
}
}

func TestCreateEecConfigMap(t *testing.T) {
t.Run("happy path", func(t *testing.T) {
instance := testBuildDynaKubeWithAnnotations("dynakube", true, map[string]string{
"internal.operator.dynatrace.com/extensions.debugExtensionDSstatsddisablenamedalivesignals": "false",
"internal.operator.dynatrace.com/extensions.debugExtensionDSstatsdlogoutboundminttraffic": "true",
"internal.operator.dynatrace.com/extensions.debugExtensionDSstatsdcustomloglevel": "trace",
})
runtimeConfig := make(map[string]interface{})

eecConfigMap := CreateEecConfigMap(instance, "activegate")
runtimeConfigJson := eecConfigMap.Data["runtimeConfiguration"]

assert.Equal(t, "dynakube-activegate-eec-config", eecConfigMap.Name)

require.NotEmpty(t, eecConfigMap.Data)
require.NoError(t, json.Unmarshal([]byte(runtimeConfigJson), &runtimeConfig))
assert.Equal(t, 1., runtimeConfig["revision"])
assert.True(t, runtimeConfig["booleanMap"].(map[string]interface{})["debugExtensionDSstatsdlogoutboundminttraffic"].(bool))
assert.False(t, runtimeConfig["booleanMap"].(map[string]interface{})["debugExtensionDSstatsddisablenamedalivesignals"].(bool))
assert.Equal(t, "trace", runtimeConfig["stringMap"].(map[string]interface{})["debugExtensionDSstatsdcustomloglevel"])
assert.Empty(t, runtimeConfig["longMap"].(map[string]interface{}))
})

t.Run("no valid EEC runtime properties, StatsD enabled", func(t *testing.T) {
instance := testBuildDynaKubeWithAnnotations("dynakube", true, map[string]string{
"internal.operator.dynatrace.com/debugExtensionDSstatsdlogoutboundminttraffic": "true",
"debugExtensionDSstatsdcustomloglevel": "info",
})
runtimeConfig := make(map[string]interface{})

eecConfigMap := CreateEecConfigMap(instance, "activegate")
runtimeConfigJson := eecConfigMap.Data["runtimeConfiguration"]

assert.Equal(t, "dynakube-activegate-eec-config", eecConfigMap.Name)

require.NotEmpty(t, eecConfigMap.Data)
require.NoError(t, json.Unmarshal([]byte(runtimeConfigJson), &runtimeConfig))
assert.Equal(t, 1., runtimeConfig["revision"])
assert.Empty(t, runtimeConfig["booleanMap"].(map[string]interface{}))
assert.Empty(t, runtimeConfig["stringMap"].(map[string]interface{}))
assert.Empty(t, runtimeConfig["longMap"].(map[string]interface{}))
})

t.Run("valid EEC runtime properties but StatsD disabled", func(t *testing.T) {
instance := testBuildDynaKubeWithAnnotations("dynakube", false, map[string]string{
"internal.operator.dynatrace.com/extensions.debugExtensionDSstatsddisablenamedalivesignals": "false",
"internal.operator.dynatrace.com/extensions.debugExtensionDSstatsdlogoutboundminttraffic": "true",
"internal.operator.dynatrace.com/extensions.debugExtensionDSstatsdcustomloglevel": "trace",
})

eecConfigMap := CreateEecConfigMap(instance, "activegate")
assert.Nil(t, eecConfigMap)
})
}
12 changes: 12 additions & 0 deletions src/controllers/activegate/reconciler/capability/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,18 @@ func (r *Reconciler) Reconcile() (update bool, err error) {
}
}

if r.Config().CreateEecRuntimeConfig {
update, err = r.createEecConfigMapIfNotExists()
if update || err != nil {
return update, errors.WithStack(err)
}

update, err = r.updateEecConfigMapIfOutdated()
if update || err != nil {
return update, errors.WithStack(err)
}
}

update, err = r.Reconciler.Reconcile()
return update, errors.WithStack(err)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package capability

import (
"context"
"reflect"

"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

func (r *Reconciler) createEecConfigMapIfNotExists() (bool, error) {
eecConfigMap := CreateEecConfigMap(r.Instance, r.ShortName())

getErr := r.Get(context.TODO(), client.ObjectKey{Name: eecConfigMap.Name, Namespace: eecConfigMap.Namespace}, eecConfigMap)
if getErr != nil && k8serrors.IsNotFound(getErr) {
log.Info("creating EEC config map", "module", r.ShortName())
if err := controllerutil.SetControllerReference(r.Instance, eecConfigMap, r.Scheme()); err != nil {
return false, errors.WithStack(err)
}

err := r.Create(context.TODO(), eecConfigMap)
return true, errors.WithStack(err)
}
return false, errors.WithStack(getErr)
}

func (r *Reconciler) updateEecConfigMapIfOutdated() (bool, error) {
desiredConfigMap := CreateEecConfigMap(r.Instance, r.ShortName())
installedConfigMap := &corev1.ConfigMap{}

err := r.Get(context.TODO(), client.ObjectKey{Name: desiredConfigMap.Name, Namespace: desiredConfigMap.Namespace}, installedConfigMap)
if err != nil {
return false, errors.WithStack(err)
}

if r.isEecConfigMapOutdated(installedConfigMap, desiredConfigMap) {
desiredConfigMap.ObjectMeta.ResourceVersion = installedConfigMap.ObjectMeta.ResourceVersion
updateErr := r.updateEecConfigMap(desiredConfigMap)
if updateErr != nil {
return false, updateErr
}
return true, nil
}
return false, nil
}

func (r *Reconciler) isEecConfigMapOutdated(installedConfigMap, desiredConfigMap *corev1.ConfigMap) bool {
configMapsEqual := reflect.DeepEqual(installedConfigMap.Data, desiredConfigMap.Data) &&
reflect.DeepEqual(installedConfigMap.BinaryData, desiredConfigMap.BinaryData)
return !configMapsEqual
}

func (r *Reconciler) updateEecConfigMap(eecConfigMap *corev1.ConfigMap) error {
return r.Update(context.TODO(), eecConfigMap)
}
Loading

0 comments on commit ad78aec

Please sign in to comment.