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

refactor: genericize target manager #122

Merged
merged 4 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all 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 .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ repos:
- id: golangci-lint-repo-mod
args: [--config, .golangci.yaml, --, --fix]
- repo: https://github.com/norwoodj/helm-docs
rev: v1.13.0
rev: v1.13.1
hooks:
- id: helm-docs
args:
Expand Down
3 changes: 3 additions & 0 deletions chart/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,6 @@ A Helm chart to install Sparrow
| startupProbe | object | `{"enabled":false,"failureThreshold":10,"initialDelaySeconds":10,"path":"/","periodSeconds":5,"successThreshold":1,"timeoutSeconds":1}` | Specifies the configuration for a startup probe to check if the sparrow application is started. Ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ |
| tolerations | list | `[]` | |


Autogenerated from chart metadata using [helm-docs v1.13.1](https://github.com/norwoodj/helm-docs/releases/v1.13.1)

2 changes: 1 addition & 1 deletion pkg/sparrow/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func New(cfg *config.Config) *Sparrow {
}

if cfg.HasTargetManager() {
gm := targets.NewGitlabManager(cfg.SparrowName, cfg.TargetManager)
gm := targets.NewManager(cfg.SparrowName, cfg.TargetManager)
sparrow.tarMan = gm
}
sparrow.loader = config.NewLoader(cfg, sparrow.cRuntime)
Expand Down
28 changes: 16 additions & 12 deletions pkg/sparrow/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,17 @@ import (
"testing"
"time"

"github.com/caas-team/sparrow/pkg/sparrow/targets"

"github.com/caas-team/sparrow/pkg/api"
"github.com/caas-team/sparrow/pkg/checks"
"github.com/caas-team/sparrow/pkg/checks/dns"
"github.com/caas-team/sparrow/pkg/checks/health"
"github.com/caas-team/sparrow/pkg/checks/latency"
"github.com/caas-team/sparrow/pkg/checks/runtime"
"github.com/caas-team/sparrow/pkg/config"
gitlabmock "github.com/caas-team/sparrow/pkg/sparrow/targets/test"
"github.com/caas-team/sparrow/pkg/sparrow/targets"
"github.com/caas-team/sparrow/pkg/sparrow/targets/interactor"
"github.com/caas-team/sparrow/pkg/sparrow/targets/remote/gitlab"
managermock "github.com/caas-team/sparrow/pkg/sparrow/targets/test"
"github.com/stretchr/testify/assert"
)

Expand All @@ -43,19 +44,22 @@ func TestSparrow_Run_FullComponentStart(t *testing.T) {
Api: api.Config{ListeningAddress: ":9090"},
Loader: config.LoaderConfig{
Type: "file",
File: config.FileLoaderConfig{Path: "../config/testdata/config.yaml"},
File: config.FileLoaderConfig{Path: "../config/test/data/config.yaml"},
Interval: time.Second * 1,
},
TargetManager: targets.TargetManagerConfig{
Config: targets.Config{
Type: "gitlab",
General: targets.General{
CheckInterval: time.Second * 1,
RegistrationInterval: time.Second * 1,
UnhealthyThreshold: time.Second * 1,
},
Gitlab: targets.GitlabTargetManagerConfig{
BaseURL: "https://gitlab.com",
Token: "my-cool-token",
ProjectID: 42,
Config: interactor.Config{
Gitlab: gitlab.Config{
BaseURL: "https://gitlab.com",
Token: "my-cool-token",
ProjectID: 42,
},
},
},
}
Expand All @@ -80,13 +84,13 @@ func TestSparrow_Run_ContextCancel(t *testing.T) {
Api: api.Config{ListeningAddress: ":9090"},
Loader: config.LoaderConfig{
Type: "file",
File: config.FileLoaderConfig{Path: "../config/testdata/config.yaml"},
File: config.FileLoaderConfig{Path: "../config/test/data/config.yaml"},
Interval: time.Second * 1,
},
}

s := New(c)
s.tarMan = &gitlabmock.MockTargetManager{}
s.tarMan = &managermock.MockTargetManager{}
ctx, cancel := context.WithCancel(context.Background())
go func() {
err := s.Run(ctx)
Expand Down Expand Up @@ -237,7 +241,7 @@ func TestSparrow_enrichTargets(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &Sparrow{
tarMan: &gitlabmock.MockTargetManager{
tarMan: &managermock.MockTargetManager{
Targets: tt.globalTargets,
},
config: &config.Config{
Expand Down
2 changes: 2 additions & 0 deletions pkg/sparrow/targets/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,6 @@ var (
ErrInvalidUnhealthyThreshold = errors.New("invalid unhealthy threshold")
// ErrInvalidUpdateInterval is returned when the update interval is invalid
ErrInvalidUpdateInterval = errors.New("invalid update interval")
// ErrInvalidInteractorType is returned when the interactor type isn't recognized
ErrInvalidInteractorType = errors.New("invalid interactor type")
)
44 changes: 44 additions & 0 deletions pkg/sparrow/targets/interactor/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// sparrow
// (C) 2024, Deutsche Telekom IT GmbH
//
// Deutsche Telekom IT GmbH and all other contributors /
// copyright owners license this file to you 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 interactor

import (
"github.com/caas-team/sparrow/pkg/sparrow/targets/remote"
"github.com/caas-team/sparrow/pkg/sparrow/targets/remote/gitlab"
)

// Config contains the configuration for the remote interactor
type Config struct {
// Gitlab contains the configuration for the gitlab interactor
Gitlab gitlab.Config `yaml:"gitlab" mapstructure:"gitlab"`
}

type Type string

const (
Gitlab Type = "gitlab"
)

func (t Type) Interactor(cfg *Config) remote.Interactor {
switch t { //nolint:gocritic // won't be a single switch case with the implementation of #66
y-eight marked this conversation as resolved.
Show resolved Hide resolved
case Gitlab:
return gitlab.New(cfg.Gitlab)
}
return nil
}
109 changes: 51 additions & 58 deletions pkg/sparrow/targets/gitlab.go → pkg/sparrow/targets/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,56 +25,51 @@ import (
"sync"
"time"

"github.com/caas-team/sparrow/pkg/checks"

"github.com/caas-team/sparrow/pkg/sparrow/gitlab"

"github.com/caas-team/sparrow/internal/logger"
"github.com/caas-team/sparrow/pkg/checks"
"github.com/caas-team/sparrow/pkg/sparrow/targets/remote"
)

var _ TargetManager = &gitlabTargetManager{}
var _ TargetManager = (*manager)(nil)

const shutdownTimeout = 30 * time.Second

// gitlabTargetManager implements TargetManager
type gitlabTargetManager struct {
// manager implements the TargetManager interface
type manager struct {
// targets contains the current global targets
targets []checks.GlobalTarget
mu sync.RWMutex
// channel to signal the "reconcile" routine to stop
// mu is used for mutex locking/unlocking
mu sync.RWMutex
// done is used to signal the reconciliation routine to stop
done chan struct{}
// the DNS name used for self-registration
// name is the DNS name used for self-registration
name string
// whether the instance has already registered itself as a global target
// registered contains whether the instance has already registered itself as a global target
registered bool
cfg Config
gitlab gitlab.Gitlab
}

type GitlabTargetManagerConfig struct {
BaseURL string `yaml:"baseUrl" mapstructure:"baseUrl"`
Token string `yaml:"token" mapstructure:"token"`
ProjectID int `yaml:"projectId" mapstructure:"projectId"`
// cfg contains the general configuration for the target manager
cfg General
// interactor is the remote interactor used to interact with the remote state backend
interactor remote.Interactor
}

// NewGitlabManager creates a new gitlabTargetManager
func NewGitlabManager(name string, gtmConfig TargetManagerConfig) *gitlabTargetManager {
return &gitlabTargetManager{
gitlab: gitlab.New(gtmConfig.Gitlab.BaseURL, gtmConfig.Gitlab.Token, gtmConfig.Gitlab.ProjectID),
name: name,
cfg: gtmConfig.Config,
mu: sync.RWMutex{},
done: make(chan struct{}, 1),
// NewManager creates a new target manager
func NewManager(name string, cfg TargetManagerConfig) TargetManager { //nolint:gocritic // no performance concerns yet
return &manager{
name: name,
cfg: cfg.General,
mu: sync.RWMutex{},
done: make(chan struct{}, 1),
interactor: cfg.Type.Interactor(&cfg.Config),
}
}

// Reconcile reconciles the targets of the gitlabTargetManager.
// The global targets are parsed from a gitlab repository.
// Reconcile reconciles the targets of the target manager.
// The global targets are parsed from a remote state backend.
//
// The global targets are evaluated for healthiness and
// unhealthy gitlabTargetManager are removed.
func (t *gitlabTargetManager) Reconcile(ctx context.Context) error {
// The global targets are evaluated for their healthiness
// and unhealthy targets are filtered out.
func (t *manager) Reconcile(ctx context.Context) error {
log := logger.FromContext(ctx)
log.Info("Starting global gitlabTargetManager reconciler")

checkTimer := startTimer(t.cfg.CheckInterval)
registrationTimer := startTimer(t.cfg.RegistrationInterval)
Expand All @@ -84,13 +79,14 @@ func (t *gitlabTargetManager) Reconcile(ctx context.Context) error {
defer registrationTimer.Stop()
defer updateTimer.Stop()

log.Info("Starting target manager reconciliation")
for {
select {
case <-ctx.Done():
log.Error("Error while reconciling gitlab targets", "err", ctx.Err())
log.Error("Error while reconciling targets", "err", ctx.Err())
return ctx.Err()
case <-t.done:
log.Info("Gitlab target reconciliation ended")
log.Info("Target manager reconciliation stopped")
return nil
case <-checkTimer.C:
err := t.refreshTargets(ctx)
Expand All @@ -114,16 +110,15 @@ func (t *gitlabTargetManager) Reconcile(ctx context.Context) error {
}
}

// GetTargets returns the current targets of the gitlabTargetManager
func (t *gitlabTargetManager) GetTargets() []checks.GlobalTarget {
// GetTargets returns the current global targets
func (t *manager) GetTargets() []checks.GlobalTarget {
t.mu.RLock()
defer t.mu.RUnlock()
return t.targets
}

// Shutdown shuts down the gitlabTargetManager and deletes the file containing
// the sparrow's registration from Gitlab
func (t *gitlabTargetManager) Shutdown(ctx context.Context) error {
// Shutdown shuts down the target manager
func (t *manager) Shutdown(ctx context.Context) error {
t.mu.Lock()
defer t.mu.Unlock()

Expand All @@ -134,14 +129,14 @@ func (t *gitlabTargetManager) Shutdown(ctx context.Context) error {
defer cancel()

if t.registered {
f := gitlab.File{
f := remote.File{
Branch: "main",
AuthorEmail: fmt.Sprintf("%s@sparrow", t.name),
AuthorName: t.name,
CommitMessage: "Unregistering global target",
}
f.SetFileName(fmt.Sprintf("%s.json", t.name))
err := t.gitlab.DeleteFile(ctxS, f)
err := t.interactor.DeleteFile(ctxS, f)
if err != nil {
log.Error("Failed to shutdown gracefully", "error", err)
return fmt.Errorf("failed to shutdown gracefully: %w", errors.Join(errC, err))
Expand All @@ -159,9 +154,9 @@ func (t *gitlabTargetManager) Shutdown(ctx context.Context) error {
}

// register registers the current instance as a global target
// in the gitlab repository
func (t *gitlabTargetManager) register(ctx context.Context) error {
func (t *manager) register(ctx context.Context) error {
log := logger.FromContext(ctx)

t.mu.Lock()
defer t.mu.Unlock()

Expand All @@ -170,8 +165,7 @@ func (t *gitlabTargetManager) register(ctx context.Context) error {
return nil
}

log.Debug("Registering as global target")
f := gitlab.File{
f := remote.File{
Branch: "main",
AuthorEmail: fmt.Sprintf("%s@sparrow", t.name),
AuthorName: t.name,
Expand All @@ -180,22 +174,21 @@ func (t *gitlabTargetManager) register(ctx context.Context) error {
}
f.SetFileName(fmt.Sprintf("%s.json", t.name))

err := t.gitlab.PostFile(ctx, f)
log.Debug("Registering as global target")
err := t.interactor.PostFile(ctx, f)
if err != nil {
log.Error("Failed to register global gitlabTargetManager", "error", err)
return err
}

log.Info("Successfully registered instance as global target")
log.Debug("Successfully registered")
t.registered = true

return nil
}

// update updates the registration file of the current sparrow instance
// in the gitlab repository
func (t *gitlabTargetManager) update(ctx context.Context) error {
func (t *manager) update(ctx context.Context) error {
log := logger.FromContext(ctx)
log.Debug("Updating registration")

t.mu.Lock()
defer t.mu.Unlock()
Expand All @@ -204,7 +197,7 @@ func (t *gitlabTargetManager) update(ctx context.Context) error {
return nil
}

f := gitlab.File{
f := remote.File{
Branch: "main",
AuthorEmail: fmt.Sprintf("%s@sparrow", t.name),
AuthorName: t.name,
Expand All @@ -213,7 +206,8 @@ func (t *gitlabTargetManager) update(ctx context.Context) error {
}
f.SetFileName(fmt.Sprintf("%s.json", t.name))

err := t.gitlab.PutFile(ctx, f)
log.Debug("Updating instance registration")
err := t.interactor.PutFile(ctx, f)
if err != nil {
log.Error("Failed to update registration", "error", err)
return err
Expand All @@ -222,14 +216,13 @@ func (t *gitlabTargetManager) update(ctx context.Context) error {
return nil
}

// refreshTargets updates the targets of the gitlabTargetManager
// with the latest available healthy targets
func (t *gitlabTargetManager) refreshTargets(ctx context.Context) error {
// refreshTargets updates the targets with the latest available healthy targets
func (t *manager) refreshTargets(ctx context.Context) error {
log := logger.FromContext(ctx)
t.mu.Lock()
defer t.mu.Unlock()
var healthyTargets []checks.GlobalTarget
targets, err := t.gitlab.FetchFiles(ctx)
targets, err := t.interactor.FetchFiles(ctx)
if err != nil {
log.Error("Failed to update global targets", "error", err)
return err
Expand Down
Loading
Loading