Skip to content

Commit 623eae7

Browse files
Merge pull request #395 from pace/failover-custom-setter
failover: Allow custom state setter
2 parents 739564e + 4dd2044 commit 623eae7

File tree

2 files changed

+108
-13
lines changed

2 files changed

+108
-13
lines changed

maintenance/failover/failover.go

+55-13
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"time"
1212

1313
"github.com/bsm/redislock"
14-
"github.com/pace/bricks/backend/k8sapi"
1514
"github.com/pace/bricks/maintenance/errors"
1615
"github.com/pace/bricks/maintenance/health"
1716
"github.com/pace/bricks/maintenance/log"
@@ -26,8 +25,6 @@ const (
2625
ACTIVE status = 1
2726
)
2827

29-
const Label = "github.com.pace.bricks.activepassive"
30-
3128
// ActivePassive implements a failover mechanism that allows
3229
// to deploy a service multiple times but ony one will accept
3330
// traffic by using the label selector of kubernetes.
@@ -51,31 +48,76 @@ type ActivePassive struct {
5148
timeToFailover time.Duration
5249
locker *redislock.Client
5350

54-
// access to the kubernetes api
55-
k8sClient *k8sapi.Client
51+
stateSetter StateSetter
5652

5753
// current status of the failover (to show it in the readiness status)
5854
state status
5955
stateMu sync.RWMutex
6056
}
6157

58+
type ActivePassiveOption func(*ActivePassive) error
59+
60+
func WithCustomStateSetter(fn func(ctx context.Context, state string) error) ActivePassiveOption {
61+
return func(ap *ActivePassive) error {
62+
stateSetter, err := NewCustomStateSetter(fn)
63+
if err != nil {
64+
return fmt.Errorf("failed to create state setter: %w", err)
65+
}
66+
67+
ap.stateSetter = stateSetter
68+
69+
return nil
70+
}
71+
}
72+
73+
func WithNoopStateSetter() ActivePassiveOption {
74+
return func(ap *ActivePassive) error {
75+
ap.stateSetter = &NoopStateSetter{}
76+
77+
return nil
78+
}
79+
}
80+
81+
func WithPodStateSetter() ActivePassiveOption {
82+
return func(ap *ActivePassive) error {
83+
stateSetter, err := NewPodStateSetter()
84+
if err != nil {
85+
return fmt.Errorf("failed to create pod state setter: %w", err)
86+
}
87+
88+
ap.stateSetter = stateSetter
89+
90+
return nil
91+
}
92+
}
93+
6294
// NewActivePassive creates a new active passive cluster
6395
// identified by the name. The time to fail over determines
6496
// the frequency of checks performed against redis to
6597
// keep the active state.
6698
// NOTE: creating multiple ActivePassive in one process
6799
// is not working correctly as there is only one readiness probe.
68-
func NewActivePassive(clusterName string, timeToFailover time.Duration, client *redis.Client) (*ActivePassive, error) {
69-
k8sClient, err := k8sapi.NewClient()
70-
if err != nil {
71-
return nil, err
72-
}
73-
100+
func NewActivePassive(clusterName string, timeToFailover time.Duration, client *redis.Client, opts ...ActivePassiveOption) (*ActivePassive, error) {
74101
activePassive := &ActivePassive{
75102
clusterName: clusterName,
76103
timeToFailover: timeToFailover,
77104
locker: redislock.New(client),
78-
k8sClient: k8sClient,
105+
}
106+
107+
for _, opt := range opts {
108+
if err := opt(activePassive); err != nil {
109+
return nil, fmt.Errorf("failed to apply option: %w", err)
110+
}
111+
}
112+
113+
if activePassive.stateSetter == nil {
114+
var err error
115+
116+
// Default state setter uses the k8s api to set the state.
117+
activePassive.stateSetter, err = NewPodStateSetter()
118+
if err != nil {
119+
return nil, fmt.Errorf("failed to create default state setter: %w", err)
120+
}
79121
}
80122

81123
health.SetCustomReadinessCheck(activePassive.Handler)
@@ -198,7 +240,7 @@ func (a *ActivePassive) becomeUndefined(ctx context.Context) {
198240

199241
// setState returns true if the state was set successfully
200242
func (a *ActivePassive) setState(ctx context.Context, state status) bool {
201-
err := a.k8sClient.SetCurrentPodLabel(ctx, Label, a.label(state))
243+
err := a.stateSetter.SetState(ctx, a.label(state))
202244
if err != nil {
203245
log.Ctx(ctx).Error().Err(err).Msg("failed to mark pod as undefined")
204246
a.stateMu.Lock()

maintenance/failover/state_setter.go

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package failover
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/pace/bricks/backend/k8sapi"
8+
)
9+
10+
const Label = "github.com.pace.bricks.activepassive"
11+
12+
type StateSetter interface {
13+
SetState(ctx context.Context, state string) error
14+
}
15+
16+
type podStateSetter struct {
17+
k8sClient *k8sapi.Client
18+
}
19+
20+
func NewPodStateSetter() (*podStateSetter, error) {
21+
k8sClient, err := k8sapi.NewClient()
22+
if err != nil {
23+
return nil, fmt.Errorf("failed to create k8s client: %w", err)
24+
}
25+
26+
return &podStateSetter{k8sClient: k8sClient}, nil
27+
}
28+
29+
func (p *podStateSetter) SetState(ctx context.Context, state string) error {
30+
return p.k8sClient.SetCurrentPodLabel(ctx, Label, state)
31+
}
32+
33+
type CustomStateSetter struct {
34+
fn func(ctx context.Context, state string) error
35+
}
36+
37+
func NewCustomStateSetter(fn func(ctx context.Context, state string) error) (*CustomStateSetter, error) {
38+
if fn == nil {
39+
return nil, fmt.Errorf("fn must not be nil")
40+
}
41+
42+
return &CustomStateSetter{fn: fn}, nil
43+
}
44+
45+
func (c *CustomStateSetter) SetState(ctx context.Context, state string) error {
46+
return c.fn(ctx, state)
47+
}
48+
49+
type NoopStateSetter struct{}
50+
51+
func (n *NoopStateSetter) SetState(ctx context.Context, state string) error {
52+
return nil
53+
}

0 commit comments

Comments
 (0)