Added server-side printers for the API object types for API priority and fairness

This commit is contained in:
Mike Spreitzer 2019-11-14 20:00:29 -05:00
parent 3d8317ae91
commit ce12105edc
4 changed files with 364 additions and 2 deletions

View File

@ -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",
],
)

View File

@ -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 := "<none>"
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{}("<none>")
queues := interface{}("<none>")
handSize := interface{}("<none>")
queueLengthLimit := interface{}("<none>")
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)

View File

@ -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), "<none>", 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", "<none>", "<none>", "<none>", "<none>", "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), "<none>", "<none>", "<none>", "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))
}
}
}

View File

@ -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