@@ -165,6 +165,7 @@ objs=$(test/e2e/mkobjs.sh)
165165isc=$( echo $objs | awk ' {print $1}' )
166166lc=$( echo $objs | awk ' {print $2}' )
167167rslb=$( echo $objs | awk ' {print $3}' )
168+ isc2=$( echo $objs | awk ' {print $4}' )
168169instlb=${rslb# my-request-}
169170
170171# Expect requester pod to be created
@@ -240,10 +241,103 @@ kubectl wait --for condition=Ready pod/$launcherlb --timeout=5s
240241
241242cheer Successful instance wake-up fast path
242243
244+ : Multiple Instances Share One Launcher
245+
246+ # Scale requester to 0 again
247+ kubectl scale rs $rslb --replicas=0
248+
249+ expect " kubectl get pods -o name -l app=dp-example,instance=$instlb | grep -c '^pod/' || true | grep -w 0"
250+ ! kubectl get pod $reqlb2
251+
252+ # Launcher should remain
253+ kubectl get pod $launcherlb
254+
255+ # Verify launcher is unbound
256+ expect ' [ "$(kubectl get pod $launcherlb -o jsonpath={.metadata.labels.dual-pods\\.llm-d\\.ai/dual})" == "" ]'
257+
258+ # Patch ReplicaSet to use isc2 instead of isc
259+ kubectl patch rs $rslb --type=json -p=' [{"op": "replace", "path": "/spec/template/metadata/annotations/dual-pods.llm-d.ai~1inference-server-config", "value": "' $isc2 ' "}]'
260+
261+ sleep 5
262+
263+ # Scale back up (should reuse same launcher and create 2nd instance)
264+ kubectl scale rs $rslb --replicas=1
265+
266+ expect " kubectl get pods -o name -l app=dp-example,instance=$instlb | grep -c '^pod/' | grep -w 1"
267+
268+ reqlb3=$( kubectl get pods -o name -l app=dp-example,instance=$instlb | sed s%pod/%%)
269+
270+ # Should still be using the same launcher pod
271+ launcherlb3=$( kubectl get pods -o name -l dual-pods.llm-d.ai/launcher-config-name=$lc | sed s%pod/%%)
272+ [ " $launcherlb3 " == " $launcherlb " ]
273+
274+ # Verify new requester is bound to same launcher
275+ expect ' [ "$(kubectl get pod $reqlb3 -o jsonpath={.metadata.labels.dual-pods\\.llm-d\\.ai/dual})" == "$launcherlb" ]'
276+
277+ # Verify launcher is bound to new requester
278+ expect ' [ "$(kubectl get pod $launcherlb -o jsonpath={.metadata.labels.dual-pods\\.llm-d\\.ai/dual})" == "$reqlb3" ]'
279+
280+ # Verify the new requester is using isc2
281+ expect ' [ "$(kubectl get pod $reqlb3 -o jsonpath={.metadata.annotations.dual-pods\\.llm-d\\.ai/inference-server-config})" == "' $isc2 ' " ]'
282+
283+ # Wait for requester to be ready (launcher should already be ready)
284+ date
285+ kubectl wait --for condition=Ready pod/$reqlb3 --timeout=30s
286+ kubectl wait --for condition=Ready pod/$launcherlb --timeout=5s
287+
288+ cheer Successful multiple instances sharing one launcher
289+
290+ : Switch Instances In One Launcher
291+
292+ # Scale requester to 0 again
293+ kubectl scale rs $rslb --replicas=0
294+
295+ expect " kubectl get pods -o name -l app=dp-example,instance=$instlb | grep -c '^pod/' || true | grep -w 0"
296+ ! kubectl get pod $reqlb3
297+
298+ # Launcher should remain
299+ kubectl get pod $launcherlb
300+
301+ # Verify launcher is unbound
302+ expect ' [ "$(kubectl get pod $launcherlb -o jsonpath={.metadata.labels.dual-pods\\.llm-d\\.ai/dual})" == "" ]'
303+
304+ # Patch ReplicaSet back to use original isc
305+ kubectl patch rs $rslb --type=json -p=' [{"op": "replace", "path": "/spec/template/metadata/annotations/dual-pods.llm-d.ai~1inference-server-config", "value": "' $isc ' "}]'
306+
307+ sleep 5
308+
309+ # Scale back up (should reuse same launcher and wake first instance)
310+ kubectl scale rs $rslb --replicas=1
311+
312+ expect " kubectl get pods -o name -l app=dp-example,instance=$instlb | grep -c '^pod/' | grep -w 1"
313+
314+ reqlb4=$( kubectl get pods -o name -l app=dp-example,instance=$instlb | sed s%pod/%%)
315+
316+ # Should still be using the same launcher pod
317+ launcherlb4=$( kubectl get pods -o name -l dual-pods.llm-d.ai/launcher-config-name=$lc | sed s%pod/%%)
318+ [ " $launcherlb4 " == " $launcherlb " ]
319+
320+ # Verify new requester is bound to same launcher
321+ expect ' [ "$(kubectl get pod $reqlb4 -o jsonpath={.metadata.labels.dual-pods\\.llm-d\\.ai/dual})" == "$launcherlb" ]'
322+
323+ # Verify launcher is bound to new requester
324+ expect ' [ "$(kubectl get pod $launcherlb -o jsonpath={.metadata.labels.dual-pods\\.llm-d\\.ai/dual})" == "$reqlb4" ]'
325+
326+ # Verify the new requester is using original isc
327+ expect ' [ "$(kubectl get pod $reqlb4 -o jsonpath={.metadata.annotations.dual-pods\\.llm-d\\.ai/inference-server-config})" == "' $isc ' " ]'
328+
329+ # Wait for requester to be ready (launcher should already be ready)
330+ date
331+ kubectl wait --for condition=Ready pod/$reqlb4 --timeout=30s
332+ kubectl wait --for condition=Ready pod/$launcherlb --timeout=5s
333+
334+ cheer Successful switching instances in one launcher
335+
243336: Clean up launcher-based workloads
244337
245338kubectl delete rs $rslb --ignore-not-found=true
246339kubectl delete inferenceserverconfig $isc --ignore-not-found=true
340+ kubectl delete inferenceserverconfig $isc2 --ignore-not-found=true
247341kubectl delete launcherconfig $lc --ignore-not-found=true
248342expect ' [ $(kubectl get pods -o name | grep -c "^pod/my-request-") == "0" ]'
249343
0 commit comments