Add ServiceProfile support to k8s utilities (#1758)

Updates to the Kubernetes utility code in `/controller/k8s` to support interacting with ServiceProfiles.

This makes use of the code generated client added in #1752 

Signed-off-by: Alex Leong <alex@buoyant.io>
This commit is contained in:
Alex Leong 2018-10-12 09:35:11 -07:00 committed by GitHub
parent 2d6fde274c
commit 1fe19bf3ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 116 additions and 46 deletions

View File

@ -148,7 +148,7 @@ spec:
}
for _, exp := range expectations {
k8sAPI, err := k8s.NewFakeAPI(exp.k8sRes...)
k8sAPI, err := k8s.NewFakeAPI("", exp.k8sRes...)
if err != nil {
t.Fatalf("NewFakeAPI returned an error: %s", err)
}

View File

@ -69,7 +69,7 @@ func genEmptyResponse() pb.StatSummaryResponse {
func testStatSummary(t *testing.T, expectations []statSumExpected) {
for _, exp := range expectations {
k8sAPI, err := k8s.NewFakeAPI(exp.k8sConfigs...)
k8sAPI, err := k8s.NewFakeAPI("", exp.k8sConfigs...)
if err != nil {
t.Fatalf("NewFakeAPI returned an error: %s", err)
}
@ -728,7 +728,7 @@ status:
})
t.Run("Given an invalid resource type, returns error", func(t *testing.T) {
k8sAPI, err := k8s.NewFakeAPI()
k8sAPI, err := k8s.NewFakeAPI("")
if err != nil {
t.Fatalf("NewFakeAPI returned an error: %s", err)
}
@ -787,7 +787,7 @@ status:
})
t.Run("Validates service stat requests", func(t *testing.T) {
k8sAPI, err := k8s.NewFakeAPI()
k8sAPI, err := k8s.NewFakeAPI("")
if err != nil {
t.Fatalf("NewFakeAPI returned an error: %s", err)
}

View File

@ -62,7 +62,7 @@ func TestCertificateController(t *testing.T) {
}
func new(fixtures ...string) (*CertificateController, chan bool, chan struct{}, error) {
k8sAPI, err := k8s.NewFakeAPI(fixtures...)
k8sAPI, err := k8s.NewFakeAPI("", fixtures...)
if err != nil {
return nil, nil, nil, fmt.Errorf("NewFakeAPI returned an error: %s", err)
}

View File

@ -36,9 +36,9 @@ func main() {
var k8sAPI *k8s.API
if *proxyAutoInject {
k8sAPI = k8s.NewAPI(k8sClient, restrictToNamespace, k8s.Pod, k8s.RS, k8s.MWC)
k8sAPI = k8s.NewAPI(k8sClient, nil, restrictToNamespace, k8s.Pod, k8s.RS, k8s.MWC)
} else {
k8sAPI = k8s.NewAPI(k8sClient, restrictToNamespace, k8s.Pod, k8s.RS)
k8sAPI = k8s.NewAPI(k8sClient, nil, restrictToNamespace, k8s.Pod, k8s.RS)
}
controller, err := ca.NewCertificateController(*controllerNamespace, k8sAPI, *proxyAutoInject)

View File

@ -30,17 +30,24 @@ func main() {
if err != nil {
log.Fatal(err.Error())
}
spClient, err := k8s.NewSpClientSet(*kubeConfigPath)
if err != nil {
log.Fatal(err.Error())
}
restrictToNamespace := ""
if *singleNamespace {
restrictToNamespace = *controllerNamespace
}
k8sAPI := k8s.NewAPI(
k8sClient,
spClient,
restrictToNamespace,
k8s.Endpoint,
k8s.Pod,
k8s.RS,
k8s.Svc,
k8s.SP,
)
done := make(chan struct{})

View File

@ -47,6 +47,7 @@ func main() {
}
k8sAPI := k8s.NewAPI(
k8sClient,
nil,
restrictToNamespace,
k8s.Deploy,
k8s.Pod,

View File

@ -25,7 +25,7 @@ func main() {
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
clientSet, err := k8s.NewClientSet(*kubeConfigPath)
k8sClient, err := k8s.NewClientSet(*kubeConfigPath)
if err != nil {
log.Fatalf("failed to create Kubernetes client: %s", err)
}
@ -34,7 +34,8 @@ func main() {
restrictToNamespace = *controllerNamespace
}
k8sAPI := k8s.NewAPI(
clientSet,
k8sClient,
nil,
restrictToNamespace,
k8s.Deploy,
k8s.Pod,

View File

@ -260,7 +260,7 @@ spec:
},
} {
t.Run("subscribes listener to "+tt.serviceType, func(t *testing.T) {
k8sAPI, err := k8s.NewFakeAPI(tt.k8sConfigs...)
k8sAPI, err := k8s.NewFakeAPI("", tt.k8sConfigs...)
if err != nil {
t.Fatalf("NewFakeAPI returned an error: %s", err)
}

View File

@ -29,7 +29,7 @@ func (m *mockDestination_GetServer) SendMsg(x interface{}) error { return m.err
func (m *mockDestination_GetServer) RecvMsg(x interface{}) error { return m.errorToReturn }
func TestBuildResolversList(t *testing.T) {
k8sAPI, err := k8s.NewFakeAPI()
k8sAPI, err := k8s.NewFakeAPI("")
if err != nil {
t.Fatalf("NewFakeAPI returned an error: %s", err)
}
@ -89,7 +89,7 @@ func TestStreamResolutionUsingCorrectResolverFor(t *testing.T) {
stream := &mockDestination_GetServer{}
host := "something"
port := 666
k8sAPI, err := k8s.NewFakeAPI()
k8sAPI, err := k8s.NewFakeAPI("")
if err != nil {
t.Fatalf("NewFakeAPI returned an error: %s", err)
}

View File

@ -6,6 +6,9 @@ import (
"strings"
"time"
spclient "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned"
sp "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions"
spinformers "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/serviceprofile/v1alpha1"
"github.com/linkerd/linkerd2/pkg/k8s"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc/codes"
@ -29,11 +32,12 @@ const (
CM ApiResource = iota
Deploy
Endpoint
MWC // mutating webhook configuration
Pod
RC
RS
SP
Svc
MWC // mutating webhook configuration
)
// API provides shared informers for all Kubernetes objects
@ -43,22 +47,26 @@ type API struct {
cm coreinformers.ConfigMapInformer
deploy appinformers.DeploymentInformer
endpoint coreinformers.EndpointsInformer
mwc arinformers.MutatingWebhookConfigurationInformer
pod coreinformers.PodInformer
rc coreinformers.ReplicationControllerInformer
rs appinformers.ReplicaSetInformer
sp spinformers.ServiceProfileInformer
svc coreinformers.ServiceInformer
mwc arinformers.MutatingWebhookConfigurationInformer
syncChecks []cache.InformerSynced
sharedInformers informers.SharedInformerFactory
namespace string
syncChecks []cache.InformerSynced
sharedInformers informers.SharedInformerFactory
spSharedInformers sp.SharedInformerFactory
namespace string
}
// NewAPI takes a Kubernetes client and returns an initialized API
func NewAPI(k8sClient kubernetes.Interface, namespace string, resources ...ApiResource) *API {
func NewAPI(k8sClient kubernetes.Interface, spClient spclient.Interface, namespace string, resources ...ApiResource) *API {
var sharedInformers informers.SharedInformerFactory
var spSharedInformers sp.SharedInformerFactory
if namespace == "" {
sharedInformers = informers.NewSharedInformerFactory(k8sClient, 10*time.Minute)
spSharedInformers = sp.NewSharedInformerFactory(spClient, 10*time.Minute)
} else {
sharedInformers = informers.NewFilteredSharedInformerFactory(
k8sClient,
@ -66,13 +74,20 @@ func NewAPI(k8sClient kubernetes.Interface, namespace string, resources ...ApiRe
namespace,
nil,
)
spSharedInformers = sp.NewFilteredSharedInformerFactory(
spClient,
10*time.Minute,
namespace,
nil,
)
}
api := &API{
Client: k8sClient,
syncChecks: make([]cache.InformerSynced, 0),
sharedInformers: sharedInformers,
namespace: namespace,
Client: k8sClient,
syncChecks: make([]cache.InformerSynced, 0),
sharedInformers: sharedInformers,
spSharedInformers: spSharedInformers,
namespace: namespace,
}
for _, resource := range resources {
@ -86,6 +101,9 @@ func NewAPI(k8sClient kubernetes.Interface, namespace string, resources ...ApiRe
case Endpoint:
api.endpoint = sharedInformers.Core().V1().Endpoints()
api.syncChecks = append(api.syncChecks, api.endpoint.Informer().HasSynced)
case MWC:
api.mwc = sharedInformers.Admissionregistration().V1beta1().MutatingWebhookConfigurations()
api.syncChecks = append(api.syncChecks, api.mwc.Informer().HasSynced)
case Pod:
api.pod = sharedInformers.Core().V1().Pods()
api.syncChecks = append(api.syncChecks, api.pod.Informer().HasSynced)
@ -95,12 +113,12 @@ func NewAPI(k8sClient kubernetes.Interface, namespace string, resources ...ApiRe
case RS:
api.rs = sharedInformers.Apps().V1beta2().ReplicaSets()
api.syncChecks = append(api.syncChecks, api.rs.Informer().HasSynced)
case SP:
api.sp = spSharedInformers.Linkerd().V1alpha1().ServiceProfiles()
api.syncChecks = append(api.syncChecks, api.sp.Informer().HasSynced)
case Svc:
api.svc = sharedInformers.Core().V1().Services()
api.syncChecks = append(api.syncChecks, api.svc.Informer().HasSynced)
case MWC:
api.mwc = sharedInformers.Admissionregistration().V1beta1().MutatingWebhookConfigurations()
api.syncChecks = append(api.syncChecks, api.mwc.Informer().HasSynced)
}
}
@ -112,6 +130,7 @@ func NewAPI(k8sClient kubernetes.Interface, namespace string, resources ...ApiRe
// For testing, call this synchronously.
func (api *API) Sync(readyCh chan<- struct{}) {
api.sharedInformers.Start(nil)
api.spSharedInformers.Start(nil)
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
@ -176,6 +195,13 @@ func (api *API) CM() coreinformers.ConfigMapInformer {
return api.cm
}
func (api *API) SP() spinformers.ServiceProfileInformer {
if api.sp == nil {
panic("SP informer not configured")
}
return api.sp
}
func (api *API) MWC() arinformers.MutatingWebhookConfigurationInformer {
if api.mwc == nil {
panic("MWC informer not configured")

View File

@ -10,9 +10,7 @@ import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/fake"
)
func newAPI(resourceConfigs []string, extraConfigs ...string) (*API, []runtime.Object, error) {
@ -32,7 +30,7 @@ func newAPI(resourceConfigs []string, extraConfigs ...string) (*API, []runtime.O
k8sConfigs = append(k8sConfigs, config)
}
api, err := NewFakeAPI(k8sConfigs...)
api, err := NewFakeAPI("", k8sConfigs...)
if err != nil {
return nil, nil, fmt.Errorf("NewFakeAPI returned an error: %s", err)
}
@ -172,19 +170,23 @@ metadata:
t.Run("In single-namespace mode", func(t *testing.T) {
t.Run("Returns only the configured namespace", func(t *testing.T) {
ns1 := &apiv1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "namespace1",
},
}
ns2 := &apiv1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "namespace2",
},
}
clientSet := fake.NewSimpleClientset(ns1, ns2)
api := NewAPI(clientSet, "namespace1")
ns1 := `
apiVersion: v1
kind: Namespace
metadata:
name: namespace1`
ns2 := `
apiVersion: v1
kind: Namespace
metadata:
name: namespace2`
api, err := NewFakeAPI("namespace1", ns1, ns2)
if err != nil {
t.Fatalf("NewFakeAPI returned an error: %s", err)
}
namespaces, err := api.GetObjects("", k8s.Namespace, "")
if err != nil {

View File

@ -1,6 +1,7 @@
package k8s
import (
spclient "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
@ -9,6 +10,24 @@ import (
)
func NewClientSet(kubeConfig string) (*kubernetes.Clientset, error) {
config, err := parseConfig(kubeConfig)
if err != nil {
return nil, err
}
return kubernetes.NewForConfig(config)
}
func NewSpClientSet(kubeConfig string) (*spclient.Clientset, error) {
config, err := parseConfig(kubeConfig)
if err != nil {
return nil, err
}
return spclient.NewForConfig(config)
}
func parseConfig(kubeConfig string) (*rest.Config, error) {
var config *rest.Config
var err error
@ -23,6 +42,5 @@ func NewClientSet(kubeConfig string) (*kubernetes.Clientset, error) {
if err != nil {
return nil, err
}
return kubernetes.NewForConfig(config)
return config, nil
}

View File

@ -1,31 +1,44 @@
package k8s
import (
"strings"
spfake "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/fake"
spscheme "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme"
"github.com/linkerd/linkerd2/pkg/k8s"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/kubernetes/scheme"
)
func toRuntimeObject(config string) (runtime.Object, error) {
spscheme.AddToScheme(scheme.Scheme)
decode := scheme.Codecs.UniversalDeserializer().Decode
obj, _, err := decode([]byte(config), nil, nil)
return obj, err
}
func NewFakeAPI(configs ...string) (*API, error) {
func NewFakeAPI(namespace string, configs ...string) (*API, error) {
objs := []runtime.Object{}
spObjs := []runtime.Object{}
for _, config := range configs {
obj, err := toRuntimeObject(config)
if err != nil {
return nil, err
}
objs = append(objs, obj)
if strings.ToLower(obj.GetObjectKind().GroupVersionKind().Kind) == k8s.ServiceProfile {
spObjs = append(spObjs, obj)
} else {
objs = append(objs, obj)
}
}
clientSet := fake.NewSimpleClientset(objs...)
spClientSet := spfake.NewSimpleClientset(spObjs...)
return NewAPI(
clientSet,
"",
spClientSet,
namespace,
CM,
Deploy,
Endpoint,
@ -33,6 +46,7 @@ func NewFakeAPI(configs ...string) (*API, error) {
RC,
RS,
Svc,
SP,
MWC,
), nil
}

View File

@ -184,7 +184,7 @@ status:
}
for _, exp := range expectations {
k8sAPI, err := k8s.NewFakeAPI(exp.k8sRes...)
k8sAPI, err := k8s.NewFakeAPI("", exp.k8sRes...)
if err != nil {
t.Fatalf("NewFakeAPI returned an error: %s", err)
}

View File

@ -18,6 +18,7 @@ const (
ReplicationController = "replicationcontroller"
ReplicaSet = "replicaset"
Service = "service"
ServiceProfile = "serviceprofile"
StatefulSet = "statefulset"
)