Skip to content

Commit

Permalink
refactor: genericize target manager (#122)
Browse files Browse the repository at this point in the history
* refactor: genericize target manager

* refactor: generalize Gitlab interface to remote interactor interface
* refactor: mv gitlab config to interactor config
* chore: adjust imports
* feat: add interactor type
* feat: adjust target manager validation for interactor type
* chore: adjust naming
* chore: bump helm-docs v1.13.1

* chore: add interactor type constant
  • Loading branch information
lvlcn-t authored Mar 14, 2024
1 parent 55f4d34 commit d786d77
Show file tree
Hide file tree
Showing 15 changed files with 398 additions and 281 deletions.
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
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

0 comments on commit d786d77

Please sign in to comment.