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  }