blob: 294983f5669ef3ed8b7985486624d522f45678a9 [file] [log] [blame]
Elia Battistonac8d23f2022-03-14 17:54:56 +01001/*
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/jhump/protoreflect/dynamic/grpcdynamic"
29 "github.com/jhump/protoreflect/grpcreflect"
30 "github.com/opencord/voltha-lib-go/v7/pkg/log"
31 "github.com/opencord/voltha-lib-go/v7/pkg/probe"
32 "github.com/opencord/voltha-protos/v5/go/adapter_service"
33 "github.com/opencord/voltha-protos/v5/go/common"
34 "github.com/opencord/voltha-protos/v5/go/core_service"
35 "github.com/opencord/voltha-protos/v5/go/olt_inter_adapter_service"
36 "github.com/opencord/voltha-protos/v5/go/onu_inter_adapter_service"
37 "google.golang.org/grpc"
38 "google.golang.org/grpc/codes"
39 rpb "google.golang.org/grpc/reflection/grpc_reflection_v1alpha"
40 "google.golang.org/grpc/status"
41)
42
43type event byte
44type state byte
45type GetServiceClient func(context.Context, *grpc.ClientConn) interface{}
46type RestartedHandler func(ctx context.Context, endPoint string) error
47
48const (
49 grpcBackoffInitialInterval = "GRPC_BACKOFF_INITIAL_INTERVAL"
50 grpcBackoffMaxInterval = "GRPC_BACKOFF_MAX_INTERVAL"
51 grpcBackoffMaxElapsedTime = "GRPC_BACKOFF_MAX_ELAPSED_TIME"
52 grpcMonitorInterval = "GRPC_MONITOR_INTERVAL"
53)
54
55const (
56 DefaultBackoffInitialInterval = 100 * time.Millisecond
57 DefaultBackoffMaxInterval = 5 * time.Second
58 DefaultBackoffMaxElapsedTime = 0 * time.Second // No time limit
59 DefaultGRPCMonitorInterval = 5 * time.Second
60)
61
62const (
63 eventConnecting = event(iota)
64 eventValidatingConnection
65 eventConnected
66 eventDisconnected
67 eventStopped
68 eventError
69
70 stateConnected = state(iota)
71 stateValidatingConnection
72 stateConnecting
73 stateDisconnected
74)
75
76type Client struct {
77 clientEndpoint string
78 clientContextData string
79 serverEndPoint string
80 remoteServiceName string
81 connection *grpc.ClientConn
82 connectionLock sync.RWMutex
83 stateLock sync.RWMutex
84 state state
85 service interface{}
86 events chan event
87 onRestart RestartedHandler
88 backoffInitialInterval time.Duration
89 backoffMaxInterval time.Duration
90 backoffMaxElapsedTime time.Duration
91 monitorInterval time.Duration
92 done bool
93 livenessLock sync.RWMutex
94 livenessCallback func(timestamp time.Time)
95}
96
97type ClientOption func(*Client)
98
99func ClientContextData(data string) ClientOption {
100 return func(args *Client) {
101 args.clientContextData = data
102 }
103}
104
105func NewClient(clientEndpoint, serverEndpoint, remoteServiceName string, onRestart RestartedHandler,
106 opts ...ClientOption) (*Client, error) {
107 c := &Client{
108 clientEndpoint: clientEndpoint,
109 serverEndPoint: serverEndpoint,
110 remoteServiceName: remoteServiceName,
111 onRestart: onRestart,
112 events: make(chan event, 5),
113 state: stateDisconnected,
114 backoffInitialInterval: DefaultBackoffInitialInterval,
115 backoffMaxInterval: DefaultBackoffMaxInterval,
116 backoffMaxElapsedTime: DefaultBackoffMaxElapsedTime,
117 monitorInterval: DefaultGRPCMonitorInterval,
118 }
119 for _, option := range opts {
120 option(c)
121 }
122
123 // Check for environment variables
124 if err := SetFromEnvVariable(grpcBackoffInitialInterval, &c.backoffInitialInterval); err != nil {
125 logger.Warnw(context.Background(), "failure-reading-env-variable", log.Fields{"error": err, "variable": grpcBackoffInitialInterval})
126 }
127
128 if err := SetFromEnvVariable(grpcBackoffMaxInterval, &c.backoffMaxInterval); err != nil {
129 logger.Warnw(context.Background(), "failure-reading-env-variable", log.Fields{"error": err, "variable": grpcBackoffMaxInterval})
130 }
131
132 if err := SetFromEnvVariable(grpcBackoffMaxElapsedTime, &c.backoffMaxElapsedTime); err != nil {
133 logger.Warnw(context.Background(), "failure-reading-env-variable", log.Fields{"error": err, "variable": grpcBackoffMaxElapsedTime})
134 }
135
136 if err := SetFromEnvVariable(grpcMonitorInterval, &c.monitorInterval); err != nil {
137 logger.Warnw(context.Background(), "failure-reading-env-variable", log.Fields{"error": err, "variable": grpcMonitorInterval})
138 }
139
140 logger.Infow(context.Background(), "initialized-client", log.Fields{"client": c})
141
142 // Sanity check
143 if c.backoffInitialInterval > c.backoffMaxInterval {
144 return nil, fmt.Errorf("initial retry delay %v is greater than maximum retry delay %v", c.backoffInitialInterval, c.backoffMaxInterval)
145 }
146
147 grpc.EnableTracing = true
148
149 return c, nil
150}
151
152func (c *Client) GetClient() (interface{}, error) {
153 c.connectionLock.RLock()
154 defer c.connectionLock.RUnlock()
155 if c.service == nil {
156 return nil, fmt.Errorf("no connection to %s", c.serverEndPoint)
157 }
158 return c.service, nil
159}
160
161// GetCoreServiceClient is a helper function that returns a concrete service instead of the GetClient() API
162// which returns an interface
163func (c *Client) GetCoreServiceClient() (core_service.CoreServiceClient, error) {
164 c.connectionLock.RLock()
165 defer c.connectionLock.RUnlock()
166 if c.service == nil {
167 return nil, fmt.Errorf("no core connection to %s", c.serverEndPoint)
168 }
169 client, ok := c.service.(core_service.CoreServiceClient)
170 if ok {
171 return client, nil
172 }
173 return nil, fmt.Errorf("invalid-service-%s", reflect.TypeOf(c.service))
174}
175
176// GetOnuAdapterServiceClient is a helper function that returns a concrete service instead of the GetClient() API
177// which returns an interface
178func (c *Client) GetOnuInterAdapterServiceClient() (onu_inter_adapter_service.OnuInterAdapterServiceClient, error) {
179 c.connectionLock.RLock()
180 defer c.connectionLock.RUnlock()
181 if c.service == nil {
182 return nil, fmt.Errorf("no child adapter connection to %s", c.serverEndPoint)
183 }
184 client, ok := c.service.(onu_inter_adapter_service.OnuInterAdapterServiceClient)
185 if ok {
186 return client, nil
187 }
188 return nil, fmt.Errorf("invalid-service-%s", reflect.TypeOf(c.service))
189}
190
191// GetOltAdapterServiceClient is a helper function that returns a concrete service instead of the GetClient() API
192// which returns an interface
193func (c *Client) GetOltInterAdapterServiceClient() (olt_inter_adapter_service.OltInterAdapterServiceClient, error) {
194 c.connectionLock.RLock()
195 defer c.connectionLock.RUnlock()
196 if c.service == nil {
197 return nil, fmt.Errorf("no parent adapter connection to %s", c.serverEndPoint)
198 }
199 client, ok := c.service.(olt_inter_adapter_service.OltInterAdapterServiceClient)
200 if ok {
201 return client, nil
202 }
203 return nil, fmt.Errorf("invalid-service-%s", reflect.TypeOf(c.service))
204}
205
206// GetAdapterServiceClient is a helper function that returns a concrete service instead of the GetClient() API
207// which returns an interface
208func (c *Client) GetAdapterServiceClient() (adapter_service.AdapterServiceClient, error) {
209 c.connectionLock.RLock()
210 defer c.connectionLock.RUnlock()
211 if c.service == nil {
212 return nil, fmt.Errorf("no adapter service connection to %s", c.serverEndPoint)
213 }
214 client, ok := c.service.(adapter_service.AdapterServiceClient)
215 if ok {
216 return client, nil
217 }
218 return nil, fmt.Errorf("invalid-service-%s", reflect.TypeOf(c.service))
219}
220
221func (c *Client) Reset(ctx context.Context) {
222 logger.Debugw(ctx, "resetting-client-connection", log.Fields{"api-endpoint": c.serverEndPoint, "client": c.clientEndpoint})
223 c.stateLock.Lock()
224 defer c.stateLock.Unlock()
225 if c.state == stateConnected {
226 c.state = stateDisconnected
227 c.events <- eventDisconnected
228 }
229}
230
231// executeWithTimeout runs a sending function (sf) along with a receiving one(rf) and returns an error, if any.
232// If the deadline d elapses first, it returns a grpc DeadlineExceeded error instead.
233func (c *Client) executeWithTimeout(sf func(*common.Connection) error, rf func() (interface{}, error), conn *common.Connection, d time.Duration) error {
234 errChan := make(chan error, 1)
235 go func() {
236 err := sf(conn)
237 logger.Debugw(context.Background(), "message-sent", log.Fields{"error": err, "qpi-endpoint": c.serverEndPoint, "client": c.clientEndpoint})
238 if err == nil {
239 response, err := rf()
240 logger.Debugw(context.Background(), "message-received", log.Fields{"error": err, "qpi-endpoint": c.serverEndPoint, "client": c.clientEndpoint, "health": response})
241 }
242 errChan <- err
243 close(errChan)
244 }()
245 t := time.NewTimer(d)
246 select {
247 case <-t.C:
248 return status.Errorf(codes.DeadlineExceeded, "timeout-on-sending-message")
249 case err := <-errChan:
250 if !t.Stop() {
251 <-t.C
252 }
253 return err
254 }
255}
256
257func (c *Client) monitorConnection(ctx context.Context) {
258 logger.Debugw(ctx, "monitor-connection-started", log.Fields{"qpi-endpoint": c.serverEndPoint, "client": c.clientEndpoint})
259
260 // If we exit, assume disconnected
261 defer func() {
262 c.stateLock.Lock()
263 if !c.done && (c.state == stateConnected || c.state == stateValidatingConnection) {
264 // Handle only connected state here. We need the validating state to know if we need to backoff before a retry
265 if c.state == stateConnected {
266 c.state = stateDisconnected
267 }
268 logger.Warnw(ctx, "sending-disconnect-event", log.Fields{"api-endpoint": c.serverEndPoint, "client": c.clientEndpoint, "curr-state": stateConnected, "new-state": c.state})
269 c.events <- eventDisconnected
270 } else {
271 logger.Debugw(ctx, "no-state-change-needed", log.Fields{"api-endpoint": c.serverEndPoint, "client": c.clientEndpoint, "state": c.state, "client-done": c.done})
272 }
273 c.stateLock.Unlock()
274 logger.Debugw(ctx, "monitor-connection-ended", log.Fields{"api-endpoint": c.serverEndPoint, "client": c.clientEndpoint})
275 }()
276
277 c.connectionLock.RLock()
278 conn := c.connection
279 c.connectionLock.RUnlock()
280 if conn == nil {
281 logger.Errorw(ctx, "connection-nil", log.Fields{"api-endpoint": c.serverEndPoint, "client": c.clientEndpoint})
282 return
283 }
284
285 // Get a new client using reflection. The server can implement any grpc service, but it
286 // needs to also implement the "StartKeepAliveStream" API
287 grpcReflectClient := grpcreflect.NewClient(ctx, rpb.NewServerReflectionClient(conn))
288 if grpcReflectClient == nil {
289 logger.Errorw(ctx, "grpc-reflect-client-nil", log.Fields{"api-endpoint": c.serverEndPoint, "client": c.clientEndpoint})
290 return
291 }
292
293 // Get the list of services - there should be 2 services: a server reflection and the voltha service we are interested in
294 services, err := grpcReflectClient.ListServices()
295 if err != nil {
296 logger.Errorw(ctx, "list-services-error", log.Fields{"api-endpoint": c.serverEndPoint, "client": c.clientEndpoint, "error": err})
297 return
298 }
299
300 // Filter out the service
301 logger.Debugw(ctx, "services", log.Fields{"services": services})
302 serviceOfInterest := ""
303 for _, service := range services {
304 if strings.EqualFold(service, c.remoteServiceName) {
305 serviceOfInterest = service
306 break
307 }
308 }
309 if serviceOfInterest == "" {
310 logger.Errorw(ctx, "no-service-found", log.Fields{"api-endpoint": c.serverEndPoint, "client": c.clientEndpoint, "services": services, "expected-remote-service": c.remoteServiceName})
311 return
312 }
313
314 // Resolve the service
315 resolvedService, err := grpcReflectClient.ResolveService(serviceOfInterest)
316 if err != nil {
317 logger.Errorw(ctx, "service-error", log.Fields{"api-endpoint": c.serverEndPoint, "client": c.clientEndpoint, "service": resolvedService, "error": err})
318 return
319 }
320
321 // Find the method of interest
322 method := resolvedService.FindMethodByName("GetHealthStatus")
323 if method == nil {
324 logger.Errorw(ctx, "nil-method", log.Fields{"api-endpoint": c.serverEndPoint, "client": c.clientEndpoint, "service": resolvedService})
325 return
326 }
327 logger.Debugw(ctx, "resolved-to-method", log.Fields{"service": resolvedService.GetName(), "method": method.GetName()})
328
329 // Get a dynamic connection
330 dynamicConn := grpcdynamic.NewStub(conn)
331
332 // Get the stream and send this client information
333 streamCtx, streamDone := context.WithCancel(log.WithSpanFromContext(context.Background(), ctx))
334 defer streamDone()
335 stream, err := dynamicConn.InvokeRpcBidiStream(streamCtx, method)
336 if err != nil {
337 logger.Errorw(ctx, "stream-error", log.Fields{"api-endpoint": c.serverEndPoint, "client": c.clientEndpoint, "service": resolvedService, "error": err})
338 return
339 }
340
341 clientInfo := &common.Connection{
342 Endpoint: c.clientEndpoint,
343 ContextInfo: c.clientContextData,
344 KeepAliveInterval: int64(c.monitorInterval),
345 }
346
347 initialConnection := true
348loop:
349 for {
350 // Let's send a keep alive message with our info
351 err := c.executeWithTimeout(
352 func(conn *common.Connection) error { return stream.SendMsg(conn) },
353 func() (interface{}, error) { return stream.RecvMsg() },
354 clientInfo,
355 c.monitorInterval)
356
357 if err != nil {
358 // Any error means the far end is gone
359 logger.Errorw(ctx, "sending-stream-error", log.Fields{"error": err, "api-endpoint": c.serverEndPoint, "client": c.clientEndpoint, "context": stream.Context().Err()})
360 break loop
361 }
362 // Send a connect event
363 if initialConnection {
364 logger.Debugw(ctx, "first-stream-data-sent", log.Fields{"api-endpoint": c.serverEndPoint, "client": c.clientEndpoint})
365 c.events <- eventConnected
366 initialConnection = false
367 }
368 logger.Debugw(ctx, "stream-data-sent", log.Fields{"api-endpoint": c.serverEndPoint, "client": c.clientEndpoint})
369 // Update liveness, if configured
370 c.livenessLock.RLock()
371 if c.livenessCallback != nil {
372 go c.livenessCallback(time.Now())
373 }
374 c.livenessLock.RUnlock()
375
376 // Wait to send the next keep alive
377 keepAliveTimer := time.NewTimer(time.Duration(clientInfo.KeepAliveInterval))
378 select {
379 case <-ctx.Done():
380 logger.Warnw(ctx, "context-done", log.Fields{"api-endpont": c.serverEndPoint, "client": c.clientEndpoint})
381 break loop
382 case <-stream.Context().Done():
383 logger.Debugw(ctx, "stream-context-done", log.Fields{"api-endpoint": c.serverEndPoint, "stream-info": stream.Context(), "client": c.clientEndpoint})
384 break loop
385 case <-keepAliveTimer.C:
386 continue
387 }
388 }
389 if stream != nil {
390 if err := stream.CloseSend(); err != nil {
391 logger.Warnw(ctx, "closing-stream-error", log.Fields{"error": err, "api-endpoint": c.serverEndPoint, "client": c.clientEndpoint})
392 }
393 }
394}
395
396// Start kicks off the adapter agent by trying to connect to the adapter
397func (c *Client) Start(ctx context.Context, handler GetServiceClient) {
398 logger.Debugw(ctx, "Starting GRPC - Client", log.Fields{"api-endpoint": c.serverEndPoint})
399
400 // If the context contains a k8s probe then register services
401 p := probe.GetProbeFromContext(ctx)
402 if p != nil {
403 p.RegisterService(ctx, c.serverEndPoint)
404 }
405
406 var monitorConnectionCtx context.Context
407 var monitorConnectionDone func()
408
409 initialConnection := true
410 c.events <- eventConnecting
411 backoff := NewBackoff(c.backoffInitialInterval, c.backoffMaxInterval, c.backoffMaxElapsedTime)
412 attempt := 1
413loop:
414 for {
415 select {
416 case <-ctx.Done():
417 logger.Warnw(ctx, "context-closing", log.Fields{"api_endpoint": c.serverEndPoint, "client": c.clientEndpoint, "context": ctx})
418 c.connectionLock.Lock()
419 if !c.done {
420 c.done = true
421 c.events <- eventStopped
422 close(c.events)
423 }
424 c.connectionLock.Unlock()
425 // break loop
426 case event := <-c.events:
427 logger.Debugw(ctx, "received-event", log.Fields{"event": event, "api-endpoint": c.serverEndPoint, "client": c.clientEndpoint})
428 c.connectionLock.RLock()
429 // On a client stopped, just allow the stop event to go through
430 if c.done && event != eventStopped {
431 c.connectionLock.RUnlock()
432 logger.Debugw(ctx, "ignoring-event-on-client-stop", log.Fields{"event": event, "api-endpoint": c.serverEndPoint, "client": c.clientEndpoint})
433 continue
434 }
435 c.connectionLock.RUnlock()
436 switch event {
437 case eventConnecting:
438 c.stateLock.Lock()
439 logger.Debugw(ctx, "connection-start", log.Fields{"api-endpoint": c.serverEndPoint, "attempts": attempt, "curr-state": c.state, "client": c.clientEndpoint})
440 if c.state == stateConnected {
441 c.state = stateDisconnected
442 }
443 if c.state != stateConnecting {
444 c.state = stateConnecting
445 go func() {
446 if err := c.connectToEndpoint(ctx, p); err != nil {
447 c.stateLock.Lock()
448 c.state = stateDisconnected
449 c.stateLock.Unlock()
450 logger.Errorw(ctx, "connection-failed", log.Fields{"api-endpoint": c.serverEndPoint, "attempt": attempt, "client": c.clientEndpoint, "error": err})
451
452 // Retry connection after a delay
453 if err = backoff.Backoff(ctx); err != nil {
454 // Context has closed or reached maximum elapsed time, if set
455 logger.Errorw(ctx, "retry-aborted", log.Fields{"api-endpoint": c.serverEndPoint, "client": c.clientEndpoint, "error": err})
456 return
457 }
458 attempt += 1
459 c.connectionLock.RLock()
460 if !c.done {
461 c.events <- eventConnecting
462 }
463 c.connectionLock.RUnlock()
464 }
465 }()
466 }
467 c.stateLock.Unlock()
468
469 case eventValidatingConnection:
470 logger.Debugw(ctx, "connection-validation", log.Fields{"api-endpoint": c.serverEndPoint, "client": c.clientEndpoint})
471 c.stateLock.Lock()
472 if c.state != stateConnected {
473 c.state = stateValidatingConnection
474 }
475 c.stateLock.Unlock()
476 monitorConnectionCtx, monitorConnectionDone = context.WithCancel(context.Background())
477 go c.monitorConnection(monitorConnectionCtx)
478
479 case eventConnected:
480 attempt = 1
481 backoff.Reset()
482 c.stateLock.Lock()
483 logger.Debugw(ctx, "endpoint-connected", log.Fields{"api-endpoint": c.serverEndPoint, "curr-state": c.state, "client": c.clientEndpoint})
484 if c.state != stateConnected {
485 // Setup the service
486 c.connectionLock.RLock()
487 conn := c.connection
488 c.connectionLock.RUnlock()
489
490 subCtx, cancel := context.WithTimeout(ctx, c.backoffMaxInterval)
491 svc := handler(subCtx, conn)
492 if svc != nil {
493 c.service = svc
494 if p != nil {
495 p.UpdateStatus(ctx, c.serverEndPoint, probe.ServiceStatusRunning)
496 }
497 logger.Infow(ctx, "connected-to-endpoint", log.Fields{"api-endpoint": c.serverEndPoint, "client": c.clientEndpoint})
498 } else {
499 // Should never happen, but just in case
500 logger.Warnw(ctx, "service-is-nil", log.Fields{"api-endpoint": c.serverEndPoint, "client": c.clientEndpoint})
501 c.events <- eventDisconnected
502 }
503 cancel()
504 c.state = stateConnected
505 if initialConnection {
506 logger.Debugw(ctx, "initial-endpoint-connection", log.Fields{"api-endpoint": c.serverEndPoint, "client": c.clientEndpoint})
507 initialConnection = false
508 } else {
509 logger.Debugw(ctx, "endpoint-reconnection", log.Fields{"api-endpoint": c.serverEndPoint, "client": c.clientEndpoint})
510 // Trigger any callback on a restart
511 go func() {
512 err := c.onRestart(log.WithSpanFromContext(context.Background(), ctx), c.serverEndPoint)
513 if err != nil {
514 logger.Errorw(ctx, "unable-to-restart-endpoint", log.Fields{"error": err, "api-endpoint": c.serverEndPoint, "client": c.clientEndpoint})
515 }
516 }()
517 }
518 }
519 c.stateLock.Unlock()
520
521 case eventDisconnected:
522 if p != nil {
523 p.UpdateStatus(ctx, c.serverEndPoint, probe.ServiceStatusNotReady)
524 }
525 connectionValidationFail := false
526 c.stateLock.Lock()
527 logger.Debugw(ctx, "endpoint-disconnected", log.Fields{"api-endpoint": c.serverEndPoint, "curr-state": c.state, "client": c.clientEndpoint})
528 if c.state == stateValidatingConnection {
529 connectionValidationFail = true
530 c.state = stateDisconnected
531 }
532 c.stateLock.Unlock()
533
534 // Stop the streaming connection
535 if monitorConnectionDone != nil {
536 monitorConnectionDone()
537 monitorConnectionDone = nil
538 }
539
540 if connectionValidationFail {
541 // Retry connection after a delay
542 if err := backoff.Backoff(ctx); err != nil {
543 // Context has closed or reached maximum elapsed time, if set
544 logger.Errorw(ctx, "retry-aborted", log.Fields{"api-endpoint": c.serverEndPoint, "client": c.clientEndpoint, "error": err})
545 return
546 }
547 }
548 c.connectionLock.RLock()
549 if !c.done {
550 c.events <- eventConnecting
551 }
552 c.connectionLock.RUnlock()
553
554 case eventStopped:
555 logger.Debugw(ctx, "endpoint-stopped", log.Fields{"api-endpoint": c.serverEndPoint, "client": c.clientEndpoint})
556
557 if monitorConnectionDone != nil {
558 monitorConnectionDone()
559 monitorConnectionDone = nil
560 }
561 if err := c.closeConnection(ctx, p); err != nil {
562 logger.Errorw(ctx, "endpoint-closing-connection-failed", log.Fields{"api-endpoint": c.serverEndPoint, "client": c.clientEndpoint, "error": err})
563 }
564 break loop
565 case eventError:
566 logger.Errorw(ctx, "endpoint-error-event", log.Fields{"api-endpoint": c.serverEndPoint, "client": c.clientEndpoint})
567 default:
568 logger.Errorw(ctx, "endpoint-unknown-event", log.Fields{"api-endpoint": c.serverEndPoint, "client": c.clientEndpoint, "error": event})
569 }
570 }
571 }
572
573 // Stop the streaming connection
574 if monitorConnectionDone != nil {
575 logger.Debugw(ctx, "closing-connection-monitoring", log.Fields{"api-endpoint": c.serverEndPoint, "client": c.clientEndpoint})
576 monitorConnectionDone()
577 }
578
579 logger.Infow(ctx, "client-stopped", log.Fields{"api-endpoint": c.serverEndPoint, "client": c.clientEndpoint})
580}
581
582func (c *Client) connectToEndpoint(ctx context.Context, p *probe.Probe) error {
583 if p != nil {
584 p.UpdateStatus(ctx, c.serverEndPoint, probe.ServiceStatusPreparing)
585 }
586
587 c.connectionLock.Lock()
588 defer c.connectionLock.Unlock()
589
590 if c.connection != nil {
591 _ = c.connection.Close()
592 c.connection = nil
593 }
594
595 c.service = nil
596
597 // Use Interceptors to:
598 // 1. automatically inject
599 // 2. publish Open Tracing Spans by this GRPC Client
600 // 3. detect connection failure on client calls such that the reconnection process can begin
601 conn, err := grpc.Dial(c.serverEndPoint,
602 grpc.WithInsecure(),
603 grpc.WithStreamInterceptor(grpc_middleware.ChainStreamClient(
604 grpc_opentracing.StreamClientInterceptor(grpc_opentracing.WithTracer(log.ActiveTracerProxy{})),
605 )),
606 grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient(
607 grpc_opentracing.UnaryClientInterceptor(grpc_opentracing.WithTracer(log.ActiveTracerProxy{})),
608 )),
609 )
610
611 if err == nil {
612 c.connection = conn
613 c.events <- eventValidatingConnection
614 return nil
615 } else {
616 logger.Warnw(ctx, "no-connection-to-endpoint", log.Fields{"api-endpoint": c.serverEndPoint, "client": c.clientEndpoint, "error": err})
617 }
618
619 if p != nil {
620 p.UpdateStatus(ctx, c.serverEndPoint, probe.ServiceStatusFailed)
621 }
622 return fmt.Errorf("no connection to api endpoint %s", c.serverEndPoint)
623}
624
625func (c *Client) closeConnection(ctx context.Context, p *probe.Probe) error {
626 if p != nil {
627 p.UpdateStatus(ctx, c.serverEndPoint, probe.ServiceStatusStopped)
628 }
629 logger.Infow(ctx, "client-closing-connection", log.Fields{"api-endpoint": c.serverEndPoint, "client": c.clientEndpoint})
630
631 c.connectionLock.Lock()
632 defer c.connectionLock.Unlock()
633
634 if c.connection != nil {
635 err := c.connection.Close()
636 c.service = nil
637 c.connection = nil
638 return err
639 }
640
641 return nil
642}
643
644func (c *Client) Stop(ctx context.Context) {
645 logger.Infow(ctx, "client-stop-request-event-received", log.Fields{"api-endpoint": c.serverEndPoint, "client": c.clientEndpoint})
646 c.connectionLock.Lock()
647 defer c.connectionLock.Unlock()
648 if !c.done {
649 c.done = true
650 c.events <- eventStopped
651 close(c.events)
652 }
653 logger.Infow(ctx, "client-stop-request-event-sent", log.Fields{"api-endpoint": c.serverEndPoint, "client": c.clientEndpoint})
654}
655
656// SetService is used for testing only
657func (c *Client) SetService(srv interface{}) {
658 c.connectionLock.Lock()
659 defer c.connectionLock.Unlock()
660 c.service = srv
661}
662
663func (c *Client) SubscribeForLiveness(callback func(timestamp time.Time)) {
664 c.livenessLock.Lock()
665 defer c.livenessLock.Unlock()
666 c.livenessCallback = callback
667}