Skip to content

Commit 046a667

Browse files
committed
[FAB-2828] DeliveryService: Connection Producer
This commit is part of [FAB-2828] and its goal is to create a connection producer object, which will be used for: 1) Selecting new endpoints once connection is broken 2) Preparing for a shared connection pool between channels in case connecting to the same ordering service instance on different channels. Signed-off-by: Yacov Manevich <[email protected]> Change-Id: Ia176614a9394b249dca3eafde7e16d86cea0331f
1 parent 22c1a1f commit 046a667

File tree

2 files changed

+223
-0
lines changed

2 files changed

+223
-0
lines changed

core/comm/producer.go

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
Copyright IBM Corp. 2017 All Rights Reserved.
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+
*/
16+
17+
package comm
18+
19+
import (
20+
"fmt"
21+
"math/rand"
22+
"sync"
23+
24+
"github.com/op/go-logging"
25+
"google.golang.org/grpc"
26+
)
27+
28+
var logger = logging.MustGetLogger("ConnProducer")
29+
30+
// ConnectionFactory creates a connection to a certain endpoint
31+
type ConnectionFactory func(endpoint string) (*grpc.ClientConn, error)
32+
33+
// ConnectionProducer produces connections out of a set of predefined
34+
// endpoints
35+
type ConnectionProducer interface {
36+
// NewConnection creates a new connection.
37+
// Returns the connection, the endpoint selected, nil on success.
38+
// Returns nil, "", error on failure
39+
NewConnection() (*grpc.ClientConn, string, error)
40+
// UpdateEndpoints updates the endpoints of the ConnectionProducer
41+
// to be the given endpoints
42+
UpdateEndpoints(endpoints []string)
43+
}
44+
45+
type connProducer struct {
46+
sync.RWMutex
47+
endpoints []string
48+
connect ConnectionFactory
49+
}
50+
51+
// NewConnectionProducer creates a new ConnectionProducer with given endpoints and connection factory.
52+
// It returns nil, if the given endpoints slice is empty.
53+
func NewConnectionProducer(factory ConnectionFactory, endpoints []string) ConnectionProducer {
54+
if len(endpoints) == 0 {
55+
return nil
56+
}
57+
return &connProducer{endpoints: endpoints, connect: factory}
58+
}
59+
60+
// NewConnection creates a new connection.
61+
// Returns the connection, the endpoint selected, nil on success.
62+
// Returns nil, "", error on failure
63+
func (cp *connProducer) NewConnection() (*grpc.ClientConn, string, error) {
64+
cp.RLock()
65+
defer cp.RUnlock()
66+
67+
endpoints := shuffle(cp.endpoints)
68+
for _, endpoint := range endpoints {
69+
conn, err := cp.connect(endpoint)
70+
if err != nil {
71+
logger.Error("Failed connecting to", endpoint, ", error:", err)
72+
continue
73+
}
74+
return conn, endpoint, nil
75+
}
76+
return nil, "", fmt.Errorf("Could not connect to any of the endpoints: %v", endpoints)
77+
}
78+
79+
// UpdateEndpoints updates the endpoints of the ConnectionProducer
80+
// to be the given endpoints
81+
func (cp *connProducer) UpdateEndpoints(endpoints []string) {
82+
if len(endpoints) == 0 {
83+
// Ignore updates with empty endpoints
84+
return
85+
}
86+
cp.Lock()
87+
defer cp.Unlock()
88+
cp.endpoints = endpoints
89+
}
90+
91+
func shuffle(a []string) []string {
92+
n := len(a)
93+
returnedSlice := make([]string, n)
94+
indices := rand.Perm(n)
95+
for i, idx := range indices {
96+
returnedSlice[i] = a[idx]
97+
}
98+
return returnedSlice
99+
}
100+
101+
// filterOut receives a slice of strings and a string to filter out
102+
// and returns the slice without the string
103+
func filterOut(a []string, filteredOut string) []string {
104+
var slice2Return []string
105+
for _, s := range a {
106+
if s == filteredOut {
107+
continue
108+
}
109+
slice2Return = append(slice2Return, s)
110+
}
111+
return slice2Return
112+
}

core/comm/producer_test.go

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
Copyright IBM Corp. 2017 All Rights Reserved.
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+
*/
16+
17+
package comm
18+
19+
import (
20+
"fmt"
21+
"testing"
22+
23+
"github.com/stretchr/testify/assert"
24+
"google.golang.org/grpc"
25+
)
26+
27+
type connMock struct {
28+
*grpc.ClientConn
29+
endpoint string
30+
}
31+
32+
func TestEmptyEndpoints(t *testing.T) {
33+
noopFactory := func(endpoint string) (*grpc.ClientConn, error) {
34+
return nil, nil
35+
}
36+
assert.Nil(t, NewConnectionProducer(noopFactory, []string{}))
37+
}
38+
39+
func TestConnFailures(t *testing.T) {
40+
conn2Endpoint := make(map[string]string)
41+
shouldConnFail := map[string]bool{
42+
"a": true,
43+
"b": false,
44+
"c": false,
45+
}
46+
connFactory := func(endpoint string) (*grpc.ClientConn, error) {
47+
conn := &grpc.ClientConn{}
48+
conn2Endpoint[fmt.Sprintf("%p", conn)] = endpoint
49+
if !shouldConnFail[endpoint] {
50+
return conn, nil
51+
}
52+
return nil, fmt.Errorf("Failed connecting to %s", endpoint)
53+
}
54+
// Create a producer with some endpoints, and have the first one fail and all others not fail
55+
producer := NewConnectionProducer(connFactory, []string{"a", "b", "c"})
56+
conn, _, err := producer.NewConnection()
57+
assert.NoError(t, err)
58+
// We should not return 'a' because connecting to 'a' fails
59+
assert.NotEqual(t, "a", conn2Endpoint[fmt.Sprintf("%p", conn)])
60+
// Now, revive 'a'
61+
shouldConnFail["a"] = false
62+
// Try obtaining a connection 1000 times in order to ensure selection is shuffled
63+
selected := make(map[string]struct{})
64+
for i := 0; i < 1000; i++ {
65+
conn, _, err := producer.NewConnection()
66+
assert.NoError(t, err)
67+
selected[conn2Endpoint[fmt.Sprintf("%p", conn)]] = struct{}{}
68+
}
69+
// The probability of a, b or c not to be selected is really small
70+
_, isAselected := selected["a"]
71+
_, isBselected := selected["b"]
72+
_, isCselected := selected["c"]
73+
assert.True(t, isBselected)
74+
assert.True(t, isCselected)
75+
assert.True(t, isAselected)
76+
77+
// Now, make every host fail
78+
shouldConnFail["a"] = true
79+
shouldConnFail["b"] = true
80+
shouldConnFail["c"] = true
81+
conn, _, err = producer.NewConnection()
82+
assert.Nil(t, conn)
83+
assert.Error(t, err)
84+
}
85+
86+
func TestUpdateEndpoints(t *testing.T) {
87+
conn2Endpoint := make(map[string]string)
88+
connFactory := func(endpoint string) (*grpc.ClientConn, error) {
89+
conn := &grpc.ClientConn{}
90+
conn2Endpoint[fmt.Sprintf("%p", conn)] = endpoint
91+
return conn, nil
92+
}
93+
// Create a producer with a single endpoint
94+
producer := NewConnectionProducer(connFactory, []string{"a"})
95+
conn, a, err := producer.NewConnection()
96+
assert.NoError(t, err)
97+
assert.Equal(t, "a", conn2Endpoint[fmt.Sprintf("%p", conn)])
98+
assert.Equal(t, "a", a)
99+
// Now update the endpoint and check that when we create a new connection,
100+
// we don't connect to the previous endpoint
101+
producer.UpdateEndpoints([]string{"b"})
102+
conn, b, err := producer.NewConnection()
103+
assert.NoError(t, err)
104+
assert.NotEqual(t, "a", conn2Endpoint[fmt.Sprintf("%p", conn)])
105+
assert.Equal(t, "b", conn2Endpoint[fmt.Sprintf("%p", conn)])
106+
assert.Equal(t, "b", b)
107+
// Next, ensure an empty update is ignored
108+
producer.UpdateEndpoints([]string{})
109+
conn, _, err = producer.NewConnection()
110+
assert.Equal(t, "b", conn2Endpoint[fmt.Sprintf("%p", conn)])
111+
}

0 commit comments

Comments
 (0)