diff --git a/README.md b/README.md index 4de47e5eb..5cce670fc 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,6 @@ The controllers are generally organized by API group: - `build.openshift.io` - OpenShift `Builds` and `BuildConfigs`. - `image.openshift.io` - `ImageStreams` and `Images`. - `project.openshift.io` - Projects, OpenShift's wrapper for `Namespaces`. -- `route.openshift.io` - OpenShift `Routes`, which provide similar capability to upstream `Ingress` - objects. - `template.openshift.io` - OpenShift `Templates` - a simple way to deploy applications. There are additional controllers which add OpenShift-specific capabilities to the cluster: diff --git a/cmd/openshift-controller-manager/main.go b/cmd/openshift-controller-manager/main.go index 4af17b764..17e251fe0 100644 --- a/cmd/openshift-controller-manager/main.go +++ b/cmd/openshift-controller-manager/main.go @@ -22,7 +22,6 @@ import ( "github.com/openshift/api/user" openshift_controller_manager "github.com/openshift/openshift-controller-manager/pkg/cmd/openshift-controller-manager" - route_controller_manager "github.com/openshift/openshift-controller-manager/pkg/cmd/route-controller-manager" "github.com/openshift/openshift-controller-manager/pkg/version" ) @@ -65,9 +64,5 @@ func NewOpenShiftControllerManagerCommand(stopCh <-chan struct{}) *cobra.Command } start := openshift_controller_manager.NewOpenShiftControllerManagerCommand("start", os.Stdout, os.Stderr, stopCh) cmd.AddCommand(start) - - routeStart := route_controller_manager.NewRouteControllerManagerCommand("route-controller-manager-start", os.Stdout, os.Stderr, stopCh) - cmd.AddCommand(routeStart) - return cmd } diff --git a/cmd/route-controller-manager/main.go b/cmd/route-controller-manager/main.go deleted file mode 100644 index 603da1163..000000000 --- a/cmd/route-controller-manager/main.go +++ /dev/null @@ -1,44 +0,0 @@ -package main - -import ( - "os" - "runtime" - - "github.com/openshift/library-go/pkg/serviceability" - "github.com/spf13/cobra" - genericapiserver "k8s.io/apiserver/pkg/server" - "k8s.io/component-base/cli" - - route_controller_manager "github.com/openshift/openshift-controller-manager/pkg/cmd/route-controller-manager" - "github.com/openshift/openshift-controller-manager/pkg/routeversion" -) - -func main() { - stopCh := genericapiserver.SetupSignalHandler() - - defer serviceability.BehaviorOnPanic(os.Getenv("OPENSHIFT_ON_PANIC"), routeversion.Get())() - defer serviceability.Profile(os.Getenv("OPENSHIFT_PROFILE")).Stop() - - if len(os.Getenv("GOMAXPROCS")) == 0 { - runtime.GOMAXPROCS(runtime.NumCPU()) - } - - command := NewRouteControllerManagerCommand(stopCh) - code := cli.Run(command) - os.Exit(code) -} - -func NewRouteControllerManagerCommand(stopCh <-chan struct{}) *cobra.Command { - cmd := &cobra.Command{ - Use: "route-controller-manager", - Short: "Command for additional management of ingress and Route resources", - Run: func(cmd *cobra.Command, args []string) { - cmd.Help() - os.Exit(1) - }, - } - start := route_controller_manager.NewRouteControllerManagerCommand("start", os.Stdout, os.Stderr, stopCh) - cmd.AddCommand(start) - - return cmd -} diff --git a/pkg/cmd/controller/config.go b/pkg/cmd/controller/config.go index d0aa7a2a5..113190b6e 100644 --- a/pkg/cmd/controller/config.go +++ b/pkg/cmd/controller/config.go @@ -34,11 +34,8 @@ const ( infraDeployerControllerServiceAccountName = "deployer-controller" infraImageTriggerControllerServiceAccountName = "image-trigger-controller" infraImageImportControllerServiceAccountName = "image-import-controller" - infraSDNControllerServiceAccountName = "sdn-controller" infraUnidlingControllerServiceAccountName = "unidling-controller" - infraServiceIngressIPControllerServiceAccountName = "service-ingress-ip-controller" infraDefaultRoleBindingsControllerServiceAccountName = "default-rolebindings-controller" - infraIngressToRouteControllerServiceAccountName = "ingress-to-route-controller" // template instance controller watches for TemplateInstance object creation // and instantiates templates as a result. diff --git a/pkg/cmd/controller/interfaces.go b/pkg/cmd/controller/interfaces.go index 64421443c..b9a0e32f9 100644 --- a/pkg/cmd/controller/interfaces.go +++ b/pkg/cmd/controller/interfaces.go @@ -29,8 +29,6 @@ import ( imageinformer "github.com/openshift/client-go/image/informers/externalversions" operatorclient "github.com/openshift/client-go/operator/clientset/versioned" operatorinformer "github.com/openshift/client-go/operator/informers/externalversions" - routeclient "github.com/openshift/client-go/route/clientset/versioned" - routeinformer "github.com/openshift/client-go/route/informers/externalversions" securityclient "github.com/openshift/client-go/security/clientset/versioned" templateclient "github.com/openshift/client-go/template/clientset/versioned" templateinformer "github.com/openshift/client-go/template/informers/externalversions" @@ -80,10 +78,6 @@ func NewControllerContext( if err != nil { return nil, err } - routerClient, err := routeclient.NewForConfig(clientConfig) - if err != nil { - return nil, err - } templateClient, err := templateclient.NewForConfig(clientConfig) if err != nil { return nil, err @@ -128,7 +122,6 @@ func NewControllerContext( ConfigInformers: configinformer.NewSharedInformerFactory(configClient, defaultInformerResyncPeriod), ImageInformers: imageinformer.NewSharedInformerFactory(imageClient, defaultInformerResyncPeriod), OperatorInformers: operatorinformer.NewSharedInformerFactory(operatorClient, defaultInformerResyncPeriod), - RouteInformers: routeinformer.NewSharedInformerFactory(routerClient, defaultInformerResyncPeriod), TemplateInformers: templateinformer.NewSharedInformerFactory(templateClient, defaultInformerResyncPeriod), Stop: ctx.Done(), Context: ctx, @@ -153,7 +146,6 @@ type ControllerContext struct { ControllerManagerKubeInformers informers.SharedInformerFactory TemplateInformers templateinformer.SharedInformerFactory - RouteInformers routeinformer.SharedInformerFactory AppsInformers appsinformer.SharedInformerFactory BuildInformers buildinformer.SharedInformerFactory @@ -185,7 +177,6 @@ func (c *ControllerContext) StartInformers(stopCh <-chan struct{}) { c.ImageInformers.Start(stopCh) c.TemplateInformers.Start(stopCh) - c.RouteInformers.Start(stopCh) c.OperatorInformers.Start(stopCh) c.informersStartedLock.Lock() diff --git a/pkg/cmd/controller/route/apiserver_authenticator.go b/pkg/cmd/controller/route/apiserver_authenticator.go deleted file mode 100644 index d34e4a83b..000000000 --- a/pkg/cmd/controller/route/apiserver_authenticator.go +++ /dev/null @@ -1,52 +0,0 @@ -package route - -import ( - "crypto/x509" - "time" - - "k8s.io/apiserver/pkg/authentication/authenticator" - "k8s.io/apiserver/pkg/authentication/group" - "k8s.io/apiserver/pkg/authentication/request/anonymous" - "k8s.io/apiserver/pkg/authentication/request/bearertoken" - "k8s.io/apiserver/pkg/authentication/request/union" - x509request "k8s.io/apiserver/pkg/authentication/request/x509" - "k8s.io/apiserver/pkg/authentication/token/cache" - webhooktoken "k8s.io/apiserver/plugin/pkg/authenticator/token/webhook" - authenticationv1client "k8s.io/client-go/kubernetes/typed/authentication/v1" -) - -// TODO this should really be removed in favor of the generic apiserver -// newRemoteAuthenticator creates an authenticator that checks the provided remote endpoint for tokens, allows any linked clientCAs to be checked, and caches -// responses as indicated. If no authentication is possible, the user will be system:anonymous. -func newRemoteAuthenticator(tokenReview authenticationv1client.AuthenticationV1Interface, clientCAs *x509.CertPool, cacheTTL time.Duration) (authenticator.Request, error) { - authenticators := []authenticator.Request{} - - // TODO audiences - TokenAccessReviewTimeout := 10 * time.Second - tokenAuthenticator, err := webhooktoken.NewFromInterface(tokenReview, nil, *webhooktoken.DefaultRetryBackoff(), TokenAccessReviewTimeout, webhooktoken.AuthenticatorMetrics{ - RecordRequestTotal: noopMetrics{}.RequestTotal, - RecordRequestLatency: noopMetrics{}.RequestLatency, - }) - if err != nil { - return nil, err - } - cachingTokenAuth := cache.New(tokenAuthenticator, false, cacheTTL, cacheTTL) - authenticators = append(authenticators, bearertoken.New(cachingTokenAuth)) - - // Client-cert auth - if clientCAs != nil { - opts := x509request.DefaultVerifyOptions() - opts.Roots = clientCAs - certauth := x509request.New(opts, x509request.CommonNameUserConversion) - authenticators = append(authenticators, certauth) - } - - // Anonymous requests will pass the token and cert checks without errors - // Bad tokens or bad certs will produce errors, in which case we should not continue to authenticate them as "system:anonymous" - return union.NewFailOnError( - // Add the "system:authenticated" group to users that pass token/cert authentication - group.NewAuthenticatedGroupAdder(union.New(authenticators...)), - // Fall back to the "system:anonymous" user - anonymous.NewAuthenticator(), - ), nil -} diff --git a/pkg/cmd/controller/route/apiserver_handlers.go b/pkg/cmd/controller/route/apiserver_handlers.go deleted file mode 100644 index 2c0f9c466..000000000 --- a/pkg/cmd/controller/route/apiserver_handlers.go +++ /dev/null @@ -1,26 +0,0 @@ -package route - -import ( - "context" - - "k8s.io/apimachinery/pkg/util/sets" - kauthorizer "k8s.io/apiserver/pkg/authorization/authorizer" -) - -type bypassAuthorizer struct { - paths sets.String - authorizer kauthorizer.Authorizer -} - -// newBypassAuthorizer creates an Authorizer that always allows the exact paths described, and delegates to the nested -// authorizer for everything else. -func newBypassAuthorizer(auth kauthorizer.Authorizer, paths ...string) kauthorizer.Authorizer { - return bypassAuthorizer{paths: sets.NewString(paths...), authorizer: auth} -} - -func (a bypassAuthorizer) Authorize(ctx context.Context, attributes kauthorizer.Attributes) (allowed kauthorizer.Decision, reason string, err error) { - if !attributes.IsResourceRequest() && a.paths.Has(attributes.GetPath()) { - return kauthorizer.DecisionAllow, "always allowed", nil - } - return a.authorizer.Authorize(ctx, attributes) -} diff --git a/pkg/cmd/controller/route/config.go b/pkg/cmd/controller/route/config.go deleted file mode 100644 index fa87d5ba4..000000000 --- a/pkg/cmd/controller/route/config.go +++ /dev/null @@ -1,13 +0,0 @@ -package route - -var ControllerManagerInitialization = map[string]InitFunc{ - "openshift.io/ingress-ip": RunIngressIPController, - "openshift.io/ingress-to-route": RunIngressToRouteController, -} - -const ( - infraServiceIngressIPControllerServiceAccountName = "service-ingress-ip-controller" - InfraIngressToRouteControllerServiceAccountName = "ingress-to-route-controller" - - defaultOpenShiftInfraNamespace = "openshift-infra" -) diff --git a/pkg/cmd/controller/route/ingress.go b/pkg/cmd/controller/route/ingress.go deleted file mode 100644 index 718996ee6..000000000 --- a/pkg/cmd/controller/route/ingress.go +++ /dev/null @@ -1,40 +0,0 @@ -package route - -import ( - coreclient "k8s.io/client-go/kubernetes/typed/core/v1" - networkingv1 "k8s.io/client-go/kubernetes/typed/networking/v1" - - routeclient "github.com/openshift/client-go/route/clientset/versioned/typed/route/v1" - "github.com/openshift/openshift-controller-manager/pkg/route/ingress" -) - -func RunIngressToRouteController(ctx *ControllerContext) (bool, error) { - clientConfig := ctx.ClientBuilder.ConfigOrDie(InfraIngressToRouteControllerServiceAccountName) - coreClient, err := coreclient.NewForConfig(clientConfig) - if err != nil { - return false, err - } - routeClient, err := routeclient.NewForConfig(clientConfig) - if err != nil { - return false, err - } - networkingClient, err := networkingv1.NewForConfig(clientConfig) - if err != nil { - return false, err - } - - controller := ingress.NewController( - coreClient, - routeClient, - networkingClient, - ctx.KubernetesInformers.Networking().V1().Ingresses(), - ctx.KubernetesInformers.Networking().V1().IngressClasses(), - ctx.KubernetesInformers.Core().V1().Secrets(), - ctx.KubernetesInformers.Core().V1().Services(), - ctx.RouteInformers.Route().V1().Routes(), - ) - - go controller.Run(5, ctx.Stop) - - return true, nil -} diff --git a/pkg/cmd/controller/route/interfaces.go b/pkg/cmd/controller/route/interfaces.go deleted file mode 100644 index db45783d1..000000000 --- a/pkg/cmd/controller/route/interfaces.go +++ /dev/null @@ -1,219 +0,0 @@ -package route - -import ( - "context" - "sync" - "time" - - "k8s.io/klog/v2" - - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/wait" - cacheddiscovery "k8s.io/client-go/discovery/cached" - "k8s.io/client-go/informers" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - "k8s.io/client-go/restmapper" - "k8s.io/controller-manager/app" - "k8s.io/controller-manager/pkg/clientbuilder" - - openshiftcontrolplanev1 "github.com/openshift/api/openshiftcontrolplane/v1" - configclient "github.com/openshift/client-go/config/clientset/versioned" - operatorclient "github.com/openshift/client-go/operator/clientset/versioned" - operatorinformer "github.com/openshift/client-go/operator/informers/externalversions" - routeclient "github.com/openshift/client-go/route/clientset/versioned" - routeinformer "github.com/openshift/client-go/route/informers/externalversions" -) - -type ControllerClientBuilder interface { - clientbuilder.ControllerClientBuilder - - OpenshiftConfigClient(name string) (configclient.Interface, error) - OpenshiftConfigClientOrDie(name string) configclient.Interface - - OpenshiftOperatorClient(name string) (operatorclient.Interface, error) - OpenshiftOperatorClientOrDie(name string) operatorclient.Interface -} - -type ControllerContext struct { - // TODO: Make this minimal config instead of passing entire controller manager config - OpenshiftControllerConfig openshiftcontrolplanev1.OpenShiftControllerManagerConfig - - // ClientBuilder will provide a client for this controller to use - ClientBuilder ControllerClientBuilder - // HighRateLimitClientBuilder will provide a client for this controller utilizing a higher rate limit. - // This will have a rate limit of at least 100 QPS, with a burst up to 200 QPS. - HighRateLimitClientBuilder ControllerClientBuilder - - KubernetesInformers informers.SharedInformerFactory - OpenshiftConfigKubernetesInformers informers.SharedInformerFactory - - RouteInformers routeinformer.SharedInformerFactory - OperatorInformers operatorinformer.SharedInformerFactory - - RestMapper meta.RESTMapper - - // Stop is the stop channel - Stop <-chan struct{} - Context context.Context - - informersStartedLock sync.Mutex - informersStartedClosed bool - // InformersStarted is closed after all of the controllers have been initialized and are running. After this point it is safe, - // for an individual controller to start the shared informers. Before it is closed, they should not. - InformersStarted chan struct{} -} - -type RouteControllerClientBuilder struct { - clientbuilder.ControllerClientBuilder -} - -// OpenshiftConfigClient provides a REST client for the build API. -// If the client cannot be created because of configuration error, this function -// will error. -func (b RouteControllerClientBuilder) OpenshiftConfigClient(name string) (configclient.Interface, error) { - clientConfig, err := b.Config(name) - if err != nil { - return nil, err - } - return configclient.NewForConfig(nonProtobufConfig(clientConfig)) -} - -// RouteControllerClientBuilder provides a REST client for the build API. -// If the client cannot be created because of configuration error, this function -// will panic. -func (b RouteControllerClientBuilder) OpenshiftConfigClientOrDie(name string) configclient.Interface { - client, err := b.OpenshiftConfigClient(name) - if err != nil { - klog.Fatal(err) - } - return client -} - -func (b RouteControllerClientBuilder) OpenshiftOperatorClient(name string) (operatorclient.Interface, error) { - clientConfig, err := b.Config(name) - if err != nil { - return nil, err - } - return operatorclient.NewForConfig(clientConfig) -} - -func (b RouteControllerClientBuilder) OpenshiftOperatorClientOrDie(name string) operatorclient.Interface { - client, err := b.OpenshiftOperatorClient(name) - if err != nil { - klog.Fatal(err) - } - return client -} - -func (c *ControllerContext) IsControllerEnabled(name string) bool { - return app.IsControllerEnabled(name, sets.String{}, c.OpenshiftControllerConfig.Controllers) -} - -func (c *ControllerContext) StartInformers(stopCh <-chan struct{}) { - c.KubernetesInformers.Start(stopCh) - c.OpenshiftConfigKubernetesInformers.Start(stopCh) - - c.RouteInformers.Start(stopCh) - c.OperatorInformers.Start(stopCh) - - c.informersStartedLock.Lock() - defer c.informersStartedLock.Unlock() - if !c.informersStartedClosed { - close(c.InformersStarted) - c.informersStartedClosed = true - } -} - -// InitFunc is used to launch a particular controller. It may run additional "should I activate checks". -// Any error returned will cause the controller process to `Fatal` -// The bool indicates whether the controller was enabled. -type InitFunc func(ctx *ControllerContext) (bool, error) - -func NewControllerContext( - ctx context.Context, - config openshiftcontrolplanev1.OpenShiftControllerManagerConfig, - inClientConfig *rest.Config, -) (*ControllerContext, error) { - - const defaultInformerResyncPeriod = 10 * time.Minute - kubeClient, err := kubernetes.NewForConfig(inClientConfig) - if err != nil { - return nil, err - } - - // copy to avoid messing with original - clientConfig := rest.CopyConfig(inClientConfig) - // divide up the QPS since it re-used separately for every client - // TODO, eventually make this configurable individually in some way. - if clientConfig.QPS > 0 { - clientConfig.QPS = clientConfig.QPS/10 + 1 - } - if clientConfig.Burst > 0 { - clientConfig.Burst = clientConfig.Burst/10 + 1 - } - - discoveryClient := cacheddiscovery.NewMemCacheClient(kubeClient.Discovery()) - dynamicRestMapper := restmapper.NewDeferredDiscoveryRESTMapper(discoveryClient) - dynamicRestMapper.Reset() - go wait.Until(dynamicRestMapper.Reset, 30*time.Second, ctx.Done()) - - routerClient, err := routeclient.NewForConfig(clientConfig) - if err != nil { - return nil, err - } - - operatorClient, err := operatorclient.NewForConfig(clientConfig) - if err != nil { - return nil, err - } - - // Create a new clientConfig for high rate limit workloads. - // Increase kube QPS to at least 100 QPS, burst to at least 200 QPS. - highRateLimitClientConfig := rest.CopyConfig(inClientConfig) - if highRateLimitClientConfig.QPS < 100 { - highRateLimitClientConfig.QPS = 100 - } - if highRateLimitClientConfig.Burst < 200 { - highRateLimitClientConfig.Burst = 200 - } - - routeControllerContext := &ControllerContext{ - OpenshiftControllerConfig: config, - - // k8s 1.21 rebase - SAControllerClientBuilder replaced with NewDynamicClientBuilder - // See https://github.com/kubernetes/kubernetes/pull/99291 - ClientBuilder: RouteControllerClientBuilder{ - ControllerClientBuilder: clientbuilder.NewDynamicClientBuilder( - rest.AnonymousClientConfig(clientConfig), - kubeClient.CoreV1(), - defaultOpenShiftInfraNamespace), - }, - HighRateLimitClientBuilder: RouteControllerClientBuilder{ - ControllerClientBuilder: clientbuilder.NewDynamicClientBuilder( - rest.AnonymousClientConfig(highRateLimitClientConfig), - kubeClient.CoreV1(), - defaultOpenShiftInfraNamespace), - }, - KubernetesInformers: informers.NewSharedInformerFactory(kubeClient, defaultInformerResyncPeriod), - OpenshiftConfigKubernetesInformers: informers.NewSharedInformerFactoryWithOptions(kubeClient, defaultInformerResyncPeriod, informers.WithNamespace("openshift-config")), - OperatorInformers: operatorinformer.NewSharedInformerFactory(operatorClient, defaultInformerResyncPeriod), - RouteInformers: routeinformer.NewSharedInformerFactory(routerClient, defaultInformerResyncPeriod), - Stop: ctx.Done(), - Context: ctx, - InformersStarted: make(chan struct{}), - RestMapper: dynamicRestMapper, - } - - return routeControllerContext, nil -} - -// nonProtobufConfig returns a copy of inConfig that doesn't force the use of protobufs, -// for working with CRD-based APIs. -func nonProtobufConfig(inConfig *rest.Config) *rest.Config { - npConfig := rest.CopyConfig(inConfig) - npConfig.ContentConfig.AcceptContentTypes = "application/json" - npConfig.ContentConfig.ContentType = "application/json" - return npConfig -} diff --git a/pkg/cmd/controller/route/metrics.go b/pkg/cmd/controller/route/metrics.go deleted file mode 100644 index 20d244f6f..000000000 --- a/pkg/cmd/controller/route/metrics.go +++ /dev/null @@ -1,19 +0,0 @@ -package route - -import ( - "context" -) - -// AuthenticatorMetrics specifies a set of methods that are used to register various metrics -type AuthenticatorMetrics struct { - // RecordRequestTotal increments the total number of requests for webhooks - RecordRequestTotal func(ctx context.Context, code string) - - // RecordRequestLatency measures request latency in seconds for webhooks. Broken down by status code. - RecordRequestLatency func(ctx context.Context, code string, latency float64) -} - -type noopMetrics struct{} - -func (noopMetrics) RequestTotal(context.Context, string) {} -func (noopMetrics) RequestLatency(context.Context, string, float64) {} diff --git a/pkg/cmd/controller/route/net.go b/pkg/cmd/controller/route/net.go deleted file mode 100644 index 694cccb41..000000000 --- a/pkg/cmd/controller/route/net.go +++ /dev/null @@ -1,106 +0,0 @@ -package route - -import ( - "crypto/tls" - "net" - "net/http" - "strings" - "time" -) - -// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted -// connections. It's used by ListenAndServe and ListenAndServeTLS so -// dead TCP connections (e.g. closing laptop mid-download) eventually -// go away. -type tcpKeepAliveListener struct { - *net.TCPListener -} - -func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { - tc, err := ln.AcceptTCP() - if err != nil { - return - } - tc.SetKeepAlive(true) - tc.SetKeepAlivePeriod(3 * time.Minute) - return tc, nil -} - -// ListenAndServeTLS starts a server that listens on the provided TCP mode (as supported -// by net.Listen). -func ListenAndServeTLS(srv *http.Server, network string, certFile, keyFile string) error { - addr := srv.Addr - if addr == "" { - addr = ":https" - } - config := &tls.Config{} - if srv.TLSConfig != nil { - config = srv.TLSConfig - } - if config.NextProtos == nil { - config.NextProtos = []string{"http/1.1"} - } - - var err error - config.Certificates = make([]tls.Certificate, 1) - config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile) - if err != nil { - return err - } - - ln, err := net.Listen(network, addr) - if err != nil { - return err - } - - tlsListener := tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, config) - return srv.Serve(tlsListener) -} - -// GetCertificateFunc returns a function that can be used in tls.Config#GetCertificate -// Returns nil if len(certs) == 0 -func GetCertificateFunc(certs map[string]*tls.Certificate) func(*tls.ClientHelloInfo) (*tls.Certificate, error) { - if len(certs) == 0 { - return nil - } - // Replica of tls.Config#getCertificate logic - return func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { - if clientHello == nil { - return nil, nil - } - - name := clientHello.ServerName - name = strings.ToLower(name) - name = strings.TrimRight(name, ".") - for _, candidate := range HostnameMatchSpecCandidates(name) { - if cert, ok := certs[candidate]; ok { - return cert, nil - } - } - return nil, nil - } -} - -// HostnameMatchSpecCandidates returns a list of match specs that would match the provided hostname -// Returns nil if len(hostname) == 0 -func HostnameMatchSpecCandidates(hostname string) []string { - if len(hostname) == 0 { - return nil - } - - // Exact match has priority - candidates := []string{hostname} - - // Replace successive labels in the name with wildcards, to require an exact match on number of - // path segments, because certificates cannot wildcard multiple levels of subdomains - // - // This is primarily to be consistent with tls.Config#getCertificate implementation - // - // It using a cert signed for *.foo.example.com and *.bar.example.com by specifying the name *.*.example.com - labels := strings.Split(hostname, ".") - for i := range labels { - labels[i] = "*" - candidates = append(candidates, strings.Join(labels, ".")) - } - return candidates -} diff --git a/pkg/cmd/controller/route/network.go b/pkg/cmd/controller/route/network.go deleted file mode 100644 index 7442138c0..000000000 --- a/pkg/cmd/controller/route/network.go +++ /dev/null @@ -1,38 +0,0 @@ -package route - -import ( - "fmt" - "net" - "time" - - "github.com/openshift/openshift-controller-manager/pkg/route/ingressip" -) - -func RunIngressIPController(ctx *ControllerContext) (bool, error) { - // TODO configurable? - resyncPeriod := 10 * time.Minute - - if len(ctx.OpenshiftControllerConfig.Ingress.IngressIPNetworkCIDR) == 0 { - return true, nil - } - - _, ipNet, err := net.ParseCIDR(ctx.OpenshiftControllerConfig.Ingress.IngressIPNetworkCIDR) - if err != nil { - return false, fmt.Errorf("unable to start ingress IP controller: %v", err) - } - - if ipNet.IP.IsUnspecified() { - // TODO: Is this an error? - return true, nil - } - - ingressIPController := ingressip.NewIngressIPController( - ctx.KubernetesInformers.Core().V1().Services().Informer(), - ctx.ClientBuilder.ClientOrDie(infraServiceIngressIPControllerServiceAccountName), - ipNet, - resyncPeriod, - ) - go ingressIPController.Run(ctx.Stop) - - return true, nil -} diff --git a/pkg/cmd/controller/route/standalone_apiserver.go b/pkg/cmd/controller/route/standalone_apiserver.go deleted file mode 100644 index 3f494ee24..000000000 --- a/pkg/cmd/controller/route/standalone_apiserver.go +++ /dev/null @@ -1,158 +0,0 @@ -package route - -import ( - "crypto/tls" - "crypto/x509" - "net/http" - "time" - - "k8s.io/client-go/util/cert" - - "k8s.io/klog/v2" - - "k8s.io/apimachinery/pkg/runtime/serializer" - utilwait "k8s.io/apimachinery/pkg/util/wait" - apifilters "k8s.io/apiserver/pkg/endpoints/filters" - apiserver "k8s.io/apiserver/pkg/server" - apiserverfilters "k8s.io/apiserver/pkg/server/filters" - "k8s.io/apiserver/pkg/server/healthz" - genericmux "k8s.io/apiserver/pkg/server/mux" - genericroutes "k8s.io/apiserver/pkg/server/routes" - authzwebhook "k8s.io/apiserver/plugin/pkg/authorizer/webhook" - clientgoclientset "k8s.io/client-go/kubernetes" - "k8s.io/kubernetes/pkg/api/legacyscheme" - - configv1 "github.com/openshift/api/config/v1" - "github.com/openshift/library-go/pkg/crypto" -) - -// TODO make this an actual API server built on the genericapiserver -func RunControllerServer(servingInfo configv1.HTTPServingInfo, kubeExternal clientgoclientset.Interface) error { - clientCAs, err := getClientCertCAPool(servingInfo) - if err != nil { - return err - } - - mux := genericmux.NewPathRecorderMux("master-healthz") - - healthz.InstallHandler(mux, healthz.PingHealthz, healthz.LogHealthz) - initReadinessCheckRoute(mux, "/healthz/ready", func() bool { return true }) - genericroutes.Profiling{}.Install(mux) - genericroutes.MetricsWithReset{}.Install(mux) - - // TODO: replace me with a service account for controller manager - tokenReview := kubeExternal.AuthenticationV1() - authn, err := newRemoteAuthenticator(tokenReview, clientCAs, 5*time.Minute) - if err != nil { - return err - } - sarClient := kubeExternal.AuthorizationV1() - remoteAuthz, err := authzwebhook.NewFromInterface(sarClient, 5*time.Minute, 5*time.Minute, *authzwebhook.DefaultRetryBackoff(), authzwebhook.AuthorizerMetrics{ - RecordRequestTotal: noopMetrics{}.RequestTotal, - RecordRequestLatency: noopMetrics{}.RequestLatency, - }) - if err != nil { - return err - } - - // requestInfoFactory for controllers only needs to be able to handle non-API endpoints - requestInfoResolver := apiserver.NewRequestInfoResolver(&apiserver.Config{}) - - // we use direct bypass to allow readiness and health to work regardless of the master health - authz := newBypassAuthorizer(remoteAuthz, "/healthz", "/healthz/ready") - handler := apifilters.WithAuthorization(mux, authz, legacyscheme.Codecs) - // TODO need audiences - - //handler = apifilters.WithAuthentication(handler, authn, apifilters.Unauthorized(legacyscheme.Codecs, false), nil) - handler = apifilters.WithAuthentication(handler, authn, apifilters.Unauthorized(serializer.WithoutConversionCodecFactory{CodecFactory: legacyscheme.Codecs}), nil) - handler = apiserverfilters.WithPanicRecovery(handler, requestInfoResolver) - handler = apifilters.WithRequestInfo(handler, requestInfoResolver) - - return serveControllers(servingInfo, handler, clientCAs) -} - -// initReadinessCheckRoute initializes an HTTP endpoint for readiness checking -// TODO this looks pointless -func initReadinessCheckRoute(mux *genericmux.PathRecorderMux, path string, readyFunc func() bool) { - mux.HandleFunc(path, func(w http.ResponseWriter, req *http.Request) { - if readyFunc() { - w.WriteHeader(http.StatusOK) - w.Write([]byte("ok")) - - } else { - w.WriteHeader(http.StatusServiceUnavailable) - } - }) -} - -// serve starts serving the provided http.Handler using security settings derived from the MasterConfig -func serveControllers(servingInfo configv1.HTTPServingInfo, handler http.Handler, clientCAs *x509.CertPool) error { - timeout := servingInfo.RequestTimeoutSeconds - if timeout == -1 { - timeout = 0 - } - - server := &http.Server{ - Addr: servingInfo.BindAddress, - Handler: handler, - ReadTimeout: time.Duration(timeout) * time.Second, - WriteTimeout: time.Duration(timeout) * time.Second, - MaxHeaderBytes: 1 << 20, - } - - go utilwait.Forever(func() { - klog.Infof("Started health checks at %s", servingInfo.BindAddress) - - extraCerts, err := getNamedCertificateMap(servingInfo.NamedCertificates) - if err != nil { - klog.Fatal(err) - } - server.TLSConfig = crypto.SecureTLSConfig(&tls.Config{ - // Populate PeerCertificates in requests, but don't reject connections without certificates - // This allows certificates to be validated by authenticators, while still allowing other auth types - ClientAuth: tls.RequestClientCert, - ClientCAs: clientCAs, - // Set SNI certificate func - GetCertificate: GetCertificateFunc(extraCerts), - MinVersion: crypto.TLSVersionOrDie(servingInfo.MinTLSVersion), - CipherSuites: crypto.CipherSuitesOrDie(servingInfo.CipherSuites), - }) - klog.Fatal(ListenAndServeTLS(server, servingInfo.BindNetwork, servingInfo.CertFile, servingInfo.KeyFile)) - }, 0) - - return nil -} - -func getClientCertCAPool(servingInfo configv1.HTTPServingInfo) (*x509.CertPool, error) { - roots := x509.NewCertPool() - // Add CAs for API - certs, err := cert.CertsFromFile(servingInfo.ClientCA) - if err != nil { - return nil, err - } - for _, root := range certs { - roots.AddCert(root) - } - - return roots, nil -} - -// getNamedCertificateMap returns a map of strings to *tls.Certificate, suitable for use in tls.Config#NamedCertificates -// Returns an error if any of the certs cannot be loaded, or do not match the configured name -// Returns nil if len(namedCertificates) == 0 -func getNamedCertificateMap(namedCertificates []configv1.NamedCertificate) (map[string]*tls.Certificate, error) { - if len(namedCertificates) == 0 { - return nil, nil - } - namedCerts := map[string]*tls.Certificate{} - for _, namedCertificate := range namedCertificates { - cert, err := tls.LoadX509KeyPair(namedCertificate.CertFile, namedCertificate.KeyFile) - if err != nil { - return nil, err - } - for _, name := range namedCertificate.Names { - namedCerts[name] = &cert - } - } - return namedCerts, nil -} diff --git a/pkg/cmd/route-controller-manager/cmd.go b/pkg/cmd/route-controller-manager/cmd.go deleted file mode 100644 index 4e203d667..000000000 --- a/pkg/cmd/route-controller-manager/cmd.go +++ /dev/null @@ -1,134 +0,0 @@ -package openshift_controller_manager - -import ( - "errors" - "fmt" - "io" - "io/ioutil" - "os" - "path" - - "github.com/coreos/go-systemd/daemon" - "github.com/spf13/cobra" - - kerrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/serializer" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/client-go/tools/clientcmd/api" - "k8s.io/klog/v2" - kcmdutil "k8s.io/kubectl/pkg/cmd/util" - "k8s.io/kubectl/pkg/util/templates" - - configv1 "github.com/openshift/api/config/v1" - openshiftcontrolplanev1 "github.com/openshift/api/openshiftcontrolplane/v1" - "github.com/openshift/library-go/pkg/config/helpers" - "github.com/openshift/library-go/pkg/serviceability" -) - -type RouteControllerManager struct { - ConfigFilePath string - Output io.Writer -} - -var longDescription = templates.LongDesc(` - Start the additional Ingress and Route controllers`) - -func NewRouteControllerManagerCommand(name string, out, errout io.Writer, stopCh <-chan struct{}) *cobra.Command { - options := &RouteControllerManager{Output: out} - - cmd := &cobra.Command{ - Use: name, - Short: "Start the additional Ingress and Route controllers", - Long: longDescription, - Run: func(c *cobra.Command, args []string) { - kcmdutil.CheckErr(options.Validate()) - - serviceability.StartProfiler() - - if err := options.StartControllerManager(stopCh); err != nil { - if kerrors.IsInvalid(err) { - if details := err.(*kerrors.StatusError).ErrStatus.Details; details != nil { - fmt.Fprintf(errout, "Invalid %s %s\n", details.Kind, details.Name) - for _, cause := range details.Causes { - fmt.Fprintf(errout, " %s: %s\n", cause.Field, cause.Message) - } - os.Exit(255) - } - } - klog.Fatal(fmt.Sprintf("fatal error occurred: %v", err)) - } - }, - } - - flags := cmd.Flags() - // This command only supports reading from config - flags.StringVar(&options.ConfigFilePath, "config", options.ConfigFilePath, "Location of the master configuration file to run from.") - cmd.MarkFlagFilename("config", "yaml", "yml") - cmd.MarkFlagRequired("config") - - return cmd -} - -func (o *RouteControllerManager) Validate() error { - if len(o.ConfigFilePath) == 0 { - return errors.New("--config is required for this command") - } - - return nil -} - -// StartControllerManager calls RunControllerManager and then waits forever -func (o *RouteControllerManager) StartControllerManager(stopCh <-chan struct{}) error { - if err := o.RunControllerManager(); err != nil { - return err - } - - go daemon.SdNotify(false, "READY=1") - select { - case <-stopCh: - klog.Infof("Route Controller Manager received stop signal. exiting.") - return nil - } -} - -// RunControllerManager takes the options and starts the controllers -func (o *RouteControllerManager) RunControllerManager() error { - // try to decode into our new types first. right now there is no validation, no file path resolution. this unsticks the operator to start. - // TODO add those things - configContent, err := ioutil.ReadFile(o.ConfigFilePath) - if err != nil { - return err - } - scheme := runtime.NewScheme() - utilruntime.Must(openshiftcontrolplanev1.Install(scheme)) - codecs := serializer.NewCodecFactory(scheme) - obj, err := runtime.Decode(codecs.UniversalDecoder(openshiftcontrolplanev1.GroupVersion, configv1.GroupVersion), configContent) - if err != nil { - return err - } - - // Resolve relative to CWD - absoluteConfigFile, err := api.MakeAbs(o.ConfigFilePath, "") - if err != nil { - return err - } - configFileLocation := path.Dir(absoluteConfigFile) - - config := obj.(*openshiftcontrolplanev1.OpenShiftControllerManagerConfig) - /// this isn't allowed to be nil when by itself. - // TODO remove this when the old path is gone. - if config.ServingInfo == nil { - config.ServingInfo = &configv1.HTTPServingInfo{} - } - if err := helpers.ResolvePaths(getOpenShiftControllerConfigFileReferences(config), configFileLocation); err != nil { - return err - } - setRecommendedOpenShiftControllerConfigDefaults(config) - - clientConfig, err := helpers.GetKubeClientConfig(config.KubeClientConfig) - if err != nil { - return err - } - return RunRouteControllerManager(config, clientConfig) -} diff --git a/pkg/cmd/route-controller-manager/controller_manager.go b/pkg/cmd/route-controller-manager/controller_manager.go deleted file mode 100644 index f9e1de748..000000000 --- a/pkg/cmd/route-controller-manager/controller_manager.go +++ /dev/null @@ -1,36 +0,0 @@ -package openshift_controller_manager - -import ( - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - "k8s.io/klog/v2" - - openshiftcontrolplanev1 "github.com/openshift/api/openshiftcontrolplane/v1" - "github.com/openshift/library-go/pkg/serviceability" - - routecontrollers "github.com/openshift/openshift-controller-manager/pkg/cmd/controller/route" - origincontrollers "github.com/openshift/openshift-controller-manager/pkg/cmd/routecontroller" - "github.com/openshift/openshift-controller-manager/pkg/routeversion" -) - -func RunRouteControllerManager(config *openshiftcontrolplanev1.OpenShiftControllerManagerConfig, clientConfig *rest.Config) error { - serviceability.InitLogrusFromKlog() - kubeClient, err := kubernetes.NewForConfig(clientConfig) - if err != nil { - return err - } - - // only serve if we have serving information. - if config.ServingInfo != nil { - klog.Infof("Starting controllers on %s (%s)", config.ServingInfo.BindAddress, routeversion.Get().String()) - - if err := routecontrollers.RunControllerServer(*config.ServingInfo, kubeClient); err != nil { - return err - } - } - _, err = origincontrollers.RunRouteControllerManager(config, kubeClient, clientConfig) - if err != nil { - return err - } - return nil -} diff --git a/pkg/cmd/route-controller-manager/openshiftcontrolplane_default.go b/pkg/cmd/route-controller-manager/openshiftcontrolplane_default.go deleted file mode 100644 index 02f958049..000000000 --- a/pkg/cmd/route-controller-manager/openshiftcontrolplane_default.go +++ /dev/null @@ -1,47 +0,0 @@ -package openshift_controller_manager - -import ( - leaderelectionconverter "github.com/openshift/library-go/pkg/config/leaderelection" - "time" - - openshiftcontrolplanev1 "github.com/openshift/api/openshiftcontrolplane/v1" - "github.com/openshift/library-go/pkg/config/configdefaults" - "github.com/openshift/library-go/pkg/config/helpers" -) - -func setRecommendedOpenShiftControllerConfigDefaults(config *openshiftcontrolplanev1.OpenShiftControllerManagerConfig) { - configdefaults.SetRecommendedHTTPServingInfoDefaults(config.ServingInfo) - configdefaults.SetRecommendedKubeClientConfigDefaults(&config.KubeClientConfig) - config.LeaderElection = leaderelectionconverter.LeaderElectionDefaulting(config.LeaderElection, "kube-system", "openshift-route-controllers") - - configdefaults.DefaultStringSlice(&config.Controllers, []string{"*"}) - - configdefaults.DefaultString(&config.SecurityAllocator.UIDAllocatorRange, "1000000000-1999999999/10000") - configdefaults.DefaultString(&config.SecurityAllocator.MCSAllocatorRange, "s0:/2") - if config.SecurityAllocator.MCSLabelsPerProject == 0 { - config.SecurityAllocator.MCSLabelsPerProject = 5 - } - - if config.ResourceQuota.MinResyncPeriod.Duration == 0 { - config.ResourceQuota.MinResyncPeriod.Duration = 5 * time.Minute - } - if config.ResourceQuota.SyncPeriod.Duration == 0 { - config.ResourceQuota.SyncPeriod.Duration = 12 * time.Hour - } - if config.ResourceQuota.ConcurrentSyncs == 0 { - config.ResourceQuota.ConcurrentSyncs = 5 - } -} - -func getOpenShiftControllerConfigFileReferences(config *openshiftcontrolplanev1.OpenShiftControllerManagerConfig) []*string { - if config == nil { - return []*string{} - } - - refs := []*string{} - - refs = append(refs, helpers.GetHTTPServingInfoFileReferences(config.ServingInfo)...) - refs = append(refs, helpers.GetKubeClientConfigFileReferences(&config.KubeClientConfig)...) - - return refs -} diff --git a/pkg/cmd/routecontroller/route.go b/pkg/cmd/routecontroller/route.go deleted file mode 100644 index 8042774ee..000000000 --- a/pkg/cmd/routecontroller/route.go +++ /dev/null @@ -1,94 +0,0 @@ -package routecontroller - -import ( - "context" - "os" - - v1 "k8s.io/api/core/v1" - "k8s.io/client-go/kubernetes" - v1core "k8s.io/client-go/kubernetes/typed/core/v1" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/leaderelection" - "k8s.io/client-go/tools/leaderelection/resourcelock" - "k8s.io/client-go/tools/record" - "k8s.io/klog/v2" - "k8s.io/kubernetes/pkg/api/legacyscheme" - - openshiftcontrolplanev1 "github.com/openshift/api/openshiftcontrolplane/v1" - - routecontrollers "github.com/openshift/openshift-controller-manager/pkg/cmd/controller/route" -) - -func RunRouteControllerManager(config *openshiftcontrolplanev1.OpenShiftControllerManagerConfig, kubeClient kubernetes.Interface, clientConfig *rest.Config) (bool, error) { - routeControllerManager := func(cntx context.Context) { - // Start Route Controllers - // TODO: This can be split further - routeControllerContext, err := routecontrollers.NewControllerContext(cntx, *config, clientConfig) - if err != nil { - klog.Fatal(err) - } - if err := startControllers(routeControllerContext); err != nil { - klog.Fatal(err) - } - routeControllerContext.StartInformers(cntx.Done()) - } - eventBroadcaster := record.NewBroadcaster() - eventBroadcaster.StartLogging(klog.Infof) - eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: kubeClient.CoreV1().Events("")}) - eventRecorder := eventBroadcaster.NewRecorder(legacyscheme.Scheme, v1.EventSource{Component: "route-controller-manager"}) - id, err := os.Hostname() - if err != nil { - return false, err - } - // Create a new lease for the route controller manager - rl, err := resourcelock.New( - "leases", - "openshift-route-controller-manager", // TODO: This namespace needs to be created by ocm for now. - "openshift-route-controllers", - kubeClient.CoreV1(), - kubeClient.CoordinationV1(), - resourcelock.ResourceLockConfig{ - Identity: id, - EventRecorder: eventRecorder, - }) - if err != nil { - return false, err - } - go leaderelection.RunOrDie(context.Background(), - leaderelection.LeaderElectionConfig{ - Lock: rl, - ReleaseOnCancel: true, - LeaseDuration: config.LeaderElection.LeaseDuration.Duration, - RenewDeadline: config.LeaderElection.RenewDeadline.Duration, - RetryPeriod: config.LeaderElection.RetryPeriod.Duration, - Callbacks: leaderelection.LeaderCallbacks{ - OnStartedLeading: routeControllerManager, - OnStoppedLeading: func() { - klog.Fatalf("leaderelection lost") - }, - }, - }) - return true, nil -} - -func startControllers(controllerContext *routecontrollers.ControllerContext) error { - for controllerName, initFn := range routecontrollers.ControllerManagerInitialization { - if !controllerContext.IsControllerEnabled(controllerName) { - klog.Warningf("%q is disabled", controllerName) - continue - } - klog.V(1).Infof("Starting %q", controllerName) - started, err := initFn(controllerContext) - if err != nil { - klog.Fatalf("Error starting %q (%v)", controllerName, err) - return err - } - if !started { - klog.Warningf("Skipping %q", controllerName) - continue - } - klog.Infof("Started %q", controllerName) - } - klog.Infof("Started Route Controllers") - return nil -} diff --git a/pkg/route/OWNERS b/pkg/route/OWNERS deleted file mode 100644 index fcab82cfa..000000000 --- a/pkg/route/OWNERS +++ /dev/null @@ -1,12 +0,0 @@ -approvers: - - ironcladlou - - knobunc - - Miciah - - frobware - - danehans -reviewers: - - ironcladlou - - knobunc - - Miciah - - frobware - - danehans diff --git a/pkg/route/ingress/ingress.go b/pkg/route/ingress/ingress.go deleted file mode 100644 index 63ed6b3c8..000000000 --- a/pkg/route/ingress/ingress.go +++ /dev/null @@ -1,923 +0,0 @@ -package ingress - -import ( - "context" - "errors" - "fmt" - "reflect" - "strings" - "sync" - "time" - - "k8s.io/klog/v2" - - corev1 "k8s.io/api/core/v1" - networkingv1 "k8s.io/api/networking/v1" - kerrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/types" - utilerrors "k8s.io/apimachinery/pkg/util/errors" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/apimachinery/pkg/util/json" - utilrand "k8s.io/apimachinery/pkg/util/rand" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/wait" - coreinformers "k8s.io/client-go/informers/core/v1" - networkingv1informers "k8s.io/client-go/informers/networking/v1" - kv1core "k8s.io/client-go/kubernetes/typed/core/v1" - kv1networking "k8s.io/client-go/kubernetes/typed/networking/v1" - corelisters "k8s.io/client-go/listers/core/v1" - networkingv1listers "k8s.io/client-go/listers/networking/v1" - "k8s.io/client-go/tools/cache" - "k8s.io/client-go/tools/record" - "k8s.io/client-go/util/workqueue" - "k8s.io/kubernetes/pkg/api/legacyscheme" - - routev1 "github.com/openshift/api/route/v1" - routeclient "github.com/openshift/client-go/route/clientset/versioned/typed/route/v1" - routeinformers "github.com/openshift/client-go/route/informers/externalversions/route/v1" - routelisters "github.com/openshift/client-go/route/listers/route/v1" -) - -// Controller ensures that zero or more routes exist to match any supported ingress. The -// controller creates a controller owner reference from the route to the parent ingress, -// allowing users to orphan their ingress. All owned routes have specific spec fields -// managed (those attributes present on the ingress), while any other fields may be -// modified by the user. -// -// Invariants: -// -// 1. For every ingress path rule with a non-empty backend statement, a route should -// exist that points to that backend. -// 2. For every TLS hostname that has a corresponding path rule and points to a secret -// that exists, a route should exist with a valid TLS config from that secret. -// 3. For every service referenced by the ingress path rule, the route should have -// a target port based on the service. -// 4. A route owned by an ingress that is not described by any of the three invariants -// above should be deleted. -// -// The controller also relies on the use of expectations to remind itself whether there -// are route creations it has not yet observed, which prevents the controller from -// creating more objects than it needs. The expectations are reset when the ingress -// object is modified. It is possible that expectations could leak if an ingress is -// deleted and its deletion is not observed by the cache, but such leaks are only expected -// if there is a bug in the informer cache which must be fixed anyway. -// -// Unsupported attributes: -// -// * the ingress class attribute -// * nginx annotations -// * the empty backend -// * paths with empty backends -// * creating a dynamic route spec.host -// -type Controller struct { - eventRecorder record.EventRecorder - - routeClient routeclient.RoutesGetter - ingressClient kv1networking.IngressesGetter - - ingressLister networkingv1listers.IngressLister - ingressclassLister networkingv1listers.IngressClassLister - secretLister corelisters.SecretLister - routeLister routelisters.RouteLister - serviceLister corelisters.ServiceLister - - // syncs are the items that must return true before the queue can be processed - syncs []cache.InformerSynced - - // queue is the list of namespace keys that must be synced. - queue workqueue.RateLimitingInterface - - // expectations track upcoming route creations that we have not yet observed - expectations *expectations - // expectationDelay controls how long the controller waits to observe its - // own creates. Exposed only for testing. - expectationDelay time.Duration -} - -// expectations track an upcoming change to a named resource related -// to an ingress. This is a thread safe object but callers assume -// responsibility for ensuring expectations do not leak. -type expectations struct { - lock sync.Mutex - expect map[queueKey]sets.String -} - -// newExpectations returns a tracking object for upcoming events -// that the controller may expect to happen. -func newExpectations() *expectations { - return &expectations{ - expect: make(map[queueKey]sets.String), - } -} - -// Expect that an event will happen in the future for the given ingress -// and a named resource related to that ingress. -func (e *expectations) Expect(namespace, ingressName, name string) { - e.lock.Lock() - defer e.lock.Unlock() - key := queueKey{namespace: namespace, name: ingressName} - set, ok := e.expect[key] - if !ok { - set = sets.NewString() - e.expect[key] = set - } - set.Insert(name) -} - -// Satisfied clears the expectation for the given resource name on an -// ingress. -func (e *expectations) Satisfied(namespace, ingressName, name string) { - e.lock.Lock() - defer e.lock.Unlock() - key := queueKey{namespace: namespace, name: ingressName} - set := e.expect[key] - set.Delete(name) - if set.Len() == 0 { - delete(e.expect, key) - } -} - -// Expecting returns true if the provided ingress is still waiting to -// see changes. -func (e *expectations) Expecting(namespace, ingressName string) bool { - e.lock.Lock() - defer e.lock.Unlock() - key := queueKey{namespace: namespace, name: ingressName} - return e.expect[key].Len() > 0 -} - -// Clear indicates that all expectations for the given ingress should -// be cleared. -func (e *expectations) Clear(namespace, ingressName string) { - e.lock.Lock() - defer e.lock.Unlock() - key := queueKey{namespace: namespace, name: ingressName} - delete(e.expect, key) -} - -type queueKey struct { - namespace string - name string -} - -// NewController instantiates a Controller -func NewController(eventsClient kv1core.EventsGetter, routeClient routeclient.RoutesGetter, ingressClient kv1networking.IngressesGetter, ingresses networkingv1informers.IngressInformer, ingressclasses networkingv1informers.IngressClassInformer, secrets coreinformers.SecretInformer, services coreinformers.ServiceInformer, routes routeinformers.RouteInformer) *Controller { - broadcaster := record.NewBroadcaster() - broadcaster.StartLogging(klog.Infof) - // TODO: remove the wrapper when every clients have moved to use the clientset. - broadcaster.StartRecordingToSink(&kv1core.EventSinkImpl{Interface: eventsClient.Events("")}) - recorder := broadcaster.NewRecorder(legacyscheme.Scheme, corev1.EventSource{Component: "ingress-to-route-controller"}) - - c := &Controller{ - eventRecorder: recorder, - queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "ingress-to-route"), - - expectations: newExpectations(), - expectationDelay: 2 * time.Second, - - routeClient: routeClient, - ingressClient: ingressClient, - - ingressLister: ingresses.Lister(), - ingressclassLister: ingressclasses.Lister(), - secretLister: secrets.Lister(), - routeLister: routes.Lister(), - serviceLister: services.Lister(), - - syncs: []cache.InformerSynced{ - ingresses.Informer().HasSynced, - secrets.Informer().HasSynced, - routes.Informer().HasSynced, - services.Informer().HasSynced, - }, - } - - // any change to a secret of type TLS in the namespace - secrets.Informer().AddEventHandler(cache.FilteringResourceEventHandler{ - FilterFunc: func(obj interface{}) bool { - switch t := obj.(type) { - case *corev1.Secret: - return t.Type == corev1.SecretTypeTLS || t.Type == corev1.SecretTypeOpaque - } - return true - }, - Handler: cache.ResourceEventHandlerFuncs{ - AddFunc: c.processNamespace, - DeleteFunc: c.processNamespace, - UpdateFunc: func(oldObj, newObj interface{}) { - c.processNamespace(newObj) - }, - }, - }) - - // any change to a service in the namespace - services.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: c.processNamespace, - DeleteFunc: c.processNamespace, - UpdateFunc: func(oldObj, newObj interface{}) { - c.processNamespace(newObj) - }, - }) - - // any change to a route that has the controller relationship to an Ingress - routes.Informer().AddEventHandler(cache.FilteringResourceEventHandler{ - FilterFunc: func(obj interface{}) bool { - switch t := obj.(type) { - case *routev1.Route: - _, ok := hasIngressOwnerRef(t.OwnerReferences) - return ok - } - return true - }, - Handler: cache.ResourceEventHandlerFuncs{ - AddFunc: c.processRoute, - DeleteFunc: c.processRoute, - UpdateFunc: func(oldObj, newObj interface{}) { - c.processRoute(newObj) - }, - }, - }) - - // changes to ingresses - ingresses.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: c.processIngress, - DeleteFunc: c.processIngress, - UpdateFunc: func(oldObj, newObj interface{}) { - c.processIngress(newObj) - }, - }) - - return c -} - -func (c *Controller) processNamespace(obj interface{}) { - switch t := obj.(type) { - case metav1.Object: - ns := t.GetNamespace() - if len(ns) == 0 { - utilruntime.HandleError(fmt.Errorf("object %T has no namespace", obj)) - return - } - c.queue.Add(queueKey{namespace: ns}) - default: - utilruntime.HandleError(fmt.Errorf("couldn't get key for object %T", obj)) - } -} - -func (c *Controller) processRoute(obj interface{}) { - switch t := obj.(type) { - case *routev1.Route: - ingressName, ok := hasIngressOwnerRef(t.OwnerReferences) - if !ok { - return - } - c.expectations.Satisfied(t.Namespace, ingressName, t.Name) - c.queue.Add(queueKey{namespace: t.Namespace, name: ingressName}) - default: - utilruntime.HandleError(fmt.Errorf("couldn't get key for object %T", obj)) - } -} - -func (c *Controller) processIngress(obj interface{}) { - switch t := obj.(type) { - case *networkingv1.Ingress: - // when we see a change to an ingress, reset our expectations - // this also allows periodic purging of the expectation list in the event - // we miss one or more events. - c.expectations.Clear(t.Namespace, t.Name) - c.queue.Add(queueKey{namespace: t.Namespace, name: t.Name}) - default: - utilruntime.HandleError(fmt.Errorf("couldn't get key for object %T", obj)) - } -} - -// Run begins watching and syncing. -func (c *Controller) Run(workers int, stopCh <-chan struct{}) { - defer utilruntime.HandleCrash() - defer c.queue.ShutDown() - - klog.Infof("Starting controller") - - if !cache.WaitForCacheSync(stopCh, c.syncs...) { - utilruntime.HandleError(fmt.Errorf("timed out waiting for caches to sync")) - return - } - - for i := 0; i < workers; i++ { - go wait.Until(c.worker, time.Second, stopCh) - } - - <-stopCh - klog.Infof("Shutting down controller") -} - -func (c *Controller) worker() { - for c.processNext() { - } - klog.V(4).Infof("Worker stopped") -} - -func (c *Controller) processNext() bool { - key, quit := c.queue.Get() - if quit { - return false - } - defer c.queue.Done(key) - - klog.V(5).Infof("processing %v begin", key) - err := c.sync(key.(queueKey)) - c.handleNamespaceErr(err, key) - klog.V(5).Infof("processing %v end", key) - - return true -} - -func (c *Controller) handleNamespaceErr(err error, key interface{}) { - if err == nil { - c.queue.Forget(key) - return - } - - klog.V(4).Infof("Error syncing %v: %v", key, err) - c.queue.AddRateLimited(key) -} - -func (c *Controller) sync(key queueKey) error { - // sync all ingresses in the namespace - if len(key.name) == 0 { - ingresses, err := c.ingressLister.Ingresses(key.namespace).List(labels.Everything()) - if err != nil { - return err - } - for _, ingress := range ingresses { - c.queue.Add(queueKey{namespace: ingress.Namespace, name: ingress.Name}) - } - return nil - } - // if we are waiting to observe the result of route creations, simply delay - if c.expectations.Expecting(key.namespace, key.name) { - c.queue.AddAfter(key, c.expectationDelay) - klog.V(5).Infof("Ingress %s/%s has unsatisfied expectations", key.namespace, key.name) - return nil - } - - ingress, err := c.ingressLister.Ingresses(key.namespace).Get(key.name) - if kerrors.IsNotFound(err) { - return nil - } - if err != nil { - return err - } - - // If the ingress specifies an ingressclass and the ingressclass does - // not specify openshift.io/ingress-to-route as its controller, ignore - // the ingress. - var ingressClassName *string - if v, ok := ingress.Annotations["kubernetes.io/ingress.class"]; ok { - ingressClassName = &v - } else { - ingressClassName = ingress.Spec.IngressClassName - } - if ingressClassName != nil { - ingressclass, err := c.ingressclassLister.Get(*ingressClassName) - if kerrors.IsNotFound(err) { - return nil - } - if err != nil { - return err - } - // TODO Replace "openshift.io/ingress-to-route" with - // routev1.IngressToRouteIngressClassControllerName once - // openshift-controller-manager bumps openshift/api to a version - // that defines it. - if ingressclass.Spec.Controller != "openshift.io/ingress-to-route" { - return nil - } - } - - // find all matching routes - routes, err := c.routeLister.Routes(key.namespace).List(labels.Everything()) - if err != nil { - return err - } - old := routes[:0] - for _, route := range routes { - ingressName, ok := hasIngressOwnerRef(route.OwnerReferences) - if !ok || ingressName != ingress.Name { - continue - } - old = append(old, route) - } - - // walk the ingress and identify whether any of the child routes need to be updated, deleted, - // or created, as efficiently as possible. - var creates, updates, matches []*routev1.Route - for _, rule := range ingress.Spec.Rules { - if rule.HTTP == nil { - continue - } - if len(rule.Host) == 0 { - continue - } - host := rule.Host - hostIsWildcard := false - if strings.HasPrefix(host, "*.") { - host = strings.Replace(host, "*", "wildcard", 1) - hostIsWildcard = true - } - for _, path := range rule.HTTP.Paths { - if path.Backend.Service == nil { - // Non-Service backends are not implemented. - continue - } - if len(path.Backend.Service.Name) == 0 { - continue - } - if path.PathType != nil && *path.PathType == networkingv1.PathTypeExact { - // Exact path type is not implemented. - continue - } - - var existing *routev1.Route - old, existing = splitForPathAndHost(old, host, path.Path) - if existing == nil { - if r := newRouteForIngress(ingress, &rule, &path, c.secretLister, c.serviceLister, host, hostIsWildcard); r != nil { - creates = append(creates, r) - } - continue - } - - if routeMatchesIngress(existing, ingress, &rule, &path, c.secretLister, c.serviceLister, host, hostIsWildcard) { - matches = append(matches, existing) - continue - } - - if r := newRouteForIngress(ingress, &rule, &path, c.secretLister, c.serviceLister, host, hostIsWildcard); r != nil { - // merge the relevant spec pieces - preserveRouteAttributesFromExisting(r, existing) - updates = append(updates, r) - } else { - // the route cannot be fully calculated, delete it - old = append(old, existing) - } - } - } - - var errs []error - // add the new routes - for _, route := range creates { - if err := createRouteWithName(c.routeClient, ingress, route, c.expectations); err != nil { - errs = append(errs, err) - } - } - - // update any existing routes in place - for _, route := range updates { - data, err := json.Marshal(&route.Spec) - if err != nil { - return err - } - annotations, err := json.Marshal(&route.Annotations) - if err != nil { - return err - } - ownerRefs, err := json.Marshal(&route.OwnerReferences) - if err != nil { - return err - } - data = []byte(fmt.Sprintf(`[{"op":"replace","path":"/spec","value":%s},`+ - `{"op":"replace","path":"/metadata/annotations","value":%s},`+ - `{"op":"replace","path":"/metadata/ownerReferences","value":%s}]`, - data, annotations, ownerRefs)) - _, err = c.routeClient.Routes(route.Namespace).Patch(context.TODO(), route.Name, types.JSONPatchType, data, metav1.PatchOptions{}) - if err != nil { - errs = append(errs, err) - } - } - // purge any previously managed routes - for _, route := range old { - if err := c.routeClient.Routes(route.Namespace).Delete(context.TODO(), route.Name, metav1.DeleteOptions{}); err != nil && !kerrors.IsNotFound(err) { - errs = append(errs, err) - } - } - - // reflect route status to ingress status - // - // We must preserve status that other controllers may have added, so we - // cannot simply compute the new status from the routes associated with - // the ingress; instead, we need to take the current status, remove - // hosts for routes we've just deleted, and then add hosts for current - // routes. In sum, we compute - // ingress.Status.LoadBalancer.Ingress[*].Hostname - - // old[*].Status.Ingress[*].RouterCanonicalHostname + - // matches[*].Status.Ingress[*].RouterCanonicalHostname. - oldCanonicalHostnames := sets.NewString() - for _, ingressIngress := range ingress.Status.LoadBalancer.Ingress { - oldCanonicalHostnames.Insert(ingressIngress.Hostname) - } - newCanonicalHostnames := sets.NewString(oldCanonicalHostnames.List()...) - for _, route := range old { - for _, routeIngress := range route.Status.Ingress { - for _, cond := range routeIngress.Conditions { - if cond.Type == routev1.RouteAdmitted { - if cond.Status == corev1.ConditionTrue { - newCanonicalHostnames.Delete(routeIngress.RouterCanonicalHostname) - } - break - } - } - } - } - for _, route := range matches { - for _, routeIngress := range route.Status.Ingress { - for _, cond := range routeIngress.Conditions { - if cond.Type == routev1.RouteAdmitted { - if cond.Status == corev1.ConditionTrue { - newCanonicalHostnames.Insert(routeIngress.RouterCanonicalHostname) - } - break - } - } - } - } - if !newCanonicalHostnames.Equal(oldCanonicalHostnames) { - updatedIngress := ingress.DeepCopy() - ingressIngresses := make([]corev1.LoadBalancerIngress, 0, newCanonicalHostnames.Len()) - for _, canonicalHostname := range newCanonicalHostnames.List() { - ingressIngresses = append(ingressIngresses, corev1.LoadBalancerIngress{Hostname: canonicalHostname}) - } - updatedIngress.Status.LoadBalancer.Ingress = ingressIngresses - if _, err := c.ingressClient.Ingresses(key.namespace).UpdateStatus(context.TODO(), updatedIngress, metav1.UpdateOptions{}); err != nil { - errs = append(errs, err) - } - } - - return utilerrors.NewAggregate(errs) -} - -// validOwnerRefAPIVersions is a set of recognized API versions for the ingress -// owner ref. -var validOwnerRefAPIVersions = sets.NewString( - "networking.k8s.io/v1", - "networking.k8s.io/v1beta1", - "extensions.k8s.io/v1beta1", -) - -func hasIngressOwnerRef(owners []metav1.OwnerReference) (string, bool) { - for _, ref := range owners { - if ref.Kind != "Ingress" || !validOwnerRefAPIVersions.Has(ref.APIVersion) || ref.Controller == nil || !*ref.Controller { - continue - } - return ref.Name, true - } - return "", false -} - -func newRouteForIngress( - ingress *networkingv1.Ingress, - rule *networkingv1.IngressRule, - path *networkingv1.HTTPIngressPath, - secretLister corelisters.SecretLister, - serviceLister corelisters.ServiceLister, - host string, - hostIsWildcard bool, -) *routev1.Route { - targetPort, err := targetPortForService(ingress.Namespace, path.Backend.Service, serviceLister) - if err != nil { - // no valid target port - return nil - } - - tlsConfig, hasInvalidSecret := tlsConfigForIngress(ingress, rule, secretLister) - if hasInvalidSecret { - return nil - } - - var port *routev1.RoutePort - if targetPort != nil { - port = &routev1.RoutePort{TargetPort: *targetPort} - } - t := true - var wildcardPolicy routev1.WildcardPolicyType - if hostIsWildcard { - wildcardPolicy = routev1.WildcardPolicySubdomain - } - return &routev1.Route{ - ObjectMeta: metav1.ObjectMeta{ - GenerateName: ingress.Name + "-", - Namespace: ingress.Namespace, - Labels: ingress.Labels, - Annotations: ingress.Annotations, - OwnerReferences: []metav1.OwnerReference{ - {APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Controller: &t, Name: ingress.Name, UID: ingress.UID}, - }, - }, - Spec: routev1.RouteSpec{ - Host: host, - Path: path.Path, - To: routev1.RouteTargetReference{ - Name: path.Backend.Service.Name, - }, - Port: port, - TLS: tlsConfig, - WildcardPolicy: wildcardPolicy, - }, - } -} - -func preserveRouteAttributesFromExisting(r, existing *routev1.Route) { - r.Name = existing.Name - r.GenerateName = "" - r.Spec.To.Weight = existing.Spec.To.Weight - if r.Spec.TLS != nil && existing.Spec.TLS != nil { - r.Spec.TLS.CACertificate = existing.Spec.TLS.CACertificate - r.Spec.TLS.InsecureEdgeTerminationPolicy = existing.Spec.TLS.InsecureEdgeTerminationPolicy - if r.Spec.TLS.Termination == routev1.TLSTerminationReencrypt { - if _, ok := r.Annotations[destinationCACertificateAnnotationKey]; !ok { - r.Spec.TLS.DestinationCACertificate = existing.Spec.TLS.DestinationCACertificate - } - } - } -} - -func routeMatchesIngress( - route *routev1.Route, - ingress *networkingv1.Ingress, - rule *networkingv1.IngressRule, - path *networkingv1.HTTPIngressPath, - secretLister corelisters.SecretLister, - serviceLister corelisters.ServiceLister, - host string, - hostIsWildcard bool, -) bool { - wildcardPolicy := routev1.WildcardPolicyNone - if hostIsWildcard { - wildcardPolicy = routev1.WildcardPolicySubdomain - } - match := route.Spec.Host == host && - route.Spec.Path == path.Path && - route.Spec.To.Name == path.Backend.Service.Name && - route.Spec.WildcardPolicy == routev1.WildcardPolicyNone && - len(route.Spec.AlternateBackends) == 0 && - route.Spec.WildcardPolicy == wildcardPolicy && - reflect.DeepEqual(route.Annotations, ingress.Annotations) && - route.OwnerReferences[0].APIVersion == "networking.k8s.io/v1" - - if !match { - return false - } - - targetPort, err := targetPortForService(ingress.Namespace, path.Backend.Service, serviceLister) - if err != nil { - // not valid - return false - } - if targetPort == nil && route.Spec.Port != nil { - return false - } - if targetPort != nil && (route.Spec.Port == nil || *targetPort != route.Spec.Port.TargetPort) { - return false - } - - tlsConfig, hasInvalidSecret := tlsConfigForIngress(ingress, rule, secretLister) - if hasInvalidSecret { - return false - } - - if route.Spec.TLS != nil && tlsConfig != nil { - tlsConfig.InsecureEdgeTerminationPolicy = route.Spec.TLS.InsecureEdgeTerminationPolicy - if _, ok := ingress.Annotations[destinationCACertificateAnnotationKey]; !ok { - tlsConfig.DestinationCACertificate = route.Spec.TLS.DestinationCACertificate - } - } - return reflect.DeepEqual(tlsConfig, route.Spec.TLS) -} - -// targetPortForService returns a target port for a Route based on the given -// Ingress backend service. If the Ingress references a Service or port that -// cannot be found, targetPortForService returns an error. If the Ingress -// references a port that has no name, a nil value is returned for the target -// port. Otherwise, the port name is returned as the target port. -// -// Note that an Ingress specifies a port on a Service whereas a Route specifies -// a port on an Endpoints resource. The ports on a Service resource and the -// ports on its corresponding Endpoints resource have the same names but may -// have different numbers. If there is only one port, it may be nameless, but -// in this case, the Route need have no port specification because omitting the -// port specification causes the Route to target every port (in this case, the -// only port) on the Endpoints resource. -func targetPortForService(namespace string, backendService *networkingv1.IngressServiceBackend, serviceLister corelisters.ServiceLister) (*intstr.IntOrString, error) { - service, err := serviceLister.Services(namespace).Get(backendService.Name) - if err != nil { - // service doesn't exist yet, wait - return nil, err - } - if len(backendService.Port.Name) != 0 { - expect := backendService.Port.Name - for _, port := range service.Spec.Ports { - if port.Name == expect { - targetPort := intstr.FromString(port.Name) - return &targetPort, nil - } - } - } else { - expect := backendService.Port.Number - for _, port := range service.Spec.Ports { - if port.Port == expect { - if len(port.Name) == 0 { - return nil, nil - } - targetPort := intstr.FromString(port.Name) - return &targetPort, nil - } - } - } - return nil, errors.New("no port found") -} - -func splitForPathAndHost(routes []*routev1.Route, host, path string) ([]*routev1.Route, *routev1.Route) { - for i, route := range routes { - if route.Spec.Host == host && route.Spec.Path == path { - last := len(routes) - 1 - routes[i], routes[last] = routes[last], route - return routes[:last], route - } - } - return routes, nil -} - -func referencesSecret(ingress *networkingv1.Ingress, host string) (string, bool) { - for _, tls := range ingress.Spec.TLS { - for _, tlsHost := range tls.Hosts { - if tlsHost == host { - return tls.SecretName, true - } - } - } - return "", false -} - -// createRouteWithName performs client side name generation so we can set a predictable expectation. -// If we fail multiple times in a row we will return an error. -// TODO: future optimization, check the local cache for the name first -func createRouteWithName(client routeclient.RoutesGetter, ingress *networkingv1.Ingress, route *routev1.Route, expect *expectations) error { - base := route.GenerateName - var lastErr error - // only retry a limited number of times - for i := 0; i < 3; i++ { - if len(base) > 0 { - route.GenerateName = "" - route.Name = generateRouteName(base) - } - - // Set the expectation before we talk to the server in order to - // prevent racing with the route cache. - expect.Expect(ingress.Namespace, ingress.Name, route.Name) - - _, err := client.Routes(route.Namespace).Create(context.TODO(), route, metav1.CreateOptions{}) - if err == nil { - return nil - } - - // We either collided with another randomly generated name, or another - // error between us and the server prevented observing the success - // of the result. In either case we are not expecting a new route. This - // is safe because expectations are an optimization to avoid churn rather - // than to prevent true duplicate creation. - expect.Satisfied(ingress.Namespace, ingress.Name, route.Name) - - // if we aren't generating names (or if we got any other type of error) - // return right away - if len(base) == 0 || !kerrors.IsAlreadyExists(err) { - return err - } - lastErr = err - } - return lastErr -} - -const ( - maxNameLength = 63 - randomLength = 5 - maxGeneratedNameLength = maxNameLength - randomLength -) - -func generateRouteName(base string) string { - if len(base) > maxGeneratedNameLength { - base = base[:maxGeneratedNameLength] - } - return fmt.Sprintf("%s%s", base, utilrand.String(randomLength)) -} - -func tlsConfigForIngress( - ingress *networkingv1.Ingress, - rule *networkingv1.IngressRule, - secretLister corelisters.SecretLister, -) (*routev1.TLSConfig, bool) { - - potentiallyNilTLSSecret, hasInvalidTLSSecret := tlsSecretIfValid(ingress, rule, secretLister) - if hasInvalidTLSSecret { - return nil, true - } - - if !tlsEnabled(ingress, rule, potentiallyNilTLSSecret) { - return nil, false - } - // Edge: May have cert - // Re-Encrypt: May have cert - // Passthrough: Must not have cert - terminationPolicy := terminationPolicyForIngress(ingress) - tlsConfig := &routev1.TLSConfig{ - Termination: terminationPolicy, - InsecureEdgeTerminationPolicy: routev1.InsecureEdgeTerminationPolicyRedirect, - } - if terminationPolicy != routev1.TLSTerminationPassthrough && potentiallyNilTLSSecret != nil { - tlsConfig.Certificate = string(potentiallyNilTLSSecret.Data[corev1.TLSCertKey]) - tlsConfig.Key = string(potentiallyNilTLSSecret.Data[corev1.TLSPrivateKeyKey]) - } - - destinationCACertificate := destinationCACertificateForIngress(ingress, secretLister) - if terminationPolicy == routev1.TLSTerminationReencrypt && destinationCACertificate != nil { - tlsConfig.DestinationCACertificate = *destinationCACertificate - } - - return tlsConfig, false -} - -var emptyTLS = networkingv1.IngressTLS{} - -func tlsEnabled(ingress *networkingv1.Ingress, rule *networkingv1.IngressRule, potentiallyNilTLSSecret *corev1.Secret) bool { - switch ingress.Annotations[terminationPolicyAnnotationKey] { - case string(routev1.TLSTerminationPassthrough), string(routev1.TLSTerminationReencrypt), string(routev1.TLSTerminationEdge): - return true - } - if potentiallyNilTLSSecret != nil { - return true - } - for _, tls := range ingress.Spec.TLS { - if reflect.DeepEqual(tls, emptyTLS) { - return true - } - } - return false -} - -func tlsSecretIfValid(ingress *networkingv1.Ingress, rule *networkingv1.IngressRule, secretLister corelisters.SecretLister) (_ *corev1.Secret, hasInvalidSecret bool) { - name, ok := referencesSecret(ingress, rule.Host) - if !ok { - return nil, false - } - secret, err := secretLister.Secrets(ingress.Namespace).Get(name) - if err != nil { - // secret doesn't exist yet, wait - return nil, true - } - if secret.Type != corev1.SecretTypeTLS { - // secret is the wrong type - return nil, true - } - if _, ok := secret.Data[corev1.TLSCertKey]; !ok { - return nil, true - } - if _, ok := secret.Data[corev1.TLSPrivateKeyKey]; !ok { - return nil, true - } - return secret, false -} - -var terminationPolicyAnnotationKey = routev1.GroupName + "/termination" - -func terminationPolicyForIngress(ingress *networkingv1.Ingress) routev1.TLSTerminationType { - switch { - case ingress.Annotations[terminationPolicyAnnotationKey] == string(routev1.TLSTerminationPassthrough): - return routev1.TLSTerminationPassthrough - case ingress.Annotations[terminationPolicyAnnotationKey] == string(routev1.TLSTerminationReencrypt): - return routev1.TLSTerminationReencrypt - default: - return routev1.TLSTerminationEdge - } -} - -var destinationCACertificateAnnotationKey = routev1.GroupName + "/destination-ca-certificate-secret" - -func destinationCACertificateForIngress(ingress *networkingv1.Ingress, secretLister corelisters.SecretLister) *string { - name := ingress.Annotations[destinationCACertificateAnnotationKey] - secret, err := secretLister.Secrets(ingress.Namespace).Get(name) - if err != nil { - return nil - } - switch secret.Type { - case corev1.SecretTypeTLS, corev1.SecretTypeOpaque: - default: - return nil - } - if v, ok := secret.Data[corev1.TLSCertKey]; ok { - value := string(v) - return &value - } - return nil -} diff --git a/pkg/route/ingress/ingress_test.go b/pkg/route/ingress/ingress_test.go deleted file mode 100644 index c5f0e0a25..000000000 --- a/pkg/route/ingress/ingress_test.go +++ /dev/null @@ -1,3963 +0,0 @@ -package ingress - -import ( - "reflect" - "strings" - "testing" - - v1 "k8s.io/api/core/v1" - networkingv1 "k8s.io/api/networking/v1" - kerrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/diff" - "k8s.io/apimachinery/pkg/util/intstr" - fake "k8s.io/client-go/kubernetes/fake" - "k8s.io/client-go/kubernetes/scheme" - corelisters "k8s.io/client-go/listers/core/v1" - networkingv1listers "k8s.io/client-go/listers/networking/v1" - clientgotesting "k8s.io/client-go/testing" - "k8s.io/client-go/util/workqueue" - - routev1 "github.com/openshift/api/route/v1" - routev1fake "github.com/openshift/client-go/route/clientset/versioned/fake" - routelisters "github.com/openshift/client-go/route/listers/route/v1" -) - -type routeLister struct { - Err error - Items []*routev1.Route -} - -func (r *routeLister) List(selector labels.Selector) (ret []*routev1.Route, err error) { - return r.Items, r.Err -} -func (r *routeLister) Routes(namespace string) routelisters.RouteNamespaceLister { - return &nsRouteLister{r: r, ns: namespace} -} - -type nsRouteLister struct { - r *routeLister - ns string -} - -func (r *nsRouteLister) List(selector labels.Selector) (ret []*routev1.Route, err error) { - return r.r.Items, r.r.Err -} -func (r *nsRouteLister) Get(name string) (*routev1.Route, error) { - for _, s := range r.r.Items { - if s.Name == name && r.ns == s.Namespace { - return s, nil - } - } - return nil, kerrors.NewNotFound(schema.GroupResource{}, name) -} - -type ingressLister struct { - Err error - Items []*networkingv1.Ingress -} - -func (r *ingressLister) List(selector labels.Selector) (ret []*networkingv1.Ingress, err error) { - return r.Items, r.Err -} -func (r *ingressLister) Ingresses(namespace string) networkingv1listers.IngressNamespaceLister { - return &nsIngressLister{r: r, ns: namespace} -} - -type nsIngressLister struct { - r *ingressLister - ns string -} - -func (r *nsIngressLister) List(selector labels.Selector) (ret []*networkingv1.Ingress, err error) { - return r.r.Items, r.r.Err -} -func (r *nsIngressLister) Get(name string) (*networkingv1.Ingress, error) { - for _, s := range r.r.Items { - if s.Name == name && r.ns == s.Namespace { - return s, nil - } - } - return nil, kerrors.NewNotFound(schema.GroupResource{}, name) -} - -type ingressclassLister struct { - Err error - Items []*networkingv1.IngressClass -} - -func (r *ingressclassLister) List(selector labels.Selector) (ret []*networkingv1.IngressClass, err error) { - return r.Items, r.Err -} -func (r *ingressclassLister) Get(name string) (*networkingv1.IngressClass, error) { - for _, s := range r.Items { - if s.Name == name { - return s, nil - } - } - return nil, kerrors.NewNotFound(schema.GroupResource{}, name) -} - -type serviceLister struct { - Err error - Items []*v1.Service -} - -func (r *serviceLister) List(selector labels.Selector) (ret []*v1.Service, err error) { - return r.Items, r.Err -} -func (r *serviceLister) Services(namespace string) corelisters.ServiceNamespaceLister { - return &nsServiceLister{r: r, ns: namespace} -} - -func (r *serviceLister) GetPodServices(pod *v1.Pod) ([]*v1.Service, error) { - panic("unsupported") -} - -type nsServiceLister struct { - r *serviceLister - ns string -} - -func (r *nsServiceLister) List(selector labels.Selector) (ret []*v1.Service, err error) { - return r.r.Items, r.r.Err -} -func (r *nsServiceLister) Get(name string) (*v1.Service, error) { - for _, s := range r.r.Items { - if s.Name == name && r.ns == s.Namespace { - return s, nil - } - } - return nil, kerrors.NewNotFound(schema.GroupResource{}, name) -} - -type secretLister struct { - Err error - Items []*v1.Secret -} - -func (r *secretLister) List(selector labels.Selector) (ret []*v1.Secret, err error) { - return r.Items, r.Err -} -func (r *secretLister) Secrets(namespace string) corelisters.SecretNamespaceLister { - return &nsSecretLister{r: r, ns: namespace} -} - -type nsSecretLister struct { - r *secretLister - ns string -} - -func (r *nsSecretLister) List(selector labels.Selector) (ret []*v1.Secret, err error) { - return r.r.Items, r.r.Err -} -func (r *nsSecretLister) Get(name string) (*v1.Secret, error) { - for _, s := range r.r.Items { - if s.Name == name && r.ns == s.Namespace { - return s, nil - } - } - return nil, kerrors.NewNotFound(schema.GroupResource{}, name) -} - -const complexIngress = ` -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: test-1 - namespace: test -spec: - rules: - - host: 1.ingress-test.com - http: - paths: - - path: /test - backend: - service: - name: ingress-endpoint-1 - port: - number: 80 - - path: /other - backend: - service: - name: ingress-endpoint-2 - port: - number: 80 - - host: 2.ingress-test.com - http: - paths: - - path: / - backend: - service: - name: ingress-endpoint-1 - port: - number: 80 - - host: 3.ingress-test.com - http: - paths: - - path: / - backend: - service: - name: ingress-endpoint-1 - port: - number: 80 -` - -func TestController_stabilizeAfterCreate(t *testing.T) { - obj, _, err := scheme.Codecs.UniversalDeserializer().Decode([]byte(complexIngress), nil, nil) - if err != nil { - t.Fatal(err) - } - ingress := obj.(*networkingv1.Ingress) - - i := &ingressLister{ - Items: []*networkingv1.Ingress{ - ingress, - }, - } - ic := &ingressclassLister{Items: []*networkingv1.IngressClass{}} - r := &routeLister{} - s := &secretLister{} - svc := &serviceLister{Items: []*v1.Service{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "ingress-endpoint-1", - Namespace: "test", - }, - Spec: v1.ServiceSpec{ - Ports: []v1.ServicePort{ - { - Name: "http", - Port: 80, - TargetPort: intstr.FromInt(8080), - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "ingress-endpoint-2", - Namespace: "test", - }, - Spec: v1.ServiceSpec{ - Ports: []v1.ServicePort{ - { - Name: "80-tcp", - Port: 80, - TargetPort: intstr.FromInt(8080), - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "ingress-endpoint-3", - Namespace: "test", - }, - Spec: v1.ServiceSpec{ - Ports: []v1.ServicePort{ - { - Port: 80, - TargetPort: intstr.FromString("tcp-8080"), - }, - }, - }, - }, - }} - - var names []string - routeClientset := &routev1fake.Clientset{} - routeClientset.AddReactor("*", "routes", func(action clientgotesting.Action) (handled bool, ret runtime.Object, err error) { - switch a := action.(type) { - case clientgotesting.CreateAction: - obj := a.GetObject().DeepCopyObject() - m := obj.(metav1.Object) - if len(m.GetName()) == 0 { - m.SetName(m.GetGenerateName()) - } - names = append(names, m.GetName()) - return true, obj, nil - } - return true, nil, nil - }) - kc := fake.NewSimpleClientset() - kc.PrependReactor("*", "ingresses", func(action clientgotesting.Action) (handled bool, ret runtime.Object, err error) { - return true, nil, nil - }) - - c := &Controller{ - queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "ingress-to-route-test"), - routeClient: routeClientset.RouteV1(), - ingressClient: kc.NetworkingV1(), - ingressLister: i, - ingressclassLister: ic, - routeLister: r, - secretLister: s, - serviceLister: svc, - expectations: newExpectations(), - } - defer c.queue.ShutDown() - - // load the ingresses for the namespace - if err := c.sync(queueKey{namespace: "test"}); err != nil { - t.Errorf("Controller.sync() error = %v", err) - } - if c.queue.Len() != 1 { - t.Fatalf("Controller.sync() unexpected queue: %#v", c.queue.Len()) - } - routeActions := routeClientset.Actions() - if len(routeActions) != 0 { - t.Fatalf("Controller.sync() unexpected actions: %#v", routeActions) - } - - // process the ingress - key, _ := c.queue.Get() - expectKey := queueKey{namespace: ingress.Namespace, name: ingress.Name} - if key.(queueKey) != expectKey { - t.Fatalf("incorrect key: %v", key) - } - if err := c.sync(key.(queueKey)); err != nil { - t.Fatalf("Controller.sync() error = %v", err) - } - c.queue.Done(key) - if c.queue.Len() != 0 { - t.Fatalf("Controller.sync() unexpected queue: %#v", c.queue.Len()) - } - routeActions = routeClientset.Actions() - if len(routeActions) == 0 { - t.Fatalf("Controller.sync() unexpected actions: %#v", routeActions) - } - if !c.expectations.Expecting("test", "test-1") { - t.Fatalf("Controller.sync() should be holding an expectation: %#v", c.expectations.expect) - } - - for _, action := range routeActions { - switch action.GetVerb() { - case "create": - switch o := action.(clientgotesting.CreateAction).GetObject().(type) { - case *routev1.Route: - r.Items = append(r.Items, o) - c.processRoute(o) - default: - t.Fatalf("Unexpected create: %T", o) - } - default: - t.Fatalf("Unexpected action: %#v", action) - } - } - if c.queue.Len() != 1 { - t.Fatalf("Controller.sync() unexpected queue: %#v", c.queue.Len()) - } - if c.expectations.Expecting("test", "test-1") { - t.Fatalf("Controller.sync() should have cleared all expectations: %#v", c.expectations.expect) - } - c.expectations.Expect("test", "test-1", names[0]) - - // waiting for a single expected route, will do nothing - key, _ = c.queue.Get() - if err := c.sync(key.(queueKey)); err != nil { - t.Errorf("Controller.sync() error = %v", err) - } - c.queue.Done(key) - routeActions = routeClientset.Actions() - if len(routeActions) == 0 { - t.Fatalf("Controller.sync() unexpected actions: %#v", routeActions) - } - if c.queue.Len() != 1 { - t.Fatalf("Controller.sync() unexpected queue: %#v", c.queue.Len()) - } - c.expectations.Satisfied("test", "test-1", names[0]) - - // steady state, nothing has changed - key, _ = c.queue.Get() - if err := c.sync(key.(queueKey)); err != nil { - t.Errorf("Controller.sync() error = %v", err) - } - c.queue.Done(key) - routeActions = routeClientset.Actions() - if len(routeActions) == 0 { - t.Fatalf("Controller.sync() unexpected actions: %#v", routeActions) - } - if c.queue.Len() != 0 { - t.Fatalf("Controller.sync() unexpected queue: %#v", c.queue.Len()) - } -} - -func newTestExpectations(fn func(*expectations)) *expectations { - e := newExpectations() - fn(e) - return e -} - -func TestController_sync(t *testing.T) { - operatorv1GroupVersion := "operator.openshift.io/v1" - ingressclasses := &ingressclassLister{Items: []*networkingv1.IngressClass{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "openshift-default", - }, - Spec: networkingv1.IngressClassSpec{ - Controller: "openshift.io/ingress-to-route", - Parameters: &networkingv1.IngressClassParametersReference{ - APIGroup: &operatorv1GroupVersion, - Kind: "IngressController", - Name: "default", - }, - }, - }, - }} - services := &serviceLister{Items: []*v1.Service{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "service-1", - Namespace: "test", - }, - Spec: v1.ServiceSpec{ - Ports: []v1.ServicePort{ - { - Name: "http", - Port: 80, - TargetPort: intstr.FromInt(8080), - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "service-2", - Namespace: "test", - }, - Spec: v1.ServiceSpec{ - Ports: []v1.ServicePort{ - { - Name: "80-tcp", - Port: 80, - TargetPort: intstr.FromInt(8080), - }, - }, - }, - }, - }} - secrets := &secretLister{Items: []*v1.Secret{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "secret-0", - Namespace: "test", - }, - Type: v1.SecretTypeOpaque, - Data: map[string][]byte{ - v1.TLSCertKey: []byte(`cert`), - v1.TLSPrivateKeyKey: []byte(`key`), - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "secret-1", - Namespace: "test", - }, - Type: v1.SecretTypeTLS, - Data: map[string][]byte{ - v1.TLSCertKey: []byte(`cert`), - v1.TLSPrivateKeyKey: []byte(`key`), - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "secret-1a", - Namespace: "test", - }, - Type: v1.SecretTypeTLS, - Data: map[string][]byte{ - v1.TLSCertKey: []byte(`cert`), - v1.TLSPrivateKeyKey: []byte(`key2`), - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "secret-2", - Namespace: "test", - }, - Type: v1.SecretTypeTLS, - Data: map[string][]byte{ - v1.TLSPrivateKeyKey: []byte(`key`), - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "secret-3", - Namespace: "test", - }, - Type: v1.SecretTypeTLS, - Data: map[string][]byte{ - v1.TLSCertKey: []byte(``), - v1.TLSPrivateKeyKey: []byte(``), - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "secret-ca-cert", - Namespace: "test", - }, - Type: v1.SecretTypeTLS, - Data: map[string][]byte{ - v1.TLSCertKey: []byte(`CAcert`), - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "secret-ca-cert-opaque", - Namespace: "test", - }, - Type: v1.SecretTypeOpaque, - Data: map[string][]byte{ - v1.TLSCertKey: []byte(`CAcert-from-opaque`), - }, - }, - }} - boolTrue := true - customIngressClassName := "custom" - openshiftCustomIngressClassName := "openshift-custom" - openshiftDefaultIngressClassName := "openshift-default" - pathTypeExact := networkingv1.PathTypeExact - pathTypePrefix := networkingv1.PathTypePrefix - pathTypeImplementationSpecific := networkingv1.PathTypeImplementationSpecific - type fields struct { - i networkingv1listers.IngressLister - ic networkingv1listers.IngressClassLister - r routelisters.RouteLister - s corelisters.SecretLister - svc corelisters.ServiceLister - } - tests := []struct { - name string - fields fields - args queueKey - expects *expectations - wantErr bool - wantRouteCreates []*routev1.Route - wantRoutePatches []clientgotesting.PatchActionImpl - wantRouteDeletes []clientgotesting.DeleteActionImpl - wantIngressUpdates []clientgotesting.UpdateActionImpl - wantQueue []queueKey - wantExpectation *expectations - wantExpects []queueKey - }{ - { - name: "no changes", - fields: fields{i: &ingressLister{}, r: &routeLister{}}, - args: queueKey{namespace: "test", name: "1"}, - }, - { - name: "sync namespace - no ingress", - fields: fields{i: &ingressLister{}, r: &routeLister{}}, - args: queueKey{namespace: "test"}, - }, - { - name: "sync namespace - two ingress", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "2", - Namespace: "test", - }, - }, - }}, - r: &routeLister{}, - }, - args: queueKey{namespace: "test"}, - wantQueue: []queueKey{ - {namespace: "test", name: "1"}, - {namespace: "test", name: "2"}, - }, - }, - { - name: "ignores incomplete ingress - no host", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - Rules: []networkingv1.IngressRule{ - { - Host: "", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/deep", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{}, - }, - args: queueKey{namespace: "test", name: "1"}, - }, - { - name: "ignores incomplete ingress - no service", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/deep", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{}, - }, - args: queueKey{namespace: "test", name: "1"}, - }, - { - name: "ignores incomplete ingress - no paths", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{}, - }, - }, - }, - }, - }, - }}, - r: &routeLister{}, - }, - args: queueKey{namespace: "test", name: "1"}, - }, - { - name: "ignores ingress with third-party ingressclass", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - IngressClassName: &customIngressClassName, - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/deep", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - ic: &ingressclassLister{Items: []*networkingv1.IngressClass{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "custom", - }, - Spec: networkingv1.IngressClassSpec{ - Controller: "acme.io/ingress-controller", - }, - }, - }}, - r: &routeLister{}, - }, - args: queueKey{namespace: "test", name: "1"}, - }, - { - name: "ignores ingress with unsupported path type", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/deep", - // "Exact" is not implemented. - PathType: &pathTypeExact, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{}, - }, - args: queueKey{namespace: "test", name: "1"}, - }, - { - name: "ignores incomplete ingress - service does not exist", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-3", - Port: networkingv1.ServiceBackendPort{ - Number: int32(80), - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{}, - }, - args: queueKey{namespace: "test", name: "1"}, - }, - { - name: "create route", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/deep", - // Behavior for empty PathType is undefined; - // treat it the same as "Prefix". - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - { - Path: "/", - // Implementations may treat "ImplementationSpecific" - // as "Exact" or "Prefix", so we treat it as "Prefix". - PathType: &pathTypeImplementationSpecific, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{}, - }, - args: queueKey{namespace: "test", name: "1"}, - wantExpects: []queueKey{{namespace: "test", name: "1"}}, - wantRouteCreates: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/deep", - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - }, - }, - }, - }, - { - name: "create route - with termination reencypt and destinationCaCert", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - Annotations: map[string]string{ - "route.openshift.io/termination": "reencrypt", - "route.openshift.io/destination-ca-certificate-secret": "secret-ca-cert", - }, - }, - Spec: networkingv1.IngressSpec{ - TLS: []networkingv1.IngressTLS{ - {Hosts: []string{"test.com"}, SecretName: "secret-1"}, - }, - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{}, - }, - args: queueKey{namespace: "test", name: "1"}, - wantExpects: []queueKey{{namespace: "test", name: "1"}}, - wantRouteCreates: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - Annotations: map[string]string{ - "route.openshift.io/termination": "reencrypt", - "route.openshift.io/destination-ca-certificate-secret": "secret-ca-cert", - }, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - TLS: &routev1.TLSConfig{ - Termination: routev1.TLSTerminationReencrypt, - Key: "key", - Certificate: "cert", - DestinationCACertificate: "CAcert", - InsecureEdgeTerminationPolicy: "Redirect", - }, - }, - }, - }, - }, - { - name: "create route - targetPort string, service port with name", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-2", - Port: networkingv1.ServiceBackendPort{ - Number: int32(80), - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{}, - }, - args: queueKey{namespace: "test", name: "1"}, - wantExpects: []queueKey{{namespace: "test", name: "1"}}, - wantRouteCreates: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - To: routev1.RouteTargetReference{ - Name: "service-2", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("80-tcp"), - }, - }, - }, - }, - }, - { - name: "create route - default ingresscontroller", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - IngressClassName: &openshiftDefaultIngressClassName, - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - ic: &ingressclassLister{Items: []*networkingv1.IngressClass{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "openshift-default", - }, - Spec: networkingv1.IngressClassSpec{ - Controller: "openshift.io/ingress-to-route", - Parameters: &networkingv1.IngressClassParametersReference{ - APIGroup: &operatorv1GroupVersion, - Kind: "IngressController", - Name: "default", - }, - }, - }, - }}, - r: &routeLister{}, - }, - args: queueKey{namespace: "test", name: "1"}, - wantExpects: []queueKey{{namespace: "test", name: "1"}}, - wantRouteCreates: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - }, - }, - }, - }, - { - name: "create route - custom ingresscontroller", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - IngressClassName: &openshiftCustomIngressClassName, - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - ic: &ingressclassLister{Items: []*networkingv1.IngressClass{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "openshift-custom", - }, - Spec: networkingv1.IngressClassSpec{ - Controller: "openshift.io/ingress-to-route", - Parameters: &networkingv1.IngressClassParametersReference{ - APIGroup: &operatorv1GroupVersion, - Kind: "IngressController", - Name: "custom", - }, - }, - }, - }}, - r: &routeLister{}, - }, - args: queueKey{namespace: "test", name: "1"}, - wantExpects: []queueKey{{namespace: "test", name: "1"}}, - wantRouteCreates: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - }, - }, - }, - }, - { - name: "create route - blocked by expectation", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/deep", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{}, - }, - expects: newTestExpectations(func(e *expectations) { - e.Expect("test", "1", "route-test-1") - }), - args: queueKey{namespace: "test", name: "1"}, - wantQueue: []queueKey{{namespace: "test", name: "1"}}, - // preserves the expectations unchanged - wantExpectation: newTestExpectations(func(e *expectations) { - e.Expect("test", "1", "route-test-1") - }), - }, - { - name: "update route", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{Items: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromInt(80), - }, - WildcardPolicy: routev1.WildcardPolicyNone, - }, - }, - }}, - }, - args: queueKey{namespace: "test", name: "1"}, - wantRoutePatches: []clientgotesting.PatchActionImpl{ - { - Name: "1-abcdef", - Patch: []byte(`[{"op":"replace","path":"/spec","value":{"host":"test.com","path":"/","to":{"kind":"","name":"service-1","weight":null},"port":{"targetPort":"http"}}},{"op":"replace","path":"/metadata/annotations","value":null},{"op":"replace","path":"/metadata/ownerReferences","value":[{"apiVersion":"networking.k8s.io/v1","kind":"Ingress","name":"1","uid":"","controller":true}]}]`), - }, - }, - }, - { - name: "no-op", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{Items: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - WildcardPolicy: routev1.WildcardPolicyNone, - }, - }, - }}, - }, - args: queueKey{namespace: "test", name: "1"}, - }, - { - name: "no-op - ignore partially owned resource", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{Items: []*routev1.Route{ - // this route is identical to the ingress - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - WildcardPolicy: routev1.WildcardPolicyNone, - }, - }, - // this route should be left as is because controller is not true - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-empty", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1"}}, - }, - Spec: routev1.RouteSpec{}, - }, - // this route should be ignored because it doesn't match the ingress name - { - ObjectMeta: metav1.ObjectMeta{ - Name: "2-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "2", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromInt(8080), - }, - WildcardPolicy: routev1.WildcardPolicyNone, - }, - }, - }}, - }, - args: queueKey{namespace: "test", name: "1"}, - }, - { - name: "no-op - ignore route created for an ingress with a third-party class", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - IngressClassName: &customIngressClassName, - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/foo", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{Items: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/bar", - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - WildcardPolicy: routev1.WildcardPolicyNone, - }, - }, - }}, - }, - args: queueKey{namespace: "test", name: "1"}, - }, - { - name: "no-op - destination CA certificate has been changed by the user", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - Annotations: map[string]string{ - "route.openshift.io/termination": "reencrypt", - }, - }, - Spec: networkingv1.IngressSpec{ - TLS: []networkingv1.IngressTLS{ - {Hosts: []string{"test.com"}, SecretName: "secret-1"}, - }, - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - Status: networkingv1.IngressStatus{ - LoadBalancer: v1.LoadBalancerStatus{ - Ingress: []v1.LoadBalancerIngress{ - {Hostname: "apps.foo.com"}, - }, - }, - }, - }, - }}, - r: &routeLister{Items: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - Annotations: map[string]string{ - "route.openshift.io/termination": "reencrypt", - }, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - WildcardPolicy: routev1.WildcardPolicyNone, - TLS: &routev1.TLSConfig{ - Termination: routev1.TLSTerminationReencrypt, - Key: "key", - Certificate: "cert", - DestinationCACertificate: "cert", - }, - }, - Status: routev1.RouteStatus{ - Ingress: []routev1.RouteIngress{ - { - RouterCanonicalHostname: "apps.foo.com", - Conditions: []routev1.RouteIngressCondition{{ - Type: routev1.RouteAdmitted, - Status: v1.ConditionTrue, - }}, - }, - }, - }, - }, - }}, - }, - args: queueKey{namespace: "test", name: "1"}, - }, - { - name: "update ingress with missing secret ref", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - TLS: []networkingv1.IngressTLS{ - {Hosts: []string{"test.com"}, SecretName: "secret-4"}, - }, - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{Items: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - WildcardPolicy: routev1.WildcardPolicyNone, - }, - }, - }}, - }, - args: queueKey{namespace: "test", name: "1"}, - wantRouteDeletes: []clientgotesting.DeleteActionImpl{ - { - Name: "1-abcdef", - }, - }, - }, - { - name: "update ingress with missing secret ref", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - TLS: []networkingv1.IngressTLS{ - {Hosts: []string{"test.com"}, SecretName: "secret-4"}, - }, - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{Items: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - WildcardPolicy: routev1.WildcardPolicyNone, - }, - }, - }}, - }, - args: queueKey{namespace: "test", name: "1"}, - wantRouteDeletes: []clientgotesting.DeleteActionImpl{ - { - Name: "1-abcdef", - }, - }, - }, - { - name: "update ingress to not reference secret", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - TLS: []networkingv1.IngressTLS{ - {Hosts: []string{"test.com1"}, SecretName: "secret-1"}, - }, - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{Items: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - WildcardPolicy: routev1.WildcardPolicyNone, - TLS: &routev1.TLSConfig{ - Termination: routev1.TLSTerminationEdge, - InsecureEdgeTerminationPolicy: routev1.InsecureEdgeTerminationPolicyRedirect, - Key: "key", - Certificate: "cert", - }, - }, - }, - }}, - }, - args: queueKey{namespace: "test", name: "1"}, - wantRoutePatches: []clientgotesting.PatchActionImpl{ - { - Name: "1-abcdef", - Patch: []byte(`[{"op":"replace","path":"/spec","value":{"host":"test.com","path":"/","to":{"kind":"","name":"service-1","weight":null},"port":{"targetPort":"http"}}},{"op":"replace","path":"/metadata/annotations","value":null},{"op":"replace","path":"/metadata/ownerReferences","value":[{"apiVersion":"networking.k8s.io/v1","kind":"Ingress","name":"1","uid":"","controller":true}]}]`), - }, - }, - }, - { - name: "update route with old owner reference", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{Items: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "extensions.k8s.io/v1beta1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - WildcardPolicy: routev1.WildcardPolicyNone, - }, - }, - }}, - }, - args: queueKey{namespace: "test", name: "1"}, - wantRoutePatches: []clientgotesting.PatchActionImpl{ - { - Name: "1-abcdef", - Patch: []byte(`[{"op":"replace","path":"/spec","value":{"host":"test.com","path":"/","to":{"kind":"","name":"service-1","weight":null},"port":{"targetPort":"http"}}},{"op":"replace","path":"/metadata/annotations","value":null},{"op":"replace","path":"/metadata/ownerReferences","value":[{"apiVersion":"networking.k8s.io/v1","kind":"Ingress","name":"1","uid":"","controller":true}]}]`), - }, - }, - }, - { - name: "update route - tls config missing", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - TLS: []networkingv1.IngressTLS{ - {Hosts: []string{"test.com"}, SecretName: "secret-1"}, - }, - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{Items: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - WildcardPolicy: routev1.WildcardPolicyNone, - }, - }, - }}, - }, - args: queueKey{namespace: "test", name: "1"}, - wantRoutePatches: []clientgotesting.PatchActionImpl{ - { - Name: "1-abcdef", - Patch: []byte(`[{"op":"replace","path":"/spec","value":{"host":"test.com","path":"/","to":{"kind":"","name":"service-1","weight":null},"port":{"targetPort":"http"},"tls":{"termination":"edge","certificate":"cert","key":"key","insecureEdgeTerminationPolicy":"Redirect"}}},{"op":"replace","path":"/metadata/annotations","value":null},{"op":"replace","path":"/metadata/ownerReferences","value":[{"apiVersion":"networking.k8s.io/v1","kind":"Ingress","name":"1","uid":"","controller":true}]}]`), - }, - }, - }, - { - name: "update route - termination policy changed to passthrough", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - Annotations: map[string]string{ - "route.openshift.io/termination": "passthrough", - }, - }, - Spec: networkingv1.IngressSpec{ - TLS: []networkingv1.IngressTLS{ - {Hosts: []string{"test.com"}, SecretName: "secret-1"}, - }, - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{Items: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - TLS: &routev1.TLSConfig{ - Termination: routev1.TLSTerminationEdge, - Certificate: "cert", - Key: "key", - InsecureEdgeTerminationPolicy: routev1.InsecureEdgeTerminationPolicyRedirect, - }, - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - WildcardPolicy: routev1.WildcardPolicyNone, - }, - }, - }}, - }, - args: queueKey{namespace: "test", name: "1"}, - wantRoutePatches: []clientgotesting.PatchActionImpl{ - { - Name: "1-abcdef", - Patch: []byte(`[{"op":"replace","path":"/spec","value":{"host":"test.com","path":"/","to":{"kind":"","name":"service-1","weight":null},"port":{"targetPort":"http"},"tls":{"termination":"passthrough","insecureEdgeTerminationPolicy":"Redirect"}}},{"op":"replace","path":"/metadata/annotations","value":{"route.openshift.io/termination":"passthrough"}},{"op":"replace","path":"/metadata/ownerReferences","value":[{"apiVersion":"networking.k8s.io/v1","kind":"Ingress","name":"1","uid":"","controller":true}]}]`), - }, - }, - }, - { - name: "update route - termination policy changed to reencrypt", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - Annotations: map[string]string{ - "route.openshift.io/termination": "reencrypt", - }, - }, - Spec: networkingv1.IngressSpec{ - TLS: []networkingv1.IngressTLS{ - {Hosts: []string{"test.com"}, SecretName: "secret-1"}, - }, - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{Items: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - TLS: &routev1.TLSConfig{ - Termination: routev1.TLSTerminationEdge, - Certificate: "cert", - Key: "key", - InsecureEdgeTerminationPolicy: routev1.InsecureEdgeTerminationPolicyRedirect, - }, - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - WildcardPolicy: routev1.WildcardPolicyNone, - }, - }, - }}, - }, - args: queueKey{namespace: "test", name: "1"}, - wantRoutePatches: []clientgotesting.PatchActionImpl{ - { - Name: "1-abcdef", - Patch: []byte(`[{"op":"replace","path":"/spec","value":{"host":"test.com","path":"/","to":{"kind":"","name":"service-1","weight":null},"port":{"targetPort":"http"},"tls":{"termination":"reencrypt","certificate":"cert","key":"key","insecureEdgeTerminationPolicy":"Redirect"}}},{"op":"replace","path":"/metadata/annotations","value":{"route.openshift.io/termination":"reencrypt"}},{"op":"replace","path":"/metadata/ownerReferences","value":[{"apiVersion":"networking.k8s.io/v1","kind":"Ingress","name":"1","uid":"","controller":true}]}]`), - }, - }, - }, - { - name: "update route - termination policy changed to reencrypt and no tls secret", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - Annotations: map[string]string{ - "route.openshift.io/termination": "reencrypt", - }, - }, - Spec: networkingv1.IngressSpec{ - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{Items: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - TLS: &routev1.TLSConfig{ - Termination: routev1.TLSTerminationEdge, - Certificate: "cert", - Key: "key", - InsecureEdgeTerminationPolicy: routev1.InsecureEdgeTerminationPolicyRedirect, - }, - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - WildcardPolicy: routev1.WildcardPolicyNone, - }, - }, - }}, - }, - args: queueKey{namespace: "test", name: "1"}, - wantRoutePatches: []clientgotesting.PatchActionImpl{ - { - Name: "1-abcdef", - Patch: []byte(`[{"op":"replace","path":"/spec","value":{"host":"test.com","path":"/","to":{"kind":"","name":"service-1","weight":null},"port":{"targetPort":"http"},"tls":{"termination":"reencrypt","insecureEdgeTerminationPolicy":"Redirect"}}},` + `{"op":"replace","path":"/metadata/annotations","value":{"route.openshift.io/termination":"reencrypt"}},{"op":"replace","path":"/metadata/ownerReferences","value":[{"apiVersion":"networking.k8s.io/v1","kind":"Ingress","name":"1","uid":"","controller":true}]}]`), - }, - }, - }, - { - name: "update route - termination policy changed to reencrypt with destCaCertCertificate", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - Annotations: map[string]string{ - "route.openshift.io/termination": "reencrypt", - "route.openshift.io/destination-ca-certificate-secret": "secret-ca-cert", - }, - }, - Spec: networkingv1.IngressSpec{ - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{Items: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - TLS: &routev1.TLSConfig{ - Termination: routev1.TLSTerminationEdge, - Certificate: "cert", - Key: "key", - InsecureEdgeTerminationPolicy: routev1.InsecureEdgeTerminationPolicyRedirect, - }, - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - WildcardPolicy: routev1.WildcardPolicyNone, - }, - }, - }}, - }, - args: queueKey{namespace: "test", name: "1"}, - wantRoutePatches: []clientgotesting.PatchActionImpl{ - { - Name: "1-abcdef", - Patch: []byte( - strings.Join( - []string{ - `[{"op":"replace","path":"/spec","value":{"host":"test.com","path":"/","to":{"kind":"","name":"service-1","weight":null},"port":{"targetPort":"http"},"tls":{"termination":"reencrypt","destinationCACertificate":"CAcert","insecureEdgeTerminationPolicy":"Redirect"}}}`, - `{"op":"replace","path":"/metadata/annotations","value":{"route.openshift.io/destination-ca-certificate-secret":"secret-ca-cert","route.openshift.io/termination":"reencrypt"}}`, - `{"op":"replace","path":"/metadata/ownerReferences","value":[{"apiVersion":"networking.k8s.io/v1","kind":"Ingress","name":"1","uid":"","controller":true}]}]`, - }, - ",", - ), - ), - }, - }, - }, - { - name: "update route - termination policy changed from reencrypt to to edge - Must clear destinationCaCertificate", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - Annotations: map[string]string{ - "route.openshift.io/termination": "edge", - }, - }, - Spec: networkingv1.IngressSpec{ - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{Items: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - Annotations: map[string]string{ - "route.openshift.io/termination": "reencrypt", - "route.openshift.io/destination-ca-certificate-secret": "secret-ca-cert", - }, - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - TLS: &routev1.TLSConfig{ - Termination: routev1.TLSTerminationReencrypt, - Certificate: "cert", - Key: "key", - InsecureEdgeTerminationPolicy: routev1.InsecureEdgeTerminationPolicyRedirect, - DestinationCACertificate: "CACert", - }, - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - WildcardPolicy: routev1.WildcardPolicyNone, - }, - }, - }}, - }, - args: queueKey{namespace: "test", name: "1"}, - wantRoutePatches: []clientgotesting.PatchActionImpl{ - { - Name: "1-abcdef", - Patch: []byte( - strings.Join( - []string{ - `[{"op":"replace","path":"/spec","value":{"host":"test.com","path":"/","to":{"kind":"","name":"service-1","weight":null},"port":{"targetPort":"http"},"tls":{"termination":"edge","insecureEdgeTerminationPolicy":"Redirect"}}}`, - `{"op":"replace","path":"/metadata/annotations","value":{"route.openshift.io/termination":"edge"}}`, - `{"op":"replace","path":"/metadata/ownerReferences","value":[{"apiVersion":"networking.k8s.io/v1","kind":"Ingress","name":"1","uid":"","controller":true}]}]`, - }, - ",", - ), - ), - }, - }, - }, - { - name: "update route - destination-ca-certificate-secret type changed", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - Annotations: map[string]string{ - "route.openshift.io/termination": "reencrypt", - "route.openshift.io/destination-ca-certificate-secret": "secret-ca-cert-opaque", - }, - }, - Spec: networkingv1.IngressSpec{ - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{Items: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - Annotations: map[string]string{ - "route.openshift.io/termination": "reencrypt", - "route.openshift.io/destination-ca-certificate-secret": "secret-ca-cert", - }, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - TLS: &routev1.TLSConfig{ - Termination: routev1.TLSTerminationEdge, - Certificate: "cert", - DestinationCACertificate: "CACert", - Key: "key", - InsecureEdgeTerminationPolicy: routev1.InsecureEdgeTerminationPolicyRedirect, - }, - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - WildcardPolicy: routev1.WildcardPolicyNone, - }, - }, - }}, - }, - args: queueKey{namespace: "test", name: "1"}, - wantRoutePatches: []clientgotesting.PatchActionImpl{ - { - Name: "1-abcdef", - Patch: []byte( - strings.Join( - []string{ - `[{"op":"replace","path":"/spec","value":{"host":"test.com","path":"/","to":{"kind":"","name":"service-1","weight":null},"port":{"targetPort":"http"},"tls":{"termination":"reencrypt","destinationCACertificate":"CAcert-from-opaque","insecureEdgeTerminationPolicy":"Redirect"}}}`, - `{"op":"replace","path":"/metadata/annotations","value":{"route.openshift.io/destination-ca-certificate-secret":"secret-ca-cert-opaque","route.openshift.io/termination":"reencrypt"}}`, - `{"op":"replace","path":"/metadata/ownerReferences","value":[{"apiVersion":"networking.k8s.io/v1","kind":"Ingress","name":"1","uid":"","controller":true}]}]`, - }, - ",", - ), - ), - }, - }, - }, - { - name: "termination policy on ingress invalid, nothing happens", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - Annotations: map[string]string{ - "route.openshift.io/termination": "Passthrough", - }, - }, - Spec: networkingv1.IngressSpec{ - TLS: []networkingv1.IngressTLS{ - {Hosts: []string{"test.com"}, SecretName: "secret-1"}, - }, - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{Items: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - Annotations: map[string]string{"route.openshift.io/termination": "Passthrough"}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - TLS: &routev1.TLSConfig{ - Termination: routev1.TLSTerminationEdge, - Certificate: "cert", - Key: "key", - InsecureEdgeTerminationPolicy: routev1.InsecureEdgeTerminationPolicyRedirect, - }, - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - WildcardPolicy: routev1.WildcardPolicyNone, - }, - }, - }}, - }, - args: queueKey{namespace: "test", name: "1"}, - }, - { - name: "termination policy on ingress invalid, disables tls", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - Annotations: map[string]string{ - "route.openshift.io/termination": "Passthrough", - }, - }, - Spec: networkingv1.IngressSpec{ - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{Items: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - TLS: &routev1.TLSConfig{ - Termination: routev1.TLSTerminationEdge, - Certificate: "cert", - Key: "key", - InsecureEdgeTerminationPolicy: routev1.InsecureEdgeTerminationPolicyRedirect, - }, - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - WildcardPolicy: routev1.WildcardPolicyNone, - }, - }, - }}, - }, - args: queueKey{namespace: "test", name: "1"}, - wantRoutePatches: []clientgotesting.PatchActionImpl{ - { - Name: "1-abcdef", - Patch: []byte(`[{"op":"replace","path":"/spec","value":{"host":"test.com","path":"/","to":{"kind":"","name":"service-1","weight":null},"port":{"targetPort":"http"}}},` + `{"op":"replace","path":"/metadata/annotations","value":{"route.openshift.io/termination":"Passthrough"}},{"op":"replace","path":"/metadata/ownerReferences","value":[{"apiVersion":"networking.k8s.io/v1","kind":"Ingress","name":"1","uid":"","controller":true}]}]`), - }, - }, - }, - { - name: "Empty tlsconfig enables edge termination without explicit cert", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - TLS: []networkingv1.IngressTLS{{Hosts: []string{"something-else"}}, {}}, - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{Items: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - WildcardPolicy: routev1.WildcardPolicyNone, - }, - }, - }}, - }, - args: queueKey{namespace: "test", name: "1"}, - wantRoutePatches: []clientgotesting.PatchActionImpl{ - { - Name: "1-abcdef", - Patch: []byte(`[{"op":"replace","path":"/spec","value":{"host":"test.com","path":"/","to":{"kind":"","name":"service-1","weight":null},"port":{"targetPort":"http"},"tls":{"termination":"edge","insecureEdgeTerminationPolicy":"Redirect"}}},{"op":"replace","path":"/metadata/annotations","value":null},{"op":"replace","path":"/metadata/ownerReferences","value":[{"apiVersion":"networking.k8s.io/v1","kind":"Ingress","name":"1","uid":"","controller":true}]}]`), - }, - }, - }, - { - name: "update route - secret values changed", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - TLS: []networkingv1.IngressTLS{ - {Hosts: []string{"test.com"}, SecretName: "secret-1a"}, - }, - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{Items: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - WildcardPolicy: routev1.WildcardPolicyNone, - TLS: &routev1.TLSConfig{ - Termination: routev1.TLSTerminationEdge, - Key: "key", - Certificate: "cert", - }, - }, - }, - }}, - }, - args: queueKey{namespace: "test", name: "1"}, - wantRoutePatches: []clientgotesting.PatchActionImpl{ - { - Name: "1-abcdef", - Patch: []byte(`[{"op":"replace","path":"/spec","value":{"host":"test.com","path":"/","to":{"kind":"","name":"service-1","weight":null},"port":{"targetPort":"http"},"tls":{"termination":"edge","certificate":"cert","key":"key2"}}},{"op":"replace","path":"/metadata/annotations","value":null},{"op":"replace","path":"/metadata/ownerReferences","value":[{"apiVersion":"networking.k8s.io/v1","kind":"Ingress","name":"1","uid":"","controller":true}]}]`), - }, - }, - }, - { - name: "no-op - has TLS", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - TLS: []networkingv1.IngressTLS{ - {Hosts: []string{"test.com"}, SecretName: "secret-1"}, - }, - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{Items: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - WildcardPolicy: routev1.WildcardPolicyNone, - TLS: &routev1.TLSConfig{ - Termination: routev1.TLSTerminationEdge, - InsecureEdgeTerminationPolicy: routev1.InsecureEdgeTerminationPolicyRedirect, - Key: "key", - Certificate: "cert", - }, - }, - }, - }}, - }, - args: queueKey{namespace: "test", name: "1"}, - }, - { - name: "no-op - has secret with empty keys", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - TLS: []networkingv1.IngressTLS{ - {Hosts: []string{"test.com"}, SecretName: "secret-3"}, - }, - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{Items: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - WildcardPolicy: routev1.WildcardPolicyNone, - TLS: &routev1.TLSConfig{ - Termination: routev1.TLSTerminationEdge, - InsecureEdgeTerminationPolicy: routev1.InsecureEdgeTerminationPolicyRedirect, - Key: "", - Certificate: "", - }, - }, - }, - }}, - }, - args: queueKey{namespace: "test", name: "1"}, - }, - { - name: "no-op - termination policy has been changed by the user", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - TLS: []networkingv1.IngressTLS{ - {Hosts: []string{"test.com"}, SecretName: "secret-1"}, - }, - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - Status: networkingv1.IngressStatus{ - LoadBalancer: v1.LoadBalancerStatus{ - Ingress: []v1.LoadBalancerIngress{ - {Hostname: "apps.foo.com"}, - }, - }, - }, - }, - }}, - r: &routeLister{Items: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - WildcardPolicy: routev1.WildcardPolicyNone, - TLS: &routev1.TLSConfig{ - Termination: routev1.TLSTerminationEdge, - Key: "key", - Certificate: "cert", - }, - }, - Status: routev1.RouteStatus{ - Ingress: []routev1.RouteIngress{ - { - RouterCanonicalHostname: "apps.foo.com", - Conditions: []routev1.RouteIngressCondition{{ - Type: routev1.RouteAdmitted, - Status: v1.ConditionTrue, - }}, - }, - }, - }, - }, - }}, - }, - args: queueKey{namespace: "test", name: "1"}, - }, - { - name: "update route - router admitted route", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - Status: networkingv1.IngressStatus{}, - }, - }}, - r: &routeLister{Items: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - WildcardPolicy: routev1.WildcardPolicyNone, - }, - Status: routev1.RouteStatus{ - Ingress: []routev1.RouteIngress{ - { - RouterCanonicalHostname: "apps.foo.com", - Conditions: []routev1.RouteIngressCondition{{ - Type: routev1.RouteAdmitted, - Status: v1.ConditionTrue, - }}, - }, - { - RouterCanonicalHostname: "apps.bar.com", - Conditions: []routev1.RouteIngressCondition{{ - Type: routev1.RouteAdmitted, - Status: v1.ConditionFalse, - }}, - }, - }, - }, - }, - }}, - }, - wantIngressUpdates: []clientgotesting.UpdateActionImpl{ - { - Object: &networkingv1.Ingress{ - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Status: networkingv1.IngressStatus{ - LoadBalancer: v1.LoadBalancerStatus{ - Ingress: []v1.LoadBalancerIngress{{ - Hostname: "apps.foo.com", - }}, - }, - }, - }, - }, - }, - args: queueKey{namespace: "test", name: "1"}, - }, - { - name: "update route - second router admitted route", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - Status: networkingv1.IngressStatus{ - LoadBalancer: v1.LoadBalancerStatus{ - Ingress: []v1.LoadBalancerIngress{ - {Hostname: "apps.foo.com"}, - }, - }, - }, - }, - }}, - r: &routeLister{Items: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - WildcardPolicy: routev1.WildcardPolicyNone, - }, - Status: routev1.RouteStatus{ - Ingress: []routev1.RouteIngress{ - { - RouterCanonicalHostname: "apps.foo.com", - Conditions: []routev1.RouteIngressCondition{{ - Type: routev1.RouteAdmitted, - Status: v1.ConditionTrue, - }}, - }, - { - RouterCanonicalHostname: "apps.bar.com", - Conditions: []routev1.RouteIngressCondition{{ - Type: routev1.RouteAdmitted, - Status: v1.ConditionTrue, - }}, - }, - }, - }, - }, - }}, - }, - wantIngressUpdates: []clientgotesting.UpdateActionImpl{ - { - Object: &networkingv1.Ingress{ - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Status: networkingv1.IngressStatus{ - LoadBalancer: v1.LoadBalancerStatus{ - Ingress: []v1.LoadBalancerIngress{ - {Hostname: "apps.bar.com"}, - {Hostname: "apps.foo.com"}, - }, - }, - }, - }, - }, - }, - args: queueKey{namespace: "test", name: "1"}, - }, - { - name: "no-op - ingress status already updated", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - Status: networkingv1.IngressStatus{ - LoadBalancer: v1.LoadBalancerStatus{ - Ingress: []v1.LoadBalancerIngress{ - {Hostname: "apps.foo.com"}, - {Hostname: "apps.bar.com"}, - }, - }, - }, - }, - }}, - r: &routeLister{Items: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - WildcardPolicy: routev1.WildcardPolicyNone, - }, - Status: routev1.RouteStatus{ - Ingress: []routev1.RouteIngress{ - { - RouterCanonicalHostname: "apps.foo.com", - Conditions: []routev1.RouteIngressCondition{{ - Type: routev1.RouteAdmitted, - Status: v1.ConditionTrue, - }}, - }, - { - RouterCanonicalHostname: "apps.bar.com", - Conditions: []routev1.RouteIngressCondition{{ - Type: routev1.RouteAdmitted, - Status: v1.ConditionTrue, - }}, - }, - }, - }, - }, - }}, - }, - args: queueKey{namespace: "test", name: "1"}, - }, - { - name: "no-op - router rejected route", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{Items: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - WildcardPolicy: routev1.WildcardPolicyNone, - }, - Status: routev1.RouteStatus{ - Ingress: []routev1.RouteIngress{{ - RouterCanonicalHostname: "apps.testcluster.com", - Conditions: []routev1.RouteIngressCondition{{ - Type: routev1.RouteAdmitted, - Status: v1.ConditionFalse, - }}, - }}, - }, - }, - }}, - }, - args: queueKey{namespace: "test", name: "1"}, - }, - { - name: "delete route when referenced secret is not TLS", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - TLS: []networkingv1.IngressTLS{ - {Hosts: []string{"test.com"}, SecretName: "secret-0"}, - }, - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - Status: networkingv1.IngressStatus{ - LoadBalancer: v1.LoadBalancerStatus{ - Ingress: []v1.LoadBalancerIngress{ - {Hostname: "apps.foo.com"}, - {Hostname: "apps.bar.com"}, - }, - }, - }, - }, - }}, - r: &routeLister{Items: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - WildcardPolicy: routev1.WildcardPolicyNone, - TLS: &routev1.TLSConfig{ - Termination: routev1.TLSTerminationEdge, - InsecureEdgeTerminationPolicy: routev1.InsecureEdgeTerminationPolicyRedirect, - Key: "key", - Certificate: "cert", - }, - }, - Status: routev1.RouteStatus{ - Ingress: []routev1.RouteIngress{{ - RouterCanonicalHostname: "apps.foo.com", - Conditions: []routev1.RouteIngressCondition{{ - Type: routev1.RouteAdmitted, - Status: v1.ConditionTrue, - }}, - }}, - }, - }, - }}, - }, - args: queueKey{namespace: "test", name: "1"}, - wantRouteDeletes: []clientgotesting.DeleteActionImpl{ - { - Name: "1-abcdef", - }, - }, - wantIngressUpdates: []clientgotesting.UpdateActionImpl{ - { - Object: &networkingv1.Ingress{ - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Status: networkingv1.IngressStatus{ - LoadBalancer: v1.LoadBalancerStatus{ - Ingress: []v1.LoadBalancerIngress{ - {Hostname: "apps.bar.com"}, - }, - }, - }, - }, - }, - }, - }, - { - name: "delete route when referenced secret is not valid", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - TLS: []networkingv1.IngressTLS{ - {Hosts: []string{"test.com"}, SecretName: "secret-2"}, - }, - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - Status: networkingv1.IngressStatus{ - LoadBalancer: v1.LoadBalancerStatus{ - Ingress: []v1.LoadBalancerIngress{ - {Hostname: "apps.foo.com"}, - }, - }, - }, - }, - }}, - r: &routeLister{Items: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - WildcardPolicy: routev1.WildcardPolicyNone, - TLS: &routev1.TLSConfig{ - Termination: routev1.TLSTerminationEdge, - InsecureEdgeTerminationPolicy: routev1.InsecureEdgeTerminationPolicyRedirect, - Key: "key", - Certificate: "", - }, - }, - Status: routev1.RouteStatus{ - Ingress: []routev1.RouteIngress{{ - RouterCanonicalHostname: "apps.foo.com", - Conditions: []routev1.RouteIngressCondition{{ - Type: routev1.RouteAdmitted, - Status: v1.ConditionTrue, - }}, - }}, - }, - }, - }}, - }, - args: queueKey{namespace: "test", name: "1"}, - wantRouteDeletes: []clientgotesting.DeleteActionImpl{ - { - Name: "1-abcdef", - }, - }, - wantIngressUpdates: []clientgotesting.UpdateActionImpl{ - { - Object: &networkingv1.Ingress{ - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Status: networkingv1.IngressStatus{ - LoadBalancer: v1.LoadBalancerStatus{ - Ingress: []v1.LoadBalancerIngress{}, - }, - }, - }, - }, - }, - }, - { - name: "ignore route when parent ingress no longer exists (gc will handle)", - fields: fields{ - i: &ingressLister{}, - r: &routeLister{Items: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{}, - }, - }}, - }, - args: queueKey{namespace: "test", name: "1"}, - }, - { - name: "update route - termination policy changed to passthrough and timeout set", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - Annotations: map[string]string{ - "route.openshift.io/termination": "passthrough", - "haproxy.router.openshift.io/timeout": "6m", - }, - }, - Spec: networkingv1.IngressSpec{ - TLS: []networkingv1.IngressTLS{ - {Hosts: []string{"test.com"}, SecretName: "secret-1"}, - }, - Rules: []networkingv1.IngressRule{ - { - Host: "test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathTypePrefix, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{Items: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "test.com", - Path: "/", - TLS: &routev1.TLSConfig{ - Termination: routev1.TLSTerminationEdge, - Certificate: "cert", - Key: "key", - InsecureEdgeTerminationPolicy: routev1.InsecureEdgeTerminationPolicyRedirect, - }, - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - WildcardPolicy: routev1.WildcardPolicyNone, - }, - }, - }}, - }, - args: queueKey{namespace: "test", name: "1"}, - wantRoutePatches: []clientgotesting.PatchActionImpl{ - { - Name: "1-abcdef", - Patch: []byte(`[{"op":"replace","path":"/spec","value":{"host":"test.com","path":"/","to":{"kind":"","name":"service-1","weight":null},"port":{"targetPort":"http"},"tls":{"termination":"passthrough","insecureEdgeTerminationPolicy":"Redirect"}}},{"op":"replace","path":"/metadata/annotations","value":{"haproxy.router.openshift.io/timeout":"6m","route.openshift.io/termination":"passthrough"}},{"op":"replace","path":"/metadata/ownerReferences","value":[{"apiVersion":"networking.k8s.io/v1","kind":"Ingress","name":"1","uid":"","controller":true}]}]`), - }, - }, - }, - { - name: "create wildcard route - targetPort string, service port with name", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - Rules: []networkingv1.IngressRule{ - { - Host: "*.test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-2", - Port: networkingv1.ServiceBackendPort{ - Number: 80, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{}, - }, - args: queueKey{namespace: "test", name: "1"}, - wantExpects: []queueKey{{namespace: "test", name: "1"}}, - wantRouteCreates: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "wildcard.test.com", - Path: "/", - To: routev1.RouteTargetReference{ - Name: "service-2", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("80-tcp")}, - WildcardPolicy: "Subdomain", - }, - }, - }, - }, - { - name: "create wildcard route with TLS config", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - TLS: []networkingv1.IngressTLS{ - {Hosts: []string{"*.test.com"}, SecretName: "secret-1"}, - }, - Rules: []networkingv1.IngressRule{ - { - Host: "*.test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{}, - }, - args: queueKey{namespace: "test", name: "1"}, - wantExpects: []queueKey{{namespace: "test", name: "1"}}, - wantRouteCreates: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "wildcard.test.com", - Path: "/", - TLS: &routev1.TLSConfig{ - Termination: routev1.TLSTerminationEdge, - InsecureEdgeTerminationPolicy: routev1.InsecureEdgeTerminationPolicyRedirect, - Key: "key", - Certificate: "cert", - }, - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("http"), - }, - WildcardPolicy: "Subdomain", - }, - }, - }, - }, - { - name: "update wildcard route ", - fields: fields{ - i: &ingressLister{Items: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1", - Namespace: "test", - }, - Spec: networkingv1.IngressSpec{ - Rules: []networkingv1.IngressRule{ - { - Host: "*.test.com", - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "service-1", - Port: networkingv1.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - r: &routeLister{Items: []*routev1.Route{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "1-abcdef", - Namespace: "test", - OwnerReferences: []metav1.OwnerReference{{APIVersion: "networking.k8s.io/v1", Kind: "Ingress", Name: "1", Controller: &boolTrue}}, - }, - Spec: routev1.RouteSpec{ - Host: "wildcard.test.com", - Path: "/", - To: routev1.RouteTargetReference{ - Name: "service-1", - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromInt(80), - }, - WildcardPolicy: routev1.WildcardPolicySubdomain, - }, - }, - }}, - }, - args: queueKey{namespace: "test", name: "1"}, - wantRoutePatches: []clientgotesting.PatchActionImpl{ - { - Name: "1-abcdef", - Patch: []byte(`[{"op":"replace","path":"/spec","value":{"host":"wildcard.test.com","path":"/","to":{"kind":"","name":"service-1","weight":null},"port":{"targetPort":"http"},"wildcardPolicy":"Subdomain"}},{"op":"replace","path":"/metadata/annotations","value":null},{"op":"replace","path":"/metadata/ownerReferences","value":[{"apiVersion":"networking.k8s.io/v1","kind":"Ingress","name":"1","uid":"","controller":true}]}]`), - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var names []string - routeClientset := &routev1fake.Clientset{} - routeClientset.AddReactor("*", "routes", func(action clientgotesting.Action) (handled bool, ret runtime.Object, err error) { - switch a := action.(type) { - case clientgotesting.CreateAction: - obj := a.GetObject().DeepCopyObject() - m := obj.(metav1.Object) - if len(m.GetName()) == 0 { - m.SetName(m.GetGenerateName()) - } - names = append(names, m.GetName()) - return true, obj, nil - } - return true, nil, nil - }) - kc := fake.NewSimpleClientset() - kc.PrependReactor("*", "ingresses", func(action clientgotesting.Action) (handled bool, ret runtime.Object, err error) { - return true, nil, nil - }) - - c := &Controller{ - queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "ingress-to-route-test"), - routeClient: routeClientset.RouteV1(), - ingressClient: kc.NetworkingV1(), - ingressLister: tt.fields.i, - ingressclassLister: tt.fields.ic, - routeLister: tt.fields.r, - secretLister: tt.fields.s, - serviceLister: tt.fields.svc, - expectations: tt.expects, - } - // default these - if c.expectations == nil { - c.expectations = newExpectations() - } - if c.ingressclassLister == nil { - c.ingressclassLister = ingressclasses - } - if c.secretLister == nil { - c.secretLister = secrets - } - if c.serviceLister == nil { - c.serviceLister = services - } - - if err := c.sync(tt.args); (err != nil) != tt.wantErr { - t.Errorf("Controller.sync() error = %v, wantErr %v", err, tt.wantErr) - } - - c.queue.ShutDown() - var hasQueue []queueKey - for { - key, shutdown := c.queue.Get() - if shutdown { - break - } - hasQueue = append(hasQueue, key.(queueKey)) - } - if !reflect.DeepEqual(tt.wantQueue, hasQueue) { - t.Errorf("unexpected queue: %s", diff.ObjectReflectDiff(tt.wantQueue, hasQueue)) - } - - wants := tt.wantExpectation - if wants == nil { - wants = newTestExpectations(func(e *expectations) { - for _, key := range tt.wantExpects { - for _, routeName := range names { - e.Expect(key.namespace, key.name, routeName) - } - } - }) - } - if !reflect.DeepEqual(wants, c.expectations) { - t.Errorf("unexpected expectations: %s", diff.ObjectReflectDiff(wants.expect, c.expectations.expect)) - } - - routeActions := routeClientset.Actions() - - for i := range tt.wantRouteCreates { - if i > len(routeActions)-1 { - t.Fatalf("Controller.sync() unexpected action[%d]: %#v", i, tt.wantRouteCreates[i]) - } - if routeActions[i].GetVerb() != "create" { - t.Fatalf("Controller.sync() unexpected action[%d]: %#v", i, tt.wantRouteCreates[i]) - } - action := routeActions[i].(clientgotesting.CreateAction) - if action.GetNamespace() != tt.args.namespace { - t.Errorf("unexpected action[%d]: %#v", i, action) - } - obj := action.GetObject() - if tt.wantRouteCreates[i].Name == "" { - tt.wantRouteCreates[i].Name = names[0] - names = names[1:] - } - if !reflect.DeepEqual(tt.wantRouteCreates[i], obj) { - t.Errorf("unexpected create: %s", diff.ObjectReflectDiff(tt.wantRouteCreates[i], obj)) - } - } - routeActions = routeActions[len(tt.wantRouteCreates):] - - for i := range tt.wantRoutePatches { - if i > len(routeActions)-1 { - t.Fatalf("Controller.sync() unexpected actions: %#v", routeClientset.Actions()) - } - if routeActions[i].GetVerb() != "patch" { - t.Fatalf("Controller.sync() unexpected actions: %#v", routeClientset.Actions()) - } - action := routeActions[i].(clientgotesting.PatchAction) - if action.GetNamespace() != tt.args.namespace || action.GetName() != tt.wantRoutePatches[i].Name { - t.Errorf("unexpected action[%d]: %#v", i, action) - } - if !reflect.DeepEqual(string(action.GetPatch()), string(tt.wantRoutePatches[i].Patch)) { - t.Errorf("unexpected action[%d]: %s", i, string(action.GetPatch())) - } - } - routeActions = routeActions[len(tt.wantRoutePatches):] - - for i := range tt.wantRouteDeletes { - if i > len(routeActions)-1 { - t.Fatalf("Controller.sync() unexpected actions: %#v", routeClientset.Actions()) - } - if routeActions[i].GetVerb() != "delete" { - t.Fatalf("Controller.sync() unexpected actions: %#v", routeClientset.Actions()) - } - action := routeActions[i].(clientgotesting.DeleteAction) - if action.GetName() != tt.wantRouteDeletes[i].Name || action.GetNamespace() != tt.args.namespace { - t.Errorf("unexpected action[%d]: %#v", i, action) - } - } - routeActions = routeActions[len(tt.wantRouteDeletes):] - - if len(routeActions) != 0 { - t.Fatalf("Controller.sync() unexpected actions: %#v", routeActions) - } - - ingressActions := kc.Actions() - for i := range tt.wantIngressUpdates { - if i > len(ingressActions)-1 { - t.Fatalf("Controller.sync() unexpected actions: %#v", kc.Actions()) - } - if ingressActions[i].GetVerb() != "update" { - t.Fatalf("Controller.sync() unexpected actions: %#v", kc.Actions()) - } - action := ingressActions[i].(clientgotesting.UpdateAction) - ingress, ok := action.GetObject().(*networkingv1.Ingress) - if !ok { - t.Fatalf("Controller.sync() unexpected actions: %#v", kc.Actions()) - } - if ingress.Name != tt.wantIngressUpdates[i].Object.(*networkingv1.Ingress).Name || ingress.Namespace != tt.args.namespace || !reflect.DeepEqual(ingress.Status, tt.wantIngressUpdates[i].Object.(*networkingv1.Ingress).Status) { - t.Errorf("unexpected ingress action[%d]: %#v", i, action) - } - } - ingressActions = ingressActions[len(tt.wantIngressUpdates):] - if len(ingressActions) != 0 { - t.Fatalf("Controller.sync() unexpected actions: %#v", ingressActions) - } - }) - } -} diff --git a/pkg/route/ingressip/service_ingressip_controller.go b/pkg/route/ingressip/service_ingressip_controller.go deleted file mode 100644 index 666c5cd37..000000000 --- a/pkg/route/ingressip/service_ingressip_controller.go +++ /dev/null @@ -1,693 +0,0 @@ -package ingressip - -import ( - "context" - "fmt" - "net" - "sort" - "sync" - "time" - - "k8s.io/klog/v2" - - v1 "k8s.io/api/core/v1" - kerrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/wait" - kclientset "k8s.io/client-go/kubernetes" - kcoreclient "k8s.io/client-go/kubernetes/typed/core/v1" - kv1core "k8s.io/client-go/kubernetes/typed/core/v1" - "k8s.io/client-go/tools/cache" - "k8s.io/client-go/tools/record" - "k8s.io/client-go/util/workqueue" - "k8s.io/kubernetes/pkg/api/legacyscheme" - "k8s.io/kubernetes/pkg/controller" - "k8s.io/kubernetes/pkg/registry/core/service/allocator" - "k8s.io/kubernetes/pkg/registry/core/service/ipallocator" -) - -const ( - // It's necessary to allocate for the initial sync in consistent - // order rather than in the order received. This requires waiting - // until the initial sync has been processed, and to avoid a hot - // loop, we'll wait this long between checks. - SyncProcessedPollPeriod = 100 * time.Millisecond - - clientRetryCount = 5 - clientRetryInterval = 5 * time.Second - clientRetryFactor = 1.1 -) - -// IngressIPController is responsible for allocating ingress ip -// addresses to Service objects of type LoadBalancer. -type IngressIPController struct { - client kcoreclient.ServicesGetter - - controller cache.Controller - hasSynced cache.InformerSynced - - maxRetries int - - // Tracks ip allocation for the configured range - ipAllocator *ipallocator.Range - // Tracks ip -> service key to allow detection of duplicate ip - // allocations. - allocationMap map[string]string - - // Tracks services requeued for allocation when the range is full. - requeuedAllocations sets.String - - // Protects the transition between initial sync and regular processing - lock sync.Mutex - cache cache.Store - queue workqueue.RateLimitingInterface - - // recorder is used to record events. - recorder record.EventRecorder - - // changeHandler does the work. It can be factored out for unit testing. - changeHandler func(change *serviceChange) error - // persistenceHandler persists service changes. It can be factored out for unit testing - persistenceHandler func(client kcoreclient.ServicesGetter, service *v1.Service, targetStatus bool) error -} - -type serviceChange struct { - key string - oldService *v1.Service - requeuedAllocation bool -} - -// NewIngressIPController creates a new IngressIPController. -// TODO this should accept a shared informer -func NewIngressIPController(services cache.SharedIndexInformer, kc kclientset.Interface, ipNet *net.IPNet, resyncInterval time.Duration) *IngressIPController { - eventBroadcaster := record.NewBroadcaster() - eventBroadcaster.StartRecordingToSink(&kv1core.EventSinkImpl{Interface: kc.CoreV1().Events("")}) - recorder := eventBroadcaster.NewRecorder(legacyscheme.Scheme, v1.EventSource{Component: "ingressip-controller"}) - - ic := &IngressIPController{ - client: kc.CoreV1(), - queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "ingressip"), - maxRetries: 10, - recorder: recorder, - } - - ic.cache = services.GetStore() - ic.controller = services.GetController() - services.AddEventHandlerWithResyncPeriod( - cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { - ic.enqueueChange(obj, nil) - }, - UpdateFunc: func(old, cur interface{}) { - ic.enqueueChange(cur, old) - }, - DeleteFunc: func(obj interface{}) { - ic.enqueueChange(nil, obj) - }, - }, - resyncInterval, - ) - ic.hasSynced = ic.controller.HasSynced - - ic.changeHandler = ic.processChange - ic.persistenceHandler = persistService - - ic.ipAllocator, _ = ipallocator.New(ipNet, func(max int, rangeSpec string, offset int) (allocator.Interface, error) { - return allocator.NewAllocationMap(max, rangeSpec), nil - }) - - ic.allocationMap = make(map[string]string) - ic.requeuedAllocations = sets.NewString() - - return ic -} - -// enqueueChange transforms the old and new objects into a change -// object and queues it. A lock is shared with processInitialSync to -// avoid enqueueing while the changes from the initial sync are being -// processed. -func (ic *IngressIPController) enqueueChange(new interface{}, old interface{}) { - ic.lock.Lock() - defer ic.lock.Unlock() - - change := &serviceChange{} - - if new != nil { - // Queue the key needed to retrieve the lastest state from the - // cache when the change is processed. - key, err := controller.KeyFunc(new) - if err != nil { - utilruntime.HandleError(fmt.Errorf("Couldn't get key for object %+v: %v", new, err)) - return - } - change.key = key - } - - if old != nil { - service, ok := old.(*v1.Service) - if !ok { - tombstone, ok := old.(cache.DeletedFinalStateUnknown) - if !ok { - utilruntime.HandleError(fmt.Errorf("couldn't get object from tombstone %#v", old)) - return - } - service, ok = tombstone.Obj.(*v1.Service) - if !ok { - utilruntime.HandleError(fmt.Errorf("tombstone contained unexpected object %#v", old)) - return - } - } - change.oldService = service - } - - ic.queue.Add(change) -} - -// Run begins watching and syncing. -func (ic *IngressIPController) Run(stopCh <-chan struct{}) { - defer utilruntime.HandleCrash() - defer ic.queue.ShutDown() - - klog.V(5).Infof("Waiting for the initial sync to be completed") - if !cache.WaitForCacheSync(stopCh, ic.hasSynced) { - return - } - - if !ic.processInitialSync() { - return - } - - klog.V(5).Infof("Initial sync completed, starting worker") - for ic.work() { - var done bool - select { - case _, ok := <-stopCh: - done = !ok - default: - } - if done { - break - } - } - - klog.V(1).Infof("Shutting down ingress ip controller") -} - -type serviceAge []*v1.Service - -func (s serviceAge) Len() int { return len(s) } -func (s serviceAge) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s serviceAge) Less(i, j int) bool { - if s[i].CreationTimestamp.Before(&s[j].CreationTimestamp) { - return true - } - return (s[i].CreationTimestamp == s[j].CreationTimestamp && s[i].UID < s[j].UID) -} - -// processInitialSync processes the items queued by informer's initial sync. -// A lock is shared between this method and enqueueService to ensure -// that queue additions are blocked while the sync is being processed. -// Returns a boolean indication of whether processing should continue. -func (ic *IngressIPController) processInitialSync() bool { - ic.lock.Lock() - defer ic.lock.Unlock() - - klog.V(5).Infof("Processing initial sync") - - // Track services that need to be processed after existing - // allocations are recorded. - var pendingServices []*v1.Service - - // Track post-sync changes that need to be added back the queue - // after allocations are recorded. These changes may end up in - // the queue if watch events were queued before completion of the - // initial sync was detected. - var pendingChanges []*serviceChange - - // Drain the queue. Len() should be safe because enqueueService - // requires the same lock held by this method. - for ic.queue.Len() > 0 { - item, quit := ic.queue.Get() - if quit { - return false - } - ic.queue.Done(item) - ic.queue.Forget(item) - change := item.(*serviceChange) - - // The initial sync only includes additions, so if an update - // or delete is seen (indicated by the presence of oldService), - // it and all subsequent changes are post-sync watch events that - // should be queued without processing. - postSyncChange := change.oldService != nil || len(pendingChanges) > 0 - if postSyncChange { - pendingChanges = append(pendingChanges, change) - continue - } - - service := ic.getCachedService(change.key) - if service == nil { - // Service was deleted - continue - } - - if service.Spec.Type == v1.ServiceTypeLoadBalancer { - // Save for subsequent addition back to the queue to - // ensure persistent state is updated during regular - // processing. - pendingServices = append(pendingServices, service) - - if len(service.Status.LoadBalancer.Ingress) > 0 { - // The service has an existing allocation - ipString := service.Status.LoadBalancer.Ingress[0].IP - // Return values indicating that reallocation is - // necessary or that an error occurred can be ignored - // since the service will be processed again. - ic.recordLocalAllocation(change.key, ipString) - } - } - } - - // Add pending service additions back to the queue in consistent order. - sort.Sort(serviceAge(pendingServices)) - for _, service := range pendingServices { - if key, err := controller.KeyFunc(service); err == nil { - klog.V(5).Infof("Adding service back to queue: %v ", key) - change := &serviceChange{key: key} - ic.queue.Add(change) - } else { - // This error should have been caught by enqueueService - utilruntime.HandleError(fmt.Errorf("Couldn't get key for service %+v: %v", service, err)) - continue - } - } - - // Add watch events back to the queue - for _, change := range pendingChanges { - ic.queue.Add(change) - } - - klog.V(5).Infof("Completed processing initial sync") - - return true -} - -// getCachedService logs if unable to retrieve a service for the given key. -func (ic *IngressIPController) getCachedService(key string) *v1.Service { - if len(key) == 0 { - return nil - } - if obj, exists, err := ic.cache.GetByKey(key); err != nil { - klog.V(5).Infof("Unable to retrieve service %v from store: %v", key, err) - } else if !exists { - klog.V(6).Infof("Service %v has been deleted", key) - } else { - return obj.(*v1.Service) - } - return nil -} - -// recordLocalAllocation attempts to update local state for the given -// service key and ingress ip. Returns a boolean indication of -// whether reallocation is necessary and an error indicating the -// reason for reallocation. If reallocation is not indicated, a -// non-nil error indicates an exceptional condition. -func (ic *IngressIPController) recordLocalAllocation(key, ipString string) (reallocate bool, err error) { - ip := net.ParseIP(ipString) - if ip == nil { - return true, fmt.Errorf("Service %v has an invalid ingress ip %v. A new ip will be allocated.", key, ipString) - } - - ipKey, ok := ic.allocationMap[ipString] - switch { - case ok && ipKey == key: - // Allocation exists for this service - return false, nil - case ok && ipKey != key: - // TODO prefer removing the allocation from a service that does not have a matching LoadBalancerIP - return true, fmt.Errorf("Another service is using ingress ip %v. A new ip will be allocated for %v.", ipString, key) - } - - err = ic.ipAllocator.Allocate(ip) - if _, ok := err.(*ipallocator.ErrNotInRange); ok { - return true, fmt.Errorf("The ingress ip %v for service %v is not in the ingress range. A new ip will be allocated.", ipString, key) - } else if err != nil { - // The only other error that Allocate() can throw is ErrAllocated, but that - // should not happen after the check against the allocation map. - return false, fmt.Errorf("Unexpected error from ip allocator for service %v: %v", key, err) - } - ic.allocationMap[ipString] = key - klog.V(5).Infof("Recorded allocation of ip %v for service %v", ipString, key) - return false, nil -} - -// work dispatches the next item in the queue to the change handler. -// If the change handler returns an error, the change will be added to -// the end of the queue to be processed again. Returns a boolean -// indication of whether processing should continue. -func (ic *IngressIPController) work() bool { - item, quit := ic.queue.Get() - if quit { - return false - } - change := item.(*serviceChange) - defer ic.queue.Done(change) - - if change.requeuedAllocation { - // Reset the allocation state so that the change can be - // requeued if necessary. Only additions/updates are requeued - // for allocation so change.key should be non-empty. - change.requeuedAllocation = false - ic.requeuedAllocations.Delete(change.key) - } - - if err := ic.changeHandler(change); err == nil { - // No further processing required - ic.queue.Forget(change) - } else { - if err == ipallocator.ErrFull { - // When the range is full, avoid requeueing more than a - // single change requiring allocation per service. - // Otherwise the queue could grow without bounds as every - // service update would add another change that would be - // endlessly requeued. - if ic.requeuedAllocations.Has(change.key) { - return true - } - change.requeuedAllocation = true - ic.requeuedAllocations.Insert(change.key) - service := ic.getCachedService(change.key) - if service != nil { - ic.recorder.Eventf(service, v1.EventTypeWarning, "IngressIPRangeFull", "No available ingress ip to allocate to service %s", change.key) - } - } - // Failed but can be retried - utilruntime.HandleError(fmt.Errorf("error syncing service, it will be retried: %v", err)) - ic.queue.AddRateLimited(change) - } - - return true -} - -// processChange responds to a service change by synchronizing the -// local and persisted ingress ip allocation state of the service. -func (ic *IngressIPController) processChange(change *serviceChange) error { - service := ic.getCachedService(change.key) - - ic.clearOldAllocation(service, change.oldService) - - if service == nil { - // Service was deleted - no further processing required - return nil - } - - typeLoadBalancer := service.Spec.Type == v1.ServiceTypeLoadBalancer - hasAllocation := len(service.Status.LoadBalancer.Ingress) > 0 - switch { - case typeLoadBalancer && hasAllocation: - return ic.recordAllocation(service, change.key) - case typeLoadBalancer && !hasAllocation: - return ic.allocate(service, change.key) - case !typeLoadBalancer && hasAllocation: - return ic.deallocate(service, change.key) - default: - return nil - } -} - -// clearOldAllocation clears the old allocation for a service if it -// differs from a new allocation. Returns a boolean indication of -// whether the old allocation was cleared. -func (ic *IngressIPController) clearOldAllocation(new, old *v1.Service) bool { - oldIP := "" - if old != nil && old.Spec.Type == v1.ServiceTypeLoadBalancer && len(old.Status.LoadBalancer.Ingress) > 0 { - oldIP = old.Status.LoadBalancer.Ingress[0].IP - } - noOldAllocation := len(oldIP) == 0 - if noOldAllocation { - return false - } - - newIP := "" - if new != nil && new.Spec.Type == v1.ServiceTypeLoadBalancer && len(new.Status.LoadBalancer.Ingress) > 0 { - newIP = new.Status.LoadBalancer.Ingress[0].IP - } - allocationUnchanged := newIP == oldIP - if allocationUnchanged { - return false - } - - // New allocation differs from old due to update or deletion - - // Get the key from the old service since the new service may be nil - if key, err := controller.KeyFunc(old); err == nil { - ic.clearLocalAllocation(key, oldIP) - return true - } else { - // Recovery/retry not possible for this error - utilruntime.HandleError(fmt.Errorf("Couldn't get key for object %+v: %v", old, err)) - return false - } -} - -// recordAllocation updates local state with the ingress ip indicated -// in a service's status and ensures that the ingress ip appears in -// the service's list of external ips. If the service's ingress ip is -// invalid for any reason, a new ip will be allocated. -func (ic *IngressIPController) recordAllocation(service *v1.Service, key string) error { - // If more than one ingress ip is present, it will be ignored - ipString := service.Status.LoadBalancer.Ingress[0].IP - - reallocate, err := ic.recordLocalAllocation(key, ipString) - if !reallocate && err != nil { - return err - } - reallocateMessage := "" - if err != nil { - reallocateMessage = err.Error() - } - - // Make a copy to modify to avoid mutating cache state - serviceCopy := service.DeepCopy() - - if reallocate { - // TODO update the external ips but not the status since - // allocate() will overwrite any existing allocation. - if err = ic.clearPersistedAllocation(serviceCopy, key, reallocateMessage); err != nil { - return err - } - ic.recorder.Eventf(serviceCopy, v1.EventTypeWarning, "IngressIPReallocated", reallocateMessage) - return ic.allocate(serviceCopy, key) - } else { - // Ensure that the ingress ip is present in the service's spec. - return ic.ensureExternalIP(serviceCopy, key, ipString) - } -} - -// allocate assigns an unallocated ip to a service and updates the -// service's persisted state. -func (ic *IngressIPController) allocate(service *v1.Service, key string) error { - // Make a copy to avoid mutating cache state - serviceCopy := service.DeepCopy() - - ip, err := ic.allocateIP(serviceCopy.Spec.LoadBalancerIP) - if err != nil { - return err - } - ipString := ip.String() - - klog.V(5).Infof("Allocating ip %v to service %v", ipString, key) - serviceCopy.Status = v1.ServiceStatus{ - LoadBalancer: v1.LoadBalancerStatus{ - Ingress: []v1.LoadBalancerIngress{ - { - IP: ipString, - }, - }, - }, - } - if err = ic.persistServiceStatus(serviceCopy); err != nil { - if releaseErr := ic.ipAllocator.Release(ip); releaseErr != nil { - // Release from contiguous allocator should never return an error, but just in case... - utilruntime.HandleError(fmt.Errorf("Error releasing ip %v for service %v: %v", ipString, key, releaseErr)) - } - return err - } - ic.allocationMap[ipString] = key - - return ic.ensureExternalIP(serviceCopy, key, ipString) -} - -// deallocate ensures that the ip currently allocated to a service is -// removed and that its loadbalancer status is cleared. -func (ic *IngressIPController) deallocate(service *v1.Service, key string) error { - klog.V(5).Infof("Clearing allocation state for %v", key) - - // Make a copy to modify to avoid mutating cache state - serviceCopy := service.DeepCopy() - - // Get the ingress ip to remove from local allocation state before - // it is removed from the service. - ipString := serviceCopy.Status.LoadBalancer.Ingress[0].IP - - if err := ic.clearPersistedAllocation(serviceCopy, key, ""); err != nil { - return err - } - - ic.clearLocalAllocation(key, ipString) - return nil -} - -// clearLocalAllocation clears an in-memory allocation if it belongs -// to the specified service key. -func (ic *IngressIPController) clearLocalAllocation(key, ipString string) bool { - klog.V(5).Infof("Attempting to clear local allocation of ip %v for service %v", ipString, key) - - ip := net.ParseIP(ipString) - if ip == nil { - // An invalid ip address cannot be deallocated - utilruntime.HandleError(fmt.Errorf("Error parsing ip: %v", ipString)) - return false - } - - ipKey, ok := ic.allocationMap[ipString] - switch { - case !ok: - klog.V(6).Infof("IP address %v is not currently allocated", ipString) - return false - case key != ipKey: - klog.V(6).Infof("IP address %v is not allocated to service %v", ipString, key) - return false - } - - // Remove allocation - if err := ic.ipAllocator.Release(ip); err != nil { - // Release from contiguous allocator should never return an error. - utilruntime.HandleError(fmt.Errorf("Error releasing ip %v for service %v: %v", ipString, key, err)) - return false - } - delete(ic.allocationMap, ipString) - klog.V(5).Infof("IP address %v is now available for allocation", ipString) - return true -} - -// clearPersistedAllocation ensures there is no ingress ip in the -// service's spec and that the service's status is cleared. -func (ic *IngressIPController) clearPersistedAllocation(service *v1.Service, key, errMessage string) error { - // Assume it is safe to modify the service without worrying about changing the local cache - - if len(errMessage) > 0 { - utilruntime.HandleError(fmt.Errorf(errMessage)) - } else { - klog.V(5).Infof("Attempting to clear persisted allocation for service: %v", key) - } - - // An ingress ip is only allowed in ExternalIPs when a - // corresponding status exists, so update the spec first to avoid - // failing admission control. - ingressIP := service.Status.LoadBalancer.Ingress[0].IP - for i, ip := range service.Spec.ExternalIPs { - if ip == ingressIP { - klog.V(5).Infof("Removing ip %v from the external ips of service %v", ingressIP, key) - service.Spec.ExternalIPs = append(service.Spec.ExternalIPs[:i], service.Spec.ExternalIPs[i+1:]...) - if err := ic.persistServiceSpec(service); err != nil { - return err - } - break - } - } - - service.Status.LoadBalancer = v1.LoadBalancerStatus{} - klog.V(5).Infof("Clearing the load balancer status of service: %v", key) - return ic.persistServiceStatus(service) -} - -// ensureExternalIP ensures that the provided service has the ingress -// ip persisted as an external ip. -func (ic *IngressIPController) ensureExternalIP(service *v1.Service, key, ingressIP string) error { - // Assume it is safe to modify the service without worrying about changing the local cache - - ipExists := false - for _, ip := range service.Spec.ExternalIPs { - if ip == ingressIP { - ipExists = true - klog.V(6).Infof("Service %v already has ip %v as an external ip", key, ingressIP) - break - } - } - if !ipExists { - service.Spec.ExternalIPs = append(service.Spec.ExternalIPs, ingressIP) - klog.V(5).Infof("Adding ip %v to service %v as an external ip", ingressIP, key) - return ic.persistServiceSpec(service) - } - return nil -} - -// allocateIP attempts to allocate the requested ip, and if that is -// not possible, allocates the next available address. -func (ic *IngressIPController) allocateIP(requestedIP string) (net.IP, error) { - if len(requestedIP) == 0 { - // Specific ip not requested - return ic.ipAllocator.AllocateNext() - } - var ip net.IP - if ip = net.ParseIP(requestedIP); ip == nil { - // Invalid ip - return ic.ipAllocator.AllocateNext() - } - if err := ic.ipAllocator.Allocate(ip); err != nil { - // Unable to allocate requested ip - return ic.ipAllocator.AllocateNext() - } - // Allocated requested ip - return ip, nil -} - -func (ic *IngressIPController) persistServiceSpec(service *v1.Service) error { - return ic.persistenceHandler(ic.client, service, false) -} - -func (ic *IngressIPController) persistServiceStatus(service *v1.Service) error { - return ic.persistenceHandler(ic.client, service, true) -} - -func persistService(client kcoreclient.ServicesGetter, service *v1.Service, targetStatus bool) error { - backoff := wait.Backoff{ - Steps: clientRetryCount, - Duration: clientRetryInterval, - Factor: clientRetryFactor, - } - return wait.ExponentialBackoff(backoff, func() (bool, error) { - var err error - if targetStatus { - _, err = client.Services(service.Namespace).UpdateStatus(context.TODO(), service, metav1.UpdateOptions{}) - } else { - _, err = client.Services(service.Namespace).Update(context.TODO(), service, metav1.UpdateOptions{}) - } - switch { - case err == nil: - return true, nil - case kerrors.IsNotFound(err): - // If the service no longer exists, we don't want to recreate - // it. Just bail out so that we can process the delete, which - // we should soon be receiving if we haven't already. - klog.V(5).Infof("Not persisting update to service '%s/%s' that no longer exists: %v", - service.Namespace, service.Name, err) - return true, nil - case kerrors.IsConflict(err): - // TODO: Try to resolve the conflict if the change was - // unrelated to load balancer status. For now, just rely on - // the fact that we'll also process the update that caused the - // resource version to change. - klog.V(5).Infof("Not persisting update to service '%s/%s' that has been changed since we received it: %v", - service.Namespace, service.Name, err) - return true, nil - default: - err = fmt.Errorf("Failed to persist updated LoadBalancerStatus to service '%s/%s': %v", - service.Namespace, service.Name, err) - return false, err - } - }) -} diff --git a/pkg/route/ingressip/service_ingressip_controller_test.go b/pkg/route/ingressip/service_ingressip_controller_test.go deleted file mode 100644 index c297d6198..000000000 --- a/pkg/route/ingressip/service_ingressip_controller_test.go +++ /dev/null @@ -1,622 +0,0 @@ -package ingressip - -import ( - "context" - "errors" - "fmt" - "net" - "reflect" - "testing" - "time" - - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/wait" - informers "k8s.io/client-go/informers" - "k8s.io/client-go/kubernetes/fake" - kcoreclient "k8s.io/client-go/kubernetes/typed/core/v1" - clientgotesting "k8s.io/client-go/testing" - "k8s.io/client-go/tools/cache" - "k8s.io/client-go/util/workqueue" - "k8s.io/kubernetes/pkg/controller" - "k8s.io/kubernetes/pkg/registry/core/service/ipallocator" -) - -const namespace = "ns" - -func newController(t *testing.T, client *fake.Clientset, stopCh <-chan struct{}) *IngressIPController { - _, ipNet, err := net.ParseCIDR("172.16.0.12/28") - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if client == nil { - client = fake.NewSimpleClientset() - } - informerFactory := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc()) - controller := NewIngressIPController( - informerFactory.Core().V1().Services().Informer(), - client, ipNet, 10*time.Minute, - ) - informerFactory.Start(stopCh) - if !cache.WaitForCacheSync(stopCh, controller.hasSynced) { - t.Fatalf("did not sync") - } - return controller -} - -func newService(name, ip string, typeLoadBalancer bool) *v1.Service { - serviceType := v1.ServiceTypeClusterIP - if typeLoadBalancer { - serviceType = v1.ServiceTypeLoadBalancer - } - service := &v1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - }, - Spec: v1.ServiceSpec{ - Type: serviceType, - }, - } - if len(ip) > 0 { - service.Status = v1.ServiceStatus{ - LoadBalancer: v1.LoadBalancerStatus{ - Ingress: []v1.LoadBalancerIngress{ - { - IP: ip, - }, - }, - }, - } - } - return service -} - -func TestProcessInitialSync(t *testing.T) { - stopCh := make(chan struct{}) - defer close(stopCh) - c := newController(t, nil, stopCh) - - allocatedKey := "lb-allocated" - allocatedIP := "172.16.0.1" - services := []*v1.Service{ - newService("regular", "", false), - newService(allocatedKey, allocatedIP, true), - newService("lb-reallocate", "foo", true), - newService("lb-unallocated", "", true), - } - for _, service := range services { - c.enqueueChange(service, nil) - c.cache.Add(service) - } - // Queue a change without caching it to validate that it is ignored - c.enqueueChange(newService("ignored", "", true), nil) - - // Enqueue post-sync changes to validate that they are added back - // to the queue without being processed. - postSyncUpdate := services[0] - c.enqueueChange(postSyncUpdate, postSyncUpdate) - c.cache.Update(postSyncUpdate) - postSyncAddition := newService("lb-post-sync-addition", "", true) - c.enqueueChange(postSyncAddition, nil) - c.cache.Add(postSyncAddition) - - c.processInitialSync() - - // Validate allocation - expectedMap := map[string]string{ - allocatedIP: fmt.Sprintf("%s/%s", namespace, allocatedKey), - } - if !reflect.DeepEqual(c.allocationMap, expectedMap) { - t.Errorf("Expected allocation map %v, got %v", expectedMap, c.allocationMap) - } - if !c.ipAllocator.Has(net.ParseIP(allocatedIP)) { - t.Errorf("IP %v was not marked as allocated", allocatedIP) - } - - // Validate queue contents - expectedQueueLength := 5 // 3 from initial sync, 2 from post-sync changes - if c.queue.Len() != expectedQueueLength { - t.Errorf("Expected queue length of %d, got %d", expectedQueueLength, c.queue.Len()) - } -} - -func TestWorkRequeuesWhenFull(t *testing.T) { - tests := []struct { - testName string - requeuedChange bool - requeuedService bool - requeued bool - }{ - { - testName: "Previously requeued change should be requeued", - requeued: true, - }, - { - testName: "The only pending allocation should be requeued", - requeuedChange: true, - requeuedService: true, - requeued: true, - }, - { - testName: "Already requeued allocation should not be requeued", - requeuedService: true, - requeued: false, - }, - } - for _, test := range tests { - stopCh := make(chan struct{}) - c := newController(t, nil, stopCh) - c.changeHandler = func(change *serviceChange) error { - return ipallocator.ErrFull - } - // Use a queue with no delay to avoid timing issues - c.queue = workqueue.NewRateLimitingQueue(workqueue.NewMaxOfRateLimiter()) - change := &serviceChange{ - key: "foo", - requeuedAllocation: test.requeuedChange, - } - if test.requeuedService { - c.requeuedAllocations.Insert(change.key) - } - c.queue.Add(change) - - c.work() - - requeued := (c.queue.Len() == 1) - if test.requeued != requeued { - t.Errorf("Expected requeued == %v, got %v", test.requeued, requeued) - } - close(stopCh) - } -} - -func TestProcessChange(t *testing.T) { - tests := []struct { - testName string - ip string - lb bool - deleted bool - allocatedIP string - ipAllocated bool - }{ - { - testName: "Deleted service", - deleted: true, - }, - { - testName: "Existing allocation", - ip: "172.16.0.1", - lb: true, - allocatedIP: "172.16.0.1", - }, - { - testName: "Needs allocation", - lb: true, - ipAllocated: true, - }, - { - testName: "Needs deallocation", - ip: "172.16.0.1", - lb: false, - }, - } - for _, test := range tests { - stopCh := make(chan struct{}) - c := newController(t, nil, stopCh) - c.persistenceHandler = func(client kcoreclient.ServicesGetter, service *v1.Service, targetStatus bool) error { - return nil - } - s := newService("svc", test.ip, test.lb) - if !test.deleted { - c.cache.Add(s) - } - key := fmt.Sprintf("%s/%s", namespace, s.Name) - addAllocation := len(test.ip) > 0 && len(test.allocatedIP) == 0 - if addAllocation { - c.allocationMap[test.ip] = key - } - change := &serviceChange{key: key} - - freeBefore := c.ipAllocator.Free() - - c.processChange(change) - - switch { - case len(test.allocatedIP) > 0: - if _, ok := c.allocationMap[test.allocatedIP]; !ok { - t.Errorf("%s: %v was not allocated as expected", test.testName, test.allocatedIP) - } - case test.ipAllocated: - if freeBefore == c.ipAllocator.Free() { - t.Errorf("%s: ip was not allocated", test.testName) - } - case len(test.ip) > 0: - if _, ok := c.allocationMap[test.ip]; ok { - t.Errorf("%s: %v was not deallocated as expected", test.testName, test.ip) - } - } - close(stopCh) - } -} - -func TestClearOldAllocation(t *testing.T) { - tests := []struct { - testName string - oldIP string - newIP string - cleared bool - }{ - { - testName: "No old allocation", - oldIP: "", - newIP: "foo", - }, - { - testName: "Unchanged allocation", - oldIP: "172.16.0.1", - newIP: "172.16.0.1", - }, - { - testName: "Old allocation should be cleared", - oldIP: "172.16.0.1", - newIP: "172.16.0.2", - cleared: true, - }, - } - for _, test := range tests { - stopCh := make(chan struct{}) - c := newController(t, nil, stopCh) - new := newService("new", test.newIP, true) - old := newService("old", test.oldIP, true) - if cleared := c.clearOldAllocation(new, old); test.cleared != cleared { - t.Errorf("%s: expected cleared %v, got %v", test.testName, test.cleared, cleared) - } - close(stopCh) - } -} - -func TestRecordAllocationReallocates(t *testing.T) { - stopCh := make(chan struct{}) - defer close(stopCh) - c := newController(t, nil, stopCh) - var persisted *v1.Service - // Keep track of the last-persisted service - c.persistenceHandler = func(client kcoreclient.ServicesGetter, service *v1.Service, targetStatus bool) error { - persisted = service - return nil - } - s := newService("bad-ip", "foo", true) - key := fmt.Sprintf("%s/%s", namespace, s.Name) - err := c.recordAllocation(s, key) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - if persisted == nil { - t.Errorf("Service was not persisted") - } - if len(c.allocationMap) != 1 { - t.Errorf("Service ip was not reallocated") - } - if ingress := persisted.Status.LoadBalancer.Ingress; len(ingress) == 0 { - t.Errorf("Ingress ip was not persisted") - } -} - -func TestAllocateReleasesOnPersistenceFailure(t *testing.T) { - stopCh := make(chan struct{}) - defer close(stopCh) - c := newController(t, nil, stopCh) - expectedFree := c.ipAllocator.Free() - expectedErr := errors.New("Persistence failure") - c.persistenceHandler = func(client kcoreclient.ServicesGetter, service *v1.Service, targetStatus bool) error { - return expectedErr - } - s := newService("svc", "", true) - key := fmt.Sprintf("%s/%s", namespace, s.Name) - err := c.allocate(s, key) - if !reflect.DeepEqual(expectedErr, err) { - t.Fatalf("Expected err %v, got %v", expectedErr, err) - } - if expectedFree != c.ipAllocator.Free() { - t.Fatalf("IP wasn't released on error") - } -} - -func TestClearLocalAllocation(t *testing.T) { - tests := []struct { - testName string - key string - ip string - allocatedKey string - cleared bool - }{ - { - testName: "Invalid ip", - ip: "foo", - }, - { - testName: "IP not allocated", - ip: "172.16.0.1", - }, - { - testName: "IP not allocated to service", - key: "foo", - ip: "172.16.0.1", - allocatedKey: "bar", - }, - { - testName: "Local ip allocation cleared", - key: "foo", - ip: "172.16.0.1", - allocatedKey: "foo", - cleared: true, - }, - } - for _, test := range tests { - stopCh := make(chan struct{}) - c := newController(t, nil, stopCh) - if len(test.allocatedKey) > 0 { - c.allocationMap[test.ip] = test.allocatedKey - c.ipAllocator.Allocate(net.ParseIP(test.ip)) - } - if cleared := c.clearLocalAllocation(test.key, test.ip); test.cleared != cleared { - t.Errorf("%s: expected cleared %v, got %v", test.testName, test.cleared, cleared) - } else if cleared { - if _, ok := c.allocationMap[test.ip]; ok { - t.Errorf("%s: allocation map was not cleared", test.testName) - } - if c.ipAllocator.Has(net.ParseIP(test.ip)) { - t.Errorf("%s: ip %v is still allocated", test.testName, test.ip) - } - } - close(stopCh) - } -} - -func TestEnsureExternalIPRespectsNonIngress(t *testing.T) { - stopCh := make(chan struct{}) - defer close(stopCh) - c := newController(t, nil, stopCh) - c.persistenceHandler = func(client kcoreclient.ServicesGetter, service *v1.Service, targetStatus bool) error { - return nil - } - ingressIP := "172.16.0.1" - s := newService("foo", ingressIP, true) - externalIP := "172.16.1.1" - s.Spec.ExternalIPs = append(s.Spec.ExternalIPs, externalIP) - c.ensureExternalIP(s, s.Name, ingressIP) - expectedExternalIPs := []string{externalIP, ingressIP} - externalIPs := s.Spec.ExternalIPs - if !reflect.DeepEqual(expectedExternalIPs, externalIPs) { - t.Errorf("Expected ExternalIPs %v, got %v", expectedExternalIPs, externalIPs) - } -} - -func TestAllocateIP(t *testing.T) { - tests := []struct { - testName string - requestedIP string - allocated bool - asRequested bool - }{ - { - testName: "No requested ip", - requestedIP: "", - asRequested: false, - }, - { - testName: "Invalid ip", - requestedIP: "foo", - asRequested: false, - }, - { - testName: "IP not available", - requestedIP: "172.16.0.1", - allocated: true, - asRequested: false, - }, - { - testName: "Available", - requestedIP: "172.16.0.1", - asRequested: true, - }, - } - for _, test := range tests { - stopCh := make(chan struct{}) - controller := newController(t, nil, stopCh) - if test.allocated { - ip := net.ParseIP(test.requestedIP) - controller.ipAllocator.Allocate(ip) - } - // Expect no error for these - ip, err := controller.allocateIP(test.requestedIP) - if err != nil { - t.Errorf("Unexpected error: %v", err) - } - if test.asRequested && ip.String() != test.requestedIP { - t.Errorf("%s: expected %s but got %s", test.testName, test.requestedIP, ip.String()) - } - if !test.asRequested && ip.String() == test.requestedIP { - t.Errorf("%s: did not expect %s", test.testName, test.requestedIP) - } - close(stopCh) - } -} - -func TestRecordLocalAllocation(t *testing.T) { - key := "svc1" - ip := "172.16.0.1" - otherKey := "svc2" - tests := []struct { - testName string - allocationMap map[string]string - ip string - reallocate bool - errExpected bool - }{ - { - testName: "Invalid ip", - ip: "foo", - reallocate: true, - errExpected: true, - }, - { - testName: "Allocation exists for service", - allocationMap: map[string]string{ - ip: key, - }, - ip: ip, - }, - { - testName: "Allocation exists for another service", - allocationMap: map[string]string{ - ip: otherKey, - }, - ip: ip, - reallocate: true, - errExpected: true, - }, - { - testName: "IP not in range", - ip: "172.16.1.1", - reallocate: true, - errExpected: true, - }, - { - testName: "Allocation successful", - ip: "172.16.0.1", - }, - } - for _, test := range tests { - stopCh := make(chan struct{}) - c := newController(t, nil, stopCh) - if test.allocationMap != nil { - c.allocationMap = test.allocationMap - for ipString := range test.allocationMap { - c.ipAllocator.Allocate(net.ParseIP(ipString)) - } - } - - reallocate, err := c.recordLocalAllocation(key, test.ip) - - if test.reallocate != reallocate { - t.Errorf("%s: expected reallocate == %v but got %v", test.testName, test.reallocate, reallocate) - } - switch { - case test.errExpected && (err == nil): - t.Errorf("%s: expected error but didn't see one", test.testName) - case !test.errExpected && (err != nil): - t.Errorf("%s: saw unexpected error: %v", test.testName, err) - } - - // Ensure allocation was successfully recorded - checkAllocation := !test.reallocate && !test.errExpected - if checkAllocation { - ipKey, ok := c.allocationMap[test.ip] - inMap := ok && ipKey == key - inAllocator := c.ipAllocator.Has(net.ParseIP(test.ip)) - if !(inMap && inAllocator) { - t.Errorf("%s: allocation not recorded", test.testName) - } - } - close(stopCh) - } -} - -func TestClearPersistedAllocation(t *testing.T) { - tests := []struct { - testName string - persistenceError error - ingressIPCount int - }{ - { - testName: "Status not cleared if external ip not removed", - persistenceError: errors.New(""), - ingressIPCount: 1, - }, - { - testName: "Status cleared", - }, - } - for _, test := range tests { - stopCh := make(chan struct{}) - c := newController(t, nil, stopCh) - var persistedService *v1.Service - c.persistenceHandler = func(client kcoreclient.ServicesGetter, service *v1.Service, targetStatus bool) error { - // Save the last persisted service - persistedService = service - return test.persistenceError - } - ip := "172.16.0.1" - s := newService("svc", ip, true) - // Add other external ips to ensure they are not affected by controller - s.Spec.ExternalIPs = []string{"172.16.1.1", ip, "172.16.1.2"} - key := fmt.Sprintf("%s/%s", namespace, s.Name) - c.clearPersistedAllocation(s, key, "") - - expectedExternalIPs := []string{"172.16.1.1", "172.16.1.2"} - externalIPs := persistedService.Spec.ExternalIPs - if !reflect.DeepEqual(expectedExternalIPs, externalIPs) { - t.Errorf("%s: Expected ExternalIPs %v, got %v", test.testName, expectedExternalIPs, externalIPs) - } - ingressIPCount := len(persistedService.Status.LoadBalancer.Ingress) - if test.ingressIPCount != ingressIPCount { - t.Errorf("%s: Expected %d ingress ips, got %d", test.testName, test.ingressIPCount, ingressIPCount) - } - close(stopCh) - } -} - -// TestBasicControllerFlow validates controller start, initial sync -// processing, and post-sync processing. -func TestBasicControllerFlow(t *testing.T) { - startingObjects := []runtime.Object{ - newService("lb-unallocated", "", true), - } - - stop := make(chan struct{}) - defer close(stop) - - client := fake.NewSimpleClientset(startingObjects...) - controller := newController(t, client, stop) - controller.changeHandler = controller.processChange - - go controller.Run(stop) - - // Wait for the service spec and status to be updated with an allocated IP. - err := wait.Poll(25*time.Millisecond, 2*time.Second, func() (done bool, err error) { - for _, genericAction := range client.Actions() { - switch action := genericAction.(type) { - case clientgotesting.UpdateAction: - service, ok := action.GetObject().(*v1.Service) - if ok && len(service.Spec.ExternalIPs) > 0 && len(service.Status.LoadBalancer.Ingress) > 0 { - return true, nil - } - } - } - return false, nil - }) - if err != nil { - t.Fatalf("failed waiting for update: %v", err) - } - - client.CoreV1().Services(namespace).Delete(context.TODO(), "lb-unallocated", metav1.DeleteOptions{}) - - // Wait for a delete to be persisted. - err = wait.Poll(25*time.Millisecond, 2*time.Second, func() (done bool, err error) { - for _, genericAction := range client.Actions() { - switch action := genericAction.(type) { - case clientgotesting.DeleteAction: - if action.GetName() == "lb-unallocated" { - return true, nil - } - } - } - return false, nil - }) - if err != nil { - t.Fatalf("failed waiting for delete: %v", err) - } -} diff --git a/pkg/routeversion/version.go b/pkg/routeversion/version.go deleted file mode 100644 index b09601d2a..000000000 --- a/pkg/routeversion/version.go +++ /dev/null @@ -1,74 +0,0 @@ -/* -Copyright 2022 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 routeversion - -import ( - "fmt" - "runtime" - - "k8s.io/component-base/metrics" - "k8s.io/component-base/metrics/legacyregistry" - - "k8s.io/apimachinery/pkg/version" -) - -var ( - // commitFromGit is a constant representing the source version that - // generated this build. It should be set during build via -ldflags. - commitFromGit string - // versionFromGit is a constant representing the version tag that - // generated this build. It should be set during build via -ldflags. - versionFromGit = "unknown" - // major version - majorFromGit string - // minor version - minorFromGit string - // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ') - buildDate string - // state of git tree, either "clean" or "dirty" - gitTreeState string -) - -// Get returns the overall codebase version. It's for detecting -// what code a binary was built from. -func Get() version.Info { - return version.Info{ - Major: majorFromGit, - Minor: minorFromGit, - GitCommit: commitFromGit, - GitVersion: versionFromGit, - GitTreeState: gitTreeState, - BuildDate: buildDate, - GoVersion: runtime.Version(), - Compiler: runtime.Compiler, - Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), - } -} - -func init() { - buildInfo := metrics.NewGaugeVec( - &metrics.GaugeOpts{ - Name: "openshift_build_info", - Help: "A metric with a constant '1' value labeled by major, minor, git commit & git version from which OpenShift was built.", - }, - []string{"major", "minor", "gitCommit", "gitVersion"}, - ) - buildInfo.WithLabelValues(majorFromGit, minorFromGit, commitFromGit, versionFromGit).Set(1) - - // we're ok with an error here for now because test-integration illegally runs the same process - legacyregistry.Register(buildInfo) -} diff --git a/vendor/github.com/openshift/client-go/route/clientset/versioned/clientset.go b/vendor/github.com/openshift/client-go/route/clientset/versioned/clientset.go deleted file mode 100644 index 2dfd30b81..000000000 --- a/vendor/github.com/openshift/client-go/route/clientset/versioned/clientset.go +++ /dev/null @@ -1,105 +0,0 @@ -// Code generated by client-gen. DO NOT EDIT. - -package versioned - -import ( - "fmt" - "net/http" - - routev1 "github.com/openshift/client-go/route/clientset/versioned/typed/route/v1" - discovery "k8s.io/client-go/discovery" - rest "k8s.io/client-go/rest" - flowcontrol "k8s.io/client-go/util/flowcontrol" -) - -type Interface interface { - Discovery() discovery.DiscoveryInterface - RouteV1() routev1.RouteV1Interface -} - -// Clientset contains the clients for groups. Each group has exactly one -// version included in a Clientset. -type Clientset struct { - *discovery.DiscoveryClient - routeV1 *routev1.RouteV1Client -} - -// RouteV1 retrieves the RouteV1Client -func (c *Clientset) RouteV1() routev1.RouteV1Interface { - return c.routeV1 -} - -// Discovery retrieves the DiscoveryClient -func (c *Clientset) Discovery() discovery.DiscoveryInterface { - if c == nil { - return nil - } - return c.DiscoveryClient -} - -// NewForConfig creates a new Clientset for the given config. -// If config's RateLimiter is not set and QPS and Burst are acceptable, -// NewForConfig will generate a rate-limiter in configShallowCopy. -// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), -// where httpClient was generated with rest.HTTPClientFor(c). -func NewForConfig(c *rest.Config) (*Clientset, error) { - configShallowCopy := *c - - if configShallowCopy.UserAgent == "" { - configShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent() - } - - // share the transport between all clients - httpClient, err := rest.HTTPClientFor(&configShallowCopy) - if err != nil { - return nil, err - } - - return NewForConfigAndClient(&configShallowCopy, httpClient) -} - -// NewForConfigAndClient creates a new Clientset for the given config and http client. -// Note the http client provided takes precedence over the configured transport values. -// If config's RateLimiter is not set and QPS and Burst are acceptable, -// NewForConfigAndClient will generate a rate-limiter in configShallowCopy. -func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, error) { - configShallowCopy := *c - if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 { - if configShallowCopy.Burst <= 0 { - return nil, fmt.Errorf("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0") - } - configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst) - } - - var cs Clientset - var err error - cs.routeV1, err = routev1.NewForConfigAndClient(&configShallowCopy, httpClient) - if err != nil { - return nil, err - } - - cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient) - if err != nil { - return nil, err - } - return &cs, nil -} - -// NewForConfigOrDie creates a new Clientset for the given config and -// panics if there is an error in the config. -func NewForConfigOrDie(c *rest.Config) *Clientset { - cs, err := NewForConfig(c) - if err != nil { - panic(err) - } - return cs -} - -// New creates a new Clientset for the given RESTClient. -func New(c rest.Interface) *Clientset { - var cs Clientset - cs.routeV1 = routev1.New(c) - - cs.DiscoveryClient = discovery.NewDiscoveryClient(c) - return &cs -} diff --git a/vendor/github.com/openshift/client-go/route/clientset/versioned/doc.go b/vendor/github.com/openshift/client-go/route/clientset/versioned/doc.go deleted file mode 100644 index 0e0c2a890..000000000 --- a/vendor/github.com/openshift/client-go/route/clientset/versioned/doc.go +++ /dev/null @@ -1,4 +0,0 @@ -// Code generated by client-gen. DO NOT EDIT. - -// This package has the automatically generated clientset. -package versioned diff --git a/vendor/github.com/openshift/client-go/route/clientset/versioned/fake/clientset_generated.go b/vendor/github.com/openshift/client-go/route/clientset/versioned/fake/clientset_generated.go deleted file mode 100644 index caea9d0ad..000000000 --- a/vendor/github.com/openshift/client-go/route/clientset/versioned/fake/clientset_generated.go +++ /dev/null @@ -1,69 +0,0 @@ -// Code generated by client-gen. DO NOT EDIT. - -package fake - -import ( - clientset "github.com/openshift/client-go/route/clientset/versioned" - routev1 "github.com/openshift/client-go/route/clientset/versioned/typed/route/v1" - fakeroutev1 "github.com/openshift/client-go/route/clientset/versioned/typed/route/v1/fake" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/watch" - "k8s.io/client-go/discovery" - fakediscovery "k8s.io/client-go/discovery/fake" - "k8s.io/client-go/testing" -) - -// NewSimpleClientset returns a clientset that will respond with the provided objects. -// It's backed by a very simple object tracker that processes creates, updates and deletions as-is, -// without applying any validations and/or defaults. It shouldn't be considered a replacement -// for a real clientset and is mostly useful in simple unit tests. -func NewSimpleClientset(objects ...runtime.Object) *Clientset { - o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) - for _, obj := range objects { - if err := o.Add(obj); err != nil { - panic(err) - } - } - - cs := &Clientset{tracker: o} - cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} - cs.AddReactor("*", "*", testing.ObjectReaction(o)) - cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { - gvr := action.GetResource() - ns := action.GetNamespace() - watch, err := o.Watch(gvr, ns) - if err != nil { - return false, nil, err - } - return true, watch, nil - }) - - return cs -} - -// Clientset implements clientset.Interface. Meant to be embedded into a -// struct to get a default implementation. This makes faking out just the method -// you want to test easier. -type Clientset struct { - testing.Fake - discovery *fakediscovery.FakeDiscovery - tracker testing.ObjectTracker -} - -func (c *Clientset) Discovery() discovery.DiscoveryInterface { - return c.discovery -} - -func (c *Clientset) Tracker() testing.ObjectTracker { - return c.tracker -} - -var ( - _ clientset.Interface = &Clientset{} - _ testing.FakeClient = &Clientset{} -) - -// RouteV1 retrieves the RouteV1Client -func (c *Clientset) RouteV1() routev1.RouteV1Interface { - return &fakeroutev1.FakeRouteV1{Fake: &c.Fake} -} diff --git a/vendor/github.com/openshift/client-go/route/clientset/versioned/fake/doc.go b/vendor/github.com/openshift/client-go/route/clientset/versioned/fake/doc.go deleted file mode 100644 index 3630ed1cd..000000000 --- a/vendor/github.com/openshift/client-go/route/clientset/versioned/fake/doc.go +++ /dev/null @@ -1,4 +0,0 @@ -// Code generated by client-gen. DO NOT EDIT. - -// This package has the automatically generated fake clientset. -package fake diff --git a/vendor/github.com/openshift/client-go/route/clientset/versioned/fake/register.go b/vendor/github.com/openshift/client-go/route/clientset/versioned/fake/register.go deleted file mode 100644 index 279f25d9d..000000000 --- a/vendor/github.com/openshift/client-go/route/clientset/versioned/fake/register.go +++ /dev/null @@ -1,40 +0,0 @@ -// Code generated by client-gen. DO NOT EDIT. - -package fake - -import ( - routev1 "github.com/openshift/api/route/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" - schema "k8s.io/apimachinery/pkg/runtime/schema" - serializer "k8s.io/apimachinery/pkg/runtime/serializer" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" -) - -var scheme = runtime.NewScheme() -var codecs = serializer.NewCodecFactory(scheme) - -var localSchemeBuilder = runtime.SchemeBuilder{ - routev1.AddToScheme, -} - -// AddToScheme adds all types of this clientset into the given scheme. This allows composition -// of clientsets, like in: -// -// import ( -// "k8s.io/client-go/kubernetes" -// clientsetscheme "k8s.io/client-go/kubernetes/scheme" -// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" -// ) -// -// kclientset, _ := kubernetes.NewForConfig(c) -// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) -// -// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types -// correctly. -var AddToScheme = localSchemeBuilder.AddToScheme - -func init() { - v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) - utilruntime.Must(AddToScheme(scheme)) -} diff --git a/vendor/github.com/openshift/client-go/route/clientset/versioned/scheme/doc.go b/vendor/github.com/openshift/client-go/route/clientset/versioned/scheme/doc.go deleted file mode 100644 index 14db57a58..000000000 --- a/vendor/github.com/openshift/client-go/route/clientset/versioned/scheme/doc.go +++ /dev/null @@ -1,4 +0,0 @@ -// Code generated by client-gen. DO NOT EDIT. - -// This package contains the scheme of the automatically generated clientset. -package scheme diff --git a/vendor/github.com/openshift/client-go/route/clientset/versioned/scheme/register.go b/vendor/github.com/openshift/client-go/route/clientset/versioned/scheme/register.go deleted file mode 100644 index 0604e5613..000000000 --- a/vendor/github.com/openshift/client-go/route/clientset/versioned/scheme/register.go +++ /dev/null @@ -1,40 +0,0 @@ -// Code generated by client-gen. DO NOT EDIT. - -package scheme - -import ( - routev1 "github.com/openshift/api/route/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" - schema "k8s.io/apimachinery/pkg/runtime/schema" - serializer "k8s.io/apimachinery/pkg/runtime/serializer" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" -) - -var Scheme = runtime.NewScheme() -var Codecs = serializer.NewCodecFactory(Scheme) -var ParameterCodec = runtime.NewParameterCodec(Scheme) -var localSchemeBuilder = runtime.SchemeBuilder{ - routev1.AddToScheme, -} - -// AddToScheme adds all types of this clientset into the given scheme. This allows composition -// of clientsets, like in: -// -// import ( -// "k8s.io/client-go/kubernetes" -// clientsetscheme "k8s.io/client-go/kubernetes/scheme" -// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" -// ) -// -// kclientset, _ := kubernetes.NewForConfig(c) -// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) -// -// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types -// correctly. -var AddToScheme = localSchemeBuilder.AddToScheme - -func init() { - v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) - utilruntime.Must(AddToScheme(Scheme)) -} diff --git a/vendor/github.com/openshift/client-go/route/clientset/versioned/typed/route/v1/doc.go b/vendor/github.com/openshift/client-go/route/clientset/versioned/typed/route/v1/doc.go deleted file mode 100644 index 225e6b2be..000000000 --- a/vendor/github.com/openshift/client-go/route/clientset/versioned/typed/route/v1/doc.go +++ /dev/null @@ -1,4 +0,0 @@ -// Code generated by client-gen. DO NOT EDIT. - -// This package has the automatically generated typed clients. -package v1 diff --git a/vendor/github.com/openshift/client-go/route/clientset/versioned/typed/route/v1/fake/doc.go b/vendor/github.com/openshift/client-go/route/clientset/versioned/typed/route/v1/fake/doc.go deleted file mode 100644 index 2b5ba4c8e..000000000 --- a/vendor/github.com/openshift/client-go/route/clientset/versioned/typed/route/v1/fake/doc.go +++ /dev/null @@ -1,4 +0,0 @@ -// Code generated by client-gen. DO NOT EDIT. - -// Package fake has the automatically generated clients. -package fake diff --git a/vendor/github.com/openshift/client-go/route/clientset/versioned/typed/route/v1/fake/fake_route.go b/vendor/github.com/openshift/client-go/route/clientset/versioned/typed/route/v1/fake/fake_route.go deleted file mode 100644 index 2b7e6726a..000000000 --- a/vendor/github.com/openshift/client-go/route/clientset/versioned/typed/route/v1/fake/fake_route.go +++ /dev/null @@ -1,126 +0,0 @@ -// Code generated by client-gen. DO NOT EDIT. - -package fake - -import ( - "context" - - routev1 "github.com/openshift/api/route/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - testing "k8s.io/client-go/testing" -) - -// FakeRoutes implements RouteInterface -type FakeRoutes struct { - Fake *FakeRouteV1 - ns string -} - -var routesResource = schema.GroupVersionResource{Group: "route.openshift.io", Version: "v1", Resource: "routes"} - -var routesKind = schema.GroupVersionKind{Group: "route.openshift.io", Version: "v1", Kind: "Route"} - -// Get takes name of the route, and returns the corresponding route object, and an error if there is any. -func (c *FakeRoutes) Get(ctx context.Context, name string, options v1.GetOptions) (result *routev1.Route, err error) { - obj, err := c.Fake. - Invokes(testing.NewGetAction(routesResource, c.ns, name), &routev1.Route{}) - - if obj == nil { - return nil, err - } - return obj.(*routev1.Route), err -} - -// List takes label and field selectors, and returns the list of Routes that match those selectors. -func (c *FakeRoutes) List(ctx context.Context, opts v1.ListOptions) (result *routev1.RouteList, err error) { - obj, err := c.Fake. - Invokes(testing.NewListAction(routesResource, routesKind, c.ns, opts), &routev1.RouteList{}) - - if obj == nil { - return nil, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &routev1.RouteList{ListMeta: obj.(*routev1.RouteList).ListMeta} - for _, item := range obj.(*routev1.RouteList).Items { - if label.Matches(labels.Set(item.Labels)) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Watch returns a watch.Interface that watches the requested routes. -func (c *FakeRoutes) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewWatchAction(routesResource, c.ns, opts)) - -} - -// Create takes the representation of a route and creates it. Returns the server's representation of the route, and an error, if there is any. -func (c *FakeRoutes) Create(ctx context.Context, route *routev1.Route, opts v1.CreateOptions) (result *routev1.Route, err error) { - obj, err := c.Fake. - Invokes(testing.NewCreateAction(routesResource, c.ns, route), &routev1.Route{}) - - if obj == nil { - return nil, err - } - return obj.(*routev1.Route), err -} - -// Update takes the representation of a route and updates it. Returns the server's representation of the route, and an error, if there is any. -func (c *FakeRoutes) Update(ctx context.Context, route *routev1.Route, opts v1.UpdateOptions) (result *routev1.Route, err error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateAction(routesResource, c.ns, route), &routev1.Route{}) - - if obj == nil { - return nil, err - } - return obj.(*routev1.Route), err -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeRoutes) UpdateStatus(ctx context.Context, route *routev1.Route, opts v1.UpdateOptions) (*routev1.Route, error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(routesResource, "status", c.ns, route), &routev1.Route{}) - - if obj == nil { - return nil, err - } - return obj.(*routev1.Route), err -} - -// Delete takes name of the route and deletes it. Returns an error if one occurs. -func (c *FakeRoutes) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewDeleteActionWithOptions(routesResource, c.ns, name, opts), &routev1.Route{}) - - return err -} - -// DeleteCollection deletes a collection of objects. -func (c *FakeRoutes) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { - action := testing.NewDeleteCollectionAction(routesResource, c.ns, listOpts) - - _, err := c.Fake.Invokes(action, &routev1.RouteList{}) - return err -} - -// Patch applies the patch and returns the patched route. -func (c *FakeRoutes) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *routev1.Route, err error) { - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(routesResource, c.ns, name, pt, data, subresources...), &routev1.Route{}) - - if obj == nil { - return nil, err - } - return obj.(*routev1.Route), err -} diff --git a/vendor/github.com/openshift/client-go/route/clientset/versioned/typed/route/v1/fake/fake_route_client.go b/vendor/github.com/openshift/client-go/route/clientset/versioned/typed/route/v1/fake/fake_route_client.go deleted file mode 100644 index f94296436..000000000 --- a/vendor/github.com/openshift/client-go/route/clientset/versioned/typed/route/v1/fake/fake_route_client.go +++ /dev/null @@ -1,24 +0,0 @@ -// Code generated by client-gen. DO NOT EDIT. - -package fake - -import ( - v1 "github.com/openshift/client-go/route/clientset/versioned/typed/route/v1" - rest "k8s.io/client-go/rest" - testing "k8s.io/client-go/testing" -) - -type FakeRouteV1 struct { - *testing.Fake -} - -func (c *FakeRouteV1) Routes(namespace string) v1.RouteInterface { - return &FakeRoutes{c, namespace} -} - -// RESTClient returns a RESTClient that is used to communicate -// with API server by this client implementation. -func (c *FakeRouteV1) RESTClient() rest.Interface { - var ret *rest.RESTClient - return ret -} diff --git a/vendor/github.com/openshift/client-go/route/clientset/versioned/typed/route/v1/generated_expansion.go b/vendor/github.com/openshift/client-go/route/clientset/versioned/typed/route/v1/generated_expansion.go deleted file mode 100644 index 4f2173b6f..000000000 --- a/vendor/github.com/openshift/client-go/route/clientset/versioned/typed/route/v1/generated_expansion.go +++ /dev/null @@ -1,5 +0,0 @@ -// Code generated by client-gen. DO NOT EDIT. - -package v1 - -type RouteExpansion interface{} diff --git a/vendor/github.com/openshift/client-go/route/clientset/versioned/typed/route/v1/route.go b/vendor/github.com/openshift/client-go/route/clientset/versioned/typed/route/v1/route.go deleted file mode 100644 index adbe7e565..000000000 --- a/vendor/github.com/openshift/client-go/route/clientset/versioned/typed/route/v1/route.go +++ /dev/null @@ -1,179 +0,0 @@ -// Code generated by client-gen. DO NOT EDIT. - -package v1 - -import ( - "context" - "time" - - v1 "github.com/openshift/api/route/v1" - scheme "github.com/openshift/client-go/route/clientset/versioned/scheme" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - rest "k8s.io/client-go/rest" -) - -// RoutesGetter has a method to return a RouteInterface. -// A group's client should implement this interface. -type RoutesGetter interface { - Routes(namespace string) RouteInterface -} - -// RouteInterface has methods to work with Route resources. -type RouteInterface interface { - Create(ctx context.Context, route *v1.Route, opts metav1.CreateOptions) (*v1.Route, error) - Update(ctx context.Context, route *v1.Route, opts metav1.UpdateOptions) (*v1.Route, error) - UpdateStatus(ctx context.Context, route *v1.Route, opts metav1.UpdateOptions) (*v1.Route, error) - Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error - DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error - Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.Route, error) - List(ctx context.Context, opts metav1.ListOptions) (*v1.RouteList, error) - Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) - Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.Route, err error) - RouteExpansion -} - -// routes implements RouteInterface -type routes struct { - client rest.Interface - ns string -} - -// newRoutes returns a Routes -func newRoutes(c *RouteV1Client, namespace string) *routes { - return &routes{ - client: c.RESTClient(), - ns: namespace, - } -} - -// Get takes name of the route, and returns the corresponding route object, and an error if there is any. -func (c *routes) Get(ctx context.Context, name string, options metav1.GetOptions) (result *v1.Route, err error) { - result = &v1.Route{} - err = c.client.Get(). - Namespace(c.ns). - Resource("routes"). - Name(name). - VersionedParams(&options, scheme.ParameterCodec). - Do(ctx). - Into(result) - return -} - -// List takes label and field selectors, and returns the list of Routes that match those selectors. -func (c *routes) List(ctx context.Context, opts metav1.ListOptions) (result *v1.RouteList, err error) { - var timeout time.Duration - if opts.TimeoutSeconds != nil { - timeout = time.Duration(*opts.TimeoutSeconds) * time.Second - } - result = &v1.RouteList{} - err = c.client.Get(). - Namespace(c.ns). - Resource("routes"). - VersionedParams(&opts, scheme.ParameterCodec). - Timeout(timeout). - Do(ctx). - Into(result) - return -} - -// Watch returns a watch.Interface that watches the requested routes. -func (c *routes) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { - var timeout time.Duration - if opts.TimeoutSeconds != nil { - timeout = time.Duration(*opts.TimeoutSeconds) * time.Second - } - opts.Watch = true - return c.client.Get(). - Namespace(c.ns). - Resource("routes"). - VersionedParams(&opts, scheme.ParameterCodec). - Timeout(timeout). - Watch(ctx) -} - -// Create takes the representation of a route and creates it. Returns the server's representation of the route, and an error, if there is any. -func (c *routes) Create(ctx context.Context, route *v1.Route, opts metav1.CreateOptions) (result *v1.Route, err error) { - result = &v1.Route{} - err = c.client.Post(). - Namespace(c.ns). - Resource("routes"). - VersionedParams(&opts, scheme.ParameterCodec). - Body(route). - Do(ctx). - Into(result) - return -} - -// Update takes the representation of a route and updates it. Returns the server's representation of the route, and an error, if there is any. -func (c *routes) Update(ctx context.Context, route *v1.Route, opts metav1.UpdateOptions) (result *v1.Route, err error) { - result = &v1.Route{} - err = c.client.Put(). - Namespace(c.ns). - Resource("routes"). - Name(route.Name). - VersionedParams(&opts, scheme.ParameterCodec). - Body(route). - Do(ctx). - Into(result) - return -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *routes) UpdateStatus(ctx context.Context, route *v1.Route, opts metav1.UpdateOptions) (result *v1.Route, err error) { - result = &v1.Route{} - err = c.client.Put(). - Namespace(c.ns). - Resource("routes"). - Name(route.Name). - SubResource("status"). - VersionedParams(&opts, scheme.ParameterCodec). - Body(route). - Do(ctx). - Into(result) - return -} - -// Delete takes name of the route and deletes it. Returns an error if one occurs. -func (c *routes) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("routes"). - Name(name). - Body(&opts). - Do(ctx). - Error() -} - -// DeleteCollection deletes a collection of objects. -func (c *routes) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { - var timeout time.Duration - if listOpts.TimeoutSeconds != nil { - timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second - } - return c.client.Delete(). - Namespace(c.ns). - Resource("routes"). - VersionedParams(&listOpts, scheme.ParameterCodec). - Timeout(timeout). - Body(&opts). - Do(ctx). - Error() -} - -// Patch applies the patch and returns the patched route. -func (c *routes) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.Route, err error) { - result = &v1.Route{} - err = c.client.Patch(pt). - Namespace(c.ns). - Resource("routes"). - Name(name). - SubResource(subresources...). - VersionedParams(&opts, scheme.ParameterCodec). - Body(data). - Do(ctx). - Into(result) - return -} diff --git a/vendor/github.com/openshift/client-go/route/clientset/versioned/typed/route/v1/route_client.go b/vendor/github.com/openshift/client-go/route/clientset/versioned/typed/route/v1/route_client.go deleted file mode 100644 index e71d826c9..000000000 --- a/vendor/github.com/openshift/client-go/route/clientset/versioned/typed/route/v1/route_client.go +++ /dev/null @@ -1,91 +0,0 @@ -// Code generated by client-gen. DO NOT EDIT. - -package v1 - -import ( - "net/http" - - v1 "github.com/openshift/api/route/v1" - "github.com/openshift/client-go/route/clientset/versioned/scheme" - rest "k8s.io/client-go/rest" -) - -type RouteV1Interface interface { - RESTClient() rest.Interface - RoutesGetter -} - -// RouteV1Client is used to interact with features provided by the route.openshift.io group. -type RouteV1Client struct { - restClient rest.Interface -} - -func (c *RouteV1Client) Routes(namespace string) RouteInterface { - return newRoutes(c, namespace) -} - -// NewForConfig creates a new RouteV1Client for the given config. -// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), -// where httpClient was generated with rest.HTTPClientFor(c). -func NewForConfig(c *rest.Config) (*RouteV1Client, error) { - config := *c - if err := setConfigDefaults(&config); err != nil { - return nil, err - } - httpClient, err := rest.HTTPClientFor(&config) - if err != nil { - return nil, err - } - return NewForConfigAndClient(&config, httpClient) -} - -// NewForConfigAndClient creates a new RouteV1Client for the given config and http client. -// Note the http client provided takes precedence over the configured transport values. -func NewForConfigAndClient(c *rest.Config, h *http.Client) (*RouteV1Client, error) { - config := *c - if err := setConfigDefaults(&config); err != nil { - return nil, err - } - client, err := rest.RESTClientForConfigAndClient(&config, h) - if err != nil { - return nil, err - } - return &RouteV1Client{client}, nil -} - -// NewForConfigOrDie creates a new RouteV1Client for the given config and -// panics if there is an error in the config. -func NewForConfigOrDie(c *rest.Config) *RouteV1Client { - client, err := NewForConfig(c) - if err != nil { - panic(err) - } - return client -} - -// New creates a new RouteV1Client for the given RESTClient. -func New(c rest.Interface) *RouteV1Client { - return &RouteV1Client{c} -} - -func setConfigDefaults(config *rest.Config) error { - gv := v1.SchemeGroupVersion - config.GroupVersion = &gv - config.APIPath = "/apis" - config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() - - if config.UserAgent == "" { - config.UserAgent = rest.DefaultKubernetesUserAgent() - } - - return nil -} - -// RESTClient returns a RESTClient that is used to communicate -// with API server by this client implementation. -func (c *RouteV1Client) RESTClient() rest.Interface { - if c == nil { - return nil - } - return c.restClient -} diff --git a/vendor/github.com/openshift/client-go/route/informers/externalversions/factory.go b/vendor/github.com/openshift/client-go/route/informers/externalversions/factory.go deleted file mode 100644 index cb6b89987..000000000 --- a/vendor/github.com/openshift/client-go/route/informers/externalversions/factory.go +++ /dev/null @@ -1,164 +0,0 @@ -// Code generated by informer-gen. DO NOT EDIT. - -package externalversions - -import ( - reflect "reflect" - sync "sync" - time "time" - - versioned "github.com/openshift/client-go/route/clientset/versioned" - internalinterfaces "github.com/openshift/client-go/route/informers/externalversions/internalinterfaces" - route "github.com/openshift/client-go/route/informers/externalversions/route" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" - schema "k8s.io/apimachinery/pkg/runtime/schema" - cache "k8s.io/client-go/tools/cache" -) - -// SharedInformerOption defines the functional option type for SharedInformerFactory. -type SharedInformerOption func(*sharedInformerFactory) *sharedInformerFactory - -type sharedInformerFactory struct { - client versioned.Interface - namespace string - tweakListOptions internalinterfaces.TweakListOptionsFunc - lock sync.Mutex - defaultResync time.Duration - customResync map[reflect.Type]time.Duration - - informers map[reflect.Type]cache.SharedIndexInformer - // startedInformers is used for tracking which informers have been started. - // This allows Start() to be called multiple times safely. - startedInformers map[reflect.Type]bool -} - -// WithCustomResyncConfig sets a custom resync period for the specified informer types. -func WithCustomResyncConfig(resyncConfig map[v1.Object]time.Duration) SharedInformerOption { - return func(factory *sharedInformerFactory) *sharedInformerFactory { - for k, v := range resyncConfig { - factory.customResync[reflect.TypeOf(k)] = v - } - return factory - } -} - -// WithTweakListOptions sets a custom filter on all listers of the configured SharedInformerFactory. -func WithTweakListOptions(tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerOption { - return func(factory *sharedInformerFactory) *sharedInformerFactory { - factory.tweakListOptions = tweakListOptions - return factory - } -} - -// WithNamespace limits the SharedInformerFactory to the specified namespace. -func WithNamespace(namespace string) SharedInformerOption { - return func(factory *sharedInformerFactory) *sharedInformerFactory { - factory.namespace = namespace - return factory - } -} - -// NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces. -func NewSharedInformerFactory(client versioned.Interface, defaultResync time.Duration) SharedInformerFactory { - return NewSharedInformerFactoryWithOptions(client, defaultResync) -} - -// NewFilteredSharedInformerFactory constructs a new instance of sharedInformerFactory. -// Listers obtained via this SharedInformerFactory will be subject to the same filters -// as specified here. -// Deprecated: Please use NewSharedInformerFactoryWithOptions instead -func NewFilteredSharedInformerFactory(client versioned.Interface, defaultResync time.Duration, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerFactory { - return NewSharedInformerFactoryWithOptions(client, defaultResync, WithNamespace(namespace), WithTweakListOptions(tweakListOptions)) -} - -// NewSharedInformerFactoryWithOptions constructs a new instance of a SharedInformerFactory with additional options. -func NewSharedInformerFactoryWithOptions(client versioned.Interface, defaultResync time.Duration, options ...SharedInformerOption) SharedInformerFactory { - factory := &sharedInformerFactory{ - client: client, - namespace: v1.NamespaceAll, - defaultResync: defaultResync, - informers: make(map[reflect.Type]cache.SharedIndexInformer), - startedInformers: make(map[reflect.Type]bool), - customResync: make(map[reflect.Type]time.Duration), - } - - // Apply all options - for _, opt := range options { - factory = opt(factory) - } - - return factory -} - -// Start initializes all requested informers. -func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { - f.lock.Lock() - defer f.lock.Unlock() - - for informerType, informer := range f.informers { - if !f.startedInformers[informerType] { - go informer.Run(stopCh) - f.startedInformers[informerType] = true - } - } -} - -// WaitForCacheSync waits for all started informers' cache were synced. -func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool { - informers := func() map[reflect.Type]cache.SharedIndexInformer { - f.lock.Lock() - defer f.lock.Unlock() - - informers := map[reflect.Type]cache.SharedIndexInformer{} - for informerType, informer := range f.informers { - if f.startedInformers[informerType] { - informers[informerType] = informer - } - } - return informers - }() - - res := map[reflect.Type]bool{} - for informType, informer := range informers { - res[informType] = cache.WaitForCacheSync(stopCh, informer.HasSynced) - } - return res -} - -// InternalInformerFor returns the SharedIndexInformer for obj using an internal -// client. -func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer { - f.lock.Lock() - defer f.lock.Unlock() - - informerType := reflect.TypeOf(obj) - informer, exists := f.informers[informerType] - if exists { - return informer - } - - resyncPeriod, exists := f.customResync[informerType] - if !exists { - resyncPeriod = f.defaultResync - } - - informer = newFunc(f.client, resyncPeriod) - f.informers[informerType] = informer - - return informer -} - -// SharedInformerFactory provides shared informers for resources in all known -// API group versions. -type SharedInformerFactory interface { - internalinterfaces.SharedInformerFactory - ForResource(resource schema.GroupVersionResource) (GenericInformer, error) - WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool - - Route() route.Interface -} - -func (f *sharedInformerFactory) Route() route.Interface { - return route.New(f, f.namespace, f.tweakListOptions) -} diff --git a/vendor/github.com/openshift/client-go/route/informers/externalversions/generic.go b/vendor/github.com/openshift/client-go/route/informers/externalversions/generic.go deleted file mode 100644 index e0067144f..000000000 --- a/vendor/github.com/openshift/client-go/route/informers/externalversions/generic.go +++ /dev/null @@ -1,46 +0,0 @@ -// Code generated by informer-gen. DO NOT EDIT. - -package externalversions - -import ( - "fmt" - - v1 "github.com/openshift/api/route/v1" - schema "k8s.io/apimachinery/pkg/runtime/schema" - cache "k8s.io/client-go/tools/cache" -) - -// GenericInformer is type of SharedIndexInformer which will locate and delegate to other -// sharedInformers based on type -type GenericInformer interface { - Informer() cache.SharedIndexInformer - Lister() cache.GenericLister -} - -type genericInformer struct { - informer cache.SharedIndexInformer - resource schema.GroupResource -} - -// Informer returns the SharedIndexInformer. -func (f *genericInformer) Informer() cache.SharedIndexInformer { - return f.informer -} - -// Lister returns the GenericLister. -func (f *genericInformer) Lister() cache.GenericLister { - return cache.NewGenericLister(f.Informer().GetIndexer(), f.resource) -} - -// ForResource gives generic access to a shared informer of the matching type -// TODO extend this to unknown resources with a client pool -func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { - switch resource { - // Group=route.openshift.io, Version=v1 - case v1.SchemeGroupVersion.WithResource("routes"): - return &genericInformer{resource: resource.GroupResource(), informer: f.Route().V1().Routes().Informer()}, nil - - } - - return nil, fmt.Errorf("no informer found for %v", resource) -} diff --git a/vendor/github.com/openshift/client-go/route/informers/externalversions/internalinterfaces/factory_interfaces.go b/vendor/github.com/openshift/client-go/route/informers/externalversions/internalinterfaces/factory_interfaces.go deleted file mode 100644 index 1f807bab6..000000000 --- a/vendor/github.com/openshift/client-go/route/informers/externalversions/internalinterfaces/factory_interfaces.go +++ /dev/null @@ -1,24 +0,0 @@ -// Code generated by informer-gen. DO NOT EDIT. - -package internalinterfaces - -import ( - time "time" - - versioned "github.com/openshift/client-go/route/clientset/versioned" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" - cache "k8s.io/client-go/tools/cache" -) - -// NewInformerFunc takes versioned.Interface and time.Duration to return a SharedIndexInformer. -type NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexInformer - -// SharedInformerFactory a small interface to allow for adding an informer without an import cycle -type SharedInformerFactory interface { - Start(stopCh <-chan struct{}) - InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer -} - -// TweakListOptionsFunc is a function that transforms a v1.ListOptions. -type TweakListOptionsFunc func(*v1.ListOptions) diff --git a/vendor/github.com/openshift/client-go/route/informers/externalversions/route/interface.go b/vendor/github.com/openshift/client-go/route/informers/externalversions/route/interface.go deleted file mode 100644 index 69e1be333..000000000 --- a/vendor/github.com/openshift/client-go/route/informers/externalversions/route/interface.go +++ /dev/null @@ -1,30 +0,0 @@ -// Code generated by informer-gen. DO NOT EDIT. - -package route - -import ( - internalinterfaces "github.com/openshift/client-go/route/informers/externalversions/internalinterfaces" - v1 "github.com/openshift/client-go/route/informers/externalversions/route/v1" -) - -// Interface provides access to each of this group's versions. -type Interface interface { - // V1 provides access to shared informers for resources in V1. - V1() v1.Interface -} - -type group struct { - factory internalinterfaces.SharedInformerFactory - namespace string - tweakListOptions internalinterfaces.TweakListOptionsFunc -} - -// New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { - return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} -} - -// V1 returns a new v1.Interface. -func (g *group) V1() v1.Interface { - return v1.New(g.factory, g.namespace, g.tweakListOptions) -} diff --git a/vendor/github.com/openshift/client-go/route/informers/externalversions/route/v1/interface.go b/vendor/github.com/openshift/client-go/route/informers/externalversions/route/v1/interface.go deleted file mode 100644 index 63ee15aec..000000000 --- a/vendor/github.com/openshift/client-go/route/informers/externalversions/route/v1/interface.go +++ /dev/null @@ -1,29 +0,0 @@ -// Code generated by informer-gen. DO NOT EDIT. - -package v1 - -import ( - internalinterfaces "github.com/openshift/client-go/route/informers/externalversions/internalinterfaces" -) - -// Interface provides access to all the informers in this group version. -type Interface interface { - // Routes returns a RouteInformer. - Routes() RouteInformer -} - -type version struct { - factory internalinterfaces.SharedInformerFactory - namespace string - tweakListOptions internalinterfaces.TweakListOptionsFunc -} - -// New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { - return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} -} - -// Routes returns a RouteInformer. -func (v *version) Routes() RouteInformer { - return &routeInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} -} diff --git a/vendor/github.com/openshift/client-go/route/informers/externalversions/route/v1/route.go b/vendor/github.com/openshift/client-go/route/informers/externalversions/route/v1/route.go deleted file mode 100644 index adb845958..000000000 --- a/vendor/github.com/openshift/client-go/route/informers/externalversions/route/v1/route.go +++ /dev/null @@ -1,74 +0,0 @@ -// Code generated by informer-gen. DO NOT EDIT. - -package v1 - -import ( - "context" - time "time" - - routev1 "github.com/openshift/api/route/v1" - versioned "github.com/openshift/client-go/route/clientset/versioned" - internalinterfaces "github.com/openshift/client-go/route/informers/externalversions/internalinterfaces" - v1 "github.com/openshift/client-go/route/listers/route/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" - watch "k8s.io/apimachinery/pkg/watch" - cache "k8s.io/client-go/tools/cache" -) - -// RouteInformer provides access to a shared informer and lister for -// Routes. -type RouteInformer interface { - Informer() cache.SharedIndexInformer - Lister() v1.RouteLister -} - -type routeInformer struct { - factory internalinterfaces.SharedInformerFactory - tweakListOptions internalinterfaces.TweakListOptionsFunc - namespace string -} - -// NewRouteInformer constructs a new informer for Route type. -// Always prefer using an informer factory to get a shared informer instead of getting an independent -// one. This reduces memory footprint and number of connections to the server. -func NewRouteInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredRouteInformer(client, namespace, resyncPeriod, indexers, nil) -} - -// NewFilteredRouteInformer constructs a new informer for Route type. -// Always prefer using an informer factory to get a shared informer instead of getting an independent -// one. This reduces memory footprint and number of connections to the server. -func NewFilteredRouteInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( - &cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.RouteV1().Routes(namespace).List(context.TODO(), options) - }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.RouteV1().Routes(namespace).Watch(context.TODO(), options) - }, - }, - &routev1.Route{}, - resyncPeriod, - indexers, - ) -} - -func (f *routeInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredRouteInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) -} - -func (f *routeInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&routev1.Route{}, f.defaultInformer) -} - -func (f *routeInformer) Lister() v1.RouteLister { - return v1.NewRouteLister(f.Informer().GetIndexer()) -} diff --git a/vendor/github.com/openshift/client-go/route/listers/route/v1/expansion_generated.go b/vendor/github.com/openshift/client-go/route/listers/route/v1/expansion_generated.go deleted file mode 100644 index 74feb6380..000000000 --- a/vendor/github.com/openshift/client-go/route/listers/route/v1/expansion_generated.go +++ /dev/null @@ -1,11 +0,0 @@ -// Code generated by lister-gen. DO NOT EDIT. - -package v1 - -// RouteListerExpansion allows custom methods to be added to -// RouteLister. -type RouteListerExpansion interface{} - -// RouteNamespaceListerExpansion allows custom methods to be added to -// RouteNamespaceLister. -type RouteNamespaceListerExpansion interface{} diff --git a/vendor/github.com/openshift/client-go/route/listers/route/v1/route.go b/vendor/github.com/openshift/client-go/route/listers/route/v1/route.go deleted file mode 100644 index ddf09a7f7..000000000 --- a/vendor/github.com/openshift/client-go/route/listers/route/v1/route.go +++ /dev/null @@ -1,83 +0,0 @@ -// Code generated by lister-gen. DO NOT EDIT. - -package v1 - -import ( - v1 "github.com/openshift/api/route/v1" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/tools/cache" -) - -// RouteLister helps list Routes. -// All objects returned here must be treated as read-only. -type RouteLister interface { - // List lists all Routes in the indexer. - // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*v1.Route, err error) - // Routes returns an object that can list and get Routes. - Routes(namespace string) RouteNamespaceLister - RouteListerExpansion -} - -// routeLister implements the RouteLister interface. -type routeLister struct { - indexer cache.Indexer -} - -// NewRouteLister returns a new RouteLister. -func NewRouteLister(indexer cache.Indexer) RouteLister { - return &routeLister{indexer: indexer} -} - -// List lists all Routes in the indexer. -func (s *routeLister) List(selector labels.Selector) (ret []*v1.Route, err error) { - err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*v1.Route)) - }) - return ret, err -} - -// Routes returns an object that can list and get Routes. -func (s *routeLister) Routes(namespace string) RouteNamespaceLister { - return routeNamespaceLister{indexer: s.indexer, namespace: namespace} -} - -// RouteNamespaceLister helps list and get Routes. -// All objects returned here must be treated as read-only. -type RouteNamespaceLister interface { - // List lists all Routes in the indexer for a given namespace. - // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*v1.Route, err error) - // Get retrieves the Route from the indexer for a given namespace and name. - // Objects returned here must be treated as read-only. - Get(name string) (*v1.Route, error) - RouteNamespaceListerExpansion -} - -// routeNamespaceLister implements the RouteNamespaceLister -// interface. -type routeNamespaceLister struct { - indexer cache.Indexer - namespace string -} - -// List lists all Routes in the indexer for a given namespace. -func (s routeNamespaceLister) List(selector labels.Selector) (ret []*v1.Route, err error) { - err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { - ret = append(ret, m.(*v1.Route)) - }) - return ret, err -} - -// Get retrieves the Route from the indexer for a given namespace and name. -func (s routeNamespaceLister) Get(name string) (*v1.Route, error) { - obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) - if err != nil { - return nil, err - } - if !exists { - return nil, errors.NewNotFound(v1.Resource("route"), name) - } - return obj.(*v1.Route), nil -} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/core/service/allocator/bitmap.go b/vendor/k8s.io/kubernetes/pkg/registry/core/service/allocator/bitmap.go deleted file mode 100644 index 3c12864c1..000000000 --- a/vendor/k8s.io/kubernetes/pkg/registry/core/service/allocator/bitmap.go +++ /dev/null @@ -1,259 +0,0 @@ -/* -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 allocator - -import ( - "errors" - "fmt" - "math/big" - "math/rand" - "sync" - "time" -) - -// AllocationBitmap is a contiguous block of resources that can be allocated atomically. -// -// Each resource has an offset. The internal structure is a bitmap, with a bit for each offset. -// -// If a resource is taken, the bit at that offset is set to one. -// r.count is always equal to the number of set bits and can be recalculated at any time -// by counting the set bits in r.allocated. -// -// TODO: use RLE and compact the allocator to minimize space. -type AllocationBitmap struct { - // strategy carries the details of how to choose the next available item out of the range - strategy bitAllocator - // max is the maximum size of the usable items in the range - max int - // rangeSpec is the range specifier, matching RangeAllocation.Range - rangeSpec string - - // lock guards the following members - lock sync.Mutex - // count is the number of currently allocated elements in the range - count int - // allocated is a bit array of the allocated items in the range - allocated *big.Int -} - -// AllocationBitmap implements Interface and Snapshottable -var _ Interface = &AllocationBitmap{} -var _ Snapshottable = &AllocationBitmap{} - -// bitAllocator represents a search strategy in the allocation map for a valid item. -type bitAllocator interface { - AllocateBit(allocated *big.Int, max, count int) (int, bool) -} - -// NewAllocationMap creates an allocation bitmap using the random scan strategy. -func NewAllocationMap(max int, rangeSpec string) *AllocationBitmap { - return NewAllocationMapWithOffset(max, rangeSpec, 0) -} - -// NewAllocationMapWithOffset creates an allocation bitmap using a random scan strategy that -// allows to pass an offset that divides the allocation bitmap in two blocks. -// The first block of values will not be used for random value assigned by the AllocateNext() -// method until the second block of values has been exhausted. -func NewAllocationMapWithOffset(max int, rangeSpec string, offset int) *AllocationBitmap { - a := AllocationBitmap{ - strategy: randomScanStrategyWithOffset{ - rand: rand.New(rand.NewSource(time.Now().UnixNano())), - offset: offset, - }, - allocated: big.NewInt(0), - count: 0, - max: max, - rangeSpec: rangeSpec, - } - - return &a -} - -// Allocate attempts to reserve the provided item. -// Returns true if it was allocated, false if it was already in use -func (r *AllocationBitmap) Allocate(offset int) (bool, error) { - r.lock.Lock() - defer r.lock.Unlock() - - // max is the maximum size of the usable items in the range - if offset < 0 || offset >= r.max { - return false, fmt.Errorf("offset %d out of range [0,%d]", offset, r.max) - } - if r.allocated.Bit(offset) == 1 { - return false, nil - } - r.allocated = r.allocated.SetBit(r.allocated, offset, 1) - r.count++ - return true, nil -} - -// AllocateNext reserves one of the items from the pool. -// (0, false, nil) may be returned if there are no items left. -func (r *AllocationBitmap) AllocateNext() (int, bool, error) { - r.lock.Lock() - defer r.lock.Unlock() - - next, ok := r.strategy.AllocateBit(r.allocated, r.max, r.count) - if !ok { - return 0, false, nil - } - r.count++ - r.allocated = r.allocated.SetBit(r.allocated, next, 1) - return next, true, nil -} - -// Release releases the item back to the pool. Releasing an -// unallocated item or an item out of the range is a no-op and -// returns no error. -func (r *AllocationBitmap) Release(offset int) error { - r.lock.Lock() - defer r.lock.Unlock() - - if r.allocated.Bit(offset) == 0 { - return nil - } - - r.allocated = r.allocated.SetBit(r.allocated, offset, 0) - r.count-- - return nil -} - -const ( - // Find the size of a big.Word in bytes. - notZero = uint64(^big.Word(0)) - wordPower = (notZero>>8)&1 + (notZero>>16)&1 + (notZero>>32)&1 - wordSize = 1 << wordPower -) - -// ForEach calls the provided function for each allocated bit. The -// AllocationBitmap may not be modified while this loop is running. -func (r *AllocationBitmap) ForEach(fn func(int)) { - r.lock.Lock() - defer r.lock.Unlock() - - words := r.allocated.Bits() - for wordIdx, word := range words { - bit := 0 - for word > 0 { - if (word & 1) != 0 { - fn((wordIdx * wordSize * 8) + bit) - word = word &^ 1 - } - bit++ - word = word >> 1 - } - } -} - -// Has returns true if the provided item is already allocated and a call -// to Allocate(offset) would fail. -func (r *AllocationBitmap) Has(offset int) bool { - r.lock.Lock() - defer r.lock.Unlock() - - return r.allocated.Bit(offset) == 1 -} - -// Free returns the count of items left in the range. -func (r *AllocationBitmap) Free() int { - r.lock.Lock() - defer r.lock.Unlock() - return r.max - r.count -} - -// Snapshot saves the current state of the pool. -func (r *AllocationBitmap) Snapshot() (string, []byte) { - r.lock.Lock() - defer r.lock.Unlock() - - return r.rangeSpec, r.allocated.Bytes() -} - -// Restore restores the pool to the previously captured state. -func (r *AllocationBitmap) Restore(rangeSpec string, data []byte) error { - r.lock.Lock() - defer r.lock.Unlock() - - if r.rangeSpec != rangeSpec { - return errors.New("the provided range does not match the current range") - } - - r.allocated = big.NewInt(0).SetBytes(data) - r.count = countBits(r.allocated) - - return nil -} - -// randomScanStrategy chooses a random address from the provided big.Int, and then -// scans forward looking for the next available address (it will wrap the range if -// necessary). -type randomScanStrategy struct { - rand *rand.Rand -} - -func (rss randomScanStrategy) AllocateBit(allocated *big.Int, max, count int) (int, bool) { - if count >= max { - return 0, false - } - offset := rss.rand.Intn(max) - for i := 0; i < max; i++ { - at := (offset + i) % max - if allocated.Bit(at) == 0 { - return at, true - } - } - return 0, false -} - -var _ bitAllocator = randomScanStrategy{} - -// randomScanStrategyWithOffset choose a random address from the provided big.Int and then scans -// forward looking for the next available address. The big.Int range is subdivided so it will try -// to allocate first from the reserved upper range of addresses (it will wrap the upper subrange if necessary). -// If there is no free address it will try to allocate one from the lower range too. -type randomScanStrategyWithOffset struct { - rand *rand.Rand - offset int -} - -func (rss randomScanStrategyWithOffset) AllocateBit(allocated *big.Int, max, count int) (int, bool) { - if count >= max { - return 0, false - } - // size of the upper subrange, prioritized for random allocation - subrangeMax := max - rss.offset - // try to get a value from the upper range [rss.reserved, max] - start := rss.rand.Intn(subrangeMax) - for i := 0; i < subrangeMax; i++ { - at := rss.offset + ((start + i) % subrangeMax) - if allocated.Bit(at) == 0 { - return at, true - } - } - - start = rss.rand.Intn(rss.offset) - // subrange full, try to get the value from the first block before giving up. - for i := 0; i < rss.offset; i++ { - at := (start + i) % rss.offset - if allocated.Bit(at) == 0 { - return i, true - } - } - return 0, false -} - -var _ bitAllocator = randomScanStrategyWithOffset{} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/core/service/allocator/interfaces.go b/vendor/k8s.io/kubernetes/pkg/registry/core/service/allocator/interfaces.go deleted file mode 100644 index bd7ebc77b..000000000 --- a/vendor/k8s.io/kubernetes/pkg/registry/core/service/allocator/interfaces.go +++ /dev/null @@ -1,44 +0,0 @@ -/* -Copyright 2014 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 allocator - -// Interface manages the allocation of items out of a range. Interface -// should be threadsafe. -type Interface interface { - Allocate(int) (bool, error) - AllocateNext() (int, bool, error) - Release(int) error - ForEach(func(int)) - - // For testing - Has(int) bool - - // For testing - Free() int -} - -// Snapshottable is an Interface that can be snapshotted and restored. Snapshottable -// should be threadsafe. -type Snapshottable interface { - Interface - Snapshot() (string, []byte) - Restore(string, []byte) error -} - -type AllocatorFactory func(max int, rangeSpec string) (Interface, error) - -type AllocatorWithOffsetFactory func(max int, rangeSpec string, offset int) (Interface, error) diff --git a/vendor/k8s.io/kubernetes/pkg/registry/core/service/allocator/utils.go b/vendor/k8s.io/kubernetes/pkg/registry/core/service/allocator/utils.go deleted file mode 100644 index c034f51e6..000000000 --- a/vendor/k8s.io/kubernetes/pkg/registry/core/service/allocator/utils.go +++ /dev/null @@ -1,31 +0,0 @@ -/* -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 allocator - -import ( - "math/big" - "math/bits" -) - -// countBits returns the number of set bits in n -func countBits(n *big.Int) int { - var count int = 0 - for _, w := range n.Bits() { - count += bits.OnesCount64(uint64(w)) - } - return count -} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/core/service/ipallocator/allocator.go b/vendor/k8s.io/kubernetes/pkg/registry/core/service/ipallocator/allocator.go deleted file mode 100644 index b8d8560dc..000000000 --- a/vendor/k8s.io/kubernetes/pkg/registry/core/service/ipallocator/allocator.go +++ /dev/null @@ -1,429 +0,0 @@ -/* -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 ipallocator - -import ( - "errors" - "fmt" - "math/big" - "net" - - utilfeature "k8s.io/apiserver/pkg/util/feature" - api "k8s.io/kubernetes/pkg/apis/core" - "k8s.io/kubernetes/pkg/features" - "k8s.io/kubernetes/pkg/registry/core/service/allocator" - netutils "k8s.io/utils/net" -) - -// Interface manages the allocation of IP addresses out of a range. Interface -// should be threadsafe. -type Interface interface { - Allocate(net.IP) error - AllocateNext() (net.IP, error) - Release(net.IP) error - ForEach(func(net.IP)) - CIDR() net.IPNet - IPFamily() api.IPFamily - Has(ip net.IP) bool - - // DryRun offers a way to try operations without persisting them. - DryRun() Interface -} - -var ( - ErrFull = errors.New("range is full") - ErrAllocated = errors.New("provided IP is already allocated") - ErrMismatchedNetwork = errors.New("the provided network does not match the current range") -) - -type ErrNotInRange struct { - IP net.IP - ValidRange string -} - -func (e *ErrNotInRange) Error() string { - return fmt.Sprintf("the provided IP (%v) is not in the valid range. The range of valid IPs is %s", e.IP, e.ValidRange) -} - -// Range is a contiguous block of IPs that can be allocated atomically. -// -// The internal structure of the range is: -// -// For CIDR 10.0.0.0/24 -// 254 addresses usable out of 256 total (minus base and broadcast IPs) -// The number of usable addresses is r.max -// -// CIDR base IP CIDR broadcast IP -// 10.0.0.0 10.0.0.255 -// | | -// 0 1 2 3 4 5 ... ... 253 254 255 -// | | -// r.base r.base + r.max -// | | -// offset #0 of r.allocated last offset of r.allocated -type Range struct { - net *net.IPNet - // base is a cached version of the start IP in the CIDR range as a *big.Int - base *big.Int - // max is the maximum size of the usable addresses in the range - max int - // family is the IP family of this range - family api.IPFamily - - alloc allocator.Interface -} - -// New creates a Range over a net.IPNet, calling allocatorFactory to construct the backing store. -func New(cidr *net.IPNet, allocatorFactory allocator.AllocatorWithOffsetFactory) (*Range, error) { - registerMetrics() - - max := netutils.RangeSize(cidr) - base := netutils.BigForIP(cidr.IP) - rangeSpec := cidr.String() - var family api.IPFamily - - if netutils.IsIPv6CIDR(cidr) { - family = api.IPv6Protocol - // Limit the max size, since the allocator keeps a bitmap of that size. - if max > 65536 { - max = 65536 - } - } else { - family = api.IPv4Protocol - // Don't use the IPv4 network's broadcast address, but don't just - // Allocate() it - we don't ever want to be able to release it. - max-- - } - - // Don't use the network's ".0" address, but don't just Allocate() it - we - // don't ever want to be able to release it. - base.Add(base, big.NewInt(1)) - max-- - - r := Range{ - net: cidr, - base: base, - max: maximum(0, int(max)), - family: family, - } - - offset := 0 - if utilfeature.DefaultFeatureGate.Enabled(features.ServiceIPStaticSubrange) { - offset = calculateRangeOffset(cidr) - } - - var err error - r.alloc, err = allocatorFactory(r.max, rangeSpec, offset) - if err != nil { - return nil, err - } - return &r, nil -} - -// NewInMemory creates an in-memory allocator. -func NewInMemory(cidr *net.IPNet) (*Range, error) { - return New(cidr, func(max int, rangeSpec string, offset int) (allocator.Interface, error) { - return allocator.NewAllocationMapWithOffset(max, rangeSpec, offset), nil - }) -} - -// NewFromSnapshot allocates a Range and initializes it from a snapshot. -func NewFromSnapshot(snap *api.RangeAllocation) (*Range, error) { - _, ipnet, err := netutils.ParseCIDRSloppy(snap.Range) - if err != nil { - return nil, err - } - r, err := NewInMemory(ipnet) - if err != nil { - return nil, err - } - if err := r.Restore(ipnet, snap.Data); err != nil { - return nil, err - } - return r, nil -} - -func maximum(a, b int) int { - if a > b { - return a - } - return b -} - -// Free returns the count of IP addresses left in the range. -func (r *Range) Free() int { - return r.alloc.Free() -} - -// Used returns the count of IP addresses used in the range. -func (r *Range) Used() int { - return r.max - r.alloc.Free() -} - -// CIDR returns the CIDR covered by the range. -func (r *Range) CIDR() net.IPNet { - return *r.net -} - -// DryRun returns a non-persisting form of this Range. -func (r *Range) DryRun() Interface { - return dryRunRange{r} -} - -// For clearer code. -const dryRunTrue = true -const dryRunFalse = false - -// Allocate attempts to reserve the provided IP. ErrNotInRange or -// ErrAllocated will be returned if the IP is not valid for this range -// or has already been reserved. ErrFull will be returned if there -// are no addresses left. -func (r *Range) Allocate(ip net.IP) error { - return r.allocate(ip, dryRunFalse) -} - -func (r *Range) allocate(ip net.IP, dryRun bool) error { - label := r.CIDR() - ok, offset := r.contains(ip) - if !ok { - // update metrics - clusterIPAllocationErrors.WithLabelValues(label.String(), "static").Inc() - return &ErrNotInRange{ip, r.net.String()} - } - if dryRun { - // Don't bother to check whether the IP is actually free. It's racy and - // not worth the effort to plumb any further. - return nil - } - - allocated, err := r.alloc.Allocate(offset) - if err != nil { - // update metrics - clusterIPAllocationErrors.WithLabelValues(label.String(), "static").Inc() - - return err - } - if !allocated { - // update metrics - clusterIPAllocationErrors.WithLabelValues(label.String(), "static").Inc() - - return ErrAllocated - } - // update metrics - clusterIPAllocations.WithLabelValues(label.String(), "static").Inc() - clusterIPAllocated.WithLabelValues(label.String()).Set(float64(r.Used())) - clusterIPAvailable.WithLabelValues(label.String()).Set(float64(r.Free())) - - return nil -} - -// AllocateNext reserves one of the IPs from the pool. ErrFull may -// be returned if there are no addresses left. -func (r *Range) AllocateNext() (net.IP, error) { - return r.allocateNext(dryRunFalse) -} - -func (r *Range) allocateNext(dryRun bool) (net.IP, error) { - label := r.CIDR() - if dryRun { - // Don't bother finding a free value. It's racy and not worth the - // effort to plumb any further. - return r.CIDR().IP, nil - } - - offset, ok, err := r.alloc.AllocateNext() - if err != nil { - // update metrics - clusterIPAllocationErrors.WithLabelValues(label.String(), "dynamic").Inc() - - return nil, err - } - if !ok { - // update metrics - clusterIPAllocationErrors.WithLabelValues(label.String(), "dynamic").Inc() - - return nil, ErrFull - } - // update metrics - clusterIPAllocations.WithLabelValues(label.String(), "dynamic").Inc() - clusterIPAllocated.WithLabelValues(label.String()).Set(float64(r.Used())) - clusterIPAvailable.WithLabelValues(label.String()).Set(float64(r.Free())) - - return netutils.AddIPOffset(r.base, offset), nil -} - -// Release releases the IP back to the pool. Releasing an -// unallocated IP or an IP out of the range is a no-op and -// returns no error. -func (r *Range) Release(ip net.IP) error { - return r.release(ip, dryRunFalse) -} - -func (r *Range) release(ip net.IP, dryRun bool) error { - ok, offset := r.contains(ip) - if !ok { - return nil - } - if dryRun { - return nil - } - - err := r.alloc.Release(offset) - if err == nil { - // update metrics - label := r.CIDR() - clusterIPAllocated.WithLabelValues(label.String()).Set(float64(r.Used())) - clusterIPAvailable.WithLabelValues(label.String()).Set(float64(r.Free())) - } - return err -} - -// ForEach calls the provided function for each allocated IP. -func (r *Range) ForEach(fn func(net.IP)) { - r.alloc.ForEach(func(offset int) { - ip, _ := netutils.GetIndexedIP(r.net, offset+1) // +1 because Range doesn't store IP 0 - fn(ip) - }) -} - -// Has returns true if the provided IP is already allocated and a call -// to Allocate(ip) would fail with ErrAllocated. -func (r *Range) Has(ip net.IP) bool { - ok, offset := r.contains(ip) - if !ok { - return false - } - - return r.alloc.Has(offset) -} - -// IPFamily returns the IP family of this range. -func (r *Range) IPFamily() api.IPFamily { - return r.family -} - -// Snapshot saves the current state of the pool. -func (r *Range) Snapshot(dst *api.RangeAllocation) error { - snapshottable, ok := r.alloc.(allocator.Snapshottable) - if !ok { - return fmt.Errorf("not a snapshottable allocator") - } - rangeString, data := snapshottable.Snapshot() - dst.Range = rangeString - dst.Data = data - return nil -} - -// Restore restores the pool to the previously captured state. ErrMismatchedNetwork -// is returned if the provided IPNet range doesn't exactly match the previous range. -func (r *Range) Restore(net *net.IPNet, data []byte) error { - if !net.IP.Equal(r.net.IP) || net.Mask.String() != r.net.Mask.String() { - return ErrMismatchedNetwork - } - snapshottable, ok := r.alloc.(allocator.Snapshottable) - if !ok { - return fmt.Errorf("not a snapshottable allocator") - } - if err := snapshottable.Restore(net.String(), data); err != nil { - return fmt.Errorf("restoring snapshot encountered %v", err) - } - return nil -} - -// contains returns true and the offset if the ip is in the range, and false -// and nil otherwise. The first and last addresses of the CIDR are omitted. -func (r *Range) contains(ip net.IP) (bool, int) { - if !r.net.Contains(ip) { - return false, 0 - } - - offset := calculateIPOffset(r.base, ip) - if offset < 0 || offset >= r.max { - return false, 0 - } - return true, offset -} - -// calculateIPOffset calculates the integer offset of ip from base such that -// base + offset = ip. It requires ip >= base. -func calculateIPOffset(base *big.Int, ip net.IP) int { - return int(big.NewInt(0).Sub(netutils.BigForIP(ip), base).Int64()) -} - -// calculateRangeOffset estimates the offset used on the range for statically allocation based on -// the following formula `min(max($min, cidrSize/$step), $max)`, described as ~never less than -// $min or more than $max, with a graduated step function between them~. The function returns 0 -// if any of the parameters is invalid. -func calculateRangeOffset(cidr *net.IPNet) int { - // default values for min(max($min, cidrSize/$step), $max) - const ( - min = 16 - max = 256 - step = 16 - ) - - cidrSize := netutils.RangeSize(cidr) - if cidrSize < min { - return 0 - } - - offset := cidrSize / step - if offset < min { - return min - } - if offset > max { - return max - } - return int(offset) -} - -// dryRunRange is a shim to satisfy Interface without persisting state. -type dryRunRange struct { - real *Range -} - -func (dry dryRunRange) Allocate(ip net.IP) error { - return dry.real.allocate(ip, dryRunTrue) -} - -func (dry dryRunRange) AllocateNext() (net.IP, error) { - return dry.real.allocateNext(dryRunTrue) -} - -func (dry dryRunRange) Release(ip net.IP) error { - return dry.real.release(ip, dryRunTrue) -} - -func (dry dryRunRange) ForEach(cb func(net.IP)) { - dry.real.ForEach(cb) -} - -func (dry dryRunRange) CIDR() net.IPNet { - return dry.real.CIDR() -} - -func (dry dryRunRange) IPFamily() api.IPFamily { - return dry.real.IPFamily() -} - -func (dry dryRunRange) DryRun() Interface { - return dry -} - -func (dry dryRunRange) Has(ip net.IP) bool { - return dry.real.Has(ip) -} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/core/service/ipallocator/metrics.go b/vendor/k8s.io/kubernetes/pkg/registry/core/service/ipallocator/metrics.go deleted file mode 100644 index 2aa1844b3..000000000 --- a/vendor/k8s.io/kubernetes/pkg/registry/core/service/ipallocator/metrics.go +++ /dev/null @@ -1,87 +0,0 @@ -/* -Copyright 2021 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 ipallocator - -import ( - "sync" - - "k8s.io/component-base/metrics" - "k8s.io/component-base/metrics/legacyregistry" -) - -const ( - namespace = "kube_apiserver" - subsystem = "clusterip_allocator" -) - -var ( - // clusterIPAllocated indicates the amount of cluster IP allocated by Service CIDR. - clusterIPAllocated = metrics.NewGaugeVec( - &metrics.GaugeOpts{ - Namespace: namespace, - Subsystem: subsystem, - Name: "allocated_ips", - Help: "Gauge measuring the number of allocated IPs for Services", - StabilityLevel: metrics.ALPHA, - }, - []string{"cidr"}, - ) - // clusterIPAvailable indicates the amount of cluster IP available by Service CIDR. - clusterIPAvailable = metrics.NewGaugeVec( - &metrics.GaugeOpts{ - Namespace: namespace, - Subsystem: subsystem, - Name: "available_ips", - Help: "Gauge measuring the number of available IPs for Services", - StabilityLevel: metrics.ALPHA, - }, - []string{"cidr"}, - ) - // clusterIPAllocation counts the total number of ClusterIP allocation and allocation mode: static or dynamic. - clusterIPAllocations = metrics.NewCounterVec( - &metrics.CounterOpts{ - Namespace: namespace, - Subsystem: subsystem, - Name: "allocation_total", - Help: "Number of Cluster IPs allocations", - StabilityLevel: metrics.ALPHA, - }, - []string{"cidr", "scope"}, - ) - // clusterIPAllocationErrors counts the number of error trying to allocate a ClusterIP and allocation mode: static or dynamic. - clusterIPAllocationErrors = metrics.NewCounterVec( - &metrics.CounterOpts{ - Namespace: namespace, - Subsystem: subsystem, - Name: "allocation_errors_total", - Help: "Number of errors trying to allocate Cluster IPs", - StabilityLevel: metrics.ALPHA, - }, - []string{"cidr", "scope"}, - ) -) - -var registerMetricsOnce sync.Once - -func registerMetrics() { - registerMetricsOnce.Do(func() { - legacyregistry.MustRegister(clusterIPAllocated) - legacyregistry.MustRegister(clusterIPAvailable) - legacyregistry.MustRegister(clusterIPAllocations) - legacyregistry.MustRegister(clusterIPAllocationErrors) - }) -} diff --git a/vendor/modules.txt b/vendor/modules.txt index b2754e449..ae8b09f9d 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -418,16 +418,6 @@ github.com/openshift/client-go/operator/informers/externalversions/operator/v1 github.com/openshift/client-go/operator/informers/externalversions/operator/v1alpha1 github.com/openshift/client-go/operator/listers/operator/v1 github.com/openshift/client-go/operator/listers/operator/v1alpha1 -github.com/openshift/client-go/route/clientset/versioned -github.com/openshift/client-go/route/clientset/versioned/fake -github.com/openshift/client-go/route/clientset/versioned/scheme -github.com/openshift/client-go/route/clientset/versioned/typed/route/v1 -github.com/openshift/client-go/route/clientset/versioned/typed/route/v1/fake -github.com/openshift/client-go/route/informers/externalversions -github.com/openshift/client-go/route/informers/externalversions/internalinterfaces -github.com/openshift/client-go/route/informers/externalversions/route -github.com/openshift/client-go/route/informers/externalversions/route/v1 -github.com/openshift/client-go/route/listers/route/v1 github.com/openshift/client-go/security/clientset/versioned github.com/openshift/client-go/security/clientset/versioned/scheme github.com/openshift/client-go/security/clientset/versioned/typed/security/v1 @@ -1513,8 +1503,6 @@ k8s.io/kubernetes/pkg/credentialprovider/secrets k8s.io/kubernetes/pkg/features k8s.io/kubernetes/pkg/fieldpath k8s.io/kubernetes/pkg/registry/core/secret -k8s.io/kubernetes/pkg/registry/core/service/allocator -k8s.io/kubernetes/pkg/registry/core/service/ipallocator k8s.io/kubernetes/pkg/serviceaccount k8s.io/kubernetes/pkg/util/hash k8s.io/kubernetes/pkg/util/labels