Skip to content

Commit

Permalink
Merge pull request #1 from Dynatrace/APM-236993/update-activegate-ver…
Browse files Browse the repository at this point in the history
…sion

Automatically update ActiveGate pod
  • Loading branch information
meik99 committed Jun 17, 2020
2 parents e2f14ad + 4a02226 commit 9178e58
Show file tree
Hide file tree
Showing 27 changed files with 772 additions and 118 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,4 @@ tags
.history
# End of https://www.gitignore.io/api/go,vim,emacs,visualstudiocode
/.idea/
/debug.sh
6 changes: 3 additions & 3 deletions deploy/crds/dynatrace.com_v1alpha1_activegate_cr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ spec:
apiUrl: https://asj34817.dev.dynatracelabs.com/api
# name of secret holding `apiToken` and `paasToken`
# if unset, name of custom resource is used
tokens: ""
# tokens: ""
# node selector to control the selection of nodes (optional)
nodeSelector: {}
# nodeSelector: {}
# https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ (optional)
tolerations:
- effect: NoSchedule
Expand All @@ -20,7 +20,7 @@ spec:
# ActiveGate installer image (optional)
# certified image from Red Hat Container Catalog for use on OpenShift: registry.connect.redhat.com/dynatrace/activegate
# for kubernetes it defaults to docker.io/dynatrace/activegate
image: "612044533526.dkr.ecr.us-east-1.amazonaws.com/activegate:latest"
# image: ""
dt_capabilities:
- kubernetes_monitoring
# resource settings for ActiveGate pods (optional)
Expand Down
11 changes: 11 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,23 @@ module github.com/Dynatrace/dynatrace-activegate-operator
go 1.13

require (
github.com/cosiner/argv v0.1.0 // indirect
github.com/go-delve/delve v1.4.1 // indirect
github.com/go-logr/logr v0.1.0
github.com/mattn/go-colorable v0.1.6 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/operator-framework/operator-sdk v0.17.1-0.20200527074332-363f7b9d2be9
github.com/peterh/liner v1.2.0 // indirect
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.5.1
go.starlark.net v0.0.0-20200609215844-cd131d1ce9d4 // indirect
golang.org/x/arch v0.0.0-20200511175325-f7c78586839d // indirect
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 // indirect
gopkg.in/yaml.v2 v2.3.0 // indirect
k8s.io/api v0.18.2
k8s.io/apimachinery v0.18.2
k8s.io/client-go v12.0.0+incompatible
k8s.io/kubectl v0.18.2
sigs.k8s.io/controller-runtime v0.6.0
)

Expand Down
57 changes: 57 additions & 0 deletions go.sum

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions pkg/apis/dynatrace/v1alpha1/activegate_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ type ActiveGateSpec struct {
// +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors.displayName="ActiveGate Capabilities"
// +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors.x-descriptors="urn:alm:descriptor:com.tectonic.ui:text"
Capabilities []string `json:"dt_capabilities"`

NetworkZone string `json:"networkZone,omitempty"`

// Disable automatic restarts of Activegate pods in case a new version is available
// +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true
// +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors.displayName="Disable Activegate update"
// +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors.x-descriptors="urn:alm:descriptor:com.tectonic.ui:advanced,urn:alm:descriptor:com.tectonic.ui:booleanSwitch"
DisableActivegateUpdate bool `json:"disableActivegateUpdate,omitempty"`
}

// ActiveGateStatus defines the observed state of ActiveGate
Expand Down Expand Up @@ -53,6 +61,12 @@ type ActiveGateStatus struct {

type ActiveGatePhaseType string

type ActiveGateInstance struct {
PodName string `json:"podName,omitempty"`
Version string `json:"version,omitempty"`
IPAddress string `json:"ipAddress,omitempty"`
}

const (
Running ActiveGatePhaseType = "Running"
Deploying ActiveGatePhaseType = "Deploying"
Expand Down
16 changes: 16 additions & 0 deletions pkg/apis/dynatrace/v1alpha1/zz_generated.deepcopy.go

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

99 changes: 68 additions & 31 deletions pkg/controller/activegate/activegate_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,25 @@ package activegate

import (
"context"
"fmt"
dynatracev1alpha1 "github.com/Dynatrace/dynatrace-activegate-operator/pkg/apis/dynatrace/v1alpha1"
"github.com/Dynatrace/dynatrace-activegate-operator/pkg/controller/builder"
agerrors "github.com/Dynatrace/dynatrace-activegate-operator/pkg/controller/errors"
parser "github.com/Dynatrace/dynatrace-activegate-operator/pkg/controller/parser"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

dynatracev1alpha1 "github.com/Dynatrace/dynatrace-activegate-operator/pkg/apis/dynatrace/v1alpha1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/handler"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"
"time"
)

var log = logf.Log.WithName("controller_activegate")
Expand Down Expand Up @@ -97,25 +99,17 @@ func (r *ReconcileActiveGate) Reconcile(request reconcile.Request) (reconcile.Re
return reconcile.Result{}, err
}

namespace := instance.GetNamespace()
secret := &corev1.Secret{}
err = r.client.Get(context.TODO(), client.ObjectKey{Name: parser.GetTokensName(instance), Namespace: namespace}, secret)
if err != nil {
log.Error(err, err.Error())
if errors.IsNotFound(err) {
// Request object not found, could have been deleted after reconcile request.
// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
// Return and don't requeue
return reconcile.Result{}, nil
}
// Error reading the object - requeue the request.
return reconcile.Result{}, err
// Fetch api token secret
secret, err := r.getTokenSecret(instance)
if err != nil || secret == nil {
return agerrors.HandleSecretError(secret, err, reqLogger)
}
log.Info("Creating new pod for custom resource")

// Define a new Pod object
log.Info("creating new pod definition from custom resource")
pod := newPodForCR(r.client, instance, secret)
//
//// Set ActiveGate instance as the owner and controller

// Set ActiveGate instance as the owner and controller
if err := controllerutil.SetControllerReference(instance, pod, r.scheme); err != nil {
return reconcile.Result{}, err
}
Expand All @@ -124,21 +118,58 @@ func (r *ReconcileActiveGate) Reconcile(request reconcile.Request) (reconcile.Re
found := &corev1.Pod{}
err = r.client.Get(context.TODO(), types.NamespacedName{Name: pod.Name, Namespace: pod.Namespace}, found)
if err != nil && errors.IsNotFound(err) {
reqLogger.Info("Creating a new Pod", "Pod.Namespace", pod.Namespace, "Pod.Name", pod.Name)
err = r.client.Create(context.TODO(), pod)
if err != nil {
return reconcile.Result{}, err
}

// Pod created successfully - don't requeue
return reconcile.Result{}, nil
return r.createNewPod(pod, instance, secret)
} else if err != nil {
return reconcile.Result{}, err
}

// Pod already exists - don't requeue
reqLogger.Info("Skip reconcile: Pod already exists", "Pod.Namespace", found.Namespace, "Pod.Name", found.Name)
return reconcile.Result{}, nil
reconcileResult, err := r.updatePods(found, instance, secret)
if err != nil {
log.Error(err, "could not update pods")
}
if reconcileResult != nil {
return *reconcileResult, err
}

//Set version and last updated timestamp
// Nothing to do - requeue after five minutes
reqLogger.Info("Nothing to do: Pod already exists", "Pod.Namespace", found.Namespace, "Pod.Name", found.Name)
return builder.ReconcileAfterFiveMinutes(), nil
}

func (r *ReconcileActiveGate) getTokenSecret(instance *dynatracev1alpha1.ActiveGate) (*corev1.Secret, error) {
namespace := instance.GetNamespace()
secret := &corev1.Secret{}
err := r.client.Get(context.TODO(), client.ObjectKey{Name: parser.GetTokensName(instance), Namespace: namespace}, secret)
if err != nil {
log.Error(err, err.Error())
if errors.IsNotFound(err) {
return nil, nil
}
return nil, err
}
return secret, nil
}

func (r *ReconcileActiveGate) updateInstanceStatus(pod *corev1.Pod, instance *dynatracev1alpha1.ActiveGate, secret *corev1.Secret) {
dtc, err := builder.BuildDynatraceClient(r.client, instance, secret)
if err != nil {
log.Error(err, err.Error())
}

query := builder.BuildActiveGateQuery(instance, pod)
activegates, err := dtc.QueryActiveGates(query)
if len(activegates) > 0 {
log.Info(fmt.Sprintf("found %d activegate(s)", len(activegates)))
log.Info("setting activegate version", "version", activegates[0].Version)
instance.Status.Version = activegates[0].Version
instance.Status.UpdatedTimestamp = metav1.Now()
err := r.client.Status().Update(context.TODO(), instance)
if err != nil {
log.Error(err, "failed to updated instance status")
}
}

}

// newPodForCR returns a busybox pod with the same name/namespace as the cr
Expand All @@ -162,3 +193,9 @@ func newPodForCR(client client.Client, cr *dynatracev1alpha1.ActiveGate, secret
Spec: builder.BuildActiveGatePodSpecs(&cr.Spec, tenantInfo),
}
}

const (
TimeUntilActive = 10 * time.Second
//UpdateInterval = 5 * time.Minute
UpdateInterval = 1 * time.Second
)
37 changes: 27 additions & 10 deletions pkg/controller/activegate/activegate_controller_test.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,36 @@
package activegate

import (
"github.com/Dynatrace/dynatrace-activegate-operator/pkg/apis"
"github.com/operator-framework/operator-sdk/pkg/k8sutil"
"k8s.io/client-go/kubernetes/scheme"
"os"
_const "github.com/Dynatrace/dynatrace-activegate-operator/pkg/controller/const"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubectl/pkg/scheme"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"testing"
)

func TestMain(m *testing.M) {
if err := apis.AddToScheme(scheme.Scheme); err != nil {
log.Error(err, err.Error())
func TestUpdatePods(t *testing.T) {
fakeClient := fake.NewFakeClientWithScheme(
scheme.Scheme,
NewSecret("activegate", "dynatrace", map[string]string{_const.DynatraceApiToken: "42", _const.DynatracePaasToken: "84"}),
)
r := ReconcileActiveGate{
client: fakeClient,
}
if err := os.Setenv(k8sutil.WatchNamespaceEnvVar, "dynatrace"); err != nil {
log.Error(err, err.Error())
request := reconcile.Request{}

reconciliation, err := r.Reconcile(request)

assert.NotNil(t, reconciliation)
assert.Nil(t, err)
}

func NewSecret(name, namespace string, kv map[string]string) *corev1.Secret {
data := make(map[string][]byte)
for k, v := range kv {
data[k] = []byte(v)
}
m.Run()
return &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}, Data: data}
}
22 changes: 22 additions & 0 deletions pkg/controller/activegate/activegate_create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package activegate

import (
"context"
dynatracev1alpha1 "github.com/Dynatrace/dynatrace-activegate-operator/pkg/apis/dynatrace/v1alpha1"
"github.com/Dynatrace/dynatrace-activegate-operator/pkg/controller/builder"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"time"
)

func (r *ReconcileActiveGate) createNewPod(pod *corev1.Pod, instance *dynatracev1alpha1.ActiveGate, secret *corev1.Secret) (reconcile.Result, error) {
log.Info("creating new Pod", "Pod.Namespace", pod.Namespace, "Pod.Name", pod.Name)
err := r.client.Create(context.TODO(), pod)
if err != nil {
return reconcile.Result{}, err
}
// Sleep until pod is ready
time.Sleep(TimeUntilActive)

return builder.ReconcileAfterFiveMinutes(), nil
}
20 changes: 20 additions & 0 deletions pkg/controller/activegate/activegate_delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package activegate

import (
"context"
"github.com/go-logr/logr"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
)

func (r *ReconcileActiveGate) deletePods(logger logr.Logger, pods []v1.Pod) error {
for _, pod := range pods {
logger.Info("deleting outdated pod", "pod", pod.Name, "node", pod.Spec.NodeName)
err := r.client.Delete(context.TODO(), &pod)
if err != nil && !errors.IsNotFound(err) {
// Not an error if pod is already gone
return err
}
}
return nil
}
Loading

0 comments on commit 9178e58

Please sign in to comment.