-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
389 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,121 @@ | ||
package integration | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os/exec" | ||
"time" | ||
|
||
. "github.com/onsi/ginkgo/v2" | ||
. "github.com/onsi/gomega" | ||
. "github.com/onsi/gomega/gexec" | ||
"github.com/onsi/gomega/types" | ||
av1alpha1 "github.com/openshift/addon-operator/apis/addons/v1alpha1" | ||
addoninstance "github.com/openshift/addon-operator/pkg/client" | ||
internaltesting "github.com/openshift/reference-addon/internal/testing" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
) | ||
|
||
var _ = Describe("Status Controller", func() { | ||
var ( | ||
ctx context.Context | ||
cancel context.CancelFunc | ||
deleteLabel string | ||
deleteLabelGen = nameGenerator("ref-test-label") | ||
addonInstanceName string | ||
addonInstanceNameGen = nameGenerator("ai-test-name") | ||
namespace string | ||
namespaceGen = nameGenerator("ref-test-namespace") | ||
operatorName string | ||
operatorNameGen = nameGenerator("ref-test-name") | ||
heartbeatInterval = 1 * time.Second | ||
) | ||
|
||
BeforeEach(func() { | ||
ctx, cancel = context.WithCancel(context.Background()) | ||
|
||
addonInstanceName = addonInstanceNameGen() | ||
deleteLabel = deleteLabelGen() | ||
namespace = namespaceGen() | ||
operatorName = operatorNameGen() | ||
|
||
By("Starting manager") | ||
|
||
manager := exec.Command(_binPath, | ||
"-addon-instance-name", addonInstanceName, | ||
"-addon-instance-namespace", namespace, | ||
"-namespace", namespace, | ||
"-delete-label", deleteLabel, | ||
"-operator-name", operatorName, | ||
"-kubeconfig", _kubeConfigPath, | ||
"-heartbeat-interval", heartbeatInterval.String(), | ||
) | ||
|
||
session, err := Start(manager, GinkgoWriter, GinkgoWriter) | ||
Expect(err).ToNot(HaveOccurred()) | ||
|
||
By("Creating the addon namespace") | ||
|
||
ns := addonNamespace(namespace) | ||
addonInstance := addonInstanceObject(addonInstanceName, namespace) | ||
|
||
_client.Create(ctx, &ns) | ||
_client.Create(ctx, &addonInstance) | ||
|
||
rbac, err := getRBAC(namespace, managerGroup) | ||
Expect(err).ToNot(HaveOccurred()) | ||
|
||
for _, obj := range rbac { | ||
_client.Create(ctx, obj) | ||
} | ||
|
||
DeferCleanup(func() { | ||
cancel() | ||
|
||
By("Stopping the managers") | ||
|
||
session.Interrupt() | ||
|
||
if usingExistingCluster() { | ||
By("Deleting test namspace") | ||
|
||
_client.Delete(ctx, &ns) | ||
} | ||
}) | ||
}) | ||
|
||
When("Addon Instance Object Exists", func() { | ||
Context("Reference Addon Status Available'", func() { | ||
It("Addon Instance should report Availalbe condition", func() { | ||
addonInstance := addonInstanceObject(addonInstanceName, namespace) | ||
_client.EventuallyObjectExists(ctx, &addonInstance, internaltesting.WithTimeout(10*time.Second)) | ||
|
||
expectedCondition := addoninstance.NewAddonInstanceConditionInstalled( | ||
"True", | ||
av1alpha1.AddonInstanceInstalledReasonSetupComplete, | ||
"All Components Available", | ||
) | ||
|
||
Eventually(func() []metav1.Condition { | ||
_client.Get(ctx, &addonInstance) | ||
|
||
return addonInstance.Status.Conditions | ||
}, 10*time.Second).Should(ContainElements(EqualCondition(expectedCondition))) | ||
|
||
fmt.Printf("%+v\n", addonInstance) | ||
|
||
Expect(addonInstance.Spec.HeartbeatUpdatePeriod.Duration).To(Equal(heartbeatInterval)) | ||
}) | ||
}) | ||
}) | ||
}) | ||
|
||
// Check if conditions match | ||
func EqualCondition(expected metav1.Condition) types.GomegaMatcher { | ||
return And( | ||
HaveField("Type", expected.Type), | ||
HaveField("Status", expected.Status), | ||
HaveField("Reason", expected.Reason), | ||
HaveField("Message", expected.Message), | ||
) | ||
} |
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,43 @@ | ||
package status | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/go-logr/logr" | ||
) | ||
|
||
type WithLog struct{ Log logr.Logger } | ||
|
||
func (w WithLog) ConfigureStatusControllerReconciler(c *StatusControllerReconcilerConfig) { | ||
c.Log = w.Log | ||
} | ||
|
||
type WithAddonInstanceNamespace string | ||
|
||
func (w WithAddonInstanceNamespace) ConfigureStatusControllerReconciler(c *StatusControllerReconcilerConfig) { | ||
c.AddonInstanceNamespace = string(w) | ||
} | ||
|
||
type WithAddonInstanceName string | ||
|
||
func (w WithAddonInstanceName) ConfigureStatusControllerReconciler(c *StatusControllerReconcilerConfig) { | ||
c.AddonInstanceName = string(w) | ||
} | ||
|
||
type WithReferenceAddonNamespace string | ||
|
||
func (w WithReferenceAddonNamespace) ConfigureStatusControllerReconciler(c *StatusControllerReconcilerConfig) { | ||
c.ReferenceAddonNamespace = string(w) | ||
} | ||
|
||
type WithReferenceAddonName string | ||
|
||
func (w WithReferenceAddonName) ConfigureStatusControllerReconciler(c *StatusControllerReconcilerConfig) { | ||
c.ReferenceAddonName = string(w) | ||
} | ||
|
||
type WithHeartbeatInterval time.Duration | ||
|
||
func (w WithHeartbeatInterval) ConfigureStatusControllerReconciler(c *StatusControllerReconcilerConfig) { | ||
c.HeartBeatInterval = time.Duration(w) | ||
} |
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,225 @@ | ||
package status | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"time" | ||
|
||
"github.com/go-logr/logr" | ||
|
||
av1alpha1 "github.com/openshift/addon-operator/apis/addons/v1alpha1" | ||
addoninstance "github.com/openshift/addon-operator/pkg/client" | ||
rv1alpha1 "github.com/openshift/reference-addon/apis/reference/v1alpha1" | ||
"github.com/openshift/reference-addon/internal/controllers" | ||
"k8s.io/apimachinery/pkg/api/meta" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/types" | ||
|
||
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/reconcile" | ||
"sigs.k8s.io/controller-runtime/pkg/source" | ||
) | ||
|
||
type StatusControllerReconciler struct { | ||
cfg StatusControllerReconcilerConfig | ||
client client.Client | ||
addonInstanceClient *addoninstance.AddonInstanceClientImpl | ||
} | ||
|
||
// Grabbing namespace/name needs to be an option | ||
func NewStatusControllerReconciler(client client.Client, opts ...StatusControllerReconcilerOption) (*StatusControllerReconciler, error) { | ||
var cfg StatusControllerReconcilerConfig | ||
|
||
cfg.Option(opts...) | ||
cfg.Default() | ||
|
||
return &StatusControllerReconciler{ | ||
cfg: cfg, | ||
client: client, | ||
addonInstanceClient: addoninstance.NewAddonInstanceClient(client), | ||
}, nil | ||
} | ||
|
||
type StatusControllerReconcilerConfig struct { | ||
Log logr.Logger | ||
|
||
AddonInstanceNamespace string | ||
AddonInstanceName string | ||
ReferenceAddonNamespace string | ||
ReferenceAddonName string | ||
HeartBeatInterval time.Duration | ||
} | ||
|
||
type StatusControllerReconcilerOption interface { | ||
ConfigureStatusControllerReconciler(*StatusControllerReconcilerConfig) | ||
} | ||
|
||
// Status controller option | ||
func (c *StatusControllerReconcilerConfig) Option(opts ...StatusControllerReconcilerOption) { | ||
for _, opt := range opts { | ||
opt.ConfigureStatusControllerReconciler(c) | ||
} | ||
} | ||
|
||
func (c *StatusControllerReconcilerConfig) Default() { | ||
if c.Log.GetSink() == nil { | ||
c.Log = logr.Discard() | ||
} | ||
if c.HeartBeatInterval == 0 { | ||
c.HeartBeatInterval = 10 * time.Second | ||
} | ||
} | ||
|
||
// Watch reference addon actions to trigger addon instance | ||
func (r *StatusControllerReconciler) SetupWithManager(mgr ctrl.Manager) error { | ||
//desired := r.desiredReferenceAddon() | ||
requestObject := types.NamespacedName{ | ||
Name: r.cfg.AddonInstanceName, | ||
Namespace: r.cfg.AddonInstanceNamespace, | ||
} | ||
|
||
statusControllerHandler := handler.EnqueueRequestsFromMapFunc(func(_ client.Object) []reconcile.Request { | ||
return []reconcile.Request{ | ||
{ | ||
NamespacedName: requestObject, | ||
}, | ||
} | ||
}) | ||
return ctrl.NewControllerManagedBy(mgr). | ||
For(&av1alpha1.AddonInstance{}). | ||
Watches( | ||
&source.Kind{Type: &rv1alpha1.ReferenceAddon{}}, | ||
statusControllerHandler, | ||
builder.WithPredicates(controllers.HasNamePrefix(r.cfg.ReferenceAddonName)), | ||
). | ||
Complete(r) | ||
} | ||
|
||
// Utilize info gathered from SetupWithManager to perform logic against | ||
func (r *StatusControllerReconciler) Reconcile(ctx context.Context, req reconcile.Request) (ctrl.Result, error) { | ||
ai, err := r.getAddonInstance(ctx) | ||
if err != nil { | ||
r.cfg.Log.Error(err, "getting addon instance") | ||
|
||
return ctrl.Result{RequeueAfter: r.cfg.HeartBeatInterval}, nil | ||
} | ||
|
||
if ai.Spec.HeartbeatUpdatePeriod.Duration != r.cfg.HeartBeatInterval { | ||
r.cfg.Log.Info("patching heartbeat interval") | ||
|
||
if err := r.patchHeartbeatInterval(ctx, ai); err != nil { | ||
r.cfg.Log.Error(err, "patching heartbeat interval") | ||
|
||
return ctrl.Result{RequeueAfter: r.cfg.HeartBeatInterval}, nil | ||
} | ||
} | ||
|
||
refAddon, err := r.getReferenceAddon(ctx) | ||
if err != nil { | ||
r.cfg.Log.Error(err, "getting reference addon") | ||
|
||
return ctrl.Result{RequeueAfter: r.cfg.HeartBeatInterval}, nil | ||
} | ||
|
||
conditions := r.getConditions(refAddon) | ||
|
||
if err := r.addonInstanceClient.SendPulse(ctx, ai, addoninstance.WithConditions(conditions)); err != nil { | ||
r.cfg.Log.Error(err, "sending pulse to addon instance") | ||
|
||
return ctrl.Result{}, err | ||
} | ||
|
||
r.cfg.Log.Info("successfully reconciled AddonInstance") | ||
|
||
return ctrl.Result{RequeueAfter: r.cfg.HeartBeatInterval}, nil | ||
} | ||
|
||
func (r *StatusControllerReconciler) getAddonInstance(ctx context.Context) (av1alpha1.AddonInstance, error) { | ||
log := r.cfg.Log.WithValues( | ||
"namespace", r.cfg.AddonInstanceNamespace, | ||
"name", r.cfg.AddonInstanceName, | ||
) | ||
|
||
addonInstanceKey := client.ObjectKey{ | ||
Namespace: r.cfg.AddonInstanceNamespace, | ||
Name: r.cfg.AddonInstanceName, | ||
} | ||
|
||
var addonInstance av1alpha1.AddonInstance | ||
if err := r.client.Get(ctx, addonInstanceKey, &addonInstance); err != nil { | ||
return addonInstance, fmt.Errorf("getting addon instance: %w", err) | ||
} | ||
|
||
log.Info("found addon instance") | ||
|
||
return addonInstance, nil | ||
} | ||
|
||
func (r *StatusControllerReconciler) patchHeartbeatInterval(ctx context.Context, ai av1alpha1.AddonInstance) error { | ||
patch := map[string]interface{}{ | ||
"metadata": map[string]interface{}{ | ||
"resourceVersion": ai.GetResourceVersion(), | ||
}, | ||
"spec": map[string]interface{}{ | ||
"heartbeatUpdatePeriod": metav1.Duration{ | ||
Duration: r.cfg.HeartBeatInterval, | ||
}, | ||
}, | ||
} | ||
|
||
patchJson, err := json.Marshal(&patch) | ||
if err != nil { | ||
return fmt.Errorf("marshalling raw patch: %w", err) | ||
} | ||
|
||
return r.client.Patch(ctx, &ai, client.RawPatch(types.MergePatchType, patchJson)) | ||
} | ||
|
||
func (r *StatusControllerReconciler) getReferenceAddon(ctx context.Context) (rv1alpha1.ReferenceAddon, error) { | ||
log := r.cfg.Log.WithValues( | ||
"namespace", r.cfg.ReferenceAddonNamespace, | ||
"name", r.cfg.ReferenceAddonName, | ||
) | ||
|
||
log.Info("getting reference addon") | ||
|
||
referenceAddonKey := client.ObjectKey{ | ||
Namespace: r.cfg.ReferenceAddonNamespace, | ||
Name: r.cfg.ReferenceAddonName, | ||
} | ||
|
||
var referenceAddon rv1alpha1.ReferenceAddon | ||
if err := r.client.Get(ctx, referenceAddonKey, &referenceAddon); err != nil { | ||
return referenceAddon, fmt.Errorf("getting reference addon: %w", err) | ||
} | ||
|
||
log.Info("found reference addon") | ||
|
||
return referenceAddon, nil | ||
} | ||
|
||
func (r *StatusControllerReconciler) getConditions(ra rv1alpha1.ReferenceAddon) []metav1.Condition { | ||
var conditions []metav1.Condition | ||
|
||
isAvailable := meta.IsStatusConditionTrue( | ||
ra.Status.Conditions, | ||
rv1alpha1.ReferenceAddonConditionAvailable.String(), | ||
) | ||
|
||
// Check if Reference addon is available for the first time | ||
if isAvailable { | ||
r.cfg.Log.Info("Reference Addon Successfully Installed") | ||
|
||
conditions = append(conditions, addoninstance.NewAddonInstanceConditionInstalled( | ||
"True", | ||
av1alpha1.AddonInstanceInstalledReasonSetupComplete, | ||
"All Components Available", | ||
)) | ||
} | ||
|
||
return conditions | ||
} |