pkg/injection
Martin Gencur 366ab85660 Profiling support (#562)
* Profiling support

* Move ProfilingPort to profiling package

* Fix golint errors

* Refactor watcher to accept variable length observers

* Cleaner happy path

* Remove profiling handlers argument

* use :8008 string as const

* Make the profiling port package private

* Make UpdateFromConfigMap member of profiling handler

* use mutex when accessing the enabled flag
* test the server as well

* Fixes

* Initialize profiling from configMap at startup

* Use httptest.ResponseRecorder to make the test more lightweight

* Fixes

* Do not initialize from configmap at startup
2019-08-22 08:17:33 -07:00
..
clients Migrate pkg to use the knative.dev/pkg import path (#489) 2019-06-26 13:02:06 -07:00
informers added limitrange and resourcequota informers (#501) 2019-07-02 22:44:32 -07:00
sharedmain Profiling support (#562) 2019-08-22 08:17:33 -07:00
OWNERS Create a library for Reconciler dependency injection. (#423) 2019-06-05 07:37:38 -07:00
README.md Remove '-' from suggested component name. (#525) 2019-07-12 12:41:44 -07:00
clients.go Create a library for Reconciler dependency injection. (#423) 2019-06-05 07:37:38 -07:00
clients_test.go Create a library for Reconciler dependency injection. (#423) 2019-06-05 07:37:38 -07:00
doc.go Remove '-' from suggested component name. (#525) 2019-07-12 12:41:44 -07:00
factories.go Create a library for Reconciler dependency injection. (#423) 2019-06-05 07:37:38 -07:00
factories_test.go Create a library for Reconciler dependency injection. (#423) 2019-06-05 07:37:38 -07:00
informers.go Migrate pkg to use the knative.dev/pkg import path (#489) 2019-06-26 13:02:06 -07:00
informers_test.go Migrate pkg to use the knative.dev/pkg import path (#489) 2019-06-26 13:02:06 -07:00
interface.go Migrate pkg to use the knative.dev/pkg import path (#489) 2019-06-26 13:02:06 -07:00

README.md

Knative Dependency Injection

This library supports the production of controller processes with minimal boilerplate outside of the reconciler implementation.

Building Controllers

To adopt this model of controller construction, implementations should start with the following controller constructor:

import (
	"context"

	"knative.dev/pkg/configmap"
	"knative.dev/pkg/controller"
	"knative.dev/pkg/logging"
)

func NewController(ctx context.Context, cmw configmap.Watcher) *controller.Impl {
	logger := logging.FromContext(ctx)

	// TODO(you): Access informers

	c := &Reconciler{
		// TODO(you): Pass listers, clients, and other stuff.
	}
	impl := controller.NewImpl(c, logger, "NameOfController")

	// TODO(you): Set up event handlers.

	return impl
}

Consuming Informers

Knative controllers use "informers" to set up the various event hooks needed to queue work, and pass the "listers" fed by the informers' caches to the nested "Reconciler" for accessing objects.

Our controller constructor is passed a context.Context onto which we inject any informers we access. The accessors for these informers are in little stub libraries, which we have hand rolled for Kubernetes (more on how to generate these below).

import (
	// These are how you access a client or informer off of the "ctx" passed
	// to set up the controller.
	"knative.dev/pkg/injection/clients/kubeclient"
	svcinformer "knative.dev/pkg/injection/informers/kubeinformers/corev1/service"

	// Other imports ...
)

func NewController(ctx context.Context, cmw configmap.Watcher) *controller.Impl {
	logger := logging.FromContext(ctx)

	// Access informers
	svcInformer := svcinformer.Get(ctx)

	c := &Reconciler{
		// Pass the lister and client to the Reconciler.
		Client:        kubeclient.Get(ctx),
		ServiceLister: svcInformer.Lister(),
	}
	impl := controller.NewImpl(c, logger, "NameOfController")

	// Set up event handlers.
	svcInformer.Informer().AddEventHandler(...)

	return impl
}

How it works: by importing the accessor for a client or informer we link it and trigger the init() method for its package to run at startup. Each of these libraries registers themselves similar to our init() and controller processes can leverage this to setup and inject all of the registered things onto a context to pass to your NewController().

Testing Controllers

Similar to injection.Default, we also have injection.Fake. While linking the normal accessors sets up the former, linking their fakes set up the latter.

import (
	"testing"

	// Link the fakes for any informers our controller accesses.
	_ "knative.dev/pkg/injection/informers/kubeinformers/corev1/service/fake"

	"k8s.io/client-go/rest"
	"knative.dev/pkg/injection"
	logtesting "knative.dev/pkg/logging/testing"
)

func TestFoo(t *testing.T) {
	ctx := logtesting.TestContextWithLogger(t)

	// Setup a context from all of the injected fakes.
	ctx, _ = injection.Fake.SetupInformers(ctx, &rest.Config{})
	cmw := configmap.NewStaticWatcher(...)
	ctrl := NewController(ctx, cmw)

	// Test the controller process.
}

The fake clients also support manually setting up contexts seeded with objects:

import (
	"testing"

	fakekubeclient "knative.dev/pkg/injection/clients/kubeclient/fake"

	"k8s.io/client-go/rest"
	"knative.dev/pkg/injection"
	logtesting "knative.dev/pkg/logging/testing"
)

func TestFoo(t *testing.T) {
	ctx := logtesting.TestContextWithLogger(t)

	objs := []runtime.Object{
		// Some list of initial objects in the client.
	}

	ctx, kubeClient := fakekubeclient.With(ctx, objs...)

	// The fake clients returned by our library are the actual fake type,
	// which enables us to access test-specific methods, e.g.
	kubeClient.AppendReactor(...)

	c := &Reconciler{
		Client: kubeClient,
	}

	// Test the reconciler...
}

Starting controllers

All we do is import the controller packages and pass their constructors along with a component name (single word) to our shared main. Then our shared main method sets it all up and runs our controllers.

package main

import (
	// The set of controllers this process will run.
	"github.com/knative/foo/pkg/reconciler/bar"
	"github.com/knative/baz/pkg/reconciler/blah"

	// This defines the shared main for injected controllers.
	"knative.dev/pkg/injection/sharedmain"
)

func main() {
	sharedmain.Main("componentname",
       bar.NewController,
       blah.NewController,
    )
}

Generating Injection Stubs.

To make generating stubs simple, we have harnessed the Kubernetes code-generation tooling to produce injection-gen. Similar to how you might ordinarily run the other foo-gen processed:

CODEGEN_PKG=${CODEGEN_PKG:-$(cd ${REPO_ROOT}; ls -d -1 ./vendor/k8s.io/code-generator 2>/dev/null || echo ../code-generator)}

${CODEGEN_PKG}/generate-groups.sh "deepcopy,client,informer,lister" \
  github.com/knative/sample-controller/pkg/client github.com/knative/sample-controller/pkg/apis \
  "samples:v1alpha1" \
  --go-header-file ${REPO_ROOT}/hack/boilerplate/boilerplate.go.txt

To run injection-gen you run the following (replacing the import path and api group):


KNATIVE_CODEGEN_PKG=${KNATIVE_CODEGEN_PKG:-$(cd ${REPO_ROOT}; ls -d -1 ./vendor/knative.dev/pkg 2>/dev/null || echo ../pkg)}

${KNATIVE_CODEGEN_PKG}/hack/generate-knative.sh "injection" \
  github.com/knative/sample-controller/pkg/client github.com/knative/sample-controller/pkg/apis \
  "samples:v1alpha1" \
  --go-header-file ${REPO_ROOT}/hack/boilerplate/boilerplate.go.txt

To ensure the appropriate tooling is vendored, add the following to Gopkg.toml:

required = [
  "knative.dev/pkg/codegen/cmd/injection-gen",
]

# .. Constraints

# Keeps things like the generate-knative.sh script
[[prune.project]]
  name = "knative.dev/pkg"
  unused-packages = false
  non-go = false