Skip to content

Commit

Permalink
feat/tm-config-update (#104)
Browse files Browse the repository at this point in the history
* chore: refactor added cfg internal struct

Signed-off-by: Bruno Bressi <[email protected]>

* chore: added errors & validation

Signed-off-by: Bruno Bressi <[email protected]>

* refactor: intoduce general TM config

Signed-off-by: Bruno Bressi <[email protected]>

* chore: validate only general config

Signed-off-by: Bruno Bressi <[email protected]>

* feat: added update interval to cfg

Signed-off-by: Bruno Bressi <[email protected]>

* feat: added option to disable update & registration

Signed-off-by: Bruno Bressi <[email protected]>

* docs: updated readme & helm values

Signed-off-by: Bruno Bressi <[email protected]>

* feat: deactivation of unhealthy threshold available

Signed-off-by: Bruno Bressi <[email protected]>

* tests: added lock to eliminate race conditions

Signed-off-by: Bruno Bressi <[email protected]>

* chore: added licence

Signed-off-by: Bruno Bressi <[email protected]>

---------

Signed-off-by: Bruno Bressi <[email protected]>
  • Loading branch information
puffitos authored Feb 13, 2024
1 parent b33a39f commit c5b9ece
Show file tree
Hide file tree
Showing 8 changed files with 366 additions and 58 deletions.
34 changes: 20 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,13 +223,18 @@ api:
# Configures the target manager
targetManager:
# time between checking for new targets
# The interval for the target reconciliation process
checkInterval: 1m
# how often the instance should register itself as a global target
# How often the instance should register itself as a global target.
# A duration of 0 means no registration.
registrationInterval: 1m
# the amount of time a target can be
# unhealthy before it is removed from the global target list
unhealthyThreshold: 3m
# How often the instance should update its registration as a global target.
# A duration of 0 means no update.
updateInterval: 120m
# The amount of time a target can be unhealthy
# before it is removed from the global target list.
# A duration of 0 means no removal.
unhealthyThreshold: 360m
# Configuration options for the gitlab target manager
gitlab:
# The url of your gitlab host
Expand Down Expand Up @@ -281,17 +286,18 @@ the `TargetManager` interface. This feature is optional; if the startup configur
the `targetManager`, it will not be used. When configured, it offers various settings, detailed below, which can be set
in the startup YAML configuration file as shown in the [example configuration](#example-startup-configuration).

| Type | Description | Default Value |
|--------------------------------------|---------------------------------------------------------------------|----------------------|
| `targetManager.checkInterval` | Interval for checking new targets. | `300s` |
| `targetManager.unhealthyThreshold` | Threshold for marking a target as unhealthy. | `600s` |
| `targetManager.registrationInterval` | Interval for registering the current sparrow at the target backend. | `300s` |
| `targetManager.gitlab.token` | Token for authenticating with the GitLab instance. | `""` |
| `targetManager.gitlab.baseUrl` | Base URL of the GitLab instance. | `https://gitlab.com` |
| `targetManager.gitlab.projectId` | Project ID for the GitLab project used as a remote state backend. | `""` |
| Type | Description |
|--------------------------------------|----------------------------------------------------------------------------------------------|
| `targetManager.checkInterval` | Interval for checking new targets. |
| `targetManager.unhealthyThreshold` | Threshold for marking a target as unhealthy. 0 means no cleanup. |
| `targetManager.registrationInterval` | Interval for registering the current sparrow at the target backend. 0 means no registration. |
| `targetManager.updateInterval` | Interval for updating the registration of the current sparrow. 0 means no update. |
| `targetManager.gitlab.token` | Token for authenticating with the GitLab instance. |
| `targetManager.gitlab.baseUrl` | Base URL of the GitLab instance. |
| `targetManager.gitlab.projectId` | Project ID for the GitLab project used as a remote state backend. |

Currently, only one target manager exists: the Gitlab target manager. It uses a gitlab project as the remote state
backend. The various `sparrow` instances will register themselves as targets in the project.
backend. The various `sparrow` instances can register themselves as targets in the project.
The `sparrow` instances will also check the project for new targets and add them to the local state.
The registration is done by committing a "state" file in the main branch of the repository,
which is named after the DNS name of the `sparrow`. The state file contains the following information:
Expand Down
1 change: 1 addition & 0 deletions chart/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ sparrowConfig:
# checkInterval: 300s
# unhealthyThreshold: 600s
# registrationInterval: 300s
# updateInterval: 900s
# gitlab:
# token: ""
# baseUrl: https://gitlab.com
Expand Down
29 changes: 24 additions & 5 deletions pkg/sparrow/gitlab/test/mockclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package gitlabmock

import (
"context"
"sync"

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

Expand All @@ -28,22 +29,31 @@ import (
)

type MockClient struct {
targets []checks.GlobalTarget
fetchFilesErr error
putFileErr error
postFileErr error
deleteFileErr error
targets []checks.GlobalTarget
mu sync.Mutex
fetchFilesErr error
putFileErr error
postFileErr error
deleteFileErr error
putFileCalled bool
postFileCalled bool
}

func (m *MockClient) PutFile(ctx context.Context, _ gitlab.File) error { //nolint: gocritic // irrelevant
log := logger.FromContext(ctx)
log.Info("MockPutFile called", "err", m.putFileErr)
m.mu.Lock()
m.putFileCalled = true
m.mu.Unlock()
return m.putFileErr
}

func (m *MockClient) PostFile(ctx context.Context, _ gitlab.File) error { //nolint: gocritic // irrelevant
log := logger.FromContext(ctx)
log.Info("MockPostFile called", "err", m.postFileErr)
m.mu.Lock()
m.postFileCalled = true
m.mu.Unlock()
return m.postFileErr
}

Expand Down Expand Up @@ -79,6 +89,15 @@ func (m *MockClient) SetDeleteFileErr(err error) {
m.deleteFileErr = err
}

// PutFileCalled returns true if PutFile was called
func (m *MockClient) PutFileCalled() bool {
return m.putFileCalled
}

func (m *MockClient) PostFileCalled() bool {
return m.postFileCalled
}

// New creates a new MockClient to mock Gitlab interaction
func New(targets []checks.GlobalTarget) *MockClient {
return &MockClient{
Expand Down
3 changes: 3 additions & 0 deletions pkg/sparrow/targets/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,6 @@ var ErrInvalidRegistrationInterval = errors.New("invalid registration interval")

// ErrInvalidUnhealthyThreshold is returned when the unhealthy threshold is invalid
var ErrInvalidUnhealthyThreshold = errors.New("invalid unhealthy threshold")

// ErrInvalidUpdateInterval is returned when the update interval is invalid
var ErrInvalidUpdateInterval = errors.New("invalid update interval")
72 changes: 58 additions & 14 deletions pkg/sparrow/targets/gitlab.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,13 @@ func (t *gitlabTargetManager) Reconcile(ctx context.Context) error {
log := logger.FromContext(ctx)
log.Info("Starting global gitlabTargetManager reconciler")

checkTimer := time.NewTimer(t.cfg.CheckInterval)
registrationTimer := time.NewTimer(t.cfg.RegistrationInterval)
checkTimer := startTimer(t.cfg.CheckInterval)
registrationTimer := startTimer(t.cfg.RegistrationInterval)
updateTimer := startTimer(t.cfg.UpdateInterval)

defer checkTimer.Stop()
defer registrationTimer.Stop()
defer updateTimer.Stop()

for {
select {
Expand All @@ -97,11 +99,17 @@ func (t *gitlabTargetManager) Reconcile(ctx context.Context) error {
}
checkTimer.Reset(t.cfg.CheckInterval)
case <-registrationTimer.C:
err := t.updateRegistration(ctx)
err := t.register(ctx)
if err != nil {
log.Warn("Failed to register self as global target", "error", err)
}
registrationTimer.Reset(t.cfg.RegistrationInterval)
case <-updateTimer.C:
err := t.update(ctx)
if err != nil {
log.Warn("Failed to update registration", "error", err)
}
updateTimer.Reset(t.cfg.UpdateInterval)
}
}
}
Expand Down Expand Up @@ -150,8 +158,37 @@ func (t *gitlabTargetManager) Shutdown(ctx context.Context) error {
return nil
}

// updateRegistration registers the current instance as a global target
func (t *gitlabTargetManager) updateRegistration(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 {
log := logger.FromContext(ctx)
log.Debug("Registering as global target")

t.mu.Lock()
defer t.mu.Unlock()
f := gitlab.File{
Branch: "main",
AuthorEmail: fmt.Sprintf("%s@sparrow", t.name),
AuthorName: t.name,
CommitMessage: "Initial registration",
Content: checks.GlobalTarget{Url: fmt.Sprintf("https://%s", t.name), LastSeen: time.Now().UTC()},
}
f.SetFileName(fmt.Sprintf("%s.json", t.name))

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

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 {
log := logger.FromContext(ctx)
log.Debug("Updating registration")

Expand All @@ -176,15 +213,6 @@ func (t *gitlabTargetManager) updateRegistration(ctx context.Context) error {
return nil
}

f.CommitMessage = "Initial registration"
err := t.gitlab.PostFile(ctx, f)
if err != nil {
log.Error("Failed to register global gitlabTargetManager", "error", err)
return err
}

log.Debug("Successfully registered")
t.registered = true
return nil
}

Expand All @@ -207,6 +235,12 @@ func (t *gitlabTargetManager) refreshTargets(ctx context.Context) error {
log.Debug("Found self as global target", "lastSeenMin", time.Since(target.LastSeen).Minutes())
t.registered = true
}

if t.cfg.UnhealthyThreshold == 0 {
healthyTargets = append(healthyTargets, target)
continue
}

if time.Now().Add(-t.cfg.UnhealthyThreshold).After(target.LastSeen) {
log.Debug("Skipping unhealthy target", "target", target)
continue
Expand All @@ -223,3 +257,13 @@ func (t *gitlabTargetManager) refreshTargets(ctx context.Context) error {
func (t *gitlabTargetManager) Registered() bool {
return t.registered
}

// startTimer creates a new timer with the given duration.
// If the duration is 0, the timer is stopped.
func startTimer(d time.Duration) *time.Timer {
res := time.NewTimer(d)
if d == 0 {
res.Stop()
}
return res
}
Loading

0 comments on commit c5b9ece

Please sign in to comment.