github.com/jenkins-x/test-infra@v0.0.7/kubetest/dump_test.go (about)

     1  /*
     2  Copyright 2017 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 main
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"io"
    23  	"io/ioutil"
    24  	"os"
    25  	"path/filepath"
    26  	"reflect"
    27  	"sort"
    28  	"strings"
    29  	"testing"
    30  )
    31  
    32  func Test_logDumperNode_findFiles(t *testing.T) {
    33  	grid := []struct {
    34  		dir      string
    35  		command  string
    36  		stdout   string
    37  		stderr   string
    38  		expected []string
    39  	}{
    40  		{
    41  			dir:     "/var/log",
    42  			command: "sudo find /var/log -print0",
    43  			stdout:  "/var/log/a\x00/var/log/b\x00",
    44  			expected: []string{
    45  				"/var/log/a",
    46  				"/var/log/b",
    47  			},
    48  		},
    49  		{
    50  			dir:     "/var/log",
    51  			command: "sudo find /var/log -print0",
    52  			stdout:  "/var/log/a\x00",
    53  			expected: []string{
    54  				"/var/log/a",
    55  			},
    56  		},
    57  		{
    58  			dir:      "/var/log",
    59  			command:  "sudo find /var/log -print0",
    60  			expected: []string{},
    61  		},
    62  	}
    63  
    64  	for _, g := range grid {
    65  		client := &mockSSHClient{}
    66  		n := logDumperNode{
    67  			client: client,
    68  		}
    69  
    70  		client.commands = append(client.commands, &mockCommand{
    71  			command: g.command,
    72  			stdout:  []byte(g.stdout),
    73  			stderr:  []byte(g.stderr),
    74  		})
    75  		actual, err := n.findFiles(context.Background(), g.dir)
    76  		if err != nil {
    77  			t.Errorf("unexpected error from findFiles: %v", err)
    78  			continue
    79  		}
    80  
    81  		if !reflect.DeepEqual(actual, g.expected) {
    82  			t.Errorf("unexpected files.  actual=%v, expected=%v", actual, g.expected)
    83  			continue
    84  		}
    85  	}
    86  }
    87  
    88  func Test_logDumperNode_listSystemdUnits(t *testing.T) {
    89  	grid := []struct {
    90  		command  string
    91  		stdout   string
    92  		stderr   string
    93  		expected []string
    94  	}{
    95  		{
    96  			command: "sudo systemctl list-units -t service --no-pager --no-legend --all",
    97  			stdout: "accounts-daemon.service            loaded active running Accounts Service\n" +
    98  				"acpid.service                      loaded active running ACPI event daemon\n" +
    99  				"atd.service                        loaded active running Deferred execution scheduler\n" +
   100  				"avahi-daemon.service               loaded active running Avahi mDNS/DNS-SD Stack\n" +
   101  				"bluetooth.service                  loaded active running Bluetooth service\n" +
   102  				"cameras.service                    loaded failed failed  cameras\n" +
   103  				"colord.service                     loaded active running Manage, Install and Generate Color Profiles\n",
   104  			expected: []string{
   105  				"accounts-daemon.service",
   106  				"acpid.service",
   107  				"atd.service",
   108  				"avahi-daemon.service",
   109  				"bluetooth.service",
   110  				"cameras.service",
   111  				"colord.service",
   112  			},
   113  		},
   114  	}
   115  
   116  	for _, g := range grid {
   117  		client := &mockSSHClient{}
   118  		n := logDumperNode{
   119  			client: client,
   120  		}
   121  
   122  		client.commands = append(client.commands, &mockCommand{
   123  			command: g.command,
   124  			stdout:  []byte(g.stdout),
   125  			stderr:  []byte(g.stderr),
   126  		})
   127  		actual, err := n.listSystemdUnits(context.Background())
   128  		if err != nil {
   129  			t.Errorf("unexpected error from listSystemdUnits: %v", err)
   130  			continue
   131  		}
   132  
   133  		if !reflect.DeepEqual(actual, g.expected) {
   134  			t.Errorf("unexpected systemdUnits.  actual=%v, expected=%v", actual, g.expected)
   135  			continue
   136  		}
   137  	}
   138  }
   139  
   140  func Test_logDumperNode_shellToFile(t *testing.T) {
   141  	grid := []struct {
   142  		command string
   143  		stdout  []byte
   144  		stderr  []byte
   145  	}{
   146  		{
   147  			command: "cat something",
   148  			stdout:  []byte("hello"),
   149  		},
   150  	}
   151  
   152  	for _, g := range grid {
   153  		client := &mockSSHClient{}
   154  		n := logDumperNode{
   155  			client: client,
   156  		}
   157  
   158  		client.commands = append(client.commands, &mockCommand{
   159  			command: g.command,
   160  			stdout:  []byte(g.stdout),
   161  			stderr:  []byte(g.stderr),
   162  		})
   163  
   164  		tmpfile, err := ioutil.TempFile("", "")
   165  		if err != nil {
   166  			t.Errorf("error creating temp file: %v", err)
   167  			continue
   168  		}
   169  
   170  		defer func() {
   171  			if err := os.Remove(tmpfile.Name()); err != nil {
   172  				t.Errorf("error removing temp file: %v", err)
   173  			}
   174  		}()
   175  
   176  		err = n.shellToFile(context.Background(), "cat something", tmpfile.Name())
   177  		if err != nil {
   178  			t.Errorf("unexpected error from shellToFile: %v", err)
   179  			continue
   180  		}
   181  
   182  		if err := tmpfile.Close(); err != nil {
   183  			t.Errorf("unexpected error closing file: %v", err)
   184  			continue
   185  		}
   186  
   187  		actual, err := ioutil.ReadFile(tmpfile.Name())
   188  		if err != nil {
   189  			t.Errorf("unexpected error reading file: %v", err)
   190  			continue
   191  		}
   192  
   193  		if !reflect.DeepEqual(actual, g.stdout) {
   194  			t.Errorf("unexpected systemdUnits.  actual=%q, expected=%q", string(actual), string(g.stdout))
   195  			continue
   196  		}
   197  	}
   198  }
   199  
   200  func Test_logDumperNode_dump(t *testing.T) {
   201  	tmpdir, err := ioutil.TempDir("", "")
   202  	if err != nil {
   203  		t.Errorf("error creating temp dir: %v", err)
   204  		return
   205  	}
   206  
   207  	defer func() {
   208  		if err := os.RemoveAll(tmpdir); err != nil {
   209  			t.Errorf("error removing temp dir: %v", err)
   210  		}
   211  	}()
   212  
   213  	host1Client := &mockSSHClient{}
   214  	host1Client.commands = append(host1Client.commands,
   215  		&mockCommand{
   216  			command: "sudo journalctl --output=short-precise -k",
   217  		},
   218  		&mockCommand{
   219  			command: "sudo journalctl --output=short-precise",
   220  		},
   221  		&mockCommand{
   222  			command: "sudo systemctl list-units -t service --no-pager --no-legend --all",
   223  			stdout: []byte(
   224  				"kubelet.service                      loaded active running kubelet daemon\n" +
   225  					"atd.service                        loaded active running Deferred execution scheduler\n" +
   226  					"avahi-daemon.service               loaded active running Avahi mDNS/DNS-SD Stack\n" +
   227  					"bluetooth.service                  loaded active running Bluetooth service\n" +
   228  					"cameras.service                    loaded failed failed  cameras\n" +
   229  					"colord.service                     loaded active running Manage, Install and Generate Color Profiles\n",
   230  			),
   231  		},
   232  		&mockCommand{
   233  			command: "sudo find /var/log -print0",
   234  			stdout: []byte(strings.Join([]string{
   235  				"/var/log",
   236  				"/var/log/kube-controller-manager.log",
   237  				"/var/log/kube-controller-manager.log.1",
   238  				"/var/log/kube-controller-manager.log.2.gz",
   239  				"/var/log/other.log",
   240  			}, "\x00")),
   241  		},
   242  		&mockCommand{
   243  			command: "sudo journalctl --output=cat -u kubelet.service",
   244  		},
   245  		&mockCommand{
   246  			command: "sudo cat /var/log/kube-controller-manager.log",
   247  		},
   248  		&mockCommand{
   249  			command: "sudo cat /var/log/kube-controller-manager.log.1",
   250  		},
   251  		&mockCommand{
   252  			command: "sudo cat /var/log/kube-controller-manager.log.2.gz",
   253  		},
   254  	)
   255  	mockSSHClientFactory := &mockSSHClientFactory{
   256  		clients: map[string]sshClient{
   257  			"host1": host1Client,
   258  		},
   259  	}
   260  
   261  	dumper, err := newLogDumper(mockSSHClientFactory, tmpdir)
   262  	if err != nil {
   263  		t.Errorf("error building logDumper: %v", err)
   264  	}
   265  
   266  	n, err := dumper.connectToNode(context.Background(), "nodename1", "host1")
   267  	if err != nil {
   268  		t.Errorf("error from connectToNode: %v", err)
   269  	}
   270  
   271  	errors := n.dump(context.Background())
   272  	if len(errors) != 0 {
   273  		t.Errorf("unexpected errors from dump: %v", errors)
   274  		return
   275  	}
   276  
   277  	actual := []string{}
   278  	err = filepath.Walk(tmpdir, func(path string, info os.FileInfo, err error) error {
   279  		if err != nil {
   280  			return err
   281  		}
   282  		p := strings.TrimPrefix(strings.TrimPrefix(path, tmpdir), "/")
   283  		if p == "" {
   284  			return nil
   285  		}
   286  		if info.IsDir() {
   287  			p += "/"
   288  		}
   289  		actual = append(actual, p)
   290  		return nil
   291  	})
   292  	if err != nil {
   293  		t.Errorf("unexpected error walking output tree: %v", err)
   294  		return
   295  	}
   296  
   297  	expected := []string{
   298  		"nodename1/",
   299  		"nodename1/kern.log",
   300  		"nodename1/journal.log",
   301  		"nodename1/kubelet.log",
   302  		"nodename1/kube-controller-manager.log",
   303  		"nodename1/kube-controller-manager.log.1",
   304  		"nodename1/kube-controller-manager.log.2.gz",
   305  	}
   306  
   307  	sort.Strings(actual)
   308  	sort.Strings(expected)
   309  
   310  	if !reflect.DeepEqual(actual, expected) {
   311  		t.Errorf("unexpected files found in dump: actual=%v, expected=%v", actual, expected)
   312  		return
   313  	}
   314  }
   315  
   316  // mockCommand is an expected command and canned response
   317  type mockCommand struct {
   318  	command string
   319  	stdout  []byte
   320  	stderr  []byte
   321  	err     error
   322  }
   323  
   324  // mockSSHClient is a mock implementation of sshClient
   325  type mockSSHClient struct {
   326  	// commands holds the canned commands and responses we expect
   327  	commands []*mockCommand
   328  	// closed is set if the mockSSHClient is closed
   329  	closed bool
   330  }
   331  
   332  var _ sshClient = &mockSSHClient{}
   333  
   334  // mockSSHClientFactory is a mock implementation of sshClientFactory
   335  type mockSSHClientFactory struct {
   336  	clients map[string]sshClient
   337  }
   338  
   339  var _ sshClientFactory = &mockSSHClientFactory{}
   340  
   341  func (f *mockSSHClientFactory) Dial(ctx context.Context, host string) (sshClient, error) {
   342  	client := f.clients[host]
   343  	if client == nil {
   344  		return nil, fmt.Errorf("host %q not registered in mockSSHClientFactory", host)
   345  	}
   346  	return client, nil
   347  }
   348  
   349  // Close implements sshClient::Close.  It records that the client was closed; future calls will fail
   350  func (m *mockSSHClient) Close() error {
   351  	if m.closed {
   352  		return fmt.Errorf("mockSSHClient::Close called on Closed mockSSHClient")
   353  	}
   354  	m.closed = true
   355  	return nil
   356  }
   357  
   358  // ExecPiped implements sshClient::ExecPiped.  It scans the configured commands, and returns the result if one is found.
   359  // If no command is found, it returns an error.
   360  func (m *mockSSHClient) ExecPiped(ctx context.Context, command string, stdout io.Writer, stderr io.Writer) error {
   361  	if m.closed {
   362  		return fmt.Errorf("mockSSHClient::ExecPiped called on Closed mockSSHClient")
   363  	}
   364  	for i := range m.commands {
   365  		c := m.commands[i]
   366  		if c == nil {
   367  			continue
   368  		}
   369  		if c.command == command {
   370  			if _, err := stdout.Write(c.stdout); err != nil {
   371  				return fmt.Errorf("error writing to stdout: %v", err)
   372  			}
   373  			if _, err := stderr.Write(c.stderr); err != nil {
   374  				return fmt.Errorf("error writing to stderr: %v", err)
   375  			}
   376  			m.commands[i] = nil
   377  			return c.err
   378  		}
   379  	}
   380  
   381  	return fmt.Errorf("unexpected command: %s", command)
   382  }
   383  
   384  func TestFindInstancesNotDumped(t *testing.T) {
   385  	n1 := &node{
   386  		Status: nodeStatus{
   387  			Addresses: []nodeAddress{{Address: "10.0.0.1"}},
   388  		},
   389  	}
   390  
   391  	n2 := &node{
   392  		Status: nodeStatus{
   393  			Addresses: []nodeAddress{{Address: "10.0.0.2"}},
   394  		},
   395  	}
   396  	n3 := &node{
   397  		Status: nodeStatus{
   398  			Addresses: []nodeAddress{
   399  				{Address: "10.0.0.3"},
   400  				{Address: "10.0.3.3"},
   401  			},
   402  		},
   403  	}
   404  
   405  	grid := []struct {
   406  		ips      []string
   407  		dumped   []*node
   408  		expected []string
   409  	}{
   410  		{
   411  			ips:      nil,
   412  			dumped:   nil,
   413  			expected: nil,
   414  		},
   415  		{
   416  			ips:      []string{"10.0.0.1"},
   417  			dumped:   nil,
   418  			expected: []string{"10.0.0.1"},
   419  		},
   420  		{
   421  			ips:      []string{"10.0.0.1"},
   422  			dumped:   []*node{n1},
   423  			expected: nil,
   424  		},
   425  		{
   426  			ips:      []string{"10.0.0.1", "10.0.0.2"},
   427  			dumped:   []*node{n1},
   428  			expected: []string{"10.0.0.2"},
   429  		},
   430  		{
   431  			ips:      []string{"10.0.0.1", "10.0.0.2", "10.0.3.3"},
   432  			dumped:   []*node{n1, n2, n3},
   433  			expected: nil,
   434  		},
   435  		{
   436  			ips:      []string{"10.0.0.1", "10.0.0.2", "10.0.3.3"},
   437  			dumped:   []*node{n1, n2},
   438  			expected: []string{"10.0.3.3"},
   439  		},
   440  	}
   441  
   442  	for _, g := range grid {
   443  		actual := findInstancesNotDumped(g.ips, g.dumped)
   444  
   445  		if !reflect.DeepEqual(actual, g.expected) {
   446  			t.Errorf("unexpected result from findInstancesNotDumped.  actual=%v, expected=%v", actual, g.expected)
   447  		}
   448  	}
   449  }