Skip to content

upload: s3: add FailOnError config#27

Merged
tommyblue merged 12 commits intomasterfrom
upload_s3_fail_on_err
Sep 9, 2020
Merged

upload: s3: add FailOnError config#27
tommyblue merged 12 commits intomasterfrom
upload_s3_fail_on_err

Conversation

@tommyblue
Copy link
Copy Markdown
Contributor

@tommyblue tommyblue commented Aug 12, 2020

❓ What

Add a FailOnError config value (default to false) 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).

  • Is there unit/integration test coverage for all new and/or changed functionality added in this PR?
  • Have the changes in this PR been functionally tested?
  • Has make gofmt-write been run on the code?
  • Has make govet been run on the code? Has the code been fixed accordingly to the output?
  • Have the changes been added to the CHANGELOG.md file?
  • Have the steps in CONTRIBUTING.md been followed to update a Go module?

@tommyblue tommyblue force-pushed the upload_s3_fail_on_err branch from e7dcc99 to f8bf618 Compare August 12, 2020 10:24
@tommyblue tommyblue requested a review from arl August 12, 2020 10:24
@tommyblue tommyblue force-pushed the upload_s3_fail_on_err branch 2 times, most recently from 6c7ad7d to e2283d5 Compare August 12, 2020 10:26
Copy link
Copy Markdown
Collaborator

@arl arl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread CHANGELOG.md Outdated
Comment thread upload/s3.go Outdated
Copy link
Copy Markdown
Collaborator

@arl arl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread upload/s3_test.go Outdated
Comment on lines +228 to +251
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
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 prepareUploadS3TestFolder the 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_uploadDirectory in 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.

Comment thread upload/s3_test.go Outdated
srcDir, _ := prepareUploadS3TestFolder(t, numFiles)

mockUploadFn := func(uploader *s3manager.Uploader, bucket, prefix, localPath, fpath string) error {
time.Sleep(100 * time.Millisecond)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this sleep here needed?

Comment thread upload/s3_test.go Outdated
Comment on lines +318 to +319
// time.Sleep(100 * time.Millisecond)
// return errors.New("Fake error")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leftover?

Comment thread upload/s3_test.go Outdated
tmpDir, fnames := prepareUploadS3TestFolder(t, 1)
fname := fnames[0]

stagingDir, err := ioutil.TempDir(".", "upload_s3_test_staging")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as before: this directory is not cleanup after the test

Comment thread upload/s3_test.go Outdated
tmpDir, fnames := prepareUploadS3TestFolder(t, 1)
fname := fnames[0]

stagingDir, err := ioutil.TempDir(".", "upload_s3_test_staging")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be useful to use t.Name() for temporary folders related to a specific test

Comment thread upload/s3.go Outdated
err := filepath.Walk(u.Cfg.StagingPath, func(fpath string, info os.FileInfo, err error) error {
if err != nil {
return err
globFatalErr := atomic.Value{}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: calling this global may be a bit misleading and be confused with a global variable.
name proposition: mainErr or exitErr

Comment thread upload/s3.go Outdated
Comment on lines +248 to +249
if globFatalErr.Load() != nil {
return globFatalErr.Load().(error)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of calling Load twice we would store the result of Load in a variable and reuse it

Comment thread upload/s3_test.go Outdated
t.Fatalf("Can't setup test: %v", err)
}
mockUploadFn := func(uploader *s3manager.Uploader, bucket, prefix, localPath, fpath string) error {
time.Sleep(100 * time.Millisecond)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same, why is this needed?

@tommyblue tommyblue force-pushed the upload_s3_fail_on_err branch from da2ca55 to cdaf041 Compare September 8, 2020 08:12
@tommyblue
Copy link
Copy Markdown
Contributor Author

All comments should have been addressed

Copy link
Copy Markdown
Collaborator

@arl arl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread upload/s3.go Outdated
totalerr int64
queuedn int64

uploadFn func(uploader *s3manager.Uploader, bucket, prefix, localPath, fpath string) error
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 both CompleteMultipartUploadOutput and PutObjectOutput. 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

Comment thread upload/s3.go Outdated
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 {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: since the previous if breaks, the else and curly braces can be removed

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did this way so that the err variable exists in both branches, but I can refactor the code assigning err before the if

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as you wish, the code was already like that. it's just a nit so it's not important

@tommyblue tommyblue merged commit cc35bb7 into master Sep 9, 2020
@tommyblue tommyblue deleted the upload_s3_fail_on_err branch September 9, 2020 08:33
tommyblue pushed a commit that referenced this pull request Sep 17, 2020
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
tommyblue pushed a commit that referenced this pull request Sep 17, 2020
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
tommyblue pushed a commit that referenced this pull request Sep 18, 2020
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
tommyblue pushed a commit that referenced this pull request Sep 18, 2020
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
tommyblue pushed a commit that referenced this pull request Sep 18, 2020
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
tommyblue pushed a commit that referenced this pull request Sep 18, 2020
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants