Commit 97889d4f authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #45432 from deads2k/agg-30-status

Automatic merge from submit-queue (batch tested with PRs 44798, 45537, 45448, 45432) use apiservice.status to break apart controller and handling concerns Still needs tests. This starts breaking the handler and controller aspects of the aggregator by making use of status and conditions instead of actually running a specific check on demand. @kubernetes/sig-api-machinery-pr-reviews @luxas since you've been asking
parents fc287626 272aa243
...@@ -66,3 +66,37 @@ func APIServiceNameToGroupVersion(apiServiceName string) schema.GroupVersion { ...@@ -66,3 +66,37 @@ func APIServiceNameToGroupVersion(apiServiceName string) schema.GroupVersion {
tokens := strings.SplitN(apiServiceName, ".", 2) tokens := strings.SplitN(apiServiceName, ".", 2)
return schema.GroupVersion{Group: tokens[1], Version: tokens[0]} return schema.GroupVersion{Group: tokens[1], Version: tokens[0]}
} }
// SetAPIServiceCondition sets the status condition. It either overwrites the existing one or
// creates a new one
func SetAPIServiceCondition(apiService *APIService, newCondition APIServiceCondition) {
var existingCondition *APIServiceCondition
for i := range apiService.Status.Conditions {
if apiService.Status.Conditions[i].Type == newCondition.Type {
existingCondition = &apiService.Status.Conditions[i]
break
}
}
if existingCondition == nil {
apiService.Status.Conditions = append(apiService.Status.Conditions, newCondition)
return
}
if existingCondition.Status != newCondition.Status {
existingCondition.Status = newCondition.Status
existingCondition.LastTransitionTime = newCondition.LastTransitionTime
}
existingCondition.Reason = newCondition.Reason
existingCondition.Message = newCondition.Message
}
// IsAPIServiceConditionTrue indicates if the condition is present and strictly true
func IsAPIServiceConditionTrue(apiService *APIService, conditionType APIServiceConditionType) bool {
for _, condition := range apiService.Status.Conditions {
if condition.Type == conditionType && condition.Status == ConditionTrue {
return true
}
}
return false
}
...@@ -24,10 +24,10 @@ import ( ...@@ -24,10 +24,10 @@ import (
utilvalidation "k8s.io/apimachinery/pkg/util/validation" utilvalidation "k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
discoveryapi "k8s.io/kube-aggregator/pkg/apis/apiregistration" "k8s.io/kube-aggregator/pkg/apis/apiregistration"
) )
func ValidateAPIService(apiService *discoveryapi.APIService) field.ErrorList { func ValidateAPIService(apiService *apiregistration.APIService) field.ErrorList {
requiredName := apiService.Spec.Version + "." + apiService.Spec.Group requiredName := apiService.Spec.Version + "." + apiService.Spec.Group
allErrs := validation.ValidateObjectMeta(&apiService.ObjectMeta, false, allErrs := validation.ValidateObjectMeta(&apiService.ObjectMeta, false,
...@@ -86,9 +86,30 @@ func ValidateAPIService(apiService *discoveryapi.APIService) field.ErrorList { ...@@ -86,9 +86,30 @@ func ValidateAPIService(apiService *discoveryapi.APIService) field.ErrorList {
return allErrs return allErrs
} }
func ValidateAPIServiceUpdate(newAPIService *discoveryapi.APIService, oldAPIService *discoveryapi.APIService) field.ErrorList { func ValidateAPIServiceUpdate(newAPIService *apiregistration.APIService, oldAPIService *apiregistration.APIService) field.ErrorList {
allErrs := validation.ValidateObjectMetaUpdate(&newAPIService.ObjectMeta, &oldAPIService.ObjectMeta, field.NewPath("metadata")) allErrs := validation.ValidateObjectMetaUpdate(&newAPIService.ObjectMeta, &oldAPIService.ObjectMeta, field.NewPath("metadata"))
allErrs = append(allErrs, ValidateAPIService(newAPIService)...) allErrs = append(allErrs, ValidateAPIService(newAPIService)...)
return allErrs return allErrs
} }
func ValidateAPIServiceStatus(status *apiregistration.APIServiceStatus, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
for i, condition := range status.Conditions {
if condition.Status != apiregistration.ConditionTrue &&
condition.Status != apiregistration.ConditionFalse &&
condition.Status != apiregistration.ConditionUnknown {
allErrs = append(allErrs, field.NotSupported(fldPath.Child("conditions").Index(i).Child("status"), condition.Status, []string{
string(apiregistration.ConditionTrue), string(apiregistration.ConditionFalse), string(apiregistration.ConditionUnknown)}))
}
}
return allErrs
}
func ValidateAPIServiceStatusUpdate(newAPIService *apiregistration.APIService, oldAPIService *apiregistration.APIService) field.ErrorList {
allErrs := validation.ValidateObjectMetaUpdate(&newAPIService.ObjectMeta, &oldAPIService.ObjectMeta, field.NewPath("metadata"))
allErrs = append(allErrs, ValidateAPIServiceStatus(&newAPIService.Status, field.NewPath("status"))...)
return allErrs
}
...@@ -81,6 +81,7 @@ go_library( ...@@ -81,6 +81,7 @@ go_library(
"//vendor/k8s.io/kube-aggregator/pkg/client/informers/internalversion/apiregistration/internalversion:go_default_library", "//vendor/k8s.io/kube-aggregator/pkg/client/informers/internalversion/apiregistration/internalversion:go_default_library",
"//vendor/k8s.io/kube-aggregator/pkg/client/listers/apiregistration/internalversion:go_default_library", "//vendor/k8s.io/kube-aggregator/pkg/client/listers/apiregistration/internalversion:go_default_library",
"//vendor/k8s.io/kube-aggregator/pkg/controllers:go_default_library", "//vendor/k8s.io/kube-aggregator/pkg/controllers:go_default_library",
"//vendor/k8s.io/kube-aggregator/pkg/controllers/status:go_default_library",
"//vendor/k8s.io/kube-aggregator/pkg/registry/apiservice/etcd:go_default_library", "//vendor/k8s.io/kube-aggregator/pkg/registry/apiservice/etcd:go_default_library",
], ],
) )
...@@ -32,7 +32,6 @@ import ( ...@@ -32,7 +32,6 @@ import (
genericapiserver "k8s.io/apiserver/pkg/server" genericapiserver "k8s.io/apiserver/pkg/server"
kubeinformers "k8s.io/client-go/informers" kubeinformers "k8s.io/client-go/informers"
kubeclientset "k8s.io/client-go/kubernetes" kubeclientset "k8s.io/client-go/kubernetes"
v1listers "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/pkg/version" "k8s.io/client-go/pkg/version"
"k8s.io/kube-aggregator/pkg/apis/apiregistration" "k8s.io/kube-aggregator/pkg/apis/apiregistration"
...@@ -41,6 +40,7 @@ import ( ...@@ -41,6 +40,7 @@ import (
"k8s.io/kube-aggregator/pkg/client/clientset_generated/internalclientset" "k8s.io/kube-aggregator/pkg/client/clientset_generated/internalclientset"
informers "k8s.io/kube-aggregator/pkg/client/informers/internalversion" informers "k8s.io/kube-aggregator/pkg/client/informers/internalversion"
listers "k8s.io/kube-aggregator/pkg/client/listers/apiregistration/internalversion" listers "k8s.io/kube-aggregator/pkg/client/listers/apiregistration/internalversion"
statuscontrollers "k8s.io/kube-aggregator/pkg/controllers/status"
apiservicestorage "k8s.io/kube-aggregator/pkg/registry/apiservice/etcd" apiservicestorage "k8s.io/kube-aggregator/pkg/registry/apiservice/etcd"
) )
...@@ -102,11 +102,6 @@ type APIAggregator struct { ...@@ -102,11 +102,6 @@ type APIAggregator struct {
// controller state // controller state
lister listers.APIServiceLister lister listers.APIServiceLister
// serviceLister is used by the aggregator handler to determine whether or not to try to expose the group
serviceLister v1listers.ServiceLister
// endpointsLister is used by the aggregator handler to determine whether or not to try to expose the group
endpointsLister v1listers.EndpointsLister
// provided for easier embedding // provided for easier embedding
APIRegistrationInformers informers.SharedInformerFactory APIRegistrationInformers informers.SharedInformerFactory
} }
...@@ -140,30 +135,34 @@ func (c completedConfig) NewWithDelegate(delegationTarget genericapiserver.Deleg ...@@ -140,30 +135,34 @@ func (c completedConfig) NewWithDelegate(delegationTarget genericapiserver.Deleg
return nil, err return nil, err
} }
apiregistrationClient, err := internalclientset.NewForConfig(c.Config.GenericConfig.LoopbackClientConfig)
if err != nil {
return nil, err
}
informerFactory := informers.NewSharedInformerFactory( informerFactory := informers.NewSharedInformerFactory(
internalclientset.NewForConfigOrDie(c.Config.GenericConfig.LoopbackClientConfig), apiregistrationClient,
5*time.Minute, // this is effectively used as a refresh interval right now. Might want to do something nicer later on. 5*time.Minute, // this is effectively used as a refresh interval right now. Might want to do something nicer later on.
) )
kubeInformers := kubeinformers.NewSharedInformerFactory(c.CoreAPIServerClient, 5*time.Minute) kubeInformers := kubeinformers.NewSharedInformerFactory(c.CoreAPIServerClient, 5*time.Minute)
s := &APIAggregator{ s := &APIAggregator{
GenericAPIServer: genericServer, GenericAPIServer: genericServer,
delegateHandler: delegationTarget.UnprotectedHandler(), delegateHandler: delegationTarget.UnprotectedHandler(),
contextMapper: c.GenericConfig.RequestContextMapper, contextMapper: c.GenericConfig.RequestContextMapper,
proxyClientCert: c.ProxyClientCert, proxyClientCert: c.ProxyClientCert,
proxyClientKey: c.ProxyClientKey, proxyClientKey: c.ProxyClientKey,
proxyHandlers: map[string]*proxyHandler{}, proxyHandlers: map[string]*proxyHandler{},
handledGroups: sets.String{}, handledGroups: sets.String{},
lister: informerFactory.Apiregistration().InternalVersion().APIServices().Lister(), lister: informerFactory.Apiregistration().InternalVersion().APIServices().Lister(),
serviceLister: kubeInformers.Core().V1().Services().Lister(),
endpointsLister: kubeInformers.Core().V1().Endpoints().Lister(),
APIRegistrationInformers: informerFactory, APIRegistrationInformers: informerFactory,
} }
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(apiregistration.GroupName, registry, Scheme, metav1.ParameterCodec, Codecs) apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(apiregistration.GroupName, registry, Scheme, metav1.ParameterCodec, Codecs)
apiGroupInfo.GroupMeta.GroupVersion = v1alpha1.SchemeGroupVersion apiGroupInfo.GroupMeta.GroupVersion = v1alpha1.SchemeGroupVersion
v1alpha1storage := map[string]rest.Storage{} v1alpha1storage := map[string]rest.Storage{}
v1alpha1storage["apiservices"] = apiservicestorage.NewREST(Scheme, c.GenericConfig.RESTOptionsGetter) apiServiceREST := apiservicestorage.NewREST(Scheme, c.GenericConfig.RESTOptionsGetter)
v1alpha1storage["apiservices"] = apiServiceREST
v1alpha1storage["apiservices/status"] = apiservicestorage.NewStatusREST(Scheme, apiServiceREST)
apiGroupInfo.VersionedResourcesStorageMap["v1alpha1"] = v1alpha1storage apiGroupInfo.VersionedResourcesStorageMap["v1alpha1"] = v1alpha1storage
if err := s.GenericAPIServer.InstallAPIGroup(&apiGroupInfo); err != nil { if err := s.GenericAPIServer.InstallAPIGroup(&apiGroupInfo); err != nil {
...@@ -171,15 +170,19 @@ func (c completedConfig) NewWithDelegate(delegationTarget genericapiserver.Deleg ...@@ -171,15 +170,19 @@ func (c completedConfig) NewWithDelegate(delegationTarget genericapiserver.Deleg
} }
apisHandler := &apisHandler{ apisHandler := &apisHandler{
codecs: Codecs, codecs: Codecs,
lister: s.lister, lister: s.lister,
serviceLister: s.serviceLister,
endpointsLister: s.endpointsLister,
} }
s.GenericAPIServer.Handler.PostGoRestfulMux.Handle("/apis", apisHandler) s.GenericAPIServer.Handler.PostGoRestfulMux.Handle("/apis", apisHandler)
s.GenericAPIServer.Handler.PostGoRestfulMux.UnlistedHandle("/apis/", apisHandler) s.GenericAPIServer.Handler.PostGoRestfulMux.UnlistedHandle("/apis/", apisHandler)
apiserviceRegistrationController := NewAPIServiceRegistrationController(informerFactory.Apiregistration().InternalVersion().APIServices(), kubeInformers.Core().V1().Services(), s) apiserviceRegistrationController := NewAPIServiceRegistrationController(informerFactory.Apiregistration().InternalVersion().APIServices(), kubeInformers.Core().V1().Services(), s)
availableController := statuscontrollers.NewAvailableConditionController(
informerFactory.Apiregistration().InternalVersion().APIServices(),
kubeInformers.Core().V1().Services(),
kubeInformers.Core().V1().Endpoints(),
apiregistrationClient.Apiregistration(),
)
s.GenericAPIServer.AddPostStartHook("start-kube-aggregator-informers", func(context genericapiserver.PostStartHookContext) error { s.GenericAPIServer.AddPostStartHook("start-kube-aggregator-informers", func(context genericapiserver.PostStartHookContext) error {
informerFactory.Start(stopCh) informerFactory.Start(stopCh)
...@@ -190,6 +193,10 @@ func (c completedConfig) NewWithDelegate(delegationTarget genericapiserver.Deleg ...@@ -190,6 +193,10 @@ func (c completedConfig) NewWithDelegate(delegationTarget genericapiserver.Deleg
go apiserviceRegistrationController.Run(stopCh) go apiserviceRegistrationController.Run(stopCh)
return nil return nil
}) })
s.GenericAPIServer.AddPostStartHook("apiservice-status-available-controller", func(context genericapiserver.PostStartHookContext) error {
go availableController.Run(stopCh)
return nil
})
return s, nil return s, nil
} }
...@@ -235,12 +242,10 @@ func (s *APIAggregator) AddAPIService(apiService *apiregistration.APIService, de ...@@ -235,12 +242,10 @@ func (s *APIAggregator) AddAPIService(apiService *apiregistration.APIService, de
// it's time to register the group aggregation endpoint // it's time to register the group aggregation endpoint
groupPath := "/apis/" + apiService.Spec.Group groupPath := "/apis/" + apiService.Spec.Group
groupDiscoveryHandler := &apiGroupHandler{ groupDiscoveryHandler := &apiGroupHandler{
codecs: Codecs, codecs: Codecs,
groupName: apiService.Spec.Group, groupName: apiService.Spec.Group,
lister: s.lister, lister: s.lister,
serviceLister: s.serviceLister, delegate: s.delegateHandler,
endpointsLister: s.endpointsLister,
delegate: s.delegateHandler,
} }
// aggregation is protected // aggregation is protected
s.GenericAPIServer.Handler.PostGoRestfulMux.Handle(groupPath, groupDiscoveryHandler) s.GenericAPIServer.Handler.PostGoRestfulMux.Handle(groupPath, groupDiscoveryHandler)
......
...@@ -96,6 +96,13 @@ func (c *APIServiceRegistrationController) sync(key string) error { ...@@ -96,6 +96,13 @@ func (c *APIServiceRegistrationController) sync(key string) error {
return err return err
} }
// remove registration handling for APIServices which are not available
if !apiregistration.IsAPIServiceConditionTrue(apiService, apiregistration.Available) {
c.apiHandlerManager.RemoveAPIService(key)
return nil
}
// TODO move the destination host to status so that you can see where its going
c.apiHandlerManager.AddAPIService(apiService, c.getDestinationHost(apiService)) c.apiHandlerManager.AddAPIService(apiService, c.getDestinationHost(apiService))
return nil return nil
} }
......
...@@ -25,7 +25,6 @@ import ( ...@@ -25,7 +25,6 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
v1listers "k8s.io/client-go/listers/core/v1"
apiregistrationapi "k8s.io/kube-aggregator/pkg/apis/apiregistration" apiregistrationapi "k8s.io/kube-aggregator/pkg/apis/apiregistration"
apiregistrationv1alpha1api "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1alpha1" apiregistrationv1alpha1api "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1alpha1"
...@@ -37,9 +36,6 @@ import ( ...@@ -37,9 +36,6 @@ import (
type apisHandler struct { type apisHandler struct {
codecs serializer.CodecFactory codecs serializer.CodecFactory
lister listers.APIServiceLister lister listers.APIServiceLister
serviceLister v1listers.ServiceLister
endpointsLister v1listers.EndpointsLister
} }
var discoveryGroup = metav1.APIGroup{ var discoveryGroup = metav1.APIGroup{
...@@ -74,7 +70,7 @@ func (r *apisHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { ...@@ -74,7 +70,7 @@ func (r *apisHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if len(apiGroupServers[0].Spec.Group) == 0 { if len(apiGroupServers[0].Spec.Group) == 0 {
continue continue
} }
discoveryGroup := convertToDiscoveryAPIGroup(apiGroupServers, r.serviceLister, r.endpointsLister) discoveryGroup := convertToDiscoveryAPIGroup(apiGroupServers)
if discoveryGroup != nil { if discoveryGroup != nil {
discoveryGroupList.Groups = append(discoveryGroupList.Groups, *discoveryGroup) discoveryGroupList.Groups = append(discoveryGroupList.Groups, *discoveryGroup)
} }
...@@ -85,33 +81,14 @@ func (r *apisHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { ...@@ -85,33 +81,14 @@ func (r *apisHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// convertToDiscoveryAPIGroup takes apiservices in a single group and returns a discovery compatible object. // convertToDiscoveryAPIGroup takes apiservices in a single group and returns a discovery compatible object.
// if none of the services are available, it will return nil. // if none of the services are available, it will return nil.
func convertToDiscoveryAPIGroup(apiServices []*apiregistrationapi.APIService, serviceLister v1listers.ServiceLister, endpointsLister v1listers.EndpointsLister) *metav1.APIGroup { func convertToDiscoveryAPIGroup(apiServices []*apiregistrationapi.APIService) *metav1.APIGroup {
apiServicesByGroup := apiregistrationapi.SortedByGroup(apiServices)[0] apiServicesByGroup := apiregistrationapi.SortedByGroup(apiServices)[0]
var discoveryGroup *metav1.APIGroup var discoveryGroup *metav1.APIGroup
for _, apiService := range apiServicesByGroup { for _, apiService := range apiServicesByGroup {
if apiService.Spec.Service != nil { if !apiregistrationapi.IsAPIServiceConditionTrue(apiService, apiregistrationapi.Available) {
// skip any API services without actual services continue
if _, err := serviceLister.Services(apiService.Spec.Service.Namespace).Get(apiService.Spec.Service.Name); err != nil {
continue
}
hasActiveEndpoints := false
endpoints, err := endpointsLister.Endpoints(apiService.Spec.Service.Namespace).Get(apiService.Spec.Service.Name)
// skip any API services without endpoints
if err != nil {
continue
}
for _, subset := range endpoints.Subsets {
if len(subset.Addresses) > 0 {
hasActiveEndpoints = true
break
}
}
if !hasActiveEndpoints {
continue
}
} }
// the first APIService which is valid becomes the default // the first APIService which is valid becomes the default
...@@ -143,9 +120,6 @@ type apiGroupHandler struct { ...@@ -143,9 +120,6 @@ type apiGroupHandler struct {
lister listers.APIServiceLister lister listers.APIServiceLister
serviceLister v1listers.ServiceLister
endpointsLister v1listers.EndpointsLister
delegate http.Handler delegate http.Handler
} }
...@@ -172,7 +146,7 @@ func (r *apiGroupHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { ...@@ -172,7 +146,7 @@ func (r *apiGroupHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
discoveryGroup := convertToDiscoveryAPIGroup(apiServicesForGroup, r.serviceLister, r.endpointsLister) discoveryGroup := convertToDiscoveryAPIGroup(apiServicesForGroup)
if discoveryGroup == nil { if discoveryGroup == nil {
http.Error(w, "", http.StatusNotFound) http.Error(w, "", http.StatusNotFound)
return return
......
...@@ -27,8 +27,6 @@ import ( ...@@ -27,8 +27,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/diff"
v1listers "k8s.io/client-go/listers/core/v1"
corev1 "k8s.io/client-go/pkg/api/v1"
"k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/cache"
"k8s.io/kube-aggregator/pkg/apis/apiregistration" "k8s.io/kube-aggregator/pkg/apis/apiregistration"
...@@ -65,6 +63,11 @@ func TestAPIs(t *testing.T) { ...@@ -65,6 +63,11 @@ func TestAPIs(t *testing.T) {
Version: "v1", Version: "v1",
Priority: 10, Priority: 10,
}, },
Status: apiregistration.APIServiceStatus{
Conditions: []apiregistration.APIServiceCondition{
{Type: apiregistration.Available, Status: apiregistration.ConditionTrue},
},
},
}, },
{ {
ObjectMeta: metav1.ObjectMeta{Name: "v1.bar"}, ObjectMeta: metav1.ObjectMeta{Name: "v1.bar"},
...@@ -77,6 +80,11 @@ func TestAPIs(t *testing.T) { ...@@ -77,6 +80,11 @@ func TestAPIs(t *testing.T) {
Version: "v1", Version: "v1",
Priority: 11, Priority: 11,
}, },
Status: apiregistration.APIServiceStatus{
Conditions: []apiregistration.APIServiceCondition{
{Type: apiregistration.Available, Status: apiregistration.ConditionTrue},
},
},
}, },
}, },
expected: &metav1.APIGroupList{ expected: &metav1.APIGroupList{
...@@ -126,6 +134,11 @@ func TestAPIs(t *testing.T) { ...@@ -126,6 +134,11 @@ func TestAPIs(t *testing.T) {
Version: "v1", Version: "v1",
Priority: 20, Priority: 20,
}, },
Status: apiregistration.APIServiceStatus{
Conditions: []apiregistration.APIServiceCondition{
{Type: apiregistration.Available, Status: apiregistration.ConditionTrue},
},
},
}, },
{ {
ObjectMeta: metav1.ObjectMeta{Name: "v2.bar"}, ObjectMeta: metav1.ObjectMeta{Name: "v2.bar"},
...@@ -138,6 +151,11 @@ func TestAPIs(t *testing.T) { ...@@ -138,6 +151,11 @@ func TestAPIs(t *testing.T) {
Version: "v2", Version: "v2",
Priority: 11, Priority: 11,
}, },
Status: apiregistration.APIServiceStatus{
Conditions: []apiregistration.APIServiceCondition{
{Type: apiregistration.Available, Status: apiregistration.ConditionTrue},
},
},
}, },
{ {
ObjectMeta: metav1.ObjectMeta{Name: "v2.foo"}, ObjectMeta: metav1.ObjectMeta{Name: "v2.foo"},
...@@ -150,6 +168,11 @@ func TestAPIs(t *testing.T) { ...@@ -150,6 +168,11 @@ func TestAPIs(t *testing.T) {
Version: "v2", Version: "v2",
Priority: 1, Priority: 1,
}, },
Status: apiregistration.APIServiceStatus{
Conditions: []apiregistration.APIServiceCondition{
{Type: apiregistration.Available, Status: apiregistration.ConditionTrue},
},
},
}, },
{ {
ObjectMeta: metav1.ObjectMeta{Name: "v1.bar"}, ObjectMeta: metav1.ObjectMeta{Name: "v1.bar"},
...@@ -162,6 +185,11 @@ func TestAPIs(t *testing.T) { ...@@ -162,6 +185,11 @@ func TestAPIs(t *testing.T) {
Version: "v1", Version: "v1",
Priority: 11, Priority: 11,
}, },
Status: apiregistration.APIServiceStatus{
Conditions: []apiregistration.APIServiceCondition{
{Type: apiregistration.Available, Status: apiregistration.ConditionTrue},
},
},
}, },
}, },
expected: &metav1.APIGroupList{ expected: &metav1.APIGroupList{
...@@ -209,25 +237,13 @@ func TestAPIs(t *testing.T) { ...@@ -209,25 +237,13 @@ func TestAPIs(t *testing.T) {
for _, tc := range tests { for _, tc := range tests {
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
serviceIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
endpointsIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
handler := &apisHandler{ handler := &apisHandler{
codecs: Codecs, codecs: Codecs,
serviceLister: v1listers.NewServiceLister(serviceIndexer), lister: listers.NewAPIServiceLister(indexer),
endpointsLister: v1listers.NewEndpointsLister(endpointsIndexer),
lister: listers.NewAPIServiceLister(indexer),
} }
for _, o := range tc.apiservices { for _, o := range tc.apiservices {
indexer.Add(o) indexer.Add(o)
} }
serviceIndexer.Add(&corev1.Service{ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "api"}})
endpointsIndexer.Add(&corev1.Endpoints{
ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "api"},
Subsets: []corev1.EndpointSubset{
{Addresses: []corev1.EndpointAddress{{}}},
},
},
)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
...@@ -319,6 +335,11 @@ func TestAPIGroup(t *testing.T) { ...@@ -319,6 +335,11 @@ func TestAPIGroup(t *testing.T) {
Version: "v1", Version: "v1",
Priority: 20, Priority: 20,
}, },
Status: apiregistration.APIServiceStatus{
Conditions: []apiregistration.APIServiceCondition{
{Type: apiregistration.Available, Status: apiregistration.ConditionTrue},
},
},
}, },
{ {
ObjectMeta: metav1.ObjectMeta{Name: "v2.bar"}, ObjectMeta: metav1.ObjectMeta{Name: "v2.bar"},
...@@ -331,6 +352,11 @@ func TestAPIGroup(t *testing.T) { ...@@ -331,6 +352,11 @@ func TestAPIGroup(t *testing.T) {
Version: "v2", Version: "v2",
Priority: 11, Priority: 11,
}, },
Status: apiregistration.APIServiceStatus{
Conditions: []apiregistration.APIServiceCondition{
{Type: apiregistration.Available, Status: apiregistration.ConditionTrue},
},
},
}, },
{ {
ObjectMeta: metav1.ObjectMeta{Name: "v2.foo"}, ObjectMeta: metav1.ObjectMeta{Name: "v2.foo"},
...@@ -343,6 +369,11 @@ func TestAPIGroup(t *testing.T) { ...@@ -343,6 +369,11 @@ func TestAPIGroup(t *testing.T) {
Version: "v2", Version: "v2",
Priority: 1, Priority: 1,
}, },
Status: apiregistration.APIServiceStatus{
Conditions: []apiregistration.APIServiceCondition{
{Type: apiregistration.Available, Status: apiregistration.ConditionTrue},
},
},
}, },
{ {
ObjectMeta: metav1.ObjectMeta{Name: "v1.bar"}, ObjectMeta: metav1.ObjectMeta{Name: "v1.bar"},
...@@ -355,6 +386,11 @@ func TestAPIGroup(t *testing.T) { ...@@ -355,6 +386,11 @@ func TestAPIGroup(t *testing.T) {
Version: "v1", Version: "v1",
Priority: 11, Priority: 11,
}, },
Status: apiregistration.APIServiceStatus{
Conditions: []apiregistration.APIServiceCondition{
{Type: apiregistration.Available, Status: apiregistration.ConditionTrue},
},
},
}, },
}, },
expected: &metav1.APIGroup{ expected: &metav1.APIGroup{
...@@ -380,26 +416,14 @@ func TestAPIGroup(t *testing.T) { ...@@ -380,26 +416,14 @@ func TestAPIGroup(t *testing.T) {
for _, tc := range tests { for _, tc := range tests {
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
serviceIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
endpointsIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
handler := &apiGroupHandler{ handler := &apiGroupHandler{
codecs: Codecs, codecs: Codecs,
lister: listers.NewAPIServiceLister(indexer), lister: listers.NewAPIServiceLister(indexer),
serviceLister: v1listers.NewServiceLister(serviceIndexer), groupName: "foo",
endpointsLister: v1listers.NewEndpointsLister(endpointsIndexer),
groupName: "foo",
} }
for _, o := range tc.apiservices { for _, o := range tc.apiservices {
indexer.Add(o) indexer.Add(o)
} }
serviceIndexer.Add(&corev1.Service{ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "api"}})
endpointsIndexer.Add(&corev1.Endpoints{
ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "api"},
Subsets: []corev1.EndpointSubset{
{Addresses: []corev1.EndpointAddress{{}}},
},
},
)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
......
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["available_controller.go"],
tags = ["automanaged"],
deps = [
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/client-go/informers/core/v1:go_default_library",
"//vendor/k8s.io/client-go/listers/core/v1:go_default_library",
"//vendor/k8s.io/client-go/pkg/api/v1:go_default_library",
"//vendor/k8s.io/client-go/tools/cache:go_default_library",
"//vendor/k8s.io/client-go/util/workqueue:go_default_library",
"//vendor/k8s.io/kube-aggregator/pkg/apis/apiregistration:go_default_library",
"//vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/internalclientset/typed/apiregistration/internalversion:go_default_library",
"//vendor/k8s.io/kube-aggregator/pkg/client/informers/internalversion/apiregistration/internalversion:go_default_library",
"//vendor/k8s.io/kube-aggregator/pkg/client/listers/apiregistration/internalversion:go_default_library",
"//vendor/k8s.io/kube-aggregator/pkg/controllers:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["available_controller_test.go"],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/client-go/listers/core/v1:go_default_library",
"//vendor/k8s.io/client-go/pkg/api/v1:go_default_library",
"//vendor/k8s.io/client-go/testing:go_default_library",
"//vendor/k8s.io/client-go/tools/cache:go_default_library",
"//vendor/k8s.io/kube-aggregator/pkg/apis/apiregistration:go_default_library",
"//vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/internalclientset/fake:go_default_library",
"//vendor/k8s.io/kube-aggregator/pkg/client/listers/apiregistration/internalversion:go_default_library",
],
)
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apiserver
import (
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1listers "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/pkg/api/v1"
clienttesting "k8s.io/client-go/testing"
"k8s.io/client-go/tools/cache"
"k8s.io/kube-aggregator/pkg/apis/apiregistration"
"k8s.io/kube-aggregator/pkg/client/clientset_generated/internalclientset/fake"
listers "k8s.io/kube-aggregator/pkg/client/listers/apiregistration/internalversion"
)
func newEndpoints(namespace, name string) *v1.Endpoints {
return &v1.Endpoints{
ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name},
}
}
func newEndpointsWithAddress(namespace, name string) *v1.Endpoints {
return &v1.Endpoints{
ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name},
Subsets: []v1.EndpointSubset{
{
Addresses: []v1.EndpointAddress{
{
IP: "val",
},
},
},
},
}
}
func newService(namespace, name string) *v1.Service {
return &v1.Service{
ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name},
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeClusterIP,
},
}
}
func newLocalAPIService(name string) *apiregistration.APIService {
return &apiregistration.APIService{
ObjectMeta: metav1.ObjectMeta{Name: name},
}
}
func newRemoteAPIService(name string) *apiregistration.APIService {
return &apiregistration.APIService{
ObjectMeta: metav1.ObjectMeta{Name: name},
Spec: apiregistration.APIServiceSpec{
Service: &apiregistration.ServiceReference{
Namespace: "foo",
Name: "bar",
},
},
}
}
func TestSync(t *testing.T) {
tests := []struct {
name string
apiServiceName string
apiServices []*apiregistration.APIService
services []*v1.Service
endpoints []*v1.Endpoints
expectedAvailability apiregistration.APIServiceCondition
}{
{
name: "local",
apiServiceName: "local.group",
apiServices: []*apiregistration.APIService{newLocalAPIService("local.group")},
expectedAvailability: apiregistration.APIServiceCondition{
Type: apiregistration.Available,
Status: apiregistration.ConditionTrue,
Reason: "Local",
Message: "Local APIServices are always available",
},
},
{
name: "no service",
apiServiceName: "remote.group",
apiServices: []*apiregistration.APIService{newRemoteAPIService("remote.group")},
services: []*v1.Service{newService("foo", "not-bar")},
expectedAvailability: apiregistration.APIServiceCondition{
Type: apiregistration.Available,
Status: apiregistration.ConditionFalse,
Reason: "ServiceNotFound",
Message: `service/bar in "foo" is not present`,
},
},
{
name: "no endpoints",
apiServiceName: "remote.group",
apiServices: []*apiregistration.APIService{newRemoteAPIService("remote.group")},
services: []*v1.Service{newService("foo", "bar")},
expectedAvailability: apiregistration.APIServiceCondition{
Type: apiregistration.Available,
Status: apiregistration.ConditionFalse,
Reason: "EndpointsNotFound",
Message: `cannot find endpoints for service/bar in "foo"`,
},
},
{
name: "missing endpoints",
apiServiceName: "remote.group",
apiServices: []*apiregistration.APIService{newRemoteAPIService("remote.group")},
services: []*v1.Service{newService("foo", "bar")},
endpoints: []*v1.Endpoints{newEndpoints("foo", "bar")},
expectedAvailability: apiregistration.APIServiceCondition{
Type: apiregistration.Available,
Status: apiregistration.ConditionFalse,
Reason: "MissingEndpoints",
Message: `endpoints for service/bar in "foo" have no addresses`,
},
},
{
name: "remote",
apiServiceName: "remote.group",
apiServices: []*apiregistration.APIService{newRemoteAPIService("remote.group")},
services: []*v1.Service{newService("foo", "bar")},
endpoints: []*v1.Endpoints{newEndpointsWithAddress("foo", "bar")},
expectedAvailability: apiregistration.APIServiceCondition{
Type: apiregistration.Available,
Status: apiregistration.ConditionTrue,
Reason: "Passed",
Message: `all checks passed`,
},
},
}
for _, tc := range tests {
fakeClient := fake.NewSimpleClientset()
apiServiceIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
serviceIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
endpointsIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
for _, obj := range tc.apiServices {
apiServiceIndexer.Add(obj)
}
for _, obj := range tc.services {
serviceIndexer.Add(obj)
}
for _, obj := range tc.endpoints {
endpointsIndexer.Add(obj)
}
c := AvailableConditionController{
apiServiceClient: fakeClient.Apiregistration(),
apiServiceLister: listers.NewAPIServiceLister(apiServiceIndexer),
serviceLister: v1listers.NewServiceLister(serviceIndexer),
endpointsLister: v1listers.NewEndpointsLister(endpointsIndexer),
}
c.sync(tc.apiServiceName)
// ought to have one action writing status
if e, a := 1, len(fakeClient.Actions()); e != a {
t.Errorf("%v expected %v, got %v", tc.name, e, fakeClient.Actions())
continue
}
action, ok := fakeClient.Actions()[0].(clienttesting.UpdateAction)
if !ok {
t.Errorf("%v got %v", tc.name, ok)
continue
}
if e, a := 1, len(action.GetObject().(*apiregistration.APIService).Status.Conditions); e != a {
t.Errorf("%v expected %v, got %v", tc.name, e, action.GetObject())
continue
}
condition := action.GetObject().(*apiregistration.APIService).Status.Conditions[0]
if e, a := tc.expectedAvailability.Type, condition.Type; e != a {
t.Errorf("%v expected %v, got %#v", tc.name, e, condition)
}
if e, a := tc.expectedAvailability.Status, condition.Status; e != a {
t.Errorf("%v expected %v, got %#v", tc.name, e, condition)
}
if e, a := tc.expectedAvailability.Reason, condition.Reason; e != a {
t.Errorf("%v expected %v, got %#v", tc.name, e, condition)
}
if e, a := tc.expectedAvailability.Message, condition.Message; e != a {
t.Errorf("%v expected %v, got %#v", tc.name, e, condition)
}
}
}
...@@ -13,8 +13,10 @@ go_library( ...@@ -13,8 +13,10 @@ go_library(
tags = ["automanaged"], tags = ["automanaged"],
deps = [ deps = [
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/generic/registry:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic/registry:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
"//vendor/k8s.io/kube-aggregator/pkg/apis/apiregistration:go_default_library", "//vendor/k8s.io/kube-aggregator/pkg/apis/apiregistration:go_default_library",
"//vendor/k8s.io/kube-aggregator/pkg/registry/apiservice:go_default_library", "//vendor/k8s.io/kube-aggregator/pkg/registry/apiservice:go_default_library",
], ],
......
...@@ -18,8 +18,10 @@ package etcd ...@@ -18,8 +18,10 @@ package etcd
import ( import (
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/generic" "k8s.io/apiserver/pkg/registry/generic"
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/kube-aggregator/pkg/apis/apiregistration" "k8s.io/kube-aggregator/pkg/apis/apiregistration"
"k8s.io/kube-aggregator/pkg/registry/apiservice" "k8s.io/kube-aggregator/pkg/registry/apiservice"
) )
...@@ -49,3 +51,28 @@ func NewREST(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) *REST ...@@ -49,3 +51,28 @@ func NewREST(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) *REST
} }
return &REST{store} return &REST{store}
} }
// NewStatusREST makes a RESTStorage for status that has more limited options.
// It is based on the original REST so that we can share the same underlying store
func NewStatusREST(scheme *runtime.Scheme, rest *REST) *StatusREST {
statusStore := *rest.Store
statusStore.CreateStrategy = nil
statusStore.DeleteStrategy = nil
statusStore.UpdateStrategy = apiservice.NewStatusStrategy(scheme)
return &StatusREST{store: &statusStore}
}
type StatusREST struct {
store *genericregistry.Store
}
var _ = rest.Updater(&StatusREST{})
func (r *StatusREST) New() runtime.Object {
return &apiregistration.APIService{}
}
// Update alters the status subset of an object.
func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) {
return r.store.Update(ctx, name, objInfo)
}
...@@ -46,7 +46,8 @@ func (apiServerStrategy) NamespaceScoped() bool { ...@@ -46,7 +46,8 @@ func (apiServerStrategy) NamespaceScoped() bool {
} }
func (apiServerStrategy) PrepareForCreate(ctx genericapirequest.Context, obj runtime.Object) { func (apiServerStrategy) PrepareForCreate(ctx genericapirequest.Context, obj runtime.Object) {
_ = obj.(*apiregistration.APIService) apiservice := obj.(*apiregistration.APIService)
apiservice.Status = apiregistration.APIServiceStatus{}
} }
func (apiServerStrategy) PrepareForUpdate(ctx genericapirequest.Context, obj, old runtime.Object) { func (apiServerStrategy) PrepareForUpdate(ctx genericapirequest.Context, obj, old runtime.Object) {
...@@ -74,6 +75,44 @@ func (apiServerStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old ...@@ -74,6 +75,44 @@ func (apiServerStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old
return validation.ValidateAPIServiceUpdate(obj.(*apiregistration.APIService), old.(*apiregistration.APIService)) return validation.ValidateAPIServiceUpdate(obj.(*apiregistration.APIService), old.(*apiregistration.APIService))
} }
type apiServerStatusStrategy struct {
runtime.ObjectTyper
names.NameGenerator
}
func NewStatusStrategy(typer runtime.ObjectTyper) apiServerStatusStrategy {
return apiServerStatusStrategy{typer, names.SimpleNameGenerator}
}
func (apiServerStatusStrategy) NamespaceScoped() bool {
return false
}
func (apiServerStatusStrategy) PrepareForUpdate(ctx genericapirequest.Context, obj, old runtime.Object) {
newAPIService := obj.(*apiregistration.APIService)
oldAPIService := old.(*apiregistration.APIService)
newAPIService.Spec = oldAPIService.Spec
newAPIService.Labels = oldAPIService.Labels
newAPIService.Annotations = oldAPIService.Annotations
newAPIService.Finalizers = oldAPIService.Finalizers
newAPIService.OwnerReferences = oldAPIService.OwnerReferences
}
func (apiServerStatusStrategy) AllowCreateOnUpdate() bool {
return false
}
func (apiServerStatusStrategy) AllowUnconditionalUpdate() bool {
return false
}
func (apiServerStatusStrategy) Canonicalize(obj runtime.Object) {
}
func (apiServerStatusStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList {
return validation.ValidateAPIServiceStatusUpdate(obj.(*apiregistration.APIService), old.(*apiregistration.APIService))
}
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
apiserver, ok := obj.(*apiregistration.APIService) apiserver, ok := obj.(*apiregistration.APIService)
if !ok { if !ok {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment