-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(storage): return file metadata on read #11212
Conversation
I'd say we can just document that it is unavailable via the JSON API. There are other fields in ReaderObjectAttrs which are only populated sometimes. I need to look at the rest of the code more deeply; just let me know when it's ready for review. |
462536d
to
b882d74
Compare
Alright, this is ready for review. |
@tritone sending a ping in case the previous comment didn't notify you |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few comments; overall looks good. Thanks for adding tests!
storage/client_test.go
Outdated
@@ -795,6 +795,62 @@ func TestOpenReaderEmulated(t *testing.T) { | |||
}) | |||
} | |||
|
|||
func TestOpenReaderEmulated_Metadata(t *testing.T) { | |||
transportClientTest(skipHTTP("metadata on read not supported by JSON api"), t, func(t *testing.T, ctx context.Context, project, bucket string, client storageClient) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe the HTTP client should use XML by default, does the test not pass with a default HTTP client?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
right you are.
t.Fatalf("closing object: %v", err) | ||
} | ||
if _, err := veneerClient.Bucket(bucket).Object(want.Name).Update(ctx, ObjectAttrsToUpdate{ | ||
Metadata: map[string]string{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's do at least 2 keys in here just to make sure the decoding logic works correctly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
storage/grpc_client.go
Outdated
@@ -1133,6 +1133,7 @@ func (c *grpcStorageClient) NewRangeReader(ctx context.Context, params *newRange | |||
CacheControl: obj.GetCacheControl(), | |||
LastModified: obj.GetUpdateTime().AsTime(), | |||
Metageneration: obj.GetMetageneration(), | |||
Metadata: obj.GetMetadata(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was concerned this wouldn't be sufficient because we didn't have it in the unmarshaler for gRPC, but it looks like we did that part already so this should be fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I was surprised that was already in there!
storage/integration_test.go
Outdated
@@ -204,10 +205,10 @@ func initIntegrationTest() func() error { | |||
if err != nil { | |||
log.Fatalf("NewStorageControlClient: %v", err) | |||
} | |||
if err := client.Bucket(bucketName).Create(ctx, testutil.ProjID(), nil); err != nil { | |||
if err := client.Bucket(bucketName).Create(ctx, testutil.ProjID(), &BucketAttrs{SoftDeletePolicy: &SoftDeletePolicy{RetentionDuration: 0}}); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should maybe be a separate change in our test harness? Was it impossible to delete the bucket if this wasn't included?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, this is leftover. My org has a policy set that made this necessary for me to be able to run the tests. I'll remove this before merge.
storage/integration_test.go
Outdated
@@ -5041,7 +5042,58 @@ func TestIntegration_ReaderAttrs(t *testing.T) { | |||
Metageneration: attrs.Metageneration, | |||
CRC32C: crc32c(c), | |||
} | |||
if got != want { | |||
if !reflect.DeepEqual(got, want) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can use https://pkg.go.dev/github.com/google/go-cmp/cmp#Diff as elsewhere in these tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
storage/integration_test.go
Outdated
if err := writeObject(ctx, o, defaultType, c); err != nil { | ||
t.Errorf("Write for %v failed with %v", o.ObjectName(), err) | ||
} | ||
defer func() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use t.Cleanup to ensure this is run.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
storage/integration_test.go
Outdated
} | ||
}() | ||
|
||
oa, err := o.Update(ctx, ObjectAttrsToUpdate{Metadata: map[string]string{"Custom-Key": "custom-value"}}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, let's use at least 2 keys here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
storage/integration_test.go
Outdated
Metageneration: attrs.Metageneration, | ||
CRC32C: crc32c(c), | ||
} | ||
if !reflect.DeepEqual(got, want) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use cmp.Diff and let's only check the Metadata field in this test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
storage/reader.go
Outdated
@@ -59,6 +59,10 @@ type ReaderObjectAttrs struct { | |||
// Generation is the generation number of the object's content. | |||
Generation int64 | |||
|
|||
// Metadata represents user-provided metadata, in key/value pairs. | |||
// Not supported by the JSON api. Use ObjectHandle.Attrs instead. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would rephrase this as follows:
// Metadata represents user-provided metadata, in key/value pairs.
//
// It can be nil if no metadata is present, or if the client uses the JSON
// API for downloads. Only the XML and gRPC APIs support getting
// custom metadata via the Reader; for JSON make a separate call to
// ObjectHandle.Attrs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
storage/retry_conformance_test.go
Outdated
@@ -586,6 +587,9 @@ func TestRetryConformance(t *testing.T) { | |||
if host == "" { | |||
t.Skip("This test must use the testbench emulator; set STORAGE_EMULATOR_HOST to run.") | |||
} | |||
if runtime.GOOS == "darwin" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's remove this from the PR; seems non-relevant to this particular issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 there was handling for this in the test script so I moved it in here, good to remove though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One more minor nit; otherwise looks good. Thanks for your contribution!
storage/reader_test.go
Outdated
expectedMetadata := map[string]string{ | ||
"Custom-Metadata-Key": "custom-metadata-value", | ||
} | ||
if !reflect.DeepEqual(rd.Attrs.Metadata, expectedMetadata) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One more place to switch to cmp.Diff
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Old habits die hard, haha.
Okay so looks like there are some presubmit issues:
(2) is kind of an annoying check (who is comparing storage.Reader using comparison operators?) but I think we should fix it. Perhaps we can make a GetMetadata() method on Reader rather than making it a field of ReaderObjectAttrs? Or maybe we could change the Metadata field to a pointer to a map; I think pointers are good to go. Sorry for not flagging that sooner and LMK if that is sufficient clarity on what needs to change... |
Adding a map field to Attrs would have made the object uncomparable
makes sense @tritone . Pushed the updates, hopefully I caught all the formatting stuff. |
storage/reader.go
Outdated
@@ -222,7 +222,9 @@ var emptyBody = ioutil.NopCloser(strings.NewReader("")) | |||
// the stored CRC, returning an error from Read if there is a mismatch. This integrity check | |||
// is skipped if transcoding occurs. See https://cloud.google.com/storage/docs/transcoding. | |||
type Reader struct { | |||
Attrs ReaderObjectAttrs | |||
Attrs ReaderObjectAttrs | |||
objectMetadata map[string]string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like even a non-exported map here makes the comparable check unhappy.
Let's make this a pointer, and you can keep the method to return the map.
to preserve API compatibility - keeps the Reader struct comparable
implements #11211
This isn't in a mergeable state quite yet, but I wanted to start the discussion. This is only supported by the XML and gRPC apis. I'm unsure of how to handle the JSON api. Should I just document that the JSON api does not support metadata on read? Or is there a more elegant way to block the user from trying to read it and being surprised by it being empty?