github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/client/fingerprint/consul_test.go (about) 1 package fingerprint 2 3 import ( 4 "io" 5 "io/ioutil" 6 "net/http" 7 "net/http/httptest" 8 "strings" 9 "testing" 10 11 "github.com/hashicorp/nomad/ci" 12 "github.com/hashicorp/nomad/client/config" 13 agentconsul "github.com/hashicorp/nomad/command/agent/consul" 14 "github.com/hashicorp/nomad/helper/testlog" 15 "github.com/hashicorp/nomad/nomad/structs" 16 "github.com/stretchr/testify/require" 17 ) 18 19 // fakeConsul creates an HTTP server mimicking Consul /v1/agent/self endpoint on 20 // the first request, and alternates between success and failure responses on 21 // subsequent requests 22 func fakeConsul(payload string) (*httptest.Server, *config.Config) { 23 working := true 24 25 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 26 if working { 27 _, _ = io.WriteString(w, payload) 28 working = false 29 } else { 30 w.WriteHeader(http.StatusInternalServerError) 31 working = true 32 } 33 })) 34 35 cfg := config.DefaultConfig() 36 cfg.ConsulConfig.Addr = strings.TrimPrefix(ts.URL, `http://`) 37 return ts, cfg 38 } 39 40 func fakeConsulPayload(t *testing.T, filename string) string { 41 b, err := ioutil.ReadFile(filename) 42 require.NoError(t, err) 43 return string(b) 44 } 45 46 func newConsulFingerPrint(t *testing.T) *ConsulFingerprint { 47 return NewConsulFingerprint(testlog.HCLogger(t)).(*ConsulFingerprint) 48 } 49 50 func TestConsulFingerprint_server(t *testing.T) { 51 ci.Parallel(t) 52 53 fp := newConsulFingerPrint(t) 54 55 t.Run("is server", func(t *testing.T) { 56 s, ok := fp.server(agentconsul.Self{ 57 "Config": {"Server": true}, 58 }) 59 require.True(t, ok) 60 require.Equal(t, "true", s) 61 }) 62 63 t.Run("is not server", func(t *testing.T) { 64 s, ok := fp.server(agentconsul.Self{ 65 "Config": {"Server": false}, 66 }) 67 require.True(t, ok) 68 require.Equal(t, "false", s) 69 }) 70 71 t.Run("missing", func(t *testing.T) { 72 _, ok := fp.server(agentconsul.Self{ 73 "Config": {}, 74 }) 75 require.False(t, ok) 76 }) 77 78 t.Run("malformed", func(t *testing.T) { 79 _, ok := fp.server(agentconsul.Self{ 80 "Config": {"Server": 9000}, 81 }) 82 require.False(t, ok) 83 }) 84 } 85 86 func TestConsulFingerprint_version(t *testing.T) { 87 ci.Parallel(t) 88 89 fp := newConsulFingerPrint(t) 90 91 t.Run("oss", func(t *testing.T) { 92 v, ok := fp.version(agentconsul.Self{ 93 "Config": {"Version": "v1.9.5"}, 94 }) 95 require.True(t, ok) 96 require.Equal(t, "v1.9.5", v) 97 }) 98 99 t.Run("ent", func(t *testing.T) { 100 v, ok := fp.version(agentconsul.Self{ 101 "Config": {"Version": "v1.9.5+ent"}, 102 }) 103 require.True(t, ok) 104 require.Equal(t, "v1.9.5+ent", v) 105 }) 106 107 t.Run("missing", func(t *testing.T) { 108 _, ok := fp.version(agentconsul.Self{ 109 "Config": {}, 110 }) 111 require.False(t, ok) 112 }) 113 114 t.Run("malformed", func(t *testing.T) { 115 _, ok := fp.version(agentconsul.Self{ 116 "Config": {"Version": 9000}, 117 }) 118 require.False(t, ok) 119 }) 120 } 121 122 func TestConsulFingerprint_sku(t *testing.T) { 123 ci.Parallel(t) 124 125 fp := newConsulFingerPrint(t) 126 127 t.Run("oss", func(t *testing.T) { 128 s, ok := fp.sku(agentconsul.Self{ 129 "Config": {"Version": "v1.9.5"}, 130 }) 131 require.True(t, ok) 132 require.Equal(t, "oss", s) 133 }) 134 135 t.Run("oss dev", func(t *testing.T) { 136 s, ok := fp.sku(agentconsul.Self{ 137 "Config": {"Version": "v1.9.5-dev"}, 138 }) 139 require.True(t, ok) 140 require.Equal(t, "oss", s) 141 }) 142 143 t.Run("ent", func(t *testing.T) { 144 s, ok := fp.sku(agentconsul.Self{ 145 "Config": {"Version": "v1.9.5+ent"}, 146 }) 147 require.True(t, ok) 148 require.Equal(t, "ent", s) 149 }) 150 151 t.Run("ent dev", func(t *testing.T) { 152 s, ok := fp.sku(agentconsul.Self{ 153 "Config": {"Version": "v1.9.5+ent-dev"}, 154 }) 155 require.True(t, ok) 156 require.Equal(t, "ent", s) 157 }) 158 159 t.Run("missing", func(t *testing.T) { 160 _, ok := fp.sku(agentconsul.Self{ 161 "Config": {}, 162 }) 163 require.False(t, ok) 164 }) 165 166 t.Run("malformed", func(t *testing.T) { 167 _, ok := fp.sku(agentconsul.Self{ 168 "Config": {"Version": "***"}, 169 }) 170 require.False(t, ok) 171 }) 172 } 173 174 func TestConsulFingerprint_revision(t *testing.T) { 175 ci.Parallel(t) 176 177 fp := newConsulFingerPrint(t) 178 179 t.Run("ok", func(t *testing.T) { 180 r, ok := fp.revision(agentconsul.Self{ 181 "Config": {"Revision": "3c1c22679"}, 182 }) 183 require.True(t, ok) 184 require.Equal(t, "3c1c22679", r) 185 }) 186 187 t.Run("malformed", func(t *testing.T) { 188 _, ok := fp.revision(agentconsul.Self{ 189 "Config": {"Revision": 9000}, 190 }) 191 require.False(t, ok) 192 }) 193 194 t.Run("missing", func(t *testing.T) { 195 _, ok := fp.revision(agentconsul.Self{ 196 "Config": {}, 197 }) 198 require.False(t, ok) 199 }) 200 } 201 202 func TestConsulFingerprint_dc(t *testing.T) { 203 ci.Parallel(t) 204 205 fp := newConsulFingerPrint(t) 206 207 t.Run("ok", func(t *testing.T) { 208 dc, ok := fp.dc(agentconsul.Self{ 209 "Config": {"Datacenter": "dc1"}, 210 }) 211 require.True(t, ok) 212 require.Equal(t, "dc1", dc) 213 }) 214 215 t.Run("malformed", func(t *testing.T) { 216 _, ok := fp.dc(agentconsul.Self{ 217 "Config": {"Datacenter": 9000}, 218 }) 219 require.False(t, ok) 220 }) 221 222 t.Run("missing", func(t *testing.T) { 223 _, ok := fp.dc(agentconsul.Self{ 224 "Config": {}, 225 }) 226 require.False(t, ok) 227 }) 228 } 229 230 func TestConsulFingerprint_segment(t *testing.T) { 231 ci.Parallel(t) 232 233 fp := newConsulFingerPrint(t) 234 235 t.Run("ok", func(t *testing.T) { 236 s, ok := fp.segment(agentconsul.Self{ 237 "Member": {"Tags": map[string]interface{}{"segment": "seg1"}}, 238 }) 239 require.True(t, ok) 240 require.Equal(t, "seg1", s) 241 }) 242 243 t.Run("segment missing", func(t *testing.T) { 244 _, ok := fp.segment(agentconsul.Self{ 245 "Member": {"Tags": map[string]interface{}{}}, 246 }) 247 require.False(t, ok) 248 }) 249 250 t.Run("tags missing", func(t *testing.T) { 251 _, ok := fp.segment(agentconsul.Self{ 252 "Member": {}, 253 }) 254 require.False(t, ok) 255 }) 256 257 t.Run("malformed", func(t *testing.T) { 258 _, ok := fp.segment(agentconsul.Self{ 259 "Member": {"Tags": map[string]interface{}{"segment": 9000}}, 260 }) 261 require.False(t, ok) 262 }) 263 } 264 265 func TestConsulFingerprint_connect(t *testing.T) { 266 ci.Parallel(t) 267 268 fp := newConsulFingerPrint(t) 269 270 t.Run("connect enabled", func(t *testing.T) { 271 s, ok := fp.connect(agentconsul.Self{ 272 "DebugConfig": {"ConnectEnabled": true}, 273 }) 274 require.True(t, ok) 275 require.Equal(t, "true", s) 276 }) 277 278 t.Run("connect not enabled", func(t *testing.T) { 279 s, ok := fp.connect(agentconsul.Self{ 280 "DebugConfig": {"ConnectEnabled": false}, 281 }) 282 require.True(t, ok) 283 require.Equal(t, "false", s) 284 }) 285 286 t.Run("connect missing", func(t *testing.T) { 287 _, ok := fp.connect(agentconsul.Self{ 288 "DebugConfig": {}, 289 }) 290 require.False(t, ok) 291 }) 292 } 293 294 func TestConsulFingerprint_grpc(t *testing.T) { 295 ci.Parallel(t) 296 297 fp := newConsulFingerPrint(t) 298 299 t.Run("grpc set pre-1.14 http", func(t *testing.T) { 300 s, ok := fp.grpc("http")(agentconsul.Self{ 301 "Config": {"Version": "1.13.3"}, 302 "DebugConfig": {"GRPCPort": 8502.0}, // JSON numbers are floats 303 }) 304 require.True(t, ok) 305 require.Equal(t, "8502", s) 306 }) 307 308 t.Run("grpc disabled pre-1.14 http", func(t *testing.T) { 309 s, ok := fp.grpc("http")(agentconsul.Self{ 310 "Config": {"Version": "1.13.3"}, 311 "DebugConfig": {"GRPCPort": -1.0}, // JSON numbers are floats 312 }) 313 require.True(t, ok) 314 require.Equal(t, "-1", s) 315 }) 316 317 t.Run("grpc set pre-1.14 https", func(t *testing.T) { 318 s, ok := fp.grpc("https")(agentconsul.Self{ 319 "Config": {"Version": "1.13.3"}, 320 "DebugConfig": {"GRPCPort": 8502.0}, // JSON numbers are floats 321 }) 322 require.True(t, ok) 323 require.Equal(t, "8502", s) 324 }) 325 326 t.Run("grpc disabled pre-1.14 https", func(t *testing.T) { 327 s, ok := fp.grpc("https")(agentconsul.Self{ 328 "Config": {"Version": "1.13.3"}, 329 "DebugConfig": {"GRPCPort": -1.0}, // JSON numbers are floats 330 }) 331 require.True(t, ok) 332 require.Equal(t, "-1", s) 333 }) 334 335 t.Run("grpc set post-1.14 http", func(t *testing.T) { 336 s, ok := fp.grpc("http")(agentconsul.Self{ 337 "Config": {"Version": "1.14.0"}, 338 "DebugConfig": {"GRPCPort": 8502.0}, // JSON numbers are floats 339 }) 340 require.True(t, ok) 341 require.Equal(t, "8502", s) 342 }) 343 344 t.Run("grpc disabled post-1.14 http", func(t *testing.T) { 345 s, ok := fp.grpc("http")(agentconsul.Self{ 346 "Config": {"Version": "1.14.0"}, 347 "DebugConfig": {"GRPCPort": -1.0}, // JSON numbers are floats 348 }) 349 require.True(t, ok) 350 require.Equal(t, "-1", s) 351 }) 352 353 t.Run("grpc disabled post-1.14 https", func(t *testing.T) { 354 s, ok := fp.grpc("https")(agentconsul.Self{ 355 "Config": {"Version": "1.14.0"}, 356 "DebugConfig": {"GRPCTLSPort": -1.0}, // JSON numbers are floats 357 }) 358 require.True(t, ok) 359 require.Equal(t, "-1", s) 360 }) 361 362 t.Run("grpc set post-1.14 https", func(t *testing.T) { 363 s, ok := fp.grpc("https")(agentconsul.Self{ 364 "Config": {"Version": "1.14.0"}, 365 "DebugConfig": {"GRPCTLSPort": 8503.0}, // JSON numbers are floats 366 }) 367 require.True(t, ok) 368 require.Equal(t, "8503", s) 369 }) 370 371 t.Run("grpc missing http", func(t *testing.T) { 372 _, ok := fp.grpc("http")(agentconsul.Self{ 373 "DebugConfig": {}, 374 }) 375 require.False(t, ok) 376 }) 377 378 t.Run("grpc missing https", func(t *testing.T) { 379 _, ok := fp.grpc("https")(agentconsul.Self{ 380 "DebugConfig": {}, 381 }) 382 require.False(t, ok) 383 }) 384 } 385 386 func TestConsulFingerprint_namespaces(t *testing.T) { 387 ci.Parallel(t) 388 389 fp := newConsulFingerPrint(t) 390 391 t.Run("supports namespaces", func(t *testing.T) { 392 value, ok := fp.namespaces(agentconsul.Self{ 393 "Stats": {"license": map[string]interface{}{"features": "Automated Backups, Automated Upgrades, Enhanced Read Scalability, Network Segments, Redundancy Zone, Advanced Network Federation, Namespaces, SSO, Audit Logging"}}, 394 }) 395 require.True(t, ok) 396 require.Equal(t, "true", value) 397 }) 398 399 t.Run("no namespaces", func(t *testing.T) { 400 value, ok := fp.namespaces(agentconsul.Self{ 401 "Stats": {"license": map[string]interface{}{"features": "Automated Backups, Automated Upgrades, Enhanced Read Scalability, Network Segments, Redundancy Zone, Advanced Network Federation, SSO, Audit Logging"}}, 402 }) 403 require.True(t, ok) 404 require.Equal(t, "false", value) 405 406 }) 407 408 t.Run("stats missing", func(t *testing.T) { 409 value, ok := fp.namespaces(agentconsul.Self{}) 410 require.True(t, ok) 411 require.Equal(t, "false", value) 412 }) 413 414 t.Run("license missing", func(t *testing.T) { 415 value, ok := fp.namespaces(agentconsul.Self{"Stats": {}}) 416 require.True(t, ok) 417 require.Equal(t, "false", value) 418 }) 419 420 t.Run("features missing", func(t *testing.T) { 421 value, ok := fp.namespaces(agentconsul.Self{"Stats": {"license": map[string]interface{}{}}}) 422 require.True(t, ok) 423 require.Equal(t, "false", value) 424 }) 425 } 426 427 func TestConsulFingerprint_Fingerprint_oss(t *testing.T) { 428 ci.Parallel(t) 429 430 cf := newConsulFingerPrint(t) 431 432 ts, cfg := fakeConsul(fakeConsulPayload(t, "test_fixtures/consul/agent_self_oss.json")) 433 defer ts.Close() 434 435 node := &structs.Node{Attributes: make(map[string]string)} 436 437 // consul not available before first run 438 require.Equal(t, consulUnavailable, cf.lastState) 439 440 // execute first query with good response 441 var resp FingerprintResponse 442 err := cf.Fingerprint(&FingerprintRequest{Config: cfg, Node: node}, &resp) 443 require.NoError(t, err) 444 require.Equal(t, map[string]string{ 445 "consul.datacenter": "dc1", 446 "consul.revision": "3c1c22679", 447 "consul.segment": "seg1", 448 "consul.server": "true", 449 "consul.sku": "oss", 450 "consul.version": "1.9.5", 451 "consul.connect": "true", 452 "consul.grpc": "8502", 453 "consul.ft.namespaces": "false", 454 "unique.consul.name": "HAL9000", 455 }, resp.Attributes) 456 require.True(t, resp.Detected) 457 458 // consul now available 459 require.Equal(t, consulAvailable, cf.lastState) 460 461 var resp2 FingerprintResponse 462 463 // pretend attributes set for failing request 464 node.Attributes["consul.datacenter"] = "foo" 465 node.Attributes["consul.revision"] = "foo" 466 node.Attributes["consul.segment"] = "foo" 467 node.Attributes["consul.server"] = "foo" 468 node.Attributes["consul.sku"] = "foo" 469 node.Attributes["consul.version"] = "foo" 470 node.Attributes["consul.connect"] = "foo" 471 node.Attributes["connect.grpc"] = "foo" 472 node.Attributes["unique.consul.name"] = "foo" 473 474 // execute second query with error 475 err2 := cf.Fingerprint(&FingerprintRequest{Config: cfg, Node: node}, &resp2) 476 require.NoError(t, err2) // does not return error 477 require.Nil(t, resp2.Attributes) // attributes unset so they don't change 478 require.True(t, resp.Detected) // never downgrade 479 480 // consul no longer available 481 require.Equal(t, consulUnavailable, cf.lastState) 482 483 // execute third query no error 484 var resp3 FingerprintResponse 485 err3 := cf.Fingerprint(&FingerprintRequest{Config: cfg, Node: node}, &resp3) 486 require.NoError(t, err3) 487 require.Equal(t, map[string]string{ 488 "consul.datacenter": "dc1", 489 "consul.revision": "3c1c22679", 490 "consul.segment": "seg1", 491 "consul.server": "true", 492 "consul.sku": "oss", 493 "consul.version": "1.9.5", 494 "consul.connect": "true", 495 "consul.grpc": "8502", 496 "consul.ft.namespaces": "false", 497 "unique.consul.name": "HAL9000", 498 }, resp3.Attributes) 499 500 // consul now available again 501 require.Equal(t, consulAvailable, cf.lastState) 502 require.True(t, resp.Detected) 503 } 504 505 func TestConsulFingerprint_Fingerprint_ent(t *testing.T) { 506 ci.Parallel(t) 507 508 cf := newConsulFingerPrint(t) 509 510 ts, cfg := fakeConsul(fakeConsulPayload(t, "test_fixtures/consul/agent_self_ent.json")) 511 defer ts.Close() 512 513 node := &structs.Node{Attributes: make(map[string]string)} 514 515 // consul not available before first run 516 require.Equal(t, consulUnavailable, cf.lastState) 517 518 // execute first query with good response 519 var resp FingerprintResponse 520 err := cf.Fingerprint(&FingerprintRequest{Config: cfg, Node: node}, &resp) 521 require.NoError(t, err) 522 require.Equal(t, map[string]string{ 523 "consul.datacenter": "dc1", 524 "consul.revision": "22ce6c6ad", 525 "consul.segment": "seg1", 526 "consul.server": "true", 527 "consul.sku": "ent", 528 "consul.version": "1.9.5+ent", 529 "consul.ft.namespaces": "true", 530 "consul.connect": "true", 531 "consul.grpc": "8502", 532 "unique.consul.name": "HAL9000", 533 }, resp.Attributes) 534 require.True(t, resp.Detected) 535 536 // consul now available 537 require.Equal(t, consulAvailable, cf.lastState) 538 539 var resp2 FingerprintResponse 540 541 // pretend attributes set for failing request 542 node.Attributes["consul.datacenter"] = "foo" 543 node.Attributes["consul.revision"] = "foo" 544 node.Attributes["consul.segment"] = "foo" 545 node.Attributes["consul.server"] = "foo" 546 node.Attributes["consul.sku"] = "foo" 547 node.Attributes["consul.version"] = "foo" 548 node.Attributes["consul.ft.namespaces"] = "foo" 549 node.Attributes["consul.connect"] = "foo" 550 node.Attributes["connect.grpc"] = "foo" 551 node.Attributes["unique.consul.name"] = "foo" 552 553 // execute second query with error 554 err2 := cf.Fingerprint(&FingerprintRequest{Config: cfg, Node: node}, &resp2) 555 require.NoError(t, err2) // does not return error 556 require.Nil(t, resp2.Attributes) // attributes unset so they don't change 557 require.True(t, resp.Detected) // never downgrade 558 559 // consul no longer available 560 require.Equal(t, consulUnavailable, cf.lastState) 561 562 // execute third query no error 563 var resp3 FingerprintResponse 564 err3 := cf.Fingerprint(&FingerprintRequest{Config: cfg, Node: node}, &resp3) 565 require.NoError(t, err3) 566 require.Equal(t, map[string]string{ 567 "consul.datacenter": "dc1", 568 "consul.revision": "22ce6c6ad", 569 "consul.segment": "seg1", 570 "consul.server": "true", 571 "consul.sku": "ent", 572 "consul.version": "1.9.5+ent", 573 "consul.ft.namespaces": "true", 574 "consul.connect": "true", 575 "consul.grpc": "8502", 576 "unique.consul.name": "HAL9000", 577 }, resp3.Attributes) 578 579 // consul now available again 580 require.Equal(t, consulAvailable, cf.lastState) 581 require.True(t, resp.Detected) 582 }