Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions cmd/thv-operator/controllers/mcpserver_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -447,23 +447,23 @@ func (r *MCPServerReconciler) validateGroupRef(ctx context.Context, mcpServer *m
Type: mcpv1alpha1.ConditionGroupRefValidated,
Status: metav1.ConditionFalse,
Reason: mcpv1alpha1.ConditionReasonGroupRefNotFound,
Message: err.Error(),
Message: fmt.Sprintf("MCPGroup '%s' not found in namespace '%s'", mcpServer.Spec.GroupRef, mcpServer.Namespace),
ObservedGeneration: mcpServer.Generation,
})
} else if group.Status.Phase != mcpv1alpha1.MCPGroupPhaseReady {
meta.SetStatusCondition(&mcpServer.Status.Conditions, metav1.Condition{
Type: mcpv1alpha1.ConditionGroupRefValidated,
Status: metav1.ConditionFalse,
Reason: mcpv1alpha1.ConditionReasonGroupRefNotReady,
Message: "GroupRef is not in Ready state",
Message: fmt.Sprintf("MCPGroup '%s' is not ready (current phase: %s)", mcpServer.Spec.GroupRef, group.Status.Phase),
ObservedGeneration: mcpServer.Generation,
})
} else {
meta.SetStatusCondition(&mcpServer.Status.Conditions, metav1.Condition{
Type: mcpv1alpha1.ConditionGroupRefValidated,
Status: metav1.ConditionTrue,
Reason: mcpv1alpha1.ConditionReasonGroupRefValidated,
Message: "GroupRef is valid and in Ready state",
Message: fmt.Sprintf("MCPGroup '%s' is valid and ready", mcpServer.Spec.GroupRef),
ObservedGeneration: mcpServer.Generation,
})
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/thv-operator/controllers/mcpserver_groupref_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func TestMCPServerReconciler_ValidateGroupRef(t *testing.T) {
},
expectedConditionStatus: metav1.ConditionFalse,
expectedConditionReason: mcpv1alpha1.ConditionReasonGroupRefNotReady,
expectedConditionMsg: "GroupRef is not in Ready state",
expectedConditionMsg: "MCPGroup 'test-group' is not ready (current phase: Pending)",
},
{
name: "GroupRef not validated when group is Failed",
Expand All @@ -122,7 +122,7 @@ func TestMCPServerReconciler_ValidateGroupRef(t *testing.T) {
},
expectedConditionStatus: metav1.ConditionFalse,
expectedConditionReason: mcpv1alpha1.ConditionReasonGroupRefNotReady,
expectedConditionMsg: "GroupRef is not in Ready state",
expectedConditionMsg: "MCPGroup 'test-group' is not ready (current phase: Failed)",
},
{
name: "No validation when GroupRef is empty",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ import (

var _ = Describe("MCPServer Controller Integration Tests", func() {
const (
timeout = time.Second * 30
interval = time.Millisecond * 250
timeout = time.Second * 30
interval = time.Millisecond * 250
defaultNamespace = "default"
conditionTypeGroupRefValidated = "GroupRefValidated"
)

Context("When creating an Stdio MCPServer", Ordered, func() {
Expand All @@ -36,7 +38,7 @@ var _ = Describe("MCPServer Controller Integration Tests", func() {
)

BeforeAll(func() {
namespace = "default"
namespace = defaultNamespace
mcpServerName = "test-mcpserver"

// Create namespace if it doesn't exist
Expand Down Expand Up @@ -363,7 +365,7 @@ var _ = Describe("MCPServer Controller Integration Tests", func() {
)

BeforeAll(func() {
namespace = "default"
namespace = defaultNamespace
mcpServerName = "test-invalid-podtemplate"

// Create namespace if it doesn't exist
Expand Down Expand Up @@ -470,6 +472,237 @@ var _ = Describe("MCPServer Controller Integration Tests", func() {
Expect(updatedMCPServer.Status.Message).To(ContainSubstring("Invalid PodTemplateSpec"))
})
})

Context("When creating an MCPServer with invalid GroupRef", Ordered, func() {
var (
namespace string
mcpServerName string
mcpServer *mcpv1alpha1.MCPServer
)

BeforeAll(func() {
namespace = defaultNamespace
mcpServerName = "test-invalid-groupref"

// Create namespace if it doesn't exist
ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: namespace,
},
}
_ = k8sClient.Create(ctx, ns)

// Define the MCPServer resource with invalid GroupRef
mcpServer = &mcpv1alpha1.MCPServer{
ObjectMeta: metav1.ObjectMeta{
Name: mcpServerName,
Namespace: namespace,
},
Spec: mcpv1alpha1.MCPServerSpec{
Image: "ghcr.io/stackloklabs/mcp-fetch:latest",
Transport: "stdio",
Port: 8080,
GroupRef: "non-existent-group", // This group doesn't exist
},
}

// Create the MCPServer
Expect(k8sClient.Create(ctx, mcpServer)).Should(Succeed())
})

AfterAll(func() {
// Clean up the MCPServer
Expect(k8sClient.Delete(ctx, mcpServer)).Should(Succeed())
})

It("Should set GroupRefValidated condition to False with reason GroupRefNotFound", func() {
// Wait for the status to be updated with the invalid condition
Eventually(func() bool {
updatedMCPServer := &mcpv1alpha1.MCPServer{}
err := k8sClient.Get(ctx, types.NamespacedName{
Name: mcpServerName,
Namespace: namespace,
}, updatedMCPServer)
if err != nil {
return false
}

// Check for GroupRefValidated condition
for _, cond := range updatedMCPServer.Status.Conditions {
if cond.Type == conditionTypeGroupRefValidated {
return cond.Status == metav1.ConditionFalse &&
cond.Reason == "GroupRefNotFound"
}
}
return false
}, timeout, interval).Should(BeTrue())

// Verify the condition message contains expected text
updatedMCPServer := &mcpv1alpha1.MCPServer{}
Expect(k8sClient.Get(ctx, types.NamespacedName{
Name: mcpServerName,
Namespace: namespace,
}, updatedMCPServer)).Should(Succeed())

var foundCondition *metav1.Condition
for i, cond := range updatedMCPServer.Status.Conditions {
if cond.Type == conditionTypeGroupRefValidated {
foundCondition = &updatedMCPServer.Status.Conditions[i]
break
}
}

Expect(foundCondition).NotTo(BeNil())
Expect(foundCondition.Message).To(Equal(fmt.Sprintf("MCPGroup 'non-existent-group' not found in namespace '%s'", defaultNamespace)))
})

It("Should not block creation of other resources despite invalid GroupRef", func() {
// Verify that deployment still gets created (GroupRef doesn't block deployment)
deployment := &appsv1.Deployment{}
Eventually(func() error {
return k8sClient.Get(ctx, types.NamespacedName{
Name: mcpServerName,
Namespace: namespace,
}, deployment)
}, timeout, interval).Should(Succeed())

// Verify the deployment was created successfully
Expect(deployment.Name).To(Equal(mcpServerName))
})
})

Context("When creating an MCPServer with valid GroupRef", Ordered, func() {
var (
namespace string
mcpServerName string
mcpGroupName string
mcpServer *mcpv1alpha1.MCPServer
mcpGroup *mcpv1alpha1.MCPGroup
)

BeforeAll(func() {
namespace = defaultNamespace
mcpServerName = "test-valid-groupref"
mcpGroupName = "test-group"

// Create namespace if it doesn't exist
ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: namespace,
},
}
_ = k8sClient.Create(ctx, ns)

// Create MCPGroup first
mcpGroup = &mcpv1alpha1.MCPGroup{
ObjectMeta: metav1.ObjectMeta{
Name: mcpGroupName,
Namespace: namespace,
},
Spec: mcpv1alpha1.MCPGroupSpec{
Description: "A test group for integration testing",
},
}
Expect(k8sClient.Create(ctx, mcpGroup)).Should(Succeed())

// Wait for the group to be created and ready
Eventually(func() bool {
updatedGroup := &mcpv1alpha1.MCPGroup{}
err := k8sClient.Get(ctx, types.NamespacedName{
Name: mcpGroupName,
Namespace: namespace,
}, updatedGroup)
return err == nil && updatedGroup.Status.Phase == mcpv1alpha1.MCPGroupPhaseReady
}, timeout, interval).Should(BeTrue())

// Define the MCPServer resource with valid GroupRef
mcpServer = &mcpv1alpha1.MCPServer{
ObjectMeta: metav1.ObjectMeta{
Name: mcpServerName,
Namespace: namespace,
},
Spec: mcpv1alpha1.MCPServerSpec{
Image: "ghcr.io/stackloklabs/mcp-fetch:latest",
Transport: "stdio",
Port: 8080,
GroupRef: mcpGroupName, // This group exists
},
}

// Create the MCPServer
Expect(k8sClient.Create(ctx, mcpServer)).Should(Succeed())
})

AfterAll(func() {
// Clean up the MCPServer first
Expect(k8sClient.Delete(ctx, mcpServer)).Should(Succeed())
// Then clean up the MCPGroup
Expect(k8sClient.Delete(ctx, mcpGroup)).Should(Succeed())
})

It("Should set GroupRefValidated condition to True with reason GroupRefIsValid", func() {
// Wait for the status to be updated with the valid condition
Eventually(func() bool {
updatedMCPServer := &mcpv1alpha1.MCPServer{}
err := k8sClient.Get(ctx, types.NamespacedName{
Name: mcpServerName,
Namespace: namespace,
}, updatedMCPServer)
if err != nil {
return false
}

// Check for GroupRefValidated condition
for _, cond := range updatedMCPServer.Status.Conditions {
if cond.Type == conditionTypeGroupRefValidated {
return cond.Status == metav1.ConditionTrue &&
cond.Reason == "GroupRefIsValid"
}
}
return false
}, timeout, interval).Should(BeTrue())

// Verify the condition message contains expected text
updatedMCPServer := &mcpv1alpha1.MCPServer{}
Expect(k8sClient.Get(ctx, types.NamespacedName{
Name: mcpServerName,
Namespace: namespace,
}, updatedMCPServer)).Should(Succeed())

var foundCondition *metav1.Condition
for i, cond := range updatedMCPServer.Status.Conditions {
if cond.Type == conditionTypeGroupRefValidated {
foundCondition = &updatedMCPServer.Status.Conditions[i]
break
}
}

Expect(foundCondition).NotTo(BeNil())
Expect(foundCondition.Message).To(Equal("MCPGroup 'test-group' is valid and ready"))
})

It("Should update MCPGroup with server reference", func() {
// Wait for the MCPGroup to be updated with the server reference
Eventually(func() bool {
updatedGroup := &mcpv1alpha1.MCPGroup{}
err := k8sClient.Get(ctx, types.NamespacedName{
Name: mcpGroupName,
Namespace: namespace,
}, updatedGroup)
if err != nil {
return false
}

// Check if the server is in the group's servers list
for _, server := range updatedGroup.Status.Servers {
if server == mcpServerName {
return true
}
}
return false
}, timeout, interval).Should(BeTrue())
})
})
})

func verifyOwnerReference(ownerRefs []metav1.OwnerReference, mcpServer *mcpv1alpha1.MCPServer, resourceType string) {
Expand Down
17 changes: 17 additions & 0 deletions cmd/thv-operator/test-integration/mcp-server/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,23 @@ var _ = BeforeSuite(func() {
})
Expect(err).ToNot(HaveOccurred())

// Set up field indexing for MCPServer.Spec.GroupRef
if err := k8sManager.GetFieldIndexer().IndexField(ctx, &mcpv1alpha1.MCPServer{}, "spec.groupRef", func(obj client.Object) []string {
mcpServer := obj.(*mcpv1alpha1.MCPServer)
if mcpServer.Spec.GroupRef == "" {
return nil
}
return []string{mcpServer.Spec.GroupRef}
}); err != nil {
Expect(err).ToNot(HaveOccurred())
}

// Register the MCPGroup controller
err = (&controllers.MCPGroupReconciler{
Client: k8sManager.GetClient(),
}).SetupWithManager(k8sManager)
Expect(err).ToNot(HaveOccurred())

// Register the MCPServer controller
err = (&controllers.MCPServerReconciler{
Client: k8sManager.GetClient(),
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Loading
Loading