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

Add statsd ingest #313

Merged
merged 6 commits into from
Feb 3, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/api/v1beta1/activegate_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ var (
RoutingCapability = ActiveGateCapability{
DisplayName: "routing",
ShortName: "routing",
ArgumentName: "MSGrouter",
ArgumentName: "MSGrouter,extension_controller",
}

KubeMonCapability = ActiveGateCapability{
Expand Down
14 changes: 14 additions & 0 deletions src/api/v1beta1/feature_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ const (
annotationFeatureIgnoredNamespaces = annotationFeaturePrefix + "ignored-namespaces"
annotationFeatureAutomaticKubernetesApiMonitoring = annotationFeaturePrefix + "automatic-kubernetes-api-monitoring"
annotationFeatureDisableMetadataEnrichment = annotationFeaturePrefix + "disable-metadata-enrichment"
annotationFeatureEnableStatsDIngest = annotationFeaturePrefix + "enable-statsd"
annotationFeatureUseActiveGateImageForStatsD = annotationFeaturePrefix + "use-activegate-image-for-statsd"
)

var (
Expand Down Expand Up @@ -117,3 +119,15 @@ func (dk *DynaKube) FeatureAutomaticKubernetesApiMonitoring() bool {
func (dk *DynaKube) FeatureDisableMetadataEnrichment() bool {
return dk.Annotations[annotationFeatureDisableMetadataEnrichment] == "true"
}

// FeatureEnableStatsDIngest is a feature flag that makes the operator include 2 extra containers (Extension Controller and StatsD data source)
// in the ActiveGate pod, and defines an extra UDP port in the AG service for StatsD packets.
func (dk *DynaKube) FeatureEnableStatsDIngest() bool {
return dk.Annotations[annotationFeatureEnableStatsDIngest] == "true"
}

// FeatureUseActiveGateImageForStatsD is a feature flag that makes the operator use ActiveGate image when initializing Extension Controller and StatsD containers
// (using special predefined entry points).
func (dk *DynaKube) FeatureUseActiveGateImageForStatsD() bool {
return dk.Annotations[annotationFeatureUseActiveGateImageForStatsD] == "true"
}
24 changes: 24 additions & 0 deletions src/api/v1beta1/internal_operator_flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package v1beta1

import (
"fmt"
"strings"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const InternalFlagPrefix = "internal.operator.dynatrace.com/"

func GetInternalFlags(obj metav1.Object) map[string]string {
internalAnnotations := make(map[string]string)
for annotation, value := range obj.GetAnnotations() {
if strings.HasPrefix(annotation, InternalFlagPrefix) {
internalAnnotations[annotation] = value
}
}
return internalAnnotations
}

func IsInternalFlagsEqual(obj1, obj2 metav1.Object) bool {
return fmt.Sprint(GetInternalFlags(obj1)) == fmt.Sprint(GetInternalFlags(obj2))
}
136 changes: 136 additions & 0 deletions src/api/v1beta1/internal_operator_flags_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package v1beta1

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestGetInternalFlags(t *testing.T) {
t.Run("Empty pod should have no internal flags", func(t *testing.T) {
annotatedObject := &corev1.Pod{}
expectedMapContents := "map[]"

assert.Equal(t, expectedMapContents, fmt.Sprint(GetInternalFlags(annotatedObject)))
})

t.Run("Only internal flags should be returned (1)", func(t *testing.T) {
annotatedObject := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
"not-a-flag": "oh, no",
},
},
}
expectedMapContents := "map[]"

assert.Equal(t, expectedMapContents, fmt.Sprint(GetInternalFlags(annotatedObject)))
})

t.Run("Only internal flags should be returned (2)", func(t *testing.T) {
annotatedObject := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
InternalFlagPrefix + "some-flag": "something",
InternalFlagPrefix + "other-flag": "nothing",
"unexpected." + InternalFlagPrefix + "not-a-flag": "oh, no",
},
},
}
expectedMapContents := "map[internal.operator.dynatrace.com/other-flag:nothing internal.operator.dynatrace.com/some-flag:something]"

assert.Equal(t, expectedMapContents, fmt.Sprint(GetInternalFlags(annotatedObject)))
})
}

func TestIsInternalFlagsEqual(t *testing.T) {
t.Run("Expected that no-flag objects are equal internal flags-wise", func(t *testing.T) {
assert.True(t, IsInternalFlagsEqual(&corev1.Pod{}, &corev1.Pod{}))
assert.True(t, IsInternalFlagsEqual(&corev1.Pod{}, &corev1.Service{}))
assert.True(t, IsInternalFlagsEqual(&corev1.Namespace{}, &corev1.Service{}))
})

t.Run("Expected that objects without internal operator flags compare as equal", func(t *testing.T) {
assert.True(t, IsInternalFlagsEqual(
&corev1.Pod{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
"": "", "space": " ", "dot": ".",
}}},
&corev1.Pod{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
"comma": ",",
}}},
))
assert.True(t, IsInternalFlagsEqual(
&corev1.Pod{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
"": "", "space": " ", "dot": ".",
}}},
&corev1.Service{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
"comma": ",",
}}},
))
assert.True(t, IsInternalFlagsEqual(
&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
"": "", "space": " ", "dot": ".",
}}},
&corev1.Service{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
"comma": ",",
}}},
))
})

t.Run("Expected that objects with internal operator flags compare as equal if the flags are identical", func(t *testing.T) {
assert.True(t, IsInternalFlagsEqual(
&corev1.Pod{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
"dyna": "trace", InternalFlagPrefix + "flag": "value",
}}},
&corev1.Pod{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
"dyna": "truce", InternalFlagPrefix + "flag": "value",
}}},
))
assert.True(t, IsInternalFlagsEqual(
&corev1.Pod{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
"dyna": "trace", InternalFlagPrefix + "flag": "value",
}}},
&corev1.Service{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
"dyna": "tarce", InternalFlagPrefix + "flag": "value",
}}},
))
assert.True(t, IsInternalFlagsEqual(
&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
"dyna": "trace", InternalFlagPrefix + "flag": "value",
}}},
&corev1.Service{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
"dyna": "trcue", InternalFlagPrefix + "flag": "value",
}}},
))
})

t.Run("Expected that objects with internal operator flags compare as different if the flags have different values or are missing", func(t *testing.T) {
assert.False(t, IsInternalFlagsEqual(
&corev1.Pod{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
"dyna": "trace", InternalFlagPrefix + "flag": "value",
}}},
&corev1.Pod{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
"dyna": "trace", InternalFlagPrefix + "flag": "other value",
}}},
))
assert.False(t, IsInternalFlagsEqual(
&corev1.Pod{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
"dyna": "trace", InternalFlagPrefix + "flag": "value",
}}},
&corev1.Service{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
"dyna": "trace", InternalFlagPrefix + "flag": "other value",
}}},
))
assert.False(t, IsInternalFlagsEqual(
&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
"dyna": "trace", InternalFlagPrefix + "flag": "value",
}}},
&corev1.Service{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
"dyna": "trace", InternalFlagPrefix + "flag": "other value",
}}},
))
})
}
23 changes: 23 additions & 0 deletions src/api/v1beta1/properties.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ package v1beta1

import (
"fmt"
"net/url"
"strings"

"github.com/Dynatrace/dynatrace-operator/src/dtclient"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
Expand Down Expand Up @@ -249,6 +251,27 @@ func (dk *DynaKube) CommunicationHosts() []dtclient.CommunicationHost {
return communicationHosts
}

func (dk *DynaKube) TenantUUID() (string, error) {
return tenantUUID(dk.Spec.APIURL)
}

func tenantUUID(apiUrl string) (string, error) {
toszr marked this conversation as resolved.
Show resolved Hide resolved
parsedUrl, err := url.Parse(apiUrl)
if err != nil {
return "", errors.WithMessagef(err, "problem parsing tenant id from url %s", apiUrl)
}

fqdn := parsedUrl.Hostname()
hostnameWithDomains := strings.FieldsFunc(fqdn,
func(r rune) bool { return r == '.' },
)
if len(hostnameWithDomains) < 1 {
return "", fmt.Errorf("problem getting tenant id from fqdn '%s'", fqdn)
}

return hostnameWithDomains[0], nil
}

func (dk *DynaKube) HostGroup() string {
var hostGroup string
if dk.CloudNativeFullstackMode() && dk.Spec.OneAgent.CloudNativeFullStack.Args != nil {
Expand Down
44 changes: 44 additions & 0 deletions src/api/v1beta1/properties_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,47 @@ func TestTokens(t *testing.T) {
assert.Equal(t, dk.Tokens(), testName)
})
}

func TestTenantUUID(t *testing.T) {
t.Run("happy path", func(t *testing.T) {
apiUrl := "https://demo.dev.dynatracelabs.com/api"
expectedTenantId := "demo"

actualTenantId, err := tenantUUID(apiUrl)

assert.NoErrorf(t, err, "Expected that getting tenant id from '%s' will be successful", apiUrl)
assert.Equalf(t, expectedTenantId, actualTenantId, "Expected that tenant id of %s is %s, but found %s",
apiUrl, expectedTenantId, actualTenantId,
)
})

t.Run("missing API URL protocol", func(t *testing.T) {
apiUrl := "demo.dev.dynatracelabs.com/api"
expectedTenantId := ""
expectedError := "problem getting tenant id from fqdn ''"

actualTenantId, err := tenantUUID(apiUrl)

assert.EqualErrorf(t, err, expectedError, "Expected that getting tenant id from '%s' will result in: '%v'",
apiUrl, expectedError,
)
assert.Equalf(t, expectedTenantId, actualTenantId, "Expected that tenant id of %s is %s, but found %s",
apiUrl, expectedTenantId, actualTenantId,
)
})

t.Run("suffix-only, relative API URL", func(t *testing.T) {
apiUrl := "/api"
expectedTenantId := ""
expectedError := "problem getting tenant id from fqdn ''"

actualTenantId, err := tenantUUID(apiUrl)

assert.EqualErrorf(t, err, expectedError, "Expected that getting tenant id from '%s' will result in: '%v'",
apiUrl, expectedError,
)
assert.Equalf(t, expectedTenantId, actualTenantId, "Expected that tenant id of %s is %s, but found %s",
apiUrl, expectedTenantId, actualTenantId,
)
})
}
17 changes: 13 additions & 4 deletions src/controllers/activegate/internal/consts/consts.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
package consts

const (
HttpsServicePort = 443
HttpsServiceTargetPort = "ag-https"
HttpServicePort = 80
HttpServiceTargetPort = "ag-http"
ActiveGateContainerName = "activegate"

HttpsServicePortName = "https"
HttpsServicePort = 443
HttpServicePortName = "http"
HttpServicePort = 80

EecContainerName = ActiveGateContainerName + "-eec"

StatsDContainerName = ActiveGateContainerName + "-statsd"
StatsDIngestPortName = "statsd"
StatsDIngestPort = 18125
StatsDIngestTargetPort = "statsd-port"
)
Loading