From 3c75afcde2db2cda7e2943ffb74dc0140ba2c652 Mon Sep 17 00:00:00 2001 From: Ekaterina Kazakova Date: Thu, 15 Aug 2024 14:32:19 +0400 Subject: [PATCH] Track deployment status Closes #138 --- cmd/main.go | 8 +++ internal/telemetry/event.go | 15 ++++- internal/telemetry/tracker.go | 100 ++++++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 internal/telemetry/tracker.go diff --git a/cmd/main.go b/cmd/main.go index 1f902b5c4..abe912285 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -36,6 +36,7 @@ import ( hmcmirantiscomv1alpha1 "github.com/Mirantis/hmc/api/v1alpha1" "github.com/Mirantis/hmc/internal/controller" + "github.com/Mirantis/hmc/internal/telemetry" hmcwebhook "github.com/Mirantis/hmc/internal/webhook" //+kubebuilder:scaffold:imports ) @@ -186,6 +187,13 @@ func main() { os.Exit(1) } + if err = mgr.Add(&telemetry.Tracker{ + Client: mgr.GetClient(), + }); err != nil { + setupLog.Error(err, "unable to create telemetry tracker") + os.Exit(1) + } + //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/internal/telemetry/event.go b/internal/telemetry/event.go index adee9bf91..edc218ace 100644 --- a/internal/telemetry/event.go +++ b/internal/telemetry/event.go @@ -21,7 +21,8 @@ import ( ) const ( - deploymentCreateEvent = "deployment-create" + deploymentCreateEvent = "deployment-create" + deploymentHeartbeatEvent = "deployment-heartbeat" ) func TrackDeploymentCreate(id, deploymentID, template string, dryRun bool) error { @@ -34,6 +35,18 @@ func TrackDeploymentCreate(id, deploymentID, template string, dryRun bool) error return TrackEvent(deploymentCreateEvent, id, props) } +func TrackDeploymentHeartbeat(id, deploymentID, clusterID, template, templateVersion, targetInfrastructure string) error { + props := map[string]interface{}{ + "hmcVersion": build.Version, + "deploymentID": deploymentID, + "clusterID": clusterID, + "targetInfrastructure": targetInfrastructure, + "template": template, + "templateVersion": templateVersion, + } + return TrackEvent(deploymentHeartbeatEvent, id, props) +} + func TrackEvent(name, id string, properties map[string]interface{}) error { if client == nil { return nil diff --git a/internal/telemetry/tracker.go b/internal/telemetry/tracker.go new file mode 100644 index 000000000..059433755 --- /dev/null +++ b/internal/telemetry/tracker.go @@ -0,0 +1,100 @@ +// Copyright 2024 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package telemetry + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + "k8s.io/apimachinery/pkg/types" + crclient "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + "github.com/Mirantis/hmc/api/v1alpha1" +) + +type Tracker struct { + crclient.Client +} + +const interval = 10 * time.Minute + +func (t *Tracker) Start(ctx context.Context) error { + timer := time.NewTimer(0) + for { + select { + case <-timer.C: + t.Tick(ctx) + timer.Reset(interval) + case <-ctx.Done(): + return nil + } + } +} + +func (t *Tracker) Tick(ctx context.Context) { + l := log.FromContext(ctx).WithName("telemetry tracker") + + logger := l.WithValues("event", deploymentHeartbeatEvent) + err := t.trackDeploymentHeartbeat(ctx) + if err != nil { + logger.Error(err, "failed to track an event") + } else { + logger.Info("successfully tracked an event") + } +} + +func (t *Tracker) trackDeploymentHeartbeat(ctx context.Context) error { + mgmt := &v1alpha1.Management{} + mgmtRef := types.NamespacedName{Namespace: v1alpha1.ManagementNamespace, Name: v1alpha1.ManagementName} + err := t.Get(ctx, mgmtRef, mgmt) + if err != nil { + return err + } + + templates := make(map[string]v1alpha1.Template) + templatesList := &v1alpha1.TemplateList{} + err = t.List(ctx, templatesList, crclient.InNamespace(v1alpha1.TemplatesNamespace)) + if err != nil { + return err + } + for _, template := range templatesList.Items { + templates[template.Name] = template + } + + var errs error + deployments := &v1alpha1.DeploymentList{} + err = t.List(ctx, deployments, &crclient.ListOptions{}) + if err != nil { + return err + } + + for _, deployment := range deployments.Items { + template := templates[deployment.Spec.Template] + targetInfrastructure := strings.Join(template.Status.Providers.InfrastructureProviders, ",") + templateVersion := template.Spec.Helm.ChartVersion + // TODO: get k0s cluster ID once it's exposed in k0smotron API + clusterID := "" + err = TrackDeploymentHeartbeat(string(mgmt.UID), string(deployment.UID), clusterID, deployment.Spec.Template, templateVersion, targetInfrastructure) + if err != nil { + errs = errors.Join(errs, fmt.Errorf("failed to track the heartbeat of the deployment %s/%s", deployment.Namespace, deployment.Name)) + continue + } + } + return errs +}