1// +build integration
2
3package etcdv3
4
5import (
6	"context"
7	"io"
8	"os"
9	"testing"
10	"time"
11
12	"github.com/go-kit/kit/endpoint"
13	"github.com/go-kit/kit/log"
14	"github.com/go-kit/kit/sd"
15)
16
17func runIntegration(settings integrationSettings, client Client, service Service, t *testing.T) {
18	// Verify test data is initially empty.
19	entries, err := client.GetEntries(settings.key)
20	if err != nil {
21		t.Fatalf("GetEntries(%q): expected no error, got one: %v", settings.key, err)
22	}
23	if len(entries) > 0 {
24		t.Fatalf("GetEntries(%q): expected no instance entries, got %d", settings.key, len(entries))
25	}
26	t.Logf("GetEntries(%q): %v (OK)", settings.key, entries)
27
28	// Instantiate a new Registrar, passing in test data.
29	registrar := NewRegistrar(
30		client,
31		service,
32		log.With(log.NewLogfmtLogger(os.Stderr), "component", "registrar"),
33	)
34
35	// Register our instance.
36	registrar.Register()
37	t.Log("Registered")
38
39	// Retrieve entries from etcd manually.
40	entries, err = client.GetEntries(settings.key)
41	if err != nil {
42		t.Fatalf("client.GetEntries(%q): %v", settings.key, err)
43	}
44	if want, have := 1, len(entries); want != have {
45		t.Fatalf("client.GetEntries(%q): want %d, have %d", settings.key, want, have)
46	}
47	if want, have := settings.value, entries[0]; want != have {
48		t.Fatalf("want %q, have %q", want, have)
49	}
50
51	instancer, err := NewInstancer(
52		client,
53		settings.prefix,
54		log.With(log.NewLogfmtLogger(os.Stderr), "component", "instancer"),
55	)
56	if err != nil {
57		t.Fatalf("NewInstancer: %v", err)
58	}
59	t.Log("Constructed Instancer OK")
60	defer instancer.Stop()
61
62	endpointer := sd.NewEndpointer(
63		instancer,
64		func(string) (endpoint.Endpoint, io.Closer, error) { return endpoint.Nop, nil, nil },
65		log.With(log.NewLogfmtLogger(os.Stderr), "component", "instancer"),
66	)
67	t.Log("Constructed Endpointer OK")
68	defer endpointer.Close()
69
70	if !within(time.Second, func() bool {
71		endpoints, err := endpointer.Endpoints()
72		return err == nil && len(endpoints) == 1
73	}) {
74		t.Fatal("Endpointer didn't see Register in time")
75	}
76	t.Log("Endpointer saw Register OK")
77
78	// Deregister first instance of test data.
79	registrar.Deregister()
80	t.Log("Deregistered")
81
82	// Check it was deregistered.
83	if !within(time.Second, func() bool {
84		endpoints, err := endpointer.Endpoints()
85		t.Logf("Checking Deregister: len(endpoints) = %d, err = %v", len(endpoints), err)
86		return err == nil && len(endpoints) == 0
87	}) {
88		t.Fatalf("Endpointer didn't see Deregister in time")
89	}
90
91	// Verify test data no longer exists in etcd.
92	entries, err = client.GetEntries(settings.key)
93	if err != nil {
94		t.Fatalf("GetEntries(%q): expected no error, got one: %v", settings.key, err)
95	}
96	if len(entries) > 0 {
97		t.Fatalf("GetEntries(%q): expected no entries, got %v", settings.key, entries)
98	}
99	t.Logf("GetEntries(%q): %v (OK)", settings.key, entries)
100}
101
102type integrationSettings struct {
103	addr     string
104	prefix   string
105	instance string
106	key      string
107	value    string
108}
109
110func testIntegrationSettings(t *testing.T) integrationSettings {
111	var settings integrationSettings
112
113	settings.addr = os.Getenv("ETCD_ADDR")
114	if settings.addr == "" {
115		t.Skip("ETCD_ADDR not set; skipping integration test")
116	}
117
118	settings.prefix = "/services/foosvc/" // known at compile time
119	settings.instance = "1.2.3.4:8080"    // taken from runtime or platform, somehow
120	settings.key = settings.prefix + settings.instance
121	settings.value = "http://" + settings.instance // based on our transport
122
123	return settings
124}
125
126// Package sd/etcd provides a wrapper around the etcd key/value store. This
127// example assumes the user has an instance of etcd installed and running
128// locally on port 2379.
129func TestIntegration(t *testing.T) {
130	settings := testIntegrationSettings(t)
131	client, err := NewClient(context.Background(), []string{settings.addr}, ClientOptions{
132		DialTimeout:   2 * time.Second,
133		DialKeepAlive: 2 * time.Second,
134	})
135	if err != nil {
136		t.Fatalf("NewClient(%q): %v", settings.addr, err)
137	}
138
139	service := Service{
140		Key:   settings.key,
141		Value: settings.value,
142	}
143
144	runIntegration(settings, client, service, t)
145}
146
147func TestIntegrationTTL(t *testing.T) {
148	settings := testIntegrationSettings(t)
149	client, err := NewClient(context.Background(), []string{settings.addr}, ClientOptions{
150		DialTimeout:   2 * time.Second,
151		DialKeepAlive: 2 * time.Second,
152	})
153	if err != nil {
154		t.Fatalf("NewClient(%q): %v", settings.addr, err)
155	}
156
157	service := Service{
158		Key:   settings.key,
159		Value: settings.value,
160		TTL:   NewTTLOption(time.Second*3, time.Second*10),
161	}
162	defer client.Deregister(service)
163
164	runIntegration(settings, client, service, t)
165}
166
167func TestIntegrationRegistrarOnly(t *testing.T) {
168	settings := testIntegrationSettings(t)
169	client, err := NewClient(context.Background(), []string{settings.addr}, ClientOptions{
170		DialTimeout:   2 * time.Second,
171		DialKeepAlive: 2 * time.Second,
172	})
173	if err != nil {
174		t.Fatalf("NewClient(%q): %v", settings.addr, err)
175	}
176
177	service := Service{
178		Key:   settings.key,
179		Value: settings.value,
180		TTL:   NewTTLOption(time.Second*3, time.Second*10),
181	}
182	defer client.Deregister(service)
183
184	// Verify test data is initially empty.
185	entries, err := client.GetEntries(settings.key)
186	if err != nil {
187		t.Fatalf("GetEntries(%q): expected no error, got one: %v", settings.key, err)
188	}
189	if len(entries) > 0 {
190		t.Fatalf("GetEntries(%q): expected no instance entries, got %d", settings.key, len(entries))
191	}
192	t.Logf("GetEntries(%q): %v (OK)", settings.key, entries)
193
194	// Instantiate a new Registrar, passing in test data.
195	registrar := NewRegistrar(
196		client,
197		service,
198		log.With(log.NewLogfmtLogger(os.Stderr), "component", "registrar"),
199	)
200
201	// Register our instance.
202	registrar.Register()
203	t.Log("Registered")
204
205	// Deregister our instance. (so we test registrar only scenario)
206	registrar.Deregister()
207	t.Log("Deregistered")
208
209}
210
211func within(d time.Duration, f func() bool) bool {
212	deadline := time.Now().Add(d)
213	for time.Now().Before(deadline) {
214		if f() {
215			return true
216		}
217		time.Sleep(d / 10)
218	}
219	return false
220}
221