diff --git a/go.mod b/go.mod index ff36485eb..4778898e2 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( k8s.io/kube-aggregator v0.27.2 k8s.io/utils v0.0.0-20230313181309-38a27ef9d749 open-cluster-management.io/addon-framework v0.7.1-0.20230626092851-963716af4eed - open-cluster-management.io/api v0.11.1-0.20230609103311-088e8fe86139 + open-cluster-management.io/api v0.11.1-0.20230621023453-8f11dd3ede06 sigs.k8s.io/controller-runtime v0.15.0 sigs.k8s.io/kube-storage-version-migrator v0.0.5 ) diff --git a/go.sum b/go.sum index 8a14e3076..bcda6f0ff 100644 --- a/go.sum +++ b/go.sum @@ -1158,8 +1158,8 @@ k8s.io/utils v0.0.0-20230313181309-38a27ef9d749 h1:xMMXJlJbsU8w3V5N2FLDQ8YgU8s1E k8s.io/utils v0.0.0-20230313181309-38a27ef9d749/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= open-cluster-management.io/addon-framework v0.7.1-0.20230626092851-963716af4eed h1:fOOKf8kzVNizc5fYvMwkPy9TT/vOpojd4IIxpzh/vhw= open-cluster-management.io/addon-framework v0.7.1-0.20230626092851-963716af4eed/go.mod h1:Cyt5knxR+sXaKvOfUKseZDAGulS2AJz6o7a9J0WXbak= -open-cluster-management.io/api v0.11.1-0.20230609103311-088e8fe86139 h1:nw/XSv4eDGqmg0ks2PHzrE2uosvjw+D314843G56xGY= -open-cluster-management.io/api v0.11.1-0.20230609103311-088e8fe86139/go.mod h1:WgKUCJ7+Bf40DsOmH1Gdkpyj3joco+QLzrlM6Ak39zE= +open-cluster-management.io/api v0.11.1-0.20230621023453-8f11dd3ede06 h1:FO+fWqXHvG2daZurmSVgOe8rpefoqmShKs7PbbVnmpw= +open-cluster-management.io/api v0.11.1-0.20230621023453-8f11dd3ede06/go.mod h1:WgKUCJ7+Bf40DsOmH1Gdkpyj3joco+QLzrlM6Ak39zE= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/manifests/cluster-manager/hub/0000_02_clusters.open-cluster-management.io_placements.crd.yaml b/manifests/cluster-manager/hub/0000_02_clusters.open-cluster-management.io_placements.crd.yaml index a483f8725..b22ab85df 100644 --- a/manifests/cluster-manager/hub/0000_02_clusters.open-cluster-management.io_placements.crd.yaml +++ b/manifests/cluster-manager/hub/0000_02_clusters.open-cluster-management.io_placements.crd.yaml @@ -69,6 +69,154 @@ spec: items: type: string type: array + decisionStrategy: + description: DecisionStrategy divide the created placement decision + to groups and define number of clusters per decision group. + properties: + groupStrategy: + description: GroupStrategy define strategies to divide selected + clusters to decision groups. + properties: + clustersPerDecisionGroup: + anyOf: + - type: integer + - type: string + default: 100% + description: ClustersPerDecisionGroup is a specific number + or percentage of the total selected clusters. The specific + number will divide the placementDecisions to decisionGroups + each group has max number of clusters equal to that specific + number. The percentage will divide the placementDecisions + to decisionGroups each group has max number of clusters + based on the total num of selected clusters and percentage. + ex; for a total 100 clusters selected, ClustersPerDecisionGroup + equal to 20% will divide the placement decision to 5 groups + each group should have 20 clusters. Default is having all + clusters in a single group. If the DecisionGroups field + defined, it will be considered first to create the decisionGroups + then the ClustersPerDecisionGroup will be used to determine + the rest of decisionGroups. + x-kubernetes-int-or-string: true + decisionGroups: + description: DecisionGroups represents a list of predefined + groups to put decision results. + items: + description: DecisionGroup define a subset of clusters that + will be added to placementDecisions with groupName label. + properties: + groupClusterSelector: + description: LabelSelector to select clusters subset + by label. + properties: + claimSelector: + description: ClaimSelector represents a selector + of ManagedClusters by clusterClaims in status + properties: + matchExpressions: + description: matchExpressions is a list of cluster + claim 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 + type: object + labelSelector: + description: LabelSelector represents a selector + of ManagedClusters by label + 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 + x-kubernetes-map-type: atomic + type: object + groupName: + description: Group name to be added as label value to + the created placement Decisions labels with label + key cluster.open-cluster-management.io/decision-group-name + pattern: ^[a-zA-Z0-9][-A-Za-z0-9_.]{0,61}[a-zA-Z0-9]$ + type: string + required: + - groupClusterSelector + - groupName + type: object + type: array + required: + - clustersPerDecisionGroup + type: object + type: object numberOfClusters: description: NumberOfClusters represents the desired number of ManagedClusters to be selected which meet the placement requirements. 1) If not @@ -466,6 +614,38 @@ spec: - type type: object type: array + decisionGroups: + description: List of decision groups determined by the placement and + DecisionStrategy. + items: + description: Present decision groups status based on the DecisionStrategy + definition. + properties: + clusterCount: + default: 0 + description: Total number of clusters in the decision group. + Clusters count is equal or less than the clusterPerDecisionGroups + defined in the decision strategy. + format: int32 + type: integer + decisionGroupIndex: + description: Present the decision group index. If there is no + decision strategy defined all placement decisions will be + in group index 0 + format: int32 + type: integer + decisionGroupName: + description: Decision group name that is defined in the DecisionStrategy's + DecisionGroup. + type: string + decisions: + description: List of placement decisions names associated with + the decision group + items: + type: string + type: array + type: object + type: array numberOfSelectedClusters: description: NumberOfSelectedClusters represents the number of selected ManagedClusters diff --git a/pkg/placement/controllers/scheduling/schedule.go b/pkg/placement/controllers/scheduling/schedule.go index 7c530d9a2..d6cfd2520 100644 --- a/pkg/placement/controllers/scheduling/schedule.go +++ b/pkg/placement/controllers/scheduling/schedule.go @@ -46,6 +46,12 @@ type Scheduler interface { ) (ScheduleResult, *framework.Status) } +// decision groups defines the group name and cluster decisions +type DecisionGroup struct { + DecisionGroupName string + ClusterDecisions []clusterapiv1beta1.ClusterDecision +} + type ScheduleResult interface { // FilterResults returns results for each filter FilterResults() []FilterResult @@ -59,6 +65,9 @@ type ScheduleResult interface { // Decisions returns the decisions of the schedule Decisions() []clusterapiv1beta1.ClusterDecision + // DecisionGroups returns the decision groups of the schedule + DecisionGroups() []DecisionGroup + // NumOfUnscheduled returns the number of unscheduled. NumOfUnscheduled() int @@ -431,6 +440,17 @@ func (r *scheduleResult) Decisions() []clusterapiv1beta1.ClusterDecision { return r.scheduledDecisions } +func (r *scheduleResult) DecisionGroups() []DecisionGroup { + // TODO: divide r.scheduledDecisions into decision groups by group strategy + decisionGroups := []DecisionGroup{} + group := DecisionGroup{ + DecisionGroupName: "", + ClusterDecisions: r.scheduledDecisions, + } + decisionGroups = append(decisionGroups, group) + return decisionGroups +} + func (r *scheduleResult) NumOfUnscheduled() int { return r.unscheduledDecisions } diff --git a/pkg/placement/controllers/scheduling/schedule_test.go b/pkg/placement/controllers/scheduling/schedule_test.go index 69872ec80..a33bbc155 100644 --- a/pkg/placement/controllers/scheduling/schedule_test.go +++ b/pkg/placement/controllers/scheduling/schedule_test.go @@ -152,12 +152,12 @@ func TestSchedule(t *testing.T) { testinghelpers.NewClusterSet(clusterSetName).Build(), testinghelpers.NewClusterSetBinding(placementNamespace, clusterSetName), testinghelpers.NewPlacementDecision(placementNamespace, placementDecisionName(placementName, 1)). - WithLabel(placementLabel, placementName). + WithLabel(clusterapiv1beta1.PlacementLabel, placementName). WithDecisions("cluster1", "cluster2").Build(), }, decisions: []runtime.Object{ testinghelpers.NewPlacementDecision(placementNamespace, placementDecisionName(placementName, 1)). - WithLabel(placementLabel, placementName). + WithLabel(clusterapiv1beta1.PlacementLabel, placementName). WithDecisions("cluster1", "cluster2").Build(), }, clusters: []*clusterapiv1.ManagedCluster{ @@ -399,7 +399,7 @@ func TestSchedule(t *testing.T) { testinghelpers.NewClusterSet(clusterSetName).Build(), testinghelpers.NewClusterSetBinding(placementNamespace, clusterSetName), testinghelpers.NewPlacementDecision(placementNamespace, placementDecisionName(placementName, 1)). - WithLabel(placementLabel, placementName). + WithLabel(clusterapiv1beta1.PlacementLabel, placementName). WithDecisions("cluster1").Build(), }, clusters: []*clusterapiv1.ManagedCluster{ @@ -408,7 +408,7 @@ func TestSchedule(t *testing.T) { }, decisions: []runtime.Object{ testinghelpers.NewPlacementDecision(placementNamespace, placementDecisionName(placementName, 1)). - WithLabel(placementLabel, placementName). + WithLabel(clusterapiv1beta1.PlacementLabel, placementName). WithDecisions("cluster1").Build(), }, expectedDecisions: []clusterapiv1beta1.ClusterDecision{ @@ -497,7 +497,7 @@ func TestSchedule(t *testing.T) { testinghelpers.NewPlacementDecision(placementNamespace, placementDecisionName("others", 2)). WithDecisions("cluster1", "cluster2").Build(), testinghelpers.NewPlacementDecision(placementNamespace, placementDecisionName(placementName, 1)). - WithLabel(placementLabel, placementName). + WithLabel(clusterapiv1beta1.PlacementLabel, placementName). WithDecisions("cluster3").Build(), }, decisions: []runtime.Object{ @@ -506,7 +506,7 @@ func TestSchedule(t *testing.T) { testinghelpers.NewPlacementDecision(placementNamespace, placementDecisionName("others", 2)). WithDecisions("cluster1", "cluster2").Build(), testinghelpers.NewPlacementDecision(placementNamespace, placementDecisionName(placementName, 1)). - WithLabel(placementLabel, placementName). + WithLabel(clusterapiv1beta1.PlacementLabel, placementName). WithDecisions("cluster3").Build(), }, clusters: []*clusterapiv1.ManagedCluster{ diff --git a/pkg/placement/controllers/scheduling/scheduling_controller.go b/pkg/placement/controllers/scheduling/scheduling_controller.go index 8fde71cf9..acc33ba3b 100644 --- a/pkg/placement/controllers/scheduling/scheduling_controller.go +++ b/pkg/placement/controllers/scheduling/scheduling_controller.go @@ -42,7 +42,6 @@ import ( const ( clusterSetLabel = "cluster.open-cluster-management.io/clusterset" - placementLabel = "cluster.open-cluster-management.io/placement" schedulingControllerName = "SchedulingController" schedulingControllerResyncName = "SchedulingControllerResync" maxNumOfClusterDecisions = 100 @@ -159,7 +158,7 @@ func NewSchedulingController( WithFilteredEventsInformersQueueKeyFunc(func(obj runtime.Object) string { accessor, _ := meta.Accessor(obj) labels := accessor.GetLabels() - placementName := labels[placementLabel] + placementName := labels[clusterapiv1beta1.PlacementLabel] return fmt.Sprintf("%s/%s", accessor.GetNamespace(), placementName) }, func(obj interface{}) bool { accessor, err := meta.Accessor(obj) @@ -167,7 +166,7 @@ func NewSchedulingController( return false } labels := accessor.GetLabels() - if _, ok := labels[placementLabel]; ok { + if _, ok := labels[clusterapiv1beta1.PlacementLabel]; ok { return true } return false @@ -256,12 +255,14 @@ func (c *schedulingController) syncPlacement(ctx context.Context, syncCtx factor syncCtx.Queue().AddAfter(key, *t) } - if err := c.bind(ctx, placement, scheduleResult.Decisions(), scheduleResult.PrioritizerScores(), status); err != nil { + // create/update placement decisions + decisionGroupStatus, err := c.bind(ctx, placement, scheduleResult.DecisionGroups(), scheduleResult.PrioritizerScores(), status) + if err != nil { return err } // update placement status if necessary to signal no bindings - if err := c.updateStatus(ctx, placement, int32(len(scheduleResult.Decisions())), misconfiguredCondition, satisfiedCondition); err != nil { + if err := c.updateStatus(ctx, placement, decisionGroupStatus, int32(len(scheduleResult.Decisions())), misconfiguredCondition, satisfiedCondition); err != nil { return err } @@ -356,11 +357,13 @@ func (c *schedulingController) getAvailableClusters(clusterSetNames []string) ([ func (c *schedulingController) updateStatus( ctx context.Context, placement *clusterapiv1beta1.Placement, + decisionGroupStatus []clusterapiv1beta1.DecisionGroupStatus, numberOfSelectedClusters int32, conditions ...metav1.Condition, ) error { newPlacement := placement.DeepCopy() newPlacement.Status.NumberOfSelectedClusters = numberOfSelectedClusters + newPlacement.Status.DecisionGroups = decisionGroupStatus for _, c := range conditions { meta.SetStatusCondition(&newPlacement.Status.Conditions, c) @@ -441,76 +444,65 @@ func newMisconfiguredCondition(status *framework.Status) metav1.Condition { // bind updates the cluster decisions in the status of the placementdecisions with the given // cluster decision slice. New placementdecisions will be created if no one exists. +// bind will also return the decision groups for placement status. func (c *schedulingController) bind( ctx context.Context, placement *clusterapiv1beta1.Placement, - clusterDecisions []clusterapiv1beta1.ClusterDecision, + decisionGroups []DecisionGroup, clusterScores PrioritizerScore, status *framework.Status, -) error { - // sort clusterdecisions by cluster name - sort.SliceStable(clusterDecisions, func(i, j int) bool { - return clusterDecisions[i].ClusterName < clusterDecisions[j].ClusterName - }) - - // split the cluster decisions into slices, the size of each slice cannot exceed - // maxNumOfClusterDecisions. - decisionSlices := [][]clusterapiv1beta1.ClusterDecision{} - remainingDecisions := clusterDecisions - for index := 0; len(remainingDecisions) > 0; index++ { - var decisionSlice []clusterapiv1beta1.ClusterDecision - switch { - case len(remainingDecisions) > maxNumOfClusterDecisions: - decisionSlice = remainingDecisions[0:maxNumOfClusterDecisions] - remainingDecisions = remainingDecisions[maxNumOfClusterDecisions:] - default: - decisionSlice = remainingDecisions - remainingDecisions = nil - } - decisionSlices = append(decisionSlices, decisionSlice) - } - // if decisionSlices is empty, append one empty slice. - // so that can create a PlacementDecision with empty decisions in status. - if len(decisionSlices) == 0 { - decisionSlices = append(decisionSlices, []clusterapiv1beta1.ClusterDecision{}) - } - - // bind cluster decision slices to placementdecisions. +) ([]clusterapiv1beta1.DecisionGroupStatus, error) { errs := []error{} - + placementDecisionIndex := 0 placementDecisionNames := sets.NewString() - for index, decisionSlice := range decisionSlices { - placementDecisionName := fmt.Sprintf("%s-decision-%d", placement.Name, index+1) - placementDecisionNames.Insert(placementDecisionName) - err := c.createOrUpdatePlacementDecision( - ctx, placement, placementDecisionName, decisionSlice, clusterScores, status) - if err != nil { - errs = append(errs, err) + decisionGroupStatus := []clusterapiv1beta1.DecisionGroupStatus{} + + // create/update placement decision for each decision group + for decisionGroupIndex, decisionGroup := range decisionGroups { + // sort clusterdecisions by cluster name + clusterDecisions := decisionGroup.ClusterDecisions + sort.SliceStable(clusterDecisions, func(i, j int) bool { + return clusterDecisions[i].ClusterName < clusterDecisions[j].ClusterName + }) + + // generate placement decisions, index starts from 1 + pds, groupStatus := c.generatePlacementDecisionsAndStatus(placement, decisionGroup, decisionGroupIndex+1, placementDecisionIndex+1) + + // create/update placement decisions + for _, pd := range pds { + placementDecisionNames.Insert(pd.Name) + err := c.createOrUpdatePlacementDecision(ctx, placement, pd, clusterScores, status) + if err != nil { + errs = append(errs, err) + } } + decisionGroupStatus = append(decisionGroupStatus, *groupStatus) + placementDecisionIndex += len(pds) } + if len(errs) != 0 { - return errorhelpers.NewMultiLineAggregate(errs) + return decisionGroupStatus, errorhelpers.NewMultiLineAggregate(errs) } // query all placementdecisions of the placement - requirement, err := labels.NewRequirement(placementLabel, selection.Equals, []string{placement.Name}) + requirement, err := labels.NewRequirement(clusterapiv1beta1.PlacementLabel, selection.Equals, []string{placement.Name}) if err != nil { - return err + return decisionGroupStatus, err } labelSelector := labels.NewSelector().Add(*requirement) - placementDecisions, err := c.placementDecisionLister.PlacementDecisions(placement.Namespace).List(labelSelector) + pds, err := c.placementDecisionLister.PlacementDecisions(placement.Namespace).List(labelSelector) if err != nil { - return err + return decisionGroupStatus, err } // delete redundant placementdecisions errs = []error{} - for _, placementDecision := range placementDecisions { - if placementDecisionNames.Has(placementDecision.Name) { + for _, pd := range pds { + if placementDecisionNames.Has(pd.Name) { continue } err := c.clusterClient.ClusterV1beta1().PlacementDecisions( - placementDecision.Namespace).Delete(ctx, placementDecision.Name, metav1.DeleteOptions{}) + pd.Namespace).Delete(ctx, pd.Name, metav1.DeleteOptions{}) if errors.IsNotFound(err) { continue } @@ -518,11 +510,73 @@ func (c *schedulingController) bind( errs = append(errs, err) } c.recorder.Eventf( - placement, placementDecision, corev1.EventTypeNormal, + placement, pd, corev1.EventTypeNormal, "DecisionDelete", "DecisionDeleted", - "Decision %s is deleted with placement %s in namespace %s", placementDecision.Name, placement.Name, placement.Namespace) + "Decision %s is deleted with placement %s in namespace %s", pd.Name, placement.Name, placement.Namespace) + } + return decisionGroupStatus, errorhelpers.NewMultiLineAggregate(errs) +} + +func (c *schedulingController) generatePlacementDecisionsAndStatus( + placement *clusterapiv1beta1.Placement, + decisionGroup DecisionGroup, + decisionGroupIndex, placementDecisionIndex int, +) ([]*clusterapiv1beta1.PlacementDecision, *clusterapiv1beta1.DecisionGroupStatus) { + // split the cluster decisions into slices, the size of each slice cannot exceed + // maxNumOfClusterDecisions. + decisionSlices := [][]clusterapiv1beta1.ClusterDecision{} + remainingDecisions := decisionGroup.ClusterDecisions + for index := 0; len(remainingDecisions) > 0; index++ { + var decisionSlice []clusterapiv1beta1.ClusterDecision + switch { + case len(remainingDecisions) > maxNumOfClusterDecisions: + decisionSlice = remainingDecisions[0:maxNumOfClusterDecisions] + remainingDecisions = remainingDecisions[maxNumOfClusterDecisions:] + default: + decisionSlice = remainingDecisions + remainingDecisions = nil + } + decisionSlices = append(decisionSlices, decisionSlice) + } + + // if decisionSlices is empty, append one empty slice. + // so that can create a PlacementDecision with empty decisions in status. + if len(decisionSlices) == 0 { + decisionSlices = append(decisionSlices, []clusterapiv1beta1.ClusterDecision{}) + } + + placementDecisionNames := []string{} + placementDecisions := []*clusterapiv1beta1.PlacementDecision{} + for index, decisionSlice := range decisionSlices { + placementDecisionName := fmt.Sprintf("%s-decision-%d", placement.Name, placementDecisionIndex+index) + owner := metav1.NewControllerRef(placement, clusterapiv1beta1.GroupVersion.WithKind("Placement")) + placementDecision := &clusterapiv1beta1.PlacementDecision{ + ObjectMeta: metav1.ObjectMeta{ + Name: placementDecisionName, + Namespace: placement.Namespace, + Labels: map[string]string{ + clusterapiv1beta1.PlacementLabel: placement.Name, + clusterapiv1beta1.DecisionGroupNameLabel: decisionGroup.DecisionGroupName, + clusterapiv1beta1.DecisionGroupIndexLabel: fmt.Sprint(decisionGroupIndex), + }, + OwnerReferences: []metav1.OwnerReference{*owner}, + }, + Status: clusterapiv1beta1.PlacementDecisionStatus{ + Decisions: decisionSlice, + }, + } + placementDecisions = append(placementDecisions, placementDecision) + placementDecisionNames = append(placementDecisionNames, placementDecisionName) } - return errorhelpers.NewMultiLineAggregate(errs) + + decisionGroupStatus := &clusterapiv1beta1.DecisionGroupStatus{ + DecisionGroupIndex: int32(decisionGroupIndex), + DecisionGroupName: decisionGroup.DecisionGroupName, + Decisions: placementDecisionNames, + ClustersCount: int32(len(decisionGroup.ClusterDecisions)), + } + + return placementDecisions, decisionGroupStatus } // createOrUpdatePlacementDecision creates a new PlacementDecision if it does not exist and @@ -530,52 +584,43 @@ func (c *schedulingController) bind( func (c *schedulingController) createOrUpdatePlacementDecision( ctx context.Context, placement *clusterapiv1beta1.Placement, - placementDecisionName string, - clusterDecisions []clusterapiv1beta1.ClusterDecision, + placementDecision *clusterapiv1beta1.PlacementDecision, clusterScores PrioritizerScore, status *framework.Status, ) error { + placementDecisionName := placementDecision.Name + clusterDecisions := placementDecision.Status.Decisions + if len(clusterDecisions) > maxNumOfClusterDecisions { return fmt.Errorf("the number of clusterdecisions %q exceeds the max limitation %q", len(clusterDecisions), maxNumOfClusterDecisions) } - placementDecision, err := c.placementDecisionLister.PlacementDecisions(placement.Namespace).Get(placementDecisionName) + existPlacementDecision, err := c.placementDecisionLister.PlacementDecisions(placement.Namespace).Get(placementDecisionName) switch { case errors.IsNotFound(err): // create the placementdecision if not exists - owner := metav1.NewControllerRef(placement, clusterapiv1beta1.GroupVersion.WithKind("Placement")) - placementDecision = &clusterapiv1beta1.PlacementDecision{ - ObjectMeta: metav1.ObjectMeta{ - Name: placementDecisionName, - Namespace: placement.Namespace, - Labels: map[string]string{ - placementLabel: placement.Name, - }, - OwnerReferences: []metav1.OwnerReference{*owner}, - }, - } var err error - placementDecision, err = c.clusterClient.ClusterV1beta1().PlacementDecisions( + existPlacementDecision, err = c.clusterClient.ClusterV1beta1().PlacementDecisions( placement.Namespace).Create(ctx, placementDecision, metav1.CreateOptions{}) if err != nil { return err } c.recorder.Eventf( - placement, placementDecision, corev1.EventTypeNormal, + placement, existPlacementDecision, corev1.EventTypeNormal, "DecisionCreate", "DecisionCreated", - "Decision %s is created with placement %s in namespace %s", placementDecision.Name, placement.Name, placement.Namespace) + "Decision %s is created with placement %s in namespace %s", existPlacementDecision.Name, placement.Name, placement.Namespace) case err != nil: return err } // update the status of the placementdecision if decisions change - if apiequality.Semantic.DeepEqual(placementDecision.Status.Decisions, clusterDecisions) { + if apiequality.Semantic.DeepEqual(existPlacementDecision.Status.Decisions, clusterDecisions) { return nil } - newPlacementDecision := placementDecision.DeepCopy() + newPlacementDecision := existPlacementDecision.DeepCopy() newPlacementDecision.Status.Decisions = clusterDecisions - newPlacementDecision, err = c.clusterClient.ClusterV1beta1().PlacementDecisions(newPlacementDecision.Namespace). + _, err = c.clusterClient.ClusterV1beta1().PlacementDecisions(newPlacementDecision.Namespace). UpdateStatus(ctx, newPlacementDecision, metav1.UpdateOptions{}) if err != nil { @@ -585,16 +630,16 @@ func (c *schedulingController) createOrUpdatePlacementDecision( // update the event with warning if status.Code() == framework.Warning { c.recorder.Eventf( - placement, placementDecision, corev1.EventTypeWarning, + placement, existPlacementDecision, corev1.EventTypeWarning, "DecisionUpdate", "DecisionUpdated", - "Decision %s is updated with placement %s in namespace %s: %s in plugin %s", placementDecision.Name, placement.Name, placement.Namespace, + "Decision %s is updated with placement %s in namespace %s: %s in plugin %s", existPlacementDecision.Name, placement.Name, placement.Namespace, status.Message(), status.Plugin()) } else { c.recorder.Eventf( - placement, placementDecision, corev1.EventTypeNormal, + placement, existPlacementDecision, corev1.EventTypeNormal, "DecisionUpdate", "DecisionUpdated", - "Decision %s is updated with placement %s in namespace %s", placementDecision.Name, placement.Name, placement.Namespace) + "Decision %s is updated with placement %s in namespace %s", existPlacementDecision.Name, placement.Name, placement.Namespace) } // update the event with prioritizer score. @@ -610,7 +655,7 @@ func (c *schedulingController) createOrUpdatePlacementDecision( } c.recorder.Eventf( - placement, placementDecision, corev1.EventTypeNormal, + placement, existPlacementDecision, corev1.EventTypeNormal, "ScoreUpdate", "ScoreUpdated", scoreStr) diff --git a/pkg/placement/controllers/scheduling/scheduling_controller_test.go b/pkg/placement/controllers/scheduling/scheduling_controller_test.go index aa79ca336..eaa0bb205 100644 --- a/pkg/placement/controllers/scheduling/scheduling_controller_test.go +++ b/pkg/placement/controllers/scheduling/scheduling_controller_test.go @@ -65,9 +65,9 @@ func TestSchedulingController_sync(t *testing.T) { }, validateActions: func(t *testing.T, actions []clienttesting.Action) { // check if PlacementDecision has been updated - testingcommon.AssertActions(t, actions, "create", "update", "update") + testingcommon.AssertActions(t, actions, "create", "update") // check if Placement has been updated - actual := actions[2].(clienttesting.UpdateActionImpl).Object + actual := actions[1].(clienttesting.UpdateActionImpl).Object placement, ok := actual.(*clusterapiv1beta1.Placement) if !ok { t.Errorf("expected Placement was updated") @@ -107,9 +107,9 @@ func TestSchedulingController_sync(t *testing.T) { }, validateActions: func(t *testing.T, actions []clienttesting.Action) { // check if PlacementDecision has been updated - testingcommon.AssertActions(t, actions, "create", "update", "update") + testingcommon.AssertActions(t, actions, "create", "update") // check if Placement has been updated - actual := actions[2].(clienttesting.UpdateActionImpl).Object + actual := actions[1].(clienttesting.UpdateActionImpl).Object placement, ok := actual.(*clusterapiv1beta1.Placement) if !ok { t.Errorf("expected Placement was updated") @@ -257,13 +257,13 @@ func TestSchedulingController_sync(t *testing.T) { { name: "placement status not changed", placement: testinghelpers.NewPlacement(placementNamespace, placementName). - WithNumOfSelectedClusters(3).WithSatisfiedCondition(3, 0).WithMisconfiguredCondition(metav1.ConditionFalse).Build(), + WithNumOfSelectedClusters(3, placementName).WithSatisfiedCondition(3, 0).WithMisconfiguredCondition(metav1.ConditionFalse).Build(), initObjs: []runtime.Object{ testinghelpers.NewClusterSet("clusterset1").Build(), testinghelpers.NewClusterSetBinding(placementNamespace, "clusterset1"), testinghelpers.NewManagedCluster("cluster1").WithLabel(clusterSetLabel, "clusterset1").Build(), testinghelpers.NewPlacementDecision(placementNamespace, placementDecisionName(placementName, 1)). - WithLabel(placementLabel, placementName). + WithLabel(clusterapiv1beta1.PlacementLabel, placementName). WithDecisions("cluster1", "cluster2", "cluster3").Build(), }, scheduleResult: &scheduleResult{ @@ -731,17 +731,17 @@ func TestBind(t *testing.T) { placementName := "placement1" cases := []struct { - name string - initObjs []runtime.Object - clusterDecisions []clusterapiv1beta1.ClusterDecision - validateActions func(t *testing.T, actions []clienttesting.Action) + name string + initObjs []runtime.Object + decisionGroups []DecisionGroup + validateActions func(t *testing.T, actions []clienttesting.Action) }{ { - name: "create single placementdecision", - clusterDecisions: newClusterDecisions(10), + name: "create single placementdecision", + decisionGroups: []DecisionGroup{{ClusterDecisions: newClusterDecisions(10)}}, validateActions: func(t *testing.T, actions []clienttesting.Action) { - testingcommon.AssertActions(t, actions, "create", "update") - actual := actions[1].(clienttesting.UpdateActionImpl).Object + testingcommon.AssertActions(t, actions, "create") + actual := actions[0].(clienttesting.CreateActionImpl).Object placementDecision, ok := actual.(*clusterapiv1beta1.PlacementDecision) if !ok { t.Errorf("expected PlacementDecision was updated") @@ -750,19 +750,19 @@ func TestBind(t *testing.T) { }, }, { - name: "create multiple placementdecisions", - clusterDecisions: newClusterDecisions(101), + name: "create multiple placementdecisions", + decisionGroups: []DecisionGroup{{ClusterDecisions: newClusterDecisions(101)}}, validateActions: func(t *testing.T, actions []clienttesting.Action) { - testingcommon.AssertActions(t, actions, "create", "update", "create", "update") + testingcommon.AssertActions(t, actions, "create", "create") selectedClusters := newSelectedClusters(101) - actual := actions[1].(clienttesting.UpdateActionImpl).Object + actual := actions[0].(clienttesting.CreateActionImpl).Object placementDecision, ok := actual.(*clusterapiv1beta1.PlacementDecision) if !ok { t.Errorf("expected PlacementDecision was updated") } assertClustersSelected(t, placementDecision.Status.Decisions, selectedClusters[0:100]...) - actual = actions[3].(clienttesting.UpdateActionImpl).Object + actual = actions[1].(clienttesting.CreateActionImpl).Object placementDecision, ok = actual.(*clusterapiv1beta1.PlacementDecision) if !ok { t.Errorf("expected PlacementDecision was updated") @@ -771,8 +771,8 @@ func TestBind(t *testing.T) { }, }, { - name: "create empty placementdecision", - clusterDecisions: newClusterDecisions(0), + name: "create empty placementdecision", + decisionGroups: []DecisionGroup{{ClusterDecisions: newClusterDecisions(0)}}, validateActions: func(t *testing.T, actions []clienttesting.Action) { testingcommon.AssertActions(t, actions, "create") actual := actions[0].(clienttesting.CreateActionImpl).Object @@ -786,30 +786,30 @@ func TestBind(t *testing.T) { }, }, { - name: "no change", - clusterDecisions: newClusterDecisions(128), + name: "no change", + decisionGroups: []DecisionGroup{{ClusterDecisions: newClusterDecisions(128)}}, initObjs: []runtime.Object{ testinghelpers.NewPlacementDecision(placementNamespace, placementDecisionName(placementName, 1)). - WithLabel(placementLabel, placementName). + WithLabel(clusterapiv1beta1.PlacementLabel, placementName). WithDecisions(newSelectedClusters(128)[:100]...).Build(), testinghelpers.NewPlacementDecision(placementNamespace, placementDecisionName(placementName, 2)). - WithLabel(placementLabel, placementName). + WithLabel(clusterapiv1beta1.PlacementLabel, placementName). WithDecisions(newSelectedClusters(128)[100:]...).Build(), }, validateActions: testingcommon.AssertNoActions, }, { - name: "update one of placementdecisions", - clusterDecisions: newClusterDecisions(128), + name: "update one of placementdecisions", + decisionGroups: []DecisionGroup{{ClusterDecisions: newClusterDecisions(128)}}, initObjs: []runtime.Object{ testinghelpers.NewPlacementDecision(placementNamespace, placementDecisionName(placementName, 1)). - WithLabel(placementLabel, placementName). + WithLabel(clusterapiv1beta1.PlacementLabel, placementName). WithDecisions(newSelectedClusters(128)[:100]...).Build(), }, validateActions: func(t *testing.T, actions []clienttesting.Action) { - testingcommon.AssertActions(t, actions, "create", "update") + testingcommon.AssertActions(t, actions, "create") selectedClusters := newSelectedClusters(128) - actual := actions[1].(clienttesting.UpdateActionImpl).Object + actual := actions[0].(clienttesting.CreateActionImpl).Object placementDecision, ok := actual.(*clusterapiv1beta1.PlacementDecision) if !ok { t.Errorf("expected PlacementDecision was updated") @@ -818,14 +818,14 @@ func TestBind(t *testing.T) { }, }, { - name: "delete redundant placementdecisions", - clusterDecisions: newClusterDecisions(10), + name: "delete redundant placementdecisions", + decisionGroups: []DecisionGroup{{ClusterDecisions: newClusterDecisions(10)}}, initObjs: []runtime.Object{ testinghelpers.NewPlacementDecision(placementNamespace, placementDecisionName(placementName, 1)). - WithLabel(placementLabel, placementName). + WithLabel(clusterapiv1beta1.PlacementLabel, placementName). WithDecisions(newSelectedClusters(128)[:100]...).Build(), testinghelpers.NewPlacementDecision(placementNamespace, placementDecisionName(placementName, 2)). - WithLabel(placementLabel, placementName). + WithLabel(clusterapiv1beta1.PlacementLabel, placementName). WithDecisions(newSelectedClusters(128)[100:]...).Build(), }, validateActions: func(t *testing.T, actions []clienttesting.Action) { @@ -839,14 +839,14 @@ func TestBind(t *testing.T) { }, }, { - name: "delete all placementdecisions and leave one empty placementdecision", - clusterDecisions: newClusterDecisions(0), + name: "delete all placementdecisions and leave one empty placementdecision", + decisionGroups: []DecisionGroup{{ClusterDecisions: newClusterDecisions(0)}}, initObjs: []runtime.Object{ testinghelpers.NewPlacementDecision(placementNamespace, placementDecisionName(placementName, 1)). - WithLabel(placementLabel, placementName). + WithLabel(clusterapiv1beta1.PlacementLabel, placementName). WithDecisions(newSelectedClusters(128)[:100]...).Build(), testinghelpers.NewPlacementDecision(placementNamespace, placementDecisionName(placementName, 2)). - WithLabel(placementLabel, placementName). + WithLabel(clusterapiv1beta1.PlacementLabel, placementName). WithDecisions(newSelectedClusters(128)[100:]...).Build(), }, validateActions: func(t *testing.T, actions []clienttesting.Action) { @@ -894,10 +894,10 @@ func TestBind(t *testing.T) { recorder: kevents.NewFakeRecorder(100), } - err := ctrl.bind( + _, err := ctrl.bind( context.TODO(), testinghelpers.NewPlacement(placementNamespace, placementName).Build(), - c.clusterDecisions, + c.decisionGroups, nil, nil, ) diff --git a/pkg/placement/debugger/debugger_test.go b/pkg/placement/debugger/debugger_test.go index e498295b9..70d0c01bc 100644 --- a/pkg/placement/debugger/debugger_test.go +++ b/pkg/placement/debugger/debugger_test.go @@ -48,6 +48,10 @@ func (r *testResult) Decisions() []clusterapiv1beta1.ClusterDecision { return []clusterapiv1beta1.ClusterDecision{} } +func (r *testResult) DecisionGroups() []scheduling.DecisionGroup { + return []scheduling.DecisionGroup{} +} + func (r *testResult) NumOfUnscheduled() int { return 0 } diff --git a/pkg/placement/helpers/testing/builders.go b/pkg/placement/helpers/testing/builders.go index 46aad4e5b..6ffcb1960 100644 --- a/pkg/placement/helpers/testing/builders.go +++ b/pkg/placement/helpers/testing/builders.go @@ -118,8 +118,16 @@ func (b *placementBuilder) AddToleration(toleration *clusterapiv1beta1.Toleratio return b } -func (b *placementBuilder) WithNumOfSelectedClusters(nosc int) *placementBuilder { +func (b *placementBuilder) WithNumOfSelectedClusters(nosc int, placementName string) *placementBuilder { b.placement.Status.NumberOfSelectedClusters = int32(nosc) + b.placement.Status.DecisionGroups = []clusterapiv1beta1.DecisionGroupStatus{ + { + DecisionGroupIndex: 1, + DecisionGroupName: "", + ClustersCount: 3, + Decisions: []string{fmt.Sprintf("%s-decision-%d", placementName, 1)}, + }, + } return b } diff --git a/vendor/modules.txt b/vendor/modules.txt index cd0a41a7e..38ac17307 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1426,7 +1426,7 @@ open-cluster-management.io/addon-framework/pkg/index open-cluster-management.io/addon-framework/pkg/manager/controllers/addonconfiguration open-cluster-management.io/addon-framework/pkg/manager/controllers/addonowner open-cluster-management.io/addon-framework/pkg/utils -# open-cluster-management.io/api v0.11.1-0.20230609103311-088e8fe86139 +# open-cluster-management.io/api v0.11.1-0.20230621023453-8f11dd3ede06 ## explicit; go 1.19 open-cluster-management.io/api/addon/v1alpha1 open-cluster-management.io/api/client/addon/clientset/versioned diff --git a/vendor/open-cluster-management.io/api/cluster/v1beta1/0000_02_clusters.open-cluster-management.io_placements.crd.yaml b/vendor/open-cluster-management.io/api/cluster/v1beta1/0000_02_clusters.open-cluster-management.io_placements.crd.yaml index a483f8725..b22ab85df 100644 --- a/vendor/open-cluster-management.io/api/cluster/v1beta1/0000_02_clusters.open-cluster-management.io_placements.crd.yaml +++ b/vendor/open-cluster-management.io/api/cluster/v1beta1/0000_02_clusters.open-cluster-management.io_placements.crd.yaml @@ -69,6 +69,154 @@ spec: items: type: string type: array + decisionStrategy: + description: DecisionStrategy divide the created placement decision + to groups and define number of clusters per decision group. + properties: + groupStrategy: + description: GroupStrategy define strategies to divide selected + clusters to decision groups. + properties: + clustersPerDecisionGroup: + anyOf: + - type: integer + - type: string + default: 100% + description: ClustersPerDecisionGroup is a specific number + or percentage of the total selected clusters. The specific + number will divide the placementDecisions to decisionGroups + each group has max number of clusters equal to that specific + number. The percentage will divide the placementDecisions + to decisionGroups each group has max number of clusters + based on the total num of selected clusters and percentage. + ex; for a total 100 clusters selected, ClustersPerDecisionGroup + equal to 20% will divide the placement decision to 5 groups + each group should have 20 clusters. Default is having all + clusters in a single group. If the DecisionGroups field + defined, it will be considered first to create the decisionGroups + then the ClustersPerDecisionGroup will be used to determine + the rest of decisionGroups. + x-kubernetes-int-or-string: true + decisionGroups: + description: DecisionGroups represents a list of predefined + groups to put decision results. + items: + description: DecisionGroup define a subset of clusters that + will be added to placementDecisions with groupName label. + properties: + groupClusterSelector: + description: LabelSelector to select clusters subset + by label. + properties: + claimSelector: + description: ClaimSelector represents a selector + of ManagedClusters by clusterClaims in status + properties: + matchExpressions: + description: matchExpressions is a list of cluster + claim 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 + type: object + labelSelector: + description: LabelSelector represents a selector + of ManagedClusters by label + 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 + x-kubernetes-map-type: atomic + type: object + groupName: + description: Group name to be added as label value to + the created placement Decisions labels with label + key cluster.open-cluster-management.io/decision-group-name + pattern: ^[a-zA-Z0-9][-A-Za-z0-9_.]{0,61}[a-zA-Z0-9]$ + type: string + required: + - groupClusterSelector + - groupName + type: object + type: array + required: + - clustersPerDecisionGroup + type: object + type: object numberOfClusters: description: NumberOfClusters represents the desired number of ManagedClusters to be selected which meet the placement requirements. 1) If not @@ -466,6 +614,38 @@ spec: - type type: object type: array + decisionGroups: + description: List of decision groups determined by the placement and + DecisionStrategy. + items: + description: Present decision groups status based on the DecisionStrategy + definition. + properties: + clusterCount: + default: 0 + description: Total number of clusters in the decision group. + Clusters count is equal or less than the clusterPerDecisionGroups + defined in the decision strategy. + format: int32 + type: integer + decisionGroupIndex: + description: Present the decision group index. If there is no + decision strategy defined all placement decisions will be + in group index 0 + format: int32 + type: integer + decisionGroupName: + description: Decision group name that is defined in the DecisionStrategy's + DecisionGroup. + type: string + decisions: + description: List of placement decisions names associated with + the decision group + items: + type: string + type: array + type: object + type: array numberOfSelectedClusters: description: NumberOfSelectedClusters represents the number of selected ManagedClusters diff --git a/vendor/open-cluster-management.io/api/cluster/v1beta1/types_placement.go b/vendor/open-cluster-management.io/api/cluster/v1beta1/types_placement.go index 9f4aabd6e..a5d1f43d5 100644 --- a/vendor/open-cluster-management.io/api/cluster/v1beta1/types_placement.go +++ b/vendor/open-cluster-management.io/api/cluster/v1beta1/types_placement.go @@ -2,6 +2,7 @@ package v1beta1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" v1 "open-cluster-management.io/api/cluster/v1" ) @@ -94,6 +95,49 @@ type PlacementSpec struct { // certain taints to be selected by placements with matching tolerations. // +optional Tolerations []Toleration `json:"tolerations,omitempty"` + + // DecisionStrategy divide the created placement decision to groups and define number of clusters per decision group. + // +optional + DecisionStrategy DecisionStrategy `json:"decisionStrategy,omitempty"` +} + +// DecisionGroup define a subset of clusters that will be added to placementDecisions with groupName label. +type DecisionGroup struct { + // Group name to be added as label value to the created placement Decisions labels with label key cluster.open-cluster-management.io/decision-group-name + // +kubebuilder:validation:Required + // +kubebuilder:validation:Pattern="^[a-zA-Z0-9][-A-Za-z0-9_.]{0,61}[a-zA-Z0-9]$" + // +required + GroupName string `json:"groupName,omitempty"` + + // LabelSelector to select clusters subset by label. + // +kubebuilder:validation:Required + // +required + ClusterSelector ClusterSelector `json:"groupClusterSelector,omitempty"` +} + +// Group the created placementDecision into decision groups based on the number of clusters per decision group. +type GroupStrategy struct { + // DecisionGroups represents a list of predefined groups to put decision results. + // +optional + DecisionGroups []DecisionGroup `json:"decisionGroups,omitempty"` + + // ClustersPerDecisionGroup is a specific number or percentage of the total selected clusters. + // The specific number will divide the placementDecisions to decisionGroups each group has max number of clusters equal to that specific number. + // The percentage will divide the placementDecisions to decisionGroups each group has max number of clusters based on the total num of selected clusters and percentage. + // ex; for a total 100 clusters selected, ClustersPerDecisionGroup equal to 20% will divide the placement decision to 5 groups each group should have 20 clusters. + // Default is having all clusters in a single group. + // If the DecisionGroups field defined, it will be considered first to create the decisionGroups then the ClustersPerDecisionGroup will be used to determine the rest of decisionGroups. + // +kubebuilder:validation:Required + // +kubebuilder:default:="100%" + // +optional + ClustersPerDecisionGroup intstr.IntOrString `json:"clustersPerDecisionGroup,omitempty"` +} + +// DecisionStrategy divide the created placement decision to groups and define number of clusters per decision group. +type DecisionStrategy struct { + // GroupStrategy define strategies to divide selected clusters to decision groups. + // +optional + GroupStrategy GroupStrategy `json:"groupStrategy,omitempty"` } // ClusterPredicate represents a predicate to select ManagedClusters. @@ -329,11 +373,35 @@ const ( TolerationOpEqual TolerationOperator = "Equal" ) +// Present decision groups status based on the DecisionStrategy definition. +type DecisionGroupStatus struct { + // Present the decision group index. If there is no decision strategy defined all placement decisions will be in group index 0 + // +optional + DecisionGroupIndex int32 `json:"decisionGroupIndex"` + + // Decision group name that is defined in the DecisionStrategy's DecisionGroup. + // +optional + DecisionGroupName string `json:"decisionGroupName"` + + // List of placement decisions names associated with the decision group + // +optional + Decisions []string `json:"decisions"` + + // Total number of clusters in the decision group. Clusters count is equal or less than the clusterPerDecisionGroups defined in the decision strategy. + // +kubebuilder:default:=0 + // +optional + ClustersCount int32 `json:"clusterCount"` +} + type PlacementStatus struct { // NumberOfSelectedClusters represents the number of selected ManagedClusters // +optional NumberOfSelectedClusters int32 `json:"numberOfSelectedClusters"` + // List of decision groups determined by the placement and DecisionStrategy. + // +optional + DecisionGroups []DecisionGroupStatus `json:"decisionGroups"` + // Conditions contains the different condition status for this Placement. // +optional Conditions []metav1.Condition `json:"conditions"` diff --git a/vendor/open-cluster-management.io/api/cluster/v1beta1/types_placementdecision.go b/vendor/open-cluster-management.io/api/cluster/v1beta1/types_placementdecision.go index e9072c8d6..b32b11e9b 100644 --- a/vendor/open-cluster-management.io/api/cluster/v1beta1/types_placementdecision.go +++ b/vendor/open-cluster-management.io/api/cluster/v1beta1/types_placementdecision.go @@ -26,9 +26,14 @@ type PlacementDecision struct { Status PlacementDecisionStatus `json:"status,omitempty"` } -// The placementDecsion label name holding the placement name +// The placementDecsion labels const ( + // Placement owner name. PlacementLabel string = "cluster.open-cluster-management.io/placement" + // decision group index. + DecisionGroupIndexLabel string = "cluster.open-cluster-management.io/decision-group-index" + // decision group name. + DecisionGroupNameLabel string = "cluster.open-cluster-management.io/decision-group-name" ) // PlacementDecisionStatus represents the current status of the PlacementDecision. diff --git a/vendor/open-cluster-management.io/api/cluster/v1beta1/zz_generated.swagger_doc_generated.go b/vendor/open-cluster-management.io/api/cluster/v1beta1/zz_generated.swagger_doc_generated.go index b8c70231c..7a372ad08 100644 --- a/vendor/open-cluster-management.io/api/cluster/v1beta1/zz_generated.swagger_doc_generated.go +++ b/vendor/open-cluster-management.io/api/cluster/v1beta1/zz_generated.swagger_doc_generated.go @@ -135,6 +135,47 @@ func (ClusterSelector) SwaggerDoc() map[string]string { return map_ClusterSelector } +var map_DecisionGroup = map[string]string{ + "": "DecisionGroup define a subset of clusters that will be added to placementDecisions with groupName label.", + "groupName": "Group name to be added as label value to the created placement Decisions labels with label key cluster.open-cluster-management.io/decision-group-name", + "groupClusterSelector": "LabelSelector to select clusters subset by label.", +} + +func (DecisionGroup) SwaggerDoc() map[string]string { + return map_DecisionGroup +} + +var map_DecisionGroupStatus = map[string]string{ + "": "Present decision groups status based on the DecisionStrategy definition.", + "decisionGroupIndex": "Present the decision group index. If there is no decision strategy defined all placement decisions will be in group index 0", + "decisionGroupName": "Decision group name that is defined in the DecisionStrategy's DecisionGroup.", + "decisions": "List of placement decisions names associated with the decision group", + "clusterCount": "Total number of clusters in the decision group. Clusters count is equal or less than the clusterPerDecisionGroups defined in the decision strategy.", +} + +func (DecisionGroupStatus) SwaggerDoc() map[string]string { + return map_DecisionGroupStatus +} + +var map_DecisionStrategy = map[string]string{ + "": "DecisionStrategy divide the created placement decision to groups and define number of clusters per decision group.", + "groupStrategy": "GroupStrategy define strategies to divide selected clusters to decision groups.", +} + +func (DecisionStrategy) SwaggerDoc() map[string]string { + return map_DecisionStrategy +} + +var map_GroupStrategy = map[string]string{ + "": "Group the created placementDecision into decision groups based on the number of clusters per decision group.", + "decisionGroups": "DecisionGroups represents a list of predefined groups to put decision results.", + "clustersPerDecisionGroup": "ClustersPerDecisionGroup is a specific number or percentage of the total selected clusters. The specific number will divide the placementDecisions to decisionGroups each group has max number of clusters equal to that specific number. The percentage will divide the placementDecisions to decisionGroups each group has max number of clusters based on the total num of selected clusters and percentage. ex; for a total 100 clusters selected, ClustersPerDecisionGroup equal to 20% will divide the placement decision to 5 groups each group should have 20 clusters. Default is having all clusters in a single group. If the DecisionGroups field defined, it will be considered first to create the decisionGroups then the ClustersPerDecisionGroup will be used to determine the rest of decisionGroups.", +} + +func (GroupStrategy) SwaggerDoc() map[string]string { + return map_GroupStrategy +} + var map_Placement = map[string]string{ "": "Placement defines a rule to select a set of ManagedClusters from the ManagedClusterSets bound to the placement namespace.\n\nHere is how the placement policy combines with other selection methods to determine a matching list of ManagedClusters:\n 1. Kubernetes clusters are registered with hub as cluster-scoped ManagedClusters;\n 2. ManagedClusters are organized into cluster-scoped ManagedClusterSets;\n 3. ManagedClusterSets are bound to workload namespaces;\n 4. Namespace-scoped Placements specify a slice of ManagedClusterSets which select a working set\n of potential ManagedClusters;\n 5. Then Placements subselect from that working set using label/claim selection.\n\nNo ManagedCluster will be selected if no ManagedClusterSet is bound to the placement namespace. User is able to bind a ManagedClusterSet to a namespace by creating a ManagedClusterSetBinding in that namespace if they have a RBAC rule to CREATE on the virtual subresource of `managedclustersets/bind`.\n\nA slice of PlacementDecisions with label cluster.open-cluster-management.io/placement={placement name} will be created to represent the ManagedClusters selected by this placement.\n\nIf a ManagedCluster is selected and added into the PlacementDecisions, other components may apply workload on it; once it is removed from the PlacementDecisions, the workload applied on this ManagedCluster should be evicted accordingly.", "spec": "Spec defines the attributes of Placement.", @@ -163,6 +204,7 @@ var map_PlacementSpec = map[string]string{ "prioritizerPolicy": "PrioritizerPolicy defines the policy of the prioritizers. If this field is unset, then default prioritizer mode and configurations are used. Referring to PrioritizerPolicy to see more description about Mode and Configurations.", "spreadPolicy": "SpreadPolicy defines how placement decisions should be distributed among a set of ManagedClusters.", "tolerations": "Tolerations are applied to placements, and allow (but do not require) the managed clusters with certain taints to be selected by placements with matching tolerations.", + "decisionStrategy": "DecisionStrategy divide the created placement decision to groups and define number of clusters per decision group.", } func (PlacementSpec) SwaggerDoc() map[string]string { @@ -171,6 +213,7 @@ func (PlacementSpec) SwaggerDoc() map[string]string { var map_PlacementStatus = map[string]string{ "numberOfSelectedClusters": "NumberOfSelectedClusters represents the number of selected ManagedClusters", + "decisionGroups": "List of decision groups determined by the placement and DecisionStrategy.", "conditions": "Conditions contains the different condition status for this Placement.", }