Skip to content

Commit

Permalink
OCPBUGS-1689: Add unit test for the reconcile function.
Browse files Browse the repository at this point in the history
  • Loading branch information
gcs278 committed Dec 15, 2022
1 parent 01178ee commit 22be71c
Show file tree
Hide file tree
Showing 227 changed files with 22,818 additions and 0 deletions.
356 changes: 356 additions & 0 deletions pkg/operator/controller/route-metrics/controller_test.go
@@ -1,12 +1,218 @@
package routemetrics

import (
"context"
"fmt"
"strings"
"testing"
"time"

v1 "github.com/openshift/api/operator/v1"
routev1 "github.com/openshift/api/route/v1"
operatorcontroller "github.com/openshift/cluster-ingress-operator/pkg/operator/controller"
"github.com/openshift/cluster-ingress-operator/test/unit"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"

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

"github.com/prometheus/client_golang/prometheus/testutil"
)

// Test_Reconcile verifies Reconcile
// Note: This is intended to be a generic unit test. Edge cases will be tested in unit tests for specific functions.
func Test_Reconcile(t *testing.T) {
testCases := []struct {
name string
request reconcile.Request
ingressController *v1.IngressController
namespace *corev1.Namespace
routes routev1.RouteList
expectedMetricFormat string
expectError bool
}{
{
name: "ingress controller doesn't exist",
request: newReconcileRequest("foo"),
expectError: false,
},
{
name: "ingress controller is being deleted",
request: newReconcileRequest("foo"),
ingressController: newIngressController("foo", true, false),
},
{
name: "ingress controller is not admitted",
request: newReconcileRequest("foo"),
ingressController: newIngressController("foo", false, false),
},
{
name: "ingress controller is not admitted with no routes admitted",
request: newReconcileRequest("foo"),
ingressController: newIngressController("foo", false, true),
expectedMetricFormat: routePerShardMetric("foo", 0),
},
{
name: "ingress controller is admitted with a route admitted",
request: newReconcileRequest("foo"),
ingressController: newIngressController("foo", false, true),
routes: routev1.RouteList{
Items: []routev1.Route{
*newRouteWithAdmittedStatuses("test", "foo-ns", "foo"),
},
},
namespace: newNamespace("foo-ns", ""),
expectedMetricFormat: routePerShardMetric("foo", 1),
},
{
name: "ingress controller with routeSelector and no namespaceSelector with 2 routes with correct labels in namespace with no labels",
request: newReconcileRequest("foo"),
ingressController: newIngressControllerWithSelectors("foo", "shard", "", "", ""),
routes: routev1.RouteList{
Items: []routev1.Route{
*newRouteWithLabelWithAdmittedStatuses("test", "foo-ns", "shard", "foo"),
*newRouteWithLabelWithAdmittedStatuses("test2", "foo-ns", "shard", "foo"),
},
},
namespace: newNamespace("foo-ns", ""),
expectedMetricFormat: routePerShardMetric("foo", 2),
},
{
name: "ingress controller with no routeSelector and namespaceSelector with route with no labels in namespace with correct labels",
request: newReconcileRequest("foo"),
ingressController: newIngressControllerWithSelectors("foo", "", "", "shard", ""),
routes: routev1.RouteList{
Items: []routev1.Route{
*newRouteWithLabelWithAdmittedStatuses("test", "foo-ns", "", "foo"),
},
},
namespace: newNamespace("foo-ns", "shard"),
expectedMetricFormat: routePerShardMetric("foo", 1),
},
{
name: "ingress controller with expression routeSelector and no namespaceSelector with route with correct labels in namespace with no labels",
request: newReconcileRequest("foo"),
ingressController: newIngressControllerWithSelectors("foo", "", "shard", "", ""),
routes: routev1.RouteList{
Items: []routev1.Route{
*newRouteWithLabelWithAdmittedStatuses("test", "foo-ns", "shard", "foo"),
},
},
namespace: newNamespace("foo-ns", ""),
expectedMetricFormat: routePerShardMetric("foo", 1),
},
{
name: "ingress controller with no routeSelector and expression namespaceSelector with route with no labels in namespace with correct labels",
request: newReconcileRequest("foo"),
ingressController: newIngressControllerWithSelectors("foo", "", "", "", "shard"),
routes: routev1.RouteList{
Items: []routev1.Route{
*newRouteWithLabelWithAdmittedStatuses("test", "foo-ns", "", "foo"),
},
},
namespace: newNamespace("foo-ns", "shard"),
expectedMetricFormat: routePerShardMetric("foo", 1),
},
{
name: "ingress controller with routeSelector and namespaceSelector with route with correct labels in namespace with correct labels",
request: newReconcileRequest("foo"),
ingressController: newIngressControllerWithSelectors("foo", "shard", "", "shard", ""),
routes: routev1.RouteList{
Items: []routev1.Route{
*newRouteWithLabelWithAdmittedStatuses("test", "foo-ns", "shard", "foo"),
},
},
namespace: newNamespace("foo-ns", "shard"),
expectedMetricFormat: routePerShardMetric("foo", 1),
},
{
name: "ingress controller with routeSelector and no namespaceSelector with route with incorrect labels in namespace with no labels",
request: newReconcileRequest("foo"),
ingressController: newIngressControllerWithSelectors("foo", "shard", "", "", ""),
routes: routev1.RouteList{
Items: []routev1.Route{
*newRouteWithLabelWithAdmittedStatuses("test", "foo-ns", "not-shard", "foo"),
},
},
namespace: newNamespace("foo-ns", ""),
expectedMetricFormat: routePerShardMetric("foo", 0),
},
{
name: "ingress controller with no routeSelector and namespaceSelector with route with no labels in namespace with incorrect labels",
request: newReconcileRequest("foo"),
ingressController: newIngressControllerWithSelectors("foo", "", "", "shard", ""),
routes: routev1.RouteList{
Items: []routev1.Route{
*newRouteWithLabelWithAdmittedStatuses("test", "foo-ns", "", "foo"),
},
},
namespace: newNamespace("foo-ns", "not-shard"),
expectedMetricFormat: routePerShardMetric("foo", 0),
},
{
name: "ingress controller with routeSelector and namespaceSelector with route with correct labels in namespace with incorrect labels",
request: newReconcileRequest("foo"),
ingressController: newIngressControllerWithSelectors("foo", "shard", "", "shard", ""),
routes: routev1.RouteList{
Items: []routev1.Route{
*newRouteWithLabelWithAdmittedStatuses("test", "foo-ns", "shard", "foo"),
},
},
namespace: newNamespace("foo-ns", "not-shard"),
expectedMetricFormat: routePerShardMetric("foo", 0),
},
{
name: "ingress controller with expression routeSelector and expression namespaceSelector with route with correct labels in namespace with correct labels",
request: newReconcileRequest("foo"),
ingressController: newIngressControllerWithSelectors("foo", "", "shard", "", "shard"),
routes: routev1.RouteList{
Items: []routev1.Route{
*newRouteWithLabelWithAdmittedStatuses("test", "foo-ns", "shard", "foo"),
},
},
namespace: newNamespace("foo-ns", "shard"),
expectedMetricFormat: routePerShardMetric("foo", 1),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
r, client := newFakeReconciler()
if tc.ingressController != nil {
if err := client.Create(context.Background(), tc.ingressController); err != nil {
t.Errorf("error creating ingress controller: %v", err)
}
}
for _, route := range tc.routes.Items {
if err := client.Create(context.Background(), &route); err != nil {
t.Errorf("error creating route: %v", err)
}
}
if tc.namespace != nil {
if err := client.Create(context.Background(), tc.namespace); err != nil {
t.Errorf("error creating namespace: %v", err)
}
}
// Cleanup the routes per shard metrics
routeMetricsControllerRoutesPerShard.Reset()

if _, err := r.Reconcile(context.Background(), tc.request); err == nil && tc.expectError {
t.Errorf("expected error, got no error")
} else if err != nil && !tc.expectError {
t.Errorf("did not expected error: %v", err)
} else {
err := testutil.CollectAndCompare(routeMetricsControllerRoutesPerShard, strings.NewReader(tc.expectedMetricFormat))
if err != nil {
t.Error(err)
}
}
})
}
}

// Test_routeStatusAdmitted verifies that routeStatusAdmitted behaves correctly.
func Test_routeStatusAdmitted(t *testing.T) {
testCases := []struct {
Expand Down Expand Up @@ -109,3 +315,153 @@ func Test_routeStatusAdmitted(t *testing.T) {
})
}
}

// newFakeReconciler builds a reconciler object for configurable-route based on fake clients and caches.
func newFakeReconciler(initObjs ...client.Object) (*reconciler, client.Client) {
client := unit.NewFakeClient(initObjs...)
cache := unit.NewFakeCache(client)
r := reconciler{
cache: cache,
routeToIngresses: make(map[types.NamespacedName]sets.String),
namespace: operatorcontroller.DefaultOperatorNamespace,
}
return &r, client
}

// newNamespace returns a new namespace with the specified name
// and if label exists, with that label
func newNamespace(name, label string) *corev1.Namespace {
namespace := corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
}
if len(label) != 0 {
namespace.ObjectMeta.Labels = map[string]string{"type": label}
}
return &namespace
}

// newReconcileRequest returns a new reconcile request for the route-metrics controller.
func newReconcileRequest(icName string) reconcile.Request {
return reconcile.Request{
NamespacedName: types.NamespacedName{
Name: icName,
Namespace: operatorcontroller.DefaultOperatorNamespace,
},
}
}

// newRouteWithAdmittedStatuses returns a new route that is admitted by ingress controllers.
func newRouteWithAdmittedStatuses(name string, namespace string, icAdmitted ...string) *routev1.Route {
route := routev1.Route{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
}
if len(icAdmitted) != 0 {
for _, ic := range icAdmitted {
route.Status.Ingress = append(route.Status.Ingress, routev1.RouteIngress{
RouterName: ic,
Conditions: []routev1.RouteIngressCondition{
{
Type: routev1.RouteAdmitted,
Status: "True",
},
},
})
}
}
return &route
}

// newRouteWithLabelWithAdmittedStatuses returns a new route that is admitted by ingress controllers.
func newRouteWithLabelWithAdmittedStatuses(name, namespace, label string, icAdmitted ...string) *routev1.Route {
route := newRouteWithAdmittedStatuses(name, namespace, icAdmitted...)
if len(label) != 0 {
route.Labels = map[string]string{
"type": label,
}
}
return route
}

// newIngressController returns a new ingresscontroller with the specified name and admitted status.
func newIngressController(name string, deleting, admitted bool) *v1.IngressController {
ic := v1.IngressController{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: operatorcontroller.DefaultOperatorNamespace,
},
}
var admittedStatus v1.ConditionStatus
if admitted {
admittedStatus = v1.ConditionTrue
} else {
admittedStatus = v1.ConditionFalse
}
ic.Status = v1.IngressControllerStatus{
Conditions: []v1.OperatorCondition{{
Type: "Admitted",
Status: admittedStatus,
}},
}
if deleting {
ic.ObjectMeta.DeletionTimestamp = &metav1.Time{Time: time.Now()}
}
return &ic
}

// newIngressControllerWithSelectors returns a new ingresscontroller with the specified name,
// routeSelectors, and namespaceSelectors based on the parameters.
func newIngressControllerWithSelectors(name, routeMatchLabel, routeMatchExpression, namespaceMatchLabel,
namespaceMatchExpression string) *v1.IngressController {
ic := newIngressController(name, false, true)
if len(routeMatchLabel) != 0 {
ic.Spec.RouteSelector = &metav1.LabelSelector{
MatchLabels: map[string]string{
"type": routeMatchLabel,
},
}
}
if len(routeMatchExpression) != 0 {
ic.Spec.RouteSelector = &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "type",
Operator: metav1.LabelSelectorOpIn,
Values: []string{routeMatchExpression},
},
},
}
}
if len(namespaceMatchLabel) != 0 {
ic.Spec.NamespaceSelector = &metav1.LabelSelector{
MatchLabels: map[string]string{
"type": namespaceMatchLabel,
},
}
}
if len(namespaceMatchExpression) != 0 {
ic.Spec.NamespaceSelector = &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "type",
Operator: metav1.LabelSelectorOpIn,
Values: []string{namespaceMatchExpression},
},
},
}
}
return ic
}

// routePerShardMetric returns a formatted Prometheus string for comparison
func routePerShardMetric(icName string, routesAdmitted int) string {
return fmt.Sprintf(`
# HELP route_metrics_controller_routes_per_shard Report the number of routes for shards (ingress controllers).
# TYPE route_metrics_controller_routes_per_shard gauge
route_metrics_controller_routes_per_shard{shard_name="%s"} %d
`, icName, routesAdmitted)
}

0 comments on commit 22be71c

Please sign in to comment.