Merge pull request #43553 from liggitt/discovery-order

Automatic merge from submit-queue

Preserve API group order in discovery, prefer extensions over apps

Fixes #42392, supercedes #43543

Kubectl 1.5 still uses compiled in types in kubectl edit and apply.

Because kubectl 1.5 does not have the apps/v1beta1/deployment resource compiled in, the preferred group order must have extensions come before apps. The preference order is determined by the order that the groups are listed by the discovery service, with the first elements preferred over the last elements.

This PR:
* updates the discovery code to preserve the order groups were registered in
* updates the registration order to move the `apps` group to the end of the list (for the same result as https://github.com/kubernetes/kubernetes/issues/43543)

This has the side benefit of making all TPR API groups (regardless of group name) come after the core API groups, instead of potentially appearing earlier in discovery order

```release-note
The API server discovery document now prioritizes the `extensions` API group over the `apps` API group. This ensures certain commands in 1.5 versions of kubectl (such as `kubectl edit deployment`) continue to function against a 1.6 API.
```
This commit is contained in:
Kubernetes Submit Queue 2017-03-23 12:45:29 -07:00 committed by GitHub
commit b1e665f57d
5 changed files with 97 additions and 22 deletions

View File

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

View File

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

View File

@ -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{},
@ -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...)

View File

@ -20,7 +20,6 @@ import (
"fmt"
"mime"
"net/http"
"sort"
"strings"
"sync"
"time"
@ -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
@ -334,6 +335,11 @@ 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
}
@ -341,6 +347,16 @@ 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)
}
@ -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
for _, groupName := range s.apiGroupNamesForDiscovery {
sortedGroups = append(sortedGroups, s.apiGroupsForDiscovery[groupName])
}

View File

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