Merge pull request #6993 from mxpv/images

CRI: cleanup cri/store package
This commit is contained in:
Mike Brown
2022-05-31 20:38:43 -05:00
committed by GitHub
9 changed files with 35 additions and 98 deletions

View File

@@ -25,7 +25,7 @@ import (
"github.com/containerd/containerd/pkg/cri/store"
"github.com/containerd/containerd/pkg/cri/store/label"
"github.com/containerd/containerd/pkg/cri/store/stats"
"github.com/containerd/containerd/pkg/cri/store/truncindex"
"github.com/containerd/containerd/pkg/truncindex"
runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
)

View File

@@ -1,33 +0,0 @@
/*
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 store
import "github.com/containerd/containerd/errdefs"
var (
// ErrAlreadyExist is the error returned when data added in the store
// already exists.
//
// This error has been DEPRECATED and will be removed in 1.5. Please switch
// usage directly to `errdefs.ErrAlreadyExists`.
ErrAlreadyExist = errdefs.ErrAlreadyExists
// ErrNotExist is the error returned when data is not in the store.
//
// This error has been DEPRECATED and will be removed in 1.5. Please switch
// usage directly to `errdefs.ErrNotFound`.
ErrNotExist = errdefs.ErrNotFound
)

View File

@@ -1,48 +0,0 @@
/*
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 store
import (
"testing"
"github.com/containerd/containerd/errdefs"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func TestStoreErrAlreadyExistGRPCStatus(t *testing.T) {
err := errdefs.ToGRPC(errdefs.ErrAlreadyExists)
s, ok := status.FromError(err)
if !ok {
t.Fatalf("failed to convert err: %v to status: %d", err, codes.AlreadyExists)
}
if s.Code() != codes.AlreadyExists {
t.Fatalf("expected code: %d got: %d", codes.AlreadyExists, s.Code())
}
}
func TestStoreErrNotExistGRPCStatus(t *testing.T) {
err := errdefs.ToGRPC(errdefs.ErrNotFound)
s, ok := status.FromError(err)
if !ok {
t.Fatalf("failed to convert err: %v to status: %d", err, codes.NotFound)
}
if s.Code() != codes.NotFound {
t.Fatalf("expected code: %d got: %d", codes.NotFound, s.Code())
}
}

View File

@@ -18,14 +18,13 @@ package image
import (
"context"
"encoding/json"
"fmt"
"sync"
"github.com/containerd/containerd"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/pkg/cri/util"
"github.com/containerd/containerd/reference"
imagedigest "github.com/opencontainers/go-digest"
"github.com/opencontainers/go-digest/digestset"
@@ -132,15 +131,12 @@ func getImage(ctx context.Context, i containerd.Image) (*Image, error) {
if err != nil {
return nil, fmt.Errorf("get image config descriptor: %w", err)
}
id := desc.Digest.String()
rb, err := content.ReadBlob(ctx, i.ContentStore(), desc)
spec, err := i.Spec(ctx)
if err != nil {
return nil, fmt.Errorf("read image config from content store: %w", err)
}
var ociimage imagespec.Image
if err := json.Unmarshal(rb, &ociimage); err != nil {
return nil, fmt.Errorf("unmarshal image config %s: %w", rb, err)
return nil, fmt.Errorf("failed to get OCI image spec: %w", err)
}
return &Image{
@@ -148,7 +144,7 @@ func getImage(ctx context.Context, i containerd.Image) (*Image, error) {
References: []string{i.Name()},
ChainID: chainID.String(),
Size: size,
ImageSpec: ociimage,
ImageSpec: spec,
}, nil
}
@@ -210,7 +206,7 @@ func (s *store) add(img Image) error {
return nil
}
// Or else, merge and sort the references.
i.References = sortReferences(util.MergeStringSlices(i.References, img.References))
i.References = reference.Sort(util.MergeStringSlices(i.References, img.References))
s.images[img.ID] = i
return nil
}

View File

@@ -1,75 +0,0 @@
/*
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 image
import (
"sort"
"github.com/containerd/containerd/reference/docker"
)
// sortReferences sorts references by refRank then string comparison
func sortReferences(references []string) []string {
var prefs []docker.Reference
var bad []string
for _, ref := range references {
pref, err := docker.ParseAnyReference(ref)
if err != nil {
bad = append(bad, ref)
} else {
prefs = append(prefs, pref)
}
}
sort.Slice(prefs, func(a, b int) bool {
ar := refRank(prefs[a])
br := refRank(prefs[b])
if ar == br {
return prefs[a].String() < prefs[b].String()
}
return ar < br
})
sort.Strings(bad)
var refs []string
for _, pref := range prefs {
refs = append(refs, pref.String())
}
return append(refs, bad...)
}
// refRank ranks precedence for reference type, preferring higher information references
// 1. Name + Tag + Digest
// 2. Name + Tag
// 3. Name + Digest
// 4. Name
// 5. Digest
// 6. Parse error
func refRank(ref docker.Reference) uint8 {
if _, ok := ref.(docker.Named); ok {
if _, ok = ref.(docker.Tagged); ok {
if _, ok = ref.(docker.Digested); ok {
return 1
}
return 2
}
if _, ok = ref.(docker.Digested); ok {
return 3
}
return 4
}
return 5
}

View File

@@ -1,83 +0,0 @@
/*
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 image
import (
"io"
"math/rand"
"testing"
"github.com/opencontainers/go-digest"
)
func TestReferenceSorting(t *testing.T) {
digested := func(seed int64) string {
b, err := io.ReadAll(io.LimitReader(rand.New(rand.NewSource(seed)), 64))
if err != nil {
panic(err)
}
return digest.FromBytes(b).String()
}
// Add z. prefix to string sort after "sha256:"
r1 := func(name, tag string, seed int64) string {
return "z.containerd.io/" + name + ":" + tag + "@" + digested(seed)
}
r2 := func(name, tag string) string {
return "z.containerd.io/" + name + ":" + tag
}
r3 := func(name string, seed int64) string {
return "z.containerd.io/" + name + "@" + digested(seed)
}
for i, tc := range []struct {
unsorted []string
expected []string
}{
{
unsorted: []string{r2("name", "latest"), r3("name", 1), r1("name", "latest", 1)},
expected: []string{r1("name", "latest", 1), r2("name", "latest"), r3("name", 1)},
},
{
unsorted: []string{"can't parse this:latest", r3("name", 1), r2("name", "latest")},
expected: []string{r2("name", "latest"), r3("name", 1), "can't parse this:latest"},
},
{
unsorted: []string{digested(1), r3("name", 1), r2("name", "latest")},
expected: []string{r2("name", "latest"), r3("name", 1), digested(1)},
},
{
unsorted: []string{r2("name", "tag2"), r2("name", "tag3"), r2("name", "tag1")},
expected: []string{r2("name", "tag1"), r2("name", "tag2"), r2("name", "tag3")},
},
{
unsorted: []string{r2("name-2", "tag"), r2("name-3", "tag"), r2("name-1", "tag")},
expected: []string{r2("name-1", "tag"), r2("name-2", "tag"), r2("name-3", "tag")},
},
} {
sorted := sortReferences(tc.unsorted)
if len(sorted) != len(tc.expected) {
t.Errorf("[%d]: Mismatched sized, got %d, expected %d", i, len(sorted), len(tc.expected))
continue
}
for j := range sorted {
if sorted[j] != tc.expected[j] {
t.Errorf("[%d]: Wrong value at %d, got %q, expected %q", i, j, sorted[j], tc.expected[j])
break
}
}
}
}

View File

@@ -24,8 +24,8 @@ import (
"github.com/containerd/containerd/pkg/cri/store"
"github.com/containerd/containerd/pkg/cri/store/label"
"github.com/containerd/containerd/pkg/cri/store/stats"
"github.com/containerd/containerd/pkg/cri/store/truncindex"
"github.com/containerd/containerd/pkg/netns"
"github.com/containerd/containerd/pkg/truncindex"
)
// Sandbox contains all resources associated with the sandbox. All methods to

View File

@@ -1,157 +0,0 @@
/*
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.
*/
// This file is a copy of moby/moby/pkg/truncindex/truncindex.go
// Package truncindex provides a general 'index tree', used by Docker
// in order to be able to reference containers by only a few unambiguous
// characters of their id.
package truncindex
import (
"errors"
"fmt"
"strings"
"sync"
"github.com/tchap/go-patricia/v2/patricia"
)
var (
// ErrEmptyPrefix is an error returned if the prefix was empty.
ErrEmptyPrefix = errors.New("Prefix can't be empty")
// ErrIllegalChar is returned when a space is in the ID
ErrIllegalChar = errors.New("illegal character: ' '")
// ErrNotExist is returned when ID or its prefix not found in index.
ErrNotExist = errors.New("ID does not exist")
)
// ErrAmbiguousPrefix is returned if the prefix was ambiguous
// (multiple ids for the prefix).
type ErrAmbiguousPrefix struct {
prefix string
}
func (e ErrAmbiguousPrefix) Error() string {
return fmt.Sprintf("Multiple IDs found with provided prefix: %s", e.prefix)
}
// TruncIndex allows the retrieval of string identifiers by any of their unique prefixes.
// This is used to retrieve image and container IDs by more convenient shorthand prefixes.
type TruncIndex struct {
sync.RWMutex
trie *patricia.Trie
ids map[string]struct{}
}
// NewTruncIndex creates a new TruncIndex and initializes with a list of IDs.
func NewTruncIndex(ids []string) (idx *TruncIndex) {
idx = &TruncIndex{
ids: make(map[string]struct{}),
// Change patricia max prefix per node length,
// because our len(ID) always 64
trie: patricia.NewTrie(patricia.MaxPrefixPerNode(64)),
}
for _, id := range ids {
idx.addID(id)
}
return
}
func (idx *TruncIndex) addID(id string) error {
if strings.Contains(id, " ") {
return ErrIllegalChar
}
if id == "" {
return ErrEmptyPrefix
}
if _, exists := idx.ids[id]; exists {
return fmt.Errorf("id already exists: '%s'", id)
}
idx.ids[id] = struct{}{}
if inserted := idx.trie.Insert(patricia.Prefix(id), struct{}{}); !inserted {
return fmt.Errorf("failed to insert id: %s", id)
}
return nil
}
// Add adds a new ID to the TruncIndex.
func (idx *TruncIndex) Add(id string) error {
idx.Lock()
defer idx.Unlock()
return idx.addID(id)
}
// Delete removes an ID from the TruncIndex. If there are multiple IDs
// with the given prefix, an error is thrown.
func (idx *TruncIndex) Delete(id string) error {
idx.Lock()
defer idx.Unlock()
if _, exists := idx.ids[id]; !exists || id == "" {
return fmt.Errorf("no such id: '%s'", id)
}
delete(idx.ids, id)
if deleted := idx.trie.Delete(patricia.Prefix(id)); !deleted {
return fmt.Errorf("no such id: '%s'", id)
}
return nil
}
// Get retrieves an ID from the TruncIndex. If there are multiple IDs
// with the given prefix, an error is thrown.
func (idx *TruncIndex) Get(s string) (string, error) {
if s == "" {
return "", ErrEmptyPrefix
}
var (
id string
)
subTreeVisitFunc := func(prefix patricia.Prefix, item patricia.Item) error {
if id != "" {
// we haven't found the ID if there are two or more IDs
id = ""
return ErrAmbiguousPrefix{prefix: s}
}
id = string(prefix)
return nil
}
idx.RLock()
defer idx.RUnlock()
if err := idx.trie.VisitSubtree(patricia.Prefix(s), subTreeVisitFunc); err != nil {
return "", err
}
if id != "" {
return id, nil
}
return "", ErrNotExist
}
// Iterate iterates over all stored IDs and passes each of them to the given
// handler. Take care that the handler method does not call any public
// method on truncindex as the internal locking is not reentrant/recursive
// and will result in deadlock.
func (idx *TruncIndex) Iterate(handler func(id string)) {
idx.Lock()
defer idx.Unlock()
idx.trie.Visit(func(prefix patricia.Prefix, item patricia.Item) error {
handler(string(prefix))
return nil
})
}