diff --git a/pkg/printers/internalversion/BUILD b/pkg/printers/internalversion/BUILD index b97046688c5..4c9bc328bb4 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", @@ -99,6 +103,7 @@ go_library( "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/duration:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/authentication/user:go_default_library", ], ) diff --git a/pkg/printers/internalversion/printers.go b/pkg/printers/internalversion/printers.go index 95d3f1438a3..3c81140b69f 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" @@ -45,6 +46,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/duration" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apiserver/pkg/authentication/user" "k8s.io/kubernetes/pkg/apis/admissionregistration" "k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/autoscaling" @@ -54,6 +56,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 +521,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: "MatchesAll", Type: "bolean", Description: "matches all requests"}, + {Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]}, + } + 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 +2272,118 @@ func printVolumeAttachmentList(list *storage.VolumeAttachmentList, options print return rows, nil } +func fsMatchesAll(obj *flowcontrol.FlowSchema) bool { + var allResources, allNonResources [2]bool + for _, prws := range obj.Spec.Rules { + allAuth, allUnauth := false, false + for _, subj := range prws.Subjects { + if subj.Group == nil { + continue + } + allAuth = allAuth || subj.Group.Name == user.AllAuthenticated + allUnauth = allUnauth || subj.Group.Name == user.AllUnauthenticated + } + anyAll := allAuth || allUnauth + if !anyAll { + continue + } + for _, nrr := range prws.NonResourceRules { + if hasWildcard(nrr.Verbs) && hasWildcard(nrr.NonResourceURLs) { + allNonResources[0] = allNonResources[0] || allAuth + allNonResources[1] = allNonResources[1] || allUnauth + break + } + } + for _, rr := range prws.ResourceRules { + if hasWildcard(rr.Verbs) && hasWildcard(rr.APIGroups) && hasWildcard(rr.Resources) && rr.ClusterScope && hasWildcard(rr.Namespaces) { + allResources[0] = allResources[0] || allAuth + allResources[1] = allResources[1] || allUnauth + break + } + } + if allResources[0] && allResources[1] && allNonResources[0] && allNonResources[1] { + return true + } + } + return false +} + +func hasWildcard(arr []string) bool { + for _, elt := range arr { + if elt == "*" { + return true + } + } + return false +} + +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) + } + row.Cells = append(row.Cells, name, plName, obj.Spec.MatchingPrecedence, distinguisherMethod, fsMatchesAll(obj), translateTimestampSince(obj.CreationTimestamp)) + + 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..bd646fb3901 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,221 @@ 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, MatchesAll, Age + expected: []metav1.TableRow{{Cells: []interface{}{"all-matcher", "allee", int32(math.MaxInt32), "ByUser", true, "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, + }}, + }}, + }, + }, + // Columns: Name, PriorityLevelName, MatchingPrecedence, DistinguisherMethod, MatchesAll, Age + expected: []metav1.TableRow{{Cells: []interface{}{"some-matcher", "allee", int32(0), "ByNamespace", false, "5m"}}}, + }, { + 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, MatchesAll, Age + expected: []metav1.TableRow{{Cells: []interface{}{"exempt", "allee", int32(0), "", false, "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 4b25613d417..eed07330a6b 100644 --- a/test/integration/apiserver/print_test.go +++ b/test/integration/apiserver/print_test.go @@ -129,8 +129,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