Skip to content

Commit a5792ff

Browse files
authored
OCPBUGS-25599: Reconcile AgentMachines for unbound Agents (#95)
AgentMachines need to be reconciled if they don't yet have an Agent, and and a valid Agent is modified. Otherwise AgentMachines that don't yet have an Agent will not be reconciled when an Agent becomes available.
1 parent 00f0c9f commit a5792ff

File tree

2 files changed

+118
-28
lines changed

2 files changed

+118
-28
lines changed

controllers/agentmachine_controller.go

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ func (r *AgentMachineReconciler) findAgent(ctx context.Context, log logrus.Field
340340

341341
// Find an agent that is unbound and whose validations pass
342342
for i := 0; i < len(agents.Items) && foundAgent == nil; i++ {
343-
if isValidAgent(&agents.Items[i], agentMachine) {
343+
if isValidAgent(&agents.Items[i]) {
344344
foundAgent = &agents.Items[i]
345345
log.Infof("Found agent to associate with AgentMachine: %s/%s", foundAgent.Namespace, foundAgent.Name)
346346
err = r.updateFoundAgent(ctx, log, agentMachine, foundAgent, clusterDeploymentRef, machineConfigPool, ignitionTokenSecretRef)
@@ -511,7 +511,7 @@ func (r *AgentMachineReconciler) processBootstrapDataSecret(ctx context.Context,
511511
return machineConfigPool, ignitionTokenSecretRef, nil
512512
}
513513

514-
func isValidAgent(agent *aiv1beta1.Agent, agentMachine *capiproviderv1.AgentMachine) bool {
514+
func isValidAgent(agent *aiv1beta1.Agent) bool {
515515
if !agent.Spec.Approved {
516516
return false
517517
}
@@ -675,31 +675,33 @@ func (r *AgentMachineReconciler) mapMachineToAgentMachine(ctx context.Context, m
675675
return []reconcile.Request{}
676676
}
677677

678-
// SetupWithManager sets up the controller with the Manager.
679-
func (r *AgentMachineReconciler) SetupWithManager(mgr ctrl.Manager, agentNamespace string) error {
680-
mapAgentToAgentMachine := func(ctx context.Context, agent client.Object) []reconcile.Request {
681-
log := r.Log.WithFields(
682-
logrus.Fields{
683-
"agent": agent.GetName(),
684-
"agent_namespace": agent.GetNamespace(),
685-
})
678+
func (r *AgentMachineReconciler) mapAgentToAgentMachine(ctx context.Context, a client.Object) []reconcile.Request {
679+
log := r.Log.WithFields(
680+
logrus.Fields{
681+
"agent": a.GetName(),
682+
"agent_namespace": a.GetNamespace(),
683+
})
686684

687-
namespace, ok := agent.GetAnnotations()[AgentMachineRefNamespace]
688-
if !ok {
689-
return make([]reconcile.Request, 0)
690-
}
685+
amList := &capiproviderv1.AgentMachineList{}
686+
opts := &client.ListOptions{}
687+
mappedAgent := false
691688

692-
amList := &capiproviderv1.AgentMachineList{}
693-
opts := &client.ListOptions{
694-
Namespace: namespace,
695-
}
689+
// The annotation will only be present on an Agent that's mapped to an AgentMachine
690+
namespace, ok := a.GetAnnotations()[AgentMachineRefNamespace]
691+
if ok {
692+
opts.Namespace = namespace
693+
mappedAgent = true
694+
}
696695

697-
if err := r.List(ctx, amList, opts); err != nil {
698-
log.WithError(err).Error("failed to list agent machines")
699-
return []reconcile.Request{}
700-
}
696+
if err := r.List(ctx, amList, opts); err != nil {
697+
log.WithError(err).Error("failed to list agent machines")
698+
return []reconcile.Request{}
699+
}
701700

702-
reply := make([]reconcile.Request, 0, len(amList.Items))
701+
reply := make([]reconcile.Request, 0, len(amList.Items))
702+
if mappedAgent {
703+
// If the Agent is mapped to an AgentMachine, return only that AgentMachine
704+
agent := a
703705
for _, agentMachine := range amList.Items {
704706
if agentMachine.Status.AgentRef != nil &&
705707
agentMachine.Status.AgentRef.Namespace == agent.GetNamespace() &&
@@ -711,12 +713,34 @@ func (r *AgentMachineReconciler) SetupWithManager(mgr ctrl.Manager, agentNamespa
711713
break
712714
}
713715
}
714-
return reply
716+
} else {
717+
// If the Agent isn't mapped to an AgentMachine and it's "valid" for use, return any AgentMachines
718+
// that don't have an Agent mapped to them yet
719+
agent := &aiv1beta1.Agent{}
720+
if err := r.Get(ctx, types.NamespacedName{Name: a.GetName(), Namespace: a.GetNamespace()}, agent); err != nil {
721+
return []reconcile.Request{}
722+
}
723+
if !isValidAgent(agent) {
724+
return []reconcile.Request{}
725+
}
726+
for _, agentMachine := range amList.Items {
727+
if agentMachine.Status.AgentRef == nil {
728+
reply = append(reply, reconcile.Request{NamespacedName: types.NamespacedName{
729+
Namespace: agentMachine.Namespace,
730+
Name: agentMachine.Name,
731+
}})
732+
}
733+
}
715734
}
716735

736+
return reply
737+
}
738+
739+
// SetupWithManager sets up the controller with the Manager.
740+
func (r *AgentMachineReconciler) SetupWithManager(mgr ctrl.Manager, agentNamespace string) error {
717741
return ctrl.NewControllerManagedBy(mgr).
718742
For(&capiproviderv1.AgentMachine{}).
719-
Watches(&aiv1beta1.Agent{}, handler.EnqueueRequestsFromMapFunc(mapAgentToAgentMachine)).
743+
Watches(&aiv1beta1.Agent{}, handler.EnqueueRequestsFromMapFunc(r.mapAgentToAgentMachine)).
720744
Watches(&clusterv1.Machine{}, handler.EnqueueRequestsFromMapFunc(r.mapMachineToAgentMachine)).
721745
Complete(r)
722746
}

controllers/agentmachine_controller_test.go

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,10 @@ func newAgentMachine(name, namespace string, spec capiproviderv1.AgentMachineSpe
154154
func newAgent(name, namespace string, spec aiv1beta1.AgentSpec) *aiv1beta1.Agent {
155155
return &aiv1beta1.Agent{
156156
ObjectMeta: metav1.ObjectMeta{
157-
Name: name,
158-
Namespace: namespace,
159-
Labels: make(map[string]string),
157+
Name: name,
158+
Namespace: namespace,
159+
Labels: make(map[string]string),
160+
Annotations: make(map[string]string),
160161
},
161162
Spec: spec,
162163
Status: aiv1beta1.AgentStatus{
@@ -642,3 +643,68 @@ var _ = Describe("mapMachineToAgentMachine", func() {
642643
Expect(amr.mapMachineToAgentMachine(ctx, &machine)).To(BeEmpty())
643644
})
644645
})
646+
647+
var _ = Describe("mapAgentToAgentMachine", func() {
648+
var (
649+
c client.Client
650+
amr *AgentMachineReconciler
651+
ctx = context.Background()
652+
testNamespace = "test-namespace"
653+
)
654+
655+
BeforeEach(func() {
656+
c = fakeclient.NewClientBuilder().WithScheme(scheme.Scheme).Build()
657+
amr = &AgentMachineReconciler{
658+
Client: c,
659+
Scheme: scheme.Scheme,
660+
Log: logrus.New(),
661+
AgentClient: c,
662+
}
663+
})
664+
665+
It("returns a request for the related agent machine", func() {
666+
agent := newAgent("agent", testNamespace, aiv1beta1.AgentSpec{})
667+
agent.ObjectMeta.Annotations[AgentMachineRefNamespace] = testNamespace
668+
Expect(c.Create(ctx, agent)).To(BeNil())
669+
agentMachine, _ := newAgentMachine("agentMachine-1", testNamespace, capiproviderv1.AgentMachineSpec{}, ctx, c)
670+
agentMachine.Status.AgentRef = &capiproviderv1.AgentReference{Namespace: testNamespace, Name: "agent"}
671+
Expect(c.Create(ctx, agentMachine)).To(Succeed())
672+
673+
requests := amr.mapAgentToAgentMachine(ctx, agent)
674+
Expect(len(requests)).To(Equal(1))
675+
676+
agentMachineKey := types.NamespacedName{
677+
Name: "agentMachine-1",
678+
Namespace: testNamespace,
679+
}
680+
Expect(requests[0].NamespacedName).To(Equal(agentMachineKey))
681+
})
682+
683+
It("returns nothing if the agent isn't valid", func() {
684+
agent := newAgent("agent", testNamespace, aiv1beta1.AgentSpec{Approved: true})
685+
agent.Status.Conditions = append(agent.Status.Conditions, v1.Condition{Type: aiv1beta1.BoundCondition, Status: boolToConditionStatus(false)})
686+
agent.Status.Conditions = append(agent.Status.Conditions, v1.Condition{Type: aiv1beta1.ValidatedCondition, Status: boolToConditionStatus(false)})
687+
Expect(c.Create(ctx, agent)).To(BeNil())
688+
agentMachine1, _ := newAgentMachine("agentMachine-1", testNamespace, capiproviderv1.AgentMachineSpec{}, ctx, c)
689+
Expect(c.Create(ctx, agentMachine1)).To(Succeed())
690+
agentMachine2, _ := newAgentMachine("agentMachine-2", testNamespace, capiproviderv1.AgentMachineSpec{}, ctx, c)
691+
Expect(c.Create(ctx, agentMachine2)).To(Succeed())
692+
693+
requests := amr.mapAgentToAgentMachine(ctx, agent)
694+
Expect(len(requests)).To(Equal(0))
695+
})
696+
697+
It("returns unmatched agent machines if no match is found", func() {
698+
agent := newAgent("agent", testNamespace, aiv1beta1.AgentSpec{Approved: true})
699+
agent.Status.Conditions = append(agent.Status.Conditions, v1.Condition{Type: aiv1beta1.BoundCondition, Status: boolToConditionStatus(false)})
700+
agent.Status.Conditions = append(agent.Status.Conditions, v1.Condition{Type: aiv1beta1.ValidatedCondition, Status: boolToConditionStatus(true)})
701+
Expect(c.Create(ctx, agent)).To(BeNil())
702+
agentMachine1, _ := newAgentMachine("agentMachine-1", testNamespace, capiproviderv1.AgentMachineSpec{}, ctx, c)
703+
Expect(c.Create(ctx, agentMachine1)).To(Succeed())
704+
agentMachine2, _ := newAgentMachine("agentMachine-2", testNamespace, capiproviderv1.AgentMachineSpec{}, ctx, c)
705+
Expect(c.Create(ctx, agentMachine2)).To(Succeed())
706+
707+
requests := amr.mapAgentToAgentMachine(ctx, agent)
708+
Expect(len(requests)).To(Equal(2))
709+
})
710+
})

0 commit comments

Comments
 (0)