blob: add2b28773069158d18db3aef28c766dd85760ae [file] [log] [blame]
khenaidood948f772021-08-11 17:49:24 -04001/*
2 * Copyright 2021-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package grpc
17
18import (
19 "context"
20 "fmt"
21 "reflect"
22 "strings"
23 "sync"
24 "time"
25
26 grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
27 grpc_opentracing "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing"
28 "github.com/opencord/voltha-lib-go/v7/pkg/log"
29 "github.com/opencord/voltha-lib-go/v7/pkg/probe"
khenaidoo9beaaf12021-10-19 17:32:01 -040030 "github.com/opencord/voltha-protos/v5/go/core_service"
31 "github.com/opencord/voltha-protos/v5/go/olt_inter_adapter_service"
32 "github.com/opencord/voltha-protos/v5/go/onu_inter_adapter_service"
khenaidood948f772021-08-11 17:49:24 -040033 "google.golang.org/grpc"
34 "google.golang.org/grpc/keepalive"
35)
36
37type event byte
38type state byte
39type SetAndTestServiceHandler func(context.Context, *grpc.ClientConn) interface{}
40type RestartedHandler func(ctx context.Context, endPoint string) error
41
42type contextKey string
43
44func (c contextKey) String() string {
45 return string(c)
46}
47
48var (
49 grpcMonitorContextKey = contextKey("grpc-monitor")
50)
51
52const (
53 grpcBackoffInitialInterval = "GRPC_BACKOFF_INITIAL_INTERVAL"
54 grpcBackoffMaxInterval = "GRPC_BACKOFF_MAX_INTERVAL"
55 grpcBackoffMaxElapsedTime = "GRPC_BACKOFF_MAX_ELAPSED_TIME"
56 grpcMonitorInterval = "GRPC_MONITOR_INTERVAL"
57)
58
59const (
60 DefaultBackoffInitialInterval = 100 * time.Millisecond
61 DefaultBackoffMaxInterval = 5 * time.Second
62 DefaultBackoffMaxElapsedTime = 0 * time.Second // No time limit
63 DefaultGRPCMonitorInterval = 5 * time.Second
64)
65
66const (
67 connectionErrorSubString = "SubConns are in TransientFailure"
68 connectionClosedSubstring = "client connection is closing"
69 connectionError = "connection error"
70 connectionSystemNotReady = "system is not ready"
71)
72
73const (
74 eventConnecting = event(iota)
75 eventConnected
76 eventDisconnected
77 eventStopped
78 eventError
79
80 stateConnected = state(iota)
81 stateConnecting
82 stateDisconnected
83)
84
85type Client struct {
86 apiEndPoint string
87 connection *grpc.ClientConn
88 connectionLock sync.RWMutex
89 stateLock sync.RWMutex
90 state state
91 service interface{}
92 events chan event
93 onRestart RestartedHandler
94 backoffInitialInterval time.Duration
95 backoffMaxInterval time.Duration
96 backoffMaxElapsedTime time.Duration
97 activityCheck bool
98 monitorInterval time.Duration
99 activeCh chan struct{}
100 activeChMutex sync.RWMutex
101 done bool
102 livenessCallback func(timestamp time.Time)
103}
104
105type ClientOption func(*Client)
106
107func ActivityCheck(enable bool) ClientOption {
108 return func(args *Client) {
109 args.activityCheck = enable
110 }
111}
112
113func NewClient(endpoint string, onRestart RestartedHandler, opts ...ClientOption) (*Client, error) {
114 c := &Client{
115 apiEndPoint: endpoint,
116 onRestart: onRestart,
117 events: make(chan event, 1),
118 state: stateDisconnected,
119 backoffInitialInterval: DefaultBackoffInitialInterval,
120 backoffMaxInterval: DefaultBackoffMaxInterval,
121 backoffMaxElapsedTime: DefaultBackoffMaxElapsedTime,
122 monitorInterval: DefaultGRPCMonitorInterval,
123 }
124 for _, option := range opts {
125 option(c)
126 }
127
128 // Check for environment variables
129 if err := SetFromEnvVariable(grpcBackoffInitialInterval, &c.backoffInitialInterval); err != nil {
130 logger.Warnw(context.Background(), "failure-reading-env-variable", log.Fields{"error": err, "variable": grpcBackoffInitialInterval})
131 }
132
133 if err := SetFromEnvVariable(grpcBackoffMaxInterval, &c.backoffMaxInterval); err != nil {
134 logger.Warnw(context.Background(), "failure-reading-env-variable", log.Fields{"error": err, "variable": grpcBackoffMaxInterval})
135 }
136
137 if err := SetFromEnvVariable(grpcBackoffMaxElapsedTime, &c.backoffMaxElapsedTime); err != nil {
138 logger.Warnw(context.Background(), "failure-reading-env-variable", log.Fields{"error": err, "variable": grpcBackoffMaxElapsedTime})
139 }
140
141 if err := SetFromEnvVariable(grpcMonitorInterval, &c.monitorInterval); err != nil {
142 logger.Warnw(context.Background(), "failure-reading-env-variable", log.Fields{"error": err, "variable": grpcMonitorInterval})
143 }
144
145 logger.Infow(context.Background(), "initialized-client", log.Fields{"client": c})
146
147 // Sanity check
148 if c.backoffInitialInterval > c.backoffMaxInterval {
149 return nil, fmt.Errorf("initial retry delay %v is greater than maximum retry delay %v", c.backoffInitialInterval, c.backoffMaxInterval)
150 }
151
152 return c, nil
153}
154
155func (c *Client) GetClient() (interface{}, error) {
156 c.connectionLock.RLock()
157 defer c.connectionLock.RUnlock()
158 if c.service == nil {
159 return nil, fmt.Errorf("no connection to %s", c.apiEndPoint)
160 }
161 return c.service, nil
162}
163
164// GetCoreServiceClient is a helper function that returns a concrete service instead of the GetClient() API
165// which returns an interface
khenaidoo9beaaf12021-10-19 17:32:01 -0400166func (c *Client) GetCoreServiceClient() (core_service.CoreServiceClient, error) {
khenaidood948f772021-08-11 17:49:24 -0400167 c.connectionLock.RLock()
168 defer c.connectionLock.RUnlock()
169 if c.service == nil {
170 return nil, fmt.Errorf("no core connection to %s", c.apiEndPoint)
171 }
khenaidoo9beaaf12021-10-19 17:32:01 -0400172 client, ok := c.service.(core_service.CoreServiceClient)
khenaidood948f772021-08-11 17:49:24 -0400173 if ok {
174 return client, nil
175 }
176 return nil, fmt.Errorf("invalid-service-%s", reflect.TypeOf(c.service))
177}
178
179// GetOnuAdapterServiceClient is a helper function that returns a concrete service instead of the GetClient() API
180// which returns an interface
khenaidoo9beaaf12021-10-19 17:32:01 -0400181func (c *Client) GetOnuInterAdapterServiceClient() (onu_inter_adapter_service.OnuInterAdapterServiceClient, error) {
khenaidood948f772021-08-11 17:49:24 -0400182 c.connectionLock.RLock()
183 defer c.connectionLock.RUnlock()
184 if c.service == nil {
185 return nil, fmt.Errorf("no child adapter connection to %s", c.apiEndPoint)
186 }
khenaidoo9beaaf12021-10-19 17:32:01 -0400187 client, ok := c.service.(onu_inter_adapter_service.OnuInterAdapterServiceClient)
khenaidood948f772021-08-11 17:49:24 -0400188 if ok {
189 return client, nil
190 }
191 return nil, fmt.Errorf("invalid-service-%s", reflect.TypeOf(c.service))
192}
193
194// GetOltAdapterServiceClient is a helper function that returns a concrete service instead of the GetClient() API
195// which returns an interface
khenaidoo9beaaf12021-10-19 17:32:01 -0400196func (c *Client) GetOltInterAdapterServiceClient() (olt_inter_adapter_service.OltInterAdapterServiceClient, error) {
khenaidood948f772021-08-11 17:49:24 -0400197 c.connectionLock.RLock()
198 defer c.connectionLock.RUnlock()
199 if c.service == nil {
200 return nil, fmt.Errorf("no parent adapter connection to %s", c.apiEndPoint)
201 }
khenaidoo9beaaf12021-10-19 17:32:01 -0400202 client, ok := c.service.(olt_inter_adapter_service.OltInterAdapterServiceClient)
khenaidood948f772021-08-11 17:49:24 -0400203 if ok {
204 return client, nil
205 }
206 return nil, fmt.Errorf("invalid-service-%s", reflect.TypeOf(c.service))
207}
208
209func (c *Client) Reset(ctx context.Context) {
210 logger.Debugw(ctx, "resetting-client-connection", log.Fields{"endpoint": c.apiEndPoint})
211 c.stateLock.Lock()
212 defer c.stateLock.Unlock()
213 if c.state == stateConnected {
214 c.state = stateDisconnected
215 c.events <- eventDisconnected
216 }
217}
218
219func (c *Client) clientInterceptor(ctx context.Context, method string, req interface{}, reply interface{},
220 cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
221 // Nothing to do before intercepting the call
222 err := invoker(ctx, method, req, reply, cc, opts...)
223 // On connection failure, start the reconnect process depending on the error response
224 if err != nil {
225 logger.Errorw(ctx, "received-error", log.Fields{"error": err, "context": ctx, "endpoint": c.apiEndPoint})
226 if strings.Contains(err.Error(), connectionErrorSubString) ||
227 strings.Contains(err.Error(), connectionError) ||
228 strings.Contains(err.Error(), connectionSystemNotReady) ||
229 isGrpcMonitorKeyPresentInContext(ctx) {
230 c.stateLock.Lock()
231 if c.state == stateConnected {
khenaidood948f772021-08-11 17:49:24 -0400232 c.state = stateDisconnected
Mahir Gunyel626a0342021-10-22 17:50:03 -0700233 logger.Warnw(context.Background(), "sending-disconnect-event", log.Fields{"endpoint": c.apiEndPoint, "error": err, "curr-state": stateConnected, "new-state": c.state})
khenaidood948f772021-08-11 17:49:24 -0400234 c.events <- eventDisconnected
235 }
236 c.stateLock.Unlock()
237 } else if strings.Contains(err.Error(), connectionClosedSubstring) {
238 logger.Errorw(context.Background(), "invalid-client-connection-closed", log.Fields{"endpoint": c.apiEndPoint, "error": err})
239 }
240 return err
241 }
242 // Update activity on success only
243 c.updateActivity(ctx)
244 return nil
245}
246
247// updateActivity pushes an activity indication on the channel so that the monitoring routine does not validate
248// the gRPC connection when the connection is being used. Note that this update is done both when the connection
249// is alive or a connection error is returned. A separate routine takes care of doing the re-connect.
250func (c *Client) updateActivity(ctx context.Context) {
251 c.activeChMutex.RLock()
252 defer c.activeChMutex.RUnlock()
253 if c.activeCh != nil {
254 logger.Debugw(ctx, "update-activity", log.Fields{"api-endpoint": c.apiEndPoint})
255 c.activeCh <- struct{}{}
256
257 // Update liveness only in connected state
258 if c.livenessCallback != nil {
259 c.stateLock.RLock()
260 if c.state == stateConnected {
261 c.livenessCallback(time.Now())
262 }
263 c.stateLock.RUnlock()
264 }
265 }
266}
267
268func WithGrpcMonitorContext(ctx context.Context, name string) context.Context {
269 ctx = context.WithValue(ctx, grpcMonitorContextKey, name)
270 return ctx
271}
272
273func isGrpcMonitorKeyPresentInContext(ctx context.Context) bool {
274 if ctx != nil {
275 _, present := ctx.Value(grpcMonitorContextKey).(string)
276 return present
277 }
278 return false
279}
280
281// monitorActivity monitors the activity on the gRPC connection. If there are no activity after a specified
282// timeout, it will send a default API request on that connection. If the connection is good then nothing
283// happens. If it's bad this will trigger reconnection attempts.
284func (c *Client) monitorActivity(ctx context.Context, handler SetAndTestServiceHandler) {
285 logger.Infow(ctx, "start-activity-monitor", log.Fields{"endpoint": c.apiEndPoint})
286
287 // Create an activity monitor channel. Unbuffered channel works well. However, we use a buffered
288 // channel here as a safeguard of having the grpc interceptor publishing too many events that can be
289 // consumed by this monitoring thread
290 c.activeChMutex.Lock()
291 c.activeCh = make(chan struct{}, 10)
292 c.activeChMutex.Unlock()
293
Mahir Gunyel626a0342021-10-22 17:50:03 -0700294 grpcMonitorCheckRunning := false
295 var grpcMonitorCheckRunningLock sync.RWMutex
296
khenaidood948f772021-08-11 17:49:24 -0400297 // Interval to wait for no activity before probing the connection
298 timeout := c.monitorInterval
299loop:
300 for {
301 timeoutTimer := time.NewTimer(timeout)
302 select {
303
304 case <-c.activeCh:
Mahir Gunyel626a0342021-10-22 17:50:03 -0700305 logger.Debugw(ctx, "endpoint-reachable", log.Fields{"endpoint": c.apiEndPoint})
khenaidood948f772021-08-11 17:49:24 -0400306
307 // Reset timer
308 if !timeoutTimer.Stop() {
Mahir Gunyel626a0342021-10-22 17:50:03 -0700309 select {
310 case <-timeoutTimer.C:
311 default:
312 }
khenaidood948f772021-08-11 17:49:24 -0400313 }
314
315 case <-ctx.Done():
316 break loop
317
318 case <-timeoutTimer.C:
319 // Trigger an activity check if the state is connected. If the state is not connected then there is already
320 // a backoff retry mechanism in place to retry establishing connection.
321 c.stateLock.RLock()
Mahir Gunyel626a0342021-10-22 17:50:03 -0700322 grpcMonitorCheckRunningLock.RLock()
323 runCheck := (c.state == stateConnected) && !grpcMonitorCheckRunning
324 grpcMonitorCheckRunningLock.RUnlock()
khenaidood948f772021-08-11 17:49:24 -0400325 c.stateLock.RUnlock()
326 if runCheck {
327 go func() {
Mahir Gunyel626a0342021-10-22 17:50:03 -0700328 grpcMonitorCheckRunningLock.Lock()
329 if grpcMonitorCheckRunning {
330 grpcMonitorCheckRunningLock.Unlock()
331 logger.Debugw(ctx, "connection-check-already-in-progress", log.Fields{"api-endpoint": c.apiEndPoint})
332 return
333 }
334 grpcMonitorCheckRunning = true
335 grpcMonitorCheckRunningLock.Unlock()
336
khenaidood948f772021-08-11 17:49:24 -0400337 logger.Debugw(ctx, "connection-check-start", log.Fields{"api-endpoint": c.apiEndPoint})
338 subCtx, cancel := context.WithTimeout(ctx, c.backoffMaxInterval)
339 defer cancel()
340 subCtx = WithGrpcMonitorContext(subCtx, "grpc-monitor")
341 c.connectionLock.RLock()
342 defer c.connectionLock.RUnlock()
343 if c.connection != nil {
344 response := handler(subCtx, c.connection)
345 logger.Debugw(ctx, "connection-check-response", log.Fields{"api-endpoint": c.apiEndPoint, "up": response != nil})
346 }
Mahir Gunyel626a0342021-10-22 17:50:03 -0700347 grpcMonitorCheckRunningLock.Lock()
348 grpcMonitorCheckRunning = false
349 grpcMonitorCheckRunningLock.Unlock()
khenaidood948f772021-08-11 17:49:24 -0400350 }()
351 }
352 }
353 }
354 logger.Infow(ctx, "activity-monitor-stopping", log.Fields{"endpoint": c.apiEndPoint})
355}
356
357// Start kicks off the adapter agent by trying to connect to the adapter
358func (c *Client) Start(ctx context.Context, handler SetAndTestServiceHandler) {
359 logger.Debugw(ctx, "Starting GRPC - Client", log.Fields{"api-endpoint": c.apiEndPoint})
360
361 // If the context contains a k8s probe then register services
362 p := probe.GetProbeFromContext(ctx)
363 if p != nil {
364 p.RegisterService(ctx, c.apiEndPoint)
365 }
366
367 // Enable activity check, if required
368 if c.activityCheck {
369 go c.monitorActivity(ctx, handler)
370 }
371
372 initialConnection := true
373 c.events <- eventConnecting
374 backoff := NewBackoff(c.backoffInitialInterval, c.backoffMaxInterval, c.backoffMaxElapsedTime)
375 attempt := 1
376loop:
377 for {
378 select {
379 case <-ctx.Done():
380 logger.Debugw(ctx, "context-closing", log.Fields{"endpoint": c.apiEndPoint})
khenaidoo68a5e0c2021-11-06 13:08:03 -0400381 break loop
khenaidood948f772021-08-11 17:49:24 -0400382 case event := <-c.events:
383 logger.Debugw(ctx, "received-event", log.Fields{"event": event, "endpoint": c.apiEndPoint})
khenaidoo68a5e0c2021-11-06 13:08:03 -0400384 c.connectionLock.RLock()
385 // On a client stopped, just allow the stop event to go through
386 if c.done && event != eventStopped {
387 c.connectionLock.RUnlock()
388 logger.Debugw(ctx, "ignoring-event-on-client-stop", log.Fields{"event": event, "endpoint": c.apiEndPoint})
389 continue
390 }
391 c.connectionLock.RUnlock()
khenaidood948f772021-08-11 17:49:24 -0400392 switch event {
393 case eventConnecting:
khenaidood948f772021-08-11 17:49:24 -0400394 c.stateLock.Lock()
Mahir Gunyel626a0342021-10-22 17:50:03 -0700395 logger.Debugw(ctx, "connection-start", log.Fields{"endpoint": c.apiEndPoint, "attempts": attempt, "curr-state": c.state})
khenaidood948f772021-08-11 17:49:24 -0400396 if c.state == stateConnected {
397 c.state = stateDisconnected
398 }
399 if c.state != stateConnecting {
400 c.state = stateConnecting
401 go func() {
402 if err := c.connectToEndpoint(ctx, handler, p); err != nil {
403 c.stateLock.Lock()
404 c.state = stateDisconnected
405 c.stateLock.Unlock()
406 logger.Errorw(ctx, "connection-failed", log.Fields{"endpoint": c.apiEndPoint, "attempt": attempt, "error": err})
407
408 // Retry connection after a delay
409 if err = backoff.Backoff(ctx); err != nil {
410 // Context has closed or reached maximum elapsed time, if set
411 logger.Errorw(ctx, "retry-aborted", log.Fields{"endpoint": c.apiEndPoint, "error": err})
412 return
413 }
414 attempt += 1
khenaidoo68a5e0c2021-11-06 13:08:03 -0400415 c.connectionLock.RLock()
416 if !c.done {
417 c.events <- eventConnecting
418 }
419 c.connectionLock.RUnlock()
khenaidood948f772021-08-11 17:49:24 -0400420 } else {
421 backoff.Reset()
422 }
423 }()
424 }
425 c.stateLock.Unlock()
426
427 case eventConnected:
khenaidood948f772021-08-11 17:49:24 -0400428 attempt = 1
429 c.stateLock.Lock()
Mahir Gunyel626a0342021-10-22 17:50:03 -0700430 logger.Debugw(ctx, "endpoint-connected", log.Fields{"endpoint": c.apiEndPoint, "curr-state": c.state})
khenaidood948f772021-08-11 17:49:24 -0400431 if c.state != stateConnected {
432 c.state = stateConnected
433 if initialConnection {
434 logger.Debugw(ctx, "initial-endpoint-connection", log.Fields{"endpoint": c.apiEndPoint})
435 initialConnection = false
436 } else {
437 logger.Debugw(ctx, "endpoint-reconnection", log.Fields{"endpoint": c.apiEndPoint})
438 // Trigger any callback on a restart
439 go func() {
440 err := c.onRestart(log.WithSpanFromContext(context.Background(), ctx), c.apiEndPoint)
441 if err != nil {
442 logger.Errorw(ctx, "unable-to-restart-endpoint", log.Fields{"error": err, "endpoint": c.apiEndPoint})
443 }
444 }()
445 }
446 }
447 c.stateLock.Unlock()
448
449 case eventDisconnected:
450 if p != nil {
451 p.UpdateStatus(ctx, c.apiEndPoint, probe.ServiceStatusNotReady)
452 }
Mahir Gunyel626a0342021-10-22 17:50:03 -0700453 c.stateLock.RLock()
454 logger.Debugw(ctx, "endpoint-disconnected", log.Fields{"endpoint": c.apiEndPoint, "curr-state": c.state})
455 c.stateLock.RUnlock()
khenaidood948f772021-08-11 17:49:24 -0400456
457 // Try to connect again
458 c.events <- eventConnecting
459
460 case eventStopped:
461 logger.Debugw(ctx, "endPoint-stopped", log.Fields{"adapter": c.apiEndPoint})
462 go func() {
463 if err := c.closeConnection(ctx, p); err != nil {
464 logger.Errorw(ctx, "endpoint-closing-connection-failed", log.Fields{"endpoint": c.apiEndPoint, "error": err})
465 }
466 }()
467 break loop
468 case eventError:
469 logger.Errorw(ctx, "endpoint-error-event", log.Fields{"endpoint": c.apiEndPoint})
470 default:
471 logger.Errorw(ctx, "endpoint-unknown-event", log.Fields{"endpoint": c.apiEndPoint, "error": event})
472 }
473 }
474 }
475 logger.Infow(ctx, "endpoint-stopped", log.Fields{"endpoint": c.apiEndPoint})
476}
477
478func (c *Client) connectToEndpoint(ctx context.Context, handler SetAndTestServiceHandler, p *probe.Probe) error {
479 if p != nil {
480 p.UpdateStatus(ctx, c.apiEndPoint, probe.ServiceStatusPreparing)
481 }
482
483 c.connectionLock.Lock()
484 defer c.connectionLock.Unlock()
485
486 if c.connection != nil {
487 _ = c.connection.Close()
488 c.connection = nil
489 }
490
491 c.service = nil
492
493 // Use Interceptors to:
494 // 1. automatically inject
495 // 2. publish Open Tracing Spans by this GRPC Client
496 // 3. detect connection failure on client calls such that the reconnection process can begin
497 conn, err := grpc.Dial(c.apiEndPoint,
498 grpc.WithInsecure(),
499 grpc.WithStreamInterceptor(grpc_middleware.ChainStreamClient(
500 grpc_opentracing.StreamClientInterceptor(grpc_opentracing.WithTracer(log.ActiveTracerProxy{})),
501 )),
502 grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient(
503 grpc_opentracing.UnaryClientInterceptor(grpc_opentracing.WithTracer(log.ActiveTracerProxy{})),
504 )),
505 grpc.WithUnaryInterceptor(c.clientInterceptor),
506 // Set keealive parameter - use default grpc values
507 grpc.WithKeepaliveParams(keepalive.ClientParameters{
508 Time: c.monitorInterval,
509 Timeout: c.backoffMaxInterval,
510 PermitWithoutStream: true,
511 }),
512 )
513
514 if err == nil {
515 subCtx, cancel := context.WithTimeout(ctx, c.backoffMaxInterval)
516 defer cancel()
517 svc := handler(subCtx, conn)
518 if svc != nil {
519 c.connection = conn
520 c.service = svc
521 if p != nil {
522 p.UpdateStatus(ctx, c.apiEndPoint, probe.ServiceStatusRunning)
523 }
524 logger.Infow(ctx, "connected-to-endpoint", log.Fields{"endpoint": c.apiEndPoint})
525 c.events <- eventConnected
526 return nil
527 }
528 }
529 logger.Warnw(ctx, "Failed to connect to endpoint",
530 log.Fields{
531 "endpoint": c.apiEndPoint,
532 "error": err,
533 })
534
535 if p != nil {
536 p.UpdateStatus(ctx, c.apiEndPoint, probe.ServiceStatusFailed)
537 }
538 return fmt.Errorf("no connection to endpoint %s", c.apiEndPoint)
539}
540
541func (c *Client) closeConnection(ctx context.Context, p *probe.Probe) error {
542 if p != nil {
543 p.UpdateStatus(ctx, c.apiEndPoint, probe.ServiceStatusStopped)
544 }
545
546 c.connectionLock.Lock()
547 defer c.connectionLock.Unlock()
548
549 if c.connection != nil {
550 err := c.connection.Close()
551 c.connection = nil
552 return err
553 }
554
555 return nil
556}
557
558func (c *Client) Stop(ctx context.Context) {
khenaidoo68a5e0c2021-11-06 13:08:03 -0400559 c.connectionLock.Lock()
560 defer c.connectionLock.Unlock()
khenaidood948f772021-08-11 17:49:24 -0400561 if !c.done {
khenaidoo68a5e0c2021-11-06 13:08:03 -0400562 c.done = true
khenaidood948f772021-08-11 17:49:24 -0400563 c.events <- eventStopped
564 close(c.events)
khenaidood948f772021-08-11 17:49:24 -0400565 }
khenaidoo68a5e0c2021-11-06 13:08:03 -0400566 logger.Infow(ctx, "client-stopped", log.Fields{"endpoint": c.apiEndPoint})
khenaidood948f772021-08-11 17:49:24 -0400567}
568
569// SetService is used for testing only
570func (c *Client) SetService(srv interface{}) {
571 c.connectionLock.Lock()
572 defer c.connectionLock.Unlock()
573 c.service = srv
574}
575
576func (c *Client) SubscribeForLiveness(callback func(timestamp time.Time)) {
577 c.livenessCallback = callback
578}