Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions manifests/00-cluster-role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ rules:
verbs:
- get

- apiGroups:
- config.openshift.io
resources:
- ingresses
verbs:
- list
- watch

- apiGroups:
- config.openshift.io
resources:
Expand Down
8 changes: 4 additions & 4 deletions pkg/manifests/bindata.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions pkg/operator/controller/ingress/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,32 @@ func New(mgr manager.Manager, config Config) (controller.Controller, error) {
if err := c.Watch(&source.Kind{Type: &iov1.DNSRecord{}}, &handler.EnqueueRequestForOwner{OwnerType: &operatorv1.IngressController{}}); err != nil {
return nil, err
}
if err := c.Watch(&source.Kind{Type: &configv1.Ingress{}}, &handler.EnqueueRequestsFromMapFunc{ToRequests: handler.ToRequestsFunc(reconciler.ingressConfigToIngressController)}); err != nil {
return nil, err
}
return c, nil
}

func (r *reconciler) ingressConfigToIngressController(o handler.MapObject) []reconcile.Request {
var requests []reconcile.Request
controllers := &operatorv1.IngressControllerList{}
if err := r.cache.List(context.Background(), controllers, client.InNamespace(r.Namespace)); err != nil {
log.Error(err, "failed to list ingresscontrollers for ingress", "related", o.Meta.GetSelfLink())
return requests
}
for _, ic := range controllers.Items {
log.Info("queueing ingresscontroller", "name", ic.Name, "related", o.Meta.GetSelfLink())
request := reconcile.Request{
NamespacedName: types.NamespacedName{
Namespace: ic.Namespace,
Name: ic.Name,
},
}
requests = append(requests, request)
}
return requests
}

func enqueueRequestForOwningIngressController(namespace string) handler.EventHandler {
return &handler.EnqueueRequestsFromMapFunc{
ToRequests: handler.ToRequestsFunc(func(a handler.MapObject) []reconcile.Request {
Expand Down
20 changes: 20 additions & 0 deletions pkg/operator/controller/ingress/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net"
"path/filepath"
"sort"
"strconv"
"strings"

"github.com/davecgh/go-spew/spew"
Expand Down Expand Up @@ -38,6 +39,9 @@ const (
RouterSyslogAddressEnvName = "ROUTER_SYSLOG_ADDRESS"
RouterSyslogFormatEnvName = "ROUTER_SYSLOG_FORMAT"
RouterSyslogFacilityEnvName = "ROUTER_LOG_FACILITY"

RouterDisableHTTP2EnvName = "ROUTER_DISABLE_HTTP2"
RouterDisableHTTP2Annotation = "ingress.operator.openshift.io/unsupported-disable-http2"
)

// ensureRouterDeployment ensures the router deployment exists for a given
Expand Down Expand Up @@ -81,6 +85,18 @@ func (r *reconciler) ensureRouterDeleted(ci *operatorv1.IngressController) error
return nil
}

// HTTP2IsDisabledByAnnotation returns true if the map m has the key
// RouterDisableHTTP2Annotation present and it has "true" as its
// value.
func HTTP2IsDisabledByAnnotation(m map[string]string) bool {
if mval, ok := m[RouterDisableHTTP2Annotation]; ok {
if val, err := strconv.ParseBool(mval); err == nil && val {
return true
}
}
return false
}

// desiredRouterDeployment returns the desired router deployment.
func desiredRouterDeployment(ci *operatorv1.IngressController, ingressControllerImage string, infraConfig *configv1.Infrastructure, ingressConfig *configv1.Ingress, apiConfig *configv1.APIServer, networkConfig *configv1.Network) (*appsv1.Deployment, error) {
deployment := manifests.RouterDeployment()
Expand Down Expand Up @@ -498,6 +514,10 @@ func desiredRouterDeployment(ci *operatorv1.IngressController, ingressController
env = append(env, corev1.EnvVar{Name: WildcardRouteAdmissionPolicy, Value: "false"})
}

if HTTP2IsDisabledByAnnotation(ci.Annotations) || HTTP2IsDisabledByAnnotation(ingressConfig.Annotations) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the cluster ingress config has ingress.operator.openshift.io/unsupported-disable-http2=true and the ingresscontroller has ingress.operator.openshift.io/unsupported-disable-http2=false? Seems like the ingresscontroller's annotation should override the ingress config's. Granted, this is an unsupported annotation, so I wouldn't worry too much about this if it makes the logic hairy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the ingresscontroller's annotation should override the ingress config's.

Interesting. I interpreted the ingress config to have the higher priority. Rather than annotating all the ingresscontrollers you could just add it to the ingress config to disable it everywhere.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My reasoning is that if the user explicitly added an annotation to both the ingress config as well as to an individual ingresscontroller, then the user is expressing a general preference with the former annotation and an overriding preference for the specific ingresscontroller with the latter annotation.

env = append(env, corev1.EnvVar{Name: RouterDisableHTTP2EnvName, Value: "true"})
}

deployment.Spec.Template.Spec.Volumes = volumes
deployment.Spec.Template.Spec.Containers[0].VolumeMounts = routerVolumeMounts
deployment.Spec.Template.Spec.Containers[0].Env = append(deployment.Spec.Template.Spec.Containers[0].Env, env...)
Expand Down
1 change: 1 addition & 0 deletions pkg/operator/controller/ingress/deployment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,7 @@ func TestDesiredRouterDeployment(t *testing.T) {
checkDeploymentHasEnvVar(t, deployment, "ROUTER_SYSLOG_FORMAT", false, "")

checkDeploymentHasEnvVar(t, deployment, "ROUTER_IP_V4_V6_MODE", true, "v6")
checkDeploymentHasEnvVar(t, deployment, RouterDisableHTTP2EnvName, false, "")
}

func TestInferTLSProfileSpecFromDeployment(t *testing.T) {
Expand Down
239 changes: 239 additions & 0 deletions test/e2e/http2_disable_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
// +build e2e

package e2e

import (
"context"
"fmt"
"strconv"
"testing"
"time"

configv1 "github.com/openshift/api/config/v1"
operatorv1 "github.com/openshift/api/operator/v1"

"github.com/openshift/cluster-ingress-operator/pkg/operator/controller"
"github.com/openshift/cluster-ingress-operator/pkg/operator/controller/ingress"
ingresscontroller "github.com/openshift/cluster-ingress-operator/pkg/operator/controller/ingress"

"sigs.k8s.io/controller-runtime/pkg/client"

appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"

"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
)

// NOTE: This test will mutate the default ingresscontroller and the
// "cluster" ingress configuration.
func TestRouteHTTP2EnableAndDisable(t *testing.T) {
routerDeployTimeout := 1 * time.Minute

if err := waitForIngressControllerCondition(kclient, 5*time.Minute, defaultName, defaultAvailableConditions...); err != nil {
t.Fatalf("failed to observe expected conditions: %v", err)
}
ic, ingressConfig, routerDeployment, err := http2RefreshTestObjects(t, kclient, 1*time.Minute)
if err != nil {
t.Fatalf("failed to get test objects: %v", err)
}
if err := checkHTTP2IsEnabled(ic, ingressConfig, routerDeployment); err != nil {
t.Fatalf("test assertions failed: %v", err)
}

// Disable http/2 on the default ingresscontroller
if err := setHTTP2DisabledForIngressController(t, kclient, 1*time.Minute, true, ic); err != nil {
t.Fatalf("failed to update ingresscontroller: %v", err)
}
if err := waitForIngressControllerCondition(kclient, 5*time.Minute, defaultName, defaultAvailableConditions...); err != nil {
t.Fatalf("failed to observe expected conditions: %v", err)
}
if err := waitForRouterDeploymentHTTP2Disabled(kclient, routerDeployTimeout, ic, true); err != nil {
t.Fatalf("expected router deployment to have http/2 disabled: %v", err)
}

// Revert the previous change to the default ingresscontroller
if err := setHTTP2DisabledForIngressController(t, kclient, 1*time.Minute, false, ic); err != nil {
t.Fatalf("failed to update ingresscontroller: %v", err)
}
if err := waitForIngressControllerCondition(kclient, 5*time.Minute, defaultName, defaultAvailableConditions...); err != nil {
t.Fatalf("failed to observe expected conditions: %v", err)
}
if err := waitForRouterDeploymentHTTP2Disabled(kclient, routerDeployTimeout, ic, false); err != nil {
t.Fatalf("expected router deployment to have http/2 enabled: %v", err)
}

//
// Now assert that changing the "cluster" ingress
// configuration will also disable/enable http/2 for all
// ingress controllers.
ic, ingressConfig, routerDeployment, err = http2RefreshTestObjects(t, kclient, 1*time.Minute)
if err != nil {
t.Fatalf("failed to get test objects: %v", err)
}
if err := checkHTTP2IsEnabled(ic, ingressConfig, routerDeployment); err != nil {
t.Fatalf("test assertions failed: %v", err)
}

// Disable http/2 on the ingress config "cluster"
if err := setHTTP2DisabledForIngressConfig(t, kclient, 1*time.Minute, true, ingressConfig); err != nil {
t.Fatalf("failed to update ingress config: %v", err)
}
if err := waitForIngressControllerCondition(kclient, 5*time.Minute, defaultName, defaultAvailableConditions...); err != nil {
t.Fatalf("failed to observe expected conditions: %v", err)
}
if err := waitForRouterDeploymentHTTP2Disabled(kclient, routerDeployTimeout, ic, true); err != nil {
t.Fatalf("expected router deployment to have http/2 disabled: %v", err)
}

// Revert the previous change to the ingress config
ic, ingressConfig, routerDeployment, err = http2RefreshTestObjects(t, kclient, 1*time.Minute)
if err != nil {
t.Fatalf("failed to get test objects: %v", err)
}
if err := setHTTP2DisabledForIngressConfig(t, kclient, 1*time.Minute, false, ingressConfig); err != nil {
t.Fatalf("failed to update ingress config: %v", err)
}
if err := waitForIngressControllerCondition(kclient, 5*time.Minute, defaultName, defaultAvailableConditions...); err != nil {
t.Fatalf("failed to observe expected conditions: %v", err)
}
if err := waitForRouterDeploymentHTTP2Disabled(kclient, routerDeployTimeout, ic, false); err != nil {
t.Fatalf("expected router deployment to have http/2 enabled: %v", err)
}

// Assert all our mutations are back to their initial state
ic, ingressConfig, routerDeployment, err = http2RefreshTestObjects(t, kclient, 1*time.Minute)
if err != nil {
t.Fatalf("failed to get test objects: %v", err)
}
if err := checkHTTP2IsEnabled(ic, ingressConfig, routerDeployment); err != nil {
t.Fatalf("test assertions failed: %v", err)
}
}

func http2IsDisabledInEnv(env []corev1.EnvVar) bool {
for _, v := range env {
if v.Name == ingresscontroller.RouterDisableHTTP2EnvName {
if val, err := strconv.ParseBool(v.Value); err == nil {
return val
}
}
}
return false
}

func http2RefreshTestObjects(t *testing.T, client client.Client, timeout time.Duration) (*operatorv1.IngressController, *configv1.Ingress, *appsv1.Deployment, error) {
ic := operatorv1.IngressController{}
if err := wait.PollImmediate(1*time.Second, timeout, func() (bool, error) {
if err := client.Get(context.TODO(), defaultName, &ic); err != nil {
t.Logf("Get %q failed: %v, retrying...", defaultName, err)
return false, nil
}
return true, nil
}); err != nil {
return nil, nil, nil, fmt.Errorf("failed to get %q: %v", defaultName, err)
}

deployment := appsv1.Deployment{}
if err := wait.PollImmediate(1*time.Second, timeout, func() (bool, error) {
if err := client.Get(context.TODO(), controller.RouterDeploymentName(&ic), &deployment); err != nil {
t.Logf("Get %q failed: %v, retrying...", controller.RouterDeploymentName(&ic), err)
return false, nil
}
return true, nil
}); err != nil {
return nil, nil, nil, fmt.Errorf("failed to get default ingresscontroller router deployment: %v", err)
}

ingressConfig := configv1.Ingress{}
if err := wait.PollImmediate(1*time.Second, timeout, func() (bool, error) {
name := types.NamespacedName{Name: "cluster"}
if err := client.Get(context.TODO(), name, &ingressConfig); err != nil {
t.Logf("Get %q failed: %v, retrying...", name, err)
return false, nil
}
return true, nil
}); err != nil {
return nil, nil, nil, fmt.Errorf("failed to get ingress configuration: %v", err)
}

return &ic, &ingressConfig, &deployment, nil
}

func checkHTTP2IsEnabled(ic *operatorv1.IngressController, ingressConfig *configv1.Ingress, d *appsv1.Deployment) error {
icName := fmt.Sprintf("%s/%s", ic.Namespace, ic.Name)
deploymentName := fmt.Sprintf("%s/%s", d.Namespace, d.Name)
ingressConfigName := fmt.Sprintf("%s/%s", ingressConfig.Namespace, ingressConfig.Name)

if ingress.HTTP2IsDisabledByAnnotation(ic.Annotations) {
return fmt.Errorf("expected ingress controller %q to have HTTP/2 enabled by default", icName)
}
if ingress.HTTP2IsDisabledByAnnotation(ingressConfig.Annotations) {
return fmt.Errorf("expected ingress config %q to have HTTP/2 enabled by default", ingressConfigName)
}
if len(d.Spec.Template.Spec.Containers) < 1 {
return fmt.Errorf("expected deployment %q to have at least one container", deploymentName)
}
if http2IsDisabledInEnv(d.Spec.Template.Spec.Containers[0].Env) {
return fmt.Errorf("expected deployment %q to have http/2 enabled by default", deploymentName)
}
return nil
}

func waitForRouterDeploymentHTTP2Disabled(client client.Client, timeout time.Duration, c *operatorv1.IngressController, expectedDisabledStatus bool) error {
return wait.PollImmediate(1*time.Second, timeout, func() (bool, error) {
deployment := &appsv1.Deployment{}
if err := client.Get(context.TODO(), controller.RouterDeploymentName(c), deployment); err != nil {
return false, nil
}
return http2IsDisabledInEnv(deployment.Spec.Template.Spec.Containers[0].Env) == expectedDisabledStatus, nil
})
}

func setHTTP2DisabledForIngressConfig(t *testing.T, client client.Client, timeout time.Duration, disableHTTP2 bool, c *configv1.Ingress) error {
name := types.NamespacedName{Namespace: c.Namespace, Name: c.Name}

return wait.PollImmediate(1*time.Second, timeout, func() (bool, error) {
if err := client.Get(context.TODO(), name, c); err != nil {
t.Logf("Get %q failed: %v, retrying...", name, err)
return false, nil
}
if c.Annotations == nil {
c.Annotations = map[string]string{}
}
if disableHTTP2 {
c.Annotations[ingress.RouterDisableHTTP2Annotation] = "true"
} else {
delete(c.Annotations, ingress.RouterDisableHTTP2Annotation)
}
if err := client.Update(context.TODO(), c); err != nil {
t.Logf("Update %q failed: %v, retrying...", name, err)
return false, nil
}
return true, nil
})
}

func setHTTP2DisabledForIngressController(t *testing.T, client client.Client, timeout time.Duration, disableHTTP2 bool, c *operatorv1.IngressController) error {
name := types.NamespacedName{Namespace: c.Namespace, Name: c.Name}

return wait.PollImmediate(1*time.Second, timeout, func() (bool, error) {
if err := client.Get(context.TODO(), name, c); err != nil {
t.Logf("Get %q failed: %v, retrying...", name, err)
return false, nil
}
if c.Annotations == nil {
c.Annotations = map[string]string{}
}
if disableHTTP2 {
c.Annotations[ingress.RouterDisableHTTP2Annotation] = "true"
} else {
delete(c.Annotations, ingress.RouterDisableHTTP2Annotation)
}
if err := client.Update(context.TODO(), c); err != nil {
t.Logf("Update %q failed: %v, retrying...", name, err)
return false, nil
}
return true, nil
})
}