mirror of https://github.com/k3s-io/k3s
457 lines
17 KiB
Go
457 lines
17 KiB
Go
/*
|
|
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/v1beta1"
|
|
appsinformers "k8s.io/client-go/informers/apps/v1beta1"
|
|
clientset "k8s.io/client-go/kubernetes"
|
|
appslisters "k8s.io/client-go/listers/apps/v1beta1"
|
|
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 uint32) string {
|
|
if len(prefix) > 223 {
|
|
prefix = prefix[:223]
|
|
}
|
|
|
|
return fmt.Sprintf("%s-%s", prefix, rand.SafeEncodeString(strconv.FormatInt(int64(hash), 10)))
|
|
}
|
|
|
|
// NewControllerRevision returns a ControllerRevision with a ControllerRef pointing to parent and indicating that
|
|
// parent is of parentKind. The ControllerRevision has labels matching selector, 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,
|
|
selector labels.Selector,
|
|
data runtime.RawExtension,
|
|
revision int64,
|
|
collisionCount *int32) (*apps.ControllerRevision, error) {
|
|
labelMap, err := labels.ConvertSelectorToLabelsMap(selector.String())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
blockOwnerDeletion := true
|
|
isController := true
|
|
cr := &apps.ControllerRevision{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Labels: labelMap,
|
|
OwnerReferences: []metav1.OwnerReference{
|
|
{
|
|
APIVersion: parentKind.GroupVersion().String(),
|
|
Kind: parentKind.Kind,
|
|
Name: parent.GetName(),
|
|
UID: parent.GetUID(),
|
|
BlockOwnerDeletion: &blockOwnerDeletion,
|
|
Controller: &isController,
|
|
},
|
|
},
|
|
},
|
|
Data: data,
|
|
Revision: revision,
|
|
}
|
|
hash := HashControllerRevision(cr, collisionCount)
|
|
cr.Name = ControllerRevisionName(parent.GetName(), hash)
|
|
cr.Labels[ControllerRevisionHashLabel] = strconv.FormatInt(int64(hash), 10)
|
|
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.
|
|
func HashControllerRevision(revision *apps.ControllerRevision, probe *int32) uint32 {
|
|
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 hf.Sum32()
|
|
|
|
}
|
|
|
|
// SortControllerRevisions sorts revisions by their Revision.
|
|
func SortControllerRevisions(revisions []*apps.ControllerRevision) {
|
|
sort.Sort(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)
|
|
}
|
|
|
|
func (br byRevision) Less(i, j int) bool {
|
|
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 and labels
|
|
clone.Name = ControllerRevisionName(parent.GetName(), hash)
|
|
created, err := rh.client.AppsV1beta1().ControllerRevisions(parent.GetNamespace()).Create(clone)
|
|
if errors.IsAlreadyExists(err) {
|
|
*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.AppsV1beta1().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.AppsV1beta1().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.AppsV1beta1().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.AppsV1beta1().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) {
|
|
blockOwnerDeletion := true
|
|
isController := true
|
|
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.OwnerReference{
|
|
APIVersion: parentKind.GroupVersion().String(),
|
|
Kind: parentKind.Kind,
|
|
Name: parent.GetName(),
|
|
UID: parent.GetUID(),
|
|
BlockOwnerDeletion: &blockOwnerDeletion,
|
|
Controller: &isController,
|
|
})
|
|
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)
|
|
}
|