@@ -156,6 +156,7 @@ objs=$(test/e2e/mkobjs.sh)
156156isc=$( echo $objs | awk ' {print $1}' )
157157lc=$( echo $objs | awk ' {print $2}' )
158158rslb=$( echo $objs | awk ' {print $3}' )
159+ isc2=$( echo $objs | awk ' {print $4}' )
159160instlb=${rslb# my-request-}
160161
161162# Expect requester pod to be created
@@ -221,10 +222,103 @@ kubectl wait --for condition=Ready pod/$launcherlb --timeout=5s
221222
222223cheer Successful instance wake-up fast path
223224
225+ : Multiple Instances Share One Launcher
226+
227+ # Scale requester to 0 again
228+ kubectl scale rs $rslb --replicas=0
229+
230+ expect " kubectl get pods -o name -l app=dp-example,instance=$instlb | grep -c '^pod/' || true | grep -w 0"
231+ ! kubectl get pod $reqlb2
232+
233+ # Launcher should remain
234+ kubectl get pod $launcherlb
235+
236+ # Verify launcher is unbound
237+ expect ' [ "$(kubectl get pod $launcherlb -o jsonpath={.metadata.labels.dual-pods\\.llm-d\\.ai/dual})" == "" ]'
238+
239+ # Patch ReplicaSet to use isc2 instead of isc
240+ kubectl patch rs $rslb --type=json -p=' [{"op": "replace", "path": "/spec/template/metadata/annotations/dual-pods.llm-d.ai~1inference-server-config", "value": "' $isc2 ' "}]'
241+
242+ sleep 5
243+
244+ # Scale back up (should reuse same launcher and create 2nd instance)
245+ kubectl scale rs $rslb --replicas=1
246+
247+ expect " kubectl get pods -o name -l app=dp-example,instance=$instlb | grep -c '^pod/' | grep -w 1"
248+
249+ reqlb3=$( kubectl get pods -o name -l app=dp-example,instance=$instlb | sed s%pod/%%)
250+
251+ # Should still be using the same launcher pod
252+ launcherlb3=$( kubectl get pods -o name -l dual-pods.llm-d.ai/launcher-config-name=$lc | sed s%pod/%%)
253+ [ " $launcherlb3 " == " $launcherlb " ]
254+
255+ # Verify new requester is bound to same launcher
256+ expect ' [ "$(kubectl get pod $reqlb3 -o jsonpath={.metadata.labels.dual-pods\\.llm-d\\.ai/dual})" == "$launcherlb" ]'
257+
258+ # Verify launcher is bound to new requester
259+ expect ' [ "$(kubectl get pod $launcherlb -o jsonpath={.metadata.labels.dual-pods\\.llm-d\\.ai/dual})" == "$reqlb3" ]'
260+
261+ # Verify the new requester is using isc2
262+ expect ' [ "$(kubectl get pod $reqlb3 -o jsonpath={.metadata.annotations.dual-pods\\.llm-d\\.ai/inference-server-config})" == "' $isc2 ' " ]'
263+
264+ # Wait for requester to be ready (launcher should already be ready)
265+ date
266+ kubectl wait --for condition=Ready pod/$reqlb3 --timeout=30s
267+ kubectl wait --for condition=Ready pod/$launcherlb --timeout=5s
268+
269+ cheer Successful multiple instances sharing one launcher
270+
271+ : Switch Instances In One Launcher
272+
273+ # Scale requester to 0 again
274+ kubectl scale rs $rslb --replicas=0
275+
276+ expect " kubectl get pods -o name -l app=dp-example,instance=$instlb | grep -c '^pod/' || true | grep -w 0"
277+ ! kubectl get pod $reqlb3
278+
279+ # Launcher should remain
280+ kubectl get pod $launcherlb
281+
282+ # Verify launcher is unbound
283+ expect ' [ "$(kubectl get pod $launcherlb -o jsonpath={.metadata.labels.dual-pods\\.llm-d\\.ai/dual})" == "" ]'
284+
285+ # Patch ReplicaSet back to use original isc
286+ kubectl patch rs $rslb --type=json -p=' [{"op": "replace", "path": "/spec/template/metadata/annotations/dual-pods.llm-d.ai~1inference-server-config", "value": "' $isc ' "}]'
287+
288+ sleep 5
289+
290+ # Scale back up (should reuse same launcher and wake first instance)
291+ kubectl scale rs $rslb --replicas=1
292+
293+ expect " kubectl get pods -o name -l app=dp-example,instance=$instlb | grep -c '^pod/' | grep -w 1"
294+
295+ reqlb4=$( kubectl get pods -o name -l app=dp-example,instance=$instlb | sed s%pod/%%)
296+
297+ # Should still be using the same launcher pod
298+ launcherlb4=$( kubectl get pods -o name -l dual-pods.llm-d.ai/launcher-config-name=$lc | sed s%pod/%%)
299+ [ " $launcherlb4 " == " $launcherlb " ]
300+
301+ # Verify new requester is bound to same launcher
302+ expect ' [ "$(kubectl get pod $reqlb4 -o jsonpath={.metadata.labels.dual-pods\\.llm-d\\.ai/dual})" == "$launcherlb" ]'
303+
304+ # Verify launcher is bound to new requester
305+ expect ' [ "$(kubectl get pod $launcherlb -o jsonpath={.metadata.labels.dual-pods\\.llm-d\\.ai/dual})" == "$reqlb4" ]'
306+
307+ # Verify the new requester is using original isc
308+ expect ' [ "$(kubectl get pod $reqlb4 -o jsonpath={.metadata.annotations.dual-pods\\.llm-d\\.ai/inference-server-config})" == "' $isc ' " ]'
309+
310+ # Wait for requester to be ready (launcher should already be ready)
311+ date
312+ kubectl wait --for condition=Ready pod/$reqlb4 --timeout=30s
313+ kubectl wait --for condition=Ready pod/$launcherlb --timeout=5s
314+
315+ cheer Successful switching instances in one launcher
316+
224317: Clean up launcher-based workloads
225318
226319kubectl delete rs $rslb --ignore-not-found=true
227320kubectl delete inferenceserverconfig $isc --ignore-not-found=true
321+ kubectl delete inferenceserverconfig $isc2 --ignore-not-found=true
228322kubectl delete launcherconfig $lc --ignore-not-found=true
229323expect ' [ $(kubectl get pods -o name | grep -c "^pod/my-request-") == "0" ]'
230324
0 commit comments