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

Rewrite metadata store #66

Merged
merged 4 commits into from
Jul 31, 2017

Conversation

Random-Liu
Copy link
Member

@Random-Liu Random-Liu commented Jun 6, 2017

Previously, containerd only managed running container, so we have to do checkpoint to maintain complete lifecycle of a container and sandbox. That's why we introduced the metadata store, which was targeted to be a checkpoint store for container, sandbox and image if needed.

However, it seems that we should not rely on the metadata store now, because:

  1. Containerd added container metadata store, and also supports container label, which could help us checkpoint constant data. We only need to maintain checkpoint of container status now.
  2. The metadata store was originally designed for pure data checkpoint, it only supports update via Update function, which has some limitations:
    a) Update function is not flexible enough, e.g. for image, we need CreateOrUpdate operation to create a metadata if it does not exsit, or update it if does.
    b) Update and checkpoint doesn't make sense to some in-memory data we need to associate with the metadata lifecycle, e.g. containerd container/image client, cni netns object, stop container channel etc.
    c) The whole metadata store logic is unnecessarily complex given that we don't need a general checkpoint solution now.

Thus I made this change. Relatively large, but help us avoid future pain.

Use container store as an example, the store is organized like this:

Store (a simple in-memory map)
   |--> Container (a collection of all resources associated with the container)
             | --> Metadata (constant metadata of the container, will be checkpointed in containerd container label)
             | --> Status (container status maintained in our own checkpoint, the only checkpoint we need now)
             | --> Containerd Container Client (container client of containerd, added soon)
             | --> Container StopCh (a channel indicates container is stopped or not, added soon)
             | --> More... (If needed in the future)

For sandbox, we don't need status.
For image, we don't event need a separate Metadata field, because containerd provided everything we need.

@Random-Liu
Copy link
Member Author

CRI Validation test result is not changed with this PR:

Ran 36 of 36 Specs in 94.648 seconds
FAIL! -- 23 Passed | 13 Failed | 0 Pending | 0 Skipped --- FAIL: TestE2ECRI (94.65s)
FAIL

This was referenced Jun 6, 2017
@@ -24,6 +24,7 @@ for d in $(find . -type d -a \( -iwholename './pkg*' -o -iwholename './cmd*' \))
--exclude='.*_test\.go:.*error return value not checked.*\(errcheck\)$' \
--exclude='duplicate of.*_test.go.*\(dupl\)$' \
--exclude='.*/mock_.*\.go:.*\(golint\)$' \
--exclude='declaration of "err" shadows declaration at.*\(vetshadow\)' \
Copy link
Member

Choose a reason for hiding this comment

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

Removing will introduce the possibility of programming errors with err vars.

Copy link
Member Author

@Random-Liu Random-Liu Jun 6, 2017

Choose a reason for hiding this comment

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

The problem is that the check is known to be error prone. It sometimes report error and sometimes not, and I don't think something like:

if err := func1(); err != nil {
}
if err := func2(); err != nil {
}

should be an error.

Copy link
Member

@mikebrow mikebrow Jun 6, 2017

Choose a reason for hiding this comment

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

only with err? Or are you arguing for a different recommended pattern of use of variables in go? Should we always redeclare := go variables instead of assigning via =? What's the point of have var declaration and assignment as separate language features if we are only going to use declaration and assignment?

Copy link
Member Author

Choose a reason for hiding this comment

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

Only with err. I mean.

Copy link
Member

Choose a reason for hiding this comment

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

Obviously err := foo is just a shortcut for var err error = In your above use case you are repetitivly stating var err error, when all you are really doing is assigning with an =. In your use case the cleaner way is to declare first then just use err = since you don't like using := sometimes and = other times, yes?

Copy link
Member

Choose a reason for hiding this comment

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

I guess my question is then, why is err a special use case of variable declaration and assignment? I understand the thought for keeping each test independent but the variable redeclaration doesn't make the test more independent than using assignment. Symmetry?

Copy link
Member Author

Choose a reason for hiding this comment

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

Offline discussed. Keep the check and update the code.

@mikebrow
Copy link
Member

mikebrow commented Jun 6, 2017

When you refactor you really refactor! :-) Will review from the perspective of it being a complete replacement.

@Random-Liu Random-Liu force-pushed the refactor-metadata-store branch 3 times, most recently from 40aba30 to ea21c3c Compare June 6, 2017 22:32
@yujuhong
Copy link
Member

Will review today.

@Random-Liu
Copy link
Member Author

@yujuhong Thanks~

}, nil
}

// LoadContainer loads the internal used container type.
Copy link
Member

Choose a reason for hiding this comment

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

nit: remove 'used' or s/internal used/internally used

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.

// TODO(random-liu): Add stop channel to get rid of stop poll waiting.
}

// NewContainer creates a internal used container type.
Copy link
Member

Choose a reason for hiding this comment

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

nit: remove 'used' or s/internal used/internally used

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.


// NewStore creates a container store.
func NewStore() *Store {
return &Store{containers: make(map[string]Container)}
Copy link
Member

Choose a reason for hiding this comment

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

I noticed that you pass around Container instead of *Container in the Store operations. Is this intentional to prevent callers from modifying the struct (which cannot work completely without deep copying)? This would lead to more memory allocation/copying, so just want to understand the use case.

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 use Container rather than *Container because:

  1. The value copy could prevent callers from mutating the struct fields in some way, because most fields are primitive types.
  2. Most importantly, when caller using the api, usually they won't expect to be able to mutate fields of a returned value, but may expect to mutate a returned pointer.

As for the overhead, metadata is small, so there shouldn't be too much overhead. :)

}

// Delete deletes the container status from disk atomically.
func (m *statusStorage) Delete() error {
Copy link
Member

Choose a reason for hiding this comment

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

Why can't we delete m.status in this function? It looks like Delete() is only meaningful if we back the data on the disk.

Copy link
Member Author

Choose a reason for hiding this comment

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

It doesn't matter whether we delete m.status or not, because once the container is removed from container list, no one should be able to retrieve and access it.

Delete m.status will only cause race condition between operations which already get the reference.


// NewStore creates an image store.
func NewStore() *Store {
return &Store{images: make(map[string]Image)}
Copy link
Member

Choose a reason for hiding this comment

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

Same question about the rationale of storing Image vs *Image

Copy link
Member Author

Choose a reason for hiding this comment

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

Same with above.

)

// Image contains all resources associated with the image. All fields
// are **read-only**.
Copy link
Member

Choose a reason for hiding this comment

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

Is that true? In Add(), you merge RepoTags and RepoDigest and write it back to the store.

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 update the comment to "All fields MUST not be mutated directly".

Copy link
Member

Choose a reason for hiding this comment

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

Can we do this change universally on the other read only comments?

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.

)

// Container contains all resources associated with the container. All fields of
// Container are thread safe.
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure how fields can be thread-safe. I think you mean the methods to mutate the status are thread-safe?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, thanks. Will update 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.

Done.

// Store stores all sandboxes.
type Store struct {
lock sync.RWMutex
sandboxes map[string]Sandbox
Copy link
Member

Choose a reason for hiding this comment

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

Same question about storing pointers vs. structs.

Copy link
Member Author

Choose a reason for hiding this comment

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

Same.

@Random-Liu
Copy link
Member Author

Will rebase this and address comments.

@Random-Liu Random-Liu modified the milestones: v0.2.0, v0.1.0-alpha.1 Jun 20, 2017
@Random-Liu
Copy link
Member Author

Random-Liu commented Jun 20, 2017

Moved to v0.2.0, because this PR mainly:

  1. Eases the work to switching to new containerd client, and add permanent network namespace.
  2. Makes it possible to checkpoint most metadata in container label.
  3. Makes it easier to make future change.

There is no new feature introduced.

Since we don't have time to finish the containerd client switch and permanent network namespace work. Let's also punt this one to v0.2.0.

@Random-Liu Random-Liu force-pushed the refactor-metadata-store branch 2 times, most recently from 3847f17 to 454280a Compare July 28, 2017 03:52
@Random-Liu
Copy link
Member Author

@yujuhong Rebased the PR. Could you help me take another look? Maybe focus on the first commit.

@mikebrow Could you help me review this PR?

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.

Please see questions and comments.

@@ -1,183 +0,0 @@
/*
Copy link
Member

Choose a reason for hiding this comment

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

Suggest rename from refactor metadata store to rewrite metadata store. Or merge the delete and create commits into one so we can see the diff.

Copy link
Member Author

Choose a reason for hiding this comment

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

You won't see the diff even after I merge those 2. Will change to "rewrite".

return nil, fmt.Errorf("failed to add container metadata %+v into store: %v",
meta, err)
if err := c.containerStore.Add(container); err != nil {
return nil, fmt.Errorf("failed to add container %q into store: %v", id, err)
Copy link
Member

Choose a reason for hiding this comment

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

Good.. like the pattern using new() (container, error) then calling add with container vs using .create(meta). But it's strange that you had to defer the container.Status.Delete for a container that never gets added to the store?

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 the storing of the status in the NewContainer() call but don't understand why the status is stored before the container. Is that to make sure the container always has status? maybe the container store.add could be changed to add container with status to make it more atomic?

Copy link
Member Author

Choose a reason for hiding this comment

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

Add need to lock the container list. I don't think we want to lock the list and do some disk operation.

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. Added Container.Delete function.

@@ -45,15 +45,16 @@ func (c *criContainerdService) ExecSync(ctx context.Context, r *runtime.ExecSync
}
}()

// Get container metadata from our container store.
meta, err := c.containerStore.Get(r.GetContainerId())
// Get container from our container store.
Copy link
Member

Choose a reason for hiding this comment

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

like the comment but the naming might get confusing down the road over the two container stores, ours and containerd's.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, that's a problem... :(


var containers []*runtime.Container
for _, meta := range metas {
containers = append(containers, toCRIContainer(meta))
for _, container := range containersInStore {
Copy link
Member

Choose a reason for hiding this comment

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

I think this is why containerd came up with the name task/tasks.

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 here? :)

Copy link
Member

@mikebrow mikebrow Jul 28, 2017

Choose a reason for hiding this comment

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

you refactored from range metas to range containersInStore presumably cause you didn't like the term meta... and didn't have a better option than just calling the data container again... :-)

if err := c.sandboxStore.Create(meta); err != nil {
return nil, fmt.Errorf("failed to add sandbox metadata %+v into store: %v",
meta, err)
sandbox.CreatedAt = time.Now().UnixNano()
Copy link
Member

Choose a reason for hiding this comment

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

defer delete for state of 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.

We don't maintain state of sandbox. We infer that directly from sandbox container state.

We could not do that for container because we also need exit code, finished timestamp etc. which we have to checkpoint ourselves. But for sandbox, we only care about whether it is running or not.

Copy link
Member

Choose a reason for hiding this comment

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

ok yeah been having some debug issues on the update PR for sandboxes that have a messed up state (can't be stopped) then later on can't be created cause /pause isn't there.. the rootfs is messed up.

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'll help debug your PR over the weekend. :)

Metadata
// Status stores the status of the container.
Status StatusStorage
// TODO(random-liu): Add containerd container client.
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.

I mean in the future, we could this here, after we start to use containerd client.

Copy link
Member

Choose a reason for hiding this comment

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

kk

)

// NOTE(random-liu):
// 1) Metadata is read-only.
Copy link
Member

Choose a reason for hiding this comment

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

? you mean can't be updated after create?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, will update.

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.


// NOTE(random-liu):
// 1) Metadata is read-only.
// 2) Metadata is checkpointed as containerd container label.
Copy link
Member

Choose a reason for hiding this comment

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

just confirming we'll use labels not annotations for all metadata so we can find the container by the metadata labels or... maybe some combination of labels and annotations...?

Copy link
Member Author

Choose a reason for hiding this comment

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

containerd only has label, there is no notion of annotation in containerd.

And we store metadata into label just for checkpoint, not for filtering.

Copy link
Member

@mikebrow mikebrow Jul 28, 2017

Choose a reason for hiding this comment

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

oci will have the annotations.. and I suspect containerd will let them pass through without providing first class support.. I was thinking about image labels.. my bad.

)

// Image contains all resources associated with the image. All fields
// are **read-only**.
Copy link
Member

Choose a reason for hiding this comment

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

Can we do this change universally on the other read only comments?

@Random-Liu Random-Liu changed the title Refactor metadata store Rewrite metadata store Jul 28, 2017
Signed-off-by: Lantao Liu <lantaol@google.com>
Signed-off-by: Lantao Liu <lantaol@google.com>
Signed-off-by: Lantao Liu <lantaol@google.com>
Signed-off-by: Lantao Liu <lantaol@google.com>
@Random-Liu
Copy link
Member Author

@mikebrow Addressed comments.

The code could be cleaned up more, especially when we start to use containerd client. We could do that in the future.

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

@mikebrow mikebrow added the lgtm label Jul 29, 2017
@Random-Liu Random-Liu merged commit cbd936b into containerd:master Jul 31, 2017
@Random-Liu Random-Liu deleted the refactor-metadata-store branch July 31, 2017 05:01
lanchongyizu pushed a commit to lanchongyizu/cri-containerd that referenced this pull request Sep 3, 2017
dcantah pushed a commit to dcantah/cri that referenced this pull request Jun 12, 2020
Respect OCI image config default username
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

4 participants