Skip to content

Commit

Permalink
Add cmd to create placement (#331)
Browse files Browse the repository at this point in the history
* Add cmd to create placement

Signed-off-by: Jian Qiu <jqiu@redhat.com>

* Add unit test

Signed-off-by: Jian Qiu <jqiu@redhat.com>

* Upgrade clusterset api to v1beta2

Signed-off-by: Jian Qiu <jqiu@redhat.com>

---------

Signed-off-by: Jian Qiu <jqiu@redhat.com>
  • Loading branch information
qiujian16 authored Apr 24, 2023
1 parent 2a9f45f commit 95f9d75
Show file tree
Hide file tree
Showing 9 changed files with 365 additions and 20 deletions.
10 changes: 5 additions & 5 deletions pkg/cmd/clusterset/bind/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clusterclientset "open-cluster-management.io/api/client/cluster/clientset/versioned"
clusterapiv1beta1 "open-cluster-management.io/api/cluster/v1beta1"
clusterapiv1beta2 "open-cluster-management.io/api/cluster/v1beta2"
)

func (o *Options) complete(cmd *cobra.Command, args []string) (err error) {
Expand Down Expand Up @@ -49,22 +49,22 @@ func (o *Options) Run() (err error) {
return err
}

_, err = clusterClient.ClusterV1beta1().ManagedClusterSets().Get(context.TODO(), o.Clusterset, metav1.GetOptions{})
_, err = clusterClient.ClusterV1beta2().ManagedClusterSets().Get(context.TODO(), o.Clusterset, metav1.GetOptions{})
if err != nil {
return err
}

binding := &clusterapiv1beta1.ManagedClusterSetBinding{
binding := &clusterapiv1beta2.ManagedClusterSetBinding{
ObjectMeta: metav1.ObjectMeta{
Name: o.Clusterset,
Namespace: o.Namespace,
},
Spec: clusterapiv1beta1.ManagedClusterSetBindingSpec{
Spec: clusterapiv1beta2.ManagedClusterSetBindingSpec{
ClusterSet: o.Clusterset,
},
}

_, err = clusterClient.ClusterV1beta1().ManagedClusterSetBindings(o.Namespace).Create(context.TODO(), binding, metav1.CreateOptions{})
_, err = clusterClient.ClusterV1beta2().ManagedClusterSetBindings(o.Namespace).Create(context.TODO(), binding, metav1.CreateOptions{})
if errors.IsAlreadyExists(err) {
fmt.Fprintf(o.Streams.Out, "Clusterset %s is already bound to Namespace %s\n", o.Clusterset, o.Namespace)
return nil
Expand Down
4 changes: 2 additions & 2 deletions pkg/cmd/clusterset/unbind/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@ func (o *Options) Run() (err error) {
return err
}

_, err = clusterClient.ClusterV1beta1().ManagedClusterSets().Get(context.TODO(), o.Clusterset, metav1.GetOptions{})
_, err = clusterClient.ClusterV1beta2().ManagedClusterSets().Get(context.TODO(), o.Clusterset, metav1.GetOptions{})
if err != nil {
return err
}

err = clusterClient.ClusterV1beta1().ManagedClusterSetBindings(o.Namespace).Delete(context.TODO(), o.Clusterset, metav1.DeleteOptions{})
err = clusterClient.ClusterV1beta2().ManagedClusterSetBindings(o.Namespace).Delete(context.TODO(), o.Clusterset, metav1.DeleteOptions{})
if err != nil && !errors.IsNotFound(err) {
return err
}
Expand Down
9 changes: 4 additions & 5 deletions pkg/cmd/create/clusterset/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ package clusterset
import (
"context"
"fmt"

"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clusterclientset "open-cluster-management.io/api/client/cluster/clientset/versioned"
clusterapiv1beta1 "open-cluster-management.io/api/cluster/v1beta1"
clusterapiv1beta2 "open-cluster-management.io/api/cluster/v1beta2"
)

func (o *Options) complete(cmd *cobra.Command, args []string) (err error) {
Expand Down Expand Up @@ -52,7 +51,7 @@ func (o *Options) runWithClient(clusterClient clusterclientset.Interface,
dryRun bool,
clusterset string) error {

_, err := clusterClient.ClusterV1beta1().ManagedClusterSets().Get(context.TODO(), clusterset, metav1.GetOptions{})
_, err := clusterClient.ClusterV1beta2().ManagedClusterSets().Get(context.TODO(), clusterset, metav1.GetOptions{})
if err == nil {
fmt.Fprintf(o.Streams.Out, "Clusterset %s is already created\n", clusterset)
return nil
Expand All @@ -63,13 +62,13 @@ func (o *Options) runWithClient(clusterClient clusterclientset.Interface,
return nil
}

mcs := &clusterapiv1beta1.ManagedClusterSet{
mcs := &clusterapiv1beta2.ManagedClusterSet{
ObjectMeta: metav1.ObjectMeta{
Name: clusterset,
},
}

_, err = clusterClient.ClusterV1beta1().ManagedClusterSets().Create(context.TODO(), mcs, metav1.CreateOptions{})
_, err = clusterClient.ClusterV1beta2().ManagedClusterSets().Create(context.TODO(), mcs, metav1.CreateOptions{})
if err != nil {
return err
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/cmd/create/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"open-cluster-management.io/clusteradm/pkg/cmd/create/clusterset"
"open-cluster-management.io/clusteradm/pkg/cmd/create/placement"
"open-cluster-management.io/clusteradm/pkg/cmd/create/sampleapp"
"open-cluster-management.io/clusteradm/pkg/cmd/create/work"
genericclioptionsclusteradm "open-cluster-management.io/clusteradm/pkg/genericclioptions"
Expand All @@ -19,6 +20,7 @@ func NewCmd(clusteradmFlags *genericclioptionsclusteradm.ClusteradmFlags, stream

cmd.AddCommand(clusterset.NewCmd(clusteradmFlags, streams))
cmd.AddCommand(work.NewCmd(clusteradmFlags, streams))
cmd.AddCommand(placement.NewCmd(clusteradmFlags, streams))
cmd.AddCommand(sampleapp.NewCmd(clusteradmFlags, streams))

return cmd
Expand Down
66 changes: 66 additions & 0 deletions pkg/cmd/create/placement/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright Contributors to the Open Cluster Management project
package placement

import (
"fmt"

genericclioptionsclusteradm "open-cluster-management.io/clusteradm/pkg/genericclioptions"
clusteradmhelpers "open-cluster-management.io/clusteradm/pkg/helpers"

"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
)

var example = `
# Create a placement with selector.
%[1]s create placement test --label-selectors region=apac
# Create a placement with count.
%[1]s create placement test --count 2
# Create a placement with clustersets.
%[1]s create placement test --clustersets set1
# Create a placement with clustersets prioritizers
%[1]s create placement test --prioritizers BuiltIn:Steady:3,BuiltIn:ResourceAllocatableCPU:2
`

// NewCmd...
func NewCmd(clusteradmFlags *genericclioptionsclusteradm.ClusteradmFlags, streams genericclioptions.IOStreams) *cobra.Command {
o := newOptions(clusteradmFlags, streams)

cmd := &cobra.Command{
Use: "placement",
Short: "create a placement",
Long: "create a placement",
Example: fmt.Sprintf(example, clusteradmhelpers.GetExampleHeader()),
SilenceUsage: true,
PreRunE: func(c *cobra.Command, args []string) error {
clusteradmhelpers.DryRunMessage(clusteradmFlags.DryRun)

return nil
},
RunE: func(c *cobra.Command, args []string) error {
if err := o.complete(c, args); err != nil {
return err
}
if err := o.validate(); err != nil {
return err
}
if err := o.run(); err != nil {
return err
}

return nil
},
}

cmd.Flags().BoolVar(&o.Overwrite, "overwrite", false, "Overwrite the existing work if it exists already")
cmd.Flags().StringVar(&o.Namespace, "namespace", "default", "Namespace to bind to a clusterset")
cmd.Flags().StringSliceVar(&o.ClusterSelector, "label-selectors", o.ClusterSelector, "Label selectors to select clusters")
cmd.Flags().StringSliceVar(&o.ClusterSets, "clustersets", o.ClusterSets, "Cluster Sets where clusters are selected")
cmd.Flags().StringSliceVar(&o.Prioritizers, "prioritizers", o.Prioritizers, "Prioritizers to sort and filter clusters")
cmd.Flags().Int32Var(&o.NumOfClusters, "count", o.NumOfClusters, "Number of clusters to select")

return cmd
}
165 changes: 165 additions & 0 deletions pkg/cmd/create/placement/exec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// Copyright Contributors to the Open Cluster Management project
package placement

import (
"context"
"fmt"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/pointer"
clusterclientset "open-cluster-management.io/api/client/cluster/clientset/versioned"
clusterv1beta1 "open-cluster-management.io/api/cluster/v1beta1"
"strconv"
"strings"
)

func (o *Options) complete(cmd *cobra.Command, args []string) (err error) {
if len(args) == 0 {
return fmt.Errorf("placement name must be specified")
}

if len(args) > 1 {
return fmt.Errorf("only one placement name can be specified")
}

o.Placement = args[0]

return nil
}

func (o *Options) validate() error {

if err := o.ClusteradmFlags.ValidateHub(); err != nil {
return err
}

return nil
}

func (o *Options) run() (err error) {
restConfig, err := o.ClusteradmFlags.KubectlFactory.ToRESTConfig()
if err != nil {
return err
}
clusterClient, err := clusterclientset.NewForConfig(restConfig)
if err != nil {
return err
}

desiredPlacement := &clusterv1beta1.Placement{
ObjectMeta: metav1.ObjectMeta{
Name: o.Placement,
Namespace: o.Namespace,
},
}

if len(o.ClusterSets) > 0 {
for _, clusterset := range o.ClusterSets {
_, err := clusterClient.ClusterV1beta2().ManagedClusterSetBindings(o.Namespace).Get(context.TODO(), clusterset, metav1.GetOptions{})
if err != nil {
return err
}
}
desiredPlacement.Spec.ClusterSets = o.ClusterSets
}

if o.NumOfClusters > 0 {
desiredPlacement.Spec.NumberOfClusters = pointer.Int32(o.NumOfClusters)
}

if len(o.ClusterSelector) > 0 {
desiredPlacement.Spec.Predicates = []clusterv1beta1.ClusterPredicate{}
for _, s := range o.ClusterSelector {
selector, err := metav1.ParseToLabelSelector(s)
if err != nil {
return fmt.Errorf("failed to parse selector %s: %v", s, err)
}
desiredPlacement.Spec.Predicates = append(desiredPlacement.Spec.Predicates, clusterv1beta1.ClusterPredicate{
RequiredClusterSelector: clusterv1beta1.ClusterSelector{
LabelSelector: *selector,
},
})
}
}

if len(o.Prioritizers) > 0 {
desiredPlacement.Spec.PrioritizerPolicy = clusterv1beta1.PrioritizerPolicy{
Mode: clusterv1beta1.PrioritizerPolicyModeAdditive,
Configurations: []clusterv1beta1.PrioritizerConfig{},
}
for _, p := range o.Prioritizers {
config, err := parsePrioritizer(p)
if err != nil {
return err
}
desiredPlacement.Spec.PrioritizerPolicy.Configurations = append(desiredPlacement.Spec.PrioritizerPolicy.Configurations, *config)
}
}

return o.applyPlacement(clusterClient, desiredPlacement)
}

func parsePrioritizer(s string) (*clusterv1beta1.PrioritizerConfig, error) {
ps := strings.Split(s, ":")
if len(ps) < 3 {
return nil, fmt.Errorf("prioritizer %s format is not correct", s)
}
switch ps[0] {
case clusterv1beta1.ScoreCoordinateTypeBuiltIn:
if len(ps) != 3 {
return nil, fmt.Errorf("prioritizer %s format is not correct, should be Builtin:{Type}:{Weight}", s)
}
weight, err := strconv.ParseInt(ps[2], 10, 32)
if err != nil {
return nil, fmt.Errorf("weight in prioritizer %s is not correct: %v", s, err)
}
return &clusterv1beta1.PrioritizerConfig{
ScoreCoordinate: &clusterv1beta1.ScoreCoordinate{
Type: clusterv1beta1.ScoreCoordinateTypeBuiltIn,
BuiltIn: ps[1],
},
Weight: int32(weight),
}, nil
case clusterv1beta1.ScoreCoordinateTypeAddOn:
if len(ps) != 4 {
return nil, fmt.Errorf("prioritizer %s format is not correct, should be Addon:{Type}:{ScoreName}:{Weight}", s)
}
weight, err := strconv.ParseInt(ps[3], 10, 32)
if err != nil {
return nil, fmt.Errorf("weight in prioritizer %s is not correct: %v", s, err)
}
return &clusterv1beta1.PrioritizerConfig{
ScoreCoordinate: &clusterv1beta1.ScoreCoordinate{
Type: clusterv1beta1.ScoreCoordinateTypeAddOn,
AddOn: &clusterv1beta1.AddOnScore{
ResourceName: ps[1],
ScoreName: ps[2],
},
},
Weight: int32(weight),
}, nil
}

return nil, fmt.Errorf("unkown prioritizer type %s for %s", ps[0], s)
}

func (o *Options) applyPlacement(clusterClient clusterclientset.Interface, placement *clusterv1beta1.Placement) error {
placementOrigin, err := clusterClient.ClusterV1beta1().Placements(o.Namespace).Get(context.TODO(), placement.Name, metav1.GetOptions{})
if errors.IsNotFound(err) {
_, createErr := clusterClient.ClusterV1beta1().Placements(o.Namespace).Create(context.TODO(), placement, metav1.CreateOptions{})
return createErr
}
if err != nil {
return err
}

if !o.Overwrite {
return fmt.Errorf("placement %s already exists", placement.Name)
}

placementOrigin.Spec = placement.Spec
_, err = clusterClient.ClusterV1beta1().Placements(o.Namespace).Update(context.TODO(), placementOrigin, metav1.UpdateOptions{})

return err
}
Loading

0 comments on commit 95f9d75

Please sign in to comment.