github.imxd.top/hashicorp/consul@v1.4.5/agent/consul/prepared_query/template_test.go (about) 1 package prepared_query 2 3 import ( 4 "reflect" 5 "strings" 6 "testing" 7 8 "github.com/hashicorp/consul/agent/structs" 9 "github.com/hashicorp/consul/types" 10 "github.com/mitchellh/copystructure" 11 ) 12 13 var ( 14 // bigBench is a test query that uses all the features of templates, not 15 // in a realistic way, but in a complete way. 16 bigBench = &structs.PreparedQuery{ 17 Name: "hello", 18 Template: structs.QueryTemplateOptions{ 19 Type: structs.QueryTemplateTypeNamePrefixMatch, 20 Regexp: "^hello-(.*)-(.*)$", 21 RemoveEmptyTags: true, 22 }, 23 Service: structs.ServiceQuery{ 24 Service: "${name.full}", 25 Failover: structs.QueryDatacenterOptions{ 26 Datacenters: []string{ 27 "${name.full}", 28 "${name.prefix}", 29 "${name.suffix}", 30 "${match(0)}", 31 "${match(1)}", 32 "${match(2)}", 33 "${agent.segment}", 34 }, 35 }, 36 IgnoreCheckIDs: []types.CheckID{ 37 "${name.full}", 38 "${name.prefix}", 39 "${name.suffix}", 40 "${match(0)}", 41 "${match(1)}", 42 "${match(2)}", 43 "${agent.segment}", 44 }, 45 Tags: []string{ 46 "${name.full}", 47 "${name.prefix}", 48 "${name.suffix}", 49 "${match(0)}", 50 "${match(1)}", 51 "${match(2)}", 52 "${agent.segment}", 53 }, 54 NodeMeta: map[string]string{ 55 "foo": "${name.prefix}", 56 "bar": "${match(0)}", 57 "baz": "${match(1)}", 58 "zoo": "${agent.segment}", 59 }, 60 }, 61 } 62 63 // smallBench is a small prepared query just for doing geo failover. This 64 // is a minimal, useful configuration. 65 smallBench = &structs.PreparedQuery{ 66 Name: "", 67 Template: structs.QueryTemplateOptions{ 68 Type: structs.QueryTemplateTypeNamePrefixMatch, 69 }, 70 Service: structs.ServiceQuery{ 71 Service: "${name.full}", 72 Failover: structs.QueryDatacenterOptions{ 73 Datacenters: []string{ 74 "dc1", 75 "dc2", 76 "dc3", 77 }, 78 }, 79 }, 80 } 81 ) 82 83 func compileBench(b *testing.B, query *structs.PreparedQuery) { 84 for i := 0; i < b.N; i++ { 85 _, err := Compile(query) 86 if err != nil { 87 b.Fatalf("err: %v", err) 88 } 89 } 90 } 91 92 func renderBench(b *testing.B, query *structs.PreparedQuery) { 93 compiled, err := Compile(query) 94 if err != nil { 95 b.Fatalf("err: %v", err) 96 } 97 98 for i := 0; i < b.N; i++ { 99 _, err := compiled.Render("hello-bench-mark", structs.QuerySource{}) 100 if err != nil { 101 b.Fatalf("err: %v", err) 102 } 103 } 104 } 105 106 func BenchmarkTemplate_CompileSmall(b *testing.B) { 107 compileBench(b, smallBench) 108 } 109 110 func BenchmarkTemplate_CompileBig(b *testing.B) { 111 compileBench(b, bigBench) 112 } 113 114 func BenchmarkTemplate_RenderSmall(b *testing.B) { 115 renderBench(b, smallBench) 116 } 117 118 func BenchmarkTemplate_RenderBig(b *testing.B) { 119 renderBench(b, bigBench) 120 } 121 122 func TestTemplate_Compile(t *testing.T) { 123 // Start with an empty query that's not even a template. 124 query := &structs.PreparedQuery{} 125 _, err := Compile(query) 126 if err == nil || !strings.Contains(err.Error(), "Bad Template") { 127 t.Fatalf("bad: %v", err) 128 } 129 if IsTemplate(query) { 130 t.Fatalf("should not be a template") 131 } 132 133 // Make it a basic template, keeping a copy before we compile. 134 query.Template.Type = structs.QueryTemplateTypeNamePrefixMatch 135 query.Template.Regexp = "^(hello)there$" 136 query.Service.Service = "${name.full}" 137 query.Service.IgnoreCheckIDs = []types.CheckID{"${match(1)}", "${agent.segment}"} 138 query.Service.Tags = []string{"${match(1)}", "${agent.segment}"} 139 backup, err := copystructure.Copy(query) 140 if err != nil { 141 t.Fatalf("err: %v", err) 142 } 143 ct, err := Compile(query) 144 if err != nil { 145 t.Fatalf("err: %v", err) 146 } 147 if !IsTemplate(query) { 148 t.Fatalf("should be a template") 149 } 150 151 // Do a sanity check render on it. 152 actual, err := ct.Render("hellothere", structs.QuerySource{Segment: "segment-foo"}) 153 if err != nil { 154 t.Fatalf("err: %v", err) 155 } 156 157 // See if it rendered correctly. 158 expected := &structs.PreparedQuery{ 159 Template: structs.QueryTemplateOptions{ 160 Type: structs.QueryTemplateTypeNamePrefixMatch, 161 Regexp: "^(hello)there$", 162 }, 163 Service: structs.ServiceQuery{ 164 Service: "hellothere", 165 IgnoreCheckIDs: []types.CheckID{ 166 "hello", 167 "segment-foo", 168 }, 169 Tags: []string{ 170 "hello", 171 "segment-foo", 172 }, 173 }, 174 } 175 if !reflect.DeepEqual(actual, expected) { 176 t.Fatalf("bad: %#v", actual) 177 } 178 179 // Prove that it didn't alter the definition we compiled. 180 if !reflect.DeepEqual(query, backup.(*structs.PreparedQuery)) { 181 t.Fatalf("bad: %#v", query) 182 } 183 184 // Try a bad HIL interpolation (syntax error). 185 query.Service.Service = "${name.full" 186 _, err = Compile(query) 187 if err == nil || !strings.Contains(err.Error(), "Bad format") { 188 t.Fatalf("bad: %v", err) 189 } 190 191 // Try a bad HIL interpolation (syntax ok but unknown variable). 192 query.Service.Service = "${name.nope}" 193 _, err = Compile(query) 194 if err == nil || !strings.Contains(err.Error(), "unknown variable") { 195 t.Fatalf("bad: %v", err) 196 } 197 198 // Try a bad regexp. 199 query.Template.Regexp = "^(nope$" 200 query.Service.Service = "${name.full}" 201 _, err = Compile(query) 202 if err == nil || !strings.Contains(err.Error(), "Bad Regexp") { 203 t.Fatalf("bad: %v", err) 204 } 205 } 206 207 func TestTemplate_Render(t *testing.T) { 208 // Try a noop template that is all static. 209 { 210 query := &structs.PreparedQuery{ 211 Template: structs.QueryTemplateOptions{ 212 Type: structs.QueryTemplateTypeNamePrefixMatch, 213 }, 214 Service: structs.ServiceQuery{ 215 Service: "hellothere", 216 }, 217 } 218 ct, err := Compile(query) 219 if err != nil { 220 t.Fatalf("err: %v", err) 221 } 222 223 actual, err := ct.Render("unused", structs.QuerySource{}) 224 if err != nil { 225 t.Fatalf("err: %v", err) 226 } 227 if !reflect.DeepEqual(actual, query) { 228 t.Fatalf("bad: %#v", actual) 229 } 230 } 231 232 // Try all the variables and functions. 233 query := &structs.PreparedQuery{ 234 Name: "hello-", 235 Template: structs.QueryTemplateOptions{ 236 Type: structs.QueryTemplateTypeNamePrefixMatch, 237 Regexp: "^(.*?)-(.*?)-(.*)$", 238 }, 239 Service: structs.ServiceQuery{ 240 Service: "${name.prefix} xxx ${name.full} xxx ${name.suffix} xxx ${agent.segment}", 241 Tags: []string{ 242 "${match(-1)}", 243 "${match(0)}", 244 "${match(1)}", 245 "${match(2)}", 246 "${match(3)}", 247 "${match(4)}", 248 "${40 + 2}", 249 }, 250 NodeMeta: map[string]string{"foo": "${match(1)}"}, 251 }, 252 } 253 ct, err := Compile(query) 254 if err != nil { 255 t.Fatalf("err: %v", err) 256 } 257 258 // Run a case that matches the regexp. 259 { 260 actual, err := ct.Render("hello-foo-bar-none", structs.QuerySource{Segment: "segment-bar"}) 261 if err != nil { 262 t.Fatalf("err: %v", err) 263 } 264 expected := &structs.PreparedQuery{ 265 Name: "hello-", 266 Template: structs.QueryTemplateOptions{ 267 Type: structs.QueryTemplateTypeNamePrefixMatch, 268 Regexp: "^(.*?)-(.*?)-(.*)$", 269 }, 270 Service: structs.ServiceQuery{ 271 Service: "hello- xxx hello-foo-bar-none xxx foo-bar-none xxx segment-bar", 272 Tags: []string{ 273 "", 274 "hello-foo-bar-none", 275 "hello", 276 "foo", 277 "bar-none", 278 "", 279 "42", 280 }, 281 NodeMeta: map[string]string{"foo": "hello"}, 282 }, 283 } 284 if !reflect.DeepEqual(actual, expected) { 285 t.Fatalf("bad: %#v", actual) 286 } 287 } 288 289 // Run a case that doesn't match the regexp 290 { 291 actual, err := ct.Render("hello-nope", structs.QuerySource{Segment: "segment-bar"}) 292 if err != nil { 293 t.Fatalf("err: %v", err) 294 } 295 expected := &structs.PreparedQuery{ 296 Name: "hello-", 297 Template: structs.QueryTemplateOptions{ 298 Type: structs.QueryTemplateTypeNamePrefixMatch, 299 Regexp: "^(.*?)-(.*?)-(.*)$", 300 }, 301 Service: structs.ServiceQuery{ 302 Service: "hello- xxx hello-nope xxx nope xxx segment-bar", 303 Tags: []string{ 304 "", 305 "", 306 "", 307 "", 308 "", 309 "", 310 "42", 311 }, 312 NodeMeta: map[string]string{"foo": ""}, 313 }, 314 } 315 if !reflect.DeepEqual(actual, expected) { 316 t.Fatalf("bad: %#v", actual) 317 } 318 } 319 320 // Try all the variables and functions, removing empty tags. 321 query = &structs.PreparedQuery{ 322 Name: "hello-", 323 Template: structs.QueryTemplateOptions{ 324 Type: structs.QueryTemplateTypeNamePrefixMatch, 325 Regexp: "^(.*?)-(.*?)-(.*)$", 326 RemoveEmptyTags: true, 327 }, 328 Service: structs.ServiceQuery{ 329 Service: "${name.prefix} xxx ${name.full} xxx ${name.suffix} xxx ${agent.segment}", 330 Tags: []string{ 331 "${match(-1)}", 332 "${match(0)}", 333 "${match(1)}", 334 "${match(2)}", 335 "${match(3)}", 336 "${match(4)}", 337 "${40 + 2}", 338 }, 339 }, 340 } 341 ct, err = Compile(query) 342 if err != nil { 343 t.Fatalf("err: %v", err) 344 } 345 346 // Run a case that matches the regexp, removing empty tags. 347 { 348 actual, err := ct.Render("hello-foo-bar-none", structs.QuerySource{Segment: "segment-baz"}) 349 if err != nil { 350 t.Fatalf("err: %v", err) 351 } 352 expected := &structs.PreparedQuery{ 353 Name: "hello-", 354 Template: structs.QueryTemplateOptions{ 355 Type: structs.QueryTemplateTypeNamePrefixMatch, 356 Regexp: "^(.*?)-(.*?)-(.*)$", 357 RemoveEmptyTags: true, 358 }, 359 Service: structs.ServiceQuery{ 360 Service: "hello- xxx hello-foo-bar-none xxx foo-bar-none xxx segment-baz", 361 Tags: []string{ 362 "hello-foo-bar-none", 363 "hello", 364 "foo", 365 "bar-none", 366 "42", 367 }, 368 }, 369 } 370 if !reflect.DeepEqual(actual, expected) { 371 t.Fatalf("bad:\n%#v\nexpected:\n%#v\n", actual, expected) 372 } 373 } 374 375 // Run a case that doesn't match the regexp, removing empty tags. 376 { 377 actual, err := ct.Render("hello-nope", structs.QuerySource{Segment: "segment-baz"}) 378 if err != nil { 379 t.Fatalf("err: %v", err) 380 } 381 expected := &structs.PreparedQuery{ 382 Name: "hello-", 383 Template: structs.QueryTemplateOptions{ 384 Type: structs.QueryTemplateTypeNamePrefixMatch, 385 Regexp: "^(.*?)-(.*?)-(.*)$", 386 RemoveEmptyTags: true, 387 }, 388 Service: structs.ServiceQuery{ 389 Service: "hello- xxx hello-nope xxx nope xxx segment-baz", 390 Tags: []string{ 391 "42", 392 }, 393 }, 394 } 395 if !reflect.DeepEqual(actual, expected) { 396 t.Fatalf("bad: %#v", actual) 397 } 398 } 399 }