diff --git a/pkg/printers/internalversion/BUILD b/pkg/printers/internalversion/BUILD index b97046688c5..54146b92f74 100644 --- a/pkg/printers/internalversion/BUILD +++ b/pkg/printers/internalversion/BUILD @@ -21,6 +21,7 @@ go_test( "//pkg/apis/coordination:go_default_library", "//pkg/apis/core:go_default_library", "//pkg/apis/discovery:go_default_library", + "//pkg/apis/flowcontrol:go_default_library", "//pkg/apis/networking:go_default_library", "//pkg/apis/node:go_default_library", "//pkg/apis/policy:go_default_library", @@ -65,6 +66,8 @@ go_library( "//pkg/apis/discovery/install:go_default_library", "//pkg/apis/events/install:go_default_library", "//pkg/apis/extensions/install:go_default_library", + "//pkg/apis/flowcontrol:go_default_library", + "//pkg/apis/flowcontrol/util:go_default_library", "//pkg/apis/networking:go_default_library", "//pkg/apis/node:go_default_library", "//pkg/apis/policy:go_default_library", @@ -88,6 +91,7 @@ go_library( "//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/api/discovery/v1beta1:go_default_library", "//staging/src/k8s.io/api/extensions/v1beta1:go_default_library", + "//staging/src/k8s.io/api/flowcontrol/v1alpha1:go_default_library", "//staging/src/k8s.io/api/policy/v1beta1:go_default_library", "//staging/src/k8s.io/api/rbac/v1beta1:go_default_library", "//staging/src/k8s.io/api/scheduling/v1:go_default_library", diff --git a/pkg/printers/internalversion/printers.go b/pkg/printers/internalversion/printers.go index 95d3f1438a3..d1b878872ba 100644 --- a/pkg/printers/internalversion/printers.go +++ b/pkg/printers/internalversion/printers.go @@ -34,6 +34,7 @@ import ( apiv1 "k8s.io/api/core/v1" discoveryv1beta1 "k8s.io/api/discovery/v1beta1" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + flowcontrolv1alpha1 "k8s.io/api/flowcontrol/v1alpha1" policyv1beta1 "k8s.io/api/policy/v1beta1" rbacv1beta1 "k8s.io/api/rbac/v1beta1" schedulingv1 "k8s.io/api/scheduling/v1" @@ -54,6 +55,8 @@ import ( api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/core/helper" "k8s.io/kubernetes/pkg/apis/discovery" + "k8s.io/kubernetes/pkg/apis/flowcontrol" + apihelpers "k8s.io/kubernetes/pkg/apis/flowcontrol/util" "k8s.io/kubernetes/pkg/apis/networking" nodeapi "k8s.io/kubernetes/pkg/apis/node" "k8s.io/kubernetes/pkg/apis/policy" @@ -517,6 +520,29 @@ func AddHandlers(h printers.PrintHandler) { } h.TableHandler(validatingWebhookColumnDefinitions, printValidatingWebhook) h.TableHandler(validatingWebhookColumnDefinitions, printValidatingWebhookList) + + flowSchemaColumnDefinitions := []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]}, + {Name: "PriorityLevel", Type: "string", Description: flowcontrolv1alpha1.PriorityLevelConfigurationReference{}.SwaggerDoc()["name"]}, + {Name: "MatchingPrecedence", Type: "string", Description: flowcontrolv1alpha1.FlowSchemaSpec{}.SwaggerDoc()["matchingPrecedence"]}, + {Name: "DistinguisherMethod", Type: "string", Description: flowcontrolv1alpha1.FlowSchemaSpec{}.SwaggerDoc()["distinguisherMethod"]}, + {Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]}, + {Name: "MissingPL", Type: "string", Description: "references a broken or non-existent PriorityLevelConfiguration"}, + } + h.TableHandler(flowSchemaColumnDefinitions, printFlowSchema) + h.TableHandler(flowSchemaColumnDefinitions, printFlowSchemaList) + + priorityLevelColumnDefinitions := []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]}, + {Name: "Type", Type: "string", Description: flowcontrolv1alpha1.PriorityLevelConfigurationSpec{}.SwaggerDoc()["type"]}, + {Name: "AssuredConcurrencyShares", Type: "string", Description: flowcontrolv1alpha1.LimitedPriorityLevelConfiguration{}.SwaggerDoc()["assuredConcurrencyShares"]}, + {Name: "Queues", Type: "string", Description: flowcontrolv1alpha1.QueuingConfiguration{}.SwaggerDoc()["queues"]}, + {Name: "HandSize", Type: "string", Description: flowcontrolv1alpha1.QueuingConfiguration{}.SwaggerDoc()["handSize"]}, + {Name: "QueueLengthLimit", Type: "string", Description: flowcontrolv1alpha1.QueuingConfiguration{}.SwaggerDoc()["queueLengthLimit"]}, + {Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]}, + } + h.TableHandler(priorityLevelColumnDefinitions, printPriorityLevelConfiguration) + h.TableHandler(priorityLevelColumnDefinitions, printPriorityLevelConfigurationList) } // Pass ports=nil for all ports. @@ -2245,6 +2271,80 @@ func printVolumeAttachmentList(list *storage.VolumeAttachmentList, options print return rows, nil } +func printFlowSchema(obj *flowcontrol.FlowSchema, options printers.GenerateOptions) ([]metav1.TableRow, error) { + row := metav1.TableRow{ + Object: runtime.RawExtension{Object: obj}, + } + + name := obj.Name + plName := obj.Spec.PriorityLevelConfiguration.Name + distinguisherMethod := "" + if obj.Spec.DistinguisherMethod != nil { + distinguisherMethod = string(obj.Spec.DistinguisherMethod.Type) + } + badPLRef := "?" + for _, cond := range obj.Status.Conditions { + if cond.Type == flowcontrol.FlowSchemaConditionDangling { + badPLRef = string(cond.Status) + break + } + } + row.Cells = append(row.Cells, name, plName, obj.Spec.MatchingPrecedence, distinguisherMethod, translateTimestampSince(obj.CreationTimestamp), badPLRef) + + return []metav1.TableRow{row}, nil +} + +func printFlowSchemaList(list *flowcontrol.FlowSchemaList, options printers.GenerateOptions) ([]metav1.TableRow, error) { + rows := make([]metav1.TableRow, 0, len(list.Items)) + fsSeq := make(apihelpers.FlowSchemaSequence, len(list.Items)) + for i := range list.Items { + fsSeq[i] = &list.Items[i] + } + sort.Sort(fsSeq) + for i := range fsSeq { + r, err := printFlowSchema(fsSeq[i], options) + if err != nil { + return nil, err + } + rows = append(rows, r...) + } + return rows, nil +} + +func printPriorityLevelConfiguration(obj *flowcontrol.PriorityLevelConfiguration, options printers.GenerateOptions) ([]metav1.TableRow, error) { + row := metav1.TableRow{ + Object: runtime.RawExtension{Object: obj}, + } + name := obj.Name + acs := interface{}("") + queues := interface{}("") + handSize := interface{}("") + queueLengthLimit := interface{}("") + if obj.Spec.Limited != nil { + acs = obj.Spec.Limited.AssuredConcurrencyShares + if qc := obj.Spec.Limited.LimitResponse.Queuing; qc != nil { + queues = qc.Queues + handSize = qc.HandSize + queueLengthLimit = qc.QueueLengthLimit + } + } + row.Cells = append(row.Cells, name, string(obj.Spec.Type), acs, queues, handSize, queueLengthLimit, translateTimestampSince(obj.CreationTimestamp)) + + return []metav1.TableRow{row}, nil +} + +func printPriorityLevelConfigurationList(list *flowcontrol.PriorityLevelConfigurationList, options printers.GenerateOptions) ([]metav1.TableRow, error) { + rows := make([]metav1.TableRow, 0, len(list.Items)) + for i := range list.Items { + r, err := printPriorityLevelConfiguration(&list.Items[i], options) + if err != nil { + return nil, err + } + rows = append(rows, r...) + } + return rows, nil +} + func printBoolPtr(value *bool) string { if value != nil { return printBool(*value) diff --git a/pkg/printers/internalversion/printers_test.go b/pkg/printers/internalversion/printers_test.go index f797221911d..a45ad60d169 100644 --- a/pkg/printers/internalversion/printers_test.go +++ b/pkg/printers/internalversion/printers_test.go @@ -17,6 +17,7 @@ limitations under the License. package internalversion import ( + "math" "reflect" "testing" "time" @@ -33,6 +34,7 @@ import ( "k8s.io/kubernetes/pkg/apis/coordination" api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/discovery" + "k8s.io/kubernetes/pkg/apis/flowcontrol" "k8s.io/kubernetes/pkg/apis/networking" nodeapi "k8s.io/kubernetes/pkg/apis/node" "k8s.io/kubernetes/pkg/apis/policy" @@ -4831,3 +4833,228 @@ func TestPrintEndpointSlice(t *testing.T) { } } } + +func TestPrintFlowSchema(t *testing.T) { + all := []string{"*"} + + tests := []struct { + fs flowcontrol.FlowSchema + expected []metav1.TableRow + }{ + { + fs: flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "all-matcher", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: flowcontrol.FlowSchemaSpec{ + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{Name: "allee"}, + MatchingPrecedence: math.MaxInt32, + DistinguisherMethod: &flowcontrol.FlowDistinguisherMethod{Type: flowcontrol.FlowDistinguisherMethodByUserType}, + Rules: []flowcontrol.PolicyRulesWithSubjects{{ + Subjects: []flowcontrol.Subject{{ + Kind: flowcontrol.SubjectKindGroup, + Group: &flowcontrol.GroupSubject{Name: "system:authenticated"}, + }}, + ResourceRules: []flowcontrol.ResourcePolicyRule{{ + Verbs: all, + APIGroups: all, + Resources: all, + ClusterScope: true, + Namespaces: all, + }}, + }, { + Subjects: []flowcontrol.Subject{{ + Kind: flowcontrol.SubjectKindGroup, + Group: &flowcontrol.GroupSubject{Name: "system:unauthenticated"}, + }}, + ResourceRules: []flowcontrol.ResourcePolicyRule{{ + Verbs: all, + APIGroups: all, + Resources: all, + ClusterScope: true, + Namespaces: all, + }}, + }, { + Subjects: []flowcontrol.Subject{{ + Kind: flowcontrol.SubjectKindGroup, + Group: &flowcontrol.GroupSubject{Name: "system:authenticated"}, + }, { + Kind: flowcontrol.SubjectKindGroup, + Group: &flowcontrol.GroupSubject{Name: "system:unauthenticated"}, + }}, + NonResourceRules: []flowcontrol.NonResourcePolicyRule{{ + Verbs: all, + NonResourceURLs: all, + }}, + }}, + }, + }, + // Columns: Name, PriorityLevelName, MatchingPrecedence, DistinguisherMethod, Age, MissingPL + expected: []metav1.TableRow{{Cells: []interface{}{"all-matcher", "allee", int32(math.MaxInt32), "ByUser", "0s", "?"}}}, + }, { + fs: flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-matcher", + CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, + }, + Spec: flowcontrol.FlowSchemaSpec{ + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{Name: "allee"}, + MatchingPrecedence: 0, + DistinguisherMethod: &flowcontrol.FlowDistinguisherMethod{Type: flowcontrol.FlowDistinguisherMethodByNamespaceType}, + Rules: []flowcontrol.PolicyRulesWithSubjects{{ + Subjects: []flowcontrol.Subject{{ + Kind: flowcontrol.SubjectKindGroup, + Group: &flowcontrol.GroupSubject{Name: "system:unauthenticated"}, + }}, + ResourceRules: []flowcontrol.ResourcePolicyRule{{ + Verbs: all, + APIGroups: all, + Resources: all, + ClusterScope: true, + Namespaces: all, + }}, + }, { + Subjects: []flowcontrol.Subject{{ + Kind: flowcontrol.SubjectKindGroup, + Group: &flowcontrol.GroupSubject{Name: "system:authenticated"}, + }, { + Kind: flowcontrol.SubjectKindGroup, + Group: &flowcontrol.GroupSubject{Name: "system:unauthenticated"}, + }}, + NonResourceRules: []flowcontrol.NonResourcePolicyRule{{ + Verbs: all, + NonResourceURLs: all, + }}, + }}, + }, + Status: flowcontrol.FlowSchemaStatus{ + Conditions: []flowcontrol.FlowSchemaCondition{{ + Type: flowcontrol.FlowSchemaConditionDangling, + Status: "True", + LastTransitionTime: metav1.Time{Time: time.Now().Add(-time.Hour)}, + }}, + }, + }, + // Columns: Name, PriorityLevelName, MatchingPrecedence, DistinguisherMethod, Age, MissingPL + expected: []metav1.TableRow{{Cells: []interface{}{"some-matcher", "allee", int32(0), "ByNamespace", "5m", "True"}}}, + }, { + fs: flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "exempt", + CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, + }, + Spec: flowcontrol.FlowSchemaSpec{ + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{Name: "allee"}, + MatchingPrecedence: 0, + DistinguisherMethod: nil, + Rules: []flowcontrol.PolicyRulesWithSubjects{{ + Subjects: []flowcontrol.Subject{{ + Kind: flowcontrol.SubjectKindGroup, + Group: &flowcontrol.GroupSubject{Name: "system:masters"}, + }}, + ResourceRules: []flowcontrol.ResourcePolicyRule{{ + Verbs: all, + APIGroups: all, + Resources: all, + ClusterScope: true, + Namespaces: all, + }}, + }}, + }, + }, + // Columns: Name, PriorityLevelName, MatchingPrecedence, DistinguisherMethod, Age, MissingPL + expected: []metav1.TableRow{{Cells: []interface{}{"exempt", "allee", int32(0), "", "5m", "?"}}}, + }, + } + + for i, test := range tests { + rows, err := printFlowSchema(&test.fs, printers.GenerateOptions{}) + if err != nil { + t.Fatal(err) + } + for i := range rows { + rows[i].Object.Object = nil + } + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) + } + } +} + +func TestPrintPriorityLevelConfiguration(t *testing.T) { + tests := []struct { + pl flowcontrol.PriorityLevelConfiguration + expected []metav1.TableRow + }{ + { + pl: flowcontrol.PriorityLevelConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "unlimited", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: flowcontrol.PriorityLevelConfigurationSpec{ + Type: flowcontrol.PriorityLevelEnablementExempt, + }, + }, + // Columns: Name, Type, AssuredConcurrencyShares, Queues, HandSize, QueueLengthLimit, Age + expected: []metav1.TableRow{{Cells: []interface{}{"unlimited", "Exempt", "", "", "", "", "0s"}}}, + }, + { + pl: flowcontrol.PriorityLevelConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "unqueued", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: flowcontrol.PriorityLevelConfigurationSpec{ + Type: flowcontrol.PriorityLevelEnablementLimited, + Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ + AssuredConcurrencyShares: 47, + LimitResponse: flowcontrol.LimitResponse{ + Type: flowcontrol.LimitResponseTypeReject, + }, + }, + }, + }, + // Columns: Name, Type, AssuredConcurrencyShares, Queues, HandSize, QueueLengthLimit, Age + expected: []metav1.TableRow{{Cells: []interface{}{"unqueued", "Limited", int32(47), "", "", "", "0s"}}}, + }, + { + pl: flowcontrol.PriorityLevelConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "queued", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: flowcontrol.PriorityLevelConfigurationSpec{ + Type: flowcontrol.PriorityLevelEnablementLimited, + Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ + AssuredConcurrencyShares: 42, + LimitResponse: flowcontrol.LimitResponse{ + Type: flowcontrol.LimitResponseTypeQueue, + Queuing: &flowcontrol.QueuingConfiguration{ + Queues: 8, + HandSize: 3, + QueueLengthLimit: 4, + }, + }, + }, + }, + }, + // Columns: Name, Type, AssuredConcurrencyShares, Queues, HandSize, QueueLengthLimit, Age + expected: []metav1.TableRow{{Cells: []interface{}{"queued", "Limited", int32(42), int32(8), int32(3), int32(4), "0s"}}}, + }, + } + + for i, test := range tests { + rows, err := printPriorityLevelConfiguration(&test.pl, printers.GenerateOptions{}) + if err != nil { + t.Fatal(err) + } + for i := range rows { + rows[i].Object.Object = nil + } + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) + } + } +} diff --git a/test/integration/apiserver/print_test.go b/test/integration/apiserver/print_test.go index eab0c99f651..679cb1280c0 100644 --- a/test/integration/apiserver/print_test.go +++ b/test/integration/apiserver/print_test.go @@ -130,8 +130,6 @@ var missingHanlders = sets.NewString( "PriorityClass", "PodPreset", "AuditSink", - "FlowSchema", // TODO(yue9944882): remove this comment by merging print-handler for flow-control API - "PriorityLevelConfiguration", // TODO(yue9944882): remove this comment by merging print-handler for flow-control API ) // known types that are no longer served we should tolerate restmapper errors for