-
Notifications
You must be signed in to change notification settings - Fork 191
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a controller that creates and manages an ingressclass for each ingresscontroller. * pkg/operator/controller/ingressclass/controller.go: New file. Define a controller that manages ingressclasses for ingresscontrollers. (controllerName): New const for the new controller's name. (openshiftIngressClass): New const with the name of OpenShift's ingress-to-route controller, for use in ingressclasses. (New): New function. Create and return a controller that watches ingresscontrollers and ingressclasses and reconciles them, using the new ingressClassToIngressController method to map ingressclasses to ingresscontrollers. (ingressClassToIngressController): New method. Given an event for an ingressclass, return a slice of reconciliation requests for any corresponding ingresscontroller, using the new ingressClassReferencesSomeIngressController function. (ingressClassReferencesSomeIngressController): New function. Given an ingressclass, return a Boolean value indicating whether it references an ingresscontroller. (Config): New type. Store the configuration needed to create an ingressclass controller. (reconciler): New type. Store the state of an ingressclass controller. (Reconcile): New method. Reconcile a reconciliation request for an ingresscontroller by ensuring that it has the expected ingressclass. * pkg/operator/controller/ingressclass/ingressclass.go: New file. (ensureIngressClass): New method. Ensure the expected ingressclass exists for the given ingresscontroller, using the new desiredIngressClass function and currentIngressClass and updateIngressClass methods. (desiredIngressClass): New function. Given an ingresscontroller's name, return a desired ingressclass. (currentIngressClass): New method. Given an ingresscontroller's name, return its current ingressclass if it exists. (updateIngressClass): New method. Given current and desired ingressclasses, update the current ingress class if needed, using the new ingressclassChanged function. (ingressclassChanged): New function. Compare current and expected ingressclasses to determine if they match, and return an updated ingressclass if they do not. * pkg/operator/controller/names.go (IngressClassName): New function. Given an ingresscontroller's name, return the name of the corresponding ingressclass.
- Loading branch information
Showing
3 changed files
with
270 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package ingressclass | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/pkg/errors" | ||
|
||
logf "github.com/openshift/cluster-ingress-operator/pkg/log" | ||
|
||
"k8s.io/client-go/tools/record" | ||
|
||
operatorv1 "github.com/openshift/api/operator/v1" | ||
|
||
networkingv1 "k8s.io/api/networking/v1" | ||
|
||
"k8s.io/apimachinery/pkg/types" | ||
|
||
"sigs.k8s.io/controller-runtime/pkg/cache" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
"sigs.k8s.io/controller-runtime/pkg/controller" | ||
"sigs.k8s.io/controller-runtime/pkg/handler" | ||
"sigs.k8s.io/controller-runtime/pkg/manager" | ||
"sigs.k8s.io/controller-runtime/pkg/reconcile" | ||
"sigs.k8s.io/controller-runtime/pkg/source" | ||
) | ||
|
||
const ( | ||
controllerName = "ingressclass_controller" | ||
openshiftIngressClass = "openshift.io/ingress-to-route" | ||
) | ||
|
||
var ( | ||
log = logf.Logger.WithName(controllerName) | ||
) | ||
|
||
// New creates and returns a controller that creates and manages IngressClass | ||
// objects for IngressControllers. | ||
func New(mgr manager.Manager, config Config) (controller.Controller, error) { | ||
reconciler := &reconciler{ | ||
config: config, | ||
client: mgr.GetClient(), | ||
cache: mgr.GetCache(), | ||
recorder: mgr.GetEventRecorderFor(controllerName), | ||
} | ||
c, err := controller.New(controllerName, mgr, controller.Options{Reconciler: reconciler}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if err := c.Watch(&source.Kind{Type: &operatorv1.IngressController{}}, &handler.EnqueueRequestForObject{}); err != nil { | ||
return nil, err | ||
} | ||
if err := c.Watch(&source.Kind{Type: &networkingv1.IngressClass{}}, &handler.EnqueueRequestsFromMapFunc{ToRequests: handler.ToRequestsFunc(reconciler.ingressClassToIngressController)}); err != nil { | ||
return nil, err | ||
} | ||
return c, nil | ||
} | ||
|
||
// ingressClassToIngressController takes an event related to an ingressclass and | ||
// returns a slice of reconcile.Request to reconcile any corresponding | ||
// ingresscontroller. owners of object that match e.OwnerType. | ||
func (r *reconciler) ingressClassToIngressController(o handler.MapObject) []reconcile.Request { | ||
var requests []reconcile.Request | ||
classes := &networkingv1.IngressClassList{} | ||
if err := r.cache.List(context.Background(), classes); err != nil { | ||
log.Error(err, "failed to list ingressclasses") | ||
return requests | ||
} | ||
for _, class := range classes.Items { | ||
if !ingressClassReferencesSomeIngressController(&class) { | ||
continue | ||
} | ||
ingresscontrollerName := class.Spec.Parameters.Name | ||
log.Info("queueing ingresscontroller", "name", ingresscontrollerName, "related", o.Meta.GetSelfLink()) | ||
request := reconcile.Request{ | ||
NamespacedName: types.NamespacedName{ | ||
Namespace: r.config.Namespace, | ||
Name: ingresscontrollerName, | ||
}, | ||
} | ||
requests = append(requests, request) | ||
} | ||
return requests | ||
} | ||
|
||
// ingressClassReferencesSomeIngressController returns a value indicating | ||
// whether the provide IngressClass references an IngressController. | ||
func ingressClassReferencesSomeIngressController(class *networkingv1.IngressClass) bool { | ||
return class.Spec.Controller == openshiftIngressClass && | ||
class.Spec.Parameters != nil && | ||
class.Spec.Parameters.APIGroup != nil && | ||
*class.Spec.Parameters.APIGroup == operatorv1.GroupVersion.String() && | ||
class.Spec.Parameters.Kind == "IngressController" | ||
} | ||
|
||
// Config holds all the configuration that must be provided when creating the | ||
// controller. | ||
type Config struct { | ||
Namespace string | ||
} | ||
|
||
// reconciler handles the actual ingressclass reconciliation logic. | ||
type reconciler struct { | ||
config Config | ||
|
||
client client.Client | ||
cache cache.Cache | ||
recorder record.EventRecorder | ||
} | ||
|
||
// Reconcile expects request to refer to an IngressController in the operator | ||
// namespace and creates or reconciles an IngressClass object for that | ||
// IngressController. | ||
func (r *reconciler) Reconcile(request reconcile.Request) (reconcile.Result, error) { | ||
log.Info("reconciling", "request", request) | ||
|
||
classes := &networkingv1.IngressClassList{} | ||
if err := r.cache.List(context.Background(), classes); err != nil { | ||
return reconcile.Result{}, errors.Wrap(err, "failed to list ingressclasses") | ||
} | ||
|
||
if _, _, err := r.ensureIngressClass(request.NamespacedName.Name, classes.Items); err != nil { | ||
return reconcile.Result{}, errors.Wrapf(err, "failed to ensure ingressclass for ingresscontroller %q", request.NamespacedName) | ||
} | ||
|
||
return reconcile.Result{}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
package ingressclass | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
"github.com/google/go-cmp/cmp/cmpopts" | ||
|
||
operatorv1 "github.com/openshift/api/operator/v1" | ||
"github.com/openshift/cluster-ingress-operator/pkg/operator/controller" | ||
|
||
corev1 "k8s.io/api/core/v1" | ||
networkingv1 "k8s.io/api/networking/v1" | ||
|
||
"k8s.io/apimachinery/pkg/api/errors" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
) | ||
|
||
// ensureIngressClass ensures an IngressClass exists for the IngressController | ||
// with the given name. Returns a Boolean indicating whether the IngressClass | ||
// exists, the current IngressClass if it does exist, and an error value. | ||
func (r *reconciler) ensureIngressClass(ingresscontrollerName string, ingressclasses []networkingv1.IngressClass) (bool, *networkingv1.IngressClass, error) { | ||
want, desired := desiredIngressClass(ingresscontrollerName, ingressclasses) | ||
|
||
have, current, err := r.currentIngressClass(ingresscontrollerName) | ||
if err != nil { | ||
return false, nil, err | ||
} | ||
|
||
switch { | ||
case !want && !have: | ||
return false, nil, nil | ||
case !want && have: | ||
if err := r.client.Delete(context.TODO(), current); err != nil { | ||
if !errors.IsNotFound(err) { | ||
return true, current, fmt.Errorf("failed to delete IngressClass: %v", err) | ||
} | ||
} else { | ||
log.Info("deleted IngressClass", "ingressclass", current) | ||
} | ||
return false, nil, nil | ||
case want && !have: | ||
if err := r.client.Create(context.TODO(), desired); err != nil { | ||
return false, nil, fmt.Errorf("failed to create IngressClass: %v", err) | ||
} | ||
log.Info("created IngressClass", "ingressclass", desired) | ||
return r.currentIngressClass(ingresscontrollerName) | ||
case want && have: | ||
if updated, err := r.updateIngressClass(current, desired); err != nil { | ||
return true, current, fmt.Errorf("failed to update IngressClass: %v", err) | ||
} else if updated { | ||
log.Info("updated IngressClass", "ingressclass", desired) | ||
return r.currentIngressClass(ingresscontrollerName) | ||
} | ||
} | ||
|
||
return true, current, nil | ||
} | ||
|
||
// desiredIngressClass returns a Boolean indicating whether an IngressClass | ||
// is desired, as well as the IngressClass if one is desired. | ||
func desiredIngressClass(ingresscontrollerName string, ingressclasses []networkingv1.IngressClass) (bool, *networkingv1.IngressClass) { | ||
apiGroup := operatorv1.GroupVersion.String() | ||
name := controller.IngressClassName(ingresscontrollerName) | ||
class := &networkingv1.IngressClass{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: name.Name, | ||
}, | ||
Spec: networkingv1.IngressClassSpec{ | ||
Controller: openshiftIngressClass, | ||
Parameters: &corev1.TypedLocalObjectReference{ | ||
APIGroup: &apiGroup, | ||
Kind: "IngressController", | ||
Name: ingresscontrollerName, | ||
}, | ||
}, | ||
} | ||
// When creating an IngressClass for the "default" IngressController, | ||
// annotate the IngressClass as the default IngressClass if no other | ||
// IngressClass has the annotation. | ||
if ingresscontrollerName == "default" { | ||
const defaultAnnotation = "ingressclass.kubernetes.io/is-default-class" | ||
someIngressClassIsDefault := false | ||
for _, class := range ingressclasses { | ||
if class.Annotations[defaultAnnotation] == "true" { | ||
someIngressClassIsDefault = true | ||
break | ||
} | ||
} | ||
if !someIngressClassIsDefault { | ||
class.ObjectMeta.Annotations = map[string]string{ | ||
defaultAnnotation: "true", | ||
} | ||
} | ||
} | ||
return true, class | ||
} | ||
|
||
// currentIngressClass returns a Boolean indicating whether an IngressClass | ||
// exists for the IngressController with the given name, as well as the | ||
// IngressClass if it does exist and an error value. | ||
func (r *reconciler) currentIngressClass(ingresscontrollerName string) (bool, *networkingv1.IngressClass, error) { | ||
name := controller.IngressClassName(ingresscontrollerName) | ||
class := &networkingv1.IngressClass{} | ||
if err := r.client.Get(context.TODO(), name, class); err != nil { | ||
if errors.IsNotFound(err) { | ||
return false, nil, nil | ||
} | ||
return false, nil, err | ||
} | ||
return true, class, nil | ||
} | ||
|
||
// updateIngressClass updates an IngressClass. Returns a Boolean indicating | ||
// whether the IngressClass was updated, and an error value. | ||
func (r *reconciler) updateIngressClass(current, desired *networkingv1.IngressClass) (bool, error) { | ||
changed, updated := ingressclassChanged(current, desired) | ||
if !changed { | ||
return false, nil | ||
} | ||
|
||
if err := r.client.Update(context.TODO(), updated); err != nil { | ||
return false, err | ||
} | ||
return true, nil | ||
} | ||
|
||
// ingressclassChanged checks if the current IngressClass spec matches | ||
// the expected spec and if not returns an updated one. | ||
func ingressclassChanged(current, expected *networkingv1.IngressClass) (bool, *networkingv1.IngressClass) { | ||
if cmp.Equal(current.Spec, expected.Spec, cmpopts.EquateEmpty()) { | ||
return false, nil | ||
} | ||
|
||
updated := current.DeepCopy() | ||
updated.Spec = expected.Spec | ||
|
||
return true, updated | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters