diff --git a/internal/cri/bandwidth/linux.go b/internal/cri/bandwidth/linux.go index 1011b8c714..53d88ad726 100644 --- a/internal/cri/bandwidth/linux.go +++ b/internal/cri/bandwidth/linux.go @@ -43,10 +43,10 @@ import ( "strings" "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/apimachinery/pkg/util/sets" "k8s.io/klog/v2" "k8s.io/utils/exec" + "github.com/containerd/containerd/v2/internal/cri/setutils" "github.com/containerd/containerd/v2/internal/lazyregexp" ) @@ -90,7 +90,7 @@ func (t *tcShaper) nextClassID() (int, error) { } scanner := bufio.NewScanner(bytes.NewBuffer(data)) - classes := sets.Set[string]{} + classes := setutils.Set[string]{} for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) // skip empty lines diff --git a/internal/cri/setutils/doc.go b/internal/cri/setutils/doc.go new file mode 100644 index 0000000000..10b53cc6fd --- /dev/null +++ b/internal/cri/setutils/doc.go @@ -0,0 +1,35 @@ +/* + 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. +*/ + +/* +Copyright 2022 The Kubernetes 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 setutils has generic set and specified sets. Generic set will +// replace specified ones over time. And specific ones are deprecated. +package setutils // cloned from "k8s.io/apimachinery/pkg/util/sets" diff --git a/internal/cri/setutils/empty.go b/internal/cri/setutils/empty.go new file mode 100644 index 0000000000..4acadae851 --- /dev/null +++ b/internal/cri/setutils/empty.go @@ -0,0 +1,37 @@ +/* + 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. +*/ + +/* +Copyright 2022 The Kubernetes 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 setutils + +// Empty is public since it is used by some internal API objects for conversions between external +// string arrays and internal sets, and conversion logic requires public types today. +type Empty struct{} diff --git a/internal/cri/setutils/int.go b/internal/cri/setutils/int.go new file mode 100644 index 0000000000..9ff53ea014 --- /dev/null +++ b/internal/cri/setutils/int.go @@ -0,0 +1,153 @@ +/* + 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. +*/ + +/* +Copyright 2022 The Kubernetes 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 setutils + +// Int is a set of ints, implemented via map[int]struct{} for minimal memory consumption. +// +// Deprecated: use generic Set instead. +// new ways: +// s1 := Set[int]{} +// s2 := New[int]() +type Int map[int]Empty + +// NewInt creates a Int from a list of values. +func NewInt(items ...int) Int { + return Int(New[int](items...)) +} + +// IntKeySet creates a Int from a keys of a map[int](? extends interface{}). +// If the value passed in is not actually a map, this will panic. +func IntKeySet[T any](theMap map[int]T) Int { + return Int(KeySet(theMap)) +} + +// Insert adds items to the set. +func (s Int) Insert(items ...int) Int { + return Int(cast(s).Insert(items...)) +} + +// Delete removes all items from the set. +func (s Int) Delete(items ...int) Int { + return Int(cast(s).Delete(items...)) +} + +// Has returns true if and only if item is contained in the set. +func (s Int) Has(item int) bool { + return cast(s).Has(item) +} + +// HasAll returns true if and only if all items are contained in the set. +func (s Int) HasAll(items ...int) bool { + return cast(s).HasAll(items...) +} + +// HasAny returns true if any items are contained in the set. +func (s Int) HasAny(items ...int) bool { + return cast(s).HasAny(items...) +} + +// Clone returns a new set which is a copy of the current set. +func (s Int) Clone() Int { + return Int(cast(s).Clone()) +} + +// Difference returns a set of objects that are not in s2. +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.Difference(s2) = {a3} +// s2.Difference(s1) = {a4, a5} +func (s Int) Difference(s2 Int) Int { + return Int(cast(s).Difference(cast(s2))) +} + +// SymmetricDifference returns a set of elements which are in either of the sets, but not in their intersection. +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.SymmetricDifference(s2) = {a3, a4, a5} +// s2.SymmetricDifference(s1) = {a3, a4, a5} +func (s Int) SymmetricDifference(s2 Int) Int { + return Int(cast(s).SymmetricDifference(cast(s2))) +} + +// Union returns a new set which includes items in either s1 or s2. +// For example: +// s1 = {a1, a2} +// s2 = {a3, a4} +// s1.Union(s2) = {a1, a2, a3, a4} +// s2.Union(s1) = {a1, a2, a3, a4} +func (s Int) Union(s2 Int) Int { + return Int(cast(s).Union(cast(s2))) +} + +// Intersection returns a new set which includes the item in BOTH s1 and s2 +// For example: +// s1 = {a1, a2} +// s2 = {a2, a3} +// s1.Intersection(s2) = {a2} +func (s Int) Intersection(s2 Int) Int { + return Int(cast(s).Intersection(cast(s2))) +} + +// IsSuperset returns true if and only if s1 is a superset of s2. +func (s Int) IsSuperset(s2 Int) bool { + return cast(s).IsSuperset(cast(s2)) +} + +// Equal returns true if and only if s1 is equal (as a set) to s2. +// Two sets are equal if their membership is identical. +// (In practice, this means same elements, order doesn't matter) +func (s Int) Equal(s2 Int) bool { + return cast(s).Equal(cast(s2)) +} + +// List returns the contents as a sorted int slice. +func (s Int) List() []int { + return List(cast(s)) +} + +// UnsortedList returns the slice with contents in random order. +func (s Int) UnsortedList() []int { + return cast(s).UnsortedList() +} + +// PopAny returns a single element from the set. +func (s Int) PopAny() (int, bool) { + return cast(s).PopAny() +} + +// Len returns the size of the set. +func (s Int) Len() int { + return len(s) +} diff --git a/internal/cri/setutils/set.go b/internal/cri/setutils/set.go new file mode 100644 index 0000000000..6b7aa97d88 --- /dev/null +++ b/internal/cri/setutils/set.go @@ -0,0 +1,252 @@ +/* + 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. +*/ + +/* +Copyright 2022 The Kubernetes 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 setutils + +import ( + "cmp" + "sort" +) + +// Set is a set of the same type elements, implemented via map[comparable]struct{} for minimal memory consumption. +type Set[T comparable] map[T]Empty + +// cast transforms specified set to generic Set[T]. +func cast[T comparable](s map[T]Empty) Set[T] { return s } + +// New creates a Set from a list of values. +// NOTE: type param must be explicitly instantiated if given items are empty. +func New[T comparable](items ...T) Set[T] { + ss := make(Set[T], len(items)) + ss.Insert(items...) + return ss +} + +// KeySet creates a Set from a keys of a map[comparable](? extends interface{}). +// If the value passed in is not actually a map, this will panic. +func KeySet[T comparable, V any](theMap map[T]V) Set[T] { + ret := make(Set[T], len(theMap)) + for keyValue := range theMap { + ret.Insert(keyValue) + } + return ret +} + +// Insert adds items to the set. +func (s Set[T]) Insert(items ...T) Set[T] { + for _, item := range items { + s[item] = Empty{} + } + return s +} + +func Insert[T comparable](set Set[T], items ...T) Set[T] { + return set.Insert(items...) +} + +// Delete removes all items from the set. +func (s Set[T]) Delete(items ...T) Set[T] { + for _, item := range items { + delete(s, item) + } + return s +} + +// Clear empties the set. +// It is preferable to replace the set with a newly constructed set, +// but not all callers can do that (when there are other references to the map). +func (s Set[T]) Clear() Set[T] { + clear(s) + return s +} + +// Has returns true if and only if item is contained in the set. +func (s Set[T]) Has(item T) bool { + _, contained := s[item] + return contained +} + +// HasAll returns true if and only if all items are contained in the set. +func (s Set[T]) HasAll(items ...T) bool { + for _, item := range items { + if !s.Has(item) { + return false + } + } + return true +} + +// HasAny returns true if any items are contained in the set. +func (s Set[T]) HasAny(items ...T) bool { + for _, item := range items { + if s.Has(item) { + return true + } + } + return false +} + +// Clone returns a new set which is a copy of the current set. +func (s Set[T]) Clone() Set[T] { + result := make(Set[T], len(s)) + for key := range s { + result.Insert(key) + } + return result +} + +// Difference returns a set of objects that are not in s2. +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.Difference(s2) = {a3} +// s2.Difference(s1) = {a4, a5} +func (s Set[T]) Difference(s2 Set[T]) Set[T] { + result := New[T]() + for key := range s { + if !s2.Has(key) { + result.Insert(key) + } + } + return result +} + +// SymmetricDifference returns a set of elements which are in either of the sets, but not in their intersection. +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.SymmetricDifference(s2) = {a3, a4, a5} +// s2.SymmetricDifference(s1) = {a3, a4, a5} +func (s Set[T]) SymmetricDifference(s2 Set[T]) Set[T] { + return s.Difference(s2).Union(s2.Difference(s)) +} + +// Union returns a new set which includes items in either s1 or s2. +// For example: +// s1 = {a1, a2} +// s2 = {a3, a4} +// s1.Union(s2) = {a1, a2, a3, a4} +// s2.Union(s1) = {a1, a2, a3, a4} +func (s Set[T]) Union(s2 Set[T]) Set[T] { + result := s.Clone() + for key := range s2 { + result.Insert(key) + } + return result +} + +// Intersection returns a new set which includes the item in BOTH s1 and s2 +// For example: +// s1 = {a1, a2} +// s2 = {a2, a3} +// s1.Intersection(s2) = {a2} +func (s Set[T]) Intersection(s2 Set[T]) Set[T] { + var walk, other Set[T] + result := New[T]() + if s.Len() < s2.Len() { + walk = s + other = s2 + } else { + walk = s2 + other = s + } + for key := range walk { + if other.Has(key) { + result.Insert(key) + } + } + return result +} + +// IsSuperset returns true if and only if s1 is a superset of s2. +func (s Set[T]) IsSuperset(s2 Set[T]) bool { + for item := range s2 { + if !s.Has(item) { + return false + } + } + return true +} + +// Equal returns true if and only if s1 is equal (as a set) to s2. +// Two sets are equal if their membership is identical. +// (In practice, this means same elements, order doesn't matter) +func (s Set[T]) Equal(s2 Set[T]) bool { + return len(s) == len(s2) && s.IsSuperset(s2) +} + +type sortableSliceOfGeneric[T cmp.Ordered] []T + +func (g sortableSliceOfGeneric[T]) Len() int { return len(g) } +func (g sortableSliceOfGeneric[T]) Less(i, j int) bool { return less[T](g[i], g[j]) } +func (g sortableSliceOfGeneric[T]) Swap(i, j int) { g[i], g[j] = g[j], g[i] } + +// List returns the contents as a sorted T slice. +// +// This is a separate function and not a method because not all types supported +// by Generic are ordered and only those can be sorted. +func List[T cmp.Ordered](s Set[T]) []T { + res := make(sortableSliceOfGeneric[T], 0, len(s)) + for key := range s { + res = append(res, key) + } + sort.Sort(res) + return res +} + +// UnsortedList returns the slice with contents in random order. +func (s Set[T]) UnsortedList() []T { + res := make([]T, 0, len(s)) + for key := range s { + res = append(res, key) + } + return res +} + +// PopAny returns a single element from the set. +func (s Set[T]) PopAny() (T, bool) { + for key := range s { + s.Delete(key) + return key, true + } + var zeroValue T + return zeroValue, false +} + +// Len returns the size of the set. +func (s Set[T]) Len() int { + return len(s) +} + +func less[T cmp.Ordered](lhs, rhs T) bool { + return lhs < rhs +} diff --git a/internal/cri/setutils/string.go b/internal/cri/setutils/string.go new file mode 100644 index 0000000000..7e30639014 --- /dev/null +++ b/internal/cri/setutils/string.go @@ -0,0 +1,153 @@ +/* + 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. +*/ + +/* +Copyright 2022 The Kubernetes 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 setutils + +// String is a set of strings, implemented via map[string]struct{} for minimal memory consumption. +// +// Deprecated: use generic Set instead. +// new ways: +// s1 := Set[string]{} +// s2 := New[string]() +type String map[string]Empty + +// NewString creates a String from a list of values. +func NewString(items ...string) String { + return String(New[string](items...)) +} + +// StringKeySet creates a String from a keys of a map[string](? extends interface{}). +// If the value passed in is not actually a map, this will panic. +func StringKeySet[T any](theMap map[string]T) String { + return String(KeySet(theMap)) +} + +// Insert adds items to the set. +func (s String) Insert(items ...string) String { + return String(cast(s).Insert(items...)) +} + +// Delete removes all items from the set. +func (s String) Delete(items ...string) String { + return String(cast(s).Delete(items...)) +} + +// Has returns true if and only if item is contained in the set. +func (s String) Has(item string) bool { + return cast(s).Has(item) +} + +// HasAll returns true if and only if all items are contained in the set. +func (s String) HasAll(items ...string) bool { + return cast(s).HasAll(items...) +} + +// HasAny returns true if any items are contained in the set. +func (s String) HasAny(items ...string) bool { + return cast(s).HasAny(items...) +} + +// Clone returns a new set which is a copy of the current set. +func (s String) Clone() String { + return String(cast(s).Clone()) +} + +// Difference returns a set of objects that are not in s2. +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.Difference(s2) = {a3} +// s2.Difference(s1) = {a4, a5} +func (s String) Difference(s2 String) String { + return String(cast(s).Difference(cast(s2))) +} + +// SymmetricDifference returns a set of elements which are in either of the sets, but not in their intersection. +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.SymmetricDifference(s2) = {a3, a4, a5} +// s2.SymmetricDifference(s1) = {a3, a4, a5} +func (s String) SymmetricDifference(s2 String) String { + return String(cast(s).SymmetricDifference(cast(s2))) +} + +// Union returns a new set which includes items in either s1 or s2. +// For example: +// s1 = {a1, a2} +// s2 = {a3, a4} +// s1.Union(s2) = {a1, a2, a3, a4} +// s2.Union(s1) = {a1, a2, a3, a4} +func (s String) Union(s2 String) String { + return String(cast(s).Union(cast(s2))) +} + +// Intersection returns a new set which includes the item in BOTH s1 and s2 +// For example: +// s1 = {a1, a2} +// s2 = {a2, a3} +// s1.Intersection(s2) = {a2} +func (s String) Intersection(s2 String) String { + return String(cast(s).Intersection(cast(s2))) +} + +// IsSuperset returns true if and only if s1 is a superset of s2. +func (s String) IsSuperset(s2 String) bool { + return cast(s).IsSuperset(cast(s2)) +} + +// Equal returns true if and only if s1 is equal (as a set) to s2. +// Two sets are equal if their membership is identical. +// (In practice, this means same elements, order doesn't matter) +func (s String) Equal(s2 String) bool { + return cast(s).Equal(cast(s2)) +} + +// List returns the contents as a sorted string slice. +func (s String) List() []string { + return List(cast(s)) +} + +// UnsortedList returns the slice with contents in random order. +func (s String) UnsortedList() []string { + return cast(s).UnsortedList() +} + +// PopAny returns a single element from the set. +func (s String) PopAny() (string, bool) { + return cast(s).PopAny() +} + +// Len returns the size of the set. +func (s String) Len() int { + return len(s) +} diff --git a/internal/cri/store/image/image.go b/internal/cri/store/image/image.go index 5887e757b8..fc0ade22d1 100644 --- a/internal/cri/store/image/image.go +++ b/internal/cri/store/image/image.go @@ -26,11 +26,11 @@ import ( "github.com/containerd/containerd/v2/core/images" "github.com/containerd/containerd/v2/core/images/usage" "github.com/containerd/containerd/v2/internal/cri/labels" + "github.com/containerd/containerd/v2/internal/cri/setutils" "github.com/containerd/containerd/v2/internal/cri/util" "github.com/containerd/errdefs" "github.com/containerd/platforms" docker "github.com/distribution/reference" - "k8s.io/apimachinery/pkg/util/sets" imagedigest "github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest/digestset" @@ -90,7 +90,7 @@ func NewStore(img Getter, provider content.InfoReaderProvider, platform platform store: &store{ images: make(map[string]Image), digestSet: digestset.NewSet(), - pinnedRefs: make(map[string]sets.Set[string]), + pinnedRefs: make(map[string]setutils.Set[string]), }, } } @@ -215,7 +215,7 @@ type store struct { lock sync.RWMutex images map[string]Image digestSet *digestset.Set - pinnedRefs map[string]sets.Set[string] + pinnedRefs map[string]setutils.Set[string] } func (s *store) list() []Image { @@ -242,7 +242,7 @@ func (s *store) add(img Image) error { if img.Pinned { if refs := s.pinnedRefs[img.ID]; refs == nil { - s.pinnedRefs[img.ID] = sets.New(img.References...) + s.pinnedRefs[img.ID] = setutils.New(img.References...) } else { refs.Insert(img.References...) } @@ -288,7 +288,7 @@ func (s *store) pin(id, ref string) error { } if refs := s.pinnedRefs[digest.String()]; refs == nil { - s.pinnedRefs[digest.String()] = sets.New(ref) + s.pinnedRefs[digest.String()] = setutils.New(ref) } else { refs.Insert(ref) } diff --git a/internal/cri/store/image/image_test.go b/internal/cri/store/image/image_test.go index 5376cb6176..59b2235909 100644 --- a/internal/cri/store/image/image_test.go +++ b/internal/cri/store/image/image_test.go @@ -21,8 +21,8 @@ import ( "strings" "testing" + "github.com/containerd/containerd/v2/internal/cri/setutils" "github.com/containerd/errdefs" - "k8s.io/apimachinery/pkg/util/sets" "github.com/opencontainers/go-digest/digestset" assertlib "github.com/stretchr/testify/assert" @@ -61,7 +61,7 @@ func TestInternalStore(t *testing.T) { s := &store{ images: make(map[string]Image), digestSet: digestset.NewSet(), - pinnedRefs: make(map[string]sets.Set[string]), + pinnedRefs: make(map[string]setutils.Set[string]), } t.Logf("should be able to add image") @@ -144,7 +144,7 @@ func TestInternalStorePinnedImage(t *testing.T) { s := &store{ images: make(map[string]Image), digestSet: digestset.NewSet(), - pinnedRefs: make(map[string]sets.Set[string]), + pinnedRefs: make(map[string]setutils.Set[string]), } ref1 := "containerd.io/ref-1" diff --git a/internal/cri/util/strings.go b/internal/cri/util/strings.go index 1dfb28c3d0..f2e3cec4ca 100644 --- a/internal/cri/util/strings.go +++ b/internal/cri/util/strings.go @@ -19,7 +19,7 @@ package util import ( "strings" - "k8s.io/apimachinery/pkg/util/sets" + "github.com/containerd/containerd/v2/internal/cri/setutils" ) // InStringSlice checks whether a string is inside a string slice. @@ -48,7 +48,7 @@ func SubtractStringSlice(ss []string, str string) []string { // MergeStringSlices merges 2 string slices into one and remove duplicated elements. func MergeStringSlices(a []string, b []string) []string { - set := sets.NewString(a...) + set := setutils.NewString(a...) set.Insert(b...) return set.UnsortedList() }