blob: 3c7815dbbeca1e5caf5e9643129265b3967aad18 [file] [log] [blame]
sslobodr392ebd52019-01-18 12:41:49 -05001/*
2 * Copyright 2018-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 */
sslobodr392ebd52019-01-18 12:41:49 -050016
17package afrouter
18
19// Backend manager handles redundant connections per backend
20
21import (
sslobodr392ebd52019-01-18 12:41:49 -050022 "errors"
Kent Hagerman0ab4cb22019-04-24 13:13:35 -040023 "fmt"
24 "github.com/opencord/voltha-go/common/log"
sslobodr392ebd52019-01-18 12:41:49 -050025 "golang.org/x/net/context"
26 "google.golang.org/grpc"
27 "google.golang.org/grpc/codes"
Kent Hagerman0ab4cb22019-04-24 13:13:35 -040028 "google.golang.org/grpc/metadata"
Kent Hagermanfcfb16b2019-06-20 11:40:03 -040029 "net/url"
Kent Hagerman0ab4cb22019-04-24 13:13:35 -040030 "strconv"
31 "strings"
32 "sync"
sslobodr392ebd52019-01-18 12:41:49 -050033)
34
Kent Hagerman1e9061e2019-05-21 16:01:21 -040035// backend represents a collection of backends in a HA configuration
sslobodr392ebd52019-01-18 12:41:49 -050036type backend struct {
Kent Hagerman1e9061e2019-05-21 16:01:21 -040037 mutex sync.Mutex
38 name string
39 beType backendType
40 activeAssociation association
41 connFailCallback func(string, *backend) bool
42 connections map[string]*connection
Kent Hagerman03b58992019-08-29 17:21:03 -040043 openConns map[*connection]*grpc.ClientConn
44 activeRequests map[*request]struct{}
sslobodr392ebd52019-01-18 12:41:49 -050045}
46
Kent Hagerman1e9061e2019-05-21 16:01:21 -040047type association struct {
48 strategy associationStrategy
49 location associationLocation
Kent Hagerman0ab4cb22019-04-24 13:13:35 -040050 field string // Used only if location is protobuf
51 key string
sslobodr392ebd52019-01-18 12:41:49 -050052}
53
Kent Hagerman03b58992019-08-29 17:21:03 -040054// splitActiveStreamsUnsafe expects the caller to have already locked the backend mutex
55func (be *backend) splitActiveStreamsUnsafe(cn *connection, conn *grpc.ClientConn) {
56 if len(be.activeRequests) != 0 {
57 log.Debugf("Creating new streams for %d existing requests", len(be.activeRequests))
58 }
59 for r := range be.activeRequests {
60 r.mutex.Lock()
61 if _, have := r.streams[cn.name]; !have {
62 log.Debugf("Opening southbound stream for existing request '%s'", r.methodInfo.method)
63 if stream, err := grpc.NewClientStream(r.ctx, clientStreamDescForProxying, conn, r.methodInfo.all); err != nil {
64 log.Debugf("Failed to create a client stream '%s', %v", cn.name, err)
65 } else {
66 go r.catchupRequestStreamThenForwardResponseStream(cn.name, stream)
67 // new thread will unlock the request mutex
68 continue
69 }
70 }
71 r.mutex.Unlock()
72 }
73}
sslobodr392ebd52019-01-18 12:41:49 -050074
Kent Hagerman03b58992019-08-29 17:21:03 -040075// openSouthboundStreams sets up a connection to each southbound frame
76func (be *backend) openSouthboundStreams(srv interface{}, serverStream grpc.ServerStream, nf *requestFrame, sf *responseFrame) (*request, error) {
77 be.mutex.Lock()
78 defer be.mutex.Unlock()
sslobodr392ebd52019-01-18 12:41:49 -050079
Kent Hagerman03b58992019-08-29 17:21:03 -040080 isStreamingRequest, isStreamingResponse := nf.router.IsStreaming(nf.methodInfo.method)
81
sslobodr392ebd52019-01-18 12:41:49 -050082 // Get the metadata from the incoming message on the server
83 md, ok := metadata.FromIncomingContext(serverStream.Context())
84 if !ok {
Kent Hagerman03b58992019-08-29 17:21:03 -040085 return nil, errors.New("could not get a server stream metadata")
sslobodr392ebd52019-01-18 12:41:49 -050086 }
87
Kent Hagerman03b58992019-08-29 17:21:03 -040088 r := &request{
89 // Create an outgoing context that includes the incoming metadata and that will cancel if the server's context is canceled
90 ctx: metadata.AppendToOutgoingContext(metadata.NewOutgoingContext(serverStream.Context(), md.Copy()), "voltha_serial_number", strconv.FormatUint(nf.serialNo, 10)),
91
92 streams: make(map[string]grpc.ClientStream),
93 responseErrChan: make(chan error, 1),
94
95 backend: be,
96 serverStream: serverStream,
97 methodInfo: nf.methodInfo,
98 requestFrame: nf,
99 responseFrame: sf,
100 isStreamingRequest: isStreamingRequest,
101 isStreamingResponse: isStreamingResponse,
102 }
103
104 log.Debugf("Opening southbound request for method '%s'", nf.methodInfo.method)
105
sslobodr392ebd52019-01-18 12:41:49 -0500106 // TODO: Need to check if this is an active/active backend cluster
107 // with a serial number in the header.
Kent Hagerman03b58992019-08-29 17:21:03 -0400108 log.Debugf("Serial number for transaction allocated: %d", nf.serialNo)
sslobodr392ebd52019-01-18 12:41:49 -0500109 // If even one stream can be created then proceed. If none can be
Kent Hagerman03b58992019-08-29 17:21:03 -0400110 // created then report an error because both the primary and redundant
111 // connections are non-existent.
Kent Hagerman1e9061e2019-05-21 16:01:21 -0400112 var atLeastOne = false
sslobodr392ebd52019-01-18 12:41:49 -0500113 var errStr strings.Builder
Kent Hagerman03b58992019-08-29 17:21:03 -0400114 log.Debugf("There are %d/%d streams to open", len(be.openConns), len(be.connections))
115 for cn, conn := range be.openConns {
116 log.Debugf("Opening stream for connection '%s'", cn.name)
117 if stream, err := grpc.NewClientStream(r.ctx, clientStreamDescForProxying, conn, r.methodInfo.all); err != nil {
118 log.Debugf("Failed to create a client stream '%s', %v", cn.name, err)
sslobodr392ebd52019-01-18 12:41:49 -0500119 } else {
Kent Hagerman03b58992019-08-29 17:21:03 -0400120 r.streams[cn.name] = stream
121 go r.forwardResponseStream(cn.name, stream)
122 atLeastOne = true
sslobodr392ebd52019-01-18 12:41:49 -0500123 }
124 }
Kent Hagerman1e9061e2019-05-21 16:01:21 -0400125 if atLeastOne {
Kent Hagerman03b58992019-08-29 17:21:03 -0400126 be.activeRequests[r] = struct{}{}
127 return r, nil
sslobodr392ebd52019-01-18 12:41:49 -0500128 }
Kent Hagerman03b58992019-08-29 17:21:03 -0400129 fmt.Fprintf(&errStr, "{{No open connections for backend '%s' unable to send}} ", be.name)
sslobodr392ebd52019-01-18 12:41:49 -0500130 log.Error(errStr.String())
131 return nil, errors.New(errStr.String())
132}
133
Kent Hagerman03b58992019-08-29 17:21:03 -0400134func (be *backend) handler(srv interface{}, serverStream grpc.ServerStream, nf *requestFrame, sf *responseFrame) error {
135 // Set up streams for each open connection
136 request, err := be.openSouthboundStreams(srv, serverStream, nf, sf)
sslobodr392ebd52019-01-18 12:41:49 -0500137 if err != nil {
Kent Hagerman0ab4cb22019-04-24 13:13:35 -0400138 log.Errorf("openStreams failed: %v", err)
sslobodr392ebd52019-01-18 12:41:49 -0500139 return err
140 }
sslobodr392ebd52019-01-18 12:41:49 -0500141
Kent Hagerman03b58992019-08-29 17:21:03 -0400142 log.Debug("Starting request stream forwarding")
143 if s2cErr := request.forwardRequestStream(serverStream); s2cErr != nil {
144 // exit with an error to the stack
145 return grpc.Errorf(codes.Internal, "failed proxying s2c: %v", s2cErr)
sslobodr392ebd52019-01-18 12:41:49 -0500146 }
Kent Hagerman03b58992019-08-29 17:21:03 -0400147 // wait for response stream to complete
148 return <-request.responseErrChan
sslobodr392ebd52019-01-18 12:41:49 -0500149}
150
sslobodr392ebd52019-01-18 12:41:49 -0500151func newBackend(conf *BackendConfig, clusterName string) (*backend, error) {
152 var rtrn_err bool = false
153
154 log.Debugf("Configuring the backend with %v", *conf)
155 // Validate the conifg and configure the backend
Kent Hagerman03b58992019-08-29 17:21:03 -0400156 be := &backend{
157 name: conf.Name,
158 connections: make(map[string]*connection),
159 openConns: make(map[*connection]*grpc.ClientConn),
160 activeRequests: make(map[*request]struct{}),
161 }
Kent Hagerman1e9061e2019-05-21 16:01:21 -0400162 if conf.Type == BackendUndefined {
sslobodr392ebd52019-01-18 12:41:49 -0500163 log.Error("Invalid type specified for backend %s in cluster %s", conf.Name, clusterName)
164 rtrn_err = true
165 }
Kent Hagerman1e9061e2019-05-21 16:01:21 -0400166 be.beType = conf.Type
sslobodr392ebd52019-01-18 12:41:49 -0500167
Kent Hagerman1e9061e2019-05-21 16:01:21 -0400168 if conf.Association.Strategy == AssociationStrategyUndefined && be.beType == BackendActiveActive {
sslobodr392ebd52019-01-18 12:41:49 -0500169 log.Errorf("An association strategy must be provided if the backend "+
Kent Hagerman0ab4cb22019-04-24 13:13:35 -0400170 "type is active/active for backend %s in cluster %s", conf.Name, clusterName)
sslobodr392ebd52019-01-18 12:41:49 -0500171 rtrn_err = true
172 }
Kent Hagerman1e9061e2019-05-21 16:01:21 -0400173 be.activeAssociation.strategy = conf.Association.Strategy
sslobodr392ebd52019-01-18 12:41:49 -0500174
Kent Hagerman1e9061e2019-05-21 16:01:21 -0400175 if conf.Association.Location == AssociationLocationUndefined && be.beType == BackendActiveActive {
sslobodr392ebd52019-01-18 12:41:49 -0500176 log.Errorf("An association location must be provided if the backend "+
Kent Hagerman0ab4cb22019-04-24 13:13:35 -0400177 "type is active/active for backend %s in cluster %s", conf.Name, clusterName)
sslobodr392ebd52019-01-18 12:41:49 -0500178 rtrn_err = true
179 }
Kent Hagerman1e9061e2019-05-21 16:01:21 -0400180 be.activeAssociation.location = conf.Association.Location
sslobodr392ebd52019-01-18 12:41:49 -0500181
Kent Hagerman1e9061e2019-05-21 16:01:21 -0400182 if conf.Association.Field == "" && be.activeAssociation.location == AssociationLocationProtobuf {
sslobodr392ebd52019-01-18 12:41:49 -0500183 log.Errorf("An association field must be provided if the backend "+
Kent Hagerman0ab4cb22019-04-24 13:13:35 -0400184 "type is active/active and the location is set to protobuf "+
185 "for backend %s in cluster %s", conf.Name, clusterName)
sslobodr392ebd52019-01-18 12:41:49 -0500186 rtrn_err = true
187 }
Kent Hagerman1e9061e2019-05-21 16:01:21 -0400188 be.activeAssociation.field = conf.Association.Field
sslobodr8e2ccb52019-02-05 09:21:47 -0500189
Kent Hagerman1e9061e2019-05-21 16:01:21 -0400190 if conf.Association.Key == "" && be.activeAssociation.location == AssociationLocationHeader {
sslobodr8e2ccb52019-02-05 09:21:47 -0500191 log.Errorf("An association key must be provided if the backend "+
Kent Hagerman0ab4cb22019-04-24 13:13:35 -0400192 "type is active/active and the location is set to header "+
193 "for backend %s in cluster %s", conf.Name, clusterName)
sslobodr8e2ccb52019-02-05 09:21:47 -0500194 rtrn_err = true
195 }
Kent Hagerman1e9061e2019-05-21 16:01:21 -0400196 be.activeAssociation.key = conf.Association.Key
sslobodr392ebd52019-01-18 12:41:49 -0500197 if rtrn_err {
198 return nil, errors.New("Backend configuration failed")
199 }
200 // Configure the connections
201 // Connections can consist of just a name. This allows for dynamic configuration
202 // at a later time.
203 // TODO: validate that there is one connection for all but active/active backends
Kent Hagerman1e9061e2019-05-21 16:01:21 -0400204 if len(conf.Connections) > 1 && be.beType != BackendActiveActive {
Kent Hagerman0ab4cb22019-04-24 13:13:35 -0400205 log.Errorf("Only one connection must be specified if the association " +
206 "strategy is not set to 'active_active'")
sslobodr8e2ccb52019-02-05 09:21:47 -0500207 rtrn_err = true
208 }
209 if len(conf.Connections) == 0 {
210 log.Errorf("At least one connection must be specified")
211 rtrn_err = true
212 }
Kent Hagerman0ab4cb22019-04-24 13:13:35 -0400213 for _, cnConf := range conf.Connections {
sslobodr392ebd52019-01-18 12:41:49 -0500214 if cnConf.Name == "" {
215 log.Errorf("A connection must have a name for backend %s in cluster %s",
Kent Hagerman0ab4cb22019-04-24 13:13:35 -0400216 conf.Name, clusterName)
sslobodr392ebd52019-01-18 12:41:49 -0500217 } else {
Kent Hagerman03b58992019-08-29 17:21:03 -0400218 ctx, cancelFunc := context.WithCancel(context.Background())
219 be.connections[cnConf.Name] = &connection{name: cnConf.Name, addr: cnConf.Addr, port: cnConf.Port, backend: be, ctx: ctx, close: cancelFunc}
220 if _, err := url.Parse(cnConf.Addr); err != nil {
221 log.Errorf("The address for connection %s in backend %s in cluster %s is invalid: %s",
222 cnConf.Name, conf.Name, clusterName, err)
223 rtrn_err = true
224 }
225 // Validate the port number. This just validtes that it's a non 0 integer
226 if n, err := strconv.Atoi(cnConf.Port); err != nil || n <= 0 || n > 65535 {
227 log.Errorf("Port %s for connection %s in backend %s in cluster %s is invalid",
228 cnConf.Port, cnConf.Name, conf.Name, clusterName)
229 rtrn_err = true
230 } else {
231 if n <= 0 && n > 65535 {
sslobodr392ebd52019-01-18 12:41:49 -0500232 log.Errorf("Port %s for connection %s in backend %s in cluster %s is invalid",
233 cnConf.Port, cnConf.Name, conf.Name, clusterName)
234 rtrn_err = true
sslobodr392ebd52019-01-18 12:41:49 -0500235 }
236 }
237 }
238 }
sslobodr63d160c2019-02-08 14:25:13 -0500239
sslobodr392ebd52019-01-18 12:41:49 -0500240 if rtrn_err {
241 return nil, errors.New("Connection configuration failed")
242 }
sslobodr392ebd52019-01-18 12:41:49 -0500243 // All is well start the backend cluster connections
244 be.connectAll()
245
246 return be, nil
247}
248
Kent Hagerman03b58992019-08-29 17:21:03 -0400249func (be *backend) incConn(cn *connection, conn *grpc.ClientConn) {
Kent Hagerman1e9061e2019-05-21 16:01:21 -0400250 be.mutex.Lock()
251 defer be.mutex.Unlock()
Kent Hagerman03b58992019-08-29 17:21:03 -0400252
253 be.openConns[cn] = conn
254 be.splitActiveStreamsUnsafe(cn, conn)
sslobodr392ebd52019-01-18 12:41:49 -0500255}
256
Kent Hagerman03b58992019-08-29 17:21:03 -0400257func (be *backend) decConn(cn *connection) {
Kent Hagerman1e9061e2019-05-21 16:01:21 -0400258 be.mutex.Lock()
259 defer be.mutex.Unlock()
Kent Hagerman03b58992019-08-29 17:21:03 -0400260
261 delete(be.openConns, cn)
262}
263
264func (be *backend) NumOpenConnections() int {
265 be.mutex.Lock()
266 defer be.mutex.Unlock()
267
268 return len(be.openConns)
sslobodr392ebd52019-01-18 12:41:49 -0500269}
270
271// Attempts to establish all the connections for a backend
272// any failures result in an abort. This should only be called
273// on a first attempt to connect. Individual connections should be
274// handled after that.
275func (be *backend) connectAll() {
Kent Hagerman0ab4cb22019-04-24 13:13:35 -0400276 for _, cn := range be.connections {
Kent Hagerman03b58992019-08-29 17:21:03 -0400277 go cn.connect()
sslobodr392ebd52019-01-18 12:41:49 -0500278 }
279}
280
sslobodr392ebd52019-01-18 12:41:49 -0500281// Set a callback for connection failure notification
282// This is currently not used.
Kent Hagerman1e9061e2019-05-21 16:01:21 -0400283func (be *backend) setConnFailCallback(cb func(string, *backend) bool) {
284 be.connFailCallback = cb
sslobodr392ebd52019-01-18 12:41:49 -0500285}