<commit_before>/*
Copyright 2020 The cert-manager 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 test

import (
	"context"
	"flag"
	"fmt"
	"reflect"
	"testing"
	"time"

	networkingv1 "k8s.io/api/networking/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	utilerrors "k8s.io/apimachinery/pkg/util/errors"
	kubeinformers "k8s.io/client-go/informers"
	kubefake "k8s.io/client-go/kubernetes/fake"
	coretesting "k8s.io/client-go/testing"
	"k8s.io/client-go/tools/cache"
	"k8s.io/utils/clock"
	fakeclock "k8s.io/utils/clock/testing"
	gwfake "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned/fake"
	gwinformers "sigs.k8s.io/gateway-api/pkg/client/informers/externalversions"

	apiutil "github.com/jetstack/cert-manager/pkg/api/util"
	cmfake "github.com/jetstack/cert-manager/pkg/client/clientset/versioned/fake"
	informers "github.com/jetstack/cert-manager/pkg/client/informers/externalversions"
	"github.com/jetstack/cert-manager/pkg/controller"
	"github.com/jetstack/cert-manager/pkg/logs"
	"github.com/jetstack/cert-manager/pkg/metrics"
	"github.com/jetstack/cert-manager/pkg/util"
	discoveryfake "github.com/jetstack/cert-manager/test/unit/discovery"
)

func init() {
	logs.InitLogs(nil)
	_ = flag.Set("alsologtostderr", fmt.Sprintf("%t", true))
	_ = flag.Lookup("v").Value.Set("4")
}

// Builder is a structure used to construct new Contexts for use during tests.
// Currently, only KubeObjects, CertManagerObjects and GWObjects can be
// specified. These will be auto loaded into the constructed fake Clientsets.
// Call ToContext() to construct a new context using the given values.
type Builder struct {
	T *testing.T

	KubeObjects        []runtime.Object
	CertManagerObjects []runtime.Object
	GWObjects          []runtime.Object
	ExpectedActions    []Action
	ExpectedEvents     []string
	StringGenerator    StringGenerator

	// Clock will be the Clock set on the controller context.
	// If not specified, the RealClock will be used.
	Clock *fakeclock.FakeClock

	// CheckFn is a custom check function that will be executed when the
	// CheckAndFinish method is called on the builder, after all other checks.
	// It will be passed a reference to the Builder in order to access state,
	// as well as a list of all the arguments passed to the CheckAndFinish
	// function (typically the list of return arguments from the function under
	// test).
	CheckFn func(*Builder, ...interface{})

	stopCh              chan struct{}
	requiredReactors    map[string]bool
	additionalSyncFuncs []cache.InformerSynced

	*controller.Context
}

func (b *Builder) generateNameReactor(action coretesting.Action) (handled bool, ret runtime.Object, err error) {
	obj := action.(coretesting.CreateAction).GetObject().(metav1.Object)
	genName := obj.GetGenerateName()
	if genName != "" {
		obj.SetName(genName + b.StringGenerator(5))
		return false, obj.(runtime.Object), nil
	}
	return false, obj.(runtime.Object), nil
}

const informerResyncPeriod = time.Millisecond * 10

// Init will construct a new context for this builder and set default values
// for any unset fields.
func (b *Builder) Init() {
	if b.Context == nil {
		b.Context = &controller.Context{
			RootContext: context.Background(),
		}
	}
	if b.StringGenerator == nil {
		b.StringGenerator = RandStringBytes
	}
	b.requiredReactors = make(map[string]bool)
	b.Client = kubefake.NewSimpleClientset(b.KubeObjects...)
	b.CMClient = cmfake.NewSimpleClientset(b.CertManagerObjects...)
	b.GWClient = gwfake.NewSimpleClientset(b.GWObjects...)
	b.DiscoveryClient = discoveryfake.NewDiscovery().WithServerResourcesForGroupVersion(func(groupVersion string) (*metav1.APIResourceList, error) {
		if groupVersion == networkingv1.SchemeGroupVersion.String() {
			return &metav1.APIResourceList{
				TypeMeta:     metav1.TypeMeta{},
				GroupVersion: networkingv1.SchemeGroupVersion.String(),
				APIResources: []metav1.APIResource{
					{
						Name:               "Ingresses",
						SingularName:       "Ingress",
						Namespaced:         true,
						Group:              networkingv1.GroupName,
						Version:            networkingv1.SchemeGroupVersion.Version,
						Kind:               networkingv1.SchemeGroupVersion.WithKind("Ingress").Kind,
						Verbs:              metav1.Verbs{"get", "list", "watch", "create", "update", "patch", "delete", "deletecollection"},
						ShortNames:         []string{"ing"},
						Categories:         []string{"all"},
						StorageVersionHash: "testing",
					},
				},
			}, nil
		}
		return &metav1.APIResourceList{}, nil
	})
	b.Recorder = new(FakeRecorder)
	b.FakeKubeClient().PrependReactor("create", "*", b.generateNameReactor)
	b.FakeCMClient().PrependReactor("create", "*", b.generateNameReactor)
	b.FakeGWClient().PrependReactor("create", "*", b.generateNameReactor)
	b.KubeSharedInformerFactory = kubeinformers.NewSharedInformerFactory(b.Client, informerResyncPeriod)
	b.SharedInformerFactory = informers.NewSharedInformerFactory(b.CMClient, informerResyncPeriod)
	b.GWShared = gwinformers.NewSharedInformerFactory(b.GWClient, informerResyncPeriod)
	b.stopCh = make(chan struct{})
	b.Metrics = metrics.New(logs.Log, clock.RealClock{})

	// set the Clock on the context
	if b.Clock == nil {
		b.Context.Clock = clock.RealClock{}
	} else {
		b.Context.Clock = b.Clock
	}
	// Fix the clock used in apiutil so that calls to set status conditions
	// can be predictably tested
	apiutil.Clock = b.Context.Clock
}

func (b *Builder) FakeKubeClient() *kubefake.Clientset {
	return b.Context.Client.(*kubefake.Clientset)
}

func (b *Builder) FakeKubeInformerFactory() kubeinformers.SharedInformerFactory {
	return b.Context.KubeSharedInformerFactory
}

func (b *Builder) FakeCMClient() *cmfake.Clientset {
	return b.Context.CMClient.(*cmfake.Clientset)
}

func (b *Builder) FakeGWClient() *gwfake.Clientset {
	return b.Context.GWClient.(*gwfake.Clientset)
}

func (b *Builder) FakeCMInformerFactory() informers.SharedInformerFactory {
	return b.Context.SharedInformerFactory
}

func (b *Builder) EnsureReactorCalled(testName string, fn coretesting.ReactionFunc) coretesting.ReactionFunc {
	b.requiredReactors[testName] = false
	return func(action coretesting.Action) (handled bool, ret runtime.Object, err error) {
		handled, ret, err = fn(action)
		if !handled {
			return
		}
		b.requiredReactors[testName] = true
		return
	}
}

// CheckAndFinish will run ensure: all reactors are called, all actions are
// expected, and all events are as expected.
// It will then call the Builder's CheckFn, if defined.
func (b *Builder) CheckAndFinish(args ...interface{}) {
	defer b.Stop()
	if err := b.AllReactorsCalled(); err != nil {
		b.T.Errorf("Not all expected reactors were called: %v", err)
	}
	if err := b.AllActionsExecuted(); err != nil {
		b.T.Errorf(err.Error())
	}
	if err := b.AllEventsCalled(); err != nil {
		b.T.Errorf(err.Error())
	}

	// resync listers before running checks
	b.Sync()
	// run custom checks
	if b.CheckFn != nil {
		b.CheckFn(b, args...)
	}
}

func (b *Builder) AllReactorsCalled() error {
	var errs []error
	for n, reactorCalled := range b.requiredReactors {
		if !reactorCalled {
			errs = append(errs, fmt.Errorf("reactor not called: %s", n))
		}
	}
	return utilerrors.NewAggregate(errs)
}

func (b *Builder) AllEventsCalled() error {
	var errs []error
	if !util.EqualSorted(b.ExpectedEvents, b.Events()) {
		errs = append(errs, fmt.Errorf("got unexpected events, exp='%s' got='%s'",
			b.ExpectedEvents, b.Events()))
	}

	return utilerrors.NewAggregate(errs)
}

// AllActionsExecuted skips the "list" and "watch" action verbs.
func (b *Builder) AllActionsExecuted() error {
	firedActions := b.FakeCMClient().Actions()
	firedActions = append(firedActions, b.FakeKubeClient().Actions()...)
	firedActions = append(firedActions, b.FakeGWClient().Actions()...)

	var unexpectedActions []coretesting.Action
	var errs []error
	missingActions := make([]Action, len(b.ExpectedActions))
	copy(missingActions, b.ExpectedActions)
	for _, a := range firedActions {
		// skip list and watch actions
		if a.GetVerb() == "list" || a.GetVerb() == "watch" {
			continue
		}
		found := false
		var err error
		for i, expA := range missingActions {
			if expA.Action().GetNamespace() != a.GetNamespace() ||
				expA.Action().GetResource() != a.GetResource() ||
				expA.Action().GetSubresource() != a.GetSubresource() ||
				expA.Action().GetVerb() != a.GetVerb() {
				continue
			}

			err = expA.Matches(a)
			// if this action doesn't match, we record the error and continue
			// as there may be multiple action matchers for the same resource
			if err != nil {
				continue
			}

			missingActions = append(missingActions[:i], missingActions[i+1:]...)
			found = true
			break
		}
		if !found {
			unexpectedActions = append(unexpectedActions, a)

			if err != nil {
				errs = append(errs, err)
			}
		}
	}
	for _, a := range missingActions {
		errs = append(errs, fmt.Errorf("missing action: %v", actionToString(a.Action())))
	}
	for _, a := range unexpectedActions {
		errs = append(errs, fmt.Errorf("unexpected action: %v", actionToString(a)))
	}
	return utilerrors.NewAggregate(errs)
}

func actionToString(a coretesting.Action) string {
	return fmt.Sprintf("%s %q in namespace %s", a.GetVerb(), a.GetResource(), a.GetNamespace())
}

// Stop will signal the informers to stop watching changes
// This method is *not* safe to be called concurrently
func (b *Builder) Stop() {
	if b.stopCh == nil {
		return
	}

	close(b.stopCh)
	b.stopCh = nil
	// Reset the clock back to the RealClock in apiutil
	apiutil.Clock = clock.RealClock{}
}

func (b *Builder) Start() {
	b.KubeSharedInformerFactory.Start(b.stopCh)
	b.SharedInformerFactory.Start(b.stopCh)
	b.GWShared.Start(b.stopCh)

	// wait for caches to sync
	b.Sync()
}

func (b *Builder) Sync() {
	if err := mustAllSync(b.KubeSharedInformerFactory.WaitForCacheSync(b.stopCh)); err != nil {
		panic("Error waiting for kubeSharedInformerFactory to sync: " + err.Error())
	}
	if err := mustAllSync(b.SharedInformerFactory.WaitForCacheSync(b.stopCh)); err != nil {
		panic("Error waiting for SharedInformerFactory to sync: " + err.Error())
	}
	if err := mustAllSync(b.GWShared.WaitForCacheSync(b.stopCh)); err != nil {
		panic("Error waiting for GWShared to sync: " + err.Error())
	}
	if b.additionalSyncFuncs != nil {
		cache.WaitForCacheSync(b.stopCh, b.additionalSyncFuncs...)
	}
	time.Sleep(informerResyncPeriod * 3)
}

// RegisterAdditionalSyncFuncs registers an additional InformerSynced function
// with the builder.
// When the Sync method is called, the builder will also wait for the given
// listers to be synced as well as the listers that were registered with the
// informer factories that the builder provides.
func (b *Builder) RegisterAdditionalSyncFuncs(fns ...cache.InformerSynced) {
	b.additionalSyncFuncs = append(b.additionalSyncFuncs, fns...)
}

func (b *Builder) Events() []string {
	if e, ok := b.Recorder.(*FakeRecorder); ok {
		return e.Events
	}

	return nil
}

func mustAllSync(in map[reflect.Type]bool) error {
	var errs []error
	for t, started := range in {
		if !started {
			errs = append(errs, fmt.Errorf("informer for %v not synced", t))
		}
	}
	return utilerrors.NewAggregate(errs)
}
<commit_msg>Update pkg/controller/test/context_builder.go<commit_after>/*
Copyright 2020 The cert-manager 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 test

import (
	"context"
	"flag"
	"fmt"
	"reflect"
	"testing"
	"time"

	networkingv1 "k8s.io/api/networking/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	utilerrors "k8s.io/apimachinery/pkg/util/errors"
	kubeinformers "k8s.io/client-go/informers"
	kubefake "k8s.io/client-go/kubernetes/fake"
	coretesting "k8s.io/client-go/testing"
	"k8s.io/client-go/tools/cache"
	"k8s.io/utils/clock"
	fakeclock "k8s.io/utils/clock/testing"
	gwfake "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned/fake"
	gwinformers "sigs.k8s.io/gateway-api/pkg/client/informers/externalversions"

	apiutil "github.com/jetstack/cert-manager/pkg/api/util"
	cmfake "github.com/jetstack/cert-manager/pkg/client/clientset/versioned/fake"
	informers "github.com/jetstack/cert-manager/pkg/client/informers/externalversions"
	"github.com/jetstack/cert-manager/pkg/controller"
	"github.com/jetstack/cert-manager/pkg/logs"
	"github.com/jetstack/cert-manager/pkg/metrics"
	"github.com/jetstack/cert-manager/pkg/util"
	discoveryfake "github.com/jetstack/cert-manager/test/unit/discovery"
)

func init() {
	logs.InitLogs(nil)
	_ = flag.Set("alsologtostderr", fmt.Sprintf("%t", true))
	_ = flag.Lookup("v").Value.Set("4")
}

// Builder is a structure used to construct new Contexts for use during tests.
// Currently, only KubeObjects, CertManagerObjects and GWObjects can be
// specified. These will be auto loaded into the constructed fake Clientsets.
// Call ToContext() to construct a new context using the given values.
type Builder struct {
	T *testing.T

	KubeObjects        []runtime.Object
	CertManagerObjects []runtime.Object
	GWObjects          []runtime.Object
	ExpectedActions    []Action
	ExpectedEvents     []string
	StringGenerator    StringGenerator

	// Clock will be the Clock set on the controller context.
	// If not specified, the RealClock will be used.
	Clock *fakeclock.FakeClock

	// CheckFn is a custom check function that will be executed when the
	// CheckAndFinish method is called on the builder, after all other checks.
	// It will be passed a reference to the Builder in order to access state,
	// as well as a list of all the arguments passed to the CheckAndFinish
	// function (typically the list of return arguments from the function under
	// test).
	CheckFn func(*Builder, ...interface{})

	stopCh              chan struct{}
	requiredReactors    map[string]bool
	additionalSyncFuncs []cache.InformerSynced

	*controller.Context
}

func (b *Builder) generateNameReactor(action coretesting.Action) (handled bool, ret runtime.Object, err error) {
	obj := action.(coretesting.CreateAction).GetObject().(metav1.Object)
	genName := obj.GetGenerateName()
	if genName != "" {
		obj.SetName(genName + b.StringGenerator(5))
		return false, obj.(runtime.Object), nil
	}
	return false, obj.(runtime.Object), nil
}

const informerResyncPeriod = time.Millisecond * 10

// Init will construct a new context for this builder and set default values
// for any unset fields.
func (b *Builder) Init() {
	if b.Context == nil {
		b.Context = &controller.Context{
			RootContext: context.Background(),
		}
	}
	if b.StringGenerator == nil {
		b.StringGenerator = RandStringBytes
	}
	b.requiredReactors = make(map[string]bool)
	b.Client = kubefake.NewSimpleClientset(b.KubeObjects...)
	b.CMClient = cmfake.NewSimpleClientset(b.CertManagerObjects...)
	b.GWClient = gwfake.NewSimpleClientset(b.GWObjects...)
	b.DiscoveryClient = discoveryfake.NewDiscovery().WithServerResourcesForGroupVersion(func(groupVersion string) (*metav1.APIResourceList, error) {
		if groupVersion == networkingv1.SchemeGroupVersion.String() {
			return &metav1.APIResourceList{
				TypeMeta:     metav1.TypeMeta{},
				GroupVersion: networkingv1.SchemeGroupVersion.String(),
				APIResources: []metav1.APIResource{
					{
						Name:               "ingresses",
						SingularName:       "Ingress",
						Namespaced:         true,
						Group:              networkingv1.GroupName,
						Version:            networkingv1.SchemeGroupVersion.Version,
						Kind:               networkingv1.SchemeGroupVersion.WithKind("Ingress").Kind,
						Verbs:              metav1.Verbs{"get", "list", "watch", "create", "update", "patch", "delete", "deletecollection"},
						ShortNames:         []string{"ing"},
						Categories:         []string{"all"},
						StorageVersionHash: "testing",
					},
				},
			}, nil
		}
		return &metav1.APIResourceList{}, nil
	})
	b.Recorder = new(FakeRecorder)
	b.FakeKubeClient().PrependReactor("create", "*", b.generateNameReactor)
	b.FakeCMClient().PrependReactor("create", "*", b.generateNameReactor)
	b.FakeGWClient().PrependReactor("create", "*", b.generateNameReactor)
	b.KubeSharedInformerFactory = kubeinformers.NewSharedInformerFactory(b.Client, informerResyncPeriod)
	b.SharedInformerFactory = informers.NewSharedInformerFactory(b.CMClient, informerResyncPeriod)
	b.GWShared = gwinformers.NewSharedInformerFactory(b.GWClient, informerResyncPeriod)
	b.stopCh = make(chan struct{})
	b.Metrics = metrics.New(logs.Log, clock.RealClock{})

	// set the Clock on the context
	if b.Clock == nil {
		b.Context.Clock = clock.RealClock{}
	} else {
		b.Context.Clock = b.Clock
	}
	// Fix the clock used in apiutil so that calls to set status conditions
	// can be predictably tested
	apiutil.Clock = b.Context.Clock
}

func (b *Builder) FakeKubeClient() *kubefake.Clientset {
	return b.Context.Client.(*kubefake.Clientset)
}

func (b *Builder) FakeKubeInformerFactory() kubeinformers.SharedInformerFactory {
	return b.Context.KubeSharedInformerFactory
}

func (b *Builder) FakeCMClient() *cmfake.Clientset {
	return b.Context.CMClient.(*cmfake.Clientset)
}

func (b *Builder) FakeGWClient() *gwfake.Clientset {
	return b.Context.GWClient.(*gwfake.Clientset)
}

func (b *Builder) FakeCMInformerFactory() informers.SharedInformerFactory {
	return b.Context.SharedInformerFactory
}

func (b *Builder) EnsureReactorCalled(testName string, fn coretesting.ReactionFunc) coretesting.ReactionFunc {
	b.requiredReactors[testName] = false
	return func(action coretesting.Action) (handled bool, ret runtime.Object, err error) {
		handled, ret, err = fn(action)
		if !handled {
			return
		}
		b.requiredReactors[testName] = true
		return
	}
}

// CheckAndFinish will run ensure: all reactors are called, all actions are
// expected, and all events are as expected.
// It will then call the Builder's CheckFn, if defined.
func (b *Builder) CheckAndFinish(args ...interface{}) {
	defer b.Stop()
	if err := b.AllReactorsCalled(); err != nil {
		b.T.Errorf("Not all expected reactors were called: %v", err)
	}
	if err := b.AllActionsExecuted(); err != nil {
		b.T.Errorf(err.Error())
	}
	if err := b.AllEventsCalled(); err != nil {
		b.T.Errorf(err.Error())
	}

	// resync listers before running checks
	b.Sync()
	// run custom checks
	if b.CheckFn != nil {
		b.CheckFn(b, args...)
	}
}

func (b *Builder) AllReactorsCalled() error {
	var errs []error
	for n, reactorCalled := range b.requiredReactors {
		if !reactorCalled {
			errs = append(errs, fmt.Errorf("reactor not called: %s", n))
		}
	}
	return utilerrors.NewAggregate(errs)
}

func (b *Builder) AllEventsCalled() error {
	var errs []error
	if !util.EqualSorted(b.ExpectedEvents, b.Events()) {
		errs = append(errs, fmt.Errorf("got unexpected events, exp='%s' got='%s'",
			b.ExpectedEvents, b.Events()))
	}

	return utilerrors.NewAggregate(errs)
}

// AllActionsExecuted skips the "list" and "watch" action verbs.
func (b *Builder) AllActionsExecuted() error {
	firedActions := b.FakeCMClient().Actions()
	firedActions = append(firedActions, b.FakeKubeClient().Actions()...)
	firedActions = append(firedActions, b.FakeGWClient().Actions()...)

	var unexpectedActions []coretesting.Action
	var errs []error
	missingActions := make([]Action, len(b.ExpectedActions))
	copy(missingActions, b.ExpectedActions)
	for _, a := range firedActions {
		// skip list and watch actions
		if a.GetVerb() == "list" || a.GetVerb() == "watch" {
			continue
		}
		found := false
		var err error
		for i, expA := range missingActions {
			if expA.Action().GetNamespace() != a.GetNamespace() ||
				expA.Action().GetResource() != a.GetResource() ||
				expA.Action().GetSubresource() != a.GetSubresource() ||
				expA.Action().GetVerb() != a.GetVerb() {
				continue
			}

			err = expA.Matches(a)
			// if this action doesn't match, we record the error and continue
			// as there may be multiple action matchers for the same resource
			if err != nil {
				continue
			}

			missingActions = append(missingActions[:i], missingActions[i+1:]...)
			found = true
			break
		}
		if !found {
			unexpectedActions = append(unexpectedActions, a)

			if err != nil {
				errs = append(errs, err)
			}
		}
	}
	for _, a := range missingActions {
		errs = append(errs, fmt.Errorf("missing action: %v", actionToString(a.Action())))
	}
	for _, a := range unexpectedActions {
		errs = append(errs, fmt.Errorf("unexpected action: %v", actionToString(a)))
	}
	return utilerrors.NewAggregate(errs)
}

func actionToString(a coretesting.Action) string {
	return fmt.Sprintf("%s %q in namespace %s", a.GetVerb(), a.GetResource(), a.GetNamespace())
}

// Stop will signal the informers to stop watching changes
// This method is *not* safe to be called concurrently
func (b *Builder) Stop() {
	if b.stopCh == nil {
		return
	}

	close(b.stopCh)
	b.stopCh = nil
	// Reset the clock back to the RealClock in apiutil
	apiutil.Clock = clock.RealClock{}
}

func (b *Builder) Start() {
	b.KubeSharedInformerFactory.Start(b.stopCh)
	b.SharedInformerFactory.Start(b.stopCh)
	b.GWShared.Start(b.stopCh)

	// wait for caches to sync
	b.Sync()
}

func (b *Builder) Sync() {
	if err := mustAllSync(b.KubeSharedInformerFactory.WaitForCacheSync(b.stopCh)); err != nil {
		panic("Error waiting for kubeSharedInformerFactory to sync: " + err.Error())
	}
	if err := mustAllSync(b.SharedInformerFactory.WaitForCacheSync(b.stopCh)); err != nil {
		panic("Error waiting for SharedInformerFactory to sync: " + err.Error())
	}
	if err := mustAllSync(b.GWShared.WaitForCacheSync(b.stopCh)); err != nil {
		panic("Error waiting for GWShared to sync: " + err.Error())
	}
	if b.additionalSyncFuncs != nil {
		cache.WaitForCacheSync(b.stopCh, b.additionalSyncFuncs...)
	}
	time.Sleep(informerResyncPeriod * 3)
}

// RegisterAdditionalSyncFuncs registers an additional InformerSynced function
// with the builder.
// When the Sync method is called, the builder will also wait for the given
// listers to be synced as well as the listers that were registered with the
// informer factories that the builder provides.
func (b *Builder) RegisterAdditionalSyncFuncs(fns ...cache.InformerSynced) {
	b.additionalSyncFuncs = append(b.additionalSyncFuncs, fns...)
}

func (b *Builder) Events() []string {
	if e, ok := b.Recorder.(*FakeRecorder); ok {
		return e.Events
	}

	return nil
}

func mustAllSync(in map[reflect.Type]bool) error {
	var errs []error
	for t, started := range in {
		if !started {
			errs = append(errs, fmt.Errorf("informer for %v not synced", t))
		}
	}
	return utilerrors.NewAggregate(errs)
}
<|endoftext|>


<commit_before>/*
Copyright 2019 Google LLC

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 operations

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"strings"

	"go.uber.org/zap"
	"knative.dev/pkg/kmeta"
	"knative.dev/pkg/logging"

	storageClient "cloud.google.com/go/storage"
	"github.com/google/knative-gcp/pkg/operations"
	"google.golang.org/grpc/codes"
	gstatus "google.golang.org/grpc/status"

	batchv1 "k8s.io/api/batch/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

var (
	// Mapping of the storage importer eventTypes to google storage types.
	storageEventTypes = map[string]string{
		"finalize":       "OBJECT_FINALIZE",
		"archive":        "OBJECT_ARCHIVE",
		"delete":         "OBJECT_DELETE",
		"metadataUpdate": "OBJECT_METADATA_UPDATE",
	}
)

// TODO: the job could output the resolved projectID.
type NotificationActionResult struct {
	// Result is the result the operation attempted.
	Result bool `json:"result,omitempty"`
	// Error is the error string if failure occurred
	Error string `json:"error,omitempty"`
	// NotificationId holds the notification ID for GCS
	// and is filled in during create operation.
	NotificationId string `json:"notificationId,omitempty"`
	// Project is the project id that we used (this might have
	// been defaulted, to we'll expose it).
	ProjectId string `json:"projectId,omitempty"`
}

// NotificationArgs are the configuration required to make a NewNotificationOps.
type NotificationArgs struct {
	// UID of the resource that caused the action to be taken. Will
	// be added as a label to the podtemplate.
	UID string
	// Image is the actual binary that we'll run to operate on the
	// notification.
	Image string
	// Action is what the binary should do
	Action    string
	ProjectID string
	// Bucket
	Bucket string
	// TopicID we'll use for pubsub target.
	TopicID string
	// NotificationId is the notifification ID that GCS gives
	// back to us. We need that to delete it.
	NotificationId string
	// EventTypes is an array of strings specifying which
	// event types we want the notification to fire on.
	EventTypes []string
	// ObjectNamePrefix is an optional filter
	ObjectNamePrefix string
	// CustomAttributes is the list of additional attributes to have
	// GCS supply back to us when it sends a notification.
	CustomAttributes map[string]string
	Secret           corev1.SecretKeySelector
	Owner            kmeta.OwnerRefable
}

// NewNotificationOps returns a new batch Job resource.
func NewNotificationOps(arg NotificationArgs) *batchv1.Job {
	env := []corev1.EnvVar{{
		Name:  "ACTION",
		Value: arg.Action,
	}, {
		Name:  "PROJECT_ID",
		Value: arg.ProjectID,
	}, {
		Name:  "BUCKET",
		Value: arg.Bucket,
	}}

	switch arg.Action {
	case operations.ActionCreate:
		env = append(env, []corev1.EnvVar{
			{
				Name:  "EVENT_TYPES",
				Value: strings.Join(arg.EventTypes, ":"),
			}, {
				Name:  "PUBSUB_TOPIC_ID",
				Value: arg.TopicID,
			}}...)
	case operations.ActionDelete:
		env = append(env, []corev1.EnvVar{{
			Name:  "NOTIFICATION_ID",
			Value: arg.NotificationId,
		}}...)
	}

	podTemplate := operations.MakePodTemplate(arg.Image, arg.UID, arg.Action, arg.Secret, env...)

	backoffLimit := int32(3)
	parallelism := int32(1)

	return &batchv1.Job{
		ObjectMeta: metav1.ObjectMeta{
			Name:            NotificationJobName(arg.Owner, arg.Action),
			Namespace:       arg.Owner.GetObjectMeta().GetNamespace(),
			Labels:          NotificationJobLabels(arg.Owner, arg.Action),
			OwnerReferences: []metav1.OwnerReference{*kmeta.NewControllerRef(arg.Owner)},
		},
		Spec: batchv1.JobSpec{
			BackoffLimit: &backoffLimit,
			Parallelism:  &parallelism,
			Template:     *podTemplate,
		},
	}
}

// NotificationOps defines the configuration to use for this operation.
type NotificationOps struct {
	StorageOps

	// Action is the operation the job should run.
	// Options: [exists, create, delete]
	Action string `envconfig:"ACTION" required:"true"`

	// Topic is the environment variable containing the PubSub Topic being
	// subscribed to's name. In the form that is unique within the project.
	// E.g. 'laconia', not 'projects/my-gcp-project/topics/laconia'.
	Topic string `envconfig:"PUBSUB_TOPIC_ID" required:"false"`

	// Bucket to operate on
	Bucket string `envconfig:"BUCKET" required:"true"`

	// NotificationId is the environment variable containing the name of the
	// subscription to use.
	NotificationId string `envconfig:"NOTIFICATION_ID" required:"false" default:""`

	// EventTypes is a : separated eventtypes, if omitted all will be used.
	// TODO: Look at native envconfig list support
	EventTypes string `envconfig:"EVENT_TYPES" required:"false" default:""`

	// ObjectNamePrefix is an optional filter for the GCS
	ObjectNamePrefix string `envconfig:"OBJECT_NAME_PREFIX" required:"false" default:""`

	// TODO; Add support for custom attributes. Look at using envconfig Map with
	// necessary encoding / decoding.
}

// Run will perform the action configured upon a subscription.
func (n *NotificationOps) Run(ctx context.Context) error {
	if n.Client == nil {
		return errors.New("pub/sub client is nil")
	}
	logger := logging.FromContext(ctx)

	logger = logger.With(
		zap.String("action", n.Action),
		zap.String("project", n.Project),
		zap.String("topic", n.Topic),
		zap.String("subscription", n.NotificationId),
	)

	logger.Info("Storage Notification Job.")

	// Load the Bucket.
	bucket := n.Client.Bucket(n.Bucket)

	switch n.Action {
	case operations.ActionExists:
		// If notification doesn't exist, that is an error.
		logger.Info("Previously created.")

	case operations.ActionCreate:
		if n.Topic == "" {
			result := &NotificationActionResult{
				Result: false,
				Error:  "Topic not specified, need it for create",
			}
			writeErr := n.writeTerminationMessage(result)
			return fmt.Errorf("Topic not specified, need it for create")
		}
		customAttributes := make(map[string]string)

		// Add our own event type here...
		customAttributes["knative-gcp"] = "google.storage"

		eventTypes := strings.Split(n.EventTypes, ":")
		logger.Infof("Creating a notification on bucket %s", n.Bucket)

		nc := storageClient.Notification{
			TopicProjectID:   n.Project,
			TopicID:          n.Topic,
			PayloadFormat:    storageClient.JSONPayload,
			EventTypes:       n.toStorageEventTypes(eventTypes),
			ObjectNamePrefix: n.ObjectNamePrefix,
			CustomAttributes: customAttributes,
		}

		notification, err := bucket.AddNotification(ctx, &nc)
		if err != nil {
			result := &NotificationActionResult{
				Result: false,
				Error:  err.Error(),
			}
			logger.Infof("Failed to create Notification: %s", err)
			err = n.writeTerminationMessage(result)
			return err
		}
		logger.Infof("Created Notification %q", notification.ID)
		result := &NotificationActionResult{
			Result:         true,
			NotificationId: notification.ID,
		}
		err = n.writeTerminationMessage(result)
		if err != nil {
			logger.Infof("Failed to write termination message: %s", err)
			return err
		}
	case operations.ActionDelete:
		notifications, err := bucket.Notifications(ctx)
		if err != nil {
			logger.Infof("Failed to fetch existing notifications: %s", err)
			return err
		}

		// This is bit wonky because, we could always just try to delete, but figuring out
		// if an error returned is NotFound seems to not really work, so, we'll try
		// checking first the list and only then deleting.
		notificationId := n.NotificationId
		if notificationId != "" {
			if existing, ok := notifications[notificationId]; ok {
				logger.Infof("Found existing notification: %+v", existing)
				logger.Infof("Deleting notification as: %q", notificationId)
				err = bucket.DeleteNotification(ctx, notificationId)
				if err == nil {
					logger.Infof("Deleted Notification: %q", notificationId)
					err = n.writeTerminationMessage(&NotificationActionResult{Result: true})
					if err != nil {
						logger.Infof("Failed to write termination message: %s", err)
						return err
					}
					return nil
				}

				if st, ok := gstatus.FromError(err); !ok {
					logger.Infof("error from the cloud storage client: %s", err)
					writeErr := n.writeTerminationMessage(&NotificationActionResult{Result: false, Error: err.Error()})
					if writeErr != nil {
						logger.Infof("Failed to write termination message: %s", writeErr)
						return err
					}
					return err
				} else if st.Code() != codes.NotFound {
					writeErr := n.writeTerminationMessage(&NotificationActionResult{Result: false, Error: err.Error()})
					if writeErr != nil {
						logger.Infof("Failed to write termination message: %s", writeErr)
						return err
					}
					return err
				}
			}
		}
	default:
		return fmt.Errorf("unknown action value %v", n.Action)
	}

	logger.Info("Done.")
	return nil
}

func (n *NotificationOps) toStorageEventTypes(eventTypes []string) []string {
	storageTypes := make([]string, 0, len(eventTypes))
	for _, eventType := range eventTypes {
		storageTypes = append(storageTypes, storageEventTypes[eventType])
	}

	if len(storageTypes) == 0 {
		return append(storageTypes, "OBJECT_FINALIZE")
	}
	return storageTypes
}

func (n *NotificationOps) writeTerminationMessage(result *NotificationActionResult) error {
	// Always add the project regardless of what we did.
	result.ProjectId = n.Project
	m, err := json.Marshal(result)
	if err != nil {
		return err
	}
	return ioutil.WriteFile("/dev/termination-log", m, 0644)
}
<commit_msg>write termination message to logs<commit_after>/*
Copyright 2019 Google LLC

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 operations

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"strings"

	"go.uber.org/zap"
	"knative.dev/pkg/kmeta"
	"knative.dev/pkg/logging"

	storageClient "cloud.google.com/go/storage"
	"github.com/google/knative-gcp/pkg/operations"
	"google.golang.org/grpc/codes"
	gstatus "google.golang.org/grpc/status"

	batchv1 "k8s.io/api/batch/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

var (
	// Mapping of the storage importer eventTypes to google storage types.
	storageEventTypes = map[string]string{
		"finalize":       "OBJECT_FINALIZE",
		"archive":        "OBJECT_ARCHIVE",
		"delete":         "OBJECT_DELETE",
		"metadataUpdate": "OBJECT_METADATA_UPDATE",
	}
)

// TODO: the job could output the resolved projectID.
type NotificationActionResult struct {
	// Result is the result the operation attempted.
	Result bool `json:"result,omitempty"`
	// Error is the error string if failure occurred
	Error string `json:"error,omitempty"`
	// NotificationId holds the notification ID for GCS
	// and is filled in during create operation.
	NotificationId string `json:"notificationId,omitempty"`
	// Project is the project id that we used (this might have
	// been defaulted, to we'll expose it).
	ProjectId string `json:"projectId,omitempty"`
}

// NotificationArgs are the configuration required to make a NewNotificationOps.
type NotificationArgs struct {
	// UID of the resource that caused the action to be taken. Will
	// be added as a label to the podtemplate.
	UID string
	// Image is the actual binary that we'll run to operate on the
	// notification.
	Image string
	// Action is what the binary should do
	Action    string
	ProjectID string
	// Bucket
	Bucket string
	// TopicID we'll use for pubsub target.
	TopicID string
	// NotificationId is the notifification ID that GCS gives
	// back to us. We need that to delete it.
	NotificationId string
	// EventTypes is an array of strings specifying which
	// event types we want the notification to fire on.
	EventTypes []string
	// ObjectNamePrefix is an optional filter
	ObjectNamePrefix string
	// CustomAttributes is the list of additional attributes to have
	// GCS supply back to us when it sends a notification.
	CustomAttributes map[string]string
	Secret           corev1.SecretKeySelector
	Owner            kmeta.OwnerRefable
}

// NewNotificationOps returns a new batch Job resource.
func NewNotificationOps(arg NotificationArgs) *batchv1.Job {
	env := []corev1.EnvVar{{
		Name:  "ACTION",
		Value: arg.Action,
	}, {
		Name:  "PROJECT_ID",
		Value: arg.ProjectID,
	}, {
		Name:  "BUCKET",
		Value: arg.Bucket,
	}}

	switch arg.Action {
	case operations.ActionCreate:
		env = append(env, []corev1.EnvVar{
			{
				Name:  "EVENT_TYPES",
				Value: strings.Join(arg.EventTypes, ":"),
			}, {
				Name:  "PUBSUB_TOPIC_ID",
				Value: arg.TopicID,
			}}...)
	case operations.ActionDelete:
		env = append(env, []corev1.EnvVar{{
			Name:  "NOTIFICATION_ID",
			Value: arg.NotificationId,
		}}...)
	}

	podTemplate := operations.MakePodTemplate(arg.Image, arg.UID, arg.Action, arg.Secret, env...)

	backoffLimit := int32(3)
	parallelism := int32(1)

	return &batchv1.Job{
		ObjectMeta: metav1.ObjectMeta{
			Name:            NotificationJobName(arg.Owner, arg.Action),
			Namespace:       arg.Owner.GetObjectMeta().GetNamespace(),
			Labels:          NotificationJobLabels(arg.Owner, arg.Action),
			OwnerReferences: []metav1.OwnerReference{*kmeta.NewControllerRef(arg.Owner)},
		},
		Spec: batchv1.JobSpec{
			BackoffLimit: &backoffLimit,
			Parallelism:  &parallelism,
			Template:     *podTemplate,
		},
	}
}

// NotificationOps defines the configuration to use for this operation.
type NotificationOps struct {
	StorageOps

	// Action is the operation the job should run.
	// Options: [exists, create, delete]
	Action string `envconfig:"ACTION" required:"true"`

	// Topic is the environment variable containing the PubSub Topic being
	// subscribed to's name. In the form that is unique within the project.
	// E.g. 'laconia', not 'projects/my-gcp-project/topics/laconia'.
	Topic string `envconfig:"PUBSUB_TOPIC_ID" required:"false"`

	// Bucket to operate on
	Bucket string `envconfig:"BUCKET" required:"true"`

	// NotificationId is the environment variable containing the name of the
	// subscription to use.
	NotificationId string `envconfig:"NOTIFICATION_ID" required:"false" default:""`

	// EventTypes is a : separated eventtypes, if omitted all will be used.
	// TODO: Look at native envconfig list support
	EventTypes string `envconfig:"EVENT_TYPES" required:"false" default:""`

	// ObjectNamePrefix is an optional filter for the GCS
	ObjectNamePrefix string `envconfig:"OBJECT_NAME_PREFIX" required:"false" default:""`

	// TODO; Add support for custom attributes. Look at using envconfig Map with
	// necessary encoding / decoding.
}

// Run will perform the action configured upon a subscription.
func (n *NotificationOps) Run(ctx context.Context) error {
	if n.Client == nil {
		return errors.New("pub/sub client is nil")
	}
	logger := logging.FromContext(ctx)

	logger = logger.With(
		zap.String("action", n.Action),
		zap.String("project", n.Project),
		zap.String("topic", n.Topic),
		zap.String("subscription", n.NotificationId),
	)

	logger.Info("Storage Notification Job.")

	// Load the Bucket.
	bucket := n.Client.Bucket(n.Bucket)

	switch n.Action {
	case operations.ActionExists:
		// If notification doesn't exist, that is an error.
		logger.Info("Previously created.")

	case operations.ActionCreate:
		if n.Topic == "" {
			result := &NotificationActionResult{
				Result: false,
				Error:  "Topic not specified, need it for create",
			}
			writeErr := n.writeTerminationMessage(result)
			logger.Errorf("Failed to write termination message: %", writeErr)
			return fmt.Errorf("Topic not specified, need it for create")
		}
		customAttributes := make(map[string]string)

		// Add our own event type here...
		customAttributes["knative-gcp"] = "google.storage"

		eventTypes := strings.Split(n.EventTypes, ":")
		logger.Infof("Creating a notification on bucket %s", n.Bucket)

		nc := storageClient.Notification{
			TopicProjectID:   n.Project,
			TopicID:          n.Topic,
			PayloadFormat:    storageClient.JSONPayload,
			EventTypes:       n.toStorageEventTypes(eventTypes),
			ObjectNamePrefix: n.ObjectNamePrefix,
			CustomAttributes: customAttributes,
		}

		notification, err := bucket.AddNotification(ctx, &nc)
		if err != nil {
			result := &NotificationActionResult{
				Result: false,
				Error:  err.Error(),
			}
			logger.Infof("Failed to create Notification: %s", err)
			err = n.writeTerminationMessage(result)
			return err
		}
		logger.Infof("Created Notification %q", notification.ID)
		result := &NotificationActionResult{
			Result:         true,
			NotificationId: notification.ID,
		}
		err = n.writeTerminationMessage(result)
		if err != nil {
			logger.Infof("Failed to write termination message: %s", err)
			return err
		}
	case operations.ActionDelete:
		notifications, err := bucket.Notifications(ctx)
		if err != nil {
			logger.Infof("Failed to fetch existing notifications: %s", err)
			return err
		}

		// This is bit wonky because, we could always just try to delete, but figuring out
		// if an error returned is NotFound seems to not really work, so, we'll try
		// checking first the list and only then deleting.
		notificationId := n.NotificationId
		if notificationId != "" {
			if existing, ok := notifications[notificationId]; ok {
				logger.Infof("Found existing notification: %+v", existing)
				logger.Infof("Deleting notification as: %q", notificationId)
				err = bucket.DeleteNotification(ctx, notificationId)
				if err == nil {
					logger.Infof("Deleted Notification: %q", notificationId)
					err = n.writeTerminationMessage(&NotificationActionResult{Result: true})
					if err != nil {
						logger.Infof("Failed to write termination message: %s", err)
						return err
					}
					return nil
				}

				if st, ok := gstatus.FromError(err); !ok {
					logger.Infof("error from the cloud storage client: %s", err)
					writeErr := n.writeTerminationMessage(&NotificationActionResult{Result: false, Error: err.Error()})
					if writeErr != nil {
						logger.Infof("Failed to write termination message: %s", writeErr)
						return err
					}
					return err
				} else if st.Code() != codes.NotFound {
					writeErr := n.writeTerminationMessage(&NotificationActionResult{Result: false, Error: err.Error()})
					if writeErr != nil {
						logger.Infof("Failed to write termination message: %s", writeErr)
						return err
					}
					return err
				}
			}
		}
	default:
		return fmt.Errorf("unknown action value %v", n.Action)
	}

	logger.Info("Done.")
	return nil
}

func (n *NotificationOps) toStorageEventTypes(eventTypes []string) []string {
	storageTypes := make([]string, 0, len(eventTypes))
	for _, eventType := range eventTypes {
		storageTypes = append(storageTypes, storageEventTypes[eventType])
	}

	if len(storageTypes) == 0 {
		return append(storageTypes, "OBJECT_FINALIZE")
	}
	return storageTypes
}

func (n *NotificationOps) writeTerminationMessage(result *NotificationActionResult) error {
	// Always add the project regardless of what we did.
	result.ProjectId = n.Project
	m, err := json.Marshal(result)
	if err != nil {
		return err
	}
	return ioutil.WriteFile("/dev/termination-log", m, 0644)
}
<|endoftext|>


<commit_before>/*
Copyright 2015 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 ipvs

import (
	"sync"
	"time"

	"fmt"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/klog"
	utilipvs "k8s.io/kubernetes/pkg/util/ipvs"
)

const (
	rsGracefulDeletePeriod = 15 * time.Minute
	rsCheckDeleteInterval  = 1 * time.Minute
)

// listItem stores real server information and the process time.
// If nothing special happened, real server will be delete after process time.
type listItem struct {
	VirtualServer *utilipvs.VirtualServer
	RealServer    *utilipvs.RealServer
}

// String return the unique real server name(with virtual server information)
func (g *listItem) String() string {
	return GetUniqueRSName(g.VirtualServer, g.RealServer)
}

// GetUniqueRSName return a string type unique rs name with vs information
func GetUniqueRSName(vs *utilipvs.VirtualServer, rs *utilipvs.RealServer) string {
	return vs.String() + "/" + rs.String()
}

type graceTerminateRSList struct {
	lock sync.Mutex
	list map[string]*listItem
}

// add push an new element to the rsList
func (q *graceTerminateRSList) add(rs *listItem) bool {
	q.lock.Lock()
	defer q.lock.Unlock()

	uniqueRS := rs.String()
	if _, ok := q.list[uniqueRS]; ok {
		return false
	}

	klog.V(5).Infof("Adding rs %v to graceful delete rsList", rs)
	q.list[uniqueRS] = rs
	return true
}

// remove remove an element from the rsList
func (q *graceTerminateRSList) remove(rs *listItem) bool {
	q.lock.Lock()
	defer q.lock.Unlock()

	uniqueRS := rs.String()
	if _, ok := q.list[uniqueRS]; ok {
		delete(q.list, uniqueRS)
		return true
	}
	return false
}

func (q *graceTerminateRSList) flushList(handler func(rsToDelete *listItem) (bool, error)) bool {
	success := true
	for name, rs := range q.list {
		deleted, err := handler(rs)
		if err != nil {
			klog.Errorf("Try delete rs %q err: %v", name, err)
			success = false
		}
		if deleted {
			klog.Infof("lw: remote out of the list: %s", name)
			q.remove(rs)
		}
	}
	return success
}

// exist check whether the specified unique RS is in the rsList
func (q *graceTerminateRSList) exist(uniqueRS string) (*listItem, bool) {
	q.lock.Lock()
	defer q.lock.Unlock()

	if rs, ok := q.list[uniqueRS]; ok {
		return rs, true
	}
	return nil, false
}

// GracefulTerminationManager manage rs graceful termination information and do graceful termination work
// rsList is the rs list to graceful termination, ipvs is the ipvsinterface to do ipvs delete/update work
type GracefulTerminationManager struct {
	rsList graceTerminateRSList
	ipvs   utilipvs.Interface
}

// NewGracefulTerminationManager create a gracefulTerminationManager to manage ipvs rs graceful termination work
func NewGracefulTerminationManager(ipvs utilipvs.Interface) *GracefulTerminationManager {
	l := make(map[string]*listItem)
	return &GracefulTerminationManager{
		rsList: graceTerminateRSList{
			list: l,
		},
		ipvs: ipvs,
	}
}

// InTerminationList to check whether specified unique rs name is in graceful termination list
func (m *GracefulTerminationManager) InTerminationList(uniqueRS string) bool {
	_, exist := m.rsList.exist(uniqueRS)
	return exist
}

// GracefulDeleteRS to update rs weight to 0, and add rs to graceful terminate list
func (m *GracefulTerminationManager) GracefulDeleteRS(vs *utilipvs.VirtualServer, rs *utilipvs.RealServer) error {
	// Try to delete rs before add it to graceful delete list
	ele := &listItem{
		VirtualServer: vs,
		RealServer:    rs,
	}
	deleted, err := m.deleteRsFunc(ele)
	if err != nil {
		klog.Errorf("Delete rs %q err: %v", ele.String(), err)
	}
	if deleted {
		return nil
	}
	rs.Weight = 0
	err = m.ipvs.UpdateRealServer(vs, rs)
	if err != nil {
		return err
	}
	klog.V(5).Infof("Adding an element to graceful delete rsList: %+v", ele)
	m.rsList.add(ele)
	return nil
}

func (m *GracefulTerminationManager) deleteRsFunc(rsToDelete *listItem) (bool, error) {
	klog.Infof("Trying to delete rs: %s", rsToDelete.String())
	rss, err := m.ipvs.GetRealServers(rsToDelete.VirtualServer)
	if err != nil {
		return false, err
	}
	for _, rs := range rss {
		if rsToDelete.RealServer.Equal(rs) {
			// Delete RS with no connections
			// For UDP, ActiveConn is always 0
			// For TCP, InactiveConn are connections not in ESTABLISHED state
			if rs.ActiveConn+rs.InactiveConn != 0 {
				klog.Infof("Not deleting, RS %v: %v ActiveConn, %v InactiveConn", rsToDelete.String(), rs.ActiveConn, rs.InactiveConn)
				return false, nil
			}
			klog.Infof("Deleting rs: %s", rsToDelete.String())
			err := m.ipvs.DeleteRealServer(rsToDelete.VirtualServer, rs)
			if err != nil {
				return false, fmt.Errorf("Delete destination %q err: %v", rs.String(), err)
			}
			return true, nil
		}
	}
	return true, fmt.Errorf("Failed to delete rs %q, can't find the real server", rsToDelete.String())
}

func (m *GracefulTerminationManager) tryDeleteRs() {
	if !m.rsList.flushList(m.deleteRsFunc) {
		klog.Errorf("Try flush graceful termination list err")
	}
}

// MoveRSOutofGracefulDeleteList to delete an rs and remove it from the rsList immediately
func (m *GracefulTerminationManager) MoveRSOutofGracefulDeleteList(uniqueRS string) error {
	rsToDelete, find := m.rsList.exist(uniqueRS)
	if !find || rsToDelete == nil {
		return fmt.Errorf("failed to find rs: %q", uniqueRS)
	}
	err := m.ipvs.DeleteRealServer(rsToDelete.VirtualServer, rsToDelete.RealServer)
	if err != nil {
		return err
	}
	m.rsList.remove(rsToDelete)
	return nil
}

// Run start a goroutine to try to delete rs in the graceful delete rsList with an interval 1 minute
func (m *GracefulTerminationManager) Run() {
	go wait.Until(m.tryDeleteRs, rsCheckDeleteInterval, wait.NeverStop)
}
<commit_msg>[proxier/ipvs] Disable graceful termination for udp<commit_after>/*
Copyright 2015 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 ipvs

import (
	"fmt"
	"sync"
	"time"

	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/klog"
	utilipvs "k8s.io/kubernetes/pkg/util/ipvs"
)

const (
	rsGracefulDeletePeriod = 15 * time.Minute
	rsCheckDeleteInterval  = 1 * time.Minute
)

// listItem stores real server information and the process time.
// If nothing special happened, real server will be delete after process time.
type listItem struct {
	VirtualServer *utilipvs.VirtualServer
	RealServer    *utilipvs.RealServer
}

// String return the unique real server name(with virtual server information)
func (g *listItem) String() string {
	return GetUniqueRSName(g.VirtualServer, g.RealServer)
}

// GetUniqueRSName return a string type unique rs name with vs information
func GetUniqueRSName(vs *utilipvs.VirtualServer, rs *utilipvs.RealServer) string {
	return vs.String() + "/" + rs.String()
}

type graceTerminateRSList struct {
	lock sync.Mutex
	list map[string]*listItem
}

// add push an new element to the rsList
func (q *graceTerminateRSList) add(rs *listItem) bool {
	q.lock.Lock()
	defer q.lock.Unlock()

	uniqueRS := rs.String()
	if _, ok := q.list[uniqueRS]; ok {
		return false
	}

	klog.V(5).Infof("Adding rs %v to graceful delete rsList", rs)
	q.list[uniqueRS] = rs
	return true
}

// remove remove an element from the rsList
func (q *graceTerminateRSList) remove(rs *listItem) bool {
	q.lock.Lock()
	defer q.lock.Unlock()

	uniqueRS := rs.String()
	if _, ok := q.list[uniqueRS]; ok {
		delete(q.list, uniqueRS)
		return true
	}
	return false
}

func (q *graceTerminateRSList) flushList(handler func(rsToDelete *listItem) (bool, error)) bool {
	success := true
	for name, rs := range q.list {
		deleted, err := handler(rs)
		if err != nil {
			klog.Errorf("Try delete rs %q err: %v", name, err)
			success = false
		}
		if deleted {
			klog.Infof("lw: remote out of the list: %s", name)
			q.remove(rs)
		}
	}
	return success
}

// exist check whether the specified unique RS is in the rsList
func (q *graceTerminateRSList) exist(uniqueRS string) (*listItem, bool) {
	q.lock.Lock()
	defer q.lock.Unlock()

	if rs, ok := q.list[uniqueRS]; ok {
		return rs, true
	}
	return nil, false
}

// GracefulTerminationManager manage rs graceful termination information and do graceful termination work
// rsList is the rs list to graceful termination, ipvs is the ipvsinterface to do ipvs delete/update work
type GracefulTerminationManager struct {
	rsList graceTerminateRSList
	ipvs   utilipvs.Interface
}

// NewGracefulTerminationManager create a gracefulTerminationManager to manage ipvs rs graceful termination work
func NewGracefulTerminationManager(ipvs utilipvs.Interface) *GracefulTerminationManager {
	l := make(map[string]*listItem)
	return &GracefulTerminationManager{
		rsList: graceTerminateRSList{
			list: l,
		},
		ipvs: ipvs,
	}
}

// InTerminationList to check whether specified unique rs name is in graceful termination list
func (m *GracefulTerminationManager) InTerminationList(uniqueRS string) bool {
	_, exist := m.rsList.exist(uniqueRS)
	return exist
}

// GracefulDeleteRS to update rs weight to 0, and add rs to graceful terminate list
func (m *GracefulTerminationManager) GracefulDeleteRS(vs *utilipvs.VirtualServer, rs *utilipvs.RealServer) error {
	// Try to delete rs before add it to graceful delete list
	ele := &listItem{
		VirtualServer: vs,
		RealServer:    rs,
	}
	deleted, err := m.deleteRsFunc(ele)
	if err != nil {
		klog.Errorf("Delete rs %q err: %v", ele.String(), err)
	}
	if deleted {
		return nil
	}
	rs.Weight = 0
	err = m.ipvs.UpdateRealServer(vs, rs)
	if err != nil {
		return err
	}
	klog.V(5).Infof("Adding an element to graceful delete rsList: %+v", ele)
	m.rsList.add(ele)
	return nil
}

func (m *GracefulTerminationManager) deleteRsFunc(rsToDelete *listItem) (bool, error) {
	klog.Infof("Trying to delete rs: %s", rsToDelete.String())
	rss, err := m.ipvs.GetRealServers(rsToDelete.VirtualServer)
	if err != nil {
		return false, err
	}
	for _, rs := range rss {
		if rsToDelete.RealServer.Equal(rs) {
			// For UDP traffic, no graceful termination, we immediately delete the RS
			//     (existing connections will be deleted on the next packet because sysctlExpireNoDestConn=1)
			// For other protocols, don't delete until all connections have expired)
			if rsToDelete.VirtualServer.Protocol != "udp" && rs.ActiveConn+rs.InactiveConn != 0 {
				klog.Infof("Not deleting, RS %v: %v ActiveConn, %v InactiveConn", rsToDelete.String(), rs.ActiveConn, rs.InactiveConn)
				return false, nil
			}
			klog.Infof("Deleting rs: %s", rsToDelete.String())
			err := m.ipvs.DeleteRealServer(rsToDelete.VirtualServer, rs)
			if err != nil {
				return false, fmt.Errorf("Delete destination %q err: %v", rs.String(), err)
			}
			return true, nil
		}
	}
	return true, fmt.Errorf("Failed to delete rs %q, can't find the real server", rsToDelete.String())
}

func (m *GracefulTerminationManager) tryDeleteRs() {
	if !m.rsList.flushList(m.deleteRsFunc) {
		klog.Errorf("Try flush graceful termination list err")
	}
}

// MoveRSOutofGracefulDeleteList to delete an rs and remove it from the rsList immediately
func (m *GracefulTerminationManager) MoveRSOutofGracefulDeleteList(uniqueRS string) error {
	rsToDelete, find := m.rsList.exist(uniqueRS)
	if !find || rsToDelete == nil {
		return fmt.Errorf("failed to find rs: %q", uniqueRS)
	}
	err := m.ipvs.DeleteRealServer(rsToDelete.VirtualServer, rsToDelete.RealServer)
	if err != nil {
		return err
	}
	m.rsList.remove(rsToDelete)
	return nil
}

// Run start a goroutine to try to delete rs in the graceful delete rsList with an interval 1 minute
func (m *GracefulTerminationManager) Run() {
	go wait.Until(m.tryDeleteRs, rsCheckDeleteInterval, wait.NeverStop)
}
<|endoftext|>


<commit_before>package validation

import (
	"fmt"
	"strings"

	"k8s.io/kubernetes/pkg/api/validation"
	kval "k8s.io/kubernetes/pkg/api/validation"
	"k8s.io/kubernetes/pkg/util"
	"k8s.io/kubernetes/pkg/util/fielderrors"
	kvalidation "k8s.io/kubernetes/pkg/util/validation"

	oapi "github.com/openshift/origin/pkg/api"
	routeapi "github.com/openshift/origin/pkg/route/api"
)

// ValidateRoute tests if required fields in the route are set.
func ValidateRoute(route *routeapi.Route) fielderrors.ValidationErrorList {
	result := fielderrors.ValidationErrorList{}

	//ensure meta is set properly
	result = append(result, kval.ValidateObjectMeta(&route.ObjectMeta, true, oapi.GetNameValidationFunc(kval.ValidatePodName)).Prefix("metadata")...)

	//host is not required but if it is set ensure it meets DNS requirements
	if len(route.Spec.Host) > 0 {
		if !kvalidation.IsDNS1123Subdomain(route.Spec.Host) {
			result = append(result, fielderrors.NewFieldInvalid("host", route.Spec.Host, "host must conform to DNS 952 subdomain conventions"))
		}
	}

	if len(route.Spec.Path) > 0 && !strings.HasPrefix(route.Spec.Path, "/") {
		result = append(result, fielderrors.NewFieldInvalid("path", route.Spec.Path, "path must begin with /"))
	}

	if len(route.Spec.Path) > 0 && route.Spec.TLS != nil &&
		route.Spec.TLS.Termination == routeapi.TLSTerminationPassthrough {
		result = append(result, fielderrors.NewFieldInvalid("path", route.Spec.Path, "paththrough termination does not support paths"))
	}

	if len(route.Spec.To.Name) == 0 {
		result = append(result, fielderrors.NewFieldRequired("serviceName"))
	}

	if route.Spec.Port != nil {
		switch target := route.Spec.Port.TargetPort; {
		case target.Kind == util.IntstrInt && target.IntVal == 0,
			target.Kind == util.IntstrString && len(target.StrVal) == 0:
			result = append(result, fielderrors.NewFieldRequired("targetPort"))
		}
	}

	if errs := validateTLS(route); len(errs) != 0 {
		result = append(result, errs.Prefix("tls")...)
	}

	return result
}

func ValidateRouteUpdate(route *routeapi.Route, older *routeapi.Route) fielderrors.ValidationErrorList {
	allErrs := fielderrors.ValidationErrorList{}
	allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(&route.ObjectMeta, &older.ObjectMeta).Prefix("metadata")...)

	allErrs = append(allErrs, ValidateRoute(route)...)
	return allErrs
}

func ValidateRouteStatusUpdate(route *routeapi.Route, older *routeapi.Route) fielderrors.ValidationErrorList {
	allErrs := fielderrors.ValidationErrorList{}
	allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(&route.ObjectMeta, &older.ObjectMeta).Prefix("metadata")...)

	// TODO: validate route status
	return allErrs
}

// ValidateTLS tests fields for different types of TLS combinations are set.  Called
// by ValidateRoute.
func validateTLS(route *routeapi.Route) fielderrors.ValidationErrorList {
	result := fielderrors.ValidationErrorList{}
	tls := route.Spec.TLS

	//no termination, ignore other settings
	if tls == nil || tls.Termination == "" {
		return nil
	}

	switch tls.Termination {
	// reencrypt must specify destination ca cert
	// cert, key, cacert may not be specified because the route may be a wildcard
	case routeapi.TLSTerminationReencrypt:
		if len(tls.DestinationCACertificate) == 0 {
			result = append(result, fielderrors.NewFieldRequired("destinationCACertificate"))
		}
	//passthrough term should not specify any cert
	case routeapi.TLSTerminationPassthrough:
		if len(tls.Certificate) > 0 {
			result = append(result, fielderrors.NewFieldInvalid("certificate", tls.Certificate, "passthrough termination does not support certificates"))
		}

		if len(tls.Key) > 0 {
			result = append(result, fielderrors.NewFieldInvalid("key", tls.Key, "passthrough termination does not support certificates"))
		}

		if len(tls.CACertificate) > 0 {
			result = append(result, fielderrors.NewFieldInvalid("caCertificate", tls.CACertificate, "passthrough termination does not support certificates"))
		}

		if len(tls.DestinationCACertificate) > 0 {
			result = append(result, fielderrors.NewFieldInvalid("destinationCACertificate", tls.DestinationCACertificate, "passthrough termination does not support certificates"))
		}
	// edge cert should only specify cert, key, and cacert but those certs
	// may not be specified if the route is a wildcard route
	case routeapi.TLSTerminationEdge:
		if len(tls.DestinationCACertificate) > 0 {
			result = append(result, fielderrors.NewFieldInvalid("destinationCACertificate", tls.DestinationCACertificate, "edge termination does not support destination certificates"))
		}
	default:
		msg := fmt.Sprintf("invalid value for termination, acceptable values are %s, %s, %s, or empty (no tls specified)", routeapi.TLSTerminationEdge, routeapi.TLSTerminationPassthrough, routeapi.TLSTerminationReencrypt)
		result = append(result, fielderrors.NewFieldInvalid("termination", tls.Termination, msg))
	}

	if err := validateInsecureEdgeTerminationPolicy(tls); err != nil {
		result = append(result, err)
	}

	result = append(result, validateNoDoubleEscapes(tls)...)
	return result
}

// validateNoDoubleEscapes ensures double escaped newlines are not in the certificates.  Double
// escaped newlines may be a remnant of old code which used to replace them for the user unnecessarily.
// TODO this is a temporary validation to reject any of our examples with double slashes.  Remove this quickly.
func validateNoDoubleEscapes(tls *routeapi.TLSConfig) fielderrors.ValidationErrorList {
	allErrs := fielderrors.ValidationErrorList{}
	if strings.Contains(tls.CACertificate, "\\n") {
		allErrs = append(allErrs, fielderrors.NewFieldInvalid("caCertificate", tls.CACertificate, `double escaped new lines (\\n) are invalid`))
	}
	if strings.Contains(tls.Certificate, "\\n") {
		allErrs = append(allErrs, fielderrors.NewFieldInvalid("certificate", tls.Certificate, `double escaped new lines (\\n) are invalid`))
	}
	if strings.Contains(tls.Key, "\\n") {
		allErrs = append(allErrs, fielderrors.NewFieldInvalid("key", tls.Key, `double escaped new lines (\\n) are invalid`))
	}
	if strings.Contains(tls.DestinationCACertificate, "\\n") {
		allErrs = append(allErrs, fielderrors.NewFieldInvalid("destinationCACertificate", tls.DestinationCACertificate, `double escaped new lines (\\n) are invalid`))
	}
	return allErrs
}

// validateInsecureEdgeTerminationPolicy tests fields for different types of
// insecure options. Called by validateTLS.
func validateInsecureEdgeTerminationPolicy(tls *routeapi.TLSConfig) *fielderrors.ValidationError {
	// Check insecure option value if specified (empty is ok).
	if len(tls.InsecureEdgeTerminationPolicy) == 0 {
		return nil
	}

	// Ensure insecure is set only for edge terminated routes.
	if routeapi.TLSTerminationEdge != tls.Termination {
		// tls.InsecureEdgeTerminationPolicy option is not supported for a non edge-terminated routes.
		return fielderrors.NewFieldInvalid("InsecureEdgeTerminationPolicy", tls.InsecureEdgeTerminationPolicy, "InsecureEdgeTerminationPolicy is only allowed for edge-terminated routes")
	}

	// It is an edge-terminated route, check insecure option value is
	// one of None(for disable), Allow or Redirect.
	allowedValues := map[routeapi.InsecureEdgeTerminationPolicyType]bool{
		routeapi.InsecureEdgeTerminationPolicyNone:     true,
		routeapi.InsecureEdgeTerminationPolicyAllow:    true,
		routeapi.InsecureEdgeTerminationPolicyRedirect: true,
	}

	if _, ok := allowedValues[tls.InsecureEdgeTerminationPolicy]; !ok {
		msg := fmt.Sprintf("invalid value for InsecureEdgeTerminationPolicy option, acceptable values are %s, %s, %s, or empty", routeapi.InsecureEdgeTerminationPolicyNone, routeapi.InsecureEdgeTerminationPolicyAllow, routeapi.InsecureEdgeTerminationPolicyRedirect)
		return fielderrors.NewFieldInvalid("InsecureEdgeTerminationPolicy", tls.InsecureEdgeTerminationPolicy, msg)
	}

	return nil
}
<commit_msg>Fix typo as per review comments from @Miciah<commit_after>package validation

import (
	"fmt"
	"strings"

	"k8s.io/kubernetes/pkg/api/validation"
	kval "k8s.io/kubernetes/pkg/api/validation"
	"k8s.io/kubernetes/pkg/util"
	"k8s.io/kubernetes/pkg/util/fielderrors"
	kvalidation "k8s.io/kubernetes/pkg/util/validation"

	oapi "github.com/openshift/origin/pkg/api"
	routeapi "github.com/openshift/origin/pkg/route/api"
)

// ValidateRoute tests if required fields in the route are set.
func ValidateRoute(route *routeapi.Route) fielderrors.ValidationErrorList {
	result := fielderrors.ValidationErrorList{}

	//ensure meta is set properly
	result = append(result, kval.ValidateObjectMeta(&route.ObjectMeta, true, oapi.GetNameValidationFunc(kval.ValidatePodName)).Prefix("metadata")...)

	//host is not required but if it is set ensure it meets DNS requirements
	if len(route.Spec.Host) > 0 {
		if !kvalidation.IsDNS1123Subdomain(route.Spec.Host) {
			result = append(result, fielderrors.NewFieldInvalid("host", route.Spec.Host, "host must conform to DNS 952 subdomain conventions"))
		}
	}

	if len(route.Spec.Path) > 0 && !strings.HasPrefix(route.Spec.Path, "/") {
		result = append(result, fielderrors.NewFieldInvalid("path", route.Spec.Path, "path must begin with /"))
	}

	if len(route.Spec.Path) > 0 && route.Spec.TLS != nil &&
		route.Spec.TLS.Termination == routeapi.TLSTerminationPassthrough {
		result = append(result, fielderrors.NewFieldInvalid("path", route.Spec.Path, "passthrough termination does not support paths"))
	}

	if len(route.Spec.To.Name) == 0 {
		result = append(result, fielderrors.NewFieldRequired("serviceName"))
	}

	if route.Spec.Port != nil {
		switch target := route.Spec.Port.TargetPort; {
		case target.Kind == util.IntstrInt && target.IntVal == 0,
			target.Kind == util.IntstrString && len(target.StrVal) == 0:
			result = append(result, fielderrors.NewFieldRequired("targetPort"))
		}
	}

	if errs := validateTLS(route); len(errs) != 0 {
		result = append(result, errs.Prefix("tls")...)
	}

	return result
}

func ValidateRouteUpdate(route *routeapi.Route, older *routeapi.Route) fielderrors.ValidationErrorList {
	allErrs := fielderrors.ValidationErrorList{}
	allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(&route.ObjectMeta, &older.ObjectMeta).Prefix("metadata")...)

	allErrs = append(allErrs, ValidateRoute(route)...)
	return allErrs
}

func ValidateRouteStatusUpdate(route *routeapi.Route, older *routeapi.Route) fielderrors.ValidationErrorList {
	allErrs := fielderrors.ValidationErrorList{}
	allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(&route.ObjectMeta, &older.ObjectMeta).Prefix("metadata")...)

	// TODO: validate route status
	return allErrs
}

// ValidateTLS tests fields for different types of TLS combinations are set.  Called
// by ValidateRoute.
func validateTLS(route *routeapi.Route) fielderrors.ValidationErrorList {
	result := fielderrors.ValidationErrorList{}
	tls := route.Spec.TLS

	//no termination, ignore other settings
	if tls == nil || tls.Termination == "" {
		return nil
	}

	switch tls.Termination {
	// reencrypt must specify destination ca cert
	// cert, key, cacert may not be specified because the route may be a wildcard
	case routeapi.TLSTerminationReencrypt:
		if len(tls.DestinationCACertificate) == 0 {
			result = append(result, fielderrors.NewFieldRequired("destinationCACertificate"))
		}
	//passthrough term should not specify any cert
	case routeapi.TLSTerminationPassthrough:
		if len(tls.Certificate) > 0 {
			result = append(result, fielderrors.NewFieldInvalid("certificate", tls.Certificate, "passthrough termination does not support certificates"))
		}

		if len(tls.Key) > 0 {
			result = append(result, fielderrors.NewFieldInvalid("key", tls.Key, "passthrough termination does not support certificates"))
		}

		if len(tls.CACertificate) > 0 {
			result = append(result, fielderrors.NewFieldInvalid("caCertificate", tls.CACertificate, "passthrough termination does not support certificates"))
		}

		if len(tls.DestinationCACertificate) > 0 {
			result = append(result, fielderrors.NewFieldInvalid("destinationCACertificate", tls.DestinationCACertificate, "passthrough termination does not support certificates"))
		}
	// edge cert should only specify cert, key, and cacert but those certs
	// may not be specified if the route is a wildcard route
	case routeapi.TLSTerminationEdge:
		if len(tls.DestinationCACertificate) > 0 {
			result = append(result, fielderrors.NewFieldInvalid("destinationCACertificate", tls.DestinationCACertificate, "edge termination does not support destination certificates"))
		}
	default:
		msg := fmt.Sprintf("invalid value for termination, acceptable values are %s, %s, %s, or empty (no tls specified)", routeapi.TLSTerminationEdge, routeapi.TLSTerminationPassthrough, routeapi.TLSTerminationReencrypt)
		result = append(result, fielderrors.NewFieldInvalid("termination", tls.Termination, msg))
	}

	if err := validateInsecureEdgeTerminationPolicy(tls); err != nil {
		result = append(result, err)
	}

	result = append(result, validateNoDoubleEscapes(tls)...)
	return result
}

// validateNoDoubleEscapes ensures double escaped newlines are not in the certificates.  Double
// escaped newlines may be a remnant of old code which used to replace them for the user unnecessarily.
// TODO this is a temporary validation to reject any of our examples with double slashes.  Remove this quickly.
func validateNoDoubleEscapes(tls *routeapi.TLSConfig) fielderrors.ValidationErrorList {
	allErrs := fielderrors.ValidationErrorList{}
	if strings.Contains(tls.CACertificate, "\\n") {
		allErrs = append(allErrs, fielderrors.NewFieldInvalid("caCertificate", tls.CACertificate, `double escaped new lines (\\n) are invalid`))
	}
	if strings.Contains(tls.Certificate, "\\n") {
		allErrs = append(allErrs, fielderrors.NewFieldInvalid("certificate", tls.Certificate, `double escaped new lines (\\n) are invalid`))
	}
	if strings.Contains(tls.Key, "\\n") {
		allErrs = append(allErrs, fielderrors.NewFieldInvalid("key", tls.Key, `double escaped new lines (\\n) are invalid`))
	}
	if strings.Contains(tls.DestinationCACertificate, "\\n") {
		allErrs = append(allErrs, fielderrors.NewFieldInvalid("destinationCACertificate", tls.DestinationCACertificate, `double escaped new lines (\\n) are invalid`))
	}
	return allErrs
}

// validateInsecureEdgeTerminationPolicy tests fields for different types of
// insecure options. Called by validateTLS.
func validateInsecureEdgeTerminationPolicy(tls *routeapi.TLSConfig) *fielderrors.ValidationError {
	// Check insecure option value if specified (empty is ok).
	if len(tls.InsecureEdgeTerminationPolicy) == 0 {
		return nil
	}

	// Ensure insecure is set only for edge terminated routes.
	if routeapi.TLSTerminationEdge != tls.Termination {
		// tls.InsecureEdgeTerminationPolicy option is not supported for a non edge-terminated routes.
		return fielderrors.NewFieldInvalid("InsecureEdgeTerminationPolicy", tls.InsecureEdgeTerminationPolicy, "InsecureEdgeTerminationPolicy is only allowed for edge-terminated routes")
	}

	// It is an edge-terminated route, check insecure option value is
	// one of None(for disable), Allow or Redirect.
	allowedValues := map[routeapi.InsecureEdgeTerminationPolicyType]bool{
		routeapi.InsecureEdgeTerminationPolicyNone:     true,
		routeapi.InsecureEdgeTerminationPolicyAllow:    true,
		routeapi.InsecureEdgeTerminationPolicyRedirect: true,
	}

	if _, ok := allowedValues[tls.InsecureEdgeTerminationPolicy]; !ok {
		msg := fmt.Sprintf("invalid value for InsecureEdgeTerminationPolicy option, acceptable values are %s, %s, %s, or empty", routeapi.InsecureEdgeTerminationPolicyNone, routeapi.InsecureEdgeTerminationPolicyAllow, routeapi.InsecureEdgeTerminationPolicyRedirect)
		return fielderrors.NewFieldInvalid("InsecureEdgeTerminationPolicy", tls.InsecureEdgeTerminationPolicy, msg)
	}

	return nil
}
<|endoftext|>


<commit_before>package driver

import (
	"encoding/json"
	"fmt"
	"net"
	"net/http"
	"time"

	common "github.com/noironetworks/cilium-net/common"
	cnc "github.com/noironetworks/cilium-net/common/client"
	"github.com/noironetworks/cilium-net/common/plugins"
	"github.com/noironetworks/cilium-net/common/types"

	"github.com/codegangsta/cli"
	"github.com/docker/libnetwork/drivers/remote/api"
	lnTypes "github.com/docker/libnetwork/types"
	"github.com/gorilla/mux"
	l "github.com/op/go-logging"
	"github.com/vishvananda/netlink"
)

var log = l.MustGetLogger("cilium-net-client")

const (
	// ContainerInterfacePrefix is the container's internal interface name prefix.
	ContainerInterfacePrefix = "cilium"
)

// Driver interface that listens for docker requests.
type Driver interface {
	Listen(string) error
}

type driver struct {
	client      *cnc.Client
	nodeAddress net.IP
}

// NewDriver creates and returns a new Driver for the given ctx.
func NewDriver(ctx *cli.Context) (Driver, error) {
	var nodeAddress string
	c, err := cnc.NewDefaultClient()
	if err != nil {
		log.Fatalf("Error while starting cilium-client: %s", err)
	}

	for tries := 10; tries > 0; tries-- {
		if res, err := c.Ping(); err != nil {
			if tries == 1 {
				log.Fatalf("Unable to reach cilium daemon: %s", err)
			} else {
				log.Warningf("Waiting for cilium daemon to come up...")
			}
			time.Sleep(time.Second)
		} else {
			nodeAddress = res.NodeAddress
			log.Infof("Received node address from daemon: %s", nodeAddress)
			break
		}
	}

	d := &driver{
		client:      c,
		nodeAddress: net.ParseIP(nodeAddress),
	}

	log.Infof("New Cilium networking instance on node %s", nodeAddress)

	return d, nil
}

// Listen listens for docker requests on a particular set of endpoints on the given
// socket path.
func (driver *driver) Listen(socket string) error {
	router := mux.NewRouter()
	router.NotFoundHandler = http.HandlerFunc(notFound)

	handleMethod := func(method string, h http.HandlerFunc) {
		router.Methods("POST").Path(fmt.Sprintf("/%s", method)).HandlerFunc(h)
	}

	handleMethod("Plugin.Activate", driver.handshake)
	handleMethod("NetworkDriver.GetCapabilities", driver.capabilities)
	handleMethod("NetworkDriver.CreateNetwork", driver.createNetwork)
	handleMethod("NetworkDriver.DeleteNetwork", driver.deleteNetwork)
	handleMethod("NetworkDriver.CreateEndpoint", driver.createEndpoint)
	handleMethod("NetworkDriver.DeleteEndpoint", driver.deleteEndpoint)
	handleMethod("NetworkDriver.EndpointOperInfo", driver.infoEndpoint)
	handleMethod("NetworkDriver.Join", driver.joinEndpoint)
	handleMethod("NetworkDriver.Leave", driver.leaveEndpoint)
	handleMethod("IpamDriver.GetCapabilities", driver.ipamCapabilities)
	handleMethod("IpamDriver.GetDefaultAddressSpaces", driver.getDefaultAddressSpaces)
	handleMethod("IpamDriver.RequestPool", driver.requestPool)
	handleMethod("IpamDriver.ReleasePool", driver.releasePool)
	handleMethod("IpamDriver.RequestAddress", driver.requestAddress)
	handleMethod("IpamDriver.ReleaseAddress", driver.releaseAddress)
	var (
		listener net.Listener
		err      error
	)

	listener, err = net.Listen("unix", socket)
	if err != nil {
		return err
	}

	return http.Serve(listener, router)
}

func notFound(w http.ResponseWriter, r *http.Request) {
	log.Warningf("plugin Not found: [ %+v ]", r)
	http.NotFound(w, r)
}

func sendError(w http.ResponseWriter, msg string, code int) {
	log.Errorf("%d %s", code, msg)
	http.Error(w, msg, code)
}

func objectResponse(w http.ResponseWriter, obj interface{}) {
	if err := json.NewEncoder(w).Encode(obj); err != nil {
		sendError(w, "Could not JSON encode response", http.StatusInternalServerError)
		return
	}
}

func emptyResponse(w http.ResponseWriter) {
	json.NewEncoder(w).Encode(map[string]string{})
}

type handshakeResp struct {
	Implements []string
}

func (driver *driver) handshake(w http.ResponseWriter, r *http.Request) {
	err := json.NewEncoder(w).Encode(&handshakeResp{
		[]string{"NetworkDriver", "IpamDriver"},
	})
	if err != nil {
		log.Fatalf("handshake encode: %s", err)
		sendError(w, "encode error", http.StatusInternalServerError)
		return
	}
	log.Debug("Handshake completed")
}

func (driver *driver) capabilities(w http.ResponseWriter, r *http.Request) {
	err := json.NewEncoder(w).Encode(&api.GetCapabilityResponse{
		Scope: "local",
	})
	if err != nil {
		log.Fatalf("capabilities encode: %s", err)
		sendError(w, "encode error", http.StatusInternalServerError)
		return
	}
	log.Debug("NetworkDriver capabilities exchange complete")
}

func (driver *driver) createNetwork(w http.ResponseWriter, r *http.Request) {
	var create api.CreateNetworkRequest
	err := json.NewDecoder(r.Body).Decode(&create)
	if err != nil {
		sendError(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest)
		return
	}
	log.Debugf("Network Create Called: [ %+v ]", create)
	emptyResponse(w)
}

func (driver *driver) deleteNetwork(w http.ResponseWriter, r *http.Request) {
	var delete api.DeleteNetworkRequest
	if err := json.NewDecoder(r.Body).Decode(&delete); err != nil {
		sendError(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest)
		return
	}
	log.Debugf("Delete network request: %+v", &delete)
	emptyResponse(w)
}

// CreateEndpointRequest is the as api.CreateEndpointRequest but with
// json.RawMessage type for Options map
type CreateEndpointRequest struct {
	NetworkID  string
	EndpointID string
	Interface  api.EndpointInterface
	Options    map[string]json.RawMessage
}

func (driver *driver) createEndpoint(w http.ResponseWriter, r *http.Request) {
	var create CreateEndpointRequest
	if err := json.NewDecoder(r.Body).Decode(&create); err != nil {
		sendError(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest)
		return
	}
	log.Debugf("Create endpoint request: %+v", &create)

	endID := create.EndpointID
	containerAddress := create.Interface.AddressIPv6
	if containerAddress == "" {
		log.Warningf("No IPv6 address provided in CreateEndpoint request")
	}

	maps := make([]types.EPPortMap, 0, 32)

	for key, val := range create.Options {
		switch key {
		case "com.docker.network.portmap":
			var portmap []lnTypes.PortBinding
			if err := json.Unmarshal(val, &portmap); err != nil {
				sendError(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest)
				return
			}
			log.Debugf("PortBinding: %+v", &portmap)

			// FIXME: Host IP is ignored for now
			for _, m := range portmap {
				maps = append(maps, types.EPPortMap{
					From:  m.HostPort,
					To:    m.Port,
					Proto: uint8(m.Proto),
				})
			}

		case "com.docker.network.endpoint.exposedports":
			var tp []lnTypes.TransportPort
			if err := json.Unmarshal(val, &tp); err != nil {
				sendError(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest)
				return
			}
			log.Debugf("ExposedPorts: %+v", &tp)
			// TODO: handle exposed ports
		}
	}

	ep, err := driver.client.EndpointGetByDockerEPID(endID)
	if err != nil {
		sendError(w, fmt.Sprintf("Error retrieving endpoint %s", err), http.StatusBadRequest)
		return
	}
	if ep != nil {
		sendError(w, "Endpoint already exists", http.StatusBadRequest)
		return
	}

	ip, _, _ := net.ParseCIDR(containerAddress)

	endpoint := types.Endpoint{
		LXCIP:            ip,
		NodeIP:           driver.nodeAddress,
		DockerNetworkID:  create.NetworkID,
		DockerEndpointID: endID,
		PortMap:          maps,
	}
	endpoint.SetID()

	if err = driver.client.EndpointSave(endpoint); err != nil {
		sendError(w, fmt.Sprintf("Error retrieving endpoint %s", err), http.StatusBadRequest)
		return
	}

	log.Debugf("Created Endpoint: %+v", endpoint)

	log.Infof("New endpoint %s with IP %s", endID, containerAddress)

	respIface := &api.EndpointInterface{
		MacAddress: common.DefaultContainerMAC,
	}
	resp := &api.CreateEndpointResponse{
		Interface: respIface,
	}
	log.Debugf("Create endpoint response: %+v", resp)
	objectResponse(w, resp)
}

func (driver *driver) deleteEndpoint(w http.ResponseWriter, r *http.Request) {
	var del api.DeleteEndpointRequest
	if err := json.NewDecoder(r.Body).Decode(&del); err != nil {
		sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest)
		return
	}
	log.Debugf("Delete endpoint request: %+v", &del)

	if err := plugins.DelLinkByName(plugins.Endpoint2IfName(del.EndpointID)); err != nil {
		log.Warningf("Error while deleting link: %s", err)
	}

	emptyResponse(w)
}

func (driver *driver) infoEndpoint(w http.ResponseWriter, r *http.Request) {
	var info api.EndpointInfoRequest
	if err := json.NewDecoder(r.Body).Decode(&info); err != nil {
		sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest)
		return
	}
	log.Debugf("Endpoint info request: %+v", &info)
	objectResponse(w, &api.EndpointInfoResponse{Value: map[string]interface{}{}})
	log.Debugf("Endpoint info %s", info.EndpointID)
}

func (driver *driver) joinEndpoint(w http.ResponseWriter, r *http.Request) {
	var (
		j   api.JoinRequest
		err error
	)
	if err := json.NewDecoder(r.Body).Decode(&j); err != nil {
		sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest)
		return
	}
	log.Debugf("Join request: %+v", &j)

	ep, err := driver.client.EndpointGetByDockerEPID(j.EndpointID)
	if err != nil {
		sendError(w, fmt.Sprintf("Error retrieving endpoint %s", err), http.StatusBadRequest)
		return
	}
	if ep == nil {
		sendError(w, "Endpoint does not exist", http.StatusBadRequest)
		return
	}

	veth, _, tmpIfName, err := plugins.SetupVeth(j.EndpointID, 1450, ep)
	if err != nil {
		sendError(w, "Error while setting up veth pair: "+err.Error(), http.StatusBadRequest)
		return
	}
	defer func() {
		if err != nil {
			if err = netlink.LinkDel(veth); err != nil {
				log.Warningf("failed to clean up veth %q: %s", veth.Name, err)
			}
		}
	}()

	ifname := &api.InterfaceName{
		SrcName:   tmpIfName,
		DstPrefix: ContainerInterfacePrefix,
	}
	if err = driver.client.EndpointJoin(*ep); err != nil {
		log.Errorf("Joining endpoint failed: %s", err)
		sendError(w, "Unable to create BPF map: "+err.Error(), http.StatusInternalServerError)
	}

	rep, err := driver.client.GetIPAMConf(types.LibnetworkIPAMType, types.IPAMReq{})
	if err != nil {
		sendError(w, fmt.Sprintf("Could not get cilium IPAM configuration: %s", err), http.StatusBadRequest)
	}

	lnRoutes := []api.StaticRoute{}
	for _, route := range rep.IPAMConfig.IP6.Routes {
		nh := ""
		if route.IsL3() {
			nh = route.NextHop.String()
		}
		lnRoute := api.StaticRoute{
			Destination: route.Destination.String(),
			RouteType:   route.Type,
			NextHop:     nh,
		}
		lnRoutes = append(lnRoutes, lnRoute)
	}

	res := &api.JoinResponse{
		GatewayIPv6:           rep.IPAMConfig.IP6.Gateway.String(),
		InterfaceName:         ifname,
		StaticRoutes:          lnRoutes,
		DisableGatewayService: true,
	}

	log.Debugf("Join response: %+v", res)
	objectResponse(w, res)
}

func (driver *driver) leaveEndpoint(w http.ResponseWriter, r *http.Request) {
	var l api.LeaveRequest
	if err := json.NewDecoder(r.Body).Decode(&l); err != nil {
		sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest)
		return
	}
	log.Debugf("Leave request: %+v", &l)

	if err := driver.client.EndpointLeaveByDockerEPID(l.EndpointID); err != nil {
		log.Warningf("Leaving the endpoint failed: %s", err)
	}

	if err := plugins.DelLinkByName(plugins.Endpoint2IfName(l.EndpointID)); err != nil {
		log.Warningf("Error while deleting link: %s", err)
	}
	emptyResponse(w)
}
<commit_msg>Increased cilium-docker connection timeout with daemon<commit_after>package driver

import (
	"encoding/json"
	"fmt"
	"net"
	"net/http"
	"time"

	common "github.com/noironetworks/cilium-net/common"
	cnc "github.com/noironetworks/cilium-net/common/client"
	"github.com/noironetworks/cilium-net/common/plugins"
	"github.com/noironetworks/cilium-net/common/types"

	"github.com/codegangsta/cli"
	"github.com/docker/libnetwork/drivers/remote/api"
	lnTypes "github.com/docker/libnetwork/types"
	"github.com/gorilla/mux"
	l "github.com/op/go-logging"
	"github.com/vishvananda/netlink"
)

var log = l.MustGetLogger("cilium-net-client")

const (
	// ContainerInterfacePrefix is the container's internal interface name prefix.
	ContainerInterfacePrefix = "cilium"
)

// Driver interface that listens for docker requests.
type Driver interface {
	Listen(string) error
}

type driver struct {
	client      *cnc.Client
	nodeAddress net.IP
}

// NewDriver creates and returns a new Driver for the given ctx.
func NewDriver(ctx *cli.Context) (Driver, error) {
	var nodeAddress string
	c, err := cnc.NewDefaultClient()
	if err != nil {
		log.Fatalf("Error while starting cilium-client: %s", err)
	}

	for tries := 0; tries < 10; tries++ {
		if res, err := c.Ping(); err != nil {
			if tries == 9 {
				log.Fatalf("Unable to reach cilium daemon: %s", err)
			} else {
				log.Warningf("Waiting for cilium daemon to come up...")
			}
			time.Sleep(time.Duration(tries) * time.Second)
		} else {
			nodeAddress = res.NodeAddress
			log.Infof("Received node address from daemon: %s", nodeAddress)
			break
		}
	}

	d := &driver{
		client:      c,
		nodeAddress: net.ParseIP(nodeAddress),
	}

	log.Infof("New Cilium networking instance on node %s", nodeAddress)

	return d, nil
}

// Listen listens for docker requests on a particular set of endpoints on the given
// socket path.
func (driver *driver) Listen(socket string) error {
	router := mux.NewRouter()
	router.NotFoundHandler = http.HandlerFunc(notFound)

	handleMethod := func(method string, h http.HandlerFunc) {
		router.Methods("POST").Path(fmt.Sprintf("/%s", method)).HandlerFunc(h)
	}

	handleMethod("Plugin.Activate", driver.handshake)
	handleMethod("NetworkDriver.GetCapabilities", driver.capabilities)
	handleMethod("NetworkDriver.CreateNetwork", driver.createNetwork)
	handleMethod("NetworkDriver.DeleteNetwork", driver.deleteNetwork)
	handleMethod("NetworkDriver.CreateEndpoint", driver.createEndpoint)
	handleMethod("NetworkDriver.DeleteEndpoint", driver.deleteEndpoint)
	handleMethod("NetworkDriver.EndpointOperInfo", driver.infoEndpoint)
	handleMethod("NetworkDriver.Join", driver.joinEndpoint)
	handleMethod("NetworkDriver.Leave", driver.leaveEndpoint)
	handleMethod("IpamDriver.GetCapabilities", driver.ipamCapabilities)
	handleMethod("IpamDriver.GetDefaultAddressSpaces", driver.getDefaultAddressSpaces)
	handleMethod("IpamDriver.RequestPool", driver.requestPool)
	handleMethod("IpamDriver.ReleasePool", driver.releasePool)
	handleMethod("IpamDriver.RequestAddress", driver.requestAddress)
	handleMethod("IpamDriver.ReleaseAddress", driver.releaseAddress)
	var (
		listener net.Listener
		err      error
	)

	listener, err = net.Listen("unix", socket)
	if err != nil {
		return err
	}

	return http.Serve(listener, router)
}

func notFound(w http.ResponseWriter, r *http.Request) {
	log.Warningf("plugin Not found: [ %+v ]", r)
	http.NotFound(w, r)
}

func sendError(w http.ResponseWriter, msg string, code int) {
	log.Errorf("%d %s", code, msg)
	http.Error(w, msg, code)
}

func objectResponse(w http.ResponseWriter, obj interface{}) {
	if err := json.NewEncoder(w).Encode(obj); err != nil {
		sendError(w, "Could not JSON encode response", http.StatusInternalServerError)
		return
	}
}

func emptyResponse(w http.ResponseWriter) {
	json.NewEncoder(w).Encode(map[string]string{})
}

type handshakeResp struct {
	Implements []string
}

func (driver *driver) handshake(w http.ResponseWriter, r *http.Request) {
	err := json.NewEncoder(w).Encode(&handshakeResp{
		[]string{"NetworkDriver", "IpamDriver"},
	})
	if err != nil {
		log.Fatalf("handshake encode: %s", err)
		sendError(w, "encode error", http.StatusInternalServerError)
		return
	}
	log.Debug("Handshake completed")
}

func (driver *driver) capabilities(w http.ResponseWriter, r *http.Request) {
	err := json.NewEncoder(w).Encode(&api.GetCapabilityResponse{
		Scope: "local",
	})
	if err != nil {
		log.Fatalf("capabilities encode: %s", err)
		sendError(w, "encode error", http.StatusInternalServerError)
		return
	}
	log.Debug("NetworkDriver capabilities exchange complete")
}

func (driver *driver) createNetwork(w http.ResponseWriter, r *http.Request) {
	var create api.CreateNetworkRequest
	err := json.NewDecoder(r.Body).Decode(&create)
	if err != nil {
		sendError(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest)
		return
	}
	log.Debugf("Network Create Called: [ %+v ]", create)
	emptyResponse(w)
}

func (driver *driver) deleteNetwork(w http.ResponseWriter, r *http.Request) {
	var delete api.DeleteNetworkRequest
	if err := json.NewDecoder(r.Body).Decode(&delete); err != nil {
		sendError(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest)
		return
	}
	log.Debugf("Delete network request: %+v", &delete)
	emptyResponse(w)
}

// CreateEndpointRequest is the as api.CreateEndpointRequest but with
// json.RawMessage type for Options map
type CreateEndpointRequest struct {
	NetworkID  string
	EndpointID string
	Interface  api.EndpointInterface
	Options    map[string]json.RawMessage
}

func (driver *driver) createEndpoint(w http.ResponseWriter, r *http.Request) {
	var create CreateEndpointRequest
	if err := json.NewDecoder(r.Body).Decode(&create); err != nil {
		sendError(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest)
		return
	}
	log.Debugf("Create endpoint request: %+v", &create)

	endID := create.EndpointID
	containerAddress := create.Interface.AddressIPv6
	if containerAddress == "" {
		log.Warningf("No IPv6 address provided in CreateEndpoint request")
	}

	maps := make([]types.EPPortMap, 0, 32)

	for key, val := range create.Options {
		switch key {
		case "com.docker.network.portmap":
			var portmap []lnTypes.PortBinding
			if err := json.Unmarshal(val, &portmap); err != nil {
				sendError(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest)
				return
			}
			log.Debugf("PortBinding: %+v", &portmap)

			// FIXME: Host IP is ignored for now
			for _, m := range portmap {
				maps = append(maps, types.EPPortMap{
					From:  m.HostPort,
					To:    m.Port,
					Proto: uint8(m.Proto),
				})
			}

		case "com.docker.network.endpoint.exposedports":
			var tp []lnTypes.TransportPort
			if err := json.Unmarshal(val, &tp); err != nil {
				sendError(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest)
				return
			}
			log.Debugf("ExposedPorts: %+v", &tp)
			// TODO: handle exposed ports
		}
	}

	ep, err := driver.client.EndpointGetByDockerEPID(endID)
	if err != nil {
		sendError(w, fmt.Sprintf("Error retrieving endpoint %s", err), http.StatusBadRequest)
		return
	}
	if ep != nil {
		sendError(w, "Endpoint already exists", http.StatusBadRequest)
		return
	}

	ip, _, _ := net.ParseCIDR(containerAddress)

	endpoint := types.Endpoint{
		LXCIP:            ip,
		NodeIP:           driver.nodeAddress,
		DockerNetworkID:  create.NetworkID,
		DockerEndpointID: endID,
		PortMap:          maps,
	}
	endpoint.SetID()

	if err = driver.client.EndpointSave(endpoint); err != nil {
		sendError(w, fmt.Sprintf("Error retrieving endpoint %s", err), http.StatusBadRequest)
		return
	}

	log.Debugf("Created Endpoint: %+v", endpoint)

	log.Infof("New endpoint %s with IP %s", endID, containerAddress)

	respIface := &api.EndpointInterface{
		MacAddress: common.DefaultContainerMAC,
	}
	resp := &api.CreateEndpointResponse{
		Interface: respIface,
	}
	log.Debugf("Create endpoint response: %+v", resp)
	objectResponse(w, resp)
}

func (driver *driver) deleteEndpoint(w http.ResponseWriter, r *http.Request) {
	var del api.DeleteEndpointRequest
	if err := json.NewDecoder(r.Body).Decode(&del); err != nil {
		sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest)
		return
	}
	log.Debugf("Delete endpoint request: %+v", &del)

	if err := plugins.DelLinkByName(plugins.Endpoint2IfName(del.EndpointID)); err != nil {
		log.Warningf("Error while deleting link: %s", err)
	}

	emptyResponse(w)
}

func (driver *driver) infoEndpoint(w http.ResponseWriter, r *http.Request) {
	var info api.EndpointInfoRequest
	if err := json.NewDecoder(r.Body).Decode(&info); err != nil {
		sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest)
		return
	}
	log.Debugf("Endpoint info request: %+v", &info)
	objectResponse(w, &api.EndpointInfoResponse{Value: map[string]interface{}{}})
	log.Debugf("Endpoint info %s", info.EndpointID)
}

func (driver *driver) joinEndpoint(w http.ResponseWriter, r *http.Request) {
	var (
		j   api.JoinRequest
		err error
	)
	if err := json.NewDecoder(r.Body).Decode(&j); err != nil {
		sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest)
		return
	}
	log.Debugf("Join request: %+v", &j)

	ep, err := driver.client.EndpointGetByDockerEPID(j.EndpointID)
	if err != nil {
		sendError(w, fmt.Sprintf("Error retrieving endpoint %s", err), http.StatusBadRequest)
		return
	}
	if ep == nil {
		sendError(w, "Endpoint does not exist", http.StatusBadRequest)
		return
	}

	veth, _, tmpIfName, err := plugins.SetupVeth(j.EndpointID, 1450, ep)
	if err != nil {
		sendError(w, "Error while setting up veth pair: "+err.Error(), http.StatusBadRequest)
		return
	}
	defer func() {
		if err != nil {
			if err = netlink.LinkDel(veth); err != nil {
				log.Warningf("failed to clean up veth %q: %s", veth.Name, err)
			}
		}
	}()

	ifname := &api.InterfaceName{
		SrcName:   tmpIfName,
		DstPrefix: ContainerInterfacePrefix,
	}
	if err = driver.client.EndpointJoin(*ep); err != nil {
		log.Errorf("Joining endpoint failed: %s", err)
		sendError(w, "Unable to create BPF map: "+err.Error(), http.StatusInternalServerError)
	}

	rep, err := driver.client.GetIPAMConf(types.LibnetworkIPAMType, types.IPAMReq{})
	if err != nil {
		sendError(w, fmt.Sprintf("Could not get cilium IPAM configuration: %s", err), http.StatusBadRequest)
	}

	lnRoutes := []api.StaticRoute{}
	for _, route := range rep.IPAMConfig.IP6.Routes {
		nh := ""
		if route.IsL3() {
			nh = route.NextHop.String()
		}
		lnRoute := api.StaticRoute{
			Destination: route.Destination.String(),
			RouteType:   route.Type,
			NextHop:     nh,
		}
		lnRoutes = append(lnRoutes, lnRoute)
	}

	res := &api.JoinResponse{
		GatewayIPv6:           rep.IPAMConfig.IP6.Gateway.String(),
		InterfaceName:         ifname,
		StaticRoutes:          lnRoutes,
		DisableGatewayService: true,
	}

	log.Debugf("Join response: %+v", res)
	objectResponse(w, res)
}

func (driver *driver) leaveEndpoint(w http.ResponseWriter, r *http.Request) {
	var l api.LeaveRequest
	if err := json.NewDecoder(r.Body).Decode(&l); err != nil {
		sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest)
		return
	}
	log.Debugf("Leave request: %+v", &l)

	if err := driver.client.EndpointLeaveByDockerEPID(l.EndpointID); err != nil {
		log.Warningf("Leaving the endpoint failed: %s", err)
	}

	if err := plugins.DelLinkByName(plugins.Endpoint2IfName(l.EndpointID)); err != nil {
		log.Warningf("Error while deleting link: %s", err)
	}
	emptyResponse(w)
}
<|endoftext|>