Skip to content

Commit 62e75af

Browse files
committed
fix: avoid cross-patching worker pool agents
1 parent 29313fa commit 62e75af

6 files changed

Lines changed: 102 additions & 15 deletions

File tree

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# To re-generate a bundle for another specific version without changing the standard setup, you can:
44
# - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.3)
55
# - use environment variables to overwrite this value (e.g export VERSION=0.0.3)
6-
VERSION ?= 0.0.22
6+
VERSION ?= 0.0.23
77

88
# CHANNELS define the bundle channels used in the bundle.
99
# Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable")

config/default/kustomization.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ resources:
3636
images:
3737
- name: controller
3838
newName: ghcr.io/containeroo/agent-forge-operator
39-
newTag: v0.0.22
39+
newTag: v0.0.23
4040

4141
# Uncomment the patches line if you enable Metrics
4242
patches:

internal/controller/planner.go

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ type AgentInfo struct {
7171
Approved bool
7272
SpecRole string
7373
RoleLabel string
74+
PoolLabel string
7475
Hostname string
7576
InventoryHostname string
7677
MAC string
@@ -138,7 +139,10 @@ func buildPlan(pool *agentforgev1alpha1.VsphereAgentPool, snapshot PoolSnapshot)
138139
if agent.Bound {
139140
continue
140141
}
141-
if agent.Approved && agent.SpecRole == pool.Spec.Agent.Role && agent.RoleLabel == pool.Spec.Agent.Role && agent.Hostname != "" {
142+
if !agentNeedsPatch(pool, agent) {
143+
continue
144+
}
145+
if !agentPatchEligible(pool, snapshot, agent) {
142146
continue
143147
}
144148
agentsToPatch = append(agentsToPatch, agent)
@@ -215,6 +219,39 @@ func buildPlan(pool *agentforgev1alpha1.VsphereAgentPool, snapshot PoolSnapshot)
215219
}
216220
}
217221

222+
func agentNeedsPatch(pool *agentforgev1alpha1.VsphereAgentPool, agent AgentInfo) bool {
223+
if desiredPoolLabel, hasPoolLabel := pool.Spec.Agent.Labels[poolLabelKey]; hasPoolLabel && agent.PoolLabel != desiredPoolLabel {
224+
return true
225+
}
226+
return !agent.Approved || agent.SpecRole != pool.Spec.Agent.Role || agent.RoleLabel != pool.Spec.Agent.Role || agent.Hostname == ""
227+
}
228+
229+
func agentPatchEligible(pool *agentforgev1alpha1.VsphereAgentPool, snapshot PoolSnapshot, agent AgentInfo) bool {
230+
if agentAssociatedWithOwnedVM(snapshot.OwnedVMs, agent) {
231+
return true
232+
}
233+
if desiredPoolLabel, hasPoolLabel := pool.Spec.Agent.Labels[poolLabelKey]; hasPoolLabel {
234+
return agent.PoolLabel == desiredPoolLabel
235+
}
236+
return snapshot.WaitingAgentMachines > 0 || snapshot.AgentMachinesWithoutAgent > 0
237+
}
238+
239+
func agentAssociatedWithOwnedVM(vms []agentforgev1alpha1.OwnedVMStatus, agent AgentInfo) bool {
240+
hostname := agentObservedHostname(agent)
241+
for _, vm := range vms {
242+
if vm.Name == "" {
243+
continue
244+
}
245+
if vmMatchesAgentIdentity(vm, agent) || vmMatchesAgentRef(vm, agent) {
246+
return true
247+
}
248+
if hostname != "" && vm.Name == hostname && !vmIdentityConflictsAgent(vm, agent) {
249+
return true
250+
}
251+
}
252+
return false
253+
}
254+
218255
func countPendingOwnedVMs(vms []agentforgev1alpha1.OwnedVMStatus) int32 {
219256
var count int32
220257
for _, vm := range vms {

internal/controller/planner_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,42 @@ func TestBuildPlanPatchesUnapprovedAgents(t *testing.T) {
119119
}
120120
}
121121

122+
func TestBuildPlanDoesNotPatchUnownedAgentsWithoutDemand(t *testing.T) {
123+
pool := testPool()
124+
125+
plan := buildPlan(pool, PoolSnapshot{
126+
WaitingAgentMachines: 0,
127+
MatchingAgents: []AgentInfo{
128+
{Name: "agent-1", Bound: false, Approved: false, RoleLabel: ""},
129+
},
130+
})
131+
132+
if len(plan.AgentsToPatch) != 0 {
133+
t.Fatalf("AgentsToPatch = %#v, want none for unowned Agent without demand", plan.AgentsToPatch)
134+
}
135+
if len(plan.Actions) != 1 || plan.Actions[0].Type != actionNoop {
136+
t.Fatalf("actions = %#v, want one Noop", plan.Actions)
137+
}
138+
}
139+
140+
func TestBuildPlanPatchesAgentAssociatedWithOwnedVMWithoutDemand(t *testing.T) {
141+
pool := testPool()
142+
143+
plan := buildPlan(pool, PoolSnapshot{
144+
WaitingAgentMachines: 0,
145+
MatchingAgents: []AgentInfo{
146+
{Name: "agent-1", Bound: false, Approved: false, RoleLabel: "", MAC: "00-50-56-b2-a1-9f"},
147+
},
148+
OwnedVMs: []agentforgev1alpha1.OwnedVMStatus{
149+
{Name: "owned-vm", Phase: phaseProvisioning, MACAddress: "00-50-56-b2-a1-9f"},
150+
},
151+
})
152+
153+
if len(plan.AgentsToPatch) != 1 || plan.AgentsToPatch[0].Name != "agent-1" {
154+
t.Fatalf("AgentsToPatch = %#v, want owned Agent patched", plan.AgentsToPatch)
155+
}
156+
}
157+
122158
func TestBuildPlanDeletesOrphanedOwnedVMsWithoutExcessAgents(t *testing.T) {
123159
pool := testPool()
124160

internal/controller/vsphereagentpool_controller.go

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -760,6 +760,7 @@ func (r *VsphereAgentPoolReconciler) listMatchingAgents(ctx context.Context, poo
760760
Approved: approved,
761761
SpecRole: specRole,
762762
RoleLabel: labels[roleLabelKey],
763+
PoolLabel: labels[poolLabelKey],
763764
Hostname: specHostname,
764765
InventoryHostname: inventoryHostname,
765766
MAC: normalizeMAC(agentPrimaryMAC(obj)),
@@ -834,17 +835,6 @@ func assignedAgentHostnames(pool *agentforgev1alpha1.VsphereAgentPool, agents []
834835
assigned := map[string]string{}
835836
reserved := map[string]struct{}{}
836837
for _, agent := range agents {
837-
if agent.Hostname == "" {
838-
continue
839-
}
840-
assigned[agent.Name] = agent.Hostname
841-
reserved[agent.Hostname] = struct{}{}
842-
}
843-
844-
for _, agent := range agents {
845-
if assigned[agent.Name] != "" {
846-
continue
847-
}
848838
for _, vm := range pool.Status.OwnedVMs {
849839
if vm.Name == "" || vm.Phase == phaseBound {
850840
continue
@@ -859,6 +849,17 @@ func assignedAgentHostnames(pool *agentforgev1alpha1.VsphereAgentPool, agents []
859849
reserved[vm.Name] = struct{}{}
860850
break
861851
}
852+
}
853+
854+
for _, agent := range agents {
855+
if assigned[agent.Name] != "" || agent.Hostname == "" {
856+
continue
857+
}
858+
assigned[agent.Name] = agent.Hostname
859+
reserved[agent.Hostname] = struct{}{}
860+
}
861+
862+
for _, agent := range agents {
862863
if assigned[agent.Name] != "" {
863864
continue
864865
}

internal/controller/vsphereagentpool_controller_test.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1450,11 +1450,16 @@ func TestReconcilePatchesCandidateAgentWithPoolDiscriminatorLabel(t *testing.T)
14501450
pool := reconcileTestPool()
14511451
pool.Spec.Agent.Labels[poolLabelKey] = "worker-32c128g"
14521452
pool.Status.OwnedVMs = []agentforgev1alpha1.OwnedVMStatus{
1453-
newOwnedVMStatus("demo-worker-32c128g-ab12"),
1453+
{
1454+
Name: "demo-worker-32c128g-ab12",
1455+
Phase: phaseProvisioning,
1456+
MACAddress: "00-50-56-b2-a1-9f",
1457+
},
14541458
}
14551459
am := testAgentMachine(testControlPlaneNamespace, testNodePool, "demo/demo-worker")
14561460
infraEnv := testInfraEnv(testNamespace, testInfraEnvName, "https://example.invalid/discovery.iso")
14571461
agent := testCandidateAgent(testNamespace, "abcdef12-3456-7890-abcd-ef1234567890")
1462+
setAgentPrimaryMAC(t, agent, "00:50:56:b2:a1:9f")
14581463

14591464
k8sClient := fake.NewClientBuilder().
14601465
WithScheme(scheme).
@@ -1726,6 +1731,7 @@ func TestAssignedAgentHostnamesUsesVMIdentityBeforeFreeSlot(t *testing.T) {
17261731

17271732
hostnames := assignedAgentHostnames(pool, []AgentInfo{{
17281733
Name: "agent-1",
1734+
Hostname: "demo-worker-stale",
17291735
BIOSUUID: "22222222-2222-2222-2222-222222222222",
17301736
}})
17311737

@@ -2475,6 +2481,13 @@ func setAgentInventoryHostname(t *testing.T, agent *unstructured.Unstructured, h
24752481
}
24762482
}
24772483

2484+
func setAgentPrimaryMAC(t *testing.T, agent *unstructured.Unstructured, mac string) {
2485+
t.Helper()
2486+
if err := unstructured.SetNestedSlice(agent.Object, []any{map[string]any{"macAddress": mac}}, "status", "inventory", "interfaces"); err != nil {
2487+
t.Fatal(err)
2488+
}
2489+
}
2490+
24782491
func findCondition(conditions []metav1.Condition, conditionType string) *metav1.Condition {
24792492
for i := range conditions {
24802493
if conditions[i].Type == conditionType {

0 commit comments

Comments
 (0)