diff --git a/OWNERS_ALIASES b/OWNERS_ALIASES index b550682ef..386942ae3 100644 --- a/OWNERS_ALIASES +++ b/OWNERS_ALIASES @@ -34,19 +34,7 @@ aliases: - FillZpp - eahydra - yarn-approvers: + prediction-approvers: - zwzhang0107 - saintube - yarn-controller-approvers: - - zwzhang0107 - - saintube - - FillZpp - - songzh215 - - yarn-copilot-approvers: - - zwzhang0107 - - saintube - - FillZpp - - songzh215 - diff --git a/apis/analysis/v1alpha1/condition.go b/apis/analysis/v1alpha1/condition.go new file mode 100644 index 000000000..39e35d6e3 --- /dev/null +++ b/apis/analysis/v1alpha1/condition.go @@ -0,0 +1,32 @@ +/* +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 v1alpha1 + +const ( + // LowConfidenceCondition indicates the low confidence for the current forecasting result. + LowConfidenceCondition string = "LowConfidence" + // NoObjectsMatchedCondition indicates that the current description didn't match any objects. + NoObjectsMatchedCondition string = "NoObjectsMatched" + // FetchingHistoryCondition indicates that forecaster is in the process of loading additional + // history samples. + FetchingHistoryCondition string = "FetchingHistory" + // ConfigDeprecatedCondition indicates that this configuration is deprecated and will stop being + // supported soon. + ConfigDeprecatedCondition string = "ConfigDeprecated" + // ConfigUnsupportedCondition indicates that this configuration is unsupported and will not be provided for it. + ConfigUnsupportedCondition string = "ConfigUnsupported" +) diff --git a/apis/analysis/v1alpha1/groupversion_info.go b/apis/analysis/v1alpha1/groupversion_info.go new file mode 100644 index 000000000..b86725533 --- /dev/null +++ b/apis/analysis/v1alpha1/groupversion_info.go @@ -0,0 +1,46 @@ +/* +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 v1alpha1 contains API Schema definitions for the analysis v1alpha1 API group +// +kubebuilder:object:generate=true +// +groupName=analysis.koordinator.sh +package v1alpha1 + +import ( + "github.com/koordinator-sh/koordinator/apis/scheme" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "analysis.koordinator.sh", Version: "v1alpha1"} + + SchemeGroupVersion = GroupVersion + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme + + // Kind is the kind of crd. + RecommendationKind = "Recommendation" +) + +// Resource is required by pkg/client/listers/... +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} diff --git a/apis/analysis/v1alpha1/recommendation_types.go b/apis/analysis/v1alpha1/recommendation_types.go new file mode 100644 index 000000000..d422fe4b2 --- /dev/null +++ b/apis/analysis/v1alpha1/recommendation_types.go @@ -0,0 +1,117 @@ +/* +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 v1alpha1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// RecommendationTargetType defines the type of analysis target +type RecommendationTargetType string + +const ( + // RecommendationTargetWorkload defines the k8s workload type + RecommendationTargetWorkload RecommendationTargetType = "workload" + // RecommendationPodSelector defines the pod selector type + RecommendationPodSelector RecommendationTargetType = "podSelector" +) + +// RecommendationTarget defines the target of analysis, which can be a k8s workload or a series of pods +type RecommendationTarget struct { + // Type indicates the type of target + Type RecommendationTargetType `json:"type"` + // Workload indicates the target is a k8s workload, which is effective when Type is "workload" + Workload *CrossVersionObjectReference `json:"workload,omitempty"` + // PodSelector defines the reference of a series of pods, which is effective when Type is "podSelector" + PodSelector *metav1.LabelSelector `json:"podSelector,omitempty"` +} + +// CrossVersionObjectReference contains enough information to let you identify the referred resource. +type CrossVersionObjectReference struct { + // Kind of the referent; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + Kind string `json:"kind"` + // Name of the referent; More info: http://kubernetes.io/docs/user-guide/identifiers#names + Name string `json:"name"` + // Namespace of the referent. + Namespace string `json:"namespace"` + // API version of the referent + APIVersion string `json:"apiVersion,omitempty"` +} + +// RecommendationSpec is the specification of the client object. +type RecommendationSpec struct { + // Target is the object to be analyzed, which can be a workload or a series of pods + Target RecommendationTarget `json:"target"` + // TODO add more tuning knobs about aggregation policy +} + +// RecommendedPodStatus defines the observed state of pod +type RecommendedPodStatus struct { + // ContainerStatuses records the most recently computed amount of resources recommended + ContainerStatuses []RecommendedContainerStatus `json:"containerStatuses,omitempty"` +} + +// RecommendedContainerStatus defines the observed state of container +type RecommendedContainerStatus struct { + // Name of the container. + ContainerName string `json:"containerName,omitempty"` + // Recommended resources of container + Resources corev1.ResourceList `json:"resources,omitempty"` +} + +// RecommendationStatus defines the observed state of Recommendation +type RecommendationStatus struct { + // PodStatus records the most recently computed amount of resources recommended + PodStatus *RecommendedPodStatus `json:"podStatus,omitempty"` + // UpdateTime is the update time of the distribution + UpdateTime *metav1.Time `json:"updateTime,omitempty"` + // Conditions is the list of conditions representing the status of the distribution + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// +genclient +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:resource:shortName=recom +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:printcolumn:name="Type",type="string",JSONPath=".spec.target.type" +// +kubebuilder:printcolumn:name="Kind",type="string",JSONPath=".spec.target.workload.kind" +// +kubebuilder:printcolumn:name="Name",type="string",JSONPath=".spec.target.workload.name" +// +kubebuilder:printcolumn:name="LastUpdateTime",type="date",JSONPath=".status.updateTime" + +// Recommendation is the Schema for the recommendations API +type Recommendation struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec RecommendationSpec `json:"spec,omitempty"` + Status RecommendationStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// RecommendationList contains a list of Recommendation +type RecommendationList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Recommendation `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Recommendation{}, &RecommendationList{}) +} diff --git a/apis/analysis/v1alpha1/zz_generated.deepcopy.go b/apis/analysis/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 000000000..24dd7caf9 --- /dev/null +++ b/apis/analysis/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,218 @@ +//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 v1alpha1 + +import ( + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CrossVersionObjectReference) DeepCopyInto(out *CrossVersionObjectReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CrossVersionObjectReference. +func (in *CrossVersionObjectReference) DeepCopy() *CrossVersionObjectReference { + if in == nil { + return nil + } + out := new(CrossVersionObjectReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Recommendation) DeepCopyInto(out *Recommendation) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Recommendation. +func (in *Recommendation) DeepCopy() *Recommendation { + if in == nil { + return nil + } + out := new(Recommendation) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Recommendation) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RecommendationList) DeepCopyInto(out *RecommendationList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Recommendation, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RecommendationList. +func (in *RecommendationList) DeepCopy() *RecommendationList { + if in == nil { + return nil + } + out := new(RecommendationList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RecommendationList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RecommendationSpec) DeepCopyInto(out *RecommendationSpec) { + *out = *in + in.Target.DeepCopyInto(&out.Target) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RecommendationSpec. +func (in *RecommendationSpec) DeepCopy() *RecommendationSpec { + if in == nil { + return nil + } + out := new(RecommendationSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RecommendationStatus) DeepCopyInto(out *RecommendationStatus) { + *out = *in + if in.PodStatus != nil { + in, out := &in.PodStatus, &out.PodStatus + *out = new(RecommendedPodStatus) + (*in).DeepCopyInto(*out) + } + if in.UpdateTime != nil { + in, out := &in.UpdateTime, &out.UpdateTime + *out = (*in).DeepCopy() + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RecommendationStatus. +func (in *RecommendationStatus) DeepCopy() *RecommendationStatus { + if in == nil { + return nil + } + out := new(RecommendationStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RecommendationTarget) DeepCopyInto(out *RecommendationTarget) { + *out = *in + if in.Workload != nil { + in, out := &in.Workload, &out.Workload + *out = new(CrossVersionObjectReference) + **out = **in + } + if in.PodSelector != nil { + in, out := &in.PodSelector, &out.PodSelector + *out = new(v1.LabelSelector) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RecommendationTarget. +func (in *RecommendationTarget) DeepCopy() *RecommendationTarget { + if in == nil { + return nil + } + out := new(RecommendationTarget) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RecommendedContainerStatus) DeepCopyInto(out *RecommendedContainerStatus) { + *out = *in + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = make(corev1.ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RecommendedContainerStatus. +func (in *RecommendedContainerStatus) DeepCopy() *RecommendedContainerStatus { + if in == nil { + return nil + } + out := new(RecommendedContainerStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RecommendedPodStatus) DeepCopyInto(out *RecommendedPodStatus) { + *out = *in + if in.ContainerStatuses != nil { + in, out := &in.ContainerStatuses, &out.ContainerStatuses + *out = make([]RecommendedContainerStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RecommendedPodStatus. +func (in *RecommendedPodStatus) DeepCopy() *RecommendedPodStatus { + if in == nil { + return nil + } + out := new(RecommendedPodStatus) + in.DeepCopyInto(out) + return out +} diff --git a/cmd/koord-manager/options/controllers.go b/cmd/koord-manager/options/controllers.go index 59fd1309e..dc3fc7259 100644 --- a/cmd/koord-manager/options/controllers.go +++ b/cmd/koord-manager/options/controllers.go @@ -21,6 +21,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/manager" + prediction "github.com/koordinator-sh/koordinator/pkg/prediction/frontend" "github.com/koordinator-sh/koordinator/pkg/quota-controller/profile" "github.com/koordinator-sh/koordinator/pkg/slo-controller/nodemetric" "github.com/koordinator-sh/koordinator/pkg/slo-controller/noderesource" @@ -36,4 +37,5 @@ var controllerAddFuncs = map[string]func(manager.Manager) error{ noderesource.Name: noderesource.Add, nodeslo.Name: nodeslo.Add, profile.Name: profile.Add, + prediction.Name: prediction.Add, } diff --git a/cmd/koord-predict/main.go b/cmd/koord-predict/main.go new file mode 100644 index 000000000..92408cb60 --- /dev/null +++ b/cmd/koord-predict/main.go @@ -0,0 +1,89 @@ +package main + +import ( + "flag" + "os" + "sync" + "time" + + analysisv1alpha1 "github.com/koordinator-sh/koordinator/apis/analysis/v1alpha1" + analysis_clientset "github.com/koordinator-sh/koordinator/pkg/client/clientset/versioned" + "github.com/koordinator-sh/koordinator/pkg/prediction/frontend" + "github.com/koordinator-sh/koordinator/pkg/prediction/manager" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/wait" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + restclient "k8s.io/client-go/rest" + "k8s.io/klog/v2" + "k8s.io/kubernetes/pkg/apis/batch" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/manager/signals" + // +kubebuilder:scaffold:imports +) + +var ( + scheme = runtime.NewScheme() + setupLog = ctrl.Log.WithName("predict-setup") +) + +func init() { + _ = clientgoscheme.AddToScheme(scheme) + _ = batch.AddToScheme(scheme) + _ = analysisv1alpha1.AddToScheme(scheme) +} + +var predictImpl *manager.PredictionMgrImpl + +func main() { + flag.Parse() + + cfg, err := restclient.InClusterConfig() + if err != nil { + setupLog.Error(err, "problem get rest client") + } + + go wait.Forever(klog.Flush, 5*time.Second) + defer klog.Flush() + + stopCtx := signals.SetupSignalHandler() + wg := &sync.WaitGroup{} + + //init manager + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{Scheme: scheme}) + if err != nil { + setupLog.Error(err, "unable to start predict manager") + os.Exit(1) + } + + //start mgr + if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { + setupLog.Error(err, "problem running predict manager") + os.Exit(1) + } + + predictImpl = manager.InitPredictMgr() + //register crd reconcile to manager + frontend.Add(mgr, predictImpl) + + client := analysis_clientset.NewForConfigOrDie(cfg).AnalysisV1alpha1() + fetcher := frontend.InitStatusFetcher(client, stopCtx, predictImpl) + setupLog.Info("starting status fetcher") + //status fetcher start + wg.Add(1) + go func() { + fetcher.Run() + wg.Done() + }() + + //predictionImpl Start + setupLog.Info("starting predict manager") + wg.Add(1) + go func() { + predictImpl.Run() + wg.Done() + }() + + <-stopCtx.Done() + wg.Wait() + // +kubebuilder:scaffold:builder +} diff --git a/config/crd/bases/analysis.koordinator.sh_recommendations.yaml b/config/crd/bases/analysis.koordinator.sh_recommendations.yaml new file mode 100644 index 000000000..38b6c6460 --- /dev/null +++ b/config/crd/bases/analysis.koordinator.sh_recommendations.yaml @@ -0,0 +1,239 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.0 + creationTimestamp: null + name: recommendations.analysis.koordinator.sh +spec: + group: analysis.koordinator.sh + names: + kind: Recommendation + listKind: RecommendationList + plural: recommendations + shortNames: + - recom + singular: recommendation + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .spec.target.type + name: Type + type: string + - jsonPath: .spec.target.workload.kind + name: Kind + type: string + - jsonPath: .spec.target.workload.name + name: Name + type: string + - jsonPath: .status.updateTime + name: LastUpdateTime + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: Recommendation is the Schema for the recommendations API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: RecommendationSpec is the specification of the client object. + properties: + target: + description: Target is the object to be analyzed, which can be a workload + or a series of pods + properties: + podSelector: + description: PodSelector defines the reference of a series of + pods, which is effective when Type is "podSelector" + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, + Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If + the operator is In or NotIn, the values array must + be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A + single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is "key", + the operator is "In", and the values array contains only + "value". The requirements are ANDed. + type: object + type: object + type: + description: Type indicates the type of target + type: string + workload: + description: Workload indicates the target is a k8s workload, + which is effective when Type is "workload" + properties: + apiVersion: + description: API version of the referent + type: string + kind: + description: 'Kind of the referent; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds"' + type: string + name: + description: 'Name of the referent; More info: http://kubernetes.io/docs/user-guide/identifiers#names' + type: string + required: + - kind + - name + type: object + required: + - type + type: object + required: + - target + type: object + status: + description: RecommendationStatus defines the observed state of Recommendation + properties: + conditions: + description: Conditions is the list of conditions representing the + status of the distribution + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + podStatus: + description: PodStatus records the most recently computed amount of + resources recommended + properties: + containerStatuses: + description: ContainerStatuses records the most recently computed + amount of resources recommended + items: + description: RecommendedContainerStatus defines the observed + state of container + properties: + containerName: + description: Name of the container. + type: string + resources: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Recommended resources of container + type: object + type: object + type: array + type: object + updateTime: + description: UpdateTime is the update time of the distribution + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 9aaafedbc..b0f97a0fa 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -12,6 +12,7 @@ resources: - bases/scheduling.sigs.k8s.io_podgroups.yaml - bases/topology.node.k8s.io_noderesourcetopologies.yaml - bases/quota.koordinator.sh_elasticquotaprofiles.yaml +- bases/analysis.koordinator.sh_recommendations.yaml #+kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: diff --git a/config/samples/analysis_v1aplha1_recommendation.yaml b/config/samples/analysis_v1aplha1_recommendation.yaml new file mode 100644 index 000000000..6afbeb4b7 --- /dev/null +++ b/config/samples/analysis_v1aplha1_recommendation.yaml @@ -0,0 +1,20 @@ +apiVersion: analysis.koordinator.sh/v1alpha1 +kind: Recommendation +metadata: + namespace: default + name: recommendation-sample +spec: + target: + type: workload + workload: + apiVersion: apps/v1 + kind: Deployment + name: nginx-sample-deployment +status: + podStatus: + containerStatuses: + - containerName: nginx + resources: + cpu: 1200m + memory: 1280Mi + updateTime: "2024-03-05T08:00:00Z" diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index d3ad71b25..2b93e473e 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -47,7 +47,7 @@ echo ">> Temporary output directory ${TEMP_DIR}" # instead of the $GOPATH directly. For normal projects this can be dropped. $SCRIPT_ROOT/hack/generate-groups.sh "client,informer,lister" \ github.com/koordinator-sh/koordinator/pkg/client github.com/koordinator-sh/koordinator/apis \ - "config:v1alpha1 slo:v1alpha1 scheduling:v1alpha1 quota:v1alpha1" \ + "analysis:v1alpha1 config:v1alpha1 slo:v1alpha1 scheduling:v1alpha1 quota:v1alpha1" \ --output-base "${TEMP_DIR}" \ --go-header-file hack/boilerplate/boilerplate.go.txt @@ -76,6 +76,7 @@ function custom_sed(){ perl -i -pe $@ } +custom_sed 's#\"analysis\"#\"config.koordinator.sh\"#g' ./pkg/client/clientset/versioned/typed/analysis/v1alpha1/fake/fake_*.go custom_sed 's#\"config\"#\"config.koordinator.sh\"#g' ./pkg/client/clientset/versioned/typed/config/v1alpha1/fake/fake_*.go custom_sed 's#\"slo\"#\"slo.koordinator.sh\"#g' ./pkg/client/clientset/versioned/typed/slo/v1alpha1/fake/fake_*.go custom_sed 's#\"scheduling\"#\"scheduling.koordinator.sh\"#g' ./pkg/client/clientset/versioned/typed/scheduling/v1alpha1/fake/fake_*.go diff --git a/pkg/client/clientset/versioned/clientset.go b/pkg/client/clientset/versioned/clientset.go index 0d6705fa7..c53d57782 100644 --- a/pkg/client/clientset/versioned/clientset.go +++ b/pkg/client/clientset/versioned/clientset.go @@ -22,6 +22,7 @@ import ( "fmt" "net/http" + analysisv1alpha1 "github.com/koordinator-sh/koordinator/pkg/client/clientset/versioned/typed/analysis/v1alpha1" configv1alpha1 "github.com/koordinator-sh/koordinator/pkg/client/clientset/versioned/typed/config/v1alpha1" quotav1alpha1 "github.com/koordinator-sh/koordinator/pkg/client/clientset/versioned/typed/quota/v1alpha1" schedulingv1alpha1 "github.com/koordinator-sh/koordinator/pkg/client/clientset/versioned/typed/scheduling/v1alpha1" @@ -33,6 +34,7 @@ import ( type Interface interface { Discovery() discovery.DiscoveryInterface + AnalysisV1alpha1() analysisv1alpha1.AnalysisV1alpha1Interface ConfigV1alpha1() configv1alpha1.ConfigV1alpha1Interface QuotaV1alpha1() quotav1alpha1.QuotaV1alpha1Interface SchedulingV1alpha1() schedulingv1alpha1.SchedulingV1alpha1Interface @@ -43,12 +45,18 @@ type Interface interface { // version included in a Clientset. type Clientset struct { *discovery.DiscoveryClient + analysisV1alpha1 *analysisv1alpha1.AnalysisV1alpha1Client configV1alpha1 *configv1alpha1.ConfigV1alpha1Client quotaV1alpha1 *quotav1alpha1.QuotaV1alpha1Client schedulingV1alpha1 *schedulingv1alpha1.SchedulingV1alpha1Client sloV1alpha1 *slov1alpha1.SloV1alpha1Client } +// AnalysisV1alpha1 retrieves the AnalysisV1alpha1Client +func (c *Clientset) AnalysisV1alpha1() analysisv1alpha1.AnalysisV1alpha1Interface { + return c.analysisV1alpha1 +} + // ConfigV1alpha1 retrieves the ConfigV1alpha1Client func (c *Clientset) ConfigV1alpha1() configv1alpha1.ConfigV1alpha1Interface { return c.configV1alpha1 @@ -113,6 +121,10 @@ func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, var cs Clientset var err error + cs.analysisV1alpha1, err = analysisv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient) + if err != nil { + return nil, err + } cs.configV1alpha1, err = configv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient) if err != nil { return nil, err @@ -150,6 +162,7 @@ func NewForConfigOrDie(c *rest.Config) *Clientset { // New creates a new Clientset for the given RESTClient. func New(c rest.Interface) *Clientset { var cs Clientset + cs.analysisV1alpha1 = analysisv1alpha1.New(c) cs.configV1alpha1 = configv1alpha1.New(c) cs.quotaV1alpha1 = quotav1alpha1.New(c) cs.schedulingV1alpha1 = schedulingv1alpha1.New(c) diff --git a/pkg/client/clientset/versioned/fake/clientset_generated.go b/pkg/client/clientset/versioned/fake/clientset_generated.go index 1e12ba4ff..d30048766 100644 --- a/pkg/client/clientset/versioned/fake/clientset_generated.go +++ b/pkg/client/clientset/versioned/fake/clientset_generated.go @@ -20,6 +20,8 @@ package fake import ( clientset "github.com/koordinator-sh/koordinator/pkg/client/clientset/versioned" + analysisv1alpha1 "github.com/koordinator-sh/koordinator/pkg/client/clientset/versioned/typed/analysis/v1alpha1" + fakeanalysisv1alpha1 "github.com/koordinator-sh/koordinator/pkg/client/clientset/versioned/typed/analysis/v1alpha1/fake" configv1alpha1 "github.com/koordinator-sh/koordinator/pkg/client/clientset/versioned/typed/config/v1alpha1" fakeconfigv1alpha1 "github.com/koordinator-sh/koordinator/pkg/client/clientset/versioned/typed/config/v1alpha1/fake" quotav1alpha1 "github.com/koordinator-sh/koordinator/pkg/client/clientset/versioned/typed/quota/v1alpha1" @@ -85,6 +87,11 @@ var ( _ testing.FakeClient = &Clientset{} ) +// AnalysisV1alpha1 retrieves the AnalysisV1alpha1Client +func (c *Clientset) AnalysisV1alpha1() analysisv1alpha1.AnalysisV1alpha1Interface { + return &fakeanalysisv1alpha1.FakeAnalysisV1alpha1{Fake: &c.Fake} +} + // ConfigV1alpha1 retrieves the ConfigV1alpha1Client func (c *Clientset) ConfigV1alpha1() configv1alpha1.ConfigV1alpha1Interface { return &fakeconfigv1alpha1.FakeConfigV1alpha1{Fake: &c.Fake} diff --git a/pkg/client/clientset/versioned/fake/register.go b/pkg/client/clientset/versioned/fake/register.go index 856389a42..7e9537883 100644 --- a/pkg/client/clientset/versioned/fake/register.go +++ b/pkg/client/clientset/versioned/fake/register.go @@ -19,6 +19,7 @@ limitations under the License. package fake import ( + analysisv1alpha1 "github.com/koordinator-sh/koordinator/apis/analysis/v1alpha1" configv1alpha1 "github.com/koordinator-sh/koordinator/apis/config/v1alpha1" quotav1alpha1 "github.com/koordinator-sh/koordinator/apis/quota/v1alpha1" schedulingv1alpha1 "github.com/koordinator-sh/koordinator/apis/scheduling/v1alpha1" @@ -34,6 +35,7 @@ var scheme = runtime.NewScheme() var codecs = serializer.NewCodecFactory(scheme) var localSchemeBuilder = runtime.SchemeBuilder{ + analysisv1alpha1.AddToScheme, configv1alpha1.AddToScheme, quotav1alpha1.AddToScheme, schedulingv1alpha1.AddToScheme, diff --git a/pkg/client/clientset/versioned/scheme/register.go b/pkg/client/clientset/versioned/scheme/register.go index d9ff69ce3..3600010d6 100644 --- a/pkg/client/clientset/versioned/scheme/register.go +++ b/pkg/client/clientset/versioned/scheme/register.go @@ -19,6 +19,7 @@ limitations under the License. package scheme import ( + analysisv1alpha1 "github.com/koordinator-sh/koordinator/apis/analysis/v1alpha1" configv1alpha1 "github.com/koordinator-sh/koordinator/apis/config/v1alpha1" quotav1alpha1 "github.com/koordinator-sh/koordinator/apis/quota/v1alpha1" schedulingv1alpha1 "github.com/koordinator-sh/koordinator/apis/scheduling/v1alpha1" @@ -34,6 +35,7 @@ var Scheme = runtime.NewScheme() var Codecs = serializer.NewCodecFactory(Scheme) var ParameterCodec = runtime.NewParameterCodec(Scheme) var localSchemeBuilder = runtime.SchemeBuilder{ + analysisv1alpha1.AddToScheme, configv1alpha1.AddToScheme, quotav1alpha1.AddToScheme, schedulingv1alpha1.AddToScheme, diff --git a/pkg/client/clientset/versioned/typed/analysis/v1alpha1/analysis_client.go b/pkg/client/clientset/versioned/typed/analysis/v1alpha1/analysis_client.go new file mode 100644 index 000000000..7959ff8ab --- /dev/null +++ b/pkg/client/clientset/versioned/typed/analysis/v1alpha1/analysis_client.go @@ -0,0 +1,107 @@ +/* +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 client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "net/http" + + v1alpha1 "github.com/koordinator-sh/koordinator/apis/analysis/v1alpha1" + "github.com/koordinator-sh/koordinator/pkg/client/clientset/versioned/scheme" + rest "k8s.io/client-go/rest" +) + +type AnalysisV1alpha1Interface interface { + RESTClient() rest.Interface + RecommendationsGetter +} + +// AnalysisV1alpha1Client is used to interact with features provided by the analysis group. +type AnalysisV1alpha1Client struct { + restClient rest.Interface +} + +func (c *AnalysisV1alpha1Client) Recommendations(namespace string) RecommendationInterface { + return newRecommendations(c, namespace) +} + +// NewForConfig creates a new AnalysisV1alpha1Client for the given config. +// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), +// where httpClient was generated with rest.HTTPClientFor(c). +func NewForConfig(c *rest.Config) (*AnalysisV1alpha1Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + httpClient, err := rest.HTTPClientFor(&config) + if err != nil { + return nil, err + } + return NewForConfigAndClient(&config, httpClient) +} + +// NewForConfigAndClient creates a new AnalysisV1alpha1Client for the given config and http client. +// Note the http client provided takes precedence over the configured transport values. +func NewForConfigAndClient(c *rest.Config, h *http.Client) (*AnalysisV1alpha1Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := rest.RESTClientForConfigAndClient(&config, h) + if err != nil { + return nil, err + } + return &AnalysisV1alpha1Client{client}, nil +} + +// NewForConfigOrDie creates a new AnalysisV1alpha1Client for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *AnalysisV1alpha1Client { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new AnalysisV1alpha1Client for the given RESTClient. +func New(c rest.Interface) *AnalysisV1alpha1Client { + return &AnalysisV1alpha1Client{c} +} + +func setConfigDefaults(config *rest.Config) error { + gv := v1alpha1.SchemeGroupVersion + config.GroupVersion = &gv + config.APIPath = "/apis" + config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() + + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } + + return nil +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *AnalysisV1alpha1Client) RESTClient() rest.Interface { + if c == nil { + return nil + } + return c.restClient +} diff --git a/pkg/client/clientset/versioned/typed/analysis/v1alpha1/doc.go b/pkg/client/clientset/versioned/typed/analysis/v1alpha1/doc.go new file mode 100644 index 000000000..28802f366 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/analysis/v1alpha1/doc.go @@ -0,0 +1,20 @@ +/* +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 client-gen. DO NOT EDIT. + +// This package has the automatically generated typed clients. +package v1alpha1 diff --git a/pkg/client/clientset/versioned/typed/analysis/v1alpha1/fake/doc.go b/pkg/client/clientset/versioned/typed/analysis/v1alpha1/fake/doc.go new file mode 100644 index 000000000..a537e15bf --- /dev/null +++ b/pkg/client/clientset/versioned/typed/analysis/v1alpha1/fake/doc.go @@ -0,0 +1,20 @@ +/* +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 client-gen. DO NOT EDIT. + +// Package fake has the automatically generated clients. +package fake diff --git a/pkg/client/clientset/versioned/typed/analysis/v1alpha1/fake/fake_analysis_client.go b/pkg/client/clientset/versioned/typed/analysis/v1alpha1/fake/fake_analysis_client.go new file mode 100644 index 000000000..441103d3d --- /dev/null +++ b/pkg/client/clientset/versioned/typed/analysis/v1alpha1/fake/fake_analysis_client.go @@ -0,0 +1,40 @@ +/* +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 client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha1 "github.com/koordinator-sh/koordinator/pkg/client/clientset/versioned/typed/analysis/v1alpha1" + rest "k8s.io/client-go/rest" + testing "k8s.io/client-go/testing" +) + +type FakeAnalysisV1alpha1 struct { + *testing.Fake +} + +func (c *FakeAnalysisV1alpha1) Recommendations(namespace string) v1alpha1.RecommendationInterface { + return &FakeRecommendations{c, namespace} +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeAnalysisV1alpha1) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/pkg/client/clientset/versioned/typed/analysis/v1alpha1/fake/fake_recommendation.go b/pkg/client/clientset/versioned/typed/analysis/v1alpha1/fake/fake_recommendation.go new file mode 100644 index 000000000..dfc543373 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/analysis/v1alpha1/fake/fake_recommendation.go @@ -0,0 +1,142 @@ +/* +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 client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "github.com/koordinator-sh/koordinator/apis/analysis/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeRecommendations implements RecommendationInterface +type FakeRecommendations struct { + Fake *FakeAnalysisV1alpha1 + ns string +} + +var recommendationsResource = schema.GroupVersionResource{Group: "config.koordinator.sh", Version: "v1alpha1", Resource: "recommendations"} + +var recommendationsKind = schema.GroupVersionKind{Group: "config.koordinator.sh", Version: "v1alpha1", Kind: "Recommendation"} + +// Get takes name of the recommendation, and returns the corresponding recommendation object, and an error if there is any. +func (c *FakeRecommendations) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.Recommendation, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(recommendationsResource, c.ns, name), &v1alpha1.Recommendation{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Recommendation), err +} + +// List takes label and field selectors, and returns the list of Recommendations that match those selectors. +func (c *FakeRecommendations) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.RecommendationList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(recommendationsResource, recommendationsKind, c.ns, opts), &v1alpha1.RecommendationList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.RecommendationList{ListMeta: obj.(*v1alpha1.RecommendationList).ListMeta} + for _, item := range obj.(*v1alpha1.RecommendationList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested recommendations. +func (c *FakeRecommendations) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(recommendationsResource, c.ns, opts)) + +} + +// Create takes the representation of a recommendation and creates it. Returns the server's representation of the recommendation, and an error, if there is any. +func (c *FakeRecommendations) Create(ctx context.Context, recommendation *v1alpha1.Recommendation, opts v1.CreateOptions) (result *v1alpha1.Recommendation, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(recommendationsResource, c.ns, recommendation), &v1alpha1.Recommendation{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Recommendation), err +} + +// Update takes the representation of a recommendation and updates it. Returns the server's representation of the recommendation, and an error, if there is any. +func (c *FakeRecommendations) Update(ctx context.Context, recommendation *v1alpha1.Recommendation, opts v1.UpdateOptions) (result *v1alpha1.Recommendation, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(recommendationsResource, c.ns, recommendation), &v1alpha1.Recommendation{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Recommendation), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeRecommendations) UpdateStatus(ctx context.Context, recommendation *v1alpha1.Recommendation, opts v1.UpdateOptions) (*v1alpha1.Recommendation, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(recommendationsResource, "status", c.ns, recommendation), &v1alpha1.Recommendation{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Recommendation), err +} + +// Delete takes name of the recommendation and deletes it. Returns an error if one occurs. +func (c *FakeRecommendations) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(recommendationsResource, c.ns, name, opts), &v1alpha1.Recommendation{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeRecommendations) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(recommendationsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.RecommendationList{}) + return err +} + +// Patch applies the patch and returns the patched recommendation. +func (c *FakeRecommendations) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.Recommendation, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(recommendationsResource, c.ns, name, pt, data, subresources...), &v1alpha1.Recommendation{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Recommendation), err +} diff --git a/pkg/client/clientset/versioned/typed/analysis/v1alpha1/generated_expansion.go b/pkg/client/clientset/versioned/typed/analysis/v1alpha1/generated_expansion.go new file mode 100644 index 000000000..46457b2b9 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/analysis/v1alpha1/generated_expansion.go @@ -0,0 +1,21 @@ +/* +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 client-gen. DO NOT EDIT. + +package v1alpha1 + +type RecommendationExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/analysis/v1alpha1/recommendation.go b/pkg/client/clientset/versioned/typed/analysis/v1alpha1/recommendation.go new file mode 100644 index 000000000..4f6969b4a --- /dev/null +++ b/pkg/client/clientset/versioned/typed/analysis/v1alpha1/recommendation.go @@ -0,0 +1,195 @@ +/* +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 client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "github.com/koordinator-sh/koordinator/apis/analysis/v1alpha1" + scheme "github.com/koordinator-sh/koordinator/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// RecommendationsGetter has a method to return a RecommendationInterface. +// A group's client should implement this interface. +type RecommendationsGetter interface { + Recommendations(namespace string) RecommendationInterface +} + +// RecommendationInterface has methods to work with Recommendation resources. +type RecommendationInterface interface { + Create(ctx context.Context, recommendation *v1alpha1.Recommendation, opts v1.CreateOptions) (*v1alpha1.Recommendation, error) + Update(ctx context.Context, recommendation *v1alpha1.Recommendation, opts v1.UpdateOptions) (*v1alpha1.Recommendation, error) + UpdateStatus(ctx context.Context, recommendation *v1alpha1.Recommendation, opts v1.UpdateOptions) (*v1alpha1.Recommendation, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.Recommendation, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.RecommendationList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.Recommendation, err error) + RecommendationExpansion +} + +// recommendations implements RecommendationInterface +type recommendations struct { + client rest.Interface + ns string +} + +// newRecommendations returns a Recommendations +func newRecommendations(c *AnalysisV1alpha1Client, namespace string) *recommendations { + return &recommendations{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the recommendation, and returns the corresponding recommendation object, and an error if there is any. +func (c *recommendations) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.Recommendation, err error) { + result = &v1alpha1.Recommendation{} + err = c.client.Get(). + Namespace(c.ns). + Resource("recommendations"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Recommendations that match those selectors. +func (c *recommendations) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.RecommendationList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.RecommendationList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("recommendations"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested recommendations. +func (c *recommendations) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("recommendations"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a recommendation and creates it. Returns the server's representation of the recommendation, and an error, if there is any. +func (c *recommendations) Create(ctx context.Context, recommendation *v1alpha1.Recommendation, opts v1.CreateOptions) (result *v1alpha1.Recommendation, err error) { + result = &v1alpha1.Recommendation{} + err = c.client.Post(). + Namespace(c.ns). + Resource("recommendations"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(recommendation). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a recommendation and updates it. Returns the server's representation of the recommendation, and an error, if there is any. +func (c *recommendations) Update(ctx context.Context, recommendation *v1alpha1.Recommendation, opts v1.UpdateOptions) (result *v1alpha1.Recommendation, err error) { + result = &v1alpha1.Recommendation{} + err = c.client.Put(). + Namespace(c.ns). + Resource("recommendations"). + Name(recommendation.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(recommendation). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *recommendations) UpdateStatus(ctx context.Context, recommendation *v1alpha1.Recommendation, opts v1.UpdateOptions) (result *v1alpha1.Recommendation, err error) { + result = &v1alpha1.Recommendation{} + err = c.client.Put(). + Namespace(c.ns). + Resource("recommendations"). + Name(recommendation.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(recommendation). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the recommendation and deletes it. Returns an error if one occurs. +func (c *recommendations) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("recommendations"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *recommendations) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("recommendations"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched recommendation. +func (c *recommendations) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.Recommendation, err error) { + result = &v1alpha1.Recommendation{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("recommendations"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/informers/externalversions/analysis/interface.go b/pkg/client/informers/externalversions/analysis/interface.go new file mode 100644 index 000000000..7976ee3cf --- /dev/null +++ b/pkg/client/informers/externalversions/analysis/interface.go @@ -0,0 +1,46 @@ +/* +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 informer-gen. DO NOT EDIT. + +package analysis + +import ( + v1alpha1 "github.com/koordinator-sh/koordinator/pkg/client/informers/externalversions/analysis/v1alpha1" + internalinterfaces "github.com/koordinator-sh/koordinator/pkg/client/informers/externalversions/internalinterfaces" +) + +// Interface provides access to each of this group's versions. +type Interface interface { + // V1alpha1 provides access to shared informers for resources in V1alpha1. + V1alpha1() v1alpha1.Interface +} + +type group struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// V1alpha1 returns a new v1alpha1.Interface. +func (g *group) V1alpha1() v1alpha1.Interface { + return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions) +} diff --git a/pkg/client/informers/externalversions/analysis/v1alpha1/interface.go b/pkg/client/informers/externalversions/analysis/v1alpha1/interface.go new file mode 100644 index 000000000..7162d7da7 --- /dev/null +++ b/pkg/client/informers/externalversions/analysis/v1alpha1/interface.go @@ -0,0 +1,45 @@ +/* +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 informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + internalinterfaces "github.com/koordinator-sh/koordinator/pkg/client/informers/externalversions/internalinterfaces" +) + +// Interface provides access to all the informers in this group version. +type Interface interface { + // Recommendations returns a RecommendationInformer. + Recommendations() RecommendationInformer +} + +type version struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// Recommendations returns a RecommendationInformer. +func (v *version) Recommendations() RecommendationInformer { + return &recommendationInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} diff --git a/pkg/client/informers/externalversions/analysis/v1alpha1/recommendation.go b/pkg/client/informers/externalversions/analysis/v1alpha1/recommendation.go new file mode 100644 index 000000000..7e94e4fd3 --- /dev/null +++ b/pkg/client/informers/externalversions/analysis/v1alpha1/recommendation.go @@ -0,0 +1,90 @@ +/* +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 informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + analysisv1alpha1 "github.com/koordinator-sh/koordinator/apis/analysis/v1alpha1" + versioned "github.com/koordinator-sh/koordinator/pkg/client/clientset/versioned" + internalinterfaces "github.com/koordinator-sh/koordinator/pkg/client/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/koordinator-sh/koordinator/pkg/client/listers/analysis/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// RecommendationInformer provides access to a shared informer and lister for +// Recommendations. +type RecommendationInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.RecommendationLister +} + +type recommendationInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewRecommendationInformer constructs a new informer for Recommendation type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewRecommendationInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredRecommendationInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredRecommendationInformer constructs a new informer for Recommendation type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredRecommendationInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.AnalysisV1alpha1().Recommendations(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.AnalysisV1alpha1().Recommendations(namespace).Watch(context.TODO(), options) + }, + }, + &analysisv1alpha1.Recommendation{}, + resyncPeriod, + indexers, + ) +} + +func (f *recommendationInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredRecommendationInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *recommendationInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&analysisv1alpha1.Recommendation{}, f.defaultInformer) +} + +func (f *recommendationInformer) Lister() v1alpha1.RecommendationLister { + return v1alpha1.NewRecommendationLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/factory.go b/pkg/client/informers/externalversions/factory.go index b814041a5..db79275e9 100644 --- a/pkg/client/informers/externalversions/factory.go +++ b/pkg/client/informers/externalversions/factory.go @@ -24,6 +24,7 @@ import ( time "time" versioned "github.com/koordinator-sh/koordinator/pkg/client/clientset/versioned" + analysis "github.com/koordinator-sh/koordinator/pkg/client/informers/externalversions/analysis" config "github.com/koordinator-sh/koordinator/pkg/client/informers/externalversions/config" internalinterfaces "github.com/koordinator-sh/koordinator/pkg/client/informers/externalversions/internalinterfaces" quota "github.com/koordinator-sh/koordinator/pkg/client/informers/externalversions/quota" @@ -175,12 +176,17 @@ type SharedInformerFactory interface { ForResource(resource schema.GroupVersionResource) (GenericInformer, error) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool + Analysis() analysis.Interface Config() config.Interface Quota() quota.Interface Scheduling() scheduling.Interface Slo() slo.Interface } +func (f *sharedInformerFactory) Analysis() analysis.Interface { + return analysis.New(f, f.namespace, f.tweakListOptions) +} + func (f *sharedInformerFactory) Config() config.Interface { return config.New(f, f.namespace, f.tweakListOptions) } diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go index 0f8c22878..c03950c1c 100644 --- a/pkg/client/informers/externalversions/generic.go +++ b/pkg/client/informers/externalversions/generic.go @@ -21,7 +21,8 @@ package externalversions import ( "fmt" - v1alpha1 "github.com/koordinator-sh/koordinator/apis/config/v1alpha1" + v1alpha1 "github.com/koordinator-sh/koordinator/apis/analysis/v1alpha1" + configv1alpha1 "github.com/koordinator-sh/koordinator/apis/config/v1alpha1" quotav1alpha1 "github.com/koordinator-sh/koordinator/apis/quota/v1alpha1" schedulingv1alpha1 "github.com/koordinator-sh/koordinator/apis/scheduling/v1alpha1" slov1alpha1 "github.com/koordinator-sh/koordinator/apis/slo/v1alpha1" @@ -55,8 +56,12 @@ func (f *genericInformer) Lister() cache.GenericLister { // TODO extend this to unknown resources with a client pool func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { switch resource { - // Group=config, Version=v1alpha1 - case v1alpha1.SchemeGroupVersion.WithResource("clustercolocationprofiles"): + // Group=analysis, Version=v1alpha1 + case v1alpha1.SchemeGroupVersion.WithResource("recommendations"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Analysis().V1alpha1().Recommendations().Informer()}, nil + + // Group=config, Version=v1alpha1 + case configv1alpha1.SchemeGroupVersion.WithResource("clustercolocationprofiles"): return &genericInformer{resource: resource.GroupResource(), informer: f.Config().V1alpha1().ClusterColocationProfiles().Informer()}, nil // Group=quota, Version=v1alpha1 diff --git a/pkg/client/listers/analysis/v1alpha1/expansion_generated.go b/pkg/client/listers/analysis/v1alpha1/expansion_generated.go new file mode 100644 index 000000000..7d2acaa45 --- /dev/null +++ b/pkg/client/listers/analysis/v1alpha1/expansion_generated.go @@ -0,0 +1,27 @@ +/* +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 lister-gen. DO NOT EDIT. + +package v1alpha1 + +// RecommendationListerExpansion allows custom methods to be added to +// RecommendationLister. +type RecommendationListerExpansion interface{} + +// RecommendationNamespaceListerExpansion allows custom methods to be added to +// RecommendationNamespaceLister. +type RecommendationNamespaceListerExpansion interface{} diff --git a/pkg/client/listers/analysis/v1alpha1/recommendation.go b/pkg/client/listers/analysis/v1alpha1/recommendation.go new file mode 100644 index 000000000..d521b6d88 --- /dev/null +++ b/pkg/client/listers/analysis/v1alpha1/recommendation.go @@ -0,0 +1,99 @@ +/* +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 lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/koordinator-sh/koordinator/apis/analysis/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// RecommendationLister helps list Recommendations. +// All objects returned here must be treated as read-only. +type RecommendationLister interface { + // List lists all Recommendations in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.Recommendation, err error) + // Recommendations returns an object that can list and get Recommendations. + Recommendations(namespace string) RecommendationNamespaceLister + RecommendationListerExpansion +} + +// recommendationLister implements the RecommendationLister interface. +type recommendationLister struct { + indexer cache.Indexer +} + +// NewRecommendationLister returns a new RecommendationLister. +func NewRecommendationLister(indexer cache.Indexer) RecommendationLister { + return &recommendationLister{indexer: indexer} +} + +// List lists all Recommendations in the indexer. +func (s *recommendationLister) List(selector labels.Selector) (ret []*v1alpha1.Recommendation, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.Recommendation)) + }) + return ret, err +} + +// Recommendations returns an object that can list and get Recommendations. +func (s *recommendationLister) Recommendations(namespace string) RecommendationNamespaceLister { + return recommendationNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// RecommendationNamespaceLister helps list and get Recommendations. +// All objects returned here must be treated as read-only. +type RecommendationNamespaceLister interface { + // List lists all Recommendations in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.Recommendation, err error) + // Get retrieves the Recommendation from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.Recommendation, error) + RecommendationNamespaceListerExpansion +} + +// recommendationNamespaceLister implements the RecommendationNamespaceLister +// interface. +type recommendationNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all Recommendations in the indexer for a given namespace. +func (s recommendationNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.Recommendation, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.Recommendation)) + }) + return ret, err +} + +// Get retrieves the Recommendation from the indexer for a given namespace and name. +func (s recommendationNamespaceLister) Get(name string) (*v1alpha1.Recommendation, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("recommendation"), name) + } + return obj.(*v1alpha1.Recommendation), nil +} diff --git a/pkg/yarn/OWNERS b/pkg/prediction/OWNERS similarity index 59% rename from pkg/yarn/OWNERS rename to pkg/prediction/OWNERS index b4057792a..38756bdc4 100644 --- a/pkg/yarn/OWNERS +++ b/pkg/prediction/OWNERS @@ -1,6 +1,6 @@ # See the OWNERS docs at https://go.k8s.io/owners approvers: - - yarn-approvers + - prediction-approvers reviewers: - - yarn-approvers + - prediction-approvers diff --git a/pkg/prediction/frontend/pod_controller.go b/pkg/prediction/frontend/pod_controller.go new file mode 100644 index 000000000..a585dbc65 --- /dev/null +++ b/pkg/prediction/frontend/pod_controller.go @@ -0,0 +1,198 @@ +/* + * Used to listen pod create/delete event, and create/delete cr for pods selector by key + */ + +package frontend + +import ( + "context" + "encoding/json" + "fmt" + "time" + + analysisv1alpha1 "github.com/koordinator-sh/koordinator/apis/analysis/v1alpha1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" + corev1listers "k8s.io/client-go/listers/core/v1" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/cache" + "k8s.io/klog/v2" + "sigs.k8s.io/controller-runtime/pkg/client" + + versioned "github.com/koordinator-sh/koordinator/pkg/client/clientset/versioned" + analysisinformers "github.com/koordinator-sh/koordinator/pkg/client/informers/externalversions" + analysislister "github.com/koordinator-sh/koordinator/pkg/client/listers/analysis/v1alpha1" +) + +const ( + key = "github.com/set-to-your-own-key" +) + +func RegisterPodListener(client client.Client, ctx context.Context) { + config, err := rest.InClusterConfig() + if err != nil { + klog.Fatalf("Failed to create in-cluster config: %v", err) + return + } + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + klog.Fatalf("Failed to create Kubernetes clientset: %v", err) + return + } + factory := informers.NewSharedInformerFactory(clientset, 10*time.Minute) + + analysisClientSet, err := versioned.NewForConfig(config) + if err != nil { + klog.Fatalf("error while init analysis Clientset") + return + } + + analysisFactory := analysisinformers.NewSharedInformerFactory(analysisClientSet, 10*time.Minute) + + podInformer := factory.Core().V1().Pods() + podLister := podInformer.Lister() + + analysisInformer := analysisFactory.Analysis().V1alpha1().Recommendations() + analysisLister := analysisInformer.Lister() + + podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + pod := obj.(*corev1.Pod) + ref := pod.OwnerReferences + if len(ref) == 0 { + createAnalysisCR(client, analysisLister, pod) + } + }, + DeleteFunc: func(obj interface{}) { + pod := obj.(*corev1.Pod) + if isLastPod(podLister, pod) { + deleteAnalysisCR(client, analysisLister, pod) + } + }, + }) + if err != nil { + klog.Errorf("error while register pod listener %+v", err) + } + go podInformer.Informer().Run(ctx.Done()) + go analysisInformer.Informer().Run(ctx.Done()) + if !cache.WaitForCacheSync(ctx.Done(), + podInformer.Informer().HasSynced, + analysisInformer.Informer().HasSynced) { + klog.Fatalf("Failed to sync cache for pod controller") + } +} + +func isLastPod(podLister corev1listers.PodLister, pod *corev1.Pod) bool { + if value, ok := pod.Labels[key]; ok { + selector := labels.SelectorFromSet(labels.Set{key: value}) + pods, err := podLister.List(selector) + if err != nil { + return true + } + if len(pods) == 0 { + return true + } + } + return false +} + +func existCR(lister analysislister.RecommendationLister, namespace, name string) bool { + _, err := lister.Recommendations(namespace).Get(name) + return err == nil +} + +func createAnalysisCR(client client.Client, lister analysislister.RecommendationLister, pod *corev1.Pod) { + types := analysisv1alpha1.RecommendationPodSelector + if value, ok := pod.Labels[key]; ok { + selector := &metav1.LabelSelector{ + MatchLabels: map[string]string{ + key: value, + }, + } + workloadName, err := parseWorkloadName(pod) + if err != nil { + klog.Errorf("failed to parse pod %s: %+v", pod.Name, err) + return + } + namespace := pod.Namespace + if existCR(lister, namespace, workloadName) { + return + } + analysisation := &analysisv1alpha1.Recommendation{ + TypeMeta: metav1.TypeMeta{ + Kind: analysisv1alpha1.RecommendationKind, + APIVersion: analysisv1alpha1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: workloadName, + }, + Spec: analysisv1alpha1.RecommendationSpec{ + Target: analysisv1alpha1.RecommendationTarget{ + Type: types, + PodSelector: selector, + Workload: nil, + }, + }, + Status: analysisv1alpha1.RecommendationStatus{}, + } + err = client.Create(context.TODO(), analysisation) + if err != nil { + klog.Infof("failed to create analysisation, pod name %s, err %+v", pod.Name, err) + return + } + klog.Infof("create %s/%s crd success by pod selector.", namespace, workloadName) + } +} + +type TopOwnerReference struct { + APIVersion string `json:"apiVersion"` + Kind string `json:"kind"` + Name string `json:"name"` + UID string `json:"uid"` + Controller bool `json:"controller"` + BlockOwnerDeletion bool `json:"blockOwnerDeletion"` +} + +func parseWorkloadName(pod *corev1.Pod) (string, error) { + annotationKey := "sd.xhs.com/top-owner-reference" + if annotation, ok := pod.Annotations[annotationKey]; ok { + var topOwnerReference TopOwnerReference + if err := json.Unmarshal([]byte(annotation), &topOwnerReference); err != nil { + return "", fmt.Errorf("failed to unmarshal annotation: %v", err) + } + return topOwnerReference.Name, nil + } + return "", fmt.Errorf("annotation %s not found", annotationKey) + +} + +func deleteAnalysisCR(client client.Client, lister analysislister.RecommendationLister, pod *corev1.Pod) { + namespace := pod.Namespace + workloadName, err := parseWorkloadName(pod) + if err != nil { + klog.Errorf("no enought info to delete analysisation for %s/%s", namespace, workloadName) + return + } + if !existCR(lister, namespace, workloadName) { + return + } + analysisation := &analysisv1alpha1.Recommendation{ + TypeMeta: metav1.TypeMeta{ + Kind: analysisv1alpha1.RecommendationKind, + APIVersion: analysisv1alpha1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: workloadName, + }, + } + err = client.Delete(context.TODO(), analysisation) + if err != nil { + return + } + klog.Infof("delete %s/%s cr sucess by pod selector", namespace, workloadName) +} diff --git a/pkg/prediction/frontend/recommendation_controller.go b/pkg/prediction/frontend/recommendation_controller.go new file mode 100644 index 000000000..0b1370324 --- /dev/null +++ b/pkg/prediction/frontend/recommendation_controller.go @@ -0,0 +1,69 @@ +/* + * register reconciler of recommendation crd. + */ + +package frontend + +import ( + "context" + + analysisv1alpha1 "github.com/koordinator-sh/koordinator/apis/analysis/v1alpha1" + "github.com/koordinator-sh/koordinator/pkg/prediction/manager" + "github.com/koordinator-sh/koordinator/pkg/prediction/manager/apis" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +// Recommendation reconciles a Recommendation object +type RecommendationReconcile struct { + client.Client + Scheme *runtime.Scheme + Recorder record.EventRecorder + Manager manager.PredictionManager +} + +// +kubebuilder:rbac:groups=analysis.koordinator.sh,resources=metricpredictions,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=analysis.koordinator.sh,resources=metricpredictions/status,verbs=get;update;patch +func (r *RecommendationReconcile) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + var recommendation analysisv1alpha1.Recommendation + _ = log.FromContext(ctx, "recommendation-reconcile", req.NamespacedName) + if err := r.Client.Get(ctx, req.NamespacedName, &recommendation); err != nil { + profileKey := apis.MakeProfileKey(req.NamespacedName) + // unreg key + err := r.Manager.Unregister(profileKey) + if err != nil { + return ctrl.Result{Requeue: true}, err + } + return ctrl.Result{}, nil + } + profileKey := apis.MakeProfileKey(req.NamespacedName) + profiler := apis.MakeProfilerSpec(profileKey, recommendation) + err := r.Manager.Register(profileKey, profiler) + if err != nil { + return ctrl.Result{Requeue: true}, err + } + + return ctrl.Result{}, nil +} + +func Add(mgr ctrl.Manager, predictionManager manager.PredictionManager) error { + reconciler := &RecommendationReconcile{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor("metric-prediction"), + Manager: predictionManager, + } + return reconciler.SetupWithManager(mgr) +} + +// SetupWithManager sets up the controller with the Manager. +func (r *RecommendationReconcile) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&analysisv1alpha1.Recommendation{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). + Complete(r) +} diff --git a/pkg/prediction/frontend/update.go b/pkg/prediction/frontend/update.go new file mode 100644 index 000000000..223204962 --- /dev/null +++ b/pkg/prediction/frontend/update.go @@ -0,0 +1,134 @@ +package frontend + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "sync" + "time" + + analysisv1alpha1 "github.com/koordinator-sh/koordinator/apis/analysis/v1alpha1" + "github.com/koordinator-sh/koordinator/pkg/client/clientset/versioned/typed/analysis/v1alpha1" + "github.com/koordinator-sh/koordinator/pkg/prediction/manager" + "github.com/koordinator-sh/koordinator/pkg/prediction/manager/apis" + "github.com/prometheus/client_golang/prometheus/promhttp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/klog/v2" +) + +var _ StatusFetcher = &statusFetcher{} + +type statusFetcher struct { + client v1alpha1.RecommendationsGetter + predictMgr manager.PredictionManager + ctx context.Context + updatePeriod int +} + +type StatusFetcher interface { + Run() + Started() bool + UpdateStatus() +} + +func Wrapper(input string, wrapper string) string { + return input + " " + wrapper +} + +func InitStatusFetcher(client v1alpha1.RecommendationsGetter, ctx context.Context, predictMgr manager.PredictionManager) *statusFetcher { + return &statusFetcher{ + client: client, + ctx: ctx, + predictMgr: predictMgr, + } +} + +func getNextUpdate(now time.Time) time.Time { + next := time.Date(now.Year(), now.Month(), now.Day(), 2, 0, 0, 0, now.Location()) + if next.Before(now) { + next = next.Add(24 * time.Hour) + } + return next +} + +func (s *statusFetcher) metricsHandler(w http.ResponseWriter, r *http.Request) { + promhttp.Handler().ServeHTTP(w, r) +} + +func (s *statusFetcher) Run() { + for { + select { + case <-s.ctx.Done(): + return + case <-time.After(time.Until(getNextUpdate(time.Now()))): + s.UpdateStatus() + } + } +} + +func (s *statusFetcher) Started() bool { + return false +} + +func (s *statusFetcher) AsStatus(r *apis.DistributionProfilerResult) (*analysisv1alpha1.RecommendationStatus, error) { + return nil, fmt.Errorf("has no implement") + +} +func (s *statusFetcher) UpdateStatus() { + var wg sync.WaitGroup + sem := make(chan struct{}, 5) + s.predictMgr.ShareState.ProfilerList.Range(func(k, v interface{}) bool { + wg.Add(1) + sem <- struct{}{} + go func(k interface{}) { + defer wg.Done() + defer func() { <-sem }() + key := k.(apis.ProfileKey) + result := &apis.DistributionProfilerResult{ + DName: key.Name(), + DNamespace: key.Namespace(), + } + err := s.predictMgr.GetResult(key, result, nil) + if err != nil { + klog.Error("Error while get result of Profilekey: ", key, err) + } + status, err := s.AsStatus(result) + if err != nil { + klog.Error("Error while transfrom status:", err) + return + } + _, err = s.patchRecommendation(key.Name(), key.Namespace(), status) + if err != nil { + klog.Error("Error while patch Recommend status:", err) + return + } + klog.Infof("Update %s/%s status", key.Namespace(), key.Name()) + }(k) + return true + }) + wg.Wait() +} + +type patchRecord struct { + Op string `json:"op,inline"` + Path string `json:"path,inline"` + Value interface{} `json:"value"` +} + +func (s *statusFetcher) patchRecommendation(name string, namespace string, + status *analysisv1alpha1.RecommendationStatus) (result *analysisv1alpha1.Recommendation, err error) { + patches := []patchRecord{{ + Op: "add", + Path: "/status", + Value: status, + }} + bytes, err := json.Marshal(patches) + if err != nil { + klog.Errorf("Can not marshal Recommend status patches %+v, Reason: %+v", patches, err) + return nil, err + } + client := s.client.Recommendations(namespace) + return client.Patch(context.TODO(), name, types.JSONPatchType, bytes, metav1.PatchOptions{}) +} diff --git a/pkg/prediction/frontend/workload_controller.go b/pkg/prediction/frontend/workload_controller.go new file mode 100644 index 000000000..bef2ae954 --- /dev/null +++ b/pkg/prediction/frontend/workload_controller.go @@ -0,0 +1,276 @@ +package frontend + +import ( + "context" + "time" + + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/predicate" + + kruisev1alpha1 "github.com/openkruise/kruise-api/apps/v1alpha1" + appsv1 "k8s.io/api/apps/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/tools/record" + "k8s.io/klog/v2" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + analysisv1alpha1 "github.com/koordinator-sh/koordinator/apis/analysis/v1alpha1" + versioned "github.com/koordinator-sh/koordinator/pkg/client/clientset/versioned" + analysisinformers "github.com/koordinator-sh/koordinator/pkg/client/informers/externalversions" + analysislister "github.com/koordinator-sh/koordinator/pkg/client/listers/analysis/v1alpha1" +) + +type WorkloadReconciler struct { + Client client.Client + Scheme *runtime.Scheme + Recorder record.EventRecorder + lister analysislister.RecommendationLister + namespaces []string + workloads []string +} + +func AddWorkloadReconciler(mgr ctrl.Manager, namespace []string, enabledWorkload []string) error { + reconciler := &WorkloadReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor("maliang-workload-reconciler"), + namespaces: make([]string, 0), + workloads: make([]string, 0), + } + reconciler.namespaces = append(reconciler.namespaces, namespace...) + klog.Infof("Register recommender for namespaces: %+v", reconciler.namespaces) + + reconciler.workloads = append(reconciler.workloads, enabledWorkload...) + klog.Infof("Register recommender for workloads: %+v", reconciler.namespaces) + + config, err := rest.InClusterConfig() + if err != nil { + klog.Errorf("can not init recommend client for workload controller") + } + client, _ := versioned.NewForConfig(config) + recommendFactory := analysisinformers.NewSharedInformerFactory(client, 10*time.Minute) + recommendInformer := recommendFactory.Analysis().V1alpha1().Recommendations() + recommendLister := recommendInformer.Lister() + + go recommendInformer.Informer().Run(context.Background().Done()) + + if !cache.WaitForCacheSync(context.Background().Done(), + recommendInformer.Informer().HasSynced) { + klog.Fatalf("Failed to sync cache for pod controller") + } + reconciler.lister = recommendLister + return reconciler.SetupWithManager(mgr) +} + +func (r *WorkloadReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { + var profile bool + + // check if should reconcile? + profile = false + for _, n := range r.namespaces { + if n == "all" { + profile = true + break + } + if n == req.Namespace { + profile = true + break + } + } + if !profile { + return ctrl.Result{}, nil + } + + // Try to get the Deployment + cloneset := &kruisev1alpha1.CloneSet{} + if err := r.Client.Get(ctx, req.NamespacedName, cloneset); err == nil { + ownerRef := cloneset.ObjectMeta.OwnerReferences + if r.IsTopOwnerReference(ownerRef) { + r.CreateOrUpdateRecommendation(analysisv1alpha1.RecommendationTargetWorkload, + cloneset.Kind, cloneset.APIVersion, req.NamespacedName, nil, ctx) + } + return ctrl.Result{}, nil + } + + deployment := &appsv1.Deployment{} + if err := r.Client.Get(ctx, req.NamespacedName, deployment); err == nil { + ownerRef := deployment.ObjectMeta.OwnerReferences + if r.IsTopOwnerReference(ownerRef) { + r.CreateOrUpdateRecommendation(analysisv1alpha1.RecommendationTargetWorkload, + deployment.Kind, deployment.APIVersion, req.NamespacedName, nil, ctx) + } + return ctrl.Result{}, nil + } + + statefulSet := &appsv1.StatefulSet{} + if err := r.Client.Get(ctx, req.NamespacedName, statefulSet); err == nil { + ownerRef := statefulSet.ObjectMeta.OwnerReferences + if r.IsTopOwnerReference(ownerRef) { + r.CreateOrUpdateRecommendation(analysisv1alpha1.RecommendationTargetWorkload, + statefulSet.Kind, statefulSet.APIVersion, req.NamespacedName, nil, ctx) + } + return ctrl.Result{}, nil + } + + daemonset := &appsv1.DaemonSet{} + if err := r.Client.Get(ctx, req.NamespacedName, daemonset); err == nil { + ownerRef := daemonset.ObjectMeta.OwnerReferences + if r.IsTopOwnerReference(ownerRef) { + r.CreateOrUpdateRecommendation(analysisv1alpha1.RecommendationTargetWorkload, + daemonset.Kind, daemonset.APIVersion, req.NamespacedName, nil, ctx) + } + return ctrl.Result{}, nil + } + + advancedDaemonset := &kruisev1alpha1.DaemonSet{} + if err := r.Client.Get(ctx, req.NamespacedName, advancedDaemonset); err == nil { + ownerRef := advancedDaemonset.ObjectMeta.OwnerReferences + if r.IsTopOwnerReference(ownerRef) { + r.CreateOrUpdateRecommendation(analysisv1alpha1.RecommendationTargetWorkload, + advancedDaemonset.Kind, advancedDaemonset.APIVersion, req.NamespacedName, nil, ctx) + } + return ctrl.Result{}, nil + } + + replicaset := &appsv1.ReplicaSet{} + if err := r.Client.Get(ctx, req.NamespacedName, replicaset); err == nil { + ownerRef := replicaset.ObjectMeta.OwnerReferences + if r.IsTopOwnerReference(ownerRef) { + r.CreateOrUpdateRecommendation(analysisv1alpha1.RecommendationTargetWorkload, + replicaset.Kind, replicaset.APIVersion, req.NamespacedName, nil, ctx) + } + return ctrl.Result{}, nil + } + + // If can not get object, it's about delete event. + r.DeleteRecommendation(ctx, req.NamespacedName) + return ctrl.Result{}, nil +} + +func (r *WorkloadReconciler) CreateOrUpdateRecommendation( + types analysisv1alpha1.RecommendationTargetType, + kind, apiversion string, namespacedName types.NamespacedName, + selector *metav1.LabelSelector, ctx context.Context) error { + name := namespacedName.Name + namespace := namespacedName.Namespace + if r.existCR(namespace, name) { + return nil + } + recommendation := &analysisv1alpha1.Recommendation{ + TypeMeta: metav1.TypeMeta{ + Kind: analysisv1alpha1.RecommendationKind, + APIVersion: analysisv1alpha1.GroupVersion.Version, + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + }, + Spec: analysisv1alpha1.RecommendationSpec{ + Target: analysisv1alpha1.RecommendationTarget{ + Type: types, + Workload: &analysisv1alpha1.CrossVersionObjectReference{ + Kind: kind, + Name: name, + Namespace: namespace, + APIVersion: apiversion, + }, + PodSelector: selector, + }, + }, + Status: analysisv1alpha1.RecommendationStatus{}, + } + err := r.Client.Create(ctx, recommendation) + if err == nil { + klog.Infof("create %s/%s crd success", namespace, name) + return nil + } + // Do not need to update existed resource. + /* + if apierrors.IsAlreadyExists(err) { + existCr := &analysisv1alpha1.Recommendation{} + if err := r.Client.Get(ctx, client.ObjectKey{ + Namespace: namespace, + Name: name, + }, existCr); err != nil { + return err + } + existCr.Spec = recommendation.Spec + err := r.Client.Update(ctx, existCr) + if err != nil { + klog.Warningf("update crd failed: %+v", err) + return err + } + klog.V(4).Infof("update %s/%s crd success", namespace, name) + } else { + klog.Warningf("create crd %s/%sfailed : %+v", namespace, name, err) + } + */ + return nil +} + +func (r *WorkloadReconciler) DeleteRecommendation(ctx context.Context, namespacedName types.NamespacedName) error { + namespace := namespacedName.Namespace + name := namespacedName.Name + if !r.existCR(namespace, name) { + return nil + } + cr := &analysisv1alpha1.Recommendation{ + TypeMeta: metav1.TypeMeta{ + Kind: analysisv1alpha1.RecommendationKind, + APIVersion: analysisv1alpha1.GroupVersion.Version, + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + }, + } + err := r.Client.Delete(ctx, cr) + if err != nil { + klog.Warningf("delete %s/%s cr failed: %+v", namespace, name, err) + return err + } + klog.Infof("delete %s/%s cr success", namespace, name) + return nil +} + +func (r *WorkloadReconciler) IsTopOwnerReference(ownerRef []metav1.OwnerReference) bool { + if len(ownerRef) == 0 { + return true + } + for _, ref := range ownerRef { + if *ref.Controller { + return false + } + } + return true +} + +func (r *WorkloadReconciler) existCR(namespace, name string) bool { + _, err := r.lister.Recommendations(namespace).Get(name) + return err == nil +} + +func (r *WorkloadReconciler) SetupWithManager(mgr ctrl.Manager) error { + createAndDelete := predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { + return true + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return true + }, + } + return ctrl.NewControllerManagedBy(mgr). + For(&appsv1.StatefulSet{}, builder.WithPredicates(createAndDelete)). + Watches(&appsv1.Deployment{}, &handler.EnqueueRequestForObject{}, builder.WithPredicates(createAndDelete)). + Watches(&appsv1.ReplicaSet{}, &handler.EnqueueRequestForObject{}, builder.WithPredicates(createAndDelete)). + WithOptions(controller.Options{MaxConcurrentReconciles: 1}).Complete(r) +} diff --git a/pkg/prediction/manager/checkpoint/checkpoint.go b/pkg/prediction/manager/checkpoint/checkpoint.go new file mode 100644 index 000000000..8b87c7b75 --- /dev/null +++ b/pkg/prediction/manager/checkpoint/checkpoint.go @@ -0,0 +1,20 @@ +/* + Copyright 2024 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 checkpoint + +type Checkpoint interface { +} diff --git a/pkg/prediction/manager/manager.go b/pkg/prediction/manager/manager.go new file mode 100644 index 000000000..6be43c180 --- /dev/null +++ b/pkg/prediction/manager/manager.go @@ -0,0 +1,76 @@ +/* + Copyright 2024 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 manager + +import ( + "github.com/koordinator-sh/koordinator/pkg/prediction/manager/checkpoint" + "github.com/koordinator-sh/koordinator/pkg/prediction/manager/metricscollector" + "github.com/koordinator-sh/koordinator/pkg/prediction/manager/profiler" + "github.com/koordinator-sh/koordinator/pkg/prediction/manager/protocol" + "github.com/koordinator-sh/koordinator/pkg/prediction/manager/workloadfetcher" +) + +type PredictionManager interface { + Run() error + Started() bool + Register(...protocol.PredictionProfile) error + Unregister(...protocol.PredictionProfileKey) error + GetResult(key protocol.PredictionProfileKey) (protocol.PredictionResult, error) +} + +var _ PredictionManager = &predictionMgrImpl{} + +type predictionMgrImpl struct { + metricsRepo metricscollector.MetricsRepository + checkpoint checkpoint.Checkpoint + workloadFetcher workloadfetcher.WorkloadFetcher + profiler profiler.Profiler +} + +func (p *predictionMgrImpl) Run() error { + // run checkpoint to load all history data + // start workload fetcher waiting for workloads + // start metrics repo ready for collect + // start profiler to calculate each model + panic("implement me") +} + +func (p *predictionMgrImpl) Started() bool { + // return true only if all components are started + panic("implement me") +} + +func (p *predictionMgrImpl) Register(profiles ...protocol.PredictionProfile) error { + // return error if not started + // add workload to WorkloadFetcher + // subscribe metric to metricsRepo + // add model to Profiler + panic("implement me") +} + +func (p *predictionMgrImpl) Unregister(keys ...protocol.PredictionProfileKey) error { + // return error if not started + // remove workload from WorkloadFetcher + // unsubscribe metric from metricsRepo + // remove model from Profiler + panic("implement me") +} + +func (p *predictionMgrImpl) GetResult(key protocol.PredictionProfileKey) (protocol.PredictionResult, error) { + //TODO implement me + panic("implement me") +} diff --git a/pkg/prediction/manager/metricscollector/repository.go b/pkg/prediction/manager/metricscollector/repository.go new file mode 100644 index 000000000..af96a0e34 --- /dev/null +++ b/pkg/prediction/manager/metricscollector/repository.go @@ -0,0 +1,20 @@ +/* + Copyright 2024 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 metricscollector + +type MetricsRepository interface { +} diff --git a/pkg/prediction/manager/profiler/model.go b/pkg/prediction/manager/profiler/model.go new file mode 100644 index 000000000..e78369506 --- /dev/null +++ b/pkg/prediction/manager/profiler/model.go @@ -0,0 +1,28 @@ +/* + Copyright 2024 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 profiler + +type ModelKey interface { +} + +type Model interface { + AddSample(sample Sample) + SubtractSample(sample Sample) +} + +type Sample interface { +} diff --git a/pkg/prediction/manager/profiler/profiler.go b/pkg/prediction/manager/profiler/profiler.go new file mode 100644 index 000000000..0cedf1def --- /dev/null +++ b/pkg/prediction/manager/profiler/profiler.go @@ -0,0 +1,50 @@ +/* + Copyright 2024 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 profiler + +import "github.com/koordinator-sh/koordinator/pkg/prediction/manager/protocol" + +type Profiler interface { + Run() error + Started() bool + GetResult(key protocol.PredictionProfileKey) (protocol.PredictionResult, error) +} + +var _ Profiler = &profilerImpl{} + +type profilerImpl struct { + models map[ModelKey]Model +} + +func (p *profilerImpl) Run() error { + // get groupings (pod/container belongs to same workload) list from workload fetcher + // create/update(args) model for each grouping + // load history from checkpoint if exist for new model + // for each model, get metric from metric repo and feed samples to model + // save checkpoint for each model + panic("implement me") +} + +func (p *profilerImpl) Started() bool { + //TODO implement me + panic("implement me") +} + +func (p *profilerImpl) GetResult(key protocol.PredictionProfileKey) (protocol.PredictionResult, error) { + //TODO implement me + panic("implement me") +} diff --git a/pkg/prediction/manager/protocol/profile.go b/pkg/prediction/manager/protocol/profile.go new file mode 100644 index 000000000..8d1c4e91f --- /dev/null +++ b/pkg/prediction/manager/protocol/profile.go @@ -0,0 +1,28 @@ +/* + Copyright 2024 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 protocol + +type PredictionProfileKey interface { +} + +type PredictionProfile interface { + PredictionProfileKey +} + +type PredictionResult interface { + PredictionProfileKey +} diff --git a/pkg/prediction/manager/workloadfetcher/workload_fetcher.go b/pkg/prediction/manager/workloadfetcher/workload_fetcher.go new file mode 100644 index 000000000..c21d5f0ab --- /dev/null +++ b/pkg/prediction/manager/workloadfetcher/workload_fetcher.go @@ -0,0 +1,52 @@ +/* + Copyright 2024 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 workloadfetcher + +type WorkloadFetcher interface { + Run() error + Started() bool + AddWorkloads(...Workload) + RemoveWorkloads(...Workload) +} + +var _ WorkloadFetcher = &workloadFetcherImpl{} + +type workloadFetcherImpl struct { +} + +func (w *workloadFetcherImpl) Run() error { + //TODO implement me + panic("implement me") +} + +func (w *workloadFetcherImpl) Started() bool { + //TODO implement me + panic("implement me") +} + +func (w *workloadFetcherImpl) AddWorkloads(workload ...Workload) { + //TODO implement me + panic("implement me") +} + +func (w *workloadFetcherImpl) RemoveWorkloads(workload ...Workload) { + //TODO implement me + panic("implement me") +} + +type Workload interface { +} diff --git a/pkg/yarn/controller/OWNERS b/pkg/yarn/controller/OWNERS deleted file mode 100644 index d12c358e2..000000000 --- a/pkg/yarn/controller/OWNERS +++ /dev/null @@ -1,6 +0,0 @@ -# See the OWNERS docs at https://go.k8s.io/owners - -approvers: - - yarn-controller-approvers -reviewers: - - yarn-controller-approvers diff --git a/pkg/yarn/copilot/OWNERS b/pkg/yarn/copilot/OWNERS deleted file mode 100644 index 520331aa3..000000000 --- a/pkg/yarn/copilot/OWNERS +++ /dev/null @@ -1,6 +0,0 @@ -# See the OWNERS docs at https://go.k8s.io/owners - -approvers: - - yarn-copilot-approvers -reviewers: - - yarn-copilot-approvers