github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/kubetest/kubeadmdind/kubeadm_dind_test.go (about)

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package kubeadmdind
    18  
    19  import (
    20  	"os/exec"
    21  	"testing"
    22  	"time"
    23  
    24  	"k8s.io/test-infra/kubetest/process"
    25  )
    26  
    27  // fakeExecCmder implements the execCmder interface for testing how the
    28  // deployer processes executed command output.
    29  type fakeExecCmder struct {
    30  	simulatedOutput string // Simulated output
    31  	generateError   bool   // Create a command that causes an error
    32  }
    33  
    34  func newFakeExecCmder(simOutput string, genError bool) *fakeExecCmder {
    35  	cmder := new(fakeExecCmder)
    36  	cmder.simulatedOutput = simOutput
    37  	cmder.generateError = genError
    38  	return cmder
    39  }
    40  
    41  // execCmd creates an exec.Cmd structure for either:
    42  // - Echoing a simulated output string to be processed by the deployer.
    43  // - Running a bogus command to cause an execution error to be processed
    44  //   by the deployer.
    45  func (f *fakeExecCmder) execCmd(cmd string) *exec.Cmd {
    46  	if f.generateError {
    47  		return exec.Command("Bogus_Command_to_Cause_an_Error")
    48  	}
    49  	return exec.Command("echo", f.simulatedOutput)
    50  }
    51  
    52  // fakeNodeCmder implements the execCmder interface for testing how the
    53  // deployer processes output from commands executed on a node.
    54  type fakeNodeCmder struct {
    55  	node            string
    56  	simulatedOutput string // Simulated output
    57  	generateError   bool   // Create a command that causes an error
    58  }
    59  
    60  func newFakeNodeCmder(node, simOutput string, genError bool) *fakeNodeCmder {
    61  	cmder := new(fakeNodeCmder)
    62  	cmder.node = node
    63  	cmder.simulatedOutput = simOutput
    64  	cmder.generateError = genError
    65  	return cmder
    66  }
    67  
    68  // execCmd creates an exec.Cmd structure for either:
    69  // - Echoing a simulated output string to be processed by the deployer.
    70  // - Running a bogus command to cause an execution error to be processed
    71  //   by the deployer.
    72  func (f *fakeNodeCmder) execCmd(cmd string) *exec.Cmd {
    73  	if f.generateError {
    74  		return exec.Command("Bogus_Command_to_Cause_an_Error")
    75  	}
    76  	return exec.Command("echo", f.simulatedOutput)
    77  }
    78  
    79  // getNode returns the node name for a fakeNodeCmder
    80  func (f *fakeNodeCmder) getNode() string {
    81  	return f.node
    82  }
    83  
    84  // createTestDeployer creates a kubeadmdind deployer for unit testing.
    85  func createTestDeployer(ipMode string) (*Deployer, error) {
    86  	*kubeadmDinDIPMode = ipMode
    87  	timeout := time.Duration(10) * time.Second
    88  	interrupt := time.NewTimer(time.Duration(10) * time.Second)
    89  	terminate := time.NewTimer(time.Duration(10) * time.Second)
    90  	verbose := false
    91  	control := process.NewControl(timeout, interrupt, terminate, verbose)
    92  	return NewDeployer(control)
    93  }
    94  
    95  // slicesAreEqual tests whether two slices of strings are of equal length
    96  // and have the same entries, independent of ordering. It assumes that
    97  // entries in the slice being compared against (argument 'sliceA', and by
    98  // extension, both slices) form a set.
    99  func slicesAreEqual(sliceA, sliceB []string) bool {
   100  	if len(sliceA) != len(sliceB) {
   101  		return false
   102  	}
   103  	matched := false
   104  	for _, stringA := range sliceA {
   105  		matched = false
   106  		for _, stringB := range sliceB {
   107  			if stringB == stringA {
   108  				matched = true
   109  				break
   110  			}
   111  		}
   112  		if !matched {
   113  			return false
   114  		}
   115  	}
   116  	return true
   117  }
   118  
   119  // TestIPModeValidation tests whether the NewDeployer method correctly
   120  // validates configured values for IP mode.
   121  func TestIPModeValidation(t *testing.T) {
   122  	oldEnableCmd := ipv6EnableCmd
   123  	ipv6EnableCmd = "echo success"
   124  	defer func() { ipv6EnableCmd = oldEnableCmd }()
   125  
   126  	testCases := []struct {
   127  		ipMode   string
   128  		expError bool
   129  	}{
   130  		{
   131  			ipMode:   "ipv4",
   132  			expError: false,
   133  		},
   134  		{
   135  			ipMode:   "ipv6",
   136  			expError: false,
   137  		},
   138  		{
   139  			ipMode:   "dual-stack",
   140  			expError: false,
   141  		},
   142  		{
   143  			ipMode:   "twas-bryllyg",
   144  			expError: true,
   145  		},
   146  	}
   147  	for _, tc := range testCases {
   148  		_, err := createTestDeployer(tc.ipMode)
   149  		switch {
   150  		case err != nil && !tc.expError:
   151  			t.Errorf("ip mode '%s': Unexpected error: %v", tc.ipMode, err)
   152  			continue
   153  		case err == nil && tc.expError:
   154  			t.Errorf("ip mode '%s': Did not get expected error", tc.ipMode)
   155  			continue
   156  		}
   157  	}
   158  }
   159  
   160  // TestClusterSize tests whether the clusterSize method:
   161  //     - Processes a sample 'kubectl get nodes --no-header' output and
   162  //       calculates the correct number of nodes, or...
   163  //     - Handles 'kubectl get nodes ...' command errors (reports -1 nodes)
   164  func TestClusterSize(t *testing.T) {
   165  	d, err := createTestDeployer("ipv4")
   166  	if err != nil {
   167  		t.Errorf("couldn't create deployer: %v", err)
   168  		return
   169  	}
   170  
   171  	testCases := []struct {
   172  		testName  string
   173  		simOutput string
   174  		genError  bool
   175  		expSize   int
   176  	}{
   177  		{
   178  			testName:  "No nodes",
   179  			simOutput: "",
   180  			expSize:   0,
   181  		},
   182  		{
   183  			testName: "3-node Cluster",
   184  			simOutput: `
   185  kube-master   Ready     master    10m       v1.11.0
   186  kube-node-1   Ready     <none>    10m       v1.11.0
   187  kube-node-2   Ready     <none>    10m       v1.11.0
   188  `,
   189  			expSize: 3,
   190  		},
   191  		{
   192  			testName: "Simulated command error",
   193  			genError: true,
   194  			expSize:  -1,
   195  		},
   196  	}
   197  	for _, tc := range testCases {
   198  		d.hostCmder = newFakeExecCmder(tc.simOutput, tc.genError)
   199  		size, err := d.clusterSize()
   200  		switch {
   201  		case err != nil && !tc.genError:
   202  			t.Errorf("test case '%s': Unexpected error %v", tc.testName, err)
   203  			continue
   204  		case err == nil && tc.genError:
   205  			t.Errorf("test case '%s': Did not get expected error", tc.testName)
   206  			continue
   207  		}
   208  		if size != tc.expSize {
   209  			t.Errorf("test case '%s': expected size %d, found size %d", tc.testName, tc.expSize, size)
   210  			continue
   211  		}
   212  	}
   213  }
   214  
   215  // TestDetectNodeContainers tests whether detectNodeContainers can
   216  // either correctly process a sample command output for 'kubectl get
   217  // nodes ...', or gracefully handle a command error. Test cases include:
   218  //     - Detect master nodes
   219  //     - Detect worker nodes
   220  //     - Return an empty list upon command error
   221  func TestDetectNodeContainers(t *testing.T) {
   222  	d, err := createTestDeployer("ipv4")
   223  	if err != nil {
   224  		t.Errorf("couldn't create deployer: %v", err)
   225  		return
   226  	}
   227  
   228  	kubectlNodesOutput := `
   229  kube-master   Ready     master    1d        v1.11.0-alpha.0
   230  kube-node-1   Ready     <none>    1d        v1.11.0-alpha.0
   231  kube-node-2   Ready     <none>    1d        v1.11.0-alpha.0
   232  `
   233  	testCases := []struct {
   234  		testName   string
   235  		nodePrefix string
   236  		genError   bool
   237  		expNodes   []string
   238  	}{
   239  		{
   240  			testName:   "Detect master nodes",
   241  			nodePrefix: kubeMasterPrefix,
   242  			expNodes:   []string{"kube-master"},
   243  		},
   244  		{
   245  			testName:   "Detect worker nodes",
   246  			nodePrefix: kubeNodePrefix,
   247  			expNodes:   []string{"kube-node-1", "kube-node-2"},
   248  		},
   249  		{
   250  			testName:   "Check error handling",
   251  			nodePrefix: kubeNodePrefix,
   252  			genError:   true,
   253  			expNodes:   []string{},
   254  		},
   255  	}
   256  
   257  	for _, tc := range testCases {
   258  		d.hostCmder = newFakeExecCmder(kubectlNodesOutput, tc.genError)
   259  		foundNodes, err := d.detectNodeContainers(tc.nodePrefix)
   260  		switch {
   261  		case err != nil && !tc.genError:
   262  			t.Errorf("test case '%s': Unexpected error: %v", tc.testName, err)
   263  			continue
   264  		case err == nil && tc.genError:
   265  			t.Errorf("test case '%s': Did not get expected error", tc.testName)
   266  			continue
   267  		}
   268  		// Check whether the expected nodes have all been detected
   269  		if !slicesAreEqual(tc.expNodes, foundNodes) {
   270  			t.Errorf("test case: '%s', Expected nodes: %v, Detected nodes: %v", tc.testName, tc.expNodes, foundNodes)
   271  			continue
   272  		}
   273  	}
   274  }
   275  
   276  // TestDetectKubeContainers tests whether detectKubeContainers can
   277  // either correctly process a sample command output for 'docker ps -a',
   278  // or gracefully handle a command error. Test cases include:
   279  //     - Detect Kubernetes system pod containers on a node
   280  //     - Return an empty list upon command error
   281  func TestDetectKubeContainers(t *testing.T) {
   282  	d, err := createTestDeployer("ipv4")
   283  	if err != nil {
   284  		t.Errorf("couldn't create deployer: %v", err)
   285  		return
   286  	}
   287  
   288  	dockerPsOutput := "CONTAINER ID  IMAGE                             COMMAND                 CREATED       STATUS         PORTS  NAMES\n" +
   289  
   290  		"fba3566d4b43  k8s.gcr.io/k8s-dns-sidecar        \"/sidecar --v=2 --log\"  10 minutes ago  Up 10 minutes         k8s_sidecar_kube-dns-69f5bbc4c7\n" +
   291  		"3b7d8cf5b937  k8s.gcr.io/k8s-dns-dnsmasq-nanny  \"/dnsmasq-nanny -v=2 \"  10 minutes ago  Up 10 minutes         k8s_dnsmasq_kube-dns-69f5bbc4c7\n" +
   292  		"5aacb0551aa6  k8s.gcr.io/k8s-dns-kube-dns       \"/kube-dns --domain=c\"  10 minutes ago  Up 10 minutes         k8s_kubedns_kube-dns-69f5bbc4c7\n" +
   293  		"a4abfb755f58  k8s.gcr.io/pause-amd64:3.1        \"/pause\"                10 minutes ago  Up 10 minutes         k8s_POD_kube-dns-69f5bbc4c7\n" +
   294  		"03d1bb19d515  60e55008753b                      \"/usr/local/bin/kube-\"  10 minutes ago  Up 10 minutes         k8s_kube-proxy_kube-proxy-4tzr8\n" +
   295  		"1455bc3829d0  k8s.gcr.io/pause-amd64:3.1        \"/pause\"                10 minutes ago  Up 10 minutes         k8s_POD_kube-proxy-4tzr8\n"
   296  
   297  	testCases := []struct {
   298  		testName      string
   299  		genError      bool
   300  		expContainers []string
   301  	}{
   302  		{
   303  			testName: "Detect Containers",
   304  			genError: false,
   305  			expContainers: []string{
   306  				"k8s_sidecar_kube-dns-69f5bbc4c7",
   307  				"k8s_dnsmasq_kube-dns-69f5bbc4c7",
   308  				"k8s_kubedns_kube-dns-69f5bbc4c7",
   309  				"k8s_kube-proxy_kube-proxy-4tzr8",
   310  			},
   311  		},
   312  		{
   313  			testName:      "Check error handling",
   314  			genError:      true,
   315  			expContainers: []string{},
   316  		},
   317  	}
   318  
   319  	for _, tc := range testCases {
   320  		node := "fakeNodeName"
   321  		fakeCmder := newFakeNodeCmder(node, dockerPsOutput, tc.genError)
   322  		containers, err := d.detectKubeContainers(fakeCmder, node, nodeKubePods)
   323  		switch {
   324  		case err != nil && !tc.genError:
   325  			t.Errorf("test case '%s': Unexpected error: %v", tc.testName, err)
   326  			continue
   327  		case err == nil && tc.genError:
   328  			t.Errorf("test case '%s': Did not get expected error", tc.testName)
   329  			continue
   330  		}
   331  		// Check whether the expected containers have been detected
   332  		if !slicesAreEqual(tc.expContainers, containers) {
   333  			t.Errorf("test case: '%s', Expected containers: %v, Detected containers: %v", tc.testName, tc.expContainers, containers)
   334  			continue
   335  		}
   336  	}
   337  }