go.ligato.io/vpp-agent/v3@v3.5.0/tests/e2e/100_agentctl_test.go (about) 1 // Copyright (c) 2019 Cisco and/or its affiliates. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at: 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package e2e 16 17 import ( 18 "bufio" 19 "encoding/json" 20 "os" 21 "strconv" 22 "strings" 23 "testing" 24 25 . "github.com/onsi/gomega" 26 "github.com/onsi/gomega/types" 27 28 . "go.ligato.io/vpp-agent/v3/tests/e2e/e2etest" 29 ) 30 31 func TestAgentCtlCommands(t *testing.T) { 32 ctx := Setup(t) 33 defer ctx.Teardown() 34 35 var err error 36 var stdout, stderr string 37 38 // File created below is required to test `import` action. 39 config1File := ctx.ShareDir + "/agentctl-config1.yaml" 40 _, err = createFileWithContent( 41 config1File, 42 `config/vpp/v2/interfaces/tap1 {"name":"tap1", "type":"TAP", "enabled":true, "ip_addresses":["10.10.10.10/24"], "tap":{"version": "2"}}`, 43 ) 44 ctx.Expect(err).To(BeNil(), "Failed to create file required by one of the tests") 45 // cleanup the file 46 defer func() { 47 err = os.Remove(config1File) 48 ctx.Expect(err).To(BeNil()) 49 }() 50 51 // These update files created below are required to test `get` and `update` action with labels. 52 // All tests using `agentctl get` depend on the existence of these files. 53 nextDummyIf := dummyIfFactory(ctx) 54 updateLabels := []string{"if=dummy", "\"if=dummy\",\"source=test\"", "\"if=differentvalue\",\"source=test\"", "", "\"onlykey=\""} 55 for _, ul := range updateLabels { 56 file, err := createFileWithContent(nextDummyIf()) 57 ctx.Expect(err).To(BeNil(), "Failed to create file required by one of the tests") 58 stdout, _, err = ctx.Agent.ExecCmd("agentctl", "config", "update", file, "--labels="+ul) 59 ctx.Expect(err).ToNot(HaveOccurred()) 60 // ctx.Expect(stderr).To(BeEmpty()) TODO: uncomment this once the warning log has been cleaned up 61 ctx.Expect(stdout).To(ContainSubstring("OK")) 62 63 // cleanup the file 64 defer func() { 65 err = os.Remove(file) 66 ctx.Expect(err).ToNot(HaveOccurred()) 67 }() 68 } 69 70 // Parsing these labels should result in an error. 71 wrongUpdateLabels := []string{"\"=onlyvalue\"", "\"duplicatekey=foo\",\"duplicatekey=bar\"", "\"\""} 72 for _, wul := range wrongUpdateLabels { 73 file, err := createFileWithContent(nextDummyIf()) 74 ctx.Expect(err).To(BeNil(), "Failed to create file required by one of the tests") 75 stdout, _, err = ctx.Agent.ExecCmd("agentctl", "config", "update", file, "--labels="+wul) 76 ctx.Expect(err).To(HaveOccurred()) 77 // ctx.Expect(stderr).To(BeEmpty()) TODO: uncomment this once the warning log has been cleaned up 78 ctx.Expect(stdout).ToNot(ContainSubstring("OK")) 79 80 // cleanup the file 81 defer func() { 82 err = os.Remove(file) 83 ctx.Expect(err).ToNot(HaveOccurred()) 84 }() 85 } 86 87 type KeyVal struct { 88 Key string 89 Value interface{} 90 } 91 tests := []struct { 92 name string 93 cmd string 94 expectErr bool 95 expectNotEmptyStdout bool 96 expectStdout string 97 expectInStdout string 98 expectNotInStdout string 99 expectReStdout string 100 expectNotReStdout string 101 expectInStderr string 102 expectJsonKeyVals []KeyVal 103 }{ 104 { 105 name: "Check if executable is present", 106 cmd: "--help", 107 expectNotEmptyStdout: true, 108 }, 109 { 110 name: "Test `config get`", 111 cmd: "config get", 112 expectInStdout: "type: DUMMY", 113 expectReStdout: "name: dummyif(0|1|2|3|4)", 114 }, 115 { 116 name: "Test `config get`", 117 cmd: "config get --labels=\"io.ligato.from-client=agentctl\"", 118 expectInStdout: "type: DUMMY", 119 expectReStdout: "name: dummyif(3)", 120 expectNotReStdout: "name: dummyif(0|1|2|4)", 121 }, 122 { 123 name: "Test `config get` with full label", 124 cmd: "config get --labels=\"if=dummy\"", 125 expectInStdout: "type: DUMMY", 126 expectReStdout: "name: dummyif(0|1)", 127 expectNotReStdout: "name: dummyif(2|3|4)", 128 }, 129 { 130 name: "Test `config get` with full excluded label", 131 cmd: "config get --labels=\"!if=dummy\"", 132 expectInStdout: "type: DUMMY", 133 expectReStdout: "name: dummyif(2|3|4)", 134 expectNotReStdout: "name: dummyif(0|1)", 135 }, 136 { 137 name: "Test `config get` with the same excluded and included label", 138 cmd: "config get --labels=\"!if\",\"if\"", 139 expectInStdout: "linuxConfig: {}", 140 expectNotReStdout: "name: dummyif(0|1|2|3|4)", 141 }, 142 { 143 name: "Test `config get` with label key", 144 cmd: "config get --labels=\"if\"", 145 expectInStdout: "type: DUMMY", 146 expectReStdout: "name: dummyif(0|1|2)", 147 expectNotReStdout: "name: dummyif(4|5)", 148 }, 149 { 150 name: "Test `config get` with label key", 151 cmd: "config get --labels=\"if=\"", 152 expectInStdout: "type: DUMMY", 153 expectReStdout: "name: dummyif(0|1|2)", 154 expectNotReStdout: "name: dummyif(3|4)", 155 }, 156 { 157 name: "Test `config get` with excluded label key", 158 cmd: "config get --labels=\"!if\"", 159 expectInStdout: "type: DUMMY", 160 expectReStdout: "name: dummyif(4|5)", 161 expectNotReStdout: "name: dummyif(0|1|2)", 162 }, 163 { 164 name: "Test `config get` with excluded label key", 165 cmd: "config get --labels=\"!if=\"", 166 expectInStdout: "type: DUMMY", 167 expectReStdout: "name: dummyif(4|5)", 168 expectNotReStdout: "name: dummyif(0|1|2)", 169 }, 170 { 171 name: "Test `config get` with included and excluded full labels", 172 cmd: "config get --labels=\"!if=dummy\" --labels=\"source=test\"", 173 expectInStdout: "type: DUMMY", 174 expectReStdout: "name: dummyif(2)", 175 expectNotReStdout: "name: dummyif(0|1|3|4)", 176 }, 177 { 178 name: "Test `config get` with multiple full labels", 179 cmd: "config get --labels=\"if=dummy\" --labels=\"source=test\"", 180 expectInStdout: "type: DUMMY", 181 expectReStdout: "name: dummyif(1)", 182 expectNotReStdout: "name: dummyif(0|2|3|4)", 183 }, 184 { 185 name: "Test `config get` with multiple label keys", 186 cmd: "config get --labels=\"if\",\"source\"", 187 expectInStdout: "type: DUMMY", 188 expectReStdout: "name: dummyif(1|2)", 189 expectNotReStdout: "name: dummyif(0|3|4)", 190 }, 191 { 192 name: "Test `config get` with multiple label keys", 193 cmd: "config get --labels=\"if=\",\"source=\"", 194 expectInStdout: "type: DUMMY", 195 expectReStdout: "name: dummyif(1|2)", 196 expectNotReStdout: "name: dummyif(0|3|4)", 197 }, 198 { 199 name: "Test `config get` with multiple label keys", 200 cmd: "config get --labels=\"if\" --labels=\"source=\"", 201 expectInStdout: "type: DUMMY", 202 expectReStdout: "name: dummyif(1|2)", 203 expectNotReStdout: "name: dummyif(0|3|4)", 204 }, 205 { 206 name: "Test `config get` with label key and full label", 207 cmd: "config get --labels=\"if=dummy\",\"source\"", 208 expectInStdout: "type: DUMMY", 209 expectReStdout: "name: dummyif(1)", 210 expectNotReStdout: "name: dummyif(0|2|3|4)", 211 }, 212 { 213 name: "Test `config get` with bad label", 214 cmd: "config get --labels=\"missingkey=missingvalue\"", 215 expectInStdout: "linuxConfig: {}", 216 expectNotReStdout: "name: dummyif(0|1|2|3|4)", 217 }, 218 { 219 name: "Test `config get` with bad label", 220 cmd: "config get --labels=\"missingkey\"", 221 expectInStdout: "linuxConfig: {}", 222 expectNotReStdout: "name: dummyif(0|1|2|3|4)", 223 }, 224 { 225 name: "Test `config get` with bad label", 226 cmd: "config get --labels=\"missingkey\",\"if=dummy\"", 227 expectInStdout: "linuxConfig: {}", 228 expectNotReStdout: "name: dummyif(0|1|2|3|4)", 229 }, 230 { 231 name: "Test `dump all` action", 232 cmd: "dump all", 233 expectInStdout: "type: SOFTWARE_LOOPBACK", 234 }, 235 { 236 name: "Test `dump vpp.*` action", 237 cmd: `dump vpp.*`, 238 expectInStdout: "type: SOFTWARE_LOOPBACK", 239 }, 240 { 241 name: "Test `dump` action with bad model", 242 cmd: "dump NoSuchModel", 243 expectErr: true, 244 expectInStderr: "no matching models found for [\"NoSuchModel\"]", 245 }, 246 { 247 name: "Test `dump` action with one bad model", 248 cmd: "dump NoSuchModel vpp.interfaces", 249 expectInStdout: "type: SOFTWARE_LOOPBACK", 250 }, 251 { 252 name: "Test `dump --view=SB` action", 253 cmd: "dump vpp.interfaces --view=SB", 254 expectInStdout: "type: SOFTWARE_LOOPBACK", 255 }, 256 { 257 name: "Test `dump --view=NB` action", 258 cmd: "dump vpp.interfaces --view=NB", 259 expectReStdout: `MODEL[\s\|]+ORIGIN[\s\|]+VALUE[\s\|]+METADATA`, 260 }, 261 { 262 name: "Test `dump --view=cached` action", 263 cmd: "dump vpp.interfaces --view=cached", 264 expectInStdout: "type: SOFTWARE_LOOPBACK", 265 }, 266 { 267 name: "Test `dump` with JSON format", 268 cmd: "dump vpp.interfaces -f=json", 269 expectReStdout: `"Value": {\s+"name": "UNTAGGED-local0",`, 270 }, 271 { 272 name: "Test `dump` with YAML format", 273 cmd: "dump vpp.interfaces -f=yaml", 274 expectReStdout: `Value:\s+name: UNTAGGED-local0`, 275 }, 276 { 277 name: "Test `dump` with custom format", 278 cmd: `dump vpp.interfaces -f "{{range.}}Name:{{.Value.name}}{{end}}"`, 279 expectStdout: `"Name:UNTAGGED-local0"`, 280 }, 281 { 282 name: "Test `generate` action", 283 cmd: "generate vpp.interfaces", 284 expectNotEmptyStdout: true, 285 }, 286 { 287 name: "Test `generate` action with not exsiting model", 288 cmd: "generate NoSuchModel", 289 expectErr: true, 290 expectInStderr: "no model found for: NoSuchModel", 291 }, 292 { 293 name: "Test `generate` action to yaml", 294 cmd: "generate vpp.interfaces -f=yaml", 295 expectInStdout: "type: UNDEFINED_TYPE", 296 }, 297 { 298 name: "Test `generate` action to json", 299 cmd: "generate vpp.interfaces -f=json", 300 expectJsonKeyVals: []KeyVal{ 301 {"type", "UNDEFINED_TYPE"}, 302 }, 303 }, 304 { 305 name: "Test `generate` action to json (oneline)", 306 cmd: "generate vpp.interfaces -f=json --oneline", 307 expectJsonKeyVals: []KeyVal{ 308 {"type", "UNDEFINED_TYPE"}, 309 }, 310 }, 311 { 312 // This test depends on file (agentctl-config1.yaml) which was created before. 313 name: "Test `import` action", 314 cmd: "import " + config1File, 315 expectErr: true, 316 expectInStderr: "connecting to Etcd failed", 317 }, 318 { 319 // This test depends on file (agentctl-config1.yaml) which was created before. 320 name: "Test `import` action (grpc)", 321 cmd: "import " + config1File + " --grpc", 322 expectStdout: "importing 1 key-value pairs\n - config/vpp/v2/interfaces/tap1\nsending via gRPC\n", 323 }, 324 { 325 name: "Test `kvdb list` action", 326 cmd: "kvdb list", 327 expectErr: true, 328 expectInStderr: "connecting to Etcd failed", 329 }, 330 { 331 name: "Test `log list` action", 332 cmd: "log list", 333 expectReStdout: `agent\s+(trace|debug|info)`, 334 }, 335 { 336 name: "Test `log set` action", 337 cmd: "log set agent debug", 338 expectStdout: "logger agent has been set to level debug\n", 339 }, 340 { 341 // This test depends on previous one. 342 name: "Test `log list` action", 343 cmd: "log list", 344 expectReStdout: `agent\s+debug`, 345 }, 346 { 347 name: "Test `model ls` action", 348 cmd: "model ls", 349 expectReStdout: `linux.interfaces.interface\s+config\s+ligato.linux.interfaces.Interface`, 350 }, 351 { 352 name: "Test `models` action", 353 cmd: "models", 354 expectReStdout: `linux.interfaces.interface\s+config\s+ligato.linux.interfaces.Interface`, 355 }, 356 { 357 name: "Test `model inspect` action", 358 cmd: "model inspect vpp.interfaces", 359 expectInStdout: `"KeyPrefix": "config/vpp/v2/interfaces/",`, 360 }, 361 { 362 name: "Test `model inspect` action (no models)", 363 cmd: "model inspect NoSuchModel", 364 expectErr: true, 365 expectInStderr: "no model found for provided prefix: NoSuchModel", 366 }, 367 { 368 name: "Test `model inspect` action (multiple models)", 369 cmd: "model inspect vpp.", 370 expectErr: true, 371 expectInStderr: "multiple models found with provided prefix: vpp.", 372 }, 373 { 374 name: "Test `status` action", 375 cmd: "status", 376 expectReStdout: `State:\s*OK`, 377 }, 378 { 379 name: "Test `status` action (with format)", 380 cmd: "status -f {{.Status.AgentStatus.State}}", 381 expectStdout: "OK", 382 }, 383 { 384 name: "Test `values` action", 385 cmd: "values", 386 expectReStdout: `vpp.interfaces\s+UNTAGGED-local0\s+obtained`, 387 }, 388 { 389 name: "Test `values` action (with model)", 390 cmd: "values vpp.proxyarp-global", 391 expectReStdout: `vpp.proxyarp-global\s+obtained `, 392 }, 393 { 394 name: "Test `vpp info` action", 395 cmd: "vpp info", 396 expectReStdout: `Version:\s+v\d{2}\.\d{2}`, 397 }, 398 { 399 name: "Test `vpp cli` action", 400 cmd: "vpp cli sh int", 401 expectReStdout: `local0\s+0\s+down\s+0/0/0/0`, 402 }, 403 } 404 405 for _, test := range tests { 406 t.Run(test.name, func(t *testing.T) { 407 g := NewWithT(t) 408 409 stdout, stderr, err = ctx.Agent.ExecCmd("agentctl", strings.Split(test.cmd, " ")...) 410 411 if test.expectErr { 412 g.Expect(err).To(HaveOccurred(), 413 "Expected command `%s` to fail\n", test.cmd) 414 } else { 415 g.Expect(err).ToNot(HaveOccurred(), 416 "Expected command `%s` not to fail, but failed with err: %v\nStderr:\n%s\n", test.cmd, err, stderr) 417 } 418 // Check STDOUT: 419 if test.expectNotEmptyStdout { 420 g.Expect(stdout).ToNot(BeEmpty(), 421 "Stdout should not be empty\n") 422 } 423 if test.expectStdout != "" { 424 g.Expect(stdout).To(Equal(test.expectStdout), 425 "Expected output not equal stdout") 426 } 427 if test.expectInStdout != "" { 428 g.Expect(stdout).To(ContainSubstring(test.expectInStdout), 429 "Expected string not found in stdout") 430 } 431 if test.expectNotInStdout != "" { 432 g.Expect(stdout).ToNot(ContainSubstring(test.expectNotInStdout), 433 "Unexpected string found in stdout") 434 } 435 if test.expectJsonKeyVals != nil { 436 var data map[string]interface{} 437 err := json.Unmarshal([]byte(stdout), &data) 438 if err != nil { 439 t.Fatal(err) 440 } 441 var matchers []types.GomegaMatcher 442 for _, kv := range test.expectJsonKeyVals { 443 matchers = append(matchers, HaveKeyWithValue(kv.Key, kv.Value)) 444 } 445 g.Expect(data).To(SatisfyAll(matchers...), "Expected key-value not found in JSON data from stdout") 446 } 447 if test.expectReStdout != "" { 448 g.Expect(stdout).To(MatchRegexp(test.expectReStdout), 449 "Expect regexp %q to match stdout for command %q, stdout:\n%s", 450 test.expectReStdout, test.cmd, stdout) 451 } 452 if test.expectNotReStdout != "" { 453 g.Expect(stdout).ToNot(MatchRegexp(test.expectNotReStdout), 454 "Expect regexp %q to not match stdout for command %q, stdout:\n%s,", 455 test.expectReStdout, test.cmd, stdout) 456 } 457 // Check STDERR: 458 if test.expectInStderr != "" { 459 g.Expect(stderr).To(ContainSubstring(test.expectInStderr), 460 "Want in stderr: \n%s\nGot stderr: \n%s\n", test.expectInStderr, stderr) 461 } 462 }) 463 } 464 } 465 466 /*func TestAgentCtlSecureGrpcWithClientCertRequired(t *testing.T) { 467 // WARNING: Do not use grpc connection created in `setupE2E` in 468 // this test (though I don't know why you would but anyway). 469 // By default `grpc.Dial` is non-blocking and connecting happens 470 // in the background, so `setupE2E` function does not know about 471 // any errors. With securing grpc on the agent (by replacing 472 // grpc.conf with grpc-secure.conf) that client won't be able 473 // to establish connection because it's not configured for this 474 // secure case. 475 476 t.Log("Replacing `GRPC_CONFIG` value with /etc/grpc-secure-full.conf") 477 defer func(oldVal string) { 478 t.Logf("Setting `GRPC_CONFIG` back to %q", oldVal) 479 os.Setenv("GRPC_CONFIG", oldVal) 480 }(os.Getenv("GRPC_CONFIG")) 481 os.Setenv("GRPC_CONFIG", "/etc/grpc-secure-full.conf") 482 483 ctx := Setup(t) 484 defer ctx.Teardown() 485 486 t.Log("Try without any TLS") 487 _, stderr, err := ctx.ExecCmd( 488 "/agentctl", "--debug", "dump", "vpp.interfaces", 489 ) 490 ctx.Expect(err).To(Not(BeNil())) 491 ctx.Expect(strings.Contains(stderr, "rpc error")).To(BeTrue(), 492 "Want in stderr: \n\"rpc error\"\nGot stderr: \n%s\n", stderr, 493 ) 494 495 t.Log("Try with TLS enabled via flag --insecure-tls, but without cert and key (note: server configured to check those files)") 496 _, stderr, err = ctx.ExecCmd( 497 "/agentctl", "--debug", "--insecure-tls", "dump", "vpp.interfaces", 498 ) 499 ctx.Expect(err).To(Not(BeNil())) 500 ctx.Expect(strings.Contains(stderr, "rpc error")).To(BeTrue(), 501 "Want in stderr: \n\"rpc error\"\nGot stderr: \n%s\n", stderr, 502 ) 503 504 t.Log("Try with fully configured TLS via config file") 505 stdout, stderr, err := ctx.ExecCmd( 506 "/agentctl", "--debug", "--config-dir=/etc/.agentctl", "dump", "vpp.interfaces", 507 ) 508 ctx.Expect(err).To(BeNil(), 509 "Should not fail. Got err: %v\nStderr:\n%s\n", err, stderr, 510 ) 511 ctx.Expect(len(stdout)).To(Not(BeZero())) 512 }*/ 513 514 func TestAgentCtlSecureGrpc(t *testing.T) { 515 // WARNING: Do not use grpc connection created in `setupE2E` in 516 // this test (though I don't know why you would but anyway). 517 // By default `grpc.Dial` is non-blocking and connecting happens 518 // in the background, so `setupE2E` function does not know about 519 // any errors. With securing grpc on the agent (by replacing 520 // grpc.conf with grpc-secure.conf) that client won't be able 521 // to establish connection because it's not configured for this 522 // secure case. 523 524 t.Log("Replacing `GRPC_CONFIG` value with /testdata/grpc-secure.conf") 525 defer func(oldVal string) { 526 t.Logf("Setting `GRPC_CONFIG` back to %q", oldVal) 527 os.Setenv("GRPC_CONFIG", oldVal) 528 }(os.Getenv("GRPC_CONFIG")) 529 os.Setenv("GRPC_CONFIG", "/testdata/grpc-secure.conf") 530 531 ctx := Setup(t) 532 defer ctx.Teardown() 533 534 ctx.ExecCmd("bash", "-c", "set -x; ls /testdata; cat /testdata/agentctl.conf") 535 536 t.Log("Try without any TLS") 537 _, stderr, err := ctx.ExecCmd( 538 "agentctl", "--debug", "dump", "vpp.interfaces") 539 ctx.Expect(err).To(Not(BeNil())) 540 ctx.Expect(stderr).To(ContainSubstring("rpc error"), "Expected string not found in stderr") 541 542 t.Log("Try with TLS enabled via flag --insecure-tls. Should work because server is not configured to check client certs.") 543 stdout, stderr, err := ctx.ExecCmd( 544 "agentctl", "--debug", "--insecure-tls", "dump", "vpp.interfaces") 545 ctx.Expect(err).To(Not(BeNil())) 546 ctx.Expect(stdout).To(BeEmpty()) 547 ctx.Expect(stderr).To(ContainSubstring("dump failed:")) 548 549 t.Log("Try with fully configured TLS via config file") 550 stdout, stderr, err = ctx.ExecCmd( 551 "agentctl", "--debug", "--config=/testdata/agentctl.conf", "dump", "vpp.interfaces") 552 ctx.Expect(err).To(Not(BeNil())) 553 ctx.Expect(stdout).To(BeEmpty()) 554 ctx.Expect(stderr).To(ContainSubstring("dump failed:")) 555 } 556 557 func TestAgentCtlSecureETCD(t *testing.T) { 558 ctx := Setup(t, WithEtcd(WithEtcdHTTPsConnection(), WithEtcdTestContainerNetworking())) 559 defer ctx.Teardown() 560 561 // test without any TLS 562 t.Run("no TLS", func(t *testing.T) { 563 _, _, err := ctx.ExecCmd("agentctl", "--debug", "kvdb", "list") 564 ctx.Expect(err).To(Not(BeNil())) 565 }) 566 567 // test with TLS enabled via flag --insecure-tls, but without cert and key (note: server configured to check those files) 568 t.Run("insecure TLS", func(t *testing.T) { 569 _, _, err := ctx.ExecCmd("agentctl", "--debug", "--insecure-tls", "kvdb", "list") 570 ctx.Expect(err).To(Not(BeNil())) 571 }) 572 573 // test with fully configured TLS via config file 574 /*t.Run("fully cofigured TLS", func(t *testing.T) { 575 _, stderr, err := ctx.ExecCmd("/agentctl", "--debug", "--config-dir=/etc/.agentctl", "kvdb", "list") 576 ctx.Expect(err).To(BeNil(), "Should not fail. Got err: %v\nStderr:\n%s\n", err, stderr) 577 })*/ 578 } 579 580 func createFileWithContent(path, content string) (string, error) { 581 f, err := os.Create(path) 582 if err != nil { 583 return path, err 584 } 585 w := bufio.NewWriter(f) 586 _, err = w.WriteString(content) 587 if err != nil { 588 return path, err 589 } 590 w.Flush() 591 return path, nil 592 } 593 594 func dummyIfFactory(ctx *TestCtx) func() (string, string) { 595 seq := 0 596 return func() (string, string) { 597 strseq := strconv.Itoa(seq) 598 file := ctx.ShareDir + "/agentctl-dummyif" + strseq + ".yaml" 599 content := `linuxConfig: 600 interfaces: 601 - name: "dummyif` + strseq + `" 602 type: DUMMY 603 enabled: true 604 ipAddresses: 605 - 9.9.9.9/24` 606 seq += 1 607 return file, content 608 } 609 }