Copyright 2016 The Kubernetes Authors.
Copyright 2017 The KubeVirt 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.
Taken from https://github.com/kubernetes/kubernetes/blob/b28a83a4cf779189d72a87e847441888e7918e5d/pkg/controller/controller_ref_manager.go
and adapted for KubeVirt.
*/
package controller
import (
"context"
"fmt"
"sync"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
poolv1 "kubevirt.io/api/pool/v1alpha1"
virtv1 "kubevirt.io/api/core/v1"
"kubevirt.io/client-go/kubecli"
"kubevirt.io/client-go/log"
cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
)
type BaseControllerRefManager struct {
Controller metav1.Object
Selector labels.Selector
canAdoptErr error
canAdoptOnce sync.Once
CanAdoptFunc func() error
}
func (m *BaseControllerRefManager) CanAdopt() error {
m.canAdoptOnce.Do(func() {
if m.CanAdoptFunc != nil {
m.canAdoptErr = m.CanAdoptFunc()
}
})
return m.canAdoptErr
}
func (m *BaseControllerRefManager) isOwned(obj metav1.Object) bool {
controllerRef := metav1.GetControllerOf(obj)
if controllerRef == nil {
return false
}
if controllerRef.UID != m.Controller.GetUID() {
return false
}
return true
}
func (m *BaseControllerRefManager) isOwnedByOther(obj metav1.Object) bool {
controllerRef := metav1.GetControllerOf(obj)
if controllerRef == nil {
return false
}
if controllerRef.UID != m.Controller.GetUID() {
return true
}
return false
}
func (m *BaseControllerRefManager) ReleaseDetachedObject(obj metav1.Object, match func(metav1.Object) bool, release func(metav1.Object) error) (bool, error) {
isOwned := m.isOwned(obj)
if isOwned && !match(obj) {
if m.Controller.GetDeletionTimestamp() != nil {
return false, nil
}
if err := release(obj); err != nil {
if errors.IsNotFound(err) {
return false, nil
}
return false, err
}
return false, nil
}
return isOwned, nil
}
func (m *BaseControllerRefManager) ClaimObject(obj metav1.Object, match func(metav1.Object) bool, adopt, release func(metav1.Object) error) (bool, error) {
owned := m.isOwned(obj)
ownedByOther := m.isOwnedByOther(obj)
matched := match(obj)
if owned && matched {
return true, nil
} else if owned && !matched {
isStillOwned, err := m.ReleaseDetachedObject(obj, match, release)
if err != nil {
return isStillOwned, err
}
return isStillOwned, nil
} else if !owned && !ownedByOther && matched {
if m.Controller.GetDeletionTimestamp() != nil || !matched {
return false, nil
}
if obj.GetDeletionTimestamp() != nil {
return false, nil
}
if err := adopt(obj); err != nil {
if errors.IsNotFound(err) {
return false, nil
}
return false, err
}
return true, nil
} else {
return false, nil
}
}
type VirtualMachineControllerRefManager struct {
BaseControllerRefManager
controllerKind schema.GroupVersionKind
virtualMachineControl VirtualMachineControlInterface
}
func NewVirtualMachineControllerRefManager(
virtualMachineControl VirtualMachineControlInterface,
controller metav1.Object,
selector labels.Selector,
controllerKind schema.GroupVersionKind,
canAdopt func() error,
) *VirtualMachineControllerRefManager {
return &VirtualMachineControllerRefManager{
BaseControllerRefManager: BaseControllerRefManager{
Controller: controller,
Selector: selector,
CanAdoptFunc: canAdopt,
},
controllerKind: controllerKind,
virtualMachineControl: virtualMachineControl,
}
}
func (m *VirtualMachineControllerRefManager) ClaimVirtualMachineInstances(vmis []*virtv1.VirtualMachineInstance, filters ...func(machine *virtv1.VirtualMachineInstance) bool) ([]*virtv1.VirtualMachineInstance, error) {
var claimed []*virtv1.VirtualMachineInstance
var errlist []error
match := func(obj metav1.Object) bool {
vmi := obj.(*virtv1.VirtualMachineInstance)
if !m.Selector.Matches(labels.Set(vmi.Labels)) {
return false
}
for _, filter := range filters {
if !filter(vmi) {
return false
}
}
return true
}
adopt := func(obj metav1.Object) error {
return m.AdoptVirtualMachineInstance(obj.(*virtv1.VirtualMachineInstance))
}
release := func(obj metav1.Object) error {
return m.ReleaseVirtualMachineInstance(obj.(*virtv1.VirtualMachineInstance))
}
for _, vmi := range vmis {
ok, err := m.ClaimObject(vmi, match, adopt, release)
if err != nil {
errlist = append(errlist, err)
continue
}
if ok {
claimed = append(claimed, vmi)
}
}
return claimed, utilerrors.NewAggregate(errlist)
}
func (m *VirtualMachineControllerRefManager) ReleaseDetachedVirtualMachines(vms []*virtv1.VirtualMachine, filters ...func(machine *virtv1.VirtualMachine) bool) ([]*virtv1.VirtualMachine, error) {
var owned []*virtv1.VirtualMachine
var errlist []error
match := func(obj metav1.Object) bool {
vm := obj.(*virtv1.VirtualMachine)
if !m.Selector.Matches(labels.Set(vm.Labels)) {
return false
}
for _, filter := range filters {
if !filter(vm) {
return false
}
}
return true
}
release := func(obj metav1.Object) error {
return m.ReleaseVirtualMachine(obj.(*virtv1.VirtualMachine))
}
for _, vm := range vms {
isOwner, err := m.ReleaseDetachedObject(vm, match, release)
if err != nil {
errlist = append(errlist, err)
continue
}
if isOwner {
owned = append(owned, vm)
}
}
return owned, utilerrors.NewAggregate(errlist)
}
func (m *VirtualMachineControllerRefManager) ClaimMatchedDataVolumes(dataVolumes []*cdiv1.DataVolume) ([]*cdiv1.DataVolume, error) {
var claimed []*cdiv1.DataVolume
var errlist []error
match := func(obj metav1.Object) bool {
return true
}
adopt := func(obj metav1.Object) error {
return m.AdoptDataVolume(obj.(*cdiv1.DataVolume))
}
release := func(obj metav1.Object) error {
return m.ReleaseDataVolume(obj.(*cdiv1.DataVolume))
}
for _, dataVolume := range dataVolumes {
ok, err := m.ClaimObject(dataVolume, match, adopt, release)
if err != nil {
errlist = append(errlist, err)
continue
}
if ok {
claimed = append(claimed, dataVolume)
}
}
return claimed, utilerrors.NewAggregate(errlist)
}
func (m *VirtualMachineControllerRefManager) ClaimVirtualMachineInstanceByName(vmi *virtv1.VirtualMachineInstance, filters ...func(machine *virtv1.VirtualMachineInstance) bool) (*virtv1.VirtualMachineInstance, error) {
match := func(obj metav1.Object) bool {
vmi := obj.(*virtv1.VirtualMachineInstance)
if m.Controller.GetName() != vmi.Name {
return false
}
for _, filter := range filters {
if !filter(vmi) {
return false
}
}
return true
}
adopt := func(obj metav1.Object) error {
return m.AdoptVirtualMachineInstance(obj.(*virtv1.VirtualMachineInstance))
}
release := func(obj metav1.Object) error {
return m.ReleaseVirtualMachineInstance(obj.(*virtv1.VirtualMachineInstance))
}
ok, err := m.ClaimObject(vmi, match, adopt, release)
if err != nil {
return nil, err
}
if ok {
return vmi, nil
}
return nil, nil
}
func (m *VirtualMachineControllerRefManager) AdoptVirtualMachineInstance(vmi *virtv1.VirtualMachineInstance) error {
if err := m.CanAdopt(); err != nil {
return fmt.Errorf("can't adopt VirtualMachineInstance %v/%v (%v): %v", vmi.Namespace, vmi.Name, vmi.UID, err)
}
addControllerPatch := fmt.Sprintf(
`{"metadata":{"ownerReferences":[{"apiVersion":"%s","kind":"%s","name":"%s","uid":"%s","controller":true,"blockOwnerDeletion":true}],"uid":"%s"}}`,
m.controllerKind.GroupVersion(), m.controllerKind.Kind,
m.Controller.GetName(), m.Controller.GetUID(), vmi.UID)
return m.virtualMachineControl.PatchVirtualMachineInstance(vmi.Namespace, vmi.Name, []byte(addControllerPatch))
}
func (m *VirtualMachineControllerRefManager) ReleaseVirtualMachineInstance(vmi *virtv1.VirtualMachineInstance) error {
log.Log.V(2).Object(vmi).Infof("patching vmi to remove its controllerRef to %s/%s:%s",
m.controllerKind.GroupVersion(), m.controllerKind.Kind, m.Controller.GetName())
deleteOwnerRefPatch := fmt.Sprint(`{"metadata":{"ownerReferences":[]}}`)
err := m.virtualMachineControl.PatchVirtualMachineInstance(vmi.Namespace, vmi.Name, []byte(deleteOwnerRefPatch))
if err != nil {
if errors.IsNotFound(err) {
return nil
}
if errors.IsInvalid(err) {
return nil
}
}
return err
}
func (m *VirtualMachineControllerRefManager) AdoptVirtualMachine(vm *virtv1.VirtualMachine) error {
if err := m.CanAdopt(); err != nil {
return fmt.Errorf("can't adopt VirtualMachine %v/%v (%v): %v", vm.Namespace, vm.Name, vm.UID, err)
}
addControllerPatch := fmt.Sprintf(
`{"metadata":{"ownerReferences":[{"apiVersion":"%s","kind":"%s","name":"%s","uid":"%s","controller":true,"blockOwnerDeletion":true}],"uid":"%s"}}`,
m.controllerKind.GroupVersion(), m.controllerKind.Kind,
m.Controller.GetName(), m.Controller.GetUID(), vm.UID)
return m.virtualMachineControl.PatchVirtualMachine(vm.Namespace, vm.Name, []byte(addControllerPatch))
}
func (m *VirtualMachineControllerRefManager) ReleaseVirtualMachine(vm *virtv1.VirtualMachine) error {
log.Log.V(2).Object(vm).Infof("patching vm to remove its controllerRef to %s/%s:%s",
m.controllerKind.GroupVersion(), m.controllerKind.Kind, m.Controller.GetName())
var newFinalizers []string
for _, fin := range vm.Finalizers {
if fin != poolv1.VirtualMachinePoolControllerFinalizer {
newFinalizers = append(newFinalizers, fin)
}
}
releaseVMPatch := fmt.Sprintf(`{"metadata":{"ownerReferences":[],"finalizers":%s}}`, newFinalizers)
err := m.virtualMachineControl.PatchVirtualMachine(vm.Namespace, vm.Name, []byte(releaseVMPatch))
if err != nil {
if errors.IsNotFound(err) {
return nil
}
if errors.IsInvalid(err) {
return nil
}
}
return err
}
func (m *VirtualMachineControllerRefManager) AdoptDataVolume(dataVolume *cdiv1.DataVolume) error {
if err := m.CanAdopt(); err != nil {
return fmt.Errorf("can't adopt DataVolume %v/%v (%v): %v", dataVolume.Namespace, dataVolume.Name, dataVolume.UID, err)
}
addControllerPatch := fmt.Sprintf(
`{"metadata":{"ownerReferences":[{"apiVersion":"%s","kind":"%s","name":"%s","uid":"%s","controller":true,"blockOwnerDeletion":true}],"uid":"%s"}}`,
m.controllerKind.GroupVersion(), m.controllerKind.Kind,
m.Controller.GetName(), m.Controller.GetUID(), dataVolume.UID)
return m.virtualMachineControl.PatchDataVolume(dataVolume.Namespace, dataVolume.Name, []byte(addControllerPatch))
}
func (m *VirtualMachineControllerRefManager) ReleaseDataVolume(dataVolume *cdiv1.DataVolume) error {
log.Log.V(2).Object(dataVolume).Infof("patching dataVolume to remove its controllerRef to %s/%s:%s",
m.controllerKind.GroupVersion(), m.controllerKind.Kind, m.Controller.GetName())
deleteOwnerRefPatch := fmt.Sprint(`{"metadata":{"ownerReferences":[]}}`)
err := m.virtualMachineControl.PatchDataVolume(dataVolume.Namespace, dataVolume.Name, []byte(deleteOwnerRefPatch))
if err != nil {
if errors.IsNotFound(err) {
return nil
}
if errors.IsInvalid(err) {
return nil
}
}
return err
}
type VirtualMachineControlInterface interface {
PatchVirtualMachine(namespace, name string, data []byte) error
PatchVirtualMachineInstance(namespace, name string, data []byte) error
PatchDataVolume(namespace, name string, data []byte) error
}
type RealVirtualMachineControl struct {
Clientset kubecli.KubevirtClient
}
func (r RealVirtualMachineControl) PatchVirtualMachineInstance(namespace, name string, data []byte) error {
_, err := r.Clientset.VirtualMachineInstance(namespace).Patch(context.Background(), name, types.MergePatchType, data, metav1.PatchOptions{})
return err
}
func (r RealVirtualMachineControl) PatchVirtualMachine(namespace, name string, data []byte) error {
_, err := r.Clientset.VirtualMachine(namespace).Patch(context.Background(), name, types.MergePatchType, data, metav1.PatchOptions{})
return err
}
func (r RealVirtualMachineControl) PatchDataVolume(namespace, name string, data []byte) error {
_, err := r.Clientset.CdiClient().CdiV1beta1().DataVolumes(namespace).Patch(context.Background(), name, types.MergePatchType, data, metav1.PatchOptions{})
return err
}
func RecheckDeletionTimestamp(getObject func() (metav1.Object, error)) func() error {
return func() error {
obj, err := getObject()
if err != nil {
return fmt.Errorf("can't recheck DeletionTimestamp: %v", err)
}
if obj.GetDeletionTimestamp() != nil {
return fmt.Errorf("%v/%v has just been deleted at %v", obj.GetNamespace(), obj.GetName(), obj.GetDeletionTimestamp())
}
return nil
}
}