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 { for _, exp := range expectations {
k8sAPI, err := k8s.NewFakeAPI(exp.k8sRes...) k8sAPI, err := k8s.NewFakeAPI("", exp.k8sRes...)
if err != nil { if err != nil {
t.Fatalf("NewFakeAPI returned an error: %s", err) 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) { func testStatSummary(t *testing.T, expectations []statSumExpected) {
for _, exp := range expectations { for _, exp := range expectations {
k8sAPI, err := k8s.NewFakeAPI(exp.k8sConfigs...) k8sAPI, err := k8s.NewFakeAPI("", exp.k8sConfigs...)
if err != nil { if err != nil {
t.Fatalf("NewFakeAPI returned an error: %s", err) 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) { t.Run("Given an invalid resource type, returns error", func(t *testing.T) {
k8sAPI, err := k8s.NewFakeAPI() k8sAPI, err := k8s.NewFakeAPI("")
if err != nil { if err != nil {
t.Fatalf("NewFakeAPI returned an error: %s", err) t.Fatalf("NewFakeAPI returned an error: %s", err)
} }
@ -787,7 +787,7 @@ status:
}) })
t.Run("Validates service stat requests", func(t *testing.T) { t.Run("Validates service stat requests", func(t *testing.T) {
k8sAPI, err := k8s.NewFakeAPI() k8sAPI, err := k8s.NewFakeAPI("")
if err != nil { if err != nil {
t.Fatalf("NewFakeAPI returned an error: %s", err) 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) { func new(fixtures ...string) (*CertificateController, chan bool, chan struct{}, error) {
k8sAPI, err := k8s.NewFakeAPI(fixtures...) k8sAPI, err := k8s.NewFakeAPI("", fixtures...)
if err != nil { if err != nil {
return nil, nil, nil, fmt.Errorf("NewFakeAPI returned an error: %s", err) return nil, nil, nil, fmt.Errorf("NewFakeAPI returned an error: %s", err)
} }

View File

@ -36,9 +36,9 @@ func main() {
var k8sAPI *k8s.API var k8sAPI *k8s.API
if *proxyAutoInject { 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 { } 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) controller, err := ca.NewCertificateController(*controllerNamespace, k8sAPI, *proxyAutoInject)

View File

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

View File

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

View File

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

View File

@ -260,7 +260,7 @@ spec:
}, },
} { } {
t.Run("subscribes listener to "+tt.serviceType, func(t *testing.T) { 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 { if err != nil {
t.Fatalf("NewFakeAPI returned an error: %s", err) 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 (m *mockDestination_GetServer) RecvMsg(x interface{}) error { return m.errorToReturn }
func TestBuildResolversList(t *testing.T) { func TestBuildResolversList(t *testing.T) {
k8sAPI, err := k8s.NewFakeAPI() k8sAPI, err := k8s.NewFakeAPI("")
if err != nil { if err != nil {
t.Fatalf("NewFakeAPI returned an error: %s", err) t.Fatalf("NewFakeAPI returned an error: %s", err)
} }
@ -89,7 +89,7 @@ func TestStreamResolutionUsingCorrectResolverFor(t *testing.T) {
stream := &mockDestination_GetServer{} stream := &mockDestination_GetServer{}
host := "something" host := "something"
port := 666 port := 666
k8sAPI, err := k8s.NewFakeAPI() k8sAPI, err := k8s.NewFakeAPI("")
if err != nil { if err != nil {
t.Fatalf("NewFakeAPI returned an error: %s", err) t.Fatalf("NewFakeAPI returned an error: %s", err)
} }

View File

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

View File

@ -10,9 +10,7 @@ import (
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
apiv1 "k8s.io/api/core/v1" apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/fake"
) )
func newAPI(resourceConfigs []string, extraConfigs ...string) (*API, []runtime.Object, error) { 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) k8sConfigs = append(k8sConfigs, config)
} }
api, err := NewFakeAPI(k8sConfigs...) api, err := NewFakeAPI("", k8sConfigs...)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("NewFakeAPI returned an error: %s", err) 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("In single-namespace mode", func(t *testing.T) {
t.Run("Returns only the configured namespace", 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) ns1 := `
api := NewAPI(clientSet, "namespace1") 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, "") namespaces, err := api.GetObjects("", k8s.Namespace, "")
if err != nil { if err != nil {

View File

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

View File

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

View File

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

View File

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