Merge pull request #5250 from dmcgowan/cri-fix-reference-ordering

Fix reference ordering in CRI image store
This commit is contained in:
Maksym Pavlenko 2021-03-23 14:45:16 -07:00 committed by GitHub
commit 181e2d4216
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 177 additions and 18 deletions

View File

@ -210,8 +210,8 @@ func (s *store) add(img Image) error {
s.images[img.ID] = img s.images[img.ID] = img
return nil return nil
} }
// Or else, merge the references. // Or else, merge and sort the references.
i.References = util.MergeStringSlices(i.References, img.References) i.References = sortReferences(util.MergeStringSlices(i.References, img.References))
s.images[img.ID] = i s.images[img.ID] = i
return nil return nil
} }

View File

@ -32,24 +32,24 @@ func TestInternalStore(t *testing.T) {
{ {
ID: "sha256:1123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", ID: "sha256:1123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
ChainID: "test-chain-id-1", ChainID: "test-chain-id-1",
References: []string{"ref-1"}, References: []string{"containerd.io/ref-1"},
Size: 10, Size: 10,
}, },
{ {
ID: "sha256:2123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", ID: "sha256:2123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
ChainID: "test-chain-id-2abcd", ChainID: "test-chain-id-2abcd",
References: []string{"ref-2abcd"}, References: []string{"containerd.io/ref-2abcd"},
Size: 20, Size: 20,
}, },
{ {
ID: "sha256:3123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", ID: "sha256:3123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
References: []string{"ref-4a333"}, References: []string{"containerd.io/ref-4a333"},
ChainID: "test-chain-id-4a333", ChainID: "test-chain-id-4a333",
Size: 30, Size: 30,
}, },
{ {
ID: "sha256:4123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", ID: "sha256:4123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
References: []string{"ref-4abcd"}, References: []string{"containerd.io/ref-4abcd"},
ChainID: "test-chain-id-4abcd", ChainID: "test-chain-id-4abcd",
Size: 40, Size: 40,
}, },
@ -143,7 +143,7 @@ func TestImageStore(t *testing.T) {
image := Image{ image := Image{
ID: id, ID: id,
ChainID: "test-chain-id-1", ChainID: "test-chain-id-1",
References: []string{"ref-1"}, References: []string{"containerd.io/ref-1"},
Size: 10, Size: 10,
} }
assert := assertlib.New(t) assert := assertlib.New(t)
@ -159,33 +159,33 @@ func TestImageStore(t *testing.T) {
expected []Image expected []Image
}{ }{
"nothing should happen if a non-exist ref disappear": { "nothing should happen if a non-exist ref disappear": {
ref: "ref-2", ref: "containerd.io/ref-2",
image: nil, image: nil,
expected: []Image{image}, expected: []Image{image},
}, },
"new ref for an existing image": { "new ref for an existing image": {
ref: "ref-2", ref: "containerd.io/ref-2",
image: &Image{ image: &Image{
ID: id, ID: id,
ChainID: "test-chain-id-1", ChainID: "test-chain-id-1",
References: []string{"ref-2"}, References: []string{"containerd.io/ref-2"},
Size: 10, Size: 10,
}, },
expected: []Image{ expected: []Image{
{ {
ID: id, ID: id,
ChainID: "test-chain-id-1", ChainID: "test-chain-id-1",
References: []string{"ref-1", "ref-2"}, References: []string{"containerd.io/ref-1", "containerd.io/ref-2"},
Size: 10, Size: 10,
}, },
}, },
}, },
"new ref for a new image": { "new ref for a new image": {
ref: "ref-2", ref: "containerd.io/ref-2",
image: &Image{ image: &Image{
ID: newID, ID: newID,
ChainID: "test-chain-id-2", ChainID: "test-chain-id-2",
References: []string{"ref-2"}, References: []string{"containerd.io/ref-2"},
Size: 20, Size: 20,
}, },
expected: []Image{ expected: []Image{
@ -193,30 +193,30 @@ func TestImageStore(t *testing.T) {
{ {
ID: newID, ID: newID,
ChainID: "test-chain-id-2", ChainID: "test-chain-id-2",
References: []string{"ref-2"}, References: []string{"containerd.io/ref-2"},
Size: 20, Size: 20,
}, },
}, },
}, },
"existing ref point to a new image": { "existing ref point to a new image": {
ref: "ref-1", ref: "containerd.io/ref-1",
image: &Image{ image: &Image{
ID: newID, ID: newID,
ChainID: "test-chain-id-2", ChainID: "test-chain-id-2",
References: []string{"ref-1"}, References: []string{"containerd.io/ref-1"},
Size: 20, Size: 20,
}, },
expected: []Image{ expected: []Image{
{ {
ID: newID, ID: newID,
ChainID: "test-chain-id-2", ChainID: "test-chain-id-2",
References: []string{"ref-1"}, References: []string{"containerd.io/ref-1"},
Size: 20, Size: 20,
}, },
}, },
}, },
"existing ref disappear": { "existing ref disappear": {
ref: "ref-1", ref: "containerd.io/ref-1",
image: nil, image: nil,
expected: []Image{}, expected: []Image{},
}, },

View File

@ -0,0 +1,75 @@
/*
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

@ -0,0 +1,84 @@
/*
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"
"io/ioutil"
"math/rand"
"testing"
"github.com/opencontainers/go-digest"
)
func TestReferenceSorting(t *testing.T) {
digested := func(seed int64) string {
b, err := ioutil.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
}
}
}
}