Skip to content

Commit 53d2e64

Browse files
Support Claw CR idling (#745)
1 parent 211f8b4 commit 53d2e64

5 files changed

Lines changed: 97 additions & 3 deletions

File tree

controllers/idler/idler_controller.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ type Reconciler struct {
8383
// There are other AAP resource kinds which are involved in the Pod -> ... -> AnsibleAutomationPlatform ownership chain. We need to be able to get/list them.
8484
//+kubebuilder:rbac:groups=aap.ansible.com,resources=*,verbs=get;list
8585

86+
//+kubebuilder:rbac:groups=claw.sandbox.redhat.com,resources=claws,verbs=get;list;patch
87+
8688
// Reconcile reads that state of the cluster for an Idler object and makes changes based on the state read
8789
// and what is in the Idler.Spec
8890
// Note:

controllers/idler/idler_controller_test.go

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,10 @@ func TestEnsureIdling(t *testing.T) {
232232
AAPRunning(noise.aap).
233233
InferenceServiceDoesNotExist(podsRunningForTooLong.inferenceService).
234234
InferenceServiceExists(podsTooEarlyToKill.inferenceService).
235-
InferenceServiceExists(noise.inferenceService)
235+
InferenceServiceExists(noise.inferenceService).
236+
ClawIdled(podsRunningForTooLong.claw).
237+
ClawRunning(podsTooEarlyToKill.claw).
238+
ClawRunning(noise.claw)
236239

237240
memberoperatortest.AssertThatIdler(t, idler.Name, fakeClients).
238241
HasConditions(memberoperatortest.Running(), memberoperatortest.IdlerNotificationCreated())
@@ -333,7 +336,8 @@ func TestEnsureIdling(t *testing.T) {
333336
StatefulSetScaledDown(toKill.statefulSet).
334337
VMStopped(toKill.vmStopCallCounter).
335338
AAPIdled(toKill.aap).
336-
InferenceServiceDoesNotExist(toKill.inferenceService)
339+
InferenceServiceDoesNotExist(toKill.inferenceService).
340+
ClawIdled(toKill.claw)
337341

338342
memberoperatortest.AssertThatIdler(t, idler.Name, fakeClients).
339343
ContainsCondition(memberoperatortest.FailedToIdle(strings.Split(err.Error(), ": ")[1]))
@@ -550,7 +554,8 @@ func TestEnsureIdlingFailed(t *testing.T) {
550554
StatefulSetScaledDown(toKill.statefulSet).
551555
VMStopped(toKill.vmStopCallCounter).
552556
AAPIdled(toKill.aap).
553-
InferenceServiceDoesNotExist(toKill.inferenceService)
557+
InferenceServiceDoesNotExist(toKill.inferenceService).
558+
ClawIdled(toKill.claw)
554559
})
555560
}
556561

@@ -884,6 +889,7 @@ type payloads struct {
884889
aap *unstructured.Unstructured
885890
servingRuntime *unstructured.Unstructured
886891
inferenceService *unstructured.Unstructured
892+
claw *unstructured.Unstructured
887893
}
888894

889895
func (p payloads) getFirstControlledPod(ownerName string) *corev1.Pod {
@@ -1089,6 +1095,13 @@ func preparePayloads(t *testing.T, clients *memberoperatortest.FakeClientSet, na
10891095
inferenceService.SetCreationTimestamp(*sTime)
10901096
createObjectWithDynamicClient(t, clients.DynamicClient, inferenceService)
10911097

1098+
// Claw
1099+
clawObject := newClaw(fmt.Sprintf("%s%s-claw", namePrefix, namespace), namespace)
1100+
createObjectWithDynamicClient(t, clients.DynamicClient, clawObject)
1101+
_, clawRs := createDeployment(t, clients, namespace, namePrefix, "-claw-deployment", clawObject)
1102+
replicaSetsWithDeployment = append(replicaSetsWithDeployment, clawRs)
1103+
controlledPods = createPods(t, clients.AllNamespacesClient, clawRs, sTime, controlledPods, noRestart())
1104+
10921105
// Pods with unknown owner. They are subject of direct management by the Idler.
10931106
// It doesn't have to be Idler. We just need any object as the owner of the pods
10941107
// which is not a supported owner such as Deployment or ReplicaSet.
@@ -1144,6 +1157,7 @@ func preparePayloads(t *testing.T, clients *memberoperatortest.FakeClientSet, na
11441157
aap: aapObject,
11451158
servingRuntime: servingRuntimeObject,
11461159
inferenceService: inferenceService,
1160+
claw: clawObject,
11471161
}
11481162
}
11491163

@@ -1154,6 +1168,16 @@ func newAAP(t *testing.T, idled bool, name, namespace string) *unstructured.Unst
11541168
return aap
11551169
}
11561170

1171+
func newClaw(name, namespace string) *unstructured.Unstructured {
1172+
claw := &unstructured.Unstructured{}
1173+
claw.SetAPIVersion("claw.sandbox.redhat.com/v1alpha1")
1174+
claw.SetKind("Claw")
1175+
claw.SetName(name)
1176+
claw.SetNamespace(namespace)
1177+
unstructured.SetNestedField(claw.Object, false, "spec", "idle") //nolint:errcheck
1178+
return claw
1179+
}
1180+
11571181
func newServingRuntime(name, namespace string) *unstructured.Unstructured {
11581182
servingRuntime := &unstructured.Unstructured{}
11591183
servingRuntime.SetAPIVersion("serving.kserve.io/v1alpha1")

controllers/idler/owners.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ func (i *ownerIdler) scaleOwnerToZero(ctx context.Context, pod *corev1.Pod) (str
8080
err = i.stopVirtualMachine(ctx, ownerWithGVR) // Nothing to scale down. Stop instead.
8181
case "AnsibleAutomationPlatform":
8282
err = i.idleAAP(ctx, ownerWithGVR) // Nothing to scale down. Stop instead.
83+
case "Claw":
84+
err = i.idleClaw(ctx, ownerWithGVR)
8385
case "ServingRuntime":
8486
err = i.idleServingRuntime(ctx, ownerWithGVR) // Idle by deleting old InferenceService objects.
8587
default:
@@ -178,6 +180,33 @@ func (i *ownerIdler) idleAAP(ctx context.Context, objectWithGVR *owners.ObjectWi
178180
return nil
179181
}
180182

183+
// idleClaw idles a Claw instance if not already idled
184+
func (i *ownerIdler) idleClaw(ctx context.Context, objectWithGVR *owners.ObjectWithGVR) error {
185+
clawName := objectWithGVR.Object.GetName()
186+
logger := log.FromContext(ctx).WithValues("name", clawName)
187+
idled, _, err := unstructured.NestedBool(objectWithGVR.Object.UnstructuredContent(), "spec", "idle")
188+
if err != nil {
189+
logger.Error(err, "Failed to parse Claw CR to get the spec.idle field")
190+
}
191+
if idled {
192+
logger.Info("Claw CR is already idled")
193+
return nil
194+
}
195+
logger.Info("Idling Claw")
196+
197+
patch := []byte(`{"spec":{"idle":true}}`)
198+
_, err = i.dynamicClient.
199+
Resource(*objectWithGVR.GVR).
200+
Namespace(objectWithGVR.Object.GetNamespace()).
201+
Patch(ctx, clawName, types.MergePatchType, patch, metav1.PatchOptions{})
202+
if err != nil {
203+
return err
204+
}
205+
206+
logger.Info("Claw idled", "name", clawName)
207+
return nil
208+
}
209+
181210
func (i *ownerIdler) deleteResource(ctx context.Context, objectWithGVR *owners.ObjectWithGVR) error {
182211
logger := log.FromContext(ctx)
183212
object := objectWithGVR.Object

controllers/idler/owners_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,19 @@ var testConfigs = map[string]createTestConfigFunc{
225225
},
226226
}
227227
},
228+
"Claw": func(plds payloads) payloadTestConfig {
229+
return payloadTestConfig{
230+
// Claw -> Deployment -> ReplicaSet -> Pod
231+
podOwnerName: fmt.Sprintf("%s-deployment-replicaset", plds.claw.GetName()),
232+
expectedAppName: plds.claw.GetName(),
233+
ownerScaledUp: func(assertion *test.IdleablePayloadAssertion) {
234+
assertion.ClawRunning(plds.claw)
235+
},
236+
ownerScaledDown: func(assertion *test.IdleablePayloadAssertion) {
237+
assertion.ClawIdled(plds.claw)
238+
},
239+
}
240+
},
228241
}
229242

230243
var customListKinds = map[schema.GroupVersionResource]string{
@@ -596,5 +609,11 @@ func allResourcesList(t *testing.T) []*metav1.APIResourceList {
596609
{Name: "inferenceservices", Namespaced: true, Kind: "InferenceService"},
597610
},
598611
},
612+
&metav1.APIResourceList{
613+
GroupVersion: "claw.sandbox.redhat.com/v1alpha1",
614+
APIResources: []metav1.APIResource{
615+
{Name: "claws", Namespaced: true, Kind: "Claw"},
616+
},
617+
},
599618
)
600619
}

test/idler_assertion.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,26 @@ func (a *IdleablePayloadAssertion) AAPRunning(aap *unstructured.Unstructured) *I
378378
return a
379379
}
380380

381+
var clawGVR = schema.GroupVersionResource{Group: "claw.sandbox.redhat.com", Version: "v1alpha1", Resource: "claws"}
382+
383+
func (a *IdleablePayloadAssertion) ClawIdled(claw *unstructured.Unstructured) *IdleablePayloadAssertion {
384+
actualClaw := &unstructured.Unstructured{}
385+
a.getResourceFromDynamicClient(clawGVR, claw.GetNamespace(), claw.GetName(), actualClaw)
386+
idled, _, err := unstructured.NestedBool(actualClaw.UnstructuredContent(), "spec", "idle")
387+
require.NoError(a.t, err)
388+
assert.True(a.t, idled)
389+
return a
390+
}
391+
392+
func (a *IdleablePayloadAssertion) ClawRunning(claw *unstructured.Unstructured) *IdleablePayloadAssertion {
393+
actualClaw := &unstructured.Unstructured{}
394+
a.getResourceFromDynamicClient(clawGVR, claw.GetNamespace(), claw.GetName(), actualClaw)
395+
idled, _, err := unstructured.NestedBool(actualClaw.UnstructuredContent(), "spec", "idle")
396+
require.NoError(a.t, err)
397+
assert.False(a.t, idled)
398+
return a
399+
}
400+
381401
var inferenceServiceGVR = schema.GroupVersionResource{Group: "serving.kserve.io", Version: "v1beta1", Resource: "inferenceservices"}
382402

383403
func (a *IdleablePayloadAssertion) InferenceServiceDoesNotExist(inferenceService *unstructured.Unstructured) *IdleablePayloadAssertion {

0 commit comments

Comments
 (0)