@@ -3,6 +3,7 @@ package virtualmcp
33import (
44 "fmt"
55 "net/http"
6+ "slices"
67 "strings"
78 "time"
89
@@ -243,4 +244,137 @@ var _ = Describe("VirtualMCPServer Yardstick Base", Ordered, func() {
243244 Expect (vmcpServer .Status .Phase ).To (Equal (mcpv1alpha1 .VirtualMCPServerPhaseReady ))
244245 })
245246 })
247+
248+ Context ("when testing group membership changes trigger reconciliation" , func () {
249+ backend3Name := "yardstick-c"
250+ backend4Name := "yardstick-d"
251+
252+ It ("should have two discovered backends initially" , func () {
253+ status , err := GetVirtualMCPServerStatus (ctx , k8sClient , vmcpServerName , testNamespace )
254+ Expect (err ).ToNot (HaveOccurred ())
255+ Expect (status .BackendCount ).To (Equal (2 ), "Should have 2 initial backends" )
256+ Expect (status .DiscoveredBackends ).To (HaveLen (2 ), "Should have 2 discovered backends" )
257+
258+ backendNames := make ([]string , len (status .DiscoveredBackends ))
259+ for i , backend := range status .DiscoveredBackends {
260+ backendNames [i ] = backend .Name
261+ }
262+ Expect (backendNames ).To (ContainElements (backend1Name , backend2Name ))
263+
264+ By (fmt .Sprintf ("Initial backends: %v" , backendNames ))
265+ })
266+
267+ It ("should discover a new backend when added to the group" , func () {
268+ By ("Creating a new yardstick backend MCPServer and adding to the group" )
269+ CreateMCPServerAndWait (ctx , k8sClient , backend3Name , testNamespace ,
270+ mcpGroupName , images .YardstickServerImage , timeout , pollingInterval )
271+
272+ By ("Waiting for VirtualMCPServer to reconcile and discover the new backend" )
273+ Eventually (func () error {
274+ status , err := GetVirtualMCPServerStatus (ctx , k8sClient , vmcpServerName , testNamespace )
275+ if err != nil {
276+ return err
277+ }
278+
279+ if status .BackendCount != 3 {
280+ return fmt .Errorf ("expected 3 backends, got %d" , status .BackendCount )
281+ }
282+
283+ if len (status .DiscoveredBackends ) != 3 {
284+ return fmt .Errorf ("expected 3 discovered backends, got %d" , len (status .DiscoveredBackends ))
285+ }
286+
287+ backendNames := make ([]string , len (status .DiscoveredBackends ))
288+ for i , backend := range status .DiscoveredBackends {
289+ backendNames [i ] = backend .Name
290+ }
291+
292+ if ! slices .Contains (backendNames , backend3Name ) {
293+ return fmt .Errorf ("new backend %s not found in discovered backends: %v" , backend3Name , backendNames )
294+ }
295+
296+ return nil
297+ }, timeout , pollingInterval ).Should (Succeed (), "VirtualMCPServer should discover the new backend" )
298+
299+ })
300+
301+ It ("should remove a backend when deleted from the group" , func () {
302+ By ("Creating a dedicated backend MCPServer for deletion test" )
303+ CreateMCPServerAndWait (ctx , k8sClient , backend4Name , testNamespace ,
304+ mcpGroupName , images .YardstickServerImage , timeout , pollingInterval )
305+
306+ By ("Waiting for VirtualMCPServer to discover the new backend (should have 4 backends)" )
307+ Eventually (func () error {
308+ status , err := GetVirtualMCPServerStatus (ctx , k8sClient , vmcpServerName , testNamespace )
309+ if err != nil {
310+ return err
311+ }
312+ if status .BackendCount != 4 {
313+ return fmt .Errorf ("expected 4 backends before deletion, got %d" , status .BackendCount )
314+ }
315+ return nil
316+ }, timeout , pollingInterval ).Should (Succeed ())
317+
318+ By ("Deleting the dedicated backend MCPServer from the group" )
319+ backend4 := & mcpv1alpha1.MCPServer {
320+ ObjectMeta : metav1.ObjectMeta {
321+ Name : backend4Name ,
322+ Namespace : testNamespace ,
323+ },
324+ }
325+ Expect (k8sClient .Delete (ctx , backend4 )).To (Succeed ())
326+
327+ By ("Waiting for VirtualMCPServer to reconcile and remove the deleted backend" )
328+ Eventually (func () error {
329+ status , err := GetVirtualMCPServerStatus (ctx , k8sClient , vmcpServerName , testNamespace )
330+ if err != nil {
331+ return err
332+ }
333+
334+ if status .BackendCount != 3 {
335+ return fmt .Errorf ("expected 3 backends after removal, got %d" , status .BackendCount )
336+ }
337+
338+ if len (status .DiscoveredBackends ) != 3 {
339+ return fmt .Errorf ("expected 3 discovered backends after removal, got %d" , len (status .DiscoveredBackends ))
340+ }
341+
342+ backendNames := make ([]string , len (status .DiscoveredBackends ))
343+ for i , backend := range status .DiscoveredBackends {
344+ backendNames [i ] = backend .Name
345+ }
346+
347+ if slices .Contains (backendNames , backend4Name ) {
348+ return fmt .Errorf ("deleted backend %s still found in discovered backends: %v" , backend4Name , backendNames )
349+ }
350+
351+ return nil
352+ }, timeout , pollingInterval ).Should (Succeed (), "VirtualMCPServer should remove the deleted backend" )
353+
354+ })
355+
356+ It ("should remain ready throughout membership changes" , func () {
357+ vmcpServer := & mcpv1alpha1.VirtualMCPServer {}
358+ err := k8sClient .Get (ctx , types.NamespacedName {
359+ Name : vmcpServerName ,
360+ Namespace : testNamespace ,
361+ }, vmcpServer )
362+ Expect (err ).ToNot (HaveOccurred ())
363+ Expect (HasCondition (vmcpServer , "Ready" , "True" )).To (BeTrue (),
364+ "VirtualMCPServer should remain ready after membership changes" )
365+ })
366+
367+ AfterAll (func () {
368+ By ("Cleaning up additional backends from membership test" )
369+ for _ , backendName := range []string {backend3Name , backend4Name } {
370+ backend := & mcpv1alpha1.MCPServer {
371+ ObjectMeta : metav1.ObjectMeta {
372+ Name : backendName ,
373+ Namespace : testNamespace ,
374+ },
375+ }
376+ _ = k8sClient .Delete (ctx , backend )
377+ }
378+ })
379+ })
246380})
0 commit comments