Skip to content

Commit f401a21

Browse files
authored
lock: add DistributedLock with initial etcd implementation (#116)
* etcdtest: add ability to test against local etcd instance * build: add integration test target, clean-integraiton-containers Adds a target that allows for running long/expensive integration tests, as well as a cleanup target that should clean dangling containers. In normal condition, containers should be cleaned up, but force cancelling tests (and other conditions) can lead to them dangling some times. * etcdtest: add ability to start a multinode cluster * lock: add DistributedLock interfaces + etcd impl
1 parent 8491cb1 commit f401a21

File tree

8 files changed

+1090
-4
lines changed

8 files changed

+1090
-4
lines changed

Makefile

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
1+
.PHONY: all
12
all: test
23

4+
.PHONY: test
35
test:
46
@go test -cover ./...
57

6-
.PHONY: all test
8+
9+
.PHONY: integration-test
10+
integration-test:
11+
@go test -tags integration -cover -timeout=5m ./...
12+
13+
14+
.PHONY: clean-integration-containers
15+
clean-integration-containers:
16+
@echo Removing etcd containers...
17+
@docker ps | grep -E "etcd-test-[0-9a-z]{8}-[0-9]+" | awk '{print $$1}' | xargs docker rm -f 2>/dev/null || true
18+
@echo Removing etcd cluster networks...
19+
@docker network ls | grep -E "etcd-test-[0-9a-z]{8}-network" | awk '{print $$1}' | xargs docker network remove 2>/dev/null || true

go.mod

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ require (
1010
github.com/emirpasic/gods v1.12.0
1111
github.com/envoyproxy/protoc-gen-validate v1.0.4
1212
github.com/golang-jwt/jwt/v5 v5.0.0
13-
github.com/golang/protobuf v1.5.3
13+
github.com/golang/protobuf v1.5.4
1414
github.com/google/uuid v1.6.0
1515
github.com/grpc-ecosystem/go-grpc-middleware v1.2.2
1616
github.com/jackc/pgconn v1.10.0
@@ -35,6 +35,7 @@ require (
3535
github.com/twilio/twilio-go v0.26.0
3636
github.com/vence722/base122-go v0.0.2
3737
github.com/ybbus/jsonrpc v2.1.2+incompatible
38+
go.etcd.io/etcd/client/v3 v3.5.13
3839
golang.org/x/crypto v0.21.0
3940
golang.org/x/net v0.22.0
4041
golang.org/x/text v0.14.0
@@ -58,6 +59,8 @@ require (
5859
github.com/bits-and-blooms/bitset v1.2.0 // indirect
5960
github.com/cenkalti/backoff/v4 v4.1.0 // indirect
6061
github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 // indirect
62+
github.com/coreos/go-semver v0.3.0 // indirect
63+
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
6164
github.com/davecgh/go-spew v1.1.1 // indirect
6265
github.com/docker/cli v20.10.7+incompatible // indirect
6366
github.com/docker/docker v20.10.7+incompatible // indirect
@@ -103,12 +106,17 @@ require (
103106
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
104107
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
105108
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
109+
go.etcd.io/etcd/api/v3 v3.5.13 // indirect
110+
go.etcd.io/etcd/client/pkg/v3 v3.5.13 // indirect
106111
go.opencensus.io v0.24.0 // indirect
107112
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect
108113
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
109114
go.opentelemetry.io/otel v1.24.0 // indirect
110115
go.opentelemetry.io/otel/metric v1.24.0 // indirect
111116
go.opentelemetry.io/otel/trace v1.24.0 // indirect
117+
go.uber.org/atomic v1.7.0 // indirect
118+
go.uber.org/multierr v1.6.0 // indirect
119+
go.uber.org/zap v1.17.0 // indirect
112120
golang.org/x/oauth2 v0.18.0 // indirect
113121
golang.org/x/sync v0.6.0 // indirect
114122
golang.org/x/sys v0.18.0 // indirect

go.sum

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,12 @@ github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 h1:NmTXa/uVn
127127
github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
128128
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
129129
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
130+
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
130131
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
131132
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
132133
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
134+
github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
135+
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
133136
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
134137
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
135138
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@@ -187,6 +190,7 @@ github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG
187190
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
188191
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
189192
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
193+
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
190194
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
191195
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
192196
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -230,8 +234,8 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
230234
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
231235
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
232236
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
233-
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
234-
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
237+
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
238+
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
235239
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
236240
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
237241
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -562,6 +566,12 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
562566
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
563567
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
564568
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
569+
go.etcd.io/etcd/api/v3 v3.5.13 h1:8WXU2/NBge6AUF1K1gOexB6e07NgsN1hXK0rSTtgSp4=
570+
go.etcd.io/etcd/api/v3 v3.5.13/go.mod h1:gBqlqkcMMZMVTMm4NDZloEVJzxQOQIls8splbqBDa0c=
571+
go.etcd.io/etcd/client/pkg/v3 v3.5.13 h1:RVZSAnWWWiI5IrYAXjQorajncORbS0zI48LQlE2kQWg=
572+
go.etcd.io/etcd/client/pkg/v3 v3.5.13/go.mod h1:XxHT4u1qU12E2+po+UVPrEeL94Um6zL58ppuJWXSAB8=
573+
go.etcd.io/etcd/client/v3 v3.5.13 h1:o0fHTNJLeO0MyVbc7I3fsCf6nrOqn5d+diSarKnB2js=
574+
go.etcd.io/etcd/client/v3 v3.5.13/go.mod h1:cqiAeY8b5DEEcpxvgWKsbLIWNM/8Wy2xJSDMtioMcoI=
565575
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
566576
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
567577
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -588,13 +598,19 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
588598
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
589599
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
590600
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
601+
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
602+
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
591603
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
592604
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
593605
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
606+
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
607+
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
594608
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
595609
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
596610
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
597611
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
612+
go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U=
613+
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
598614
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
599615
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
600616
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -1093,10 +1109,12 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
10931109
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
10941110
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
10951111
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
1112+
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
10961113
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
10971114
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
10981115
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
10991116
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
1117+
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
11001118
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
11011119
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
11021120
gotest.tools/v3 v3.0.2 h1:kG1BFyqVHuQoVQiR1bWGnfz/fmHvvuiSPIV7rvl360E=

pkg/etcdtest/container.go

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
package etcdtest
2+
3+
import (
4+
"context"
5+
"crypto/rand"
6+
"encoding/hex"
7+
"fmt"
8+
"time"
9+
10+
"github.com/ory/dockertest/v3"
11+
"github.com/ory/dockertest/v3/docker"
12+
"github.com/sirupsen/logrus"
13+
v3 "go.etcd.io/etcd/client/v3"
14+
)
15+
16+
const (
17+
imageName = "quay.io/coreos/etcd"
18+
imageTag = "v3.5.13"
19+
20+
containerAutoKill = 120 * time.Second
21+
)
22+
23+
func StartEtcd(pool *dockertest.Pool) (client *v3.Client, teardown func(), err error) {
24+
teardown = func() {}
25+
26+
resource, err := pool.RunWithOptions(&dockertest.RunOptions{
27+
Repository: imageName,
28+
Tag: imageTag,
29+
Env: []string{
30+
"ALLOW_NONE_AUTHENTICATION=true",
31+
"ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379",
32+
"ETCD_ADVERTISE_CLIENT_URLS=http://0.0.0.0:2379",
33+
},
34+
}, func(config *docker.HostConfig) {
35+
// set AutoRemove to true so that stopped container goes away by itself
36+
config.AutoRemove = true
37+
config.RestartPolicy = docker.RestartPolicy{Name: "no"}
38+
})
39+
40+
if err != nil {
41+
return nil, teardown, fmt.Errorf("failed to start etcd: %w", err)
42+
}
43+
44+
// 2024/04/14: Expire() _never_ returns an error
45+
_ = resource.Expire(uint(containerAutoKill.Seconds()))
46+
47+
log := logrus.StandardLogger().WithField("method", "StartEtcd")
48+
49+
client, err = v3.New(v3.Config{
50+
Endpoints: []string{fmt.Sprintf("localhost:%s", resource.GetPort("2379/tcp"))},
51+
})
52+
if err != nil {
53+
return nil, teardown, fmt.Errorf("failed to create v3 client: %w", err)
54+
}
55+
56+
teardown = func() {
57+
if err := pool.Purge(resource); err != nil {
58+
log.WithError(err).Errorf("failed to cleanup etcd resource")
59+
}
60+
}
61+
62+
err = pool.Retry(func() error {
63+
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
64+
defer cancel()
65+
66+
_, err = client.Get(ctx, "__startup_test")
67+
return err
68+
})
69+
if err != nil {
70+
return nil, teardown, fmt.Errorf("failed waiting for stable connection: %w", err)
71+
}
72+
73+
return client, teardown, nil
74+
}
75+
76+
func StartEtcdCluster(pool *dockertest.Pool, size int, autoRemove bool) (client *v3.Client, nodes []*dockertest.Resource, teardown func(), err error) {
77+
teardown = func() {}
78+
79+
if size%2 == 0 {
80+
return nil, nil, teardown, fmt.Errorf("must specify an odd number of size")
81+
}
82+
83+
clusterName, err := randClusterName()
84+
if err != nil {
85+
return nil, nil, teardown, fmt.Errorf("failed to generate cluster name: %w", err)
86+
}
87+
88+
log := logrus.StandardLogger().WithFields(logrus.Fields{
89+
"method": "StartEtcdCluster",
90+
"cluster": clusterName,
91+
})
92+
93+
network, err := pool.CreateNetwork(fmt.Sprintf("%s-network", clusterName))
94+
if err != nil {
95+
return nil, nil, teardown, fmt.Errorf("failed to create network: %w", err)
96+
}
97+
98+
cleanupNetwork := func() {
99+
if err := pool.RemoveNetwork(network); err != nil {
100+
log.WithError(err).Errorf("failed to remove network")
101+
}
102+
}
103+
104+
teardown = func() {
105+
cleanupNetwork()
106+
}
107+
108+
cluster := fmt.Sprintf("%s-0=http://%s-0:2380", clusterName, clusterName)
109+
for i := 1; i < size; i++ {
110+
cluster += fmt.Sprintf(",%s-%d=http://%s-%d:2380", clusterName, i, clusterName, i)
111+
}
112+
113+
containers := make([]*dockertest.Resource, size)
114+
for i := 0; i < size; i++ {
115+
peerURL := fmt.Sprintf("http://%s-%d:2380", clusterName, i)
116+
containers[i], err = pool.RunWithOptions(&dockertest.RunOptions{
117+
Repository: imageName,
118+
Tag: imageTag,
119+
Name: fmt.Sprintf("%s-%d", clusterName, i),
120+
Networks: []*dockertest.Network{network},
121+
Env: []string{
122+
"ALLOW_NONE_AUTHENTICATION=true",
123+
"ETCD_NAME=" + fmt.Sprintf("%s-%d", clusterName, i),
124+
"ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379",
125+
"ETCD_ADVERTISE_CLIENT_URLS=http://0.0.0.0:2379",
126+
"ETCD_INITIAL_CLUSTER=" + cluster,
127+
"ETCD_INITIAL_ADVERTISE_PEER_URLS=" + peerURL,
128+
"ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380",
129+
},
130+
}, func(config *docker.HostConfig) {
131+
config.AutoRemove = autoRemove
132+
config.RestartPolicy = docker.RestartPolicy{Name: "no"}
133+
})
134+
135+
if err != nil {
136+
return nil, nil, teardown, fmt.Errorf("failed to start etcd-%d: %w", i, err)
137+
}
138+
}
139+
140+
teardown = func() {
141+
for _, c := range containers {
142+
if err := pool.Purge(c); err != nil {
143+
log.WithError(err).Errorf("failed to cleanup %s resource", c.Container.Name)
144+
}
145+
}
146+
147+
cleanupNetwork()
148+
}
149+
150+
endpoints := make([]string, size)
151+
for i := range endpoints {
152+
endpoints[i] = fmt.Sprintf("localhost:%s", containers[i].GetPort("2379/tcp"))
153+
}
154+
155+
client, err = v3.New(v3.Config{
156+
Endpoints: endpoints,
157+
})
158+
if err != nil {
159+
return nil, nil, teardown, fmt.Errorf("failed to create v3 client: %w", err)
160+
}
161+
162+
err = pool.Retry(func() error {
163+
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
164+
defer cancel()
165+
166+
_, err = client.Get(ctx, "__startup_test")
167+
return err
168+
})
169+
if err != nil {
170+
return nil, nil, teardown, fmt.Errorf("failed waiting for stable connection: %w", err)
171+
}
172+
173+
return client, containers, teardown, nil
174+
}
175+
176+
func randClusterName() (string, error) {
177+
var b [4]byte
178+
_, err := rand.Read(b[:])
179+
if err != nil {
180+
return "", err
181+
}
182+
183+
return fmt.Sprintf("etcd-test-%s", hex.EncodeToString(b[:])), nil
184+
}

0 commit comments

Comments
 (0)