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 }