The sandbox might be recovered from v1.x release. It doesn't have
metadata bucket. We should ignore the not-found error.
How to reproduce the issue:
```bash
➜ containerd git:(main) sudo ctr version
Client:
Version: 1.6.22
Revision: 8165feabfdfe38c65b599c4993d227328c231fca
Go version: go1.19.11
Server:
Version: 1.6.22
Revision: 8165feabfdfe38c65b599c4993d227328c231fca
UUID: be4216aa-8a2e-4305-9186-efeacd2d9a17
➜ containerd git:(main) cat /tmp/pod.json
{
"metadata": {
"name": "nginx-sandbox",
"namespace": "default",
"attempt": 1,
"uid": "hdishd83djaidwnduwk28bcsb"
},
"log_directory": "/tmp",
"linux": {
}
}
➜ containerd git:(main) sudo crictl runp /tmp/pod.json
616ea1cc657c57e80abf74e707a8177878ac2ec1ab7c346b4adb7bc0fadf986e
➜ containerd git:(main) sudo crictl pods
POD ID CREATED STATE NAME NAMESPACE ATTEMPT RUNTIME
616ea1cc657c5 9 seconds ago Ready nginx-sandbox default 1 (default)
➜ containerd git:(main) make BUILDTAGS=no_btrfs
➜ containerd git:(main) sudo PREFIX=/usr make install
+ install bin/ctr bin/containerd bin/containerd-stress bin/containerd-shim-runc-v2
➜ containerd git:(main) sudo systemctl restart containerd
➜ containerd git:(main) sudo ctr version
Client:
Version: v1.7.0-943-g980767551
Revision: 9807675518
Go version: go1.20.10
Server:
Version: v1.7.0-943-g980767551
Revision: 9807675518
UUID: be4216aa-8a2e-4305-9186-efeacd2d9a17
➜ containerd git:(main) sudo crictl stopp 616ea1cc657c5
Stopped sandbox 616ea1cc657c5
➜ containerd git:(main) sudo crictl rmp 616ea1cc657c5
E1019 14:03:37.885162 2052643 remote_runtime.go:295] "RemovePodSandbox from runtime service failed" err="rpc error: code = Unknown desc = failed to remove sandbox metadata from store: failed to delete sandbox \"616ea1cc657c57e80abf74e707a8177878ac2ec1ab7c346b4adb7bc0fadf986e\": bucket not found" podSandboxID="616ea1cc657c5"
removing the pod sandbox "616ea1cc657c5": rpc error: code = Unknown desc = failed to remove sandbox metadata from store: failed to delete sandbox "616ea1cc657c57e80abf74e707a8177878ac2ec1ab7c346b4adb7bc0fadf986e": bucket not found
```
Signed-off-by: Wei Fu <fuweid89@gmail.com>
385 lines
9.1 KiB
Go
385 lines
9.1 KiB
Go
/*
|
|
Copyright The containerd Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package metadata
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/containerd/containerd/errdefs"
|
|
"github.com/containerd/containerd/filters"
|
|
"github.com/containerd/containerd/identifiers"
|
|
"github.com/containerd/containerd/metadata/boltutil"
|
|
"github.com/containerd/containerd/namespaces"
|
|
api "github.com/containerd/containerd/sandbox"
|
|
"github.com/containerd/typeurl/v2"
|
|
"go.etcd.io/bbolt"
|
|
)
|
|
|
|
type sandboxStore struct {
|
|
db *DB
|
|
}
|
|
|
|
var _ api.Store = (*sandboxStore)(nil)
|
|
|
|
// NewSandboxStore creates a datababase client for sandboxes
|
|
func NewSandboxStore(db *DB) api.Store {
|
|
return &sandboxStore{db: db}
|
|
}
|
|
|
|
// Create a sandbox record in the store
|
|
func (s *sandboxStore) Create(ctx context.Context, sandbox api.Sandbox) (api.Sandbox, error) {
|
|
ns, err := namespaces.NamespaceRequired(ctx)
|
|
if err != nil {
|
|
return api.Sandbox{}, err
|
|
}
|
|
|
|
sandbox.CreatedAt = time.Now().UTC()
|
|
sandbox.UpdatedAt = sandbox.CreatedAt
|
|
|
|
if err := s.validate(&sandbox); err != nil {
|
|
return api.Sandbox{}, fmt.Errorf("failed to validate sandbox: %w", err)
|
|
}
|
|
|
|
if err := update(ctx, s.db, func(tx *bbolt.Tx) error {
|
|
parent, err := createSandboxBucket(tx, ns)
|
|
if err != nil {
|
|
return fmt.Errorf("create error: %w", err)
|
|
}
|
|
|
|
if err := s.write(parent, &sandbox, false); err != nil {
|
|
return fmt.Errorf("write error: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}); err != nil {
|
|
return api.Sandbox{}, err
|
|
}
|
|
|
|
return sandbox, nil
|
|
}
|
|
|
|
// Update the sandbox with the provided sandbox object and fields
|
|
func (s *sandboxStore) Update(ctx context.Context, sandbox api.Sandbox, fieldpaths ...string) (api.Sandbox, error) {
|
|
ns, err := namespaces.NamespaceRequired(ctx)
|
|
if err != nil {
|
|
return api.Sandbox{}, err
|
|
}
|
|
|
|
ret := api.Sandbox{}
|
|
if err := update(ctx, s.db, func(tx *bbolt.Tx) error {
|
|
parent := getSandboxBucket(tx, ns)
|
|
if parent == nil {
|
|
return fmt.Errorf("no sandbox buckets: %w", errdefs.ErrNotFound)
|
|
}
|
|
|
|
updated, err := s.read(parent, []byte(sandbox.ID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(fieldpaths) == 0 {
|
|
fieldpaths = []string{"labels", "extensions", "spec", "runtime"}
|
|
|
|
if updated.Runtime.Name != sandbox.Runtime.Name {
|
|
return fmt.Errorf("sandbox.Runtime.Name field is immutable: %w", errdefs.ErrInvalidArgument)
|
|
}
|
|
}
|
|
|
|
for _, path := range fieldpaths {
|
|
if strings.HasPrefix(path, "labels.") {
|
|
if updated.Labels == nil {
|
|
updated.Labels = map[string]string{}
|
|
}
|
|
|
|
key := strings.TrimPrefix(path, "labels.")
|
|
updated.Labels[key] = sandbox.Labels[key]
|
|
continue
|
|
} else if strings.HasPrefix(path, "extensions.") {
|
|
if updated.Extensions == nil {
|
|
updated.Extensions = map[string]typeurl.Any{}
|
|
}
|
|
|
|
key := strings.TrimPrefix(path, "extensions.")
|
|
updated.Extensions[key] = sandbox.Extensions[key]
|
|
continue
|
|
}
|
|
|
|
switch path {
|
|
case "labels":
|
|
updated.Labels = sandbox.Labels
|
|
case "extensions":
|
|
updated.Extensions = sandbox.Extensions
|
|
case "runtime":
|
|
updated.Runtime = sandbox.Runtime
|
|
case "spec":
|
|
updated.Spec = sandbox.Spec
|
|
default:
|
|
return fmt.Errorf("cannot update %q field on sandbox %q: %w", path, sandbox.ID, errdefs.ErrInvalidArgument)
|
|
}
|
|
}
|
|
|
|
updated.UpdatedAt = time.Now().UTC()
|
|
|
|
if err := s.write(parent, &updated, true); err != nil {
|
|
return err
|
|
}
|
|
|
|
ret = updated
|
|
return nil
|
|
}); err != nil {
|
|
return api.Sandbox{}, err
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
// Get sandbox metadata using the id
|
|
func (s *sandboxStore) Get(ctx context.Context, id string) (api.Sandbox, error) {
|
|
ns, err := namespaces.NamespaceRequired(ctx)
|
|
if err != nil {
|
|
return api.Sandbox{}, err
|
|
}
|
|
|
|
ret := api.Sandbox{}
|
|
if err := view(ctx, s.db, func(tx *bbolt.Tx) error {
|
|
bucket := getSandboxBucket(tx, ns)
|
|
if bucket == nil {
|
|
return fmt.Errorf("no sandbox buckets: %w", errdefs.ErrNotFound)
|
|
}
|
|
|
|
out, err := s.read(bucket, []byte(id))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ret = out
|
|
return nil
|
|
}); err != nil {
|
|
return api.Sandbox{}, err
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
// List returns sandboxes that match one or more of the provided filters
|
|
func (s *sandboxStore) List(ctx context.Context, fields ...string) ([]api.Sandbox, error) {
|
|
ns, err := namespaces.NamespaceRequired(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
filter, err := filters.ParseAll(fields...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s: %w", err.Error(), errdefs.ErrInvalidArgument)
|
|
}
|
|
|
|
var (
|
|
list []api.Sandbox
|
|
)
|
|
|
|
if err := view(ctx, s.db, func(tx *bbolt.Tx) error {
|
|
bucket := getSandboxBucket(tx, ns)
|
|
if bucket == nil {
|
|
// We haven't created any sandboxes yet, just return empty list
|
|
return nil
|
|
}
|
|
|
|
if err := bucket.ForEach(func(k, v []byte) error {
|
|
info, err := s.read(bucket, k)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read bucket %q: %w", string(k), err)
|
|
}
|
|
|
|
if filter.Match(adaptSandbox(&info)) {
|
|
list = append(list, info)
|
|
}
|
|
|
|
return nil
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return list, nil
|
|
}
|
|
|
|
// Delete a sandbox from metadata store using the id
|
|
func (s *sandboxStore) Delete(ctx context.Context, id string) error {
|
|
ns, err := namespaces.NamespaceRequired(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := update(ctx, s.db, func(tx *bbolt.Tx) error {
|
|
buckets := getSandboxBucket(tx, ns)
|
|
if buckets == nil {
|
|
return fmt.Errorf("no sandbox buckets: %w", errdefs.ErrNotFound)
|
|
}
|
|
|
|
if err := buckets.DeleteBucket([]byte(id)); err != nil {
|
|
if err == bbolt.ErrBucketNotFound {
|
|
err = errdefs.ErrNotFound
|
|
}
|
|
return fmt.Errorf("failed to delete sandbox %q: %w", id, err)
|
|
}
|
|
|
|
return nil
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *sandboxStore) write(parent *bbolt.Bucket, instance *api.Sandbox, overwrite bool) error {
|
|
if err := s.validate(instance); err != nil {
|
|
return err
|
|
}
|
|
|
|
var (
|
|
bucket *bbolt.Bucket
|
|
err error
|
|
id = []byte(instance.ID)
|
|
)
|
|
|
|
if overwrite {
|
|
bucket, err = parent.CreateBucketIfNotExists(id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
bucket, err = parent.CreateBucket(id)
|
|
if err != nil {
|
|
if err == bbolt.ErrBucketExists {
|
|
return fmt.Errorf("sandbox bucket %q already exists: %w", instance.ID, errdefs.ErrAlreadyExists)
|
|
}
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := boltutil.WriteTimestamps(bucket, instance.CreatedAt, instance.UpdatedAt); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := boltutil.WriteLabels(bucket, instance.Labels); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := boltutil.WriteExtensions(bucket, instance.Extensions); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := boltutil.WriteAny(bucket, bucketKeySpec, instance.Spec); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := bucket.Put(bucketKeySandboxer, []byte(instance.Sandboxer)); err != nil {
|
|
return err
|
|
}
|
|
|
|
runtimeBucket, err := bucket.CreateBucketIfNotExists(bucketKeyRuntime)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := runtimeBucket.Put(bucketKeyName, []byte(instance.Runtime.Name)); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := boltutil.WriteAny(runtimeBucket, bucketKeyOptions, instance.Runtime.Options); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *sandboxStore) read(parent *bbolt.Bucket, id []byte) (api.Sandbox, error) {
|
|
var (
|
|
inst api.Sandbox
|
|
err error
|
|
)
|
|
|
|
bucket := parent.Bucket(id)
|
|
if bucket == nil {
|
|
return api.Sandbox{}, fmt.Errorf("bucket %q not found: %w", id, errdefs.ErrNotFound)
|
|
}
|
|
|
|
inst.ID = string(id)
|
|
|
|
inst.Labels, err = boltutil.ReadLabels(bucket)
|
|
if err != nil {
|
|
return api.Sandbox{}, err
|
|
}
|
|
|
|
if err := boltutil.ReadTimestamps(bucket, &inst.CreatedAt, &inst.UpdatedAt); err != nil {
|
|
return api.Sandbox{}, err
|
|
}
|
|
|
|
inst.Spec, err = boltutil.ReadAny(bucket, bucketKeySpec)
|
|
if err != nil {
|
|
return api.Sandbox{}, err
|
|
}
|
|
|
|
runtimeBucket := bucket.Bucket(bucketKeyRuntime)
|
|
if runtimeBucket == nil {
|
|
return api.Sandbox{}, errors.New("no runtime bucket")
|
|
}
|
|
|
|
inst.Runtime.Name = string(runtimeBucket.Get(bucketKeyName))
|
|
inst.Runtime.Options, err = boltutil.ReadAny(runtimeBucket, bucketKeyOptions)
|
|
if err != nil {
|
|
return api.Sandbox{}, err
|
|
}
|
|
|
|
inst.Extensions, err = boltutil.ReadExtensions(bucket)
|
|
if err != nil {
|
|
return api.Sandbox{}, err
|
|
}
|
|
sandboxer := bucket.Get(bucketKeySandboxer)
|
|
if sandboxer == nil {
|
|
inst.Sandboxer = ""
|
|
} else {
|
|
inst.Sandboxer = string(sandboxer)
|
|
}
|
|
|
|
return inst, nil
|
|
}
|
|
|
|
func (s *sandboxStore) validate(new *api.Sandbox) error {
|
|
if err := identifiers.Validate(new.ID); err != nil {
|
|
return fmt.Errorf("invalid sandbox ID: %w", err)
|
|
}
|
|
|
|
if new.CreatedAt.IsZero() {
|
|
return fmt.Errorf("creation date must not be zero: %w", errdefs.ErrInvalidArgument)
|
|
}
|
|
|
|
if new.UpdatedAt.IsZero() {
|
|
return fmt.Errorf("updated date must not be zero: %w", errdefs.ErrInvalidArgument)
|
|
}
|
|
|
|
return nil
|
|
}
|