Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preserve API group order in discovery, prefer extensions over apps #43553

Merged
merged 1 commit into from
Mar 23, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 11 additions & 11 deletions api/swagger-spec/resourceListing.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,6 @@
"path": "/api",
"description": "get available API versions"
},
{
"path": "/apis/apps/v1beta1",
"description": "API at /apis/apps/v1beta1"
},
{
"path": "/apis/apps",
"description": "get information of a group"
},
{
"path": "/apis/authentication.k8s.io/v1",
"description": "API at /apis/authentication.k8s.io/v1"
Expand Down Expand Up @@ -113,6 +105,14 @@
"path": "/apis/rbac.authorization.k8s.io",
"description": "get information of a group"
},
{
"path": "/apis/settings.k8s.io/v1alpha1",
"description": "API at /apis/settings.k8s.io/v1alpha1"
},
{
"path": "/apis/settings.k8s.io",
"description": "get information of a group"
},
{
"path": "/apis/storage.k8s.io/v1beta1",
"description": "API at /apis/storage.k8s.io/v1beta1"
Expand All @@ -126,11 +126,11 @@
"description": "get information of a group"
},
{
"path": "/apis/settings.k8s.io/v1alpha1",
"description": "API at /apis/settings.k8s.io/v1alpha1"
"path": "/apis/apps/v1beta1",
"description": "API at /apis/apps/v1beta1"
},
{
"path": "/apis/settings.k8s.io",
"path": "/apis/apps",
"description": "get information of a group"
}
],
Expand Down
2 changes: 1 addition & 1 deletion hack/make-rules/test-cmd-util.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1151,7 +1151,7 @@ run_kubectl_get_tests() {
kube::test::if_has_string "${output_message}" "/apis/apps/v1beta1/namespaces/default/statefulsets 200 OK"
kube::test::if_has_string "${output_message}" "/apis/autoscaling/v1/namespaces/default/horizontalpodautoscalers 200"
kube::test::if_has_string "${output_message}" "/apis/batch/v1/namespaces/default/jobs 200 OK"
kube::test::if_has_string "${output_message}" "/apis/apps/v1beta1/namespaces/default/deployments 200 OK"
kube::test::if_has_string "${output_message}" "/apis/extensions/v1beta1/namespaces/default/deployments 200 OK"
kube::test::if_has_string "${output_message}" "/apis/extensions/v1beta1/namespaces/default/replicasets 200 OK"

### Test --allow-missing-template-keys
Expand Down
9 changes: 7 additions & 2 deletions pkg/master/master.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,10 @@ func (c completedConfig) New() (*Master, error) {
m.InstallLegacyAPI(c.Config, c.Config.GenericConfig.RESTOptionsGetter, legacyRESTStorageProvider)
}

// The order here is preserved in discovery.
// If resources with identical names exist in more than one of these groups (e.g. "deployments.apps"" and "deployments.extensions"),
// the order of this list determines which group an unqualified resource name (e.g. "deployments") should prefer.
restStorageProviders := []RESTStorageProvider{
appsrest.RESTStorageProvider{},
authenticationrest.RESTStorageProvider{Authenticator: c.GenericConfig.Authenticator},
authorizationrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorizer},
autoscalingrest.RESTStorageProvider{},
Expand All @@ -250,8 +252,11 @@ func (c completedConfig) New() (*Master, error) {
extensionsrest.RESTStorageProvider{ResourceInterface: thirdparty.NewThirdPartyResourceServer(s, c.StorageFactory)},
policyrest.RESTStorageProvider{},
rbacrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorizer},
storagerest.RESTStorageProvider{},
settingsrest.RESTStorageProvider{},
storagerest.RESTStorageProvider{},
// keep apps after extensions so legacy clients resolve the extensions versions of shared resource names.
// See https://github.com/kubernetes/kubernetes/issues/42392
appsrest.RESTStorageProvider{},
}
m.InstallAPIs(c.Config.APIResourceConfigSource, c.Config.GenericConfig.RESTOptionsGetter, restStorageProviders...)

Expand Down
28 changes: 20 additions & 8 deletions staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (
"fmt"
"mime"
"net/http"
"sort"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -128,8 +127,10 @@ type GenericAPIServer struct {

// Map storing information about all groups to be exposed in discovery response.
// The map is from name to the group.
// The slice preserves group name insertion order.
apiGroupsForDiscoveryLock sync.RWMutex
apiGroupsForDiscovery map[string]metav1.APIGroup
apiGroupNamesForDiscovery []string

// Enable swagger and/or OpenAPI if these configs are non-nil.
swaggerConfig *swagger.Config
Expand Down Expand Up @@ -334,13 +335,28 @@ func (s *GenericAPIServer) AddAPIGroupForDiscovery(apiGroup metav1.APIGroup) {
s.apiGroupsForDiscoveryLock.Lock()
defer s.apiGroupsForDiscoveryLock.Unlock()

// Insert the group into the ordered list if it is not already present
if _, exists := s.apiGroupsForDiscovery[apiGroup.Name]; !exists {
s.apiGroupNamesForDiscovery = append(s.apiGroupNamesForDiscovery, apiGroup.Name)
}

s.apiGroupsForDiscovery[apiGroup.Name] = apiGroup
}

func (s *GenericAPIServer) RemoveAPIGroupForDiscovery(groupName string) {
s.apiGroupsForDiscoveryLock.Lock()
defer s.apiGroupsForDiscoveryLock.Unlock()

// Remove the group from the ordered list
// https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating
newOrder := s.apiGroupNamesForDiscovery[:0]
for _, orderedGroupName := range s.apiGroupNamesForDiscovery {
if orderedGroupName != groupName {
newOrder = append(newOrder, orderedGroupName)
}
}
s.apiGroupNamesForDiscovery = newOrder

delete(s.apiGroupsForDiscovery, groupName)
}

Expand Down Expand Up @@ -385,14 +401,10 @@ func (s *GenericAPIServer) DynamicApisDiscovery() *restful.WebService {
s.apiGroupsForDiscoveryLock.RLock()
defer s.apiGroupsForDiscoveryLock.RUnlock()

// sort to have a deterministic order
sortedGroups := []metav1.APIGroup{}
groupNames := make([]string, 0, len(s.apiGroupsForDiscovery))
for groupName := range s.apiGroupsForDiscovery {
groupNames = append(groupNames, groupName)
}
sort.Strings(groupNames)
for _, groupName := range groupNames {

// ranging over apiGroupNamesForDiscovery preserves the registration order
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please document that the order is preserved in the function description, also.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 Add a godoc to AddAPIGroupForDiscovery that the call order is significant.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

opened #43623 with doc

for _, groupName := range s.apiGroupNamesForDiscovery {
sortedGroups = append(sortedGroups, s.apiGroupsForDiscovery[groupName])
}

Expand Down
58 changes: 58 additions & 0 deletions staging/src/k8s.io/apiserver/pkg/server/genericapiserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,64 @@ func TestDiscoveryAtAPIS(t *testing.T) {
assert.Equal(0, len(groupList.Groups))
}

func TestDiscoveryOrdering(t *testing.T) {
master, etcdserver, _, assert := newMaster(t)
defer etcdserver.Terminate(t)

server := httptest.NewServer(master.InsecureHandler)
groupList, err := getGroupList(server)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
assert.Equal(0, len(groupList.Groups))

// Register three groups
master.AddAPIGroupForDiscovery(metav1.APIGroup{Name: "x"})
master.AddAPIGroupForDiscovery(metav1.APIGroup{Name: "y"})
master.AddAPIGroupForDiscovery(metav1.APIGroup{Name: "z"})
// Register three additional groups that come earlier alphabetically
master.AddAPIGroupForDiscovery(metav1.APIGroup{Name: "a"})
master.AddAPIGroupForDiscovery(metav1.APIGroup{Name: "b"})
master.AddAPIGroupForDiscovery(metav1.APIGroup{Name: "c"})
// Make sure re-adding doesn't double-register or make a group lose its place
master.AddAPIGroupForDiscovery(metav1.APIGroup{Name: "x"})

groupList, err = getGroupList(server)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

assert.Equal(6, len(groupList.Groups))
assert.Equal("x", groupList.Groups[0].Name)
assert.Equal("y", groupList.Groups[1].Name)
assert.Equal("z", groupList.Groups[2].Name)
assert.Equal("a", groupList.Groups[3].Name)
assert.Equal("b", groupList.Groups[4].Name)
assert.Equal("c", groupList.Groups[5].Name)

// Remove a group.
master.RemoveAPIGroupForDiscovery("a")
groupList, err = getGroupList(server)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
assert.Equal(5, len(groupList.Groups))

// Re-adding should move to the end.
master.AddAPIGroupForDiscovery(metav1.APIGroup{Name: "a"})
groupList, err = getGroupList(server)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
assert.Equal(6, len(groupList.Groups))
assert.Equal("x", groupList.Groups[0].Name)
assert.Equal("y", groupList.Groups[1].Name)
assert.Equal("z", groupList.Groups[2].Name)
assert.Equal("b", groupList.Groups[3].Name)
assert.Equal("c", groupList.Groups[4].Name)
assert.Equal("a", groupList.Groups[5].Name)
}

func TestGetServerAddressByClientCIDRs(t *testing.T) {
publicAddressCIDRMap := []metav1.ServerAddressByClientCIDR{
{
Expand Down