mirror of
https://github.com/kubernetes-sigs/descheduler.git
synced 2026-01-28 06:29:29 +01:00
RemovePodsViolatingTopologySpreadConstraint defaulting + moving arguments to its corresponding plugin
This commit is contained in:
@@ -5,5 +5,5 @@ go build -o "${OS_OUTPUT_BINPATH}/conversion-gen" "k8s.io/code-generator/cmd/con
|
|||||||
|
|
||||||
${OS_OUTPUT_BINPATH}/conversion-gen \
|
${OS_OUTPUT_BINPATH}/conversion-gen \
|
||||||
--go-header-file "hack/boilerplate/boilerplate.go.txt" \
|
--go-header-file "hack/boilerplate/boilerplate.go.txt" \
|
||||||
--input-dirs "${PRJ_PREFIX}/pkg/apis/componentconfig/v1alpha1,${PRJ_PREFIX}/pkg/api/v1alpha1,${PRJ_PREFIX}/pkg/framework/plugins/removefailedpods,${PRJ_PREFIX}/pkg/framework/plugins/nodeutilization,${PRJ_PREFIX}/pkg/framework/plugins/podlifetime,${PRJ_PREFIX}/pkg/framework/plugins/removeduplicates,${PRJ_PREFIX}/pkg/framework/plugins/removepodshavingtoomanyrestarts,${PRJ_PREFIX}/pkg/framework/plugins/removepodsviolatinginterpodantiaffinity,${PRJ_PREFIX}/pkg/framework/plugins/removepodsviolatingnodeaffinity,${PRJ_PREFIX}/pkg/framework/plugins/removepodsviolatingnodetaints" \
|
--input-dirs "${PRJ_PREFIX}/pkg/apis/componentconfig/v1alpha1,${PRJ_PREFIX}/pkg/api/v1alpha1,${PRJ_PREFIX}/pkg/framework/plugins/removefailedpods,${PRJ_PREFIX}/pkg/framework/plugins/nodeutilization,${PRJ_PREFIX}/pkg/framework/plugins/podlifetime,${PRJ_PREFIX}/pkg/framework/plugins/removeduplicates,${PRJ_PREFIX}/pkg/framework/plugins/removepodshavingtoomanyrestarts,${PRJ_PREFIX}/pkg/framework/plugins/removepodsviolatinginterpodantiaffinity,${PRJ_PREFIX}/pkg/framework/plugins/removepodsviolatingnodeaffinity,${PRJ_PREFIX}/pkg/framework/plugins/removepodsviolatingnodetaints,${PRJ_PREFIX}/pkg/framework/plugins/removepodsviolatingtopologyspreadconstraint" \
|
||||||
--output-file-base zz_generated.conversion
|
--output-file-base zz_generated.conversion
|
||||||
|
|||||||
@@ -5,6 +5,6 @@ go build -o "${OS_OUTPUT_BINPATH}/deepcopy-gen" "k8s.io/code-generator/cmd/deepc
|
|||||||
|
|
||||||
${OS_OUTPUT_BINPATH}/deepcopy-gen \
|
${OS_OUTPUT_BINPATH}/deepcopy-gen \
|
||||||
--go-header-file "hack/boilerplate/boilerplate.go.txt" \
|
--go-header-file "hack/boilerplate/boilerplate.go.txt" \
|
||||||
--input-dirs "${PRJ_PREFIX}/pkg/apis/componentconfig,${PRJ_PREFIX}/pkg/apis/componentconfig/v1alpha1,${PRJ_PREFIX}/pkg/api,${PRJ_PREFIX}/pkg/api/v1alpha1,${PRJ_PREFIX}/pkg/framework/plugins/defaultevictor/,${PRJ_PREFIX}/pkg/framework/plugins/removefailedpods,${PRJ_PREFIX}/pkg/framework/plugins/nodeutilization,${PRJ_PREFIX}/pkg/framework/plugins/podlifetime,${PRJ_PREFIX}/pkg/framework/plugins/removeduplicates,${PRJ_PREFIX}/pkg/framework/plugins/removepodshavingtoomanyrestarts,${PRJ_PREFIX}/pkg/framework/plugins/removepodsviolatinginterpodantiaffinity,${PRJ_PREFIX}/pkg/framework/plugins/removepodsviolatingnodeaffinity,${PRJ_PREFIX}/pkg/framework/plugins/removepodsviolatingnodetaints" \
|
--input-dirs "${PRJ_PREFIX}/pkg/apis/componentconfig,${PRJ_PREFIX}/pkg/apis/componentconfig/v1alpha1,${PRJ_PREFIX}/pkg/api,${PRJ_PREFIX}/pkg/api/v1alpha1,${PRJ_PREFIX}/pkg/framework/plugins/defaultevictor/,${PRJ_PREFIX}/pkg/framework/plugins/removefailedpods,${PRJ_PREFIX}/pkg/framework/plugins/nodeutilization,${PRJ_PREFIX}/pkg/framework/plugins/podlifetime,${PRJ_PREFIX}/pkg/framework/plugins/removeduplicates,${PRJ_PREFIX}/pkg/framework/plugins/removepodshavingtoomanyrestarts,${PRJ_PREFIX}/pkg/framework/plugins/removepodsviolatinginterpodantiaffinity,${PRJ_PREFIX}/pkg/framework/plugins/removepodsviolatingnodeaffinity,${PRJ_PREFIX}/pkg/framework/plugins/removepodsviolatingnodetaints,${PRJ_PREFIX}/pkg/framework/plugins/removepodsviolatingtopologyspreadconstraint" \
|
||||||
--output-file-base zz_generated.deepcopy
|
--output-file-base zz_generated.deepcopy
|
||||||
|
|
||||||
|
|||||||
@@ -19,52 +19,15 @@ package validation
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||||
"sigs.k8s.io/descheduler/pkg/api"
|
|
||||||
"sigs.k8s.io/descheduler/pkg/apis/componentconfig"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// MinResourcePercentage is the minimum value of a resource's percentage
|
|
||||||
MinResourcePercentage = 0
|
|
||||||
// MaxResourcePercentage is the maximum value of a resource's percentage
|
|
||||||
MaxResourcePercentage = 100
|
|
||||||
)
|
|
||||||
|
|
||||||
// ValidateRemovePodsViolatingTopologySpreadConstraintArgs validates RemovePodsViolatingTopologySpreadConstraint arguments
|
|
||||||
func ValidateRemovePodsViolatingTopologySpreadConstraintArgs(args *componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs) error {
|
|
||||||
return errorsAggregate(
|
|
||||||
validateNamespaceArgs(args.Namespaces),
|
|
||||||
validateLabelSelectorArgs(args.LabelSelector),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// errorsAggregate converts all arg validation errors to a single error interface.
|
// errorsAggregate converts all arg validation errors to a single error interface.
|
||||||
// if no errors, it will return nil.
|
// if no errors, it will return nil.
|
||||||
func errorsAggregate(errors ...error) error {
|
func errorsAggregate(errors ...error) error {
|
||||||
return utilerrors.NewAggregate(errors)
|
return utilerrors.NewAggregate(errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateNamespaceArgs(namespaces *api.Namespaces) error {
|
|
||||||
// At most one of include/exclude can be set
|
|
||||||
if namespaces != nil && len(namespaces.Include) > 0 && len(namespaces.Exclude) > 0 {
|
|
||||||
return fmt.Errorf("only one of Include/Exclude namespaces can be set")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateLabelSelectorArgs(labelSelector *metav1.LabelSelector) error {
|
|
||||||
if labelSelector != nil {
|
|
||||||
if _, err := metav1.LabelSelectorAsSelector(labelSelector); err != nil {
|
|
||||||
return fmt.Errorf("failed to get label selectors from strategy's params: %+v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validatePodRestartThreshold(podRestartThreshold int32) error {
|
func validatePodRestartThreshold(podRestartThreshold int32) error {
|
||||||
if podRestartThreshold < 1 {
|
if podRestartThreshold < 1 {
|
||||||
return fmt.Errorf("PodsHavingTooManyRestarts threshold not set")
|
return fmt.Errorf("PodsHavingTooManyRestarts threshold not set")
|
||||||
|
|||||||
@@ -22,9 +22,7 @@ limitations under the License.
|
|||||||
package componentconfig
|
package componentconfig
|
||||||
|
|
||||||
import (
|
import (
|
||||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
api "sigs.k8s.io/descheduler/pkg/api"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
@@ -53,38 +51,3 @@ func (in *DeschedulerConfiguration) DeepCopyObject() runtime.Object {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
|
||||||
func (in *RemovePodsViolatingTopologySpreadConstraintArgs) DeepCopyInto(out *RemovePodsViolatingTopologySpreadConstraintArgs) {
|
|
||||||
*out = *in
|
|
||||||
out.TypeMeta = in.TypeMeta
|
|
||||||
if in.Namespaces != nil {
|
|
||||||
in, out := &in.Namespaces, &out.Namespaces
|
|
||||||
*out = new(api.Namespaces)
|
|
||||||
(*in).DeepCopyInto(*out)
|
|
||||||
}
|
|
||||||
if in.LabelSelector != nil {
|
|
||||||
in, out := &in.LabelSelector, &out.LabelSelector
|
|
||||||
*out = new(v1.LabelSelector)
|
|
||||||
(*in).DeepCopyInto(*out)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RemovePodsViolatingTopologySpreadConstraintArgs.
|
|
||||||
func (in *RemovePodsViolatingTopologySpreadConstraintArgs) DeepCopy() *RemovePodsViolatingTopologySpreadConstraintArgs {
|
|
||||||
if in == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
out := new(RemovePodsViolatingTopologySpreadConstraintArgs)
|
|
||||||
in.DeepCopyInto(out)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
|
||||||
func (in *RemovePodsViolatingTopologySpreadConstraintArgs) DeepCopyObject() runtime.Object {
|
|
||||||
if c := in.DeepCopy(); c != nil {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ import (
|
|||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
"sigs.k8s.io/descheduler/pkg/api"
|
"sigs.k8s.io/descheduler/pkg/api"
|
||||||
"sigs.k8s.io/descheduler/pkg/apis/componentconfig"
|
|
||||||
"sigs.k8s.io/descheduler/pkg/apis/componentconfig/validation"
|
|
||||||
"sigs.k8s.io/descheduler/pkg/framework"
|
"sigs.k8s.io/descheduler/pkg/framework"
|
||||||
"sigs.k8s.io/descheduler/pkg/framework/plugins/nodeutilization"
|
"sigs.k8s.io/descheduler/pkg/framework/plugins/nodeutilization"
|
||||||
"sigs.k8s.io/descheduler/pkg/framework/plugins/podlifetime"
|
"sigs.k8s.io/descheduler/pkg/framework/plugins/podlifetime"
|
||||||
@@ -209,12 +207,12 @@ var pluginsMap = map[string]func(ctx context.Context, nodes []*v1.Node, params *
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"RemovePodsViolatingTopologySpreadConstraint": func(ctx context.Context, nodes []*v1.Node, params *api.StrategyParameters, handle *handleImpl) {
|
"RemovePodsViolatingTopologySpreadConstraint": func(ctx context.Context, nodes []*v1.Node, params *api.StrategyParameters, handle *handleImpl) {
|
||||||
args := &componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs{
|
args := &removepodsviolatingtopologyspreadconstraint.RemovePodsViolatingTopologySpreadConstraintArgs{
|
||||||
Namespaces: params.Namespaces,
|
Namespaces: params.Namespaces,
|
||||||
LabelSelector: params.LabelSelector,
|
LabelSelector: params.LabelSelector,
|
||||||
IncludeSoftConstraints: params.IncludePreferNoSchedule,
|
IncludeSoftConstraints: params.IncludePreferNoSchedule,
|
||||||
}
|
}
|
||||||
if err := validation.ValidateRemovePodsViolatingTopologySpreadConstraintArgs(args); err != nil {
|
if err := removepodsviolatingtopologyspreadconstraint.ValidateRemovePodsViolatingTopologySpreadConstraintArgs(args); err != nil {
|
||||||
klog.V(1).ErrorS(err, "unable to validate plugin arguments", "pluginName", removepodsviolatingtopologyspreadconstraint.PluginName)
|
klog.V(1).ErrorS(err, "unable to validate plugin arguments", "pluginName", removepodsviolatingtopologyspreadconstraint.PluginName)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
"sigs.k8s.io/descheduler/pkg/apis/componentconfig"
|
|
||||||
"sigs.k8s.io/descheduler/pkg/descheduler/evictions"
|
"sigs.k8s.io/descheduler/pkg/descheduler/evictions"
|
||||||
"sigs.k8s.io/descheduler/pkg/descheduler/node"
|
"sigs.k8s.io/descheduler/pkg/descheduler/node"
|
||||||
podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod"
|
podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod"
|
||||||
@@ -51,7 +50,7 @@ type topology struct {
|
|||||||
// RemovePodsViolatingTopologySpreadConstraint evicts pods which violate their topology spread constraints
|
// RemovePodsViolatingTopologySpreadConstraint evicts pods which violate their topology spread constraints
|
||||||
type RemovePodsViolatingTopologySpreadConstraint struct {
|
type RemovePodsViolatingTopologySpreadConstraint struct {
|
||||||
handle framework.Handle
|
handle framework.Handle
|
||||||
args *componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs
|
args *RemovePodsViolatingTopologySpreadConstraintArgs
|
||||||
podFilter podutil.FilterFunc
|
podFilter podutil.FilterFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +58,7 @@ var _ framework.BalancePlugin = &RemovePodsViolatingTopologySpreadConstraint{}
|
|||||||
|
|
||||||
// New builds plugin from its arguments while passing a handle
|
// New builds plugin from its arguments while passing a handle
|
||||||
func New(args runtime.Object, handle framework.Handle) (framework.Plugin, error) {
|
func New(args runtime.Object, handle framework.Handle) (framework.Plugin, error) {
|
||||||
pluginArgs, ok := args.(*componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs)
|
pluginArgs, ok := args.(*RemovePodsViolatingTopologySpreadConstraintArgs)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("want args to be of type RemovePodsViolatingTopologySpreadConstraintArgs, got %T", args)
|
return nil, fmt.Errorf("want args to be of type RemovePodsViolatingTopologySpreadConstraintArgs, got %T", args)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import (
|
|||||||
"k8s.io/client-go/tools/events"
|
"k8s.io/client-go/tools/events"
|
||||||
|
|
||||||
"sigs.k8s.io/descheduler/pkg/api"
|
"sigs.k8s.io/descheduler/pkg/api"
|
||||||
"sigs.k8s.io/descheduler/pkg/apis/componentconfig"
|
|
||||||
"sigs.k8s.io/descheduler/pkg/descheduler/evictions"
|
"sigs.k8s.io/descheduler/pkg/descheduler/evictions"
|
||||||
podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod"
|
podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod"
|
||||||
"sigs.k8s.io/descheduler/pkg/framework"
|
"sigs.k8s.io/descheduler/pkg/framework"
|
||||||
@@ -33,7 +32,7 @@ func TestTopologySpreadConstraint(t *testing.T) {
|
|||||||
expectedEvictedPods []string // if specified, will assert specific pods were evicted
|
expectedEvictedPods []string // if specified, will assert specific pods were evicted
|
||||||
nodes []*v1.Node
|
nodes []*v1.Node
|
||||||
namespaces []string
|
namespaces []string
|
||||||
args componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs
|
args RemovePodsViolatingTopologySpreadConstraintArgs
|
||||||
nodeFit bool
|
nodeFit bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@@ -69,7 +68,7 @@ func TestTopologySpreadConstraint(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
expectedEvictedCount: 0,
|
expectedEvictedCount: 0,
|
||||||
namespaces: []string{"ns1"},
|
namespaces: []string{"ns1"},
|
||||||
args: componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs{},
|
args: RemovePodsViolatingTopologySpreadConstraintArgs{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "2 domains, sizes [3,1], maxSkew=1, move 1 pod to achieve [2,2]",
|
name: "2 domains, sizes [3,1], maxSkew=1, move 1 pod to achieve [2,2]",
|
||||||
@@ -97,7 +96,7 @@ func TestTopologySpreadConstraint(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
expectedEvictedCount: 1,
|
expectedEvictedCount: 1,
|
||||||
namespaces: []string{"ns1"},
|
namespaces: []string{"ns1"},
|
||||||
args: componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs{},
|
args: RemovePodsViolatingTopologySpreadConstraintArgs{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "2 domains, sizes [3,1], maxSkew=1, move 1 pod to achieve [2,2] (soft constraints)",
|
name: "2 domains, sizes [3,1], maxSkew=1, move 1 pod to achieve [2,2] (soft constraints)",
|
||||||
@@ -132,7 +131,7 @@ func TestTopologySpreadConstraint(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
expectedEvictedCount: 1,
|
expectedEvictedCount: 1,
|
||||||
namespaces: []string{"ns1"},
|
namespaces: []string{"ns1"},
|
||||||
args: componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs{IncludeSoftConstraints: true},
|
args: RemovePodsViolatingTopologySpreadConstraintArgs{IncludeSoftConstraints: true},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "2 domains, sizes [3,1], maxSkew=1, no pods eligible, move 0 pods",
|
name: "2 domains, sizes [3,1], maxSkew=1, no pods eligible, move 0 pods",
|
||||||
@@ -163,7 +162,7 @@ func TestTopologySpreadConstraint(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
expectedEvictedCount: 0,
|
expectedEvictedCount: 0,
|
||||||
namespaces: []string{"ns1"},
|
namespaces: []string{"ns1"},
|
||||||
args: componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs{},
|
args: RemovePodsViolatingTopologySpreadConstraintArgs{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "2 domains, sizes [3,1], maxSkew=1, move 1 pod to achieve [2,2], exclude kube-system namespace",
|
name: "2 domains, sizes [3,1], maxSkew=1, move 1 pod to achieve [2,2], exclude kube-system namespace",
|
||||||
@@ -191,7 +190,7 @@ func TestTopologySpreadConstraint(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
expectedEvictedCount: 1,
|
expectedEvictedCount: 1,
|
||||||
namespaces: []string{"ns1"},
|
namespaces: []string{"ns1"},
|
||||||
args: componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs{Namespaces: &api.Namespaces{Exclude: []string{"kube-system"}}},
|
args: RemovePodsViolatingTopologySpreadConstraintArgs{Namespaces: &api.Namespaces{Exclude: []string{"kube-system"}}},
|
||||||
nodeFit: true,
|
nodeFit: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -220,7 +219,7 @@ func TestTopologySpreadConstraint(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
expectedEvictedCount: 1,
|
expectedEvictedCount: 1,
|
||||||
namespaces: []string{"ns1"},
|
namespaces: []string{"ns1"},
|
||||||
args: componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs{},
|
args: RemovePodsViolatingTopologySpreadConstraintArgs{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "2 domains, sizes [4,0], maxSkew=1, move 2 pods to achieve [2,2]",
|
name: "2 domains, sizes [4,0], maxSkew=1, move 2 pods to achieve [2,2]",
|
||||||
@@ -243,7 +242,7 @@ func TestTopologySpreadConstraint(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
expectedEvictedCount: 2,
|
expectedEvictedCount: 2,
|
||||||
namespaces: []string{"ns1"},
|
namespaces: []string{"ns1"},
|
||||||
args: componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs{},
|
args: RemovePodsViolatingTopologySpreadConstraintArgs{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "2 domains, sizes [4,0], maxSkew=1, only move 1 pod since pods with nodeSelector and nodeAffinity aren't evicted",
|
name: "2 domains, sizes [4,0], maxSkew=1, only move 1 pod since pods with nodeSelector and nodeAffinity aren't evicted",
|
||||||
@@ -283,7 +282,7 @@ func TestTopologySpreadConstraint(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
expectedEvictedCount: 1,
|
expectedEvictedCount: 1,
|
||||||
namespaces: []string{"ns1"},
|
namespaces: []string{"ns1"},
|
||||||
args: componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs{},
|
args: RemovePodsViolatingTopologySpreadConstraintArgs{},
|
||||||
nodeFit: true,
|
nodeFit: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -327,7 +326,7 @@ func TestTopologySpreadConstraint(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
expectedEvictedCount: 2,
|
expectedEvictedCount: 2,
|
||||||
namespaces: []string{"ns1"},
|
namespaces: []string{"ns1"},
|
||||||
args: componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs{},
|
args: RemovePodsViolatingTopologySpreadConstraintArgs{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "3 domains, sizes [0, 1, 100], maxSkew=1, move 66 pods to get [34, 33, 34]",
|
name: "3 domains, sizes [0, 1, 100], maxSkew=1, move 66 pods to get [34, 33, 34]",
|
||||||
@@ -351,7 +350,7 @@ func TestTopologySpreadConstraint(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
expectedEvictedCount: 66,
|
expectedEvictedCount: 66,
|
||||||
namespaces: []string{"ns1"},
|
namespaces: []string{"ns1"},
|
||||||
args: componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs{},
|
args: RemovePodsViolatingTopologySpreadConstraintArgs{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "4 domains, sizes [0, 1, 3, 5], should move 3 to get [2, 2, 3, 2]",
|
name: "4 domains, sizes [0, 1, 3, 5], should move 3 to get [2, 2, 3, 2]",
|
||||||
@@ -381,7 +380,7 @@ func TestTopologySpreadConstraint(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
expectedEvictedCount: 3,
|
expectedEvictedCount: 3,
|
||||||
namespaces: []string{"ns1"},
|
namespaces: []string{"ns1"},
|
||||||
args: componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs{},
|
args: RemovePodsViolatingTopologySpreadConstraintArgs{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "2 domains size [2 6], maxSkew=2, should move 1 to get [3 5]",
|
name: "2 domains size [2 6], maxSkew=2, should move 1 to get [3 5]",
|
||||||
@@ -409,7 +408,7 @@ func TestTopologySpreadConstraint(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
expectedEvictedCount: 1,
|
expectedEvictedCount: 1,
|
||||||
namespaces: []string{"ns1"},
|
namespaces: []string{"ns1"},
|
||||||
args: componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs{},
|
args: RemovePodsViolatingTopologySpreadConstraintArgs{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "2 domains size [2 6], maxSkew=2, can't move any because of node taints",
|
name: "2 domains size [2 6], maxSkew=2, can't move any because of node taints",
|
||||||
@@ -453,7 +452,7 @@ func TestTopologySpreadConstraint(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
expectedEvictedCount: 0,
|
expectedEvictedCount: 0,
|
||||||
namespaces: []string{"ns1"},
|
namespaces: []string{"ns1"},
|
||||||
args: componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs{},
|
args: RemovePodsViolatingTopologySpreadConstraintArgs{},
|
||||||
nodeFit: true,
|
nodeFit: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -482,7 +481,7 @@ func TestTopologySpreadConstraint(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
expectedEvictedCount: 0,
|
expectedEvictedCount: 0,
|
||||||
namespaces: []string{"ns1"},
|
namespaces: []string{"ns1"},
|
||||||
args: componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs{},
|
args: RemovePodsViolatingTopologySpreadConstraintArgs{},
|
||||||
nodeFit: true,
|
nodeFit: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -588,7 +587,7 @@ func TestTopologySpreadConstraint(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
expectedEvictedCount: 1,
|
expectedEvictedCount: 1,
|
||||||
namespaces: []string{"ns1"},
|
namespaces: []string{"ns1"},
|
||||||
args: componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs{IncludeSoftConstraints: true},
|
args: RemovePodsViolatingTopologySpreadConstraintArgs{IncludeSoftConstraints: true},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "3 domains size [8 7 0], maxSkew=1, should move 5 to get [5 5 5]",
|
name: "3 domains size [8 7 0], maxSkew=1, should move 5 to get [5 5 5]",
|
||||||
@@ -614,7 +613,7 @@ func TestTopologySpreadConstraint(t *testing.T) {
|
|||||||
expectedEvictedCount: 5,
|
expectedEvictedCount: 5,
|
||||||
expectedEvictedPods: []string{"pod-5", "pod-6", "pod-7", "pod-8"},
|
expectedEvictedPods: []string{"pod-5", "pod-6", "pod-7", "pod-8"},
|
||||||
namespaces: []string{"ns1"},
|
namespaces: []string{"ns1"},
|
||||||
args: componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs{},
|
args: RemovePodsViolatingTopologySpreadConstraintArgs{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "3 domains size [5 5 5], maxSkew=1, should move 0 to retain [5 5 5]",
|
name: "3 domains size [5 5 5], maxSkew=1, should move 0 to retain [5 5 5]",
|
||||||
@@ -645,7 +644,7 @@ func TestTopologySpreadConstraint(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
expectedEvictedCount: 0,
|
expectedEvictedCount: 0,
|
||||||
namespaces: []string{"ns1"},
|
namespaces: []string{"ns1"},
|
||||||
args: componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs{},
|
args: RemovePodsViolatingTopologySpreadConstraintArgs{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "2 domains, sizes [2,0], maxSkew=1, move 1 pod since pod tolerates the node with taint",
|
name: "2 domains, sizes [2,0], maxSkew=1, move 1 pod since pod tolerates the node with taint",
|
||||||
@@ -687,7 +686,7 @@ func TestTopologySpreadConstraint(t *testing.T) {
|
|||||||
expectedEvictedCount: 1,
|
expectedEvictedCount: 1,
|
||||||
expectedEvictedPods: []string{"pod-0"},
|
expectedEvictedPods: []string{"pod-0"},
|
||||||
namespaces: []string{"ns1"},
|
namespaces: []string{"ns1"},
|
||||||
args: componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs{},
|
args: RemovePodsViolatingTopologySpreadConstraintArgs{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "2 domains, sizes [2,0], maxSkew=1, move 0 pods since pod does not tolerate the tainted node",
|
name: "2 domains, sizes [2,0], maxSkew=1, move 0 pods since pod does not tolerate the tainted node",
|
||||||
@@ -720,7 +719,7 @@ func TestTopologySpreadConstraint(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
expectedEvictedCount: 0,
|
expectedEvictedCount: 0,
|
||||||
namespaces: []string{"ns1"},
|
namespaces: []string{"ns1"},
|
||||||
args: componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs{},
|
args: RemovePodsViolatingTopologySpreadConstraintArgs{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "2 domains, sizes [2,0], maxSkew=1, move 0 pods since pod does not tolerate the tainted node, and NodeFit is enabled",
|
name: "2 domains, sizes [2,0], maxSkew=1, move 0 pods since pod does not tolerate the tainted node, and NodeFit is enabled",
|
||||||
@@ -753,7 +752,7 @@ func TestTopologySpreadConstraint(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
expectedEvictedCount: 0,
|
expectedEvictedCount: 0,
|
||||||
namespaces: []string{"ns1"},
|
namespaces: []string{"ns1"},
|
||||||
args: componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs{},
|
args: RemovePodsViolatingTopologySpreadConstraintArgs{},
|
||||||
nodeFit: true,
|
nodeFit: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -788,7 +787,7 @@ func TestTopologySpreadConstraint(t *testing.T) {
|
|||||||
expectedEvictedCount: 1,
|
expectedEvictedCount: 1,
|
||||||
expectedEvictedPods: []string{"pod-0"},
|
expectedEvictedPods: []string{"pod-0"},
|
||||||
namespaces: []string{"ns1"},
|
namespaces: []string{"ns1"},
|
||||||
args: componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs{},
|
args: RemovePodsViolatingTopologySpreadConstraintArgs{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "2 domains, sizes [2,0], maxSkew=1, move 0 pod for node with unmatched label filtering",
|
name: "2 domains, sizes [2,0], maxSkew=1, move 0 pod for node with unmatched label filtering",
|
||||||
@@ -811,7 +810,7 @@ func TestTopologySpreadConstraint(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
expectedEvictedCount: 0,
|
expectedEvictedCount: 0,
|
||||||
namespaces: []string{"ns1"},
|
namespaces: []string{"ns1"},
|
||||||
args: componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs{LabelSelector: getLabelSelector("foo", []string{"baz"}, metav1.LabelSelectorOpIn)},
|
args: RemovePodsViolatingTopologySpreadConstraintArgs{LabelSelector: getLabelSelector("foo", []string{"baz"}, metav1.LabelSelectorOpIn)},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "2 domains, sizes [2,0], maxSkew=1, move 1 pod for node with matched label filtering",
|
name: "2 domains, sizes [2,0], maxSkew=1, move 1 pod for node with matched label filtering",
|
||||||
@@ -835,7 +834,7 @@ func TestTopologySpreadConstraint(t *testing.T) {
|
|||||||
expectedEvictedCount: 1,
|
expectedEvictedCount: 1,
|
||||||
expectedEvictedPods: []string{"pod-1"},
|
expectedEvictedPods: []string{"pod-1"},
|
||||||
namespaces: []string{"ns1"},
|
namespaces: []string{"ns1"},
|
||||||
args: componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs{LabelSelector: getLabelSelector("foo", []string{"bar"}, metav1.LabelSelectorOpIn)},
|
args: RemovePodsViolatingTopologySpreadConstraintArgs{LabelSelector: getLabelSelector("foo", []string{"bar"}, metav1.LabelSelectorOpIn)},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "2 domains, sizes [2,0], maxSkew=1, move 1 pod for node with matched label filtering (NotIn op)",
|
name: "2 domains, sizes [2,0], maxSkew=1, move 1 pod for node with matched label filtering (NotIn op)",
|
||||||
@@ -859,7 +858,7 @@ func TestTopologySpreadConstraint(t *testing.T) {
|
|||||||
expectedEvictedCount: 1,
|
expectedEvictedCount: 1,
|
||||||
expectedEvictedPods: []string{"pod-1"},
|
expectedEvictedPods: []string{"pod-1"},
|
||||||
namespaces: []string{"ns1"},
|
namespaces: []string{"ns1"},
|
||||||
args: componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs{LabelSelector: getLabelSelector("foo", []string{"baz"}, metav1.LabelSelectorOpNotIn)},
|
args: RemovePodsViolatingTopologySpreadConstraintArgs{LabelSelector: getLabelSelector("foo", []string{"baz"}, metav1.LabelSelectorOpNotIn)},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "2 domains, sizes [4,2], maxSkew=1, 2 pods in termination; nothing should be moved",
|
name: "2 domains, sizes [4,2], maxSkew=1, 2 pods in termination; nothing should be moved",
|
||||||
@@ -890,7 +889,7 @@ func TestTopologySpreadConstraint(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
expectedEvictedCount: 0,
|
expectedEvictedCount: 0,
|
||||||
namespaces: []string{"ns1"},
|
namespaces: []string{"ns1"},
|
||||||
args: componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs{LabelSelector: getLabelSelector("foo", []string{"bar"}, metav1.LabelSelectorOpIn)},
|
args: RemovePodsViolatingTopologySpreadConstraintArgs{LabelSelector: getLabelSelector("foo", []string{"bar"}, metav1.LabelSelectorOpIn)},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "3 domains, sizes [2,3,4], maxSkew=1, NodeFit is enabled, and not enough cpu on zoneA; nothing should be moved",
|
name: "3 domains, sizes [2,3,4], maxSkew=1, NodeFit is enabled, and not enough cpu on zoneA; nothing should be moved",
|
||||||
@@ -921,7 +920,7 @@ func TestTopologySpreadConstraint(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
expectedEvictedCount: 0,
|
expectedEvictedCount: 0,
|
||||||
namespaces: []string{"ns1"},
|
namespaces: []string{"ns1"},
|
||||||
args: componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs{},
|
args: RemovePodsViolatingTopologySpreadConstraintArgs{},
|
||||||
nodeFit: true,
|
nodeFit: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -968,7 +967,7 @@ func TestTopologySpreadConstraint(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
expectedEvictedCount: 0,
|
expectedEvictedCount: 0,
|
||||||
namespaces: []string{"ns1"},
|
namespaces: []string{"ns1"},
|
||||||
args: componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs{},
|
args: RemovePodsViolatingTopologySpreadConstraintArgs{},
|
||||||
nodeFit: true,
|
nodeFit: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1008,7 +1007,7 @@ func TestTopologySpreadConstraint(t *testing.T) {
|
|||||||
expectedEvictedCount: 1,
|
expectedEvictedCount: 1,
|
||||||
expectedEvictedPods: []string{"pod-4"},
|
expectedEvictedPods: []string{"pod-4"},
|
||||||
namespaces: []string{"ns1"},
|
namespaces: []string{"ns1"},
|
||||||
args: componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs{},
|
args: RemovePodsViolatingTopologySpreadConstraintArgs{},
|
||||||
nodeFit: true,
|
nodeFit: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1115,7 +1114,7 @@ func TestTopologySpreadConstraint(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
expectedEvictedCount: 0,
|
expectedEvictedCount: 0,
|
||||||
namespaces: []string{"ns1"},
|
namespaces: []string{"ns1"},
|
||||||
args: componentconfig.RemovePodsViolatingTopologySpreadConstraintArgs{},
|
args: RemovePodsViolatingTopologySpreadConstraintArgs{},
|
||||||
nodeFit: true,
|
nodeFit: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,13 +14,14 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package componentconfig
|
package removepodsviolatingtopologyspreadconstraint
|
||||||
|
|
||||||
import (
|
import (
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"sigs.k8s.io/descheduler/pkg/api"
|
"sigs.k8s.io/descheduler/pkg/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen=true
|
||||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
// RemovePodsViolatingTopologySpreadConstraintArgs holds arguments used to configure RemovePodsViolatingTopologySpreadConstraint plugin.
|
// RemovePodsViolatingTopologySpreadConstraintArgs holds arguments used to configure RemovePodsViolatingTopologySpreadConstraint plugin.
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Kubernetes 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 removepodsviolatingtopologyspreadconstraint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValidateRemovePodsViolatingTopologySpreadConstraintArgs validates RemovePodsViolatingTopologySpreadConstraint arguments
|
||||||
|
func ValidateRemovePodsViolatingTopologySpreadConstraintArgs(args *RemovePodsViolatingTopologySpreadConstraintArgs) error {
|
||||||
|
// At most one of include/exclude can be set
|
||||||
|
if args.Namespaces != nil && len(args.Namespaces.Include) > 0 && len(args.Namespaces.Exclude) > 0 {
|
||||||
|
return fmt.Errorf("only one of Include/Exclude namespaces can be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.LabelSelector != nil {
|
||||||
|
if _, err := metav1.LabelSelectorAsSelector(args.LabelSelector); err != nil {
|
||||||
|
return fmt.Errorf("failed to get label selectors from strategy's params: %+v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
//go:build !ignore_autogenerated
|
||||||
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2022 The Kubernetes 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 deepcopy-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package removepodsviolatingtopologyspreadconstraint
|
||||||
|
|
||||||
|
import (
|
||||||
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
|
api "sigs.k8s.io/descheduler/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *RemovePodsViolatingTopologySpreadConstraintArgs) DeepCopyInto(out *RemovePodsViolatingTopologySpreadConstraintArgs) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
if in.Namespaces != nil {
|
||||||
|
in, out := &in.Namespaces, &out.Namespaces
|
||||||
|
*out = new(api.Namespaces)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.LabelSelector != nil {
|
||||||
|
in, out := &in.LabelSelector, &out.LabelSelector
|
||||||
|
*out = new(v1.LabelSelector)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RemovePodsViolatingTopologySpreadConstraintArgs.
|
||||||
|
func (in *RemovePodsViolatingTopologySpreadConstraintArgs) DeepCopy() *RemovePodsViolatingTopologySpreadConstraintArgs {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(RemovePodsViolatingTopologySpreadConstraintArgs)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *RemovePodsViolatingTopologySpreadConstraintArgs) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user