diff --git a/pkg/reconciler/autoscaling/interface.go b/pkg/reconciler/autoscaling/interface.go new file mode 100644 index 000000000000..e81cb4dbb9ae --- /dev/null +++ b/pkg/reconciler/autoscaling/interface.go @@ -0,0 +1,69 @@ +/* +Copyright 2019 The Knative 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 autoscaling + +import ( + "context" + "fmt" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/tools/cache" + autoscalingv1alpha1 "knative.dev/serving/pkg/apis/autoscaling/v1alpha1" +) + +// ScalerType classified by Workload type +type ScalerType string + +func ScalerTypeFactoryByWR(wr WorkloadResource) ScalerType { + return ScalerType(fmt.Sprintf("ApiVersion:%s/Kind:%s", wr.ApiVersion, wr.Kind)) +} + +func ScalerTypeFactory(or v1.ObjectReference) ScalerType { + return ScalerType(fmt.Sprintf("ApiVersion:%s/Kind:%s", or.APIVersion, or.Kind)) +} + +type Scaler interface { + ApplyScale(ctx context.Context, pa *autoscalingv1alpha1.PodAutoscaler, desiredScale int32) error +} + +// ScalerFactory the Scaler factory to automatically generate Scaler objects. +type ScalerFactory func(ctx context.Context, bs BaseScaler) (Scaler, error) + +// ScalerFactories is a factory holder for any ScalerType +var ScalerFactories = make(map[ScalerType]ScalerFactory) + +// ScalerTypes is a scaler instance holder for any ScalerType +var ScalerTypes = make(map[ScalerType]Scaler) + +// RegisterScalerType Inject the Scaler factory to automatically generate Scaler objects. +func RegisterScalerType(key ScalerType, factory ScalerFactory) bool { + if _, ok := ScalerFactories[key]; ok { + return ok + } else { + ScalerFactories[key] = factory + return ok + } +} + +type BaseScaler interface { + // GetDynamicClient Get the default dynamic client + GetDynamicClient() dynamic.Interface + + // GetListerFactory Get the ListerFactory function related to the corresponding workload. + GetListerFactory() func(schema.GroupVersionResource) (cache.GenericLister, error) +} diff --git a/pkg/reconciler/autoscaling/kpa/scaler.go b/pkg/reconciler/autoscaling/kpa/scaler.go index e59b82fd60a0..b1e986dc98ba 100644 --- a/pkg/reconciler/autoscaling/kpa/scaler.go +++ b/pkg/reconciler/autoscaling/kpa/scaler.go @@ -19,6 +19,8 @@ package kpa import ( "context" "fmt" + "k8s.io/client-go/dynamic" + "knative.dev/serving/pkg/reconciler/autoscaling" "net/http" "time" @@ -40,10 +42,7 @@ import ( aresources "knative.dev/serving/pkg/reconciler/autoscaling/resources" "knative.dev/serving/pkg/resources" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/dynamic" "k8s.io/client-go/tools/cache" ) @@ -295,36 +294,6 @@ func (ks *scaler) handleScaleToZero(ctx context.Context, pa *autoscalingv1alpha1 } } -func (ks *scaler) applyScale(ctx context.Context, pa *autoscalingv1alpha1.PodAutoscaler, desiredScale int32, - ps *autoscalingv1alpha1.PodScalable) error { - logger := logging.FromContext(ctx) - - gvr, name, err := resources.ScaleResourceArguments(pa.Spec.ScaleTargetRef) - if err != nil { - return err - } - - psNew := ps.DeepCopy() - psNew.Spec.Replicas = &desiredScale - patch, err := duck.CreatePatch(ps, psNew) - if err != nil { - return err - } - patchBytes, err := patch.MarshalJSON() - if err != nil { - return err - } - - _, err = ks.dynamicClient.Resource(*gvr).Namespace(pa.Namespace).Patch(ctx, ps.Name, types.JSONPatchType, - patchBytes, metav1.PatchOptions{}) - if err != nil { - return fmt.Errorf("failed to apply scale %d to scale target %s: %w", desiredScale, name, err) - } - - logger.Debug("Successfully scaled to ", desiredScale) - return nil -} - // scale attempts to scale the given PA's target reference to the desired scale. func (ks *scaler) scale(ctx context.Context, pa *autoscalingv1alpha1.PodAutoscaler, sks *netv1alpha1.ServerlessService, desiredScale int32) (int32, error) { asConfig := config.FromContext(ctx).Autoscaler @@ -373,4 +342,29 @@ func (ks *scaler) scale(ctx context.Context, pa *autoscalingv1alpha1.PodAutoscal logger.Infof("Scaling from %d to %d", currentScale, desiredScale) return desiredScale, ks.applyScale(ctx, pa, desiredScale, ps) + // Operate different types of workloads. + scaleType := autoscaling.ScalerTypeFactory(pa.Spec.ScaleTargetRef) + if scaler, ok := autoscaling.ScalerTypes[scaleType]; ok { + err = scaler.ApplyScale(ctx, pa, desiredScale) + } else { + if sff, ok := autoscaling.ScalerFactories[scaleType]; ok { + s, err := sff(ctx, ks) + if err != nil { + return desiredScale, err + } + autoscaling.ScalerTypes[scaleType] = s + return desiredScale, s.ApplyScale(ctx, pa, desiredScale) + } + } + return desiredScale, err +} + +// GetDynamicClient Get the default dynamic client +func (ks *scaler) GetDynamicClient() dynamic.Interface { + return ks.dynamicClient +} + +// GetListerFactory Get the ListerFactory function related to the corresponding workload. +func (ks *scaler) GetListerFactory() func(schema.GroupVersionResource) (cache.GenericLister, error) { + return ks.listerFactory } diff --git a/pkg/reconciler/autoscaling/types.go b/pkg/reconciler/autoscaling/types.go new file mode 100644 index 000000000000..aec62875c758 --- /dev/null +++ b/pkg/reconciler/autoscaling/types.go @@ -0,0 +1,31 @@ +/* +Copyright 2019 The Knative 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 autoscaling + +// WorkloadResource Define a type of workload +type WorkloadResource struct { + Kind string + ApiVersion string +} + +var ( + DEPLOYMENT = WorkloadResource{ + Kind: "Deployment", + ApiVersion: "apps/v1", + } + // Add others... +) diff --git a/pkg/reconciler/autoscaling/workload/deployment_scaler.go b/pkg/reconciler/autoscaling/workload/deployment_scaler.go new file mode 100644 index 000000000000..c40b85680b9e --- /dev/null +++ b/pkg/reconciler/autoscaling/workload/deployment_scaler.go @@ -0,0 +1,68 @@ +/* +Copyright 2019 The Knative 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 workload + +import ( + "context" + "fmt" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "knative.dev/pkg/apis/duck" + autoscalingv1alpha1 "knative.dev/serving/pkg/apis/autoscaling/v1alpha1" + "knative.dev/serving/pkg/reconciler/autoscaling" + "knative.dev/serving/pkg/resources" +) + +type DeploymentScaler struct { + ctx context.Context + bs autoscaling.BaseScaler +} + +var _ autoscaling.Scaler = &DeploymentScaler{} + +func init() { + autoscaling.RegisterScalerType(autoscaling.ScalerTypeFactoryByWR(autoscaling.DEPLOYMENT), NewDeploymentScaler) +} + +func NewDeploymentScaler(ctx context.Context, bs autoscaling.BaseScaler) (autoscaling.Scaler, error) { + return &DeploymentScaler{ctx: ctx, bs: bs}, nil +} + +func (a *DeploymentScaler) ApplyScale(ctx context.Context, pa *autoscalingv1alpha1.PodAutoscaler, desiredScale int32) error { + ps, err := resources.GetScaleResource(pa.Namespace, pa.Spec.ScaleTargetRef, a.bs.GetListerFactory()) + if err != nil { + return fmt.Errorf("failed to get scale target %v: %w", pa.Spec.ScaleTargetRef, err) + } + gvr, _, err := resources.ScaleResourceArguments(pa.Spec.ScaleTargetRef) + if err != nil { + return err + } + psNew := ps.DeepCopy() + psNew.Spec.Replicas = &desiredScale + patch, err := duck.CreatePatch(ps, psNew) + if err != nil { + return err + } + patchBytes, err := patch.MarshalJSON() + if err != nil { + return err + } + + _, err = a.bs.GetDynamicClient().Resource(*gvr).Namespace(pa.Namespace).Patch(ctx, ps.Name, types.JSONPatchType, + patchBytes, metav1.PatchOptions{}) + return err +}