123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450 |
- /*
- 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"
- appsinformers "k8s.io/client-go/informers/apps/v1"
- clientset "k8s.io/client-go/kubernetes"
- appslisters "k8s.io/client-go/listers/apps/v1"
- hashutil "k8s.io/kubernetes/pkg/util/hash"
- apiequality "k8s.io/apimachinery/pkg/api/equality"
- "k8s.io/apimachinery/pkg/api/errors"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/labels"
- "k8s.io/apimachinery/pkg/runtime"
- "k8s.io/apimachinery/pkg/types"
- "k8s.io/apimachinery/pkg/runtime/schema"
- "k8s.io/apimachinery/pkg/util/rand"
- "k8s.io/client-go/tools/cache"
- "k8s.io/client-go/util/retry"
- )
- // 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]
- }
- // Interface provides an interface allowing for management of a Controller's history as realized by recorded
- // ControllerRevisions. An instance of Interface can be retrieved from NewHistory. Implementations must treat all
- // pointer parameters as "in" parameter, and they must not be mutated.
- type Interface interface {
- // ListControllerRevisions lists all ControllerRevisions matching selector and owned by parent or no other
- // controller. If the returned error is nil the returned slice of ControllerRevisions is valid. If the
- // returned error is not nil, the returned slice is not valid.
- ListControllerRevisions(parent metav1.Object, selector labels.Selector) ([]*apps.ControllerRevision, error)
- // CreateControllerRevision attempts to create the revision as owned by parent via a ControllerRef. If name
- // collision occurs, collisionCount (incremented each time collision occurs except for the first time) is
- // added to the hash of the revision and it is renamed using ControllerRevisionName. Implementations may
- // cease to attempt to retry creation after some number of attempts and return an error. If the returned
- // error is not nil, creation failed. If the returned error is nil, the returned ControllerRevision has been
- // created.
- // Callers must make sure that collisionCount is not nil. An error is returned if it is.
- CreateControllerRevision(parent metav1.Object, revision *apps.ControllerRevision, collisionCount *int32) (*apps.ControllerRevision, error)
- // DeleteControllerRevision attempts to delete revision. If the returned error is not nil, deletion has failed.
- DeleteControllerRevision(revision *apps.ControllerRevision) error
- // UpdateControllerRevision updates revision such that its Revision is equal to newRevision. Implementations
- // may retry on conflict. If the returned error is nil, the update was successful and returned ControllerRevision
- // is valid. If the returned error is not nil, the update failed and the returned ControllerRevision is invalid.
- UpdateControllerRevision(revision *apps.ControllerRevision, newRevision int64) (*apps.ControllerRevision, error)
- // AdoptControllerRevision attempts to adopt revision by adding a ControllerRef indicating that the parent
- // Object of parentKind is the owner of revision. If revision is already owned, an error is returned. If the
- // resource patch fails, an error is returned. If no error is returned, the returned ControllerRevision is
- // valid.
- AdoptControllerRevision(parent metav1.Object, parentKind schema.GroupVersionKind, revision *apps.ControllerRevision) (*apps.ControllerRevision, error)
- // ReleaseControllerRevision attempts to release parent's ownership of revision by removing parent from the
- // OwnerReferences of revision. If an error is returned, parent remains the owner of revision. If no error is
- // returned, the returned ControllerRevision is valid.
- ReleaseControllerRevision(parent metav1.Object, revision *apps.ControllerRevision) (*apps.ControllerRevision, error)
- }
- // NewHistory returns an instance of Interface that uses client to communicate with the API Server and lister to list
- // ControllerRevisions. This method should be used to create an Interface for all scenarios other than testing.
- func NewHistory(client clientset.Interface, lister appslisters.ControllerRevisionLister) Interface {
- return &realHistory{client, lister}
- }
- // NewFakeHistory returns an instance of Interface that uses informer to create, update, list, and delete
- // ControllerRevisions. This method should be used to create an Interface for testing purposes.
- func NewFakeHistory(informer appsinformers.ControllerRevisionInformer) Interface {
- return &fakeHistory{informer.Informer().GetIndexer(), informer.Lister()}
- }
- type realHistory struct {
- client clientset.Interface
- lister appslisters.ControllerRevisionLister
- }
- func (rh *realHistory) ListControllerRevisions(parent metav1.Object, selector labels.Selector) ([]*apps.ControllerRevision, error) {
- // List all revisions in the namespace that match the selector
- history, err := rh.lister.ControllerRevisions(parent.GetNamespace()).List(selector)
- if err != nil {
- return nil, err
- }
- var owned []*apps.ControllerRevision
- for i := range history {
- ref := metav1.GetControllerOf(history[i])
- if ref == nil || ref.UID == parent.GetUID() {
- owned = append(owned, history[i])
- }
- }
- return owned, err
- }
- func (rh *realHistory) CreateControllerRevision(parent metav1.Object, revision *apps.ControllerRevision, collisionCount *int32) (*apps.ControllerRevision, error) {
- if collisionCount == nil {
- return nil, fmt.Errorf("collisionCount should not be nil")
- }
- // Clone the input
- clone := revision.DeepCopy()
- // Continue to attempt to create the revision updating the name with a new hash on each iteration
- for {
- hash := HashControllerRevision(revision, collisionCount)
- // Update the revisions name
- clone.Name = ControllerRevisionName(parent.GetName(), hash)
- ns := parent.GetNamespace()
- created, err := rh.client.AppsV1().ControllerRevisions(ns).Create(clone)
- if errors.IsAlreadyExists(err) {
- exists, err := rh.client.AppsV1().ControllerRevisions(ns).Get(clone.Name, metav1.GetOptions{})
- if err != nil {
- return nil, err
- }
- if bytes.Equal(exists.Data.Raw, clone.Data.Raw) {
- return exists, nil
- }
- *collisionCount++
- continue
- }
- return created, err
- }
- }
- func (rh *realHistory) UpdateControllerRevision(revision *apps.ControllerRevision, newRevision int64) (*apps.ControllerRevision, error) {
- clone := revision.DeepCopy()
- err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
- if clone.Revision == newRevision {
- return nil
- }
- clone.Revision = newRevision
- updated, updateErr := rh.client.AppsV1().ControllerRevisions(clone.Namespace).Update(clone)
- if updateErr == nil {
- return nil
- }
- if updated != nil {
- clone = updated
- }
- if updated, err := rh.lister.ControllerRevisions(clone.Namespace).Get(clone.Name); err == nil {
- // make a copy so we don't mutate the shared cache
- clone = updated.DeepCopy()
- }
- return updateErr
- })
- return clone, err
- }
- func (rh *realHistory) DeleteControllerRevision(revision *apps.ControllerRevision) error {
- return rh.client.AppsV1().ControllerRevisions(revision.Namespace).Delete(revision.Name, nil)
- }
- func (rh *realHistory) AdoptControllerRevision(parent metav1.Object, parentKind schema.GroupVersionKind, revision *apps.ControllerRevision) (*apps.ControllerRevision, error) {
- // Return an error if the parent does not own the revision
- if owner := metav1.GetControllerOf(revision); owner != nil {
- return nil, fmt.Errorf("attempt to adopt revision owned by %v", owner)
- }
- // Use strategic merge patch to add an owner reference indicating a controller ref
- return rh.client.AppsV1().ControllerRevisions(parent.GetNamespace()).Patch(revision.GetName(),
- types.StrategicMergePatchType, []byte(fmt.Sprintf(
- `{"metadata":{"ownerReferences":[{"apiVersion":"%s","kind":"%s","name":"%s","uid":"%s","controller":true,"blockOwnerDeletion":true}],"uid":"%s"}}`,
- parentKind.GroupVersion().String(), parentKind.Kind,
- parent.GetName(), parent.GetUID(), revision.UID)))
- }
- func (rh *realHistory) ReleaseControllerRevision(parent metav1.Object, revision *apps.ControllerRevision) (*apps.ControllerRevision, error) {
- // Use strategic merge patch to add an owner reference indicating a controller ref
- released, err := rh.client.AppsV1().ControllerRevisions(revision.GetNamespace()).Patch(revision.GetName(),
- types.StrategicMergePatchType,
- []byte(fmt.Sprintf(`{"metadata":{"ownerReferences":[{"$patch":"delete","uid":"%s"}],"uid":"%s"}}`, parent.GetUID(), revision.UID)))
- if err != nil {
- if errors.IsNotFound(err) {
- // We ignore deleted revisions
- return nil, nil
- }
- if errors.IsInvalid(err) {
- // We ignore cases where the parent no longer owns the revision or where the revision has no
- // owner.
- return nil, nil
- }
- }
- return released, err
- }
- type fakeHistory struct {
- indexer cache.Indexer
- lister appslisters.ControllerRevisionLister
- }
- func (fh *fakeHistory) ListControllerRevisions(parent metav1.Object, selector labels.Selector) ([]*apps.ControllerRevision, error) {
- history, err := fh.lister.ControllerRevisions(parent.GetNamespace()).List(selector)
- if err != nil {
- return nil, err
- }
- var owned []*apps.ControllerRevision
- for i := range history {
- ref := metav1.GetControllerOf(history[i])
- if ref == nil || ref.UID == parent.GetUID() {
- owned = append(owned, history[i])
- }
- }
- return owned, err
- }
- func (fh *fakeHistory) addRevision(revision *apps.ControllerRevision) (*apps.ControllerRevision, error) {
- key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(revision)
- if err != nil {
- return nil, err
- }
- obj, found, err := fh.indexer.GetByKey(key)
- if err != nil {
- return nil, err
- }
- if found {
- foundRevision := obj.(*apps.ControllerRevision)
- return foundRevision, errors.NewAlreadyExists(apps.Resource("controllerrevision"), revision.Name)
- }
- return revision, fh.indexer.Update(revision)
- }
- func (fh *fakeHistory) CreateControllerRevision(parent metav1.Object, revision *apps.ControllerRevision, collisionCount *int32) (*apps.ControllerRevision, error) {
- if collisionCount == nil {
- return nil, fmt.Errorf("collisionCount should not be nil")
- }
- // Clone the input
- clone := revision.DeepCopy()
- clone.Namespace = parent.GetNamespace()
- // Continue to attempt to create the revision updating the name with a new hash on each iteration
- for {
- hash := HashControllerRevision(revision, collisionCount)
- // Update the revisions name and labels
- clone.Name = ControllerRevisionName(parent.GetName(), hash)
- created, err := fh.addRevision(clone)
- if errors.IsAlreadyExists(err) {
- *collisionCount++
- continue
- }
- return created, err
- }
- }
- func (fh *fakeHistory) DeleteControllerRevision(revision *apps.ControllerRevision) error {
- key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(revision)
- if err != nil {
- return err
- }
- obj, found, err := fh.indexer.GetByKey(key)
- if err != nil {
- return err
- }
- if !found {
- return errors.NewNotFound(apps.Resource("controllerrevisions"), revision.Name)
- }
- return fh.indexer.Delete(obj)
- }
- func (fh *fakeHistory) UpdateControllerRevision(revision *apps.ControllerRevision, newRevision int64) (*apps.ControllerRevision, error) {
- clone := revision.DeepCopy()
- clone.Revision = newRevision
- return clone, fh.indexer.Update(clone)
- }
- func (fh *fakeHistory) AdoptControllerRevision(parent metav1.Object, parentKind schema.GroupVersionKind, revision *apps.ControllerRevision) (*apps.ControllerRevision, error) {
- if owner := metav1.GetControllerOf(revision); owner != nil {
- return nil, fmt.Errorf("attempt to adopt revision owned by %v", owner)
- }
- key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(revision)
- if err != nil {
- return nil, err
- }
- _, found, err := fh.indexer.GetByKey(key)
- if err != nil {
- return nil, err
- }
- if !found {
- return nil, errors.NewNotFound(apps.Resource("controllerrevisions"), revision.Name)
- }
- clone := revision.DeepCopy()
- clone.OwnerReferences = append(clone.OwnerReferences, *metav1.NewControllerRef(parent, parentKind))
- return clone, fh.indexer.Update(clone)
- }
- func (fh *fakeHistory) ReleaseControllerRevision(parent metav1.Object, revision *apps.ControllerRevision) (*apps.ControllerRevision, error) {
- key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(revision)
- if err != nil {
- return nil, err
- }
- _, found, err := fh.indexer.GetByKey(key)
- if err != nil {
- return nil, err
- }
- if !found {
- return nil, nil
- }
- clone := revision.DeepCopy()
- refs := clone.OwnerReferences
- clone.OwnerReferences = nil
- for i := range refs {
- if refs[i].UID != parent.GetUID() {
- clone.OwnerReferences = append(clone.OwnerReferences, refs[i])
- }
- }
- return clone, fh.indexer.Update(clone)
- }
|