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 option for selfregistering activegate #423

Merged
merged 12 commits into from
Jan 10, 2022
1 change: 1 addition & 0 deletions src/api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 14 additions & 7 deletions src/api/v1beta1/feature_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@ import (
)

const (
annotationFeaturePrefix = "alpha.operator.dynatrace.com/feature-"
annotationFeatureDisableActiveGateUpdates = annotationFeaturePrefix + "disable-activegate-updates"
annotationFeatureDisableHostsRequests = annotationFeaturePrefix + "disable-hosts-requests"
annotationFeatureOneAgentMaxUnavailable = annotationFeaturePrefix + "oneagent-max-unavailable"
annotationFeatureEnableWebhookReinvocationPolicy = annotationFeaturePrefix + "enable-webhook-reinvocation-policy"
annotationFeatureIgnoreUnknownState = annotationFeaturePrefix + "ignore-unknown-state"
annotationFeatureIgnoredNamespaces = annotationFeaturePrefix + "ignored-namespaces"
annotationFeaturePrefix = "alpha.operator.dynatrace.com/feature-"
annotationFeatureDisableActiveGateUpdates = annotationFeaturePrefix + "disable-activegate-updates"
annotationFeatureDisableHostsRequests = annotationFeaturePrefix + "disable-hosts-requests"
annotationFeatureOneAgentMaxUnavailable = annotationFeaturePrefix + "oneagent-max-unavailable"
annotationFeatureEnableWebhookReinvocationPolicy = annotationFeaturePrefix + "enable-webhook-reinvocation-policy"
annotationFeatureIgnoreUnknownState = annotationFeaturePrefix + "ignore-unknown-state"
annotationFeatureIgnoredNamespaces = annotationFeaturePrefix + "ignored-namespaces"
annotationFeatureAutomaticKubernetesApiMonitoring = annotationFeaturePrefix + "automatic-kubernetes-api-monitoring"
)

var (
Expand Down Expand Up @@ -100,3 +101,9 @@ func (dk *DynaKube) FeatureIgnoredNamespaces() []string {
}
return *ignoredNamespaces
}

// FeatureAutomaticKubernetesApiMonitoring is a feature flag to enable automatic kubernetes api monitoring,
// which ensures that settings for this kubernetes cluster exist in Dynatrace
func (dk *DynaKube) FeatureAutomaticKubernetesApiMonitoring() bool {
return dk.Annotations[annotationFeatureAutomaticKubernetesApiMonitoring] == "true"
}
4 changes: 4 additions & 0 deletions src/api/v1beta1/properties.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ func (dk *DynaKube) IsActiveGateMode(mode string) bool {
return false
}

func (dk *DynaKube) KubernetesMonitoringMode() bool {
return dk.IsActiveGateMode(string(KubeMonCapability.DisplayName)) || dk.Spec.KubernetesMonitoring.Enabled
}

// ShouldAutoUpdateOneAgent returns true if the Operator should update OneAgent instances automatically.
func (dk *DynaKube) ShouldAutoUpdateOneAgent() bool {
if dk.CloudNativeFullstackMode() {
Expand Down
1 change: 1 addition & 0 deletions src/api/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package automaticapimonitoring

import (
"github.com/Dynatrace/dynatrace-operator/src/logger"
)

var (
log = logger.NewDTLogger().WithName("automatic-api-monitoring")
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package automaticapimonitoring

import (
"fmt"

"github.com/Dynatrace/dynatrace-operator/src/dtclient"
"github.com/pkg/errors"
)

type AutomaticApiMonitoringReconciler struct {
dtc dtclient.Client
name string
kubeSystemUUID string
}

func NewReconciler(dtc dtclient.Client, name, kubeSystemUUID string) *AutomaticApiMonitoringReconciler {
return &AutomaticApiMonitoringReconciler{
dtc,
name,
kubeSystemUUID,
}
}

func (r *AutomaticApiMonitoringReconciler) Reconcile() error {
objectID, err := r.ensureSettingExists()

if err != nil {
return err
}

if objectID != "" {
log.Info(fmt.Sprintf("created setting '%s' for Cluster '%s'. Settings object ID: %s", r.name, r.kubeSystemUUID, objectID))
fabwer marked this conversation as resolved.
Show resolved Hide resolved
} else {
log.Info(fmt.Sprintf("setting '%s' for Cluster '%s' already exists.", r.name, r.kubeSystemUUID))
fabwer marked this conversation as resolved.
Show resolved Hide resolved
}

return nil
}

func (r *AutomaticApiMonitoringReconciler) ensureSettingExists() (string, error) {
if r.kubeSystemUUID == "" {
return "", errors.New("no kube-system namespace UUID given")
}

// check if ME with UID exists
var monitoredEntities, err = r.dtc.GetMonitoredEntitiesForKubeSystemUUID(r.kubeSystemUUID)
if err != nil {
return "", fmt.Errorf("error while loading MEs: %s", err.Error())
}

// check if Setting for ME exists
settings, err := r.dtc.GetSettingsForMonitoredEntities(monitoredEntities)
if err != nil {
return "", fmt.Errorf("error trying to check if setting exists %s", err.Error())
}

if settings.TotalCount > 0 {
return "", nil
}

var objectID string
if len(monitoredEntities) > 0 {
// determine newest me
meID := determineNewestMonitoredEntity(monitoredEntities)
objectID, err = r.dtc.CreateKubernetesSetting(r.name, r.kubeSystemUUID, meID)
fabwer marked this conversation as resolved.
Show resolved Hide resolved
} else {
objectID, err = r.dtc.CreateKubernetesSetting(r.name, r.kubeSystemUUID, "")
}

if err != nil {
return "", err
}

return objectID, nil
}

func determineNewestMonitoredEntity(entities []dtclient.MonitoredEntity) string {
var newestMe dtclient.MonitoredEntity
for _, entity := range entities {
if entity.LastSeenTms > newestMe.LastSeenTms {
newestMe = entity
}
}

return newestMe.EntityId
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package automaticapimonitoring

import (
"testing"

"github.com/Dynatrace/dynatrace-operator/src/dtclient"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)

const (
testUID = "test-uid"
testName = "test-name"
testObjectID = "test-objectid"
)

func TestNewDefaultReconiler(t *testing.T) {
createDefaultReconciler(t)
}

func createDefaultReconciler(t *testing.T) *AutomaticApiMonitoringReconciler {
return createReconciler(t, testUID, []dtclient.MonitoredEntity{}, dtclient.GetSettingsResponse{TotalCount: 0}, "")
}

func createReconciler(t *testing.T, uid string, monitoredEntities []dtclient.MonitoredEntity, getSettingsResponse dtclient.GetSettingsResponse, objectID string) *AutomaticApiMonitoringReconciler {
mockClient := &dtclient.MockDynatraceClient{}
mockClient.On("GetMonitoredEntitiesForKubeSystemUUID", mock.AnythingOfType("string")).
Return(monitoredEntities, nil)
mockClient.On("GetSettingsForMonitoredEntities", monitoredEntities).
Return(getSettingsResponse, nil)
mockClient.On("CreateKubernetesSetting", testName, testUID, mock.AnythingOfType("string")).
Return(objectID, nil)

r := NewReconciler(mockClient, testName, uid)
require.NotNil(t, r)
require.NotNil(t, r.dtc)

return r
}

func createReconcilerWithError(t *testing.T, monitoredEntitiesError error, getSettingsResponseError error, createSettingsResponseError error) *AutomaticApiMonitoringReconciler {
mockClient := &dtclient.MockDynatraceClient{}
mockClient.On("GetMonitoredEntitiesForKubeSystemUUID", mock.AnythingOfType("string")).
Return([]dtclient.MonitoredEntity{}, monitoredEntitiesError)
mockClient.On("GetSettingsForMonitoredEntities", []dtclient.MonitoredEntity{}).
Return(dtclient.GetSettingsResponse{}, getSettingsResponseError)
mockClient.On("CreateKubernetesSetting", testName, testUID, mock.AnythingOfType("string")).
Return("", createSettingsResponseError)

r := NewReconciler(mockClient, testName, testUID)
require.NotNil(t, r)
require.NotNil(t, r.dtc)

return r
}

func createMonitoredEntities() []dtclient.MonitoredEntity {
return []dtclient.MonitoredEntity{
{EntityId: "KUBERNETES_CLUSTER-0E30FE4BF2007587", DisplayName: "operator test entity 1", LastSeenTms: 1639483869085},
{EntityId: "KUBERNETES_CLUSTER-119C75CCDA94799F", DisplayName: "operator test entity 2", LastSeenTms: 1639034988126},
}
}

func TestReconcile(t *testing.T) {
t.Run(`reconciler does not fail in with defaults`, func(t *testing.T) {
// arrange
r := createDefaultReconciler(t)

// act
err := r.Reconcile()

// assert
assert.NoError(t, err)
})

t.Run(`create setting when no monitored entities are existing`, func(t *testing.T) {
// arrange
r := createReconciler(t, testUID, []dtclient.MonitoredEntity{}, dtclient.GetSettingsResponse{}, testObjectID)

// act
actual, err := r.ensureSettingExists()

// assert
assert.NoError(t, err)
assert.Equal(t, testObjectID, actual)
})

t.Run(`create setting when no settings for the found monitored entities are existing`, func(t *testing.T) {
// arrange
entities := createMonitoredEntities()
r := createReconciler(t, testUID, entities, dtclient.GetSettingsResponse{}, testObjectID)

// act
actual, err := r.ensureSettingExists()

// assert
assert.NoError(t, err)
assert.Equal(t, testObjectID, actual)
})

t.Run(`don't create setting when settings for the found monitored entities are existing`, func(t *testing.T) {
// arrange
entities := createMonitoredEntities()
r := createReconciler(t, testUID, entities, dtclient.GetSettingsResponse{TotalCount: 1}, testObjectID)

// act
actual, err := r.ensureSettingExists()

// assert
assert.NoError(t, err)
assert.Equal(t, "", actual)
})
}

func TestReconcileErrors(t *testing.T) {
t.Run(`don't create setting when no kube-system uuid is given`, func(t *testing.T) {
// arrange
r := createReconciler(t, "", []dtclient.MonitoredEntity{}, dtclient.GetSettingsResponse{}, testObjectID)

// act
actual, err := r.ensureSettingExists()

// assert
assert.Error(t, err)
assert.Equal(t, "", actual)
})

t.Run(`don't create setting when get entities api response is error`, func(t *testing.T) {
// arrange
r := createReconcilerWithError(t, errors.New("could not get monitored entities"), nil, nil)

// act
actual, err := r.ensureSettingExists()

// assert
assert.Error(t, err)
assert.Equal(t, "", actual)
})

t.Run(`don't create setting when get settings api response is error`, func(t *testing.T) {
// arrange
r := createReconcilerWithError(t, nil, errors.New("could not get settings for monitored entities"), nil)

// act
actual, err := r.ensureSettingExists()

// assert
assert.Error(t, err)
assert.Equal(t, "", actual)
})

t.Run(`don't create setting when create settings api response is error`, func(t *testing.T) {
// arrange
r := createReconcilerWithError(t, nil, nil, errors.New("could not create monitored entity"))

// act
actual, err := r.ensureSettingExists()

// assert
assert.Error(t, err)
assert.Equal(t, "", actual)
})
}

func TestDetermineNewestMonitoredEntity(t *testing.T) {
t.Run(`newest monitored entity is correctly calculated`, func(t *testing.T) {
// arrange
// explicit create of entities here to visualize that one has the newest LastSeenTimestamp
// here it is the first one
entities := []dtclient.MonitoredEntity{
{EntityId: "KUBERNETES_CLUSTER-0E30FE4BF2007587", DisplayName: "operator test entity newest", LastSeenTms: 1639483869085},
{EntityId: "KUBERNETES_CLUSTER-119C75CCDA94799F", DisplayName: "operator test entity 1", LastSeenTms: 1639034988126},
{EntityId: "KUBERNETES_CLUSTER-119C75CCDA947993", DisplayName: "operator test entity 2", LastSeenTms: 1639134988126},
{EntityId: "KUBERNETES_CLUSTER-119C75CCDA94799D", DisplayName: "operator test entity 3", LastSeenTms: 1639234988126},
}

// act
newestEntity := determineNewestMonitoredEntity(entities)

// assert
assert.NotNil(t, newestEntity)
assert.Equal(t, entities[0].EntityId, newestEntity)
})
}
17 changes: 15 additions & 2 deletions src/controllers/dynakube/dynakube_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

dynatracev1beta1 "github.com/Dynatrace/dynatrace-operator/src/api/v1beta1"
"github.com/Dynatrace/dynatrace-operator/src/controllers/activegate/capability"
"github.com/Dynatrace/dynatrace-operator/src/controllers/activegate/reconciler/automaticapimonitoring"
rcap "github.com/Dynatrace/dynatrace-operator/src/controllers/activegate/reconciler/capability"
"github.com/Dynatrace/dynatrace-operator/src/controllers/dynakube/dtpullsecret"
"github.com/Dynatrace/dynatrace-operator/src/controllers/dynakube/dtversion"
Expand Down Expand Up @@ -199,7 +200,7 @@ func (r *ReconcileDynaKube) reconcileDynaKube(ctx context.Context, dkState *stat
dkState.Update(upd, defaultUpdateInterval, "Found updates")
dkState.Error(err)

if !r.reconcileActiveGateCapabilities(dkState) {
if !r.reconcileActiveGateCapabilities(dkState, dtc) {
return
}
if dkState.Instance.HostMonitoringMode() {
Expand Down Expand Up @@ -263,6 +264,7 @@ func (r *ReconcileDynaKube) reconcileDynaKube(ctx context.Context, dkState *stat
return
}
}

}

func (r *ReconcileDynaKube) ensureDeleted(obj client.Object) error {
Expand All @@ -272,7 +274,7 @@ func (r *ReconcileDynaKube) ensureDeleted(obj client.Object) error {
return nil
}

func (r *ReconcileDynaKube) reconcileActiveGateCapabilities(dkState *status.DynakubeState) bool {
func (r *ReconcileDynaKube) reconcileActiveGateCapabilities(dkState *status.DynakubeState, dtc dtclient.Client) bool {
var caps = []capability.Capability{
capability.NewKubeMonCapability(dkState.Instance),
capability.NewRoutingCapability(dkState.Instance),
Expand Down Expand Up @@ -311,6 +313,17 @@ func (r *ReconcileDynaKube) reconcileActiveGateCapabilities(dkState *status.Dyna
}
}

//start automatic config creation
if dkState.Instance.Status.KubeSystemUUID != "" &&
dkState.Instance.FeatureAutomaticKubernetesApiMonitoring() &&
dkState.Instance.KubernetesMonitoringMode() {
err := automaticapimonitoring.NewReconciler(dtc, dkState.Instance.Name, dkState.Instance.Status.KubeSystemUUID).
Reconcile()
if err != nil {
log.Error(err, "could not create setting")
}
}

return true
}

Expand Down
Loading