Skip to content

Commit

Permalink
Add ipv6 support to ActiveGate deployment (#2951)
Browse files Browse the repository at this point in the history
  • Loading branch information
0sewa0 authored Apr 4, 2024
1 parent 3669689 commit e62b235
Show file tree
Hide file tree
Showing 10 changed files with 258 additions and 157 deletions.
6 changes: 2 additions & 4 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,8 @@ service:
golangci-lint-version: 1.55.x # use the fixed version to not introduce new linters unexpectedly

issues:
exclude-dirs:
- pkg/api/v1alpha1/dynakube # legacy version, should not be changed
exclude-rules:
# Exclude duplicate code and function length and complexity checking in test
# files (due to common repeats and long functions in test code)
Expand Down Expand Up @@ -337,7 +339,3 @@ issues:
- path-except: 'pkg/api/(.+)\.go'
linters:
- godot

run:
skip-dirs:
- pkg/api/v1alpha1/dynakube # legacy version, should not be changed
6 changes: 6 additions & 0 deletions config/crd/bases/dynatrace.com_dynakubes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3485,6 +3485,12 @@ spec:
performed
format: date-time
type: string
serviceIPs:
description: The ClusterIPs set by Kubernetes on the ActiveGate
Service created by the Operator
items:
type: string
type: array
source:
description: Source of the image (tenant-registry, public-registry,
...)
Expand Down
7 changes: 4 additions & 3 deletions config/crd/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ resources:
- bases
#+kubebuilder:scaffold:crdkustomizeresource

patchesStrategicMerge:
- patches/webhook_in_dynakubes.yaml
#+kubebuilder:scaffold:crdkustomizewebhookpatch
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
patches:
- path: patches/webhook_in_dynakubes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4150,6 +4150,12 @@ spec:
performed
format: date-time
type: string
serviceIPs:
description: The ClusterIPs set by Kubernetes on the ActiveGate
Service created by the Operator
items:
type: string
type: array
source:
description: Source of the image (tenant-registry, public-registry,
...)
Expand Down
3 changes: 3 additions & 0 deletions pkg/api/v1beta1/dynakube/dynakube_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ type ActiveGateStatus struct {

// Information about Active Gate's connections
ConnectionInfoStatus ActiveGateConnectionInfoStatus `json:"connectionInfoStatus,omitempty"`

// The ClusterIPs set by Kubernetes on the ActiveGate Service created by the Operator
ServiceIPs []string `json:"serviceIPs,omitempty"`
}

type CodeModulesStatus struct {
Expand Down
63 changes: 32 additions & 31 deletions pkg/controllers/dynakube/activegate/capability/capability.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

dynatracev1beta1 "github.com/Dynatrace/dynatrace-operator/pkg/api/v1beta1/dynakube"
"github.com/Dynatrace/dynatrace-operator/pkg/controllers/dynakube/activegate/consts"
"k8s.io/utils/net"
)

type baseFunc func() *capabilityBase
Expand Down Expand Up @@ -67,7 +68,7 @@ type MultiCapability struct {
capabilityBase
}

func NewMultiCapability(dk *dynatracev1beta1.DynaKube) *MultiCapability {
func NewMultiCapability(dk *dynatracev1beta1.DynaKube) Capability {
mc := MultiCapability{
capabilityBase{
shortName: consts.MultiActiveGateName,
Expand Down Expand Up @@ -97,7 +98,7 @@ func NewMultiCapability(dk *dynatracev1beta1.DynaKube) *MultiCapability {
}

// Deprecated
func NewKubeMonCapability(dk *dynatracev1beta1.DynaKube) *KubeMonCapability {
func NewKubeMonCapability(dk *dynatracev1beta1.DynaKube) Capability {
c := &KubeMonCapability{
*kubeMonBase(),
}
Expand All @@ -112,7 +113,7 @@ func NewKubeMonCapability(dk *dynatracev1beta1.DynaKube) *KubeMonCapability {
}

// Deprecated
func NewRoutingCapability(dk *dynatracev1beta1.DynaKube) *RoutingCapability {
func NewRoutingCapability(dk *dynatracev1beta1.DynaKube) Capability {
c := &RoutingCapability{
*routingBase(),
}
Expand Down Expand Up @@ -174,40 +175,40 @@ func BuildServiceName(dynakubeName string, module string) string {
return dynakubeName + "-" + module
}

// BuildServiceHostName converts the name returned by BuildServiceName
// into the variable name which Kubernetes uses to reference the associated service.
// For more information see: https://kubernetes.io/docs/concepts/services-networking/service/
func BuildServiceHostName(dynakubeName string, module string) string {
serviceName := BuildServiceNameUnderscore(dynakubeName, module)

return fmt.Sprintf("$(%s_SERVICE_HOST):$(%s_SERVICE_PORT)", serviceName, serviceName)
func BuildDNSEntryPointWithoutEnvVars(dynakubeName, dynakubeNamespace string, capability Capability) string {
return fmt.Sprintf("%s.%s", BuildServiceName(dynakubeName, capability.ShortName()), dynakubeNamespace)
}

// BuildServiceDomainName builds service domain name
func BuildServiceDomainName(dynakubeName string, namespaceName string, module string) string {
return fmt.Sprintf("%s.%s:$(%s_SERVICE_PORT)", BuildServiceName(dynakubeName, module), namespaceName, BuildServiceNameUnderscore(dynakubeName, module))
}
func BuildDNSEntryPoint(dk dynatracev1beta1.DynaKube, capability Capability) string {
entries := []string{}

// BuildServiceNameUnderscore converts result of BuildServiceName by replacing dashes with underscores
// to make it env variable compatible because it's only special symbol it supports
func BuildServiceNameUnderscore(dynakubeName string, module string) string {
return strings.ReplaceAll(
strings.ToUpper(
BuildServiceName(dynakubeName, module)),
"-", "_")
}
for _, ip := range dk.Status.ActiveGate.ServiceIPs {
serviceHost := ip
if net.IsIPv6String(ip) {
serviceHost = "[" + serviceHost + "]"
}

serviceHostEntry := buildDNSEntry(buildServiceHostName(serviceHost))
entries = append(entries, serviceHostEntry)
}

// BuildDNSEntryPoint for give capability
func BuildDNSEntryPoint(dynakubeName, dynakubeNamespace string, capability Capability) string {
if capability.ShortName() == consts.MultiActiveGateName && strings.Contains(capability.ArgName(), dynatracev1beta1.RoutingCapability.ArgumentName) ||
capability.ShortName() == dynatracev1beta1.RoutingCapability.ShortName {
return fmt.Sprintf("https://%s/communication,https://%s/communication", BuildServiceHostName(dynakubeName, capability.ShortName()), BuildServiceDomainName(dynakubeName, dynakubeNamespace, capability.ShortName()))
if dk.IsRoutingActiveGateEnabled() {
serviceDomain := buildServiceDomainName(dk.Name, dk.Namespace, capability.ShortName())
serviceDomainEntry := buildDNSEntry(serviceDomain)
entries = append(entries, serviceDomainEntry)
}

return fmt.Sprintf("https://%s/communication", BuildServiceHostName(dynakubeName, capability.ShortName()))
return strings.Join(entries, ",")
}

// BuildDNSEntryPointWithoutEnvVars for give capability
func BuildDNSEntryPointWithoutEnvVars(dynakubeName, dynakubeNamespace string, capability Capability) string {
return fmt.Sprintf("%s.%s", BuildServiceName(dynakubeName, capability.ShortName()), dynakubeNamespace)
func buildServiceHostName(host string) string {
return fmt.Sprintf("%s:%d", host, consts.HttpsServicePort)
}

func buildServiceDomainName(dynakubeName string, namespaceName string, module string) string {
return fmt.Sprintf("%s.%s:%d", BuildServiceName(dynakubeName, module), namespaceName, consts.HttpsServicePort)
}

func buildDNSEntry(host string) string {
return fmt.Sprintf("https://%s/communication", host)
}
199 changes: 182 additions & 17 deletions pkg/controllers/dynakube/activegate/capability/capability_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,31 +77,196 @@ func TestNewMultiCapability(t *testing.T) {
})
}

func TestBuildServiceHostNameForDNSEntryPoint(t *testing.T) {
actual := BuildServiceHostName("test-name", "test-component-feature")
func TestBuildServiceDomainNameForDNSEntryPoint(t *testing.T) {
actual := buildServiceDomainName("test-name", "test-namespace", "test-component-feature")
assert.NotEmpty(t, actual)

expected := "$(TEST_NAME_TEST_COMPONENT_FEATURE_SERVICE_HOST):$(TEST_NAME_TEST_COMPONENT_FEATURE_SERVICE_PORT)"
expected := "test-name-test-component-feature.test-namespace:443"
assert.Equal(t, expected, actual)

testStringName := "this---test_string"
testStringName := "this---dynakube_string"
testNamespace := "this_is---namespace_string"
testStringFeature := "SHOULD--_--PaRsEcORrEcTlY"
expected = "$(THIS___TEST_STRING_SHOULD_____PARSECORRECTLY_SERVICE_HOST):$(THIS___TEST_STRING_SHOULD_____PARSECORRECTLY_SERVICE_PORT)"
actual = BuildServiceHostName(testStringName, testStringFeature)
expected = "this---dynakube_string-SHOULD--_--PaRsEcORrEcTlY.this_is---namespace_string:443"
actual = buildServiceDomainName(testStringName, testNamespace, testStringFeature)
assert.Equal(t, expected, actual)
}

func TestBuildServiceDomainNameForDNSEntryPoint(t *testing.T) {
actual := BuildServiceDomainName("test-name", "test-namespace", "test-component-feature")
assert.NotEmpty(t, actual)
func TestBuildDNSEntryPoint(t *testing.T) {
type capabilityBuilder func(*dynatracev1beta1.DynaKube) Capability

expected := "test-name-test-component-feature.test-namespace:$(TEST_NAME_TEST_COMPONENT_FEATURE_SERVICE_PORT)"
assert.Equal(t, expected, actual)
type testCase struct {
title string
dk *dynatracev1beta1.DynaKube
capability capabilityBuilder
expectedDNS string
}

testStringName := "this---dynakube_string"
testNamespace := "this_is---namespace_string"
testStringFeature := "SHOULD--_--PaRsEcORrEcTlY"
expected = "this---dynakube_string-SHOULD--_--PaRsEcORrEcTlY.this_is---namespace_string:$(THIS___DYNAKUBE_STRING_SHOULD_____PARSECORRECTLY_SERVICE_PORT)"
actual = BuildServiceDomainName(testStringName, testNamespace, testStringFeature)
assert.Equal(t, expected, actual)
testCases := []testCase{
{
title: "DNSEntryPoint for ActiveGate routing capability",
dk: &dynatracev1beta1.DynaKube{
ObjectMeta: metav1.ObjectMeta{
Name: "dynakube",
Namespace: "dynatrace",
},
Spec: dynatracev1beta1.DynaKubeSpec{
ActiveGate: dynatracev1beta1.ActiveGateSpec{
Capabilities: []dynatracev1beta1.CapabilityDisplayName{
dynatracev1beta1.RoutingCapability.DisplayName,
},
},
},
Status: dynatracev1beta1.DynaKubeStatus{
ActiveGate: dynatracev1beta1.ActiveGateStatus{
ServiceIPs: []string{"1.2.3.4"},
},
},
},
capability: NewMultiCapability,
expectedDNS: "https://1.2.3.4:443/communication,https://dynakube-activegate.dynatrace:443/communication",
},
{
title: "DNSEntryPoint with multiple service IPs",
dk: &dynatracev1beta1.DynaKube{
ObjectMeta: metav1.ObjectMeta{
Name: "dynakube",
Namespace: "dynatrace",
},
Spec: dynatracev1beta1.DynaKubeSpec{
ActiveGate: dynatracev1beta1.ActiveGateSpec{
Capabilities: []dynatracev1beta1.CapabilityDisplayName{
dynatracev1beta1.RoutingCapability.DisplayName,
},
},
},
Status: dynatracev1beta1.DynaKubeStatus{
ActiveGate: dynatracev1beta1.ActiveGateStatus{
ServiceIPs: []string{"1.2.3.4", "4.3.2.1"},
},
},
},
capability: NewMultiCapability,
expectedDNS: "https://1.2.3.4:443/communication,https://4.3.2.1:443/communication,https://dynakube-activegate.dynatrace:443/communication",
},
{
title: "DNSEntryPoint with multiple service IPs, dual-stack",
dk: &dynatracev1beta1.DynaKube{
ObjectMeta: metav1.ObjectMeta{
Name: "dynakube",
Namespace: "dynatrace",
},
Spec: dynatracev1beta1.DynaKubeSpec{
ActiveGate: dynatracev1beta1.ActiveGateSpec{
Capabilities: []dynatracev1beta1.CapabilityDisplayName{
dynatracev1beta1.RoutingCapability.DisplayName,
},
},
},
Status: dynatracev1beta1.DynaKubeStatus{
ActiveGate: dynatracev1beta1.ActiveGateStatus{
ServiceIPs: []string{"1.2.3.4", "2600:2d00:0:4:f9b7:bd67:1d97:5994", "4.3.2.1", "2600:2d00:0:4:f9b7:bd67:1d97:5996"},
},
},
},
capability: NewMultiCapability,
expectedDNS: "https://1.2.3.4:443/communication,https://[2600:2d00:0:4:f9b7:bd67:1d97:5994]:443/communication,https://4.3.2.1:443/communication,https://[2600:2d00:0:4:f9b7:bd67:1d97:5996]:443/communication,https://dynakube-activegate.dynatrace:443/communication",
},
{
title: "DNSEntryPoint for ActiveGate k8s monitoring capability",
dk: &dynatracev1beta1.DynaKube{
ObjectMeta: metav1.ObjectMeta{
Name: "dynakube",
Namespace: "dynatrace",
},
Spec: dynatracev1beta1.DynaKubeSpec{
ActiveGate: dynatracev1beta1.ActiveGateSpec{
Capabilities: []dynatracev1beta1.CapabilityDisplayName{
dynatracev1beta1.KubeMonCapability.DisplayName,
},
},
},
Status: dynatracev1beta1.DynaKubeStatus{
ActiveGate: dynatracev1beta1.ActiveGateStatus{
ServiceIPs: []string{"1.2.3.4"},
},
},
},
capability: NewMultiCapability,
expectedDNS: "https://1.2.3.4:443/communication",
},
{
title: "DNSEntryPoint for ActiveGate routing+kubemon capabilities",
dk: &dynatracev1beta1.DynaKube{
ObjectMeta: metav1.ObjectMeta{
Name: "dynakube",
Namespace: "dynatrace",
},
Spec: dynatracev1beta1.DynaKubeSpec{
ActiveGate: dynatracev1beta1.ActiveGateSpec{
Capabilities: []dynatracev1beta1.CapabilityDisplayName{
dynatracev1beta1.KubeMonCapability.DisplayName,
dynatracev1beta1.RoutingCapability.DisplayName,
},
},
},
Status: dynatracev1beta1.DynaKubeStatus{
ActiveGate: dynatracev1beta1.ActiveGateStatus{
ServiceIPs: []string{"1.2.3.4"},
},
},
},
capability: NewMultiCapability,
expectedDNS: "https://1.2.3.4:443/communication,https://dynakube-activegate.dynatrace:443/communication",
},
{
title: "DNSEntryPoint for deprecated routing ActiveGate",
dk: &dynatracev1beta1.DynaKube{
ObjectMeta: metav1.ObjectMeta{
Name: "dynakube",
Namespace: "dynatrace",
},
Spec: dynatracev1beta1.DynaKubeSpec{
Routing: dynatracev1beta1.RoutingSpec{
Enabled: true,
},
},
Status: dynatracev1beta1.DynaKubeStatus{
ActiveGate: dynatracev1beta1.ActiveGateStatus{
ServiceIPs: []string{"1.2.3.4"},
},
},
},
capability: NewRoutingCapability,
expectedDNS: "https://1.2.3.4:443/communication,https://dynakube-routing.dynatrace:443/communication",
},
{
title: "DNSEntryPoint for deprecated kubernetes monitoring ActiveGate",
dk: &dynatracev1beta1.DynaKube{
ObjectMeta: metav1.ObjectMeta{
Name: "dynakube",
Namespace: "dynatrace",
},
Spec: dynatracev1beta1.DynaKubeSpec{
KubernetesMonitoring: dynatracev1beta1.KubernetesMonitoringSpec{
Enabled: true,
},
},
Status: dynatracev1beta1.DynaKubeStatus{
ActiveGate: dynatracev1beta1.ActiveGateStatus{
ServiceIPs: []string{"1.2.3.4"},
},
},
},
capability: NewKubeMonCapability,
expectedDNS: "https://1.2.3.4:443/communication",
},
}
for _, test := range testCases {
t.Run(test.title, func(t *testing.T) {
capability := test.capability(test.dk)
dnsEntryPoint := BuildDNSEntryPoint(*test.dk, capability)
assert.Equal(t, test.expectedDNS, dnsEntryPoint)
})
}
}
Loading

0 comments on commit e62b235

Please sign in to comment.