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