зеркало из https://github.com/Azure/ARO-RP.git
Splits ingress checker into a separate controller
This commit is contained in:
Родитель
39326bca8c
Коммит
556fb820b3
|
@ -26,8 +26,8 @@ import (
|
|||
"github.com/Azure/ARO-RP/pkg/operator/controllers/alertwebhook"
|
||||
"github.com/Azure/ARO-RP/pkg/operator/controllers/autosizednodes"
|
||||
"github.com/Azure/ARO-RP/pkg/operator/controllers/banner"
|
||||
"github.com/Azure/ARO-RP/pkg/operator/controllers/checker"
|
||||
"github.com/Azure/ARO-RP/pkg/operator/controllers/checkers/clusterdnschecker"
|
||||
"github.com/Azure/ARO-RP/pkg/operator/controllers/checkers/ingresscertificatechecker"
|
||||
"github.com/Azure/ARO-RP/pkg/operator/controllers/checkers/internetchecker"
|
||||
"github.com/Azure/ARO-RP/pkg/operator/controllers/checkers/serviceprincipalchecker"
|
||||
"github.com/Azure/ARO-RP/pkg/operator/controllers/clusteroperatoraro"
|
||||
|
@ -240,11 +240,6 @@ func operator(ctx context.Context, log *logrus.Entry) error {
|
|||
arocli, operatorcli)).SetupWithManager(mgr); err != nil {
|
||||
return fmt.Errorf("unable to create controller %s: %v", ingress.ControllerName, err)
|
||||
}
|
||||
if err = (checker.NewReconciler(
|
||||
log.WithField("controller", checker.ControllerName),
|
||||
arocli, kubernetescli, maocli, operatorcli, configcli, role)).SetupWithManager(mgr); err != nil {
|
||||
return fmt.Errorf("unable to create controller %s: %v", checker.ControllerName, err)
|
||||
}
|
||||
if err = (serviceprincipalchecker.NewReconciler(
|
||||
log.WithField("controller", serviceprincipalchecker.ControllerName),
|
||||
arocli, kubernetescli, role)).SetupWithManager(mgr); err != nil {
|
||||
|
@ -255,6 +250,11 @@ func operator(ctx context.Context, log *logrus.Entry) error {
|
|||
arocli, operatorcli, role)).SetupWithManager(mgr); err != nil {
|
||||
return fmt.Errorf("unable to create controller %s: %v", clusterdnschecker.ControllerName, err)
|
||||
}
|
||||
if err = (ingresscertificatechecker.NewReconciler(
|
||||
log.WithField("controller", ingresscertificatechecker.ControllerName),
|
||||
arocli, operatorcli, configcli, role)).SetupWithManager(mgr); err != nil {
|
||||
return fmt.Errorf("unable to create controller %s: %v", ingresscertificatechecker.ControllerName, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err = (internetchecker.NewReconciler(
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
package checker
|
||||
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the Apache License 2.0.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type Checker interface {
|
||||
Check(context.Context) error
|
||||
Name() string
|
||||
}
|
||||
|
||||
var errRequeue = errors.New("requeue")
|
|
@ -1,98 +0,0 @@
|
|||
package checker
|
||||
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the Apache License 2.0.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
configclient "github.com/openshift/client-go/config/clientset/versioned"
|
||||
machineclient "github.com/openshift/client-go/machine/clientset/versioned"
|
||||
operatorclient "github.com/openshift/client-go/operator/clientset/versioned"
|
||||
"github.com/sirupsen/logrus"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
arov1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1"
|
||||
aroclient "github.com/Azure/ARO-RP/pkg/operator/clientset/versioned"
|
||||
checkercommon "github.com/Azure/ARO-RP/pkg/operator/controllers/checkers/common"
|
||||
)
|
||||
|
||||
const (
|
||||
ControllerName = "Checker"
|
||||
)
|
||||
|
||||
// Reconciler runs a number of checkers
|
||||
type Reconciler struct {
|
||||
log *logrus.Entry
|
||||
|
||||
checkers []Checker
|
||||
arocli aroclient.Interface
|
||||
}
|
||||
|
||||
func NewReconciler(log *logrus.Entry, arocli aroclient.Interface, kubernetescli kubernetes.Interface, machinecli machineclient.Interface, operatorcli operatorclient.Interface, configcli configclient.Interface, role string) *Reconciler {
|
||||
checkers := []Checker{
|
||||
NewIngressCertificateChecker(log, arocli, operatorcli, configcli, role),
|
||||
}
|
||||
|
||||
return &Reconciler{
|
||||
log: log,
|
||||
checkers: checkers,
|
||||
arocli: arocli,
|
||||
}
|
||||
}
|
||||
|
||||
// This is the permissions that this controller needs to work.
|
||||
// "make generate" will run kubebuilder and cause operator/deploy/staticresources/*/role.yaml to be updated
|
||||
// from the annotation below.
|
||||
// +kubebuilder:rbac:groups=aro.openshift.io,resources=clusters,verbs=get;list;watch
|
||||
// +kubebuilder:rbac:groups=aro.openshift.io,resources=clusters/status,verbs=get;update;patch
|
||||
|
||||
// Reconcile will keep checking that the cluster can connect to essential services.
|
||||
func (r *Reconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) {
|
||||
instance, err := r.arocli.AroV1alpha1().Clusters().Get(ctx, arov1alpha1.SingletonClusterName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
if !instance.Spec.OperatorFlags.GetSimpleBoolean(checkercommon.ControllerEnabled) {
|
||||
r.log.Debug("controller is disabled")
|
||||
return reconcile.Result{}, nil
|
||||
}
|
||||
|
||||
r.log.Debug("running")
|
||||
for _, c := range r.checkers {
|
||||
thisErr := c.Check(ctx)
|
||||
if thisErr != nil {
|
||||
// do all checks even if there is an error
|
||||
err = thisErr
|
||||
if thisErr != errRequeue {
|
||||
r.log.Errorf("checker %s failed with %v", c.Name(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We always requeue here:
|
||||
// * Either immediately (with rate limiting) based on the error
|
||||
// when err != nil.
|
||||
// * Or based on RequeueAfter when err == nil.
|
||||
return reconcile.Result{RequeueAfter: time.Hour}, err
|
||||
}
|
||||
|
||||
// SetupWithManager setup our manager
|
||||
func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
aroClusterPredicate := predicate.NewPredicateFuncs(func(o client.Object) bool {
|
||||
return o.GetName() == arov1alpha1.SingletonClusterName
|
||||
})
|
||||
|
||||
builder := ctrl.NewControllerManagedBy(mgr).
|
||||
For(&arov1alpha1.Cluster{}, builder.WithPredicates(aroClusterPredicate))
|
||||
|
||||
return builder.Named(ControllerName).Complete(r)
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
// Implements a check that provides detail on potentially faulty or customised
|
||||
// IngressController configurations on the default controller.
|
||||
//
|
||||
// Included checks are:
|
||||
// - existence of custom ingress certificate
|
||||
// - existence of default ingresscontroller
|
||||
|
||||
package checker
|
||||
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the Apache License 2.0.
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
operatorv1 "github.com/openshift/api/operator/v1"
|
||||
configclient "github.com/openshift/client-go/config/clientset/versioned"
|
||||
operatorclient "github.com/openshift/client-go/operator/clientset/versioned"
|
||||
"github.com/sirupsen/logrus"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
arov1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1"
|
||||
aroclient "github.com/Azure/ARO-RP/pkg/operator/clientset/versioned"
|
||||
"github.com/Azure/ARO-RP/pkg/util/conditions"
|
||||
)
|
||||
|
||||
type IngressCertificateChecker struct {
|
||||
arocli aroclient.Interface
|
||||
operatorcli operatorclient.Interface
|
||||
configcli configclient.Interface
|
||||
|
||||
role string
|
||||
}
|
||||
|
||||
func NewIngressCertificateChecker(log *logrus.Entry, arocli aroclient.Interface, operatorcli operatorclient.Interface, configcli configclient.Interface, role string) *IngressCertificateChecker {
|
||||
return &IngressCertificateChecker{
|
||||
arocli: arocli,
|
||||
operatorcli: operatorcli,
|
||||
configcli: configcli,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *IngressCertificateChecker) Name() string {
|
||||
return "IngressCertificateChecker"
|
||||
}
|
||||
|
||||
func (r *IngressCertificateChecker) Check(ctx context.Context) error {
|
||||
cond := &operatorv1.OperatorCondition{
|
||||
Type: arov1alpha1.DefaultIngressCertificate,
|
||||
Status: operatorv1.ConditionUnknown,
|
||||
Message: "",
|
||||
Reason: "CheckDone",
|
||||
}
|
||||
|
||||
cv, err := r.configcli.ConfigV1().ClusterVersions().Get(ctx, "version", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
cond.Message = err.Error()
|
||||
cond.Reason = "CheckFailed"
|
||||
return conditions.SetCondition(ctx, r.arocli, cond, r.role)
|
||||
}
|
||||
|
||||
ingress, err := r.operatorcli.OperatorV1().IngressControllers("openshift-ingress-operator").Get(ctx, "default", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
cond.Message = err.Error()
|
||||
cond.Reason = "CheckFailed"
|
||||
return conditions.SetCondition(ctx, r.arocli, cond, r.role)
|
||||
}
|
||||
|
||||
if ingress.Spec.DefaultCertificate == nil {
|
||||
cond.Status = operatorv1.ConditionFalse
|
||||
cond.Message = "Ingress has no certificate yet"
|
||||
} else if ingress.Spec.DefaultCertificate.Name != string(cv.Spec.ClusterID)+"-ingress" {
|
||||
cond.Status = operatorv1.ConditionFalse
|
||||
cond.Message = "Custom ingress certificate in use: " + ingress.Spec.DefaultCertificate.Name
|
||||
} else {
|
||||
cond.Status = operatorv1.ConditionTrue
|
||||
cond.Message = "Default ingress certificate in use"
|
||||
}
|
||||
|
||||
return conditions.SetCondition(ctx, r.arocli, cond, r.role)
|
||||
}
|
|
@ -1,221 +0,0 @@
|
|||
package checker
|
||||
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the Apache License 2.0.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-test/deep"
|
||||
configv1 "github.com/openshift/api/config/v1"
|
||||
operatorv1 "github.com/openshift/api/operator/v1"
|
||||
configfake "github.com/openshift/client-go/config/clientset/versioned/fake"
|
||||
operatorfake "github.com/openshift/client-go/operator/clientset/versioned/fake"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
arov1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1"
|
||||
arofake "github.com/Azure/ARO-RP/pkg/operator/clientset/versioned/fake"
|
||||
)
|
||||
|
||||
func TestDefaultIngressCertificate(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
aroCluster *arov1alpha1.Cluster
|
||||
clusterVersion *configv1.ClusterVersion
|
||||
ingressController *operatorv1.IngressController
|
||||
expectedState operatorv1.OperatorCondition
|
||||
}{
|
||||
{
|
||||
name: "run: has default certificate",
|
||||
aroCluster: &arov1alpha1.Cluster{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: arov1alpha1.SingletonClusterName,
|
||||
},
|
||||
},
|
||||
clusterVersion: &configv1.ClusterVersion{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "version",
|
||||
},
|
||||
Spec: configv1.ClusterVersionSpec{
|
||||
ClusterID: "00000000-0000-0000-0000-000000000001",
|
||||
},
|
||||
},
|
||||
ingressController: &operatorv1.IngressController{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "default",
|
||||
Namespace: "openshift-ingress-operator",
|
||||
},
|
||||
Spec: operatorv1.IngressControllerSpec{
|
||||
DefaultCertificate: &corev1.LocalObjectReference{
|
||||
Name: "00000000-0000-0000-0000-000000000001-ingress",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedState: operatorv1.OperatorCondition{
|
||||
Type: arov1alpha1.DefaultIngressCertificate,
|
||||
Status: operatorv1.ConditionTrue,
|
||||
Reason: "CheckDone",
|
||||
Message: "Default ingress certificate in use",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "run: does not have default certificate",
|
||||
aroCluster: &arov1alpha1.Cluster{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: arov1alpha1.SingletonClusterName,
|
||||
},
|
||||
},
|
||||
clusterVersion: &configv1.ClusterVersion{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "version",
|
||||
},
|
||||
Spec: configv1.ClusterVersionSpec{
|
||||
ClusterID: "00000000-0000-0000-0000-000000000001",
|
||||
},
|
||||
},
|
||||
ingressController: &operatorv1.IngressController{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "default",
|
||||
Namespace: "openshift-ingress-operator",
|
||||
},
|
||||
Spec: operatorv1.IngressControllerSpec{
|
||||
DefaultCertificate: &corev1.LocalObjectReference{
|
||||
Name: "fancy-custom-cert",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedState: operatorv1.OperatorCondition{
|
||||
Type: arov1alpha1.DefaultIngressCertificate,
|
||||
Status: operatorv1.ConditionFalse,
|
||||
Reason: "CheckDone",
|
||||
Message: "Custom ingress certificate in use: fancy-custom-cert",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "run: does not have default certificate defined",
|
||||
aroCluster: &arov1alpha1.Cluster{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: arov1alpha1.SingletonClusterName,
|
||||
},
|
||||
},
|
||||
clusterVersion: &configv1.ClusterVersion{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "version",
|
||||
},
|
||||
Spec: configv1.ClusterVersionSpec{
|
||||
ClusterID: "00000000-0000-0000-0000-000000000001",
|
||||
},
|
||||
},
|
||||
ingressController: &operatorv1.IngressController{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "default",
|
||||
Namespace: "openshift-ingress-operator",
|
||||
},
|
||||
Spec: operatorv1.IngressControllerSpec{
|
||||
DefaultCertificate: nil,
|
||||
},
|
||||
},
|
||||
expectedState: operatorv1.OperatorCondition{
|
||||
Type: arov1alpha1.DefaultIngressCertificate,
|
||||
Status: operatorv1.ConditionFalse,
|
||||
Reason: "CheckDone",
|
||||
Message: "Ingress has no certificate yet",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "fail: no ingress controller",
|
||||
aroCluster: &arov1alpha1.Cluster{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: arov1alpha1.SingletonClusterName,
|
||||
},
|
||||
},
|
||||
clusterVersion: &configv1.ClusterVersion{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "version",
|
||||
},
|
||||
Spec: configv1.ClusterVersionSpec{
|
||||
ClusterID: "00000000-0000-0000-0000-000000000001",
|
||||
},
|
||||
},
|
||||
expectedState: operatorv1.OperatorCondition{
|
||||
Type: arov1alpha1.DefaultIngressCertificate,
|
||||
Status: operatorv1.ConditionUnknown,
|
||||
Reason: "CheckFailed",
|
||||
Message: `ingresscontrollers.operator.openshift.io "default" not found`,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "fail: no clusterversion",
|
||||
aroCluster: &arov1alpha1.Cluster{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: arov1alpha1.SingletonClusterName,
|
||||
},
|
||||
},
|
||||
ingressController: &operatorv1.IngressController{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "default",
|
||||
Namespace: "openshift-ingress-operator",
|
||||
},
|
||||
Spec: operatorv1.IngressControllerSpec{
|
||||
DefaultCertificate: &corev1.LocalObjectReference{
|
||||
Name: "fancy-custom-cert",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedState: operatorv1.OperatorCondition{
|
||||
Type: arov1alpha1.DefaultIngressCertificate,
|
||||
Status: operatorv1.ConditionUnknown,
|
||||
Reason: "CheckFailed",
|
||||
Message: `clusterversions.config.openshift.io "version" not found`,
|
||||
},
|
||||
},
|
||||
} {
|
||||
arocli := arofake.NewSimpleClientset()
|
||||
configcli := configfake.NewSimpleClientset()
|
||||
operatorcli := operatorfake.NewSimpleClientset()
|
||||
|
||||
if tt.aroCluster != nil {
|
||||
arocli = arofake.NewSimpleClientset(tt.aroCluster)
|
||||
}
|
||||
if tt.clusterVersion != nil {
|
||||
configcli = configfake.NewSimpleClientset(tt.clusterVersion)
|
||||
}
|
||||
if tt.ingressController != nil {
|
||||
operatorcli = operatorfake.NewSimpleClientset(tt.ingressController)
|
||||
}
|
||||
|
||||
sp := NewIngressCertificateChecker(nil, arocli, operatorcli, configcli, "")
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := sp.Check(ctx)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
cluster, err := arocli.AroV1alpha1().Clusters().Get(ctx, arov1alpha1.SingletonClusterName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
conds := []operatorv1.OperatorCondition{}
|
||||
|
||||
// nil out time
|
||||
for _, c := range cluster.Status.Conditions {
|
||||
c.LastTransitionTime = metav1.NewTime(time.Time{})
|
||||
conds = append(conds, c)
|
||||
}
|
||||
|
||||
errs := deep.Equal(conds, []operatorv1.OperatorCondition{tt.expectedState})
|
||||
for _, err := range errs {
|
||||
t.Error(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
// Implements a check that provides detail on potentially faulty or customised
|
||||
// IngressController configurations on the default controller.
|
||||
//
|
||||
// Included checks are:
|
||||
// - existence of custom ingress certificate
|
||||
// - existence of default ingresscontroller
|
||||
|
||||
package ingresscertificatechecker
|
||||
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the Apache License 2.0.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
configclient "github.com/openshift/client-go/config/clientset/versioned"
|
||||
operatorclient "github.com/openshift/client-go/operator/clientset/versioned"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
var (
|
||||
// errNoDefaultCertificate means a cluster has no default cert reference.
|
||||
// This can happen because of the following reasons:
|
||||
// 1. A cluster doesn't use a managed domain.
|
||||
// For example it was created with a custom domain)
|
||||
// or in a dev env where we don't have managed domains.
|
||||
// 2. When a customer changed the ingress config incorrectly.
|
||||
//
|
||||
// While the first is valid the second is something we should be aware of.
|
||||
errNoDefaultCertificate = errors.New("ingress has no default certificate set")
|
||||
)
|
||||
|
||||
type ingressCertificateChecker interface {
|
||||
Check(ctx context.Context) error
|
||||
}
|
||||
|
||||
type checker struct {
|
||||
operatorcli operatorclient.Interface
|
||||
configcli configclient.Interface
|
||||
}
|
||||
|
||||
func newIngressCertificateChecker(operatorcli operatorclient.Interface, configcli configclient.Interface) *checker {
|
||||
return &checker{
|
||||
operatorcli: operatorcli,
|
||||
configcli: configcli,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *checker) Check(ctx context.Context) error {
|
||||
cv, err := r.configcli.ConfigV1().ClusterVersions().Get(ctx, "version", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ingress, err := r.operatorcli.OperatorV1().IngressControllers("openshift-ingress-operator").Get(ctx, "default", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ingress.Spec.DefaultCertificate == nil {
|
||||
return errNoDefaultCertificate
|
||||
}
|
||||
|
||||
if ingress.Spec.DefaultCertificate.Name != string(cv.Spec.ClusterID)+"-ingress" {
|
||||
return fmt.Errorf("custom ingress certificate in use: %q", ingress.Spec.DefaultCertificate.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package ingresscertificatechecker
|
||||
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the Apache License 2.0.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
configv1 "github.com/openshift/api/config/v1"
|
||||
operatorv1 "github.com/openshift/api/operator/v1"
|
||||
configfake "github.com/openshift/client-go/config/clientset/versioned/fake"
|
||||
operatorfake "github.com/openshift/client-go/operator/clientset/versioned/fake"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestCheck(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
clusterVersion := &configv1.ClusterVersion{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "version",
|
||||
},
|
||||
Spec: configv1.ClusterVersionSpec{
|
||||
ClusterID: configv1.ClusterID("fake-cluster-id"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
ingressController *operatorv1.IngressController
|
||||
clusterVersion *configv1.ClusterVersion
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "default certificate is set",
|
||||
clusterVersion: clusterVersion,
|
||||
ingressController: fakeIngressController(&corev1.LocalObjectReference{
|
||||
Name: "fake-cluster-id-ingress",
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "unexpected certificate name",
|
||||
clusterVersion: clusterVersion,
|
||||
ingressController: fakeIngressController(&corev1.LocalObjectReference{
|
||||
Name: "fake-custom-name-ingress",
|
||||
}),
|
||||
wantErr: `custom ingress certificate in use: "fake-custom-name-ingress"`,
|
||||
},
|
||||
{
|
||||
name: "no default certificate set",
|
||||
clusterVersion: clusterVersion,
|
||||
ingressController: fakeIngressController(nil),
|
||||
wantErr: "ingress has no default certificate set",
|
||||
},
|
||||
{
|
||||
name: "missing IngressController",
|
||||
clusterVersion: clusterVersion,
|
||||
wantErr: `ingresscontrollers.operator.openshift.io "default" not found`,
|
||||
},
|
||||
{
|
||||
name: "missing ClusterVersion",
|
||||
wantErr: `clusterversions.config.openshift.io "version" not found`,
|
||||
},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
operatorcliFake := operatorfake.NewSimpleClientset()
|
||||
if tt.ingressController != nil {
|
||||
operatorcliFake.Tracker().Add(tt.ingressController)
|
||||
}
|
||||
configcliFake := configfake.NewSimpleClientset()
|
||||
if tt.clusterVersion != nil {
|
||||
configcliFake.Tracker().Add(tt.clusterVersion)
|
||||
}
|
||||
|
||||
sp := &checker{
|
||||
operatorcli: operatorcliFake,
|
||||
configcli: configcliFake,
|
||||
}
|
||||
|
||||
err := sp.Check(ctx)
|
||||
if err != nil && err.Error() != tt.wantErr ||
|
||||
err == nil && tt.wantErr != "" {
|
||||
t.Errorf("\n%s\n !=\n%s", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func fakeIngressController(certificateRef *corev1.LocalObjectReference) *operatorv1.IngressController {
|
||||
return &operatorv1.IngressController{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "default",
|
||||
Namespace: "openshift-ingress-operator",
|
||||
},
|
||||
Spec: operatorv1.IngressControllerSpec{
|
||||
DefaultCertificate: certificateRef,
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
package ingresscertificatechecker
|
||||
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the Apache License 2.0.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
configv1 "github.com/openshift/api/config/v1"
|
||||
operatorv1 "github.com/openshift/api/operator/v1"
|
||||
configclient "github.com/openshift/client-go/config/clientset/versioned"
|
||||
operatorclient "github.com/openshift/client-go/operator/clientset/versioned"
|
||||
"github.com/sirupsen/logrus"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
"sigs.k8s.io/controller-runtime/pkg/source"
|
||||
|
||||
arov1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1"
|
||||
aroclient "github.com/Azure/ARO-RP/pkg/operator/clientset/versioned"
|
||||
checkercommon "github.com/Azure/ARO-RP/pkg/operator/controllers/checkers/common"
|
||||
"github.com/Azure/ARO-RP/pkg/util/conditions"
|
||||
)
|
||||
|
||||
// This is the permissions that this controller needs to work.
|
||||
// "make generate" will run kubebuilder and cause operator/deploy/staticresources/*/role.yaml to be updated
|
||||
// from the annotation below.
|
||||
// +kubebuilder:rbac:groups=aro.openshift.io,resources=clusters,verbs=get;list;watch
|
||||
// +kubebuilder:rbac:groups=aro.openshift.io,resources=clusters/status,verbs=get;update;patch
|
||||
|
||||
const (
|
||||
ControllerName = "ServicePrincipalChecker"
|
||||
)
|
||||
|
||||
// Reconciler runs a number of checkers
|
||||
type Reconciler struct {
|
||||
log *logrus.Entry
|
||||
role string
|
||||
|
||||
arocli aroclient.Interface
|
||||
checker ingressCertificateChecker
|
||||
}
|
||||
|
||||
func NewReconciler(log *logrus.Entry, arocli aroclient.Interface, operatorcli operatorclient.Interface, configcli configclient.Interface, role string) *Reconciler {
|
||||
return &Reconciler{
|
||||
log: log,
|
||||
role: role,
|
||||
|
||||
arocli: arocli,
|
||||
checker: newIngressCertificateChecker(operatorcli, configcli),
|
||||
}
|
||||
}
|
||||
|
||||
// Reconcile will keep checking that the cluster has a valid default IngressController configuration.
|
||||
func (r *Reconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) {
|
||||
instance, err := r.arocli.AroV1alpha1().Clusters().Get(ctx, arov1alpha1.SingletonClusterName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
if !instance.Spec.OperatorFlags.GetSimpleBoolean(checkercommon.ControllerEnabled) {
|
||||
r.log.Debug("controller is disabled")
|
||||
return r.reconcileDisabled(ctx)
|
||||
}
|
||||
|
||||
r.log.Debug("running")
|
||||
checkErr := r.checker.Check(ctx)
|
||||
condition := r.condition(checkErr)
|
||||
|
||||
err = conditions.SetCondition(ctx, r.arocli, condition, r.role)
|
||||
if err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
// In case of this error we want to set condition to False, but
|
||||
// we don't want to continuously try to reconcile it as it might
|
||||
// be expected config in some cases (e.g. custom domain cluster)
|
||||
if errors.Is(checkErr, errNoDefaultCertificate) {
|
||||
return reconcile.Result{RequeueAfter: time.Hour}, nil
|
||||
}
|
||||
|
||||
// We always requeue here:
|
||||
// * Either immediately (with rate limiting) based on the error
|
||||
// when checkErr != nil.
|
||||
// * Or based on RequeueAfter when err == nil.
|
||||
return reconcile.Result{RequeueAfter: time.Hour}, checkErr
|
||||
}
|
||||
|
||||
func (r *Reconciler) reconcileDisabled(ctx context.Context) (ctrl.Result, error) {
|
||||
condition := &operatorv1.OperatorCondition{
|
||||
Type: arov1alpha1.DefaultIngressCertificate,
|
||||
Status: operatorv1.ConditionUnknown,
|
||||
}
|
||||
|
||||
return reconcile.Result{}, conditions.SetCondition(ctx, r.arocli, condition, r.role)
|
||||
}
|
||||
|
||||
func (r *Reconciler) condition(checkErr error) *operatorv1.OperatorCondition {
|
||||
if checkErr != nil {
|
||||
return &operatorv1.OperatorCondition{
|
||||
Type: arov1alpha1.DefaultIngressCertificate,
|
||||
Status: operatorv1.ConditionFalse,
|
||||
Message: checkErr.Error(),
|
||||
Reason: "CheckFailed",
|
||||
}
|
||||
}
|
||||
|
||||
return &operatorv1.OperatorCondition{
|
||||
Type: arov1alpha1.DefaultIngressCertificate,
|
||||
Status: operatorv1.ConditionTrue,
|
||||
Message: "Default ingress certificate is in use",
|
||||
Reason: "CheckDone",
|
||||
}
|
||||
}
|
||||
|
||||
// SetupWithManager setup our manager
|
||||
func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
aroClusterPredicate := predicate.NewPredicateFuncs(func(o client.Object) bool {
|
||||
return o.GetName() == arov1alpha1.SingletonClusterName
|
||||
})
|
||||
|
||||
defaultIngressControllerPredicate := predicate.NewPredicateFuncs(func(o client.Object) bool {
|
||||
return o.GetNamespace() == "openshift-ingress-operator" && o.GetName() == "default"
|
||||
})
|
||||
|
||||
clusterVersionPredicate := predicate.NewPredicateFuncs(func(o client.Object) bool {
|
||||
return o.GetName() == "version"
|
||||
})
|
||||
|
||||
builder := ctrl.NewControllerManagedBy(mgr).
|
||||
For(&arov1alpha1.Cluster{}, builder.WithPredicates(aroClusterPredicate)).
|
||||
Watches(
|
||||
&source.Kind{Type: &operatorv1.IngressController{}},
|
||||
&handler.EnqueueRequestForObject{},
|
||||
builder.WithPredicates(defaultIngressControllerPredicate),
|
||||
).
|
||||
Watches(
|
||||
&source.Kind{Type: &configv1.ClusterVersion{}},
|
||||
&handler.EnqueueRequestForObject{},
|
||||
builder.WithPredicates(clusterVersionPredicate),
|
||||
)
|
||||
|
||||
return builder.Named(ControllerName).Complete(r)
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
package ingresscertificatechecker
|
||||
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the Apache License 2.0.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
operatorv1 "github.com/openshift/api/operator/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
arov1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1"
|
||||
arofake "github.com/Azure/ARO-RP/pkg/operator/clientset/versioned/fake"
|
||||
checkercommon "github.com/Azure/ARO-RP/pkg/operator/controllers/checkers/common"
|
||||
"github.com/Azure/ARO-RP/pkg/util/azureclient"
|
||||
"github.com/Azure/ARO-RP/pkg/util/cmp"
|
||||
utillog "github.com/Azure/ARO-RP/pkg/util/log"
|
||||
)
|
||||
|
||||
type fakeChecker func(ctx context.Context) error
|
||||
|
||||
func (fc fakeChecker) Check(ctx context.Context) error {
|
||||
return fc(ctx)
|
||||
}
|
||||
|
||||
func TestReconcile(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
controllerDisabled bool
|
||||
checkerReturnErr error
|
||||
wantConditionStatus operatorv1.ConditionStatus
|
||||
wantConditionMessage string
|
||||
wantErr string
|
||||
wantResult reconcile.Result
|
||||
}{
|
||||
{
|
||||
name: "no errors",
|
||||
wantConditionStatus: operatorv1.ConditionTrue,
|
||||
wantConditionMessage: "Default ingress certificate is in use",
|
||||
wantResult: reconcile.Result{RequeueAfter: time.Hour},
|
||||
},
|
||||
{
|
||||
name: "check failed with an error",
|
||||
wantConditionStatus: operatorv1.ConditionFalse,
|
||||
wantConditionMessage: "fake basic error",
|
||||
checkerReturnErr: errors.New("fake basic error"),
|
||||
wantErr: "fake basic error",
|
||||
wantResult: reconcile.Result{RequeueAfter: time.Hour},
|
||||
},
|
||||
{
|
||||
name: "no default cert set",
|
||||
wantConditionStatus: operatorv1.ConditionFalse,
|
||||
wantConditionMessage: "ingress has no default certificate set",
|
||||
checkerReturnErr: errNoDefaultCertificate,
|
||||
wantResult: reconcile.Result{RequeueAfter: time.Hour},
|
||||
},
|
||||
{
|
||||
name: "controller disabled",
|
||||
controllerDisabled: true,
|
||||
wantConditionStatus: operatorv1.ConditionUnknown,
|
||||
wantResult: reconcile.Result{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
instance := &arov1alpha1.Cluster{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: arov1alpha1.SingletonClusterName,
|
||||
},
|
||||
Spec: arov1alpha1.ClusterSpec{
|
||||
AZEnvironment: azureclient.PublicCloud.Environment.Name,
|
||||
OperatorFlags: arov1alpha1.OperatorFlags{
|
||||
checkercommon.ControllerEnabled: "true",
|
||||
},
|
||||
},
|
||||
}
|
||||
if tt.controllerDisabled {
|
||||
instance.Spec.OperatorFlags[checkercommon.ControllerEnabled] = "false"
|
||||
}
|
||||
|
||||
arocli := arofake.NewSimpleClientset(instance)
|
||||
|
||||
r := &Reconciler{
|
||||
log: utillog.GetLogger(),
|
||||
role: "master",
|
||||
checker: fakeChecker(func(ctx context.Context) error {
|
||||
return tt.checkerReturnErr
|
||||
}),
|
||||
arocli: arocli,
|
||||
}
|
||||
|
||||
result, err := r.Reconcile(ctx, ctrl.Request{})
|
||||
if err != nil && err.Error() != tt.wantErr ||
|
||||
err == nil && tt.wantErr != "" {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tt.wantResult, result) {
|
||||
t.Error(cmp.Diff(tt.wantResult, result))
|
||||
}
|
||||
|
||||
instance, err = arocli.AroV1alpha1().Clusters().Get(ctx, arov1alpha1.SingletonClusterName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var condition *operatorv1.OperatorCondition
|
||||
for i := range instance.Status.Conditions {
|
||||
if instance.Status.Conditions[i].Type == arov1alpha1.DefaultIngressCertificate {
|
||||
condition = &instance.Status.Conditions[i]
|
||||
}
|
||||
}
|
||||
if condition == nil {
|
||||
t.Fatal("no condition found")
|
||||
}
|
||||
|
||||
if condition.Status != tt.wantConditionStatus {
|
||||
t.Errorf(string(condition.Status))
|
||||
}
|
||||
|
||||
if condition.Message != tt.wantConditionMessage {
|
||||
t.Errorf(condition.Message)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче