-
Notifications
You must be signed in to change notification settings - Fork 350
Add the metadata store interface and an in-memory implementation. #9
Conversation
0c53a26
to
db59882
Compare
Change to |
6a8e318
to
7e743dc
Compare
Please take a look at this PR to make sure the design makes sense to you. I'll add unit test after the design is reviewed. |
7e743dc
to
210b71e
Compare
210b71e
to
7784624
Compare
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.
Just nits.. would need to see it in action though since there's so many spinning wheels I might've missed something in my read only review.
// version and versionedSandboxMetadata is not used now, but should be used | ||
// in the future: | ||
// 1) Only versioned metadata should be written into the store; | ||
// 2) Only unversioned metadata should be returned to the user; |
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.
why hide the version info?
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.
Because I don't think the caller want to care about the version.
All version stuff should be handled inside the metadata store.
pkg/metadata/store/store.go
Outdated
List() ([]Metadata, error) | ||
// Delete the metadata by id. | ||
// Note: | ||
// * Delete MUST not return error if the id doesn't exist. |
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.
/s/not//
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 just mean Delete should be idempotent, it should not return error when corresponding metadata doesn't exist.
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 see so:
// Delete(id) returns error = nil when there is no metadata for the id or when the metadata has been successfully deleted. Otherwise Delete(id) returns an error !=nil when there is metadata but the delete operation has failed...
pkg/metadata/store/store.go
Outdated
Create(string, []byte) (Metadata, error) | ||
// Get the metadata by id. | ||
Get(string) (Metadata, error) | ||
// List all the metadatas. |
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.
return entire array of Metadata from MetadataStore
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.
pkg/metadata/store/store.go
Outdated
Get(string) (Metadata, error) | ||
// List all the metadatas. | ||
List() ([]Metadata, error) | ||
// Delete the metadata by id. |
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.
/s/metadata/Metadata/
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.
pkg/metadata/store/store.go
Outdated
// * Create MUST return error if the id already exists. | ||
// * The id and metadata MUST be added in one transaction. | ||
Create(string, []byte) (Metadata, error) | ||
// Get the metadata by id. |
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.
/s/metadata/Metadata/
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.
pkg/metadata/store/store.go
Outdated
// []byte with interface{}, so as to avoid extra marshal/unmarshal on the | ||
// user side. | ||
type MetadataStore interface { | ||
// Create the metadata with the specified id. |
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.
/s/the metadata with the specified id/an instance of Metadata with the specified id containing the array of bytes passed./
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
pkg/metadata/store/store.go
Outdated
// Create the metadata with the specified id. | ||
// Note: | ||
// * Create MUST return error if the id already exists. | ||
// * The id and metadata MUST be added in one transaction. |
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.
/s/metadata/the instance of Metadata/
/s/./to the MetadataStore./
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.
pkg/metadata/store/store.go
Outdated
// array element directly!! | ||
|
||
// UpdateFunc is function used to update a specific metadata. The value | ||
// passed in is the current value. The updated value should be returned. |
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.
/s/current/new/
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.
Nope, it's the old value. I'll explicitly say "old" here. :)
a427278
to
45dc29b
Compare
Added unit test, PR ready for review. |
f2d405e
to
1e83709
Compare
pkg/metadata/sandbox_test.go
Outdated
sbs, err = s.List() | ||
assert.NoError(err) | ||
assert.Len(sbs, 2) | ||
meta, err := s.Get(testID) |
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.
t.Logf("after deleting metadata for id Get for that id should return nil for metadata and with no error")
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.
pkg/metadata/sandbox_test.go
Outdated
meta, err := s.Get(testID) | ||
assert.NoError(err) | ||
assert.True(meta == 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'd add another test that creates the metadata again for id.. is that the expected pattern? or are we only deleting the metadata as we delete a sandbox?
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.
Will only add the case into the basic metadata store.
We'll have SandboxStore
, ContainerStore
and ImageStore
. They will be sharing the same underlying basic metadata store. If we add full test coverage for all these 3 stores, there will be a lot of duplicated code. (Golang doesn't support something like C++ template... :()
So let's add more test coverage in the basic metadata store, and only basic and necessary case at this level.
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.
question on the delete ...
pkg/metadata/store/metadata_store.go
Outdated
|
||
// delete deletes the data on disk atomically. | ||
func (m *memoryMeta) delete() error { | ||
// TODO(random-liu): Hold write lock, rename the data on the disk. |
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.
m.data = 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.
Option 1: If we set m.data=nil
, other operation (e.g. ListPodSandbox, PodSandboxStatus) which has already got the metadata reference will fail, and return error.
Option 2: If we don't set m.data=nil
, other operation which has got the reference will success:
- If that operation is read only, it will return staled data.
- If that operation updates the metadata, it will only change the in-memory state.
Any future operations won't be able to get the metadata because it is removed from the list already.
Either option 1 or 2 is acceptable to me, because this is unavoidable race unless we lock the whole store for each single metadata operation, which is not what we want. :)
I prefer option 2. WDYT?
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.
Is there a door number 3? I suppose we should talk about this. Maybe defer calls to delete?
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.
What do you mean by "defer calls to delete"?
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.
If deleting metadata can't be done due to dangling references one could treat the request as a deferred request to delete.. or perhaps direct that delete should only be called like this "defer meta.delete()" and in such a manner as the dangling references will have been removed first.
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.
guess I don't get the part about the call is only for deleting data on disk.. while leaving it around for further use.. Maybe a quick phone call.
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.
guess I don't get the part about the call is only for deleting data on disk.. while leaving it around for further use.. Maybe a quick phone call.
Actually the data is already removed from the store, that is where the deletion happens. It's already not accessible by future operations.
The only problem is for the operations which have already got the reference, they could either fail or return/update staled data, because there is no order guarantee for 2 concurrent operations.
Maybe I should change the name to deleteCheckpoint
or something to make it more clear.
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 by further I meant.. continued use by existing references..
yes deleteCheckpoint works! Then just some comments to explain it and we're good
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.
@mikebrow OK. Will update the PR. :)
assert.Equal(testData[0], old) | ||
|
||
// TODO(random-liu): Test delete and cleanup after disk based | ||
// implementation is added. |
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.
TODO for delete and cleanup?
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.
The TODO is just for this.
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.
Not sure how I missed the TODO text.. odd :-)
existingGot, err := existingMeta.Get() | ||
assert.NoError(err) | ||
assert.Equal([]byte("updated-metadata-1"), existingGot) | ||
} |
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.
create again after delete?
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.
cbc7694
to
8b102e7
Compare
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.
LGTM
Only thing I would change is to consider refactoring the packaging a bit. For example: the metadata package has a sandbox in it ... might be better if the sandbox was in it's own package; also the metadata/store package has the metadata interface in it .. this might be better in the metadata package; the metadatastore interface could move to the metadata_store.go file..
@yujuhong Addressed comments. The code does become cleaner after combine Will squash the last commits before merging. |
5d257f2
to
f9c48d3
Compare
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.
LGTM
f9c48d3
to
6aa2d84
Compare
pkg/metadata/store/metadata_store.go
Outdated
// into files during update. Metadata should serve from memory, but any modification | ||
// should be checkpointed, so that memory could be recovered after restart. It | ||
// should be possible to disable the checkpoint for testing. | ||
// Note that checkpoint update may fail, so the recovery logic should tolerant that. |
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.
s/tolerant/tolerate
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.
Will do.
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.
pkg/metadata/store/metadata_store.go
Outdated
// MetadataStore is the interface for storing metadata. | ||
// TODO(random-liu): Initialize the metadata store with a type, and replace | ||
// []byte with interface{}, so as to avoid extra marshal/unmarshal on the | ||
// user side. |
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.
Are these methods thread-safe? Should clarify it in the comment.
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.
Will do.
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.
pkg/metadata/store/metadata_store.go
Outdated
// UpdateFunc is function used to update a specific metadata. The value | ||
// passed in is the old value. The updated value should be returned. | ||
// If there is an error, the update will be rolled back. | ||
type UpdateFunc func([]byte) ([]byte, error) |
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.
The purpose of the UpdateFunc
is unclear to me. Is it supposed to consume the existing/old data, modify, and output the new data? If that's the case, it's merely modifying an in-memory copy of the data. How would it guarantee a rollback, like the comment suggested? Rollback should be a feature provided by the MetadataStore
itself, no?
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.
As is comment above:
// All byte arrays are expected to be read-only. User MUST NOT modify byte
// array element directly!!
We could pass in a copy of the current data. However, that introduces extra overhead.
In fact all higher level metadata store will just unmarshal the byte array instead of changing it directly.
Will make the comment more clear.
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.
Updated the comments.
pkg/metadata/store/metadata_store.go
Outdated
// in memory and checkpoint metadata into files during update. Metadata should serve | ||
// from memory, but any modification should be checkpointed, so that memory could be | ||
// recovered after restart. It should be possible to disable the checkpoint for testing. | ||
// Note that checkpoint update may fail, so the recovery logic should tolerant that. |
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.
s/tolerant/tolerate
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.
Will do.
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.
pkg/metadata/store/metadata_store.go
Outdated
// recovered after restart. It should be possible to disable the checkpoint for testing. | ||
// Note that checkpoint update may fail, so the recovery logic should tolerant that. | ||
|
||
// An atomic file operator is needed. |
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 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.
This is a part of TODO. We need atomic file writer to do checkpoint. Could be removed now.
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.
Removed.
pkg/metadata/sandbox.go
Outdated
return s.store.Create(metadata.ID, data) | ||
} | ||
|
||
// Get gets a specified sandbox. |
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.
s/a/the
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.
Will do.
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.
return nil | ||
} | ||
|
||
// cleanup cleans up all temporary files left-over. |
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.
Why would there be multiple temporary files for a single piece of metadata?
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 may be temporary files left over from previous failed update operations.
pkg/metadata/store/metadata_store.go
Outdated
return nil | ||
} | ||
|
||
// metadataStore is the implementation of MetadataStore |
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.
metadataStore implements MetadataStore
or metadataStore is an implementation of MetadataStore
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.
Will do.
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.
} | ||
|
||
// SandboxStore is the store for metadata of all sandboxes. | ||
type SandboxStore interface { |
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.
Do we need the interface?
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.
Why not an interface? Should we avoid interface?
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.
Not saying we should avoid interfaces, but just asking to see why interface is needed here. Do we expect other implmentations or fakes/mocks for testing?
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 don't think we'll have other implementations at this level. But it seems more clear to me to have an interface to tell the caller what operations are supported, instead of going through the code.
May be a misusing. :)
pkg/metadata/sandbox_test.go
Outdated
t.Logf("get should return nil without error after deletion") | ||
meta, err := s.Get(testID) | ||
assert.NoError(err) | ||
assert.True(meta == 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.
Can use assert.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.
Done.
@yujuhong Addressed comments in the latest commit. Thanks for reviewing! |
LGTM |
Apply LGTM based on #9 (comment) and #9 (review). |
Signed-off-by: Random-Liu <lantaol@google.com>
Signed-off-by: Random-Liu <lantaol@google.com>
Signed-off-by: Random-Liu <lantaol@google.com>
0e31b26
to
86997f0
Compare
Add the metadata store interface and an in-memory implementation.
Added symlink resolution for host path and windows platform support in sandbox-platform
Vendor hcsshim commit 041736614696a7394edcb733292688c2bab69ead
For #6.
This PR added:
In the future, we need to have an on-disk implementation. The
TODOs
inmemory_store.go
describes how to implement a file-based checkpoint.The metadata store interface is defined as follows:
/cc @kubernetes-incubator/maintainers-cri-containerd @feiskyer @resouer