diff --git a/Makefile b/Makefile index 4fabff6..d28d60b 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. ENVTEST_K8S_VERSION = 1.22 -KOORD_VERSION ?= v1.1.0 +KOORD_VERSION ?= v1.1.1 # Set license header files. LICENSE_HEADER_GO ?= hack/boilerplate/boilerplate.go.txt diff --git a/extension/descheduling.go b/extension/descheduling.go new file mode 100644 index 0000000..f3b9865 --- /dev/null +++ b/extension/descheduling.go @@ -0,0 +1,59 @@ +/* +Copyright 2022 The Koordinator 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 extension + +import ( + "fmt" + "strconv" +) + +const ( + // AnnotationEvictionCost indicates the eviction cost. It can be used to set to an int32. + // Although the K8s community has [Pod Deletion Cost #2255](https://github.com/kubernetes/enhancements/issues/2255), + // it is not a general mechanism. To avoid conflicts with components that use `Pod Deletion Cost`, + // users can individually mark the eviction cost for Pods. + // The implicit eviction cost for pods that don't set the annotation is 0, negative values are permitted. + // If set the cost with `math.MaxInt32`, it means the Pod will not be evicted. + // Pods with lower eviction cost are preferred to be evicted before pods with higher eviction cost. + // If a batch of Pods to be evicted have the same priority, they will be sorted by cost, + // and the Pod with the smallest cost will be evicted. + AnnotationEvictionCost = SchedulingDomainPrefix + "/eviction-cost" +) + +func GetEvictionCost(annotations map[string]string) (int32, error) { + if value, exist := annotations[AnnotationEvictionCost]; exist { + // values that start with plus sign (e.g, "+10") or leading zeros (e.g., "008") are not valid. + if !validFirstDigit(value) { + return 0, fmt.Errorf("invalid value %q", value) + } + + i, err := strconv.ParseInt(value, 10, 32) + if err != nil { + // make sure we default to 0 on error. + return 0, err + } + return int32(i), nil + } + return 0, nil +} + +func validFirstDigit(str string) bool { + if len(str) == 0 { + return false + } + return str[0] == '-' || (str[0] == '0' && str == "0") || (str[0] >= '1' && str[0] <= '9') +} diff --git a/extension/slo_controller_config.go b/extension/slo_controller_config.go new file mode 100644 index 0000000..ee9efd3 --- /dev/null +++ b/extension/slo_controller_config.go @@ -0,0 +1,333 @@ +/* +Copyright 2022 The Koordinator 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 extension + +import ( + "time" + + "github.com/mohae/deepcopy" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + slov1alpha1 "github.com/koordinator-sh/apis/slo/v1alpha1" +) + +const ( + // keys in the configmap + ColocationConfigKey = "colocation-config" + ResourceThresholdConfigKey = "resource-threshold-config" + ResourceQOSConfigKey = "resource-qos-config" + CPUBurstConfigKey = "cpu-burst-config" +) + +// +k8s:deepcopy-gen=true +type ColocationCfg struct { + ColocationStrategy `json:",inline"` + NodeConfigs []NodeColocationCfg `json:"nodeConfigs,omitempty"` +} + +// +k8s:deepcopy-gen=true +type NodeColocationCfg struct { + NodeSelector *metav1.LabelSelector + ColocationStrategy +} + +// +k8s:deepcopy-gen=true +type ResourceThresholdCfg struct { + ClusterStrategy *slov1alpha1.ResourceThresholdStrategy `json:"clusterStrategy,omitempty"` + NodeStrategies []NodeResourceThresholdStrategy `json:"nodeStrategies,omitempty"` +} + +// +k8s:deepcopy-gen=true +type NodeResourceThresholdStrategy struct { + // an empty label selector matches all objects while a nil label selector matches no objects + NodeSelector *metav1.LabelSelector `json:"nodeSelector,omitempty"` + *slov1alpha1.ResourceThresholdStrategy +} + +// +k8s:deepcopy-gen=true +type NodeCPUBurstCfg struct { + // an empty label selector matches all objects while a nil label selector matches no objects + NodeSelector *metav1.LabelSelector `json:"nodeSelector,omitempty"` + *slov1alpha1.CPUBurstStrategy +} + +// +k8s:deepcopy-gen=true +type CPUBurstCfg struct { + ClusterStrategy *slov1alpha1.CPUBurstStrategy `json:"clusterStrategy,omitempty"` + NodeStrategies []NodeCPUBurstCfg `json:"nodeStrategies,omitempty"` +} + +// +k8s:deepcopy-gen=true +type ResourceQOSCfg struct { + ClusterStrategy *slov1alpha1.ResourceQOSStrategy `json:"clusterStrategy,omitempty"` + NodeStrategies []NodeResourceQOSStrategy `json:"nodeStrategies,omitempty"` +} + +// +k8s:deepcopy-gen=true +type NodeResourceQOSStrategy struct { + // an empty label selector matches all objects while a nil label selector matches no objects + NodeSelector *metav1.LabelSelector `json:"nodeSelector,omitempty"` + *slov1alpha1.ResourceQOSStrategy +} + +type CalculatePolicy string + +const ( + CalculateByPodUsage CalculatePolicy = "usage" + CalculateByPodRequest CalculatePolicy = "request" +) + +// +k8s:deepcopy-gen=true +type ColocationStrategyExtender struct { + Extensions ExtraFields `json:"extensions,omitempty"` +} + +// +k8s:deepcopy-gen=false +type ExtraFields map[string]interface{} + +func (in *ExtraFields) DeepCopyInto(out *ExtraFields) { + if in == nil { + return + } else { + outIf := deepcopy.Copy(*in) + *out = outIf.(ExtraFields) + } +} + +func (in *ExtraFields) DeepCopy() *ExtraFields { + if in == nil { + return nil + } + out := new(ExtraFields) + in.DeepCopyInto(out) + return out +} + +// +k8s:deepcopy-gen=true +type AggregatePolicy struct { + Durations []time.Duration `json:"durations,omitempty"` + StatisticTypes []slov1alpha1.AggregationType `json:"statisticTypes,omitempty"` +} + +// +k8s:deepcopy-gen=true +type ColocationStrategy struct { + Enable *bool `json:"enable,omitempty"` + MetricAggregateDurationSeconds *int64 `json:"metricAggregateDurationSeconds,omitempty"` + MetricReportIntervalSeconds *int64 `json:"metricReportIntervalSeconds,omitempty"` + MetricAggregatePolicy *slov1alpha1.AggregatePolicy `json:"metricAggregatePolicy,omitempty"` + CPUReclaimThresholdPercent *int64 `json:"cpuReclaimThresholdPercent,omitempty"` + MemoryReclaimThresholdPercent *int64 `json:"memoryReclaimThresholdPercent,omitempty"` + MemoryCalculatePolicy *CalculatePolicy `json:"memoryCalculatePolicy,omitempty"` + DegradeTimeMinutes *int64 `json:"degradeTimeMinutes,omitempty"` + UpdateTimeThresholdSeconds *int64 `json:"updateTimeThresholdSeconds,omitempty"` + ResourceDiffThreshold *float64 `json:"resourceDiffThreshold,omitempty"` + ColocationStrategyExtender `json:",inline"` // for third-party extension +} + +/* +Koordinator uses configmap to manage the configuration of SLO, the configmap is stored in + /, with the following keys respectively: + - + - + - + - + +et. + +For example, the configmap is as follows: + +``` +apiVersion: v1 +data: + colocation-config: | + { + "enable": false, + "metricAggregateDurationSeconds": 300, + "metricReportIntervalSeconds": 60, + "metricAggregatePolicy": { + "durations": [ + { + "duration": "5m" + }, + { + "duration": "10m" + }, + { + "duration": "15m" + } + ] + }, + "cpuReclaimThresholdPercent": 60, + "memoryReclaimThresholdPercent": 65, + "memoryCalculatePolicy": "usage", + "degradeTimeMinutes": 15, + "updateTimeThresholdSeconds": 300, + "resourceDiffThreshold": 0.1, + "nodeConfigs": [ + { + "nodeSelector": { + "matchLabels": { + "kubernetes.io/kernel": "alios" + } + }, + "updateTimeThresholdSeconds": 360, + "resourceDiffThreshold": 0.2 + } + ] + } + cpu-burst-config: | + { + "clusterStrategy": { + "policy": "none", + "cpuBurstPercent": 1000, + "cfsQuotaBurstPercent": 300, + "cfsQuotaBurstPeriodSeconds": -1, + "sharePoolThresholdPercent": 50 + }, + "nodeStrategies": [ + { + "nodeSelector": { + "matchLabels": { + "kubernetes.io/kernel": "alios" + } + }, + "policy": "cfsQuotaBurstOnly", + "cfsQuotaBurstPercent": 400 + } + ] + } + resource-qos-config: | + { + "clusterStrategy": { + "lsrClass": { + "cpuQOS": { + "enable": false, + "groupIdentity": 2 + }, + "memoryQOS": { + "enable": false, + "minLimitPercent": 0, + "lowLimitPercent": 0, + "throttlingPercent": 0, + "wmarkRatio": 95, + "wmarkScalePermill": 20, + "wmarkMinAdj": -25, + "priorityEnable": 0, + "priority": 0, + "oomKillGroup": 0 + }, + "resctrlQOS": { + "enable": false, + "catRangeStartPercent": 0, + "catRangeEndPercent": 100, + "mbaPercent": 100 + } + }, + "lsClass": { + "cpuQOS": { + "enable": false, + "groupIdentity": 2 + }, + "memoryQOS": { + "enable": false, + "minLimitPercent": 0, + "lowLimitPercent": 0, + "throttlingPercent": 0, + "wmarkRatio": 95, + "wmarkScalePermill": 20, + "wmarkMinAdj": -25, + "priorityEnable": 0, + "priority": 0, + "oomKillGroup": 0 + }, + "resctrlQOS": { + "enable": false, + "catRangeStartPercent": 0, + "catRangeEndPercent": 100, + "mbaPercent": 100 + } + }, + "beClass": { + "cpuQOS": { + "enable": false, + "groupIdentity": -1 + }, + "memoryQOS": { + "enable": false, + "minLimitPercent": 0, + "lowLimitPercent": 0, + "throttlingPercent": 0, + "wmarkRatio": 95, + "wmarkScalePermill": 20, + "wmarkMinAdj": 50, + "priorityEnable": 0, + "priority": 0, + "oomKillGroup": 0 + }, + "resctrlQOS": { + "enable": false, + "catRangeStartPercent": 0, + "catRangeEndPercent": 30, + "mbaPercent": 100 + } + } + }, + "nodeStrategies": [ + { + "nodeSelector": { + "matchLabels": { + "kubernetes.io/kernel": "alios" + } + }, + "beClass": { + "memoryQOS": { + "wmarkRatio": 90 + } + } + } + ] + } + resource-threshold-config: | + { + "clusterStrategy": { + "enable": false, + "cpuSuppressThresholdPercent": 65, + "cpuSuppressPolicy": "cpuset", + "memoryEvictThresholdPercent": 70 + }, + "nodeStrategies": [ + { + "nodeSelector": { + "matchLabels": { + "kubernetes.io/kernel": "alios" + } + }, + "cpuEvictBEUsageThresholdPercent": 80 + } + ] + } +kind: ConfigMap +metadata: + annotations: + meta.helm.sh/release-name: koordinator + meta.helm.sh/release-namespace: default + labels: + app.kubernetes.io/managed-by: Helm + name: slo-controller-config + namespace: koordinator-system +``` +*/ diff --git a/extension/zz_generated.deepcopy.go b/extension/zz_generated.deepcopy.go new file mode 100644 index 0000000..cb3a29b --- /dev/null +++ b/extension/zz_generated.deepcopy.go @@ -0,0 +1,335 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 2022 The Koordinator 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. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package extension + +import ( + "github.com/koordinator-sh/apis/slo/v1alpha1" + "k8s.io/apimachinery/pkg/apis/meta/v1" + timex "time" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AggregatePolicy) DeepCopyInto(out *AggregatePolicy) { + *out = *in + if in.Durations != nil { + in, out := &in.Durations, &out.Durations + *out = make([]timex.Duration, len(*in)) + copy(*out, *in) + } + if in.StatisticTypes != nil { + in, out := &in.StatisticTypes, &out.StatisticTypes + *out = make([]v1alpha1.AggregationType, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AggregatePolicy. +func (in *AggregatePolicy) DeepCopy() *AggregatePolicy { + if in == nil { + return nil + } + out := new(AggregatePolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CPUBurstCfg) DeepCopyInto(out *CPUBurstCfg) { + *out = *in + if in.ClusterStrategy != nil { + in, out := &in.ClusterStrategy, &out.ClusterStrategy + *out = new(v1alpha1.CPUBurstStrategy) + (*in).DeepCopyInto(*out) + } + if in.NodeStrategies != nil { + in, out := &in.NodeStrategies, &out.NodeStrategies + *out = make([]NodeCPUBurstCfg, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CPUBurstCfg. +func (in *CPUBurstCfg) DeepCopy() *CPUBurstCfg { + if in == nil { + return nil + } + out := new(CPUBurstCfg) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ColocationCfg) DeepCopyInto(out *ColocationCfg) { + *out = *in + in.ColocationStrategy.DeepCopyInto(&out.ColocationStrategy) + if in.NodeConfigs != nil { + in, out := &in.NodeConfigs, &out.NodeConfigs + *out = make([]NodeColocationCfg, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ColocationCfg. +func (in *ColocationCfg) DeepCopy() *ColocationCfg { + if in == nil { + return nil + } + out := new(ColocationCfg) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ColocationStrategy) DeepCopyInto(out *ColocationStrategy) { + *out = *in + if in.Enable != nil { + in, out := &in.Enable, &out.Enable + *out = new(bool) + **out = **in + } + if in.MetricAggregateDurationSeconds != nil { + in, out := &in.MetricAggregateDurationSeconds, &out.MetricAggregateDurationSeconds + *out = new(int64) + **out = **in + } + if in.MetricReportIntervalSeconds != nil { + in, out := &in.MetricReportIntervalSeconds, &out.MetricReportIntervalSeconds + *out = new(int64) + **out = **in + } + if in.MetricAggregatePolicy != nil { + in, out := &in.MetricAggregatePolicy, &out.MetricAggregatePolicy + *out = new(v1alpha1.AggregatePolicy) + (*in).DeepCopyInto(*out) + } + if in.CPUReclaimThresholdPercent != nil { + in, out := &in.CPUReclaimThresholdPercent, &out.CPUReclaimThresholdPercent + *out = new(int64) + **out = **in + } + if in.MemoryReclaimThresholdPercent != nil { + in, out := &in.MemoryReclaimThresholdPercent, &out.MemoryReclaimThresholdPercent + *out = new(int64) + **out = **in + } + if in.MemoryCalculatePolicy != nil { + in, out := &in.MemoryCalculatePolicy, &out.MemoryCalculatePolicy + *out = new(CalculatePolicy) + **out = **in + } + if in.DegradeTimeMinutes != nil { + in, out := &in.DegradeTimeMinutes, &out.DegradeTimeMinutes + *out = new(int64) + **out = **in + } + if in.UpdateTimeThresholdSeconds != nil { + in, out := &in.UpdateTimeThresholdSeconds, &out.UpdateTimeThresholdSeconds + *out = new(int64) + **out = **in + } + if in.ResourceDiffThreshold != nil { + in, out := &in.ResourceDiffThreshold, &out.ResourceDiffThreshold + *out = new(float64) + **out = **in + } + in.ColocationStrategyExtender.DeepCopyInto(&out.ColocationStrategyExtender) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ColocationStrategy. +func (in *ColocationStrategy) DeepCopy() *ColocationStrategy { + if in == nil { + return nil + } + out := new(ColocationStrategy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ColocationStrategyExtender) DeepCopyInto(out *ColocationStrategyExtender) { + *out = *in + in.Extensions.DeepCopyInto(&out.Extensions) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ColocationStrategyExtender. +func (in *ColocationStrategyExtender) DeepCopy() *ColocationStrategyExtender { + if in == nil { + return nil + } + out := new(ColocationStrategyExtender) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeCPUBurstCfg) DeepCopyInto(out *NodeCPUBurstCfg) { + *out = *in + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = new(v1.LabelSelector) + (*in).DeepCopyInto(*out) + } + if in.CPUBurstStrategy != nil { + in, out := &in.CPUBurstStrategy, &out.CPUBurstStrategy + *out = new(v1alpha1.CPUBurstStrategy) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeCPUBurstCfg. +func (in *NodeCPUBurstCfg) DeepCopy() *NodeCPUBurstCfg { + if in == nil { + return nil + } + out := new(NodeCPUBurstCfg) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeColocationCfg) DeepCopyInto(out *NodeColocationCfg) { + *out = *in + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = new(v1.LabelSelector) + (*in).DeepCopyInto(*out) + } + in.ColocationStrategy.DeepCopyInto(&out.ColocationStrategy) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeColocationCfg. +func (in *NodeColocationCfg) DeepCopy() *NodeColocationCfg { + if in == nil { + return nil + } + out := new(NodeColocationCfg) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeResourceQOSStrategy) DeepCopyInto(out *NodeResourceQOSStrategy) { + *out = *in + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = new(v1.LabelSelector) + (*in).DeepCopyInto(*out) + } + if in.ResourceQOSStrategy != nil { + in, out := &in.ResourceQOSStrategy, &out.ResourceQOSStrategy + *out = new(v1alpha1.ResourceQOSStrategy) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeResourceQOSStrategy. +func (in *NodeResourceQOSStrategy) DeepCopy() *NodeResourceQOSStrategy { + if in == nil { + return nil + } + out := new(NodeResourceQOSStrategy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeResourceThresholdStrategy) DeepCopyInto(out *NodeResourceThresholdStrategy) { + *out = *in + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = new(v1.LabelSelector) + (*in).DeepCopyInto(*out) + } + if in.ResourceThresholdStrategy != nil { + in, out := &in.ResourceThresholdStrategy, &out.ResourceThresholdStrategy + *out = new(v1alpha1.ResourceThresholdStrategy) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeResourceThresholdStrategy. +func (in *NodeResourceThresholdStrategy) DeepCopy() *NodeResourceThresholdStrategy { + if in == nil { + return nil + } + out := new(NodeResourceThresholdStrategy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceQOSCfg) DeepCopyInto(out *ResourceQOSCfg) { + *out = *in + if in.ClusterStrategy != nil { + in, out := &in.ClusterStrategy, &out.ClusterStrategy + *out = new(v1alpha1.ResourceQOSStrategy) + (*in).DeepCopyInto(*out) + } + if in.NodeStrategies != nil { + in, out := &in.NodeStrategies, &out.NodeStrategies + *out = make([]NodeResourceQOSStrategy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceQOSCfg. +func (in *ResourceQOSCfg) DeepCopy() *ResourceQOSCfg { + if in == nil { + return nil + } + out := new(ResourceQOSCfg) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceThresholdCfg) DeepCopyInto(out *ResourceThresholdCfg) { + *out = *in + if in.ClusterStrategy != nil { + in, out := &in.ClusterStrategy, &out.ClusterStrategy + *out = new(v1alpha1.ResourceThresholdStrategy) + (*in).DeepCopyInto(*out) + } + if in.NodeStrategies != nil { + in, out := &in.NodeStrategies, &out.NodeStrategies + *out = make([]NodeResourceThresholdStrategy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceThresholdCfg. +func (in *ResourceThresholdCfg) DeepCopy() *ResourceThresholdCfg { + if in == nil { + return nil + } + out := new(ResourceThresholdCfg) + in.DeepCopyInto(out) + return out +}