github.com/kubewharf/katalyst-core@v0.5.3/pkg/client/control/vpa.go (about)

     1  /*
     2  Copyright 2022 The Katalyst Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package control
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/types"
    26  	"k8s.io/apimachinery/pkg/util/jsonmergepatch"
    27  	"k8s.io/klog/v2"
    28  
    29  	apis "github.com/kubewharf/katalyst-api/pkg/apis/autoscaling/v1alpha1"
    30  	clientset "github.com/kubewharf/katalyst-api/pkg/client/clientset/versioned"
    31  	"github.com/kubewharf/katalyst-core/pkg/util/general"
    32  )
    33  
    34  // VPAUpdater is used to update VPA CR
    35  // todo: use patch instead of update to avoid conflict
    36  type VPAUpdater interface {
    37  	UpdateVPA(ctx context.Context, vpa *apis.KatalystVerticalPodAutoscaler,
    38  		opts metav1.UpdateOptions) (*apis.KatalystVerticalPodAutoscaler, error)
    39  	UpdateVPAStatus(ctx context.Context, vpa *apis.KatalystVerticalPodAutoscaler,
    40  		opts metav1.UpdateOptions) (*apis.KatalystVerticalPodAutoscaler, error)
    41  	PatchVPA(ctx context.Context, oldVPA,
    42  		newVPA *apis.KatalystVerticalPodAutoscaler) (*apis.KatalystVerticalPodAutoscaler, error)
    43  	PatchVPAStatus(ctx context.Context, oldVPA,
    44  		newVPA *apis.KatalystVerticalPodAutoscaler) (*apis.KatalystVerticalPodAutoscaler, error)
    45  }
    46  
    47  type DummyVPAUpdater struct{}
    48  
    49  func (d *DummyVPAUpdater) UpdateVPA(_ context.Context,
    50  	_ *apis.KatalystVerticalPodAutoscaler, _ metav1.UpdateOptions,
    51  ) (*apis.KatalystVerticalPodAutoscaler, error) {
    52  	return nil, nil
    53  }
    54  
    55  func (d *DummyVPAUpdater) UpdateVPAStatus(_ context.Context,
    56  	_ *apis.KatalystVerticalPodAutoscaler, _ metav1.UpdateOptions,
    57  ) (*apis.KatalystVerticalPodAutoscaler, error) {
    58  	return nil, nil
    59  }
    60  
    61  func (d *DummyVPAUpdater) PatchVPA(_ context.Context, _,
    62  	newVPA *apis.KatalystVerticalPodAutoscaler,
    63  ) (*apis.KatalystVerticalPodAutoscaler, error) {
    64  	return newVPA, nil
    65  }
    66  
    67  func (d *DummyVPAUpdater) PatchVPAStatus(_ context.Context, _,
    68  	newVPA *apis.KatalystVerticalPodAutoscaler,
    69  ) (*apis.KatalystVerticalPodAutoscaler, error) {
    70  	return newVPA, nil
    71  }
    72  
    73  type RealVPAUpdater struct {
    74  	client clientset.Interface
    75  }
    76  
    77  func NewRealVPAUpdater(client clientset.Interface) VPAUpdater {
    78  	return &RealVPAUpdater{
    79  		client: client,
    80  	}
    81  }
    82  
    83  func (r *RealVPAUpdater) UpdateVPA(ctx context.Context, vpa *apis.KatalystVerticalPodAutoscaler,
    84  	opts metav1.UpdateOptions,
    85  ) (*apis.KatalystVerticalPodAutoscaler, error) {
    86  	if vpa == nil {
    87  		return nil, fmt.Errorf("can't update a nil vpa")
    88  	}
    89  
    90  	return r.client.AutoscalingV1alpha1().KatalystVerticalPodAutoscalers(vpa.Namespace).Update(
    91  		ctx, vpa, opts)
    92  }
    93  
    94  func (r *RealVPAUpdater) UpdateVPAStatus(ctx context.Context, vpa *apis.KatalystVerticalPodAutoscaler,
    95  	opts metav1.UpdateOptions,
    96  ) (*apis.KatalystVerticalPodAutoscaler, error) {
    97  	if vpa == nil {
    98  		return nil, fmt.Errorf("can't update a nil vpa's status")
    99  	}
   100  
   101  	return r.client.AutoscalingV1alpha1().KatalystVerticalPodAutoscalers(vpa.Namespace).UpdateStatus(
   102  		ctx, vpa, opts)
   103  }
   104  
   105  func (r *RealVPAUpdater) PatchVPA(ctx context.Context, oldVPA,
   106  	newVPA *apis.KatalystVerticalPodAutoscaler,
   107  ) (*apis.KatalystVerticalPodAutoscaler, error) {
   108  	if oldVPA == nil || newVPA == nil {
   109  		return nil, fmt.Errorf("can't patch a nil vpa")
   110  	}
   111  
   112  	oldData, err := json.Marshal(oldVPA)
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  
   117  	newData, err := json.Marshal(newVPA)
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  
   122  	patchBytes, err := jsonmergepatch.CreateThreeWayJSONMergePatch(oldData, newData, oldData)
   123  	if err != nil {
   124  		return nil, fmt.Errorf("failed to create merge patch for vpa %q/%q: %v",
   125  			oldVPA.Namespace, oldVPA.Name, err)
   126  	} else if general.JsonPathEmpty(patchBytes) {
   127  		return newVPA, nil
   128  	}
   129  
   130  	return r.client.AutoscalingV1alpha1().KatalystVerticalPodAutoscalers(oldVPA.Namespace).
   131  		Patch(ctx, oldVPA.Name, types.MergePatchType, patchBytes, metav1.PatchOptions{})
   132  }
   133  
   134  func (r *RealVPAUpdater) PatchVPAStatus(ctx context.Context, oldVPA,
   135  	newVPA *apis.KatalystVerticalPodAutoscaler,
   136  ) (*apis.KatalystVerticalPodAutoscaler, error) {
   137  	if oldVPA == nil || newVPA == nil {
   138  		return nil, fmt.Errorf("can't patch a nil vpa")
   139  	}
   140  
   141  	oldData, err := json.Marshal(apis.KatalystVerticalPodAutoscaler{Status: oldVPA.Status})
   142  	if err != nil {
   143  		return nil, err
   144  	}
   145  
   146  	newData, err := json.Marshal(apis.KatalystVerticalPodAutoscaler{Status: newVPA.Status})
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  
   151  	if klog.V(5).Enabled() {
   152  		d, _ := json.Marshal(&oldVPA.Status)
   153  		dn, _ := json.Marshal(&newVPA.Status)
   154  		klog.Infof("vpa %s status updated: %v => %v", oldVPA.Name, string(d), string(dn))
   155  	}
   156  
   157  	patchBytes, err := jsonmergepatch.CreateThreeWayJSONMergePatch(oldData, newData, oldData)
   158  	if err != nil {
   159  		return nil, fmt.Errorf("failed to create merge patch for vpa %q/%q: %v",
   160  			oldVPA.Namespace, oldVPA.Name, err)
   161  	} else if general.JsonPathEmpty(patchBytes) {
   162  		return newVPA, nil
   163  	}
   164  
   165  	return r.client.AutoscalingV1alpha1().KatalystVerticalPodAutoscalers(oldVPA.Namespace).
   166  		Patch(ctx, oldVPA.Name, types.MergePatchType, patchBytes, metav1.PatchOptions{}, "status")
   167  }
   168  
   169  // RealVPAUpdaterWithMetric todo: implement with emitting metrics on updating
   170  type RealVPAUpdaterWithMetric struct {
   171  	client clientset.Interface
   172  	RealVPAUpdater
   173  }