Skip to content

Commit

Permalink
internal/k8s: add check to verify resources exist in cluster
Browse files Browse the repository at this point in the history
Add a discovery client to look up server groups and resources which
allows for a check to be made that a resource type exists before
starting any informers against.

Fixes projectcontour#2219

Signed-off-by: Steve Sloka <[email protected]>
  • Loading branch information
stevesloka committed Jul 9, 2020
1 parent eb45c16 commit a4ca720
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 11 deletions.
31 changes: 23 additions & 8 deletions cmd/contour/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,24 +236,38 @@ func doServe(log logrus.FieldLogger, ctx *serveContext) error {

// step 4. register our resource event handler with the k8s informers,
// using the SyncList to keep track of what to sync later.
var informerSyncList k8s.InformerSyncList
informerSyncList := k8s.InformerSyncList{
Clients: clients,
}

apiResources, err := clients.ServerResources()
if err != nil {
log.WithField("context", "ServerGroupsAndResources").Errorf("error getting list of API Resources: %v", err)
return err
}

informerSyncList.InformOnResources(clusterInformerFactory, dynamicHandler, k8s.DefaultResources()...)
informerSyncList.InformOnResources(clusterInformerFactory, dynamicHandler, apiResources, k8s.DefaultResources()...)

if ctx.UseExperimentalServiceAPITypes {
informerSyncList.InformOnResources(clusterInformerFactory,
dynamicHandler, k8s.ServiceAPIResources()...)
for _, s := range k8s.ServiceAPIResources() {
// Check if the resource exists in the API server before setting up the informer.
if !clients.ResourceExists(s, apiResources) {
log.WithField("InformOnResources", "ExperimentalServiceAPITypes").Warnf("resource %v not found in api server", s.String())
continue
}
informerSyncList.InformOnResources(clusterInformerFactory, dynamicHandler, apiResources, s)
}
}

// TODO(youngnick): Move this logic out to internal/k8s/informers.go somehow.
// Add informers for each root namespace
for _, factory := range namespacedInformerFactories {
informerSyncList.InformOnResources(factory, dynamicHandler, k8s.SecretsResources()...)
informerSyncList.InformOnResources(factory, dynamicHandler, apiResources, k8s.SecretsResources()...)
}

// If root namespaces are not defined, then add the informer for all namespaces
if len(namespacedInformerFactories) == 0 {
informerSyncList.InformOnResources(clusterInformerFactory, dynamicHandler, k8s.SecretsResources()...)
informerSyncList.InformOnResources(clusterInformerFactory, dynamicHandler, apiResources, k8s.SecretsResources()...)
}

// step 5. endpoints updates are handled directly by the EndpointsTranslator
Expand All @@ -270,7 +284,7 @@ func doServe(log logrus.FieldLogger, ctx *serveContext) error {
},
Converter: converter,
Logger: log.WithField("context", "endpointstranslator"),
}, k8s.EndpointsResources()...)
}, apiResources, k8s.EndpointsResources()...)

// step 6. setup workgroup runner and register informers.
var g workgroup.Group
Expand Down Expand Up @@ -368,7 +382,8 @@ func doServe(log logrus.FieldLogger, ctx *serveContext) error {
Logger: log.WithField("context", "serviceStatusLoadBalancerWatcher"),
}
factory := clients.NewInformerFactoryForNamespace(ctx.EnvoyServiceNamespace)
informerSyncList.InformOnResources(factory, dynamicServiceHandler, k8s.ServicesResources()...)
informerSyncList.InformOnResources(factory, dynamicServiceHandler, apiResources, k8s.ServicesResources()...)

g.Add(startInformer(factory, log.WithField("context", "serviceStatusLoadBalancerWatcher")))
log.WithField("envoy-service-name", ctx.EnvoyServiceName).
WithField("envoy-service-namespace", ctx.EnvoyServiceNamespace).
Expand Down
34 changes: 32 additions & 2 deletions internal/k8s/clients.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ package k8s
import (
"time"

"k8s.io/apimachinery/pkg/runtime/schema"

"k8s.io/client-go/discovery"

"k8s.io/client-go/dynamic"
"k8s.io/client-go/dynamic/dynamicinformer"
"k8s.io/client-go/kubernetes"
Expand All @@ -25,8 +29,9 @@ import (

// Clients holds the various API clients required by Contour.
type Clients struct {
core *kubernetes.Clientset
dynamic dynamic.Interface
core *kubernetes.Clientset
dynamic dynamic.Interface
discovery *discovery.DiscoveryClient
}

// NewClients returns a new set of the various API clients required
Expand All @@ -49,6 +54,11 @@ func NewClients(kubeconfig string, inCluster bool) (*Clients, error) {
return nil, err
}

clients.discovery, err = discovery.NewDiscoveryClientForConfig(config)
if err != nil {
return nil, err
}

return &clients, nil
}

Expand Down Expand Up @@ -84,3 +94,23 @@ func (c *Clients) ClientSet() *kubernetes.Clientset {
func (c *Clients) DynamicClient() dynamic.Interface {
return c.dynamic
}

// ServerResources returns the list of all the resources supported
// by the API server. Note that this method guarantees to populate the
// Group and Version fields in the result.
func (c *Clients) ServerResources() (map[schema.GroupVersionResource]struct{}, error) {
_, apiList, err := c.discovery.ServerGroupsAndResources()
if err != nil {
return nil, err
}
return discovery.GroupVersionResources(apiList)
}

// ResourceExists returns true if an GroupVersionResource exists in the cluster.
func (c *Clients) ResourceExists(gvr schema.GroupVersionResource, apiResources map[schema.GroupVersionResource]struct{}) bool {

if _, ok := apiResources[gvr]; ok {
return true
}
return false
}
71 changes: 71 additions & 0 deletions internal/k8s/clients_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright Project Contour 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 k8s

import (
"testing"

"k8s.io/apimachinery/pkg/runtime/schema"

"github.com/projectcontour/contour/internal/assert"
)

func TestResourceKindExists(t *testing.T) {
type testcase struct {
gvr schema.GroupVersionResource
apiResourceList map[schema.GroupVersionResource]struct{}
want bool
}

run := func(t *testing.T, name string, tc testcase) {
t.Helper()

t.Run(name, func(t *testing.T) {
t.Helper()

clients := &Clients{}
got := clients.ResourceExists(tc.gvr, tc.apiResourceList)
assert.Equal(t, tc.want, got)
})
}

valid := schema.GroupVersionResource{
Group: "networking.k8s.io",
Version: "v1beta1",
Resource: "ingress",
}

invalid := schema.GroupVersionResource{
Group: "networking.k8s.io",
Version: "v1beta1",
Resource: "ingressclass",
}

run(t, "ingress exist", testcase{
gvr: valid,
want: true,
apiResourceList: map[schema.GroupVersionResource]struct{}{
valid: struct{}{},
},
})

run(t, "ingressclass does not exist", testcase{
gvr: invalid,
want: false,
apiResourceList: map[schema.GroupVersionResource]struct{}{
valid: struct{}{},
},
})

}
3 changes: 2 additions & 1 deletion internal/k8s/syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ import (
// InformerSyncList holds the functions to call to check that an informer is synced.
type InformerSyncList struct {
syncers []cache.InformerSynced
Clients *Clients
}

// InformOnResources creates informers for each of the given resources and registers their sync callbacks.
func (sl *InformerSyncList) InformOnResources(f InformerFactory, handler *DynamicClientHandler, resources ...schema.GroupVersionResource) {
func (sl *InformerSyncList) InformOnResources(f InformerFactory, handler *DynamicClientHandler, apiResource map[schema.GroupVersionResource]struct{}, resources ...schema.GroupVersionResource) {

for _, r := range resources {
informer := f.ForResource(r).Informer()
Expand Down

0 comments on commit a4ca720

Please sign in to comment.