Conversation
e7dcc99 to
f8bf618
Compare
6c7ad7d to
e2283d5
Compare
arl
left a comment
There was a problem hiding this comment.
We just have changed the prototype of baker.Output interface so that Run returns an error.
We did this in order to avoid having the components themselves call os.Exit.
IMHO rather than adding a bunch of log.Fatal in the S3 uploader we should make baker.Upload.Run returns an error, and for now just handle that error in the Topology by calling os.Exit there, exactly as we did for errors returned by baker.Output.Run.
arl
left a comment
There was a problem hiding this comment.
LGTM
Only problem I see is with the test which do not remove the created folders and should use the machine temporary directory rather than the current directory (./upload) in this case.
| func prepareUploadS3TestFolder(t *testing.T, numFiles int) (string, []string) { | ||
| t.Helper() | ||
|
|
||
| // Create a folder to store files to be uploaded | ||
| srcDir, err := ioutil.TempDir(".", "upload_s3_test") | ||
| if err != nil { | ||
| t.Fatalf("Can't setup test: %v", err) | ||
| } | ||
| defer os.Remove(srcDir) | ||
|
|
||
| // Write a bunch of files | ||
| var fnames []string | ||
| for i := 0; i < numFiles; i++ { | ||
| fname := filepath.Join(srcDir, fmt.Sprintf("test_file_%d", i)) | ||
|
|
||
| if err := ioutil.WriteFile(fname, []byte("abc"), 0644); err != nil { | ||
| t.Fatalf("can't create temp file: %v", err) | ||
| } | ||
|
|
||
| fnames = append(fnames, fname) | ||
| } | ||
|
|
||
| return srcDir, fnames | ||
| } |
There was a problem hiding this comment.
It seems this function has been extracted from Test_uploadDirectory. In that case Test_uploadDirectory could also use that test function.
However, in Test_uploadDirectory the defer was called at the end of the test, effectively removing the created temp folder. os.Remove only removes a directory if it's empty.
But there are 2 problems with the actual code:
- in
prepareUploadS3TestFolderthe defer is called when exiting the function, but does nothing since the directory is not empty - worse, the directories remain after the tests and pollute the $REPO/upload directory. Those directories should be cleaned up
- I think those directories should be (as it was the case in
Test_uploadDirectoryin the machine temp folder, so even in case of panic or anything, the test directories won't pollute the working tree.
To do so prepareUploadS3TestFolder should also return a callback (rmdir or something) called with a defer in the test function. This callback should call os.RemoveAll to ensure the directory gets removed even if the directory is not empty.
| srcDir, _ := prepareUploadS3TestFolder(t, numFiles) | ||
|
|
||
| mockUploadFn := func(uploader *s3manager.Uploader, bucket, prefix, localPath, fpath string) error { | ||
| time.Sleep(100 * time.Millisecond) |
There was a problem hiding this comment.
Why is this sleep here needed?
| // time.Sleep(100 * time.Millisecond) | ||
| // return errors.New("Fake error") |
| tmpDir, fnames := prepareUploadS3TestFolder(t, 1) | ||
| fname := fnames[0] | ||
|
|
||
| stagingDir, err := ioutil.TempDir(".", "upload_s3_test_staging") |
There was a problem hiding this comment.
same as before: this directory is not cleanup after the test
| tmpDir, fnames := prepareUploadS3TestFolder(t, 1) | ||
| fname := fnames[0] | ||
|
|
||
| stagingDir, err := ioutil.TempDir(".", "upload_s3_test_staging") |
There was a problem hiding this comment.
It can be useful to use t.Name() for temporary folders related to a specific test
| err := filepath.Walk(u.Cfg.StagingPath, func(fpath string, info os.FileInfo, err error) error { | ||
| if err != nil { | ||
| return err | ||
| globFatalErr := atomic.Value{} |
There was a problem hiding this comment.
nit: calling this global may be a bit misleading and be confused with a global variable.
name proposition: mainErr or exitErr
| if globFatalErr.Load() != nil { | ||
| return globFatalErr.Load().(error) |
There was a problem hiding this comment.
Instead of calling Load twice we would store the result of Load in a variable and reuse it
| t.Fatalf("Can't setup test: %v", err) | ||
| } | ||
| mockUploadFn := func(uploader *s3manager.Uploader, bucket, prefix, localPath, fpath string) error { | ||
| time.Sleep(100 * time.Millisecond) |
da2ca55 to
cdaf041
Compare
|
All comments should have been addressed |
arl
left a comment
There was a problem hiding this comment.
LGTM
Only thing is the addition, just for test, of a function pointer in the production code that could/should be avoided IMHO
and a nit :D
| totalerr int64 | ||
| queuedn int64 | ||
|
|
||
| uploadFn func(uploader *s3manager.Uploader, bucket, prefix, localPath, fpath string) error |
There was a problem hiding this comment.
Is this have been added only for tests?
In general it's better to avoid modifying production code just to make it more testable, unless absolutely necessary.
However in this case there is already a mocked S3 service, instead of modifying the Upload itself, you could modify the mock to satisfy your needs, which are:
- report the number of uploaded files (as required by
Test_uploadDirectory). To do so just increment an atomic counter in the bothCompleteMultipartUploadOutputandPutObjectOutput. Those are both the handlers for correct termination of a file upload. - optionally return an error (required by
Test_uploadDirectoryError). For this it's probably enough to play with the HTTP response code
| if err := u.uploadFn(u.uploader, u.Cfg.Bucket, u.Cfg.Prefix, u.Cfg.StagingPath, fpath); err == nil { | ||
| atomic.AddInt64(&u.queuedn, int64(-1)) | ||
| break | ||
| } else { |
There was a problem hiding this comment.
nit: since the previous if breaks, the else and curly braces can be removed
There was a problem hiding this comment.
I did this way so that the err variable exists in both branches, but I can refactor the code assigning err before the if
There was a problem hiding this comment.
as you wish, the code was already like that. it's just a nit so it's not important
1) #27 introduced an issue: the upload returned too soon and the last file in the logs path wasn't uploaded 2) we found an existing issue that caused the last file in the staging path not to be uploaded. Now the last upload is performed when the upload channel is closed
1) #27 introduced an issue: the upload returned too soon and the last file in the logs path wasn't uploaded 2) we found an existing issue that caused the last file in the staging path not to be uploaded. Now the last upload is performed when the upload channel is closed
1) #27 introduced an issue: the upload returned too soon and the last file in the logs path wasn't uploaded 2) we found an existing issue that caused the last file in the staging path not to be uploaded. Now the last upload is performed when the upload channel is closed
1) #27 introduced an issue: the upload returned too soon and the last file in the logs path wasn't uploaded 2) we found an existing issue that caused the last file in the staging path not to be uploaded. Now the last upload is performed when the upload channel is closed
1) #27 introduced an issue: the upload returned too soon and the last file in the logs path wasn't uploaded 2) we found an existing issue that caused the last file in the staging path not to be uploaded. Now the last upload is performed when the upload channel is closed
1) #27 introduced an issue: the upload returned too soon and the last file in the logs path wasn't uploaded 2) we found an existing issue that caused the last file in the staging path not to be uploaded. Now the last upload is performed when the upload channel is closed
❓ What
Add a
FailOnErrorconfig value (default tofalse) that makes baker fail when an error happens instead of only log it.The uploaders now return an error and it's managed in the topology
✅ Checklists
This section contains a list of checklists for common uses, please delete the checklists that are useless for your current use case (or add another checklist if your use case isn't covered yet).
make gofmt-writebeen run on the code?make govetbeen run on the code? Has the code been fixed accordingly to the output?