sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/spyglass/podlogartifact_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 spyglass 18 19 import ( 20 "bytes" 21 "fmt" 22 "io" 23 "testing" 24 25 prowapi "sigs.k8s.io/prow/pkg/apis/prowjobs/v1" 26 "sigs.k8s.io/prow/pkg/kube" 27 "sigs.k8s.io/prow/pkg/spyglass/api" 28 "sigs.k8s.io/prow/pkg/spyglass/lenses" 29 ) 30 31 const customContainerName = "custom-container" 32 33 // fakePodLogJAgent used for pod log artifact dependency injection 34 type fakePodLogJAgent struct { 35 } 36 37 func (j *fakePodLogJAgent) GetProwJob(job, id string) (prowapi.ProwJob, error) { 38 return prowapi.ProwJob{}, nil 39 } 40 41 func (j *fakePodLogJAgent) GetJobLog(job, id, container string) ([]byte, error) { 42 if job == "BFG" && id == "435" { 43 switch container { 44 case kube.TestContainerName: 45 return []byte("frobscottle"), nil 46 case customContainerName: 47 return []byte("snozzcumber"), nil 48 } 49 } else if job == "Fantastic Mr. Fox" && id == "4" { 50 return []byte("a hundred smoked hams and fifty sides of bacon"), nil 51 } 52 return nil, fmt.Errorf("could not find job %s, id %s, container %s", job, id, container) 53 } 54 55 func TestNewPodLogArtifact(t *testing.T) { 56 testCases := []struct { 57 name string 58 jobName string 59 buildID string 60 container string 61 artifact string 62 sizeLimit int64 63 expectedErr error 64 expectedLink string 65 }{ 66 { 67 name: "Create pod log with valid fields", 68 jobName: "job", 69 buildID: "123", 70 container: kube.TestContainerName, 71 artifact: singleLogName, 72 sizeLimit: 500e6, 73 expectedErr: nil, 74 expectedLink: fmt.Sprintf("/log?container=%s&id=123&job=job", kube.TestContainerName), 75 }, 76 { 77 name: "Create pod log with valid fields and custom container", 78 jobName: "job", 79 buildID: "123", 80 container: customContainerName, 81 artifact: fmt.Sprintf("%s-%s", customContainerName, singleLogName), 82 sizeLimit: 500e6, 83 expectedErr: nil, 84 expectedLink: fmt.Sprintf("/log?container=%s&id=123&job=job", customContainerName), 85 }, 86 { 87 name: "Create pod log with no jobName", 88 jobName: "", 89 buildID: "123", 90 container: kube.TestContainerName, 91 artifact: singleLogName, 92 sizeLimit: 500e6, 93 expectedErr: errInsufficientJobInfo, 94 expectedLink: "", 95 }, 96 { 97 name: "Create pod log with no buildID", 98 jobName: "job", 99 buildID: "", 100 container: kube.TestContainerName, 101 artifact: singleLogName, 102 sizeLimit: 500e6, 103 expectedErr: errInsufficientJobInfo, 104 expectedLink: "", 105 }, 106 { 107 name: "Create pod log with negative sizeLimit", 108 jobName: "job", 109 buildID: "123", 110 container: kube.TestContainerName, 111 artifact: singleLogName, 112 sizeLimit: -4, 113 expectedErr: errInvalidSizeLimit, 114 expectedLink: "", 115 }, 116 } 117 for _, tc := range testCases { 118 t.Run(tc.name, func(t *testing.T) { 119 artifact, err := NewPodLogArtifact(tc.jobName, tc.buildID, tc.artifact, tc.container, tc.sizeLimit, &fakePodLogJAgent{}) 120 if err != nil { 121 if err != tc.expectedErr { 122 t.Fatalf("failed creating artifact. err: %v", err) 123 } 124 return 125 } 126 link := artifact.CanonicalLink() 127 if link != tc.expectedLink { 128 t.Errorf("Unexpected link, expected %s, got %q", tc.expectedLink, link) 129 } 130 }) 131 } 132 } 133 134 func TestReadTail_PodLog(t *testing.T) { 135 testCases := []struct { 136 name string 137 jobName string 138 buildID string 139 container string 140 artifact *PodLogArtifact 141 n int64 142 expected []byte 143 expectErr bool 144 }{ 145 { 146 name: "Podlog ReadTail longer than contents", 147 jobName: "BFG", 148 buildID: "435", 149 container: kube.TestContainerName, 150 n: 50, 151 expected: []byte("frobscottle"), 152 }, 153 { 154 name: "Podlog ReadTail shorter than contents", 155 jobName: "Fantastic Mr. Fox", 156 buildID: "4", 157 container: kube.TestContainerName, 158 n: 3, 159 expected: []byte("con"), 160 }, 161 { 162 name: "Podlog ReadTail longer for different container", 163 jobName: "BFG", 164 buildID: "435", 165 container: customContainerName, 166 n: 50, 167 expected: []byte("snozzcumber"), 168 }, 169 { 170 name: "Podlog ReadTail shorter for different container", 171 jobName: "BFG", 172 buildID: "435", 173 container: customContainerName, 174 n: 3, 175 expected: []byte("ber"), 176 }, 177 { 178 name: "Podlog ReadTail nonexistent pod", 179 jobName: "Fax", 180 buildID: "4", 181 container: kube.TestContainerName, 182 n: 3, 183 expectErr: true, 184 }, 185 } 186 for _, tc := range testCases { 187 t.Run(tc.name, func(t *testing.T) { 188 artifact, err := NewPodLogArtifact(tc.jobName, tc.buildID, singleLogName, tc.container, 500e6, &fakePodLogJAgent{}) 189 if err != nil { 190 t.Fatalf("Pod Log Tests failed to create pod log artifact, err %v", err) 191 } 192 res, err := artifact.ReadTail(tc.n) 193 if err != nil && !tc.expectErr { 194 t.Fatalf("failed reading bytes of log. did not expect err, got err: %v", err) 195 } 196 if err == nil && tc.expectErr { 197 t.Errorf("expected an error, got none") 198 } 199 if !bytes.Equal(tc.expected, res) { 200 t.Errorf("Unexpected result of reading pod logs, expected %q, got %q", tc.expected, res) 201 } 202 }) 203 } 204 205 } 206 func TestReadAt_PodLog(t *testing.T) { 207 testCases := []struct { 208 name string 209 jobName string 210 buildID string 211 container string 212 n int64 213 offset int64 214 expectedErr error 215 expected []byte 216 }{ 217 { 218 name: "Podlog ReadAt range longer than contents", 219 n: 100, 220 jobName: "BFG", 221 buildID: "435", 222 container: kube.TestContainerName, 223 offset: 3, 224 expectedErr: io.EOF, 225 expected: []byte("bscottle"), 226 }, 227 { 228 name: "Podlog ReadAt range within contents", 229 n: 4, 230 jobName: "Fantastic Mr. Fox", 231 buildID: "4", 232 container: kube.TestContainerName, 233 offset: 2, 234 expectedErr: nil, 235 expected: []byte("hund"), 236 }, 237 } 238 for _, tc := range testCases { 239 t.Run(tc.name, func(t *testing.T) { 240 artifact, err := NewPodLogArtifact(tc.jobName, tc.buildID, singleLogName, tc.container, 500e6, &fakePodLogJAgent{}) 241 if err != nil { 242 t.Fatalf("Pod Log Tests failed to create pod log artifact, err %v", err) 243 } 244 res := make([]byte, tc.n) 245 readBytes, err := artifact.ReadAt(res, tc.offset) 246 if err != tc.expectedErr { 247 t.Fatalf("failed reading bytes of log. err: %v, expected err: %v", err, tc.expectedErr) 248 } 249 if !bytes.Equal(tc.expected, res[:readBytes]) { 250 t.Errorf("Unexpected result of reading pod logs, expected %q, got %q", tc.expected, res) 251 } 252 }) 253 } 254 255 } 256 func TestReadAtMost_PodLog(t *testing.T) { 257 testCases := []struct { 258 name string 259 n int64 260 jobName string 261 buildID string 262 container string 263 expectedErr error 264 expected []byte 265 }{ 266 { 267 name: "Podlog ReadAtMost longer than contents", 268 jobName: "BFG", 269 buildID: "435", 270 container: kube.TestContainerName, 271 n: 100, 272 expectedErr: io.EOF, 273 expected: []byte("frobscottle"), 274 }, 275 { 276 name: "Podlog ReadAtMost shorter than contents", 277 n: 3, 278 jobName: "BFG", 279 buildID: "435", 280 container: kube.TestContainerName, 281 expectedErr: nil, 282 expected: []byte("fro"), 283 }, 284 } 285 for _, tc := range testCases { 286 t.Run(tc.name, func(t *testing.T) { 287 artifact, err := NewPodLogArtifact(tc.jobName, tc.buildID, singleLogName, tc.container, 500e6, &fakePodLogJAgent{}) 288 if err != nil { 289 t.Fatalf("Pod Log Tests failed to create pod log artifact, err %v", err) 290 } 291 res, err := artifact.ReadAtMost(tc.n) 292 if err != tc.expectedErr { 293 t.Fatalf("failed reading bytes of log. err: %v, expected err: %v", err, tc.expectedErr) 294 } 295 if !bytes.Equal(tc.expected, res) { 296 t.Errorf("Unexpected result of reading pod logs, expected %q, got %q", tc.expected, res) 297 } 298 }) 299 } 300 301 } 302 303 func TestReadAll_PodLog(t *testing.T) { 304 fakePodLogAgent := &fakePodLogJAgent{} 305 testCases := []struct { 306 name string 307 jobName string 308 buildID string 309 container string 310 sizeLimit int64 311 expectedErr error 312 expected []byte 313 }{ 314 { 315 name: "Podlog readall not found", 316 jobName: "job", 317 buildID: "123", 318 container: kube.TestContainerName, 319 sizeLimit: 500e6, 320 expectedErr: fmt.Errorf("error getting pod log size: error getting size of pod log: could not find job job, id 123, container %s", kube.TestContainerName), 321 expected: nil, 322 }, 323 { 324 name: "Simple \"BFG\" Podlog readall", 325 jobName: "BFG", 326 buildID: "435", 327 container: kube.TestContainerName, 328 sizeLimit: 500e6, 329 expectedErr: nil, 330 expected: []byte("frobscottle"), 331 }, 332 { 333 name: "Simple \"BFG\" Podlog readall for custom container", 334 jobName: "BFG", 335 buildID: "435", 336 container: customContainerName, 337 sizeLimit: 500e6, 338 expectedErr: nil, 339 expected: []byte("snozzcumber"), 340 }, 341 { 342 name: "\"Fantastic Mr. Fox\" Podlog readall", 343 jobName: "Fantastic Mr. Fox", 344 buildID: "4", 345 container: kube.TestContainerName, 346 sizeLimit: 500e6, 347 expectedErr: nil, 348 expected: []byte("a hundred smoked hams and fifty sides of bacon"), 349 }, 350 { 351 name: "Podlog readall over size limit", 352 jobName: "Fantastic Mr. Fox", 353 buildID: "4", 354 container: kube.TestContainerName, 355 sizeLimit: 5, 356 expectedErr: lenses.ErrFileTooLarge, 357 expected: nil, 358 }, 359 } 360 for _, tc := range testCases { 361 artifact, err := NewPodLogArtifact(tc.jobName, tc.buildID, singleLogName, tc.container, tc.sizeLimit, fakePodLogAgent) 362 if err != nil { 363 t.Fatalf("Pod Log Tests failed to create pod log artifact, err %v", err) 364 } 365 res, err := artifact.ReadAll() 366 if err != nil && err.Error() != tc.expectedErr.Error() { 367 t.Fatalf("%s failed reading bytes of log. got err: %v, expected err: %v", tc.name, err, tc.expectedErr) 368 } 369 if err != nil { 370 continue 371 } 372 if !bytes.Equal(tc.expected, res) { 373 t.Errorf("Unexpected result of reading pod logs, expected %q, got %q", tc.expected, res) 374 } 375 376 } 377 378 } 379 380 type fakeAgent struct { 381 contents string 382 } 383 384 func (f *fakeAgent) GetProwJob(job, id string) (prowapi.ProwJob, error) { 385 return prowapi.ProwJob{}, nil 386 } 387 388 func (f *fakeAgent) GetJobLog(job, id, container string) ([]byte, error) { 389 return []byte(f.contents), nil 390 } 391 392 func TestPodLogArtifact_RespectsSizeLimit(t *testing.T) { 393 contents := "Supercalifragilisticexpialidocious" 394 numRequestedBytes := int64(10) 395 396 testCases := []struct { 397 name string 398 expected error 399 contents string 400 skipGzip bool 401 sizeLimit int64 402 action func(api.Artifact) error 403 }{ 404 { 405 name: "ReadAll", 406 expected: lenses.ErrFileTooLarge, 407 action: func(a api.Artifact) error { 408 _, err := a.ReadAll() 409 return err 410 }, 411 }, 412 { 413 name: "ReadAt", 414 expected: lenses.ErrRequestSizeTooLarge, 415 action: func(a api.Artifact) error { 416 buf := make([]byte, numRequestedBytes) 417 _, err := a.ReadAt(buf, 3) 418 return err 419 }, 420 }, 421 { 422 name: "ReadAtMost", 423 expected: lenses.ErrRequestSizeTooLarge, 424 action: func(a api.Artifact) error { 425 _, err := a.ReadAtMost(numRequestedBytes) 426 return err 427 }, 428 }, 429 { 430 name: "ReadTail", 431 expected: lenses.ErrRequestSizeTooLarge, 432 action: func(a api.Artifact) error { 433 _, err := a.ReadTail(numRequestedBytes) 434 return err 435 }, 436 }, 437 } 438 for _, tc := range testCases { 439 t.Run(tc.name+"_NoError", func(nested *testing.T) { 440 sizeLimit := int64(2 * len(contents)) 441 artifact, err := NewPodLogArtifact("job-name", "build-id", "log-name", "container-name", sizeLimit, &fakeAgent{contents: contents}) 442 if err != nil { 443 nested.Fatalf("error creating test data: %s", err) 444 } 445 446 actual := tc.action(artifact) 447 if actual != nil { 448 nested.Fatalf("unexpected error: %s", actual) 449 } 450 }) 451 t.Run(tc.name+"_WithError", func(nested *testing.T) { 452 sizeLimit := int64(5) 453 artifact, err := NewPodLogArtifact("job-name", "build-id", "log-name", "container-name", sizeLimit, &fakeAgent{contents: contents}) 454 if err != nil { 455 nested.Fatalf("error creating test data: %s", err) 456 } 457 458 actual := tc.action(artifact) 459 if actual == nil { 460 nested.Fatalf("expected error (%s), but got: nil", tc.expected) 461 } else if tc.expected.Error() != actual.Error() { 462 nested.Fatalf("expected error (%s), but got: %s", tc.expected, actual) 463 } 464 }) 465 } 466 }