1package api 2 3import ( 4 "fmt" 5 "testing" 6 "time" 7 8 "github.com/stretchr/testify/require" 9) 10 11func TestAPI_ConfigEntry_DiscoveryChain(t *testing.T) { 12 t.Parallel() 13 c, s := makeClient(t) 14 defer s.Stop() 15 16 config_entries := c.ConfigEntries() 17 18 verifyResolver := func(t *testing.T, initial ConfigEntry) { 19 t.Helper() 20 require.IsType(t, &ServiceResolverConfigEntry{}, initial) 21 testEntry := initial.(*ServiceResolverConfigEntry) 22 23 // set it 24 _, wm, err := config_entries.Set(testEntry, nil) 25 require.NoError(t, err) 26 require.NotNil(t, wm) 27 require.NotEqual(t, 0, wm.RequestTime) 28 29 // get it 30 entry, qm, err := config_entries.Get(ServiceResolver, testEntry.Name, nil) 31 require.NoError(t, err) 32 require.NotNil(t, qm) 33 require.NotEqual(t, 0, qm.RequestTime) 34 35 // generic verification 36 require.Equal(t, testEntry.Meta, entry.GetMeta()) 37 38 // verify it 39 readResolver, ok := entry.(*ServiceResolverConfigEntry) 40 require.True(t, ok) 41 readResolver.ModifyIndex = 0 // reset for Equals() 42 readResolver.CreateIndex = 0 // reset for Equals() 43 44 require.Equal(t, testEntry, readResolver) 45 46 // TODO(rb): cas? 47 // TODO(rb): list? 48 } 49 50 verifySplitter := func(t *testing.T, initial ConfigEntry) { 51 t.Helper() 52 require.IsType(t, &ServiceSplitterConfigEntry{}, initial) 53 testEntry := initial.(*ServiceSplitterConfigEntry) 54 55 // set it 56 _, wm, err := config_entries.Set(testEntry, nil) 57 require.NoError(t, err) 58 require.NotNil(t, wm) 59 require.NotEqual(t, 0, wm.RequestTime) 60 61 // get it 62 entry, qm, err := config_entries.Get(ServiceSplitter, testEntry.Name, nil) 63 require.NoError(t, err) 64 require.NotNil(t, qm) 65 require.NotEqual(t, 0, qm.RequestTime) 66 67 // generic verification 68 require.Equal(t, testEntry.Meta, entry.GetMeta()) 69 70 // verify it 71 readSplitter, ok := entry.(*ServiceSplitterConfigEntry) 72 require.True(t, ok) 73 readSplitter.ModifyIndex = 0 // reset for Equals() 74 readSplitter.CreateIndex = 0 // reset for Equals() 75 76 require.Equal(t, testEntry, readSplitter) 77 78 // TODO(rb): cas? 79 // TODO(rb): list? 80 } 81 82 verifyRouter := func(t *testing.T, initial ConfigEntry) { 83 t.Helper() 84 require.IsType(t, &ServiceRouterConfigEntry{}, initial) 85 testEntry := initial.(*ServiceRouterConfigEntry) 86 87 // set it 88 _, wm, err := config_entries.Set(testEntry, nil) 89 require.NoError(t, err) 90 require.NotNil(t, wm) 91 require.NotEqual(t, 0, wm.RequestTime) 92 93 // get it 94 entry, qm, err := config_entries.Get(ServiceRouter, testEntry.Name, nil) 95 require.NoError(t, err) 96 require.NotNil(t, qm) 97 require.NotEqual(t, 0, qm.RequestTime) 98 99 // generic verification 100 require.Equal(t, testEntry.Meta, entry.GetMeta()) 101 102 // verify it 103 readRouter, ok := entry.(*ServiceRouterConfigEntry) 104 require.True(t, ok) 105 readRouter.ModifyIndex = 0 // reset for Equals() 106 readRouter.CreateIndex = 0 // reset for Equals() 107 108 require.Equal(t, testEntry, readRouter) 109 110 // TODO(rb): cas? 111 // TODO(rb): list? 112 } 113 114 // First set the necessary protocols to allow advanced routing features. 115 for _, service := range []string{ 116 "test-failover", 117 "test-redirect", 118 "alternate", 119 "test-split", 120 "test-route", 121 } { 122 serviceDefaults := &ServiceConfigEntry{ 123 Kind: ServiceDefaults, 124 Name: service, 125 Protocol: "http", 126 } 127 _, _, err := config_entries.Set(serviceDefaults, nil) 128 require.NoError(t, err) 129 } 130 131 // NOTE: Due to service graph validation, these have to happen in a specific order. 132 for _, tc := range []struct { 133 name string 134 entry ConfigEntry 135 verify func(t *testing.T, initial ConfigEntry) 136 }{ 137 { 138 name: "failover", 139 entry: &ServiceResolverConfigEntry{ 140 Kind: ServiceResolver, 141 Name: "test-failover", 142 Namespace: defaultNamespace, 143 DefaultSubset: "v1", 144 Subsets: map[string]ServiceResolverSubset{ 145 "v1": { 146 Filter: "Service.Meta.version == v1", 147 }, 148 "v2": { 149 Filter: "Service.Meta.version == v2", 150 }, 151 }, 152 Failover: map[string]ServiceResolverFailover{ 153 "*": { 154 Datacenters: []string{"dc2"}, 155 }, 156 "v1": { 157 Service: "alternate", 158 Namespace: defaultNamespace, 159 }, 160 }, 161 ConnectTimeout: 5 * time.Second, 162 Meta: map[string]string{ 163 "foo": "bar", 164 "gir": "zim", 165 }, 166 }, 167 verify: verifyResolver, 168 }, 169 { 170 name: "redirect", 171 entry: &ServiceResolverConfigEntry{ 172 Kind: ServiceResolver, 173 Name: "test-redirect", 174 Namespace: defaultNamespace, 175 Redirect: &ServiceResolverRedirect{ 176 Service: "test-failover", 177 ServiceSubset: "v2", 178 Namespace: defaultNamespace, 179 Datacenter: "d", 180 }, 181 }, 182 verify: verifyResolver, 183 }, 184 { 185 name: "mega splitter", // use one mega object to avoid multiple trips 186 entry: &ServiceSplitterConfigEntry{ 187 Kind: ServiceSplitter, 188 Name: "test-split", 189 Namespace: defaultNamespace, 190 Splits: []ServiceSplit{ 191 { 192 Weight: 90, 193 Service: "test-failover", 194 ServiceSubset: "v1", 195 Namespace: defaultNamespace, 196 }, 197 { 198 Weight: 10, 199 Service: "test-redirect", 200 Namespace: defaultNamespace, 201 }, 202 }, 203 Meta: map[string]string{ 204 "foo": "bar", 205 "gir": "zim", 206 }, 207 }, 208 verify: verifySplitter, 209 }, 210 { 211 name: "mega router", // use one mega object to avoid multiple trips 212 entry: &ServiceRouterConfigEntry{ 213 Kind: ServiceRouter, 214 Name: "test-route", 215 Namespace: defaultNamespace, 216 Routes: []ServiceRoute{ 217 { 218 Match: &ServiceRouteMatch{ 219 HTTP: &ServiceRouteHTTPMatch{ 220 PathPrefix: "/prefix", 221 Header: []ServiceRouteHTTPMatchHeader{ 222 {Name: "x-debug", Exact: "1"}, 223 }, 224 QueryParam: []ServiceRouteHTTPMatchQueryParam{ 225 {Name: "debug", Exact: "1"}, 226 }, 227 }, 228 }, 229 Destination: &ServiceRouteDestination{ 230 Service: "test-failover", 231 ServiceSubset: "v2", 232 Namespace: defaultNamespace, 233 PrefixRewrite: "/", 234 RequestTimeout: 5 * time.Second, 235 NumRetries: 5, 236 RetryOnConnectFailure: true, 237 RetryOnStatusCodes: []uint32{500, 503, 401}, 238 }, 239 }, 240 }, 241 Meta: map[string]string{ 242 "foo": "bar", 243 "gir": "zim", 244 }, 245 }, 246 verify: verifyRouter, 247 }, 248 } { 249 tc := tc 250 name := fmt.Sprintf("%s:%s: %s", tc.entry.GetKind(), tc.entry.GetName(), tc.name) 251 ok := t.Run(name, func(t *testing.T) { 252 tc.verify(t, tc.entry) 253 }) 254 require.True(t, ok, "subtest %q failed so aborting remainder", name) 255 } 256} 257 258func TestAPI_ConfigEntry_ServiceResolver_LoadBalancer(t *testing.T) { 259 t.Parallel() 260 c, s := makeClient(t) 261 defer s.Stop() 262 263 config_entries := c.ConfigEntries() 264 265 verifyResolver := func(t *testing.T, initial ConfigEntry) { 266 t.Helper() 267 require.IsType(t, &ServiceResolverConfigEntry{}, initial) 268 testEntry := initial.(*ServiceResolverConfigEntry) 269 270 // set it 271 _, wm, err := config_entries.Set(testEntry, nil) 272 require.NoError(t, err) 273 require.NotNil(t, wm) 274 require.NotEqual(t, 0, wm.RequestTime) 275 276 // get it 277 entry, qm, err := config_entries.Get(ServiceResolver, testEntry.Name, nil) 278 require.NoError(t, err) 279 require.NotNil(t, qm) 280 require.NotEqual(t, 0, qm.RequestTime) 281 282 // verify it 283 readResolver, ok := entry.(*ServiceResolverConfigEntry) 284 require.True(t, ok) 285 readResolver.ModifyIndex = 0 // reset for Equals() 286 readResolver.CreateIndex = 0 // reset for Equals() 287 288 require.Equal(t, testEntry, readResolver) 289 } 290 291 // First set the necessary protocols to allow advanced routing features. 292 for _, service := range []string{ 293 "test-least-req", 294 "test-ring-hash", 295 } { 296 serviceDefaults := &ServiceConfigEntry{ 297 Kind: ServiceDefaults, 298 Name: service, 299 Protocol: "http", 300 } 301 _, _, err := config_entries.Set(serviceDefaults, nil) 302 require.NoError(t, err) 303 } 304 305 // NOTE: Due to service graph validation, these have to happen in a specific order. 306 for _, tc := range []struct { 307 name string 308 entry ConfigEntry 309 verify func(t *testing.T, initial ConfigEntry) 310 }{ 311 { 312 name: "least-req", 313 entry: &ServiceResolverConfigEntry{ 314 Kind: ServiceResolver, 315 Name: "test-least-req", 316 Namespace: defaultNamespace, 317 LoadBalancer: &LoadBalancer{ 318 Policy: "least_request", 319 LeastRequestConfig: &LeastRequestConfig{ChoiceCount: 10}, 320 }, 321 }, 322 verify: verifyResolver, 323 }, 324 { 325 name: "ring-hash-with-policies", 326 entry: &ServiceResolverConfigEntry{ 327 Kind: ServiceResolver, 328 Name: "test-ring-hash", 329 Namespace: defaultNamespace, 330 LoadBalancer: &LoadBalancer{ 331 Policy: "ring_hash", 332 RingHashConfig: &RingHashConfig{ 333 MinimumRingSize: 1024 * 2, 334 MaximumRingSize: 1024 * 4, 335 }, 336 HashPolicies: []HashPolicy{ 337 { 338 Field: "header", 339 FieldValue: "my-session-header", 340 Terminal: true, 341 }, 342 { 343 Field: "cookie", 344 FieldValue: "oreo", 345 CookieConfig: &CookieConfig{ 346 Path: "/tray", 347 TTL: 20 * time.Millisecond, 348 }, 349 }, 350 { 351 Field: "cookie", 352 FieldValue: "sugar", 353 CookieConfig: &CookieConfig{ 354 Session: true, 355 Path: "/tin", 356 }, 357 }, 358 { 359 SourceIP: true, 360 }, 361 }, 362 }, 363 }, 364 verify: verifyResolver, 365 }, 366 } { 367 tc := tc 368 name := fmt.Sprintf("%s:%s: %s", tc.entry.GetKind(), tc.entry.GetName(), tc.name) 369 ok := t.Run(name, func(t *testing.T) { 370 tc.verify(t, tc.entry) 371 }) 372 require.True(t, ok, "subtest %q failed so aborting remainder", name) 373 } 374} 375