-
Notifications
You must be signed in to change notification settings - Fork 589
/
readiness.go
164 lines (145 loc) · 5.61 KB
/
readiness.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
package clients
import (
"context"
"errors"
"fmt"
"time"
"github.com/go-logr/logr"
k8stypes "k8s.io/apimachinery/pkg/types"
"github.com/kong/kubernetes-ingress-controller/v3/internal/adminapi"
"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
)
const (
readinessCheckTimeout = time.Second
)
// ReadinessCheckResult represents the result of a readiness check.
type ReadinessCheckResult struct {
// ClientsTurnedReady are the clients that were pending and are now ready to be used.
ClientsTurnedReady []*adminapi.Client
// ClientsTurnedPending are the clients that were ready and are now pending to be created.
ClientsTurnedPending []adminapi.DiscoveredAdminAPI
}
// HasChanges returns true if there are any changes in the readiness check result.
// When no changes are present, it means that the readiness check haven't successfully created any pending client
// nor detected any already created client that became not ready.
func (r ReadinessCheckResult) HasChanges() bool {
return len(r.ClientsTurnedReady) > 0 || len(r.ClientsTurnedPending) > 0
}
// ReadinessChecker is responsible for checking the readiness of Admin API clients.
type ReadinessChecker interface {
// CheckReadiness checks readiness of the provided clients:
// - alreadyCreatedClients are the clients that have already been created. The readiness of these clients will be
// checked by their IsReady() method.
// - pendingClients are the clients that have not been created yet and are pending to be created. The readiness of
// these clients will be checked by trying to create them.
CheckReadiness(
ctx context.Context,
alreadyCreatedClients []AlreadyCreatedClient,
pendingClients []adminapi.DiscoveredAdminAPI,
) ReadinessCheckResult
}
// AlreadyCreatedClient represents an Admin API client that has already been created.
type AlreadyCreatedClient interface {
IsReady(context.Context) error
PodReference() (k8stypes.NamespacedName, bool)
BaseRootURL() string
}
type DefaultReadinessChecker struct {
factory ClientFactory
logger logr.Logger
}
func NewDefaultReadinessChecker(factory ClientFactory, logger logr.Logger) DefaultReadinessChecker {
return DefaultReadinessChecker{
factory: factory,
logger: logger,
}
}
func (c DefaultReadinessChecker) CheckReadiness(
ctx context.Context,
readyClients []AlreadyCreatedClient,
pendingClients []adminapi.DiscoveredAdminAPI,
) ReadinessCheckResult {
return ReadinessCheckResult{
ClientsTurnedReady: c.checkPendingGatewayClients(ctx, pendingClients),
ClientsTurnedPending: c.checkAlreadyExistingClients(ctx, readyClients),
}
}
// checkPendingGatewayClients checks if the pending clients are ready to be used and returns the ones that are.
func (c DefaultReadinessChecker) checkPendingGatewayClients(ctx context.Context, lastPending []adminapi.DiscoveredAdminAPI) (turnedReady []*adminapi.Client) {
for _, adminAPI := range lastPending {
if client := c.checkPendingClient(ctx, adminAPI); client != nil {
turnedReady = append(turnedReady, client)
}
}
return turnedReady
}
// checkPendingClient indirectly check readiness of the client by trying to create it. If it succeeds then it
// means that the client is ready to be used. It returns a non-nil client if the client is ready to be used, otherwise
// nil is returned.
func (c DefaultReadinessChecker) checkPendingClient(
ctx context.Context,
pendingClient adminapi.DiscoveredAdminAPI,
) (client *adminapi.Client) {
defer func() {
c.logger.V(util.DebugLevel).
Info(fmt.Sprintf("Checking readiness of pending client for %q", pendingClient.Address),
"ok", client != nil,
)
}()
ctx, cancel := context.WithTimeout(ctx, readinessCheckTimeout)
defer cancel()
client, err := c.factory.CreateAdminAPIClient(ctx, pendingClient)
if err != nil {
// Despite the error reason we still want to keep the client in the pending list to retry later.
c.logger.V(util.DebugLevel).Info("Pending client is not ready yet",
"reason", err.Error(),
"address", pendingClient.Address,
)
return nil
}
return client
}
// checkAlreadyExistingClients checks if the already existing clients are still ready to be used and returns the ones
// that are not.
func (c DefaultReadinessChecker) checkAlreadyExistingClients(ctx context.Context, alreadyCreatedClients []AlreadyCreatedClient) (turnedPending []adminapi.DiscoveredAdminAPI) {
for _, client := range alreadyCreatedClients {
// For ready clients we check readiness by calling the Status endpoint.
if ready := c.checkAlreadyCreatedClient(ctx, client); !ready {
podRef, ok := client.PodReference()
if !ok {
// This should never happen, but if it does, we want to log it.
c.logger.Error(
errors.New("missing pod reference"),
"Failed to get PodReference for client",
"address", client.BaseRootURL(),
)
continue
}
turnedPending = append(turnedPending, adminapi.DiscoveredAdminAPI{
Address: client.BaseRootURL(),
PodRef: podRef,
})
}
}
return turnedPending
}
func (c DefaultReadinessChecker) checkAlreadyCreatedClient(ctx context.Context, client AlreadyCreatedClient) (ready bool) {
defer func() {
c.logger.V(util.DebugLevel).Info(
fmt.Sprintf("Checking readiness of already created client for %q", client.BaseRootURL()),
"ok", ready,
)
}()
ctx, cancel := context.WithTimeout(ctx, readinessCheckTimeout)
defer cancel()
if err := client.IsReady(ctx); err != nil {
// Despite the error reason we still want to keep the client in the pending list to retry later.
c.logger.V(util.DebugLevel).Info(
"Already created client is not ready, moving to pending",
"address", client.BaseRootURL(),
"reason", err.Error(),
)
return false
}
return true
}