Skip to content
This repository has been archived by the owner on Mar 9, 2022. It is now read-only.

Add the metadata store interface and an in-memory implementation. #9

Merged
merged 3 commits into from
May 3, 2017

Conversation

Random-Liu
Copy link
Member

@Random-Liu Random-Liu commented Apr 18, 2017

For #6.

This PR added:

  • The metadata store interface;
  • An in-memory implementation;
  • A sandbox metadata store based on the metadata store.

In the future, we need to have an on-disk implementation. The TODOs in memory_store.go describes how to implement a file-based checkpoint.

The metadata store interface is defined as follows:

// All byte arrays are expected to be read-only. User MUST NOT modify byte
// 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.
// If there is an error, the update will be rolled back.
type UpdateFunc func([]byte) ([]byte, error)

// Metadata is a metadata in the store. It supports transactional update.
type Metadata interface {
	// Get a snapshot of the metadata.
	Get() ([]byte, error)
	// Update the value.
	Update(UpdateFunc) error
}

// MetadataStore is the interface for storing metadata.
type MetadataStore interface {
	// 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.
	Create(string, []byte) (Metadata, error)
	// Get the metadata by id.
	Get(string) (Metadata, error)
	// List all the metadatas.
	List() ([]Metadata, error)
	// Delete the metadata by id.
	// Note:
	// * Delete MUST not return error if the id doesn't exist.
	// * The id and metadata MUST be deleted in one transaction.
	Delete(string) error
}

/cc @kubernetes-incubator/maintainers-cri-containerd @feiskyer @resouer

@Random-Liu Random-Liu mentioned this pull request Apr 18, 2017
34 tasks
@Random-Liu Random-Liu force-pushed the add-metadata-store branch 2 times, most recently from 0c53a26 to db59882 Compare April 19, 2017 06:12
@Random-Liu Random-Liu changed the title Add the metadata store interface and an in-memory implementation. [WIP] Add the metadata store interface and an in-memory implementation. Apr 19, 2017
@Random-Liu
Copy link
Member Author

Change to WIP, I'll firstly try to use the metadata store in the sandbox lifecycle management to see whether the interface is sufficient.

@Random-Liu Random-Liu force-pushed the add-metadata-store branch 3 times, most recently from 6a8e318 to 7e743dc Compare April 20, 2017 23:54
@Random-Liu
Copy link
Member Author

Random-Liu commented Apr 21, 2017

@yujuhong @dchen1107

  • The metadata store will be very important for the stability of cri-containerd;
  • And this is the first time we will do checkpoint.

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.

Copy link
Member

@mikebrow mikebrow left a 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;
Copy link
Member

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?

Copy link
Member Author

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.

List() ([]Metadata, error)
// Delete the metadata by id.
// Note:
// * Delete MUST not return error if the id doesn't exist.
Copy link
Member

Choose a reason for hiding this comment

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

/s/not//

Copy link
Member Author

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.

Copy link
Member

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...

Create(string, []byte) (Metadata, error)
// Get the metadata by id.
Get(string) (Metadata, error)
// List all the metadatas.
Copy link
Member

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

Get(string) (Metadata, error)
// List all the metadatas.
List() ([]Metadata, error)
// Delete the metadata by id.
Copy link
Member

Choose a reason for hiding this comment

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

/s/metadata/Metadata/

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

// * 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.
Copy link
Member

Choose a reason for hiding this comment

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

/s/metadata/Metadata/

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

// []byte with interface{}, so as to avoid extra marshal/unmarshal on the
// user side.
type MetadataStore interface {
// Create the metadata with the specified id.
Copy link
Member

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./

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

// 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.
Copy link
Member

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./

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

// 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.
Copy link
Member

Choose a reason for hiding this comment

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

/s/current/new/

Copy link
Member Author

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. :)

@Random-Liu Random-Liu force-pushed the add-metadata-store branch 2 times, most recently from a427278 to 45dc29b Compare April 26, 2017 01:28
@Random-Liu Random-Liu changed the title [WIP] Add the metadata store interface and an in-memory implementation. Add the metadata store interface and an in-memory implementation. Apr 26, 2017
@Random-Liu
Copy link
Member Author

Added unit test, PR ready for review.

@Random-Liu Random-Liu force-pushed the add-metadata-store branch 4 times, most recently from f2d405e to 1e83709 Compare April 26, 2017 04:54
sbs, err = s.List()
assert.NoError(err)
assert.Len(sbs, 2)
meta, err := s.Get(testID)
Copy link
Member

@mikebrow mikebrow Apr 26, 2017

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")

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

meta, err := s.Get(testID)
assert.NoError(err)
assert.True(meta == nil)
}
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

@mikebrow mikebrow left a 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 ...


// delete deletes the data on disk atomically.
func (m *memoryMeta) delete() error {
// TODO(random-liu): Hold write lock, rename the data on the disk.
Copy link
Member

Choose a reason for hiding this comment

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

m.data = nil?

Copy link
Member Author

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:

  1. If that operation is read only, it will return staled data.
  2. 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?

Copy link
Member

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?

Copy link
Member Author

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"?

Copy link
Member

@mikebrow mikebrow Apr 27, 2017

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.

Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

@mikebrow mikebrow Apr 28, 2017

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

Copy link
Member Author

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.
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

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)
}
Copy link
Member

Choose a reason for hiding this comment

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

create again after delete?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

Copy link
Member

@mikebrow mikebrow 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 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..

@Random-Liu
Copy link
Member Author

Random-Liu commented Apr 29, 2017

@yujuhong Addressed comments. The code does become cleaner after combine Metadata and MetadataStore.

Will squash the last commits before merging.

@Random-Liu Random-Liu force-pushed the add-metadata-store branch 3 times, most recently from 5d257f2 to f9c48d3 Compare April 29, 2017 00:58
Copy link
Member

@mikebrow mikebrow left a comment

Choose a reason for hiding this comment

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

LGTM

// 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.
Copy link
Member

Choose a reason for hiding this comment

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

s/tolerant/tolerate

Copy link
Member Author

Choose a reason for hiding this comment

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

Will do.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

// 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.
Copy link
Member

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

Will do.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

// 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)
Copy link
Member

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?

Copy link
Member Author

@Random-Liu Random-Liu May 3, 2017

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

Updated the comments.

// 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.
Copy link
Member

Choose a reason for hiding this comment

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

s/tolerant/tolerate

Copy link
Member Author

Choose a reason for hiding this comment

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

Will do.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

// 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.
Copy link
Member

Choose a reason for hiding this comment

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

??

Copy link
Member Author

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

Removed.

return s.store.Create(metadata.ID, data)
}

// Get gets a specified sandbox.
Copy link
Member

Choose a reason for hiding this comment

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

s/a/the

Copy link
Member Author

Choose a reason for hiding this comment

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

Will do.

Copy link
Member Author

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.
Copy link
Member

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?

Copy link
Member Author

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.

return nil
}

// metadataStore is the implementation of MetadataStore
Copy link
Member

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Will do.

Copy link
Member Author

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 {
Copy link
Member

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?

Copy link
Member Author

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?

Copy link
Member

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?

Copy link
Member Author

@Random-Liu Random-Liu May 3, 2017

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. :)

t.Logf("get should return nil without error after deletion")
meta, err := s.Get(testID)
assert.NoError(err)
assert.True(meta == nil)
Copy link
Member

Choose a reason for hiding this comment

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

Can use assert.Nil()

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

@Random-Liu
Copy link
Member Author

Random-Liu commented May 3, 2017

@yujuhong Addressed comments in the latest commit. Thanks for reviewing!

@yujuhong
Copy link
Member

yujuhong commented May 3, 2017

LGTM

@Random-Liu
Copy link
Member Author

Random-Liu commented May 3, 2017

Apply LGTM based on #9 (comment) and #9 (review).
@yujuhong @mikebrow Thanks for reviewing! Will squash and merge this one, and rebase the others! :)

@Random-Liu Random-Liu added the lgtm label May 3, 2017
Signed-off-by: Random-Liu <lantaol@google.com>
Signed-off-by: Random-Liu <lantaol@google.com>
Signed-off-by: Random-Liu <lantaol@google.com>
@Random-Liu Random-Liu merged commit 1532079 into containerd:master May 3, 2017
@Random-Liu Random-Liu deleted the add-metadata-store branch May 3, 2017 21:18
lanchongyizu pushed a commit to lanchongyizu/cri-containerd that referenced this pull request Sep 3, 2017
Add the metadata store interface and an in-memory implementation.
adelina-t pushed a commit to adelina-t/cri that referenced this pull request Jun 20, 2019
Added symlink resolution for host path and windows platform support in sandbox-platform
dcantah pushed a commit to dcantah/cri that referenced this pull request Jan 7, 2022
Vendor hcsshim commit 041736614696a7394edcb733292688c2bab69ead
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants