From e2d6795bde2355a86da24e7dc8231efd874c1b2b Mon Sep 17 00:00:00 2001 From: Leo Huang <77225753+huangchenzhao@users.noreply.github.com> Date: Mon, 25 Mar 2024 19:42:57 +0800 Subject: [PATCH] feat: Avoid calling functions under package k8s.io/kubernetes, move it to util (#1967) (#1983) Signed-off-by: Chenzhao Huang <949412843@qq.com> --- pkg/util/kubernetes/controller/hash/hash.go | 37 ++++ .../controller/history/controller_history.go | 158 ++++++++++++++++++ .../controller/yurtappset/revision.go | 2 +- 3 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 pkg/util/kubernetes/controller/hash/hash.go create mode 100644 pkg/util/kubernetes/controller/history/controller_history.go diff --git a/pkg/util/kubernetes/controller/hash/hash.go b/pkg/util/kubernetes/controller/hash/hash.go new file mode 100644 index 00000000000..803f066a440 --- /dev/null +++ b/pkg/util/kubernetes/controller/hash/hash.go @@ -0,0 +1,37 @@ +/* +Copyright 2015 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 hash + +import ( + "hash" + + "github.com/davecgh/go-spew/spew" +) + +// DeepHashObject writes specified object to hash using the spew library +// which follows pointers and prints actual values of the nested objects +// ensuring the hash does not change when a pointer changes. +func DeepHashObject(hasher hash.Hash, objectToWrite interface{}) { + hasher.Reset() + printer := spew.ConfigState{ + Indent: " ", + SortKeys: true, + DisableMethods: true, + SpewKeys: true, + } + printer.Fprintf(hasher, "%#v", objectToWrite) +} diff --git a/pkg/util/kubernetes/controller/history/controller_history.go b/pkg/util/kubernetes/controller/history/controller_history.go new file mode 100644 index 00000000000..29e8ac834ee --- /dev/null +++ b/pkg/util/kubernetes/controller/history/controller_history.go @@ -0,0 +1,158 @@ +/* +Copyright 2017 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 history + +import ( + "bytes" + "fmt" + "hash/fnv" + "sort" + "strconv" + + apps "k8s.io/api/apps/v1" + apiequality "k8s.io/apimachinery/pkg/api/equality" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/rand" + + hashutil "github.com/openyurtio/openyurt/pkg/util/kubernetes/controller/hash" +) + +// ControllerRevisionHashLabel is the label used to indicate the hash value of a ControllerRevision's Data. +const ControllerRevisionHashLabel = "controller.kubernetes.io/hash" + +// ControllerRevisionName returns the Name for a ControllerRevision in the form prefix-hash. If the length +// of prefix is greater than 223 bytes, it is truncated to allow for a name that is no larger than 253 bytes. +func ControllerRevisionName(prefix string, hash string) string { + if len(prefix) > 223 { + prefix = prefix[:223] + } + + return fmt.Sprintf("%s-%s", prefix, hash) +} + +// NewControllerRevision returns a ControllerRevision with a ControllerRef pointing to parent and indicating that +// parent is of parentKind. The ControllerRevision has labels matching template labels, contains Data equal to data, and +// has a Revision equal to revision. The collisionCount is used when creating the name of the ControllerRevision +// so the name is likely unique. If the returned error is nil, the returned ControllerRevision is valid. If the +// returned error is not nil, the returned ControllerRevision is invalid for use. +func NewControllerRevision(parent metav1.Object, + parentKind schema.GroupVersionKind, + templateLabels map[string]string, + data runtime.RawExtension, + revision int64, + collisionCount *int32) (*apps.ControllerRevision, error) { + labelMap := make(map[string]string) + for k, v := range templateLabels { + labelMap[k] = v + } + cr := &apps.ControllerRevision{ + ObjectMeta: metav1.ObjectMeta{ + Labels: labelMap, + OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(parent, parentKind)}, + }, + Data: data, + Revision: revision, + } + hash := HashControllerRevision(cr, collisionCount) + cr.Name = ControllerRevisionName(parent.GetName(), hash) + cr.Labels[ControllerRevisionHashLabel] = hash + return cr, nil +} + +// HashControllerRevision hashes the contents of revision's Data using FNV hashing. If probe is not nil, the byte value +// of probe is added written to the hash as well. The returned hash will be a safe encoded string to avoid bad words. +func HashControllerRevision(revision *apps.ControllerRevision, probe *int32) string { + hf := fnv.New32() + if len(revision.Data.Raw) > 0 { + hf.Write(revision.Data.Raw) + } + if revision.Data.Object != nil { + hashutil.DeepHashObject(hf, revision.Data.Object) + } + if probe != nil { + hf.Write([]byte(strconv.FormatInt(int64(*probe), 10))) + } + return rand.SafeEncodeString(fmt.Sprint(hf.Sum32())) +} + +// SortControllerRevisions sorts revisions by their Revision. +func SortControllerRevisions(revisions []*apps.ControllerRevision) { + sort.Stable(byRevision(revisions)) +} + +// EqualRevision returns true if lhs and rhs are either both nil, or both point to non-nil ControllerRevisions that +// contain semantically equivalent data. Otherwise this method returns false. +func EqualRevision(lhs *apps.ControllerRevision, rhs *apps.ControllerRevision) bool { + var lhsHash, rhsHash *uint32 + if lhs == nil || rhs == nil { + return lhs == rhs + } + if hs, found := lhs.Labels[ControllerRevisionHashLabel]; found { + hash, err := strconv.ParseInt(hs, 10, 32) + if err == nil { + lhsHash = new(uint32) + *lhsHash = uint32(hash) + } + } + if hs, found := rhs.Labels[ControllerRevisionHashLabel]; found { + hash, err := strconv.ParseInt(hs, 10, 32) + if err == nil { + rhsHash = new(uint32) + *rhsHash = uint32(hash) + } + } + if lhsHash != nil && rhsHash != nil && *lhsHash != *rhsHash { + return false + } + return bytes.Equal(lhs.Data.Raw, rhs.Data.Raw) && apiequality.Semantic.DeepEqual(lhs.Data.Object, rhs.Data.Object) +} + +// FindEqualRevisions returns all ControllerRevisions in revisions that are equal to needle using EqualRevision as the +// equality test. The returned slice preserves the order of revisions. +func FindEqualRevisions(revisions []*apps.ControllerRevision, needle *apps.ControllerRevision) []*apps.ControllerRevision { + var eq []*apps.ControllerRevision + for i := range revisions { + if EqualRevision(revisions[i], needle) { + eq = append(eq, revisions[i]) + } + } + return eq +} + +// byRevision implements sort.Interface to allow ControllerRevisions to be sorted by Revision. +type byRevision []*apps.ControllerRevision + +func (br byRevision) Len() int { + return len(br) +} + +// Less breaks ties first by creation timestamp, then by name +func (br byRevision) Less(i, j int) bool { + if br[i].Revision == br[j].Revision { + if br[j].CreationTimestamp.Equal(&br[i].CreationTimestamp) { + return br[i].Name < br[j].Name + } + return br[j].CreationTimestamp.After(br[i].CreationTimestamp.Time) + } + return br[i].Revision < br[j].Revision +} + +func (br byRevision) Swap(i, j int) { + br[i], br[j] = br[j], br[i] +} diff --git a/pkg/yurtmanager/controller/yurtappset/revision.go b/pkg/yurtmanager/controller/yurtappset/revision.go index 7fd8a11fcef..b6c62bb6671 100644 --- a/pkg/yurtmanager/controller/yurtappset/revision.go +++ b/pkg/yurtmanager/controller/yurtappset/revision.go @@ -28,11 +28,11 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/klog/v2" - "k8s.io/kubernetes/pkg/controller/history" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/apiutil" appsbetav1 "github.com/openyurtio/openyurt/pkg/apis/apps/v1beta1" + "github.com/openyurtio/openyurt/pkg/util/kubernetes/controller/history" "github.com/openyurtio/openyurt/pkg/yurtmanager/controller/util/refmanager" "github.com/openyurtio/openyurt/pkg/yurtmanager/controller/yurtappset/workloadmanager" )