Skip to content

Commit abbde98

Browse files
Akrogclaude
andcommitted
MCP: Deploy MCP server as sidecar container
With this commit we start deploying the MCP server as a sidecar container of the lightspeed-service container. The MCP server deployed is the one from our new lightspeed-mcps repository [1]. On installation the `openshift-cli` tool is enabled when OpenStack Lightspeed is configured (therefore deployed), and the `openstack-cli` tool is enabled when the `OpenStackControlPlane` is ready. From a security perspective since we are deploying the MCP tools in the pod's network namespace, it's a sidecar container and the port is not exposed, not using TLS is not a real security risk. [1]: https://github.com/openstack-lightspeed/lightspeed-mcps Jira: OSPRH-27075 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d440464 commit abbde98

51 files changed

Lines changed: 1679 additions & 12 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

api/v1beta1/conditions.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ const (
2727
// OpenShift Lightspeed Operator Status=True condition which indicates if OpenShift Lightspeed is installed and
2828
// operational and it can be used by OpenStack Lightspeed operator.
2929
OpenShiftLightspeedOperatorReadyCondition condition.Type = "OpenShiftLightspeedOperatorReady"
30+
31+
// OpenStackLightspeedMCPServerReadyCondition is set to True when the MCP server
32+
// deployment succeeds. False indicates a failure during MCP server deployment.
33+
OpenStackLightspeedMCPServerReadyCondition condition.Type = "OpenStackLightspeedMCPServerReady"
3034
)
3135

3236
// Common Messages used by API objects.
@@ -46,6 +50,15 @@ const (
4650
// OpenShiftLightspeedOperatorReady
4751
OpenShiftLightspeedOperatorReady = "OpenShift Lightspeed operator is ready."
4852

53+
// OpenStackLightspeedMCPServerInitMessage
54+
OpenStackLightspeedMCPServerInitMessage = "MCP server deployment has not resolved"
55+
56+
// OpenStackLightspeedMCPServerDeployed
57+
OpenStackLightspeedMCPServerDeployed = "MCP server is ready"
58+
59+
// OpenStackLightspeedMCPServerWaitingOpenStack
60+
OpenStackLightspeedMCPServerWaitingOpenStack = "MCP server deployed, waiting for OpenStackControlPlane to become ready"
61+
4962
// DeploymentCheckFailedMessage
5063
DeploymentCheckFailedMessage = "Failed to check deployment status: %s"
5164

api/v1beta1/openstacklightspeed_types.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ const (
4646
// OKPContainerImage is the fall-back container image for OKP (Offline Knowledge Portal)
4747
OKPContainerImage = "registry.redhat.io/offline-knowledge-portal/rhokp-rhel9:latest"
4848

49+
// MCPServerContainerImage is the fall-back container image for the MCP server
50+
MCPServerContainerImage = "quay.io/openstack-lightspeed/lightspeed-mcps:latest"
51+
4952
// MaxTokensForResponseDefault is the default maximum number of tokens that should be used for response
5053
MaxTokensForResponseDefault = 2048
5154
)
@@ -205,6 +208,11 @@ type OpenStackLightspeedStatus struct {
205208

206209
// ObservedGeneration - the most recent generation observed for this object.
207210
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
211+
212+
// +optional
213+
// OpenStackReady indicates whether an OpenStackControlPlane was detected and
214+
// is ready. When true, the OpenStack MCP tools are included in lightspeed-stack config.
215+
OpenStackReady bool `json:"openStackReady,omitempty"`
208216
}
209217

210218
// +kubebuilder:object:root=true
@@ -229,6 +237,7 @@ type OpenStackLightspeedStatus struct {
229237
// +operator-sdk:csv:customresourcedefinitions:resources={{PersistentVolumeClaim,v1,openstack-lightspeed-database}}
230238
// +operator-sdk:csv:customresourcedefinitions:resources={{ClusterRole,v1,lightspeed-app-server-sar-role}}
231239
// +operator-sdk:csv:customresourcedefinitions:resources={{ClusterRoleBinding,v1,lightspeed-app-server-sar-role-binding}}
240+
// +operator-sdk:csv:customresourcedefinitions:resources={{ConfigMap,v1,mcp-config}}
232241
// +operator-sdk:csv:customresourcedefinitions:resources={{Subscription,v1alpha1}}
233242
// +operator-sdk:csv:customresourcedefinitions:resources={{ClusterServiceVersion,v1alpha1}}
234243
// +operator-sdk:csv:customresourcedefinitions:resources={{InstallPlan,v1alpha1}}
@@ -268,6 +277,7 @@ type OpenStackLightspeedDefaults struct {
268277
ConsoleImageURL string
269278
ConsoleImagePF5URL string
270279
OKPImageURL string
280+
MCPServerImageURL string
271281
MaxTokensForResponse int
272282
}
273283

@@ -291,6 +301,8 @@ func SetupDefaults() {
291301
"RELATED_IMAGE_CONSOLE_PF5_IMAGE_URL_DEFAULT", ConsoleContainerImagePF5),
292302
OKPImageURL: util.GetEnvVar(
293303
"RELATED_IMAGE_OKP_IMAGE_URL_DEFAULT", OKPContainerImage),
304+
MCPServerImageURL: util.GetEnvVar(
305+
"RELATED_IMAGE_MCP_SERVER_IMAGE_URL_DEFAULT", MCPServerContainerImage),
294306
MaxTokensForResponse: MaxTokensForResponseDefault,
295307
}
296308

bundle/manifests/lightspeed.openstack.org_openstacklightspeeds.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,11 @@ spec:
234234
for this object.
235235
format: int64
236236
type: integer
237+
openStackReady:
238+
description: |-
239+
OpenStackReady indicates whether an OpenStackControlPlane was detected and
240+
is ready. When true, the OpenStack MCP tools are included in lightspeed-stack config.
241+
type: boolean
237242
type: object
238243
type: object
239244
served: true

bundle/manifests/openstack-lightspeed-operator.clusterserviceversion.yaml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ spec:
110110
- kind: ConfigMap
111111
name: llama-stack-config
112112
version: v1
113+
- kind: ConfigMap
114+
name: mcp-config
115+
version: v1
113116
- kind: Secret
114117
name: metrics-reader-token
115118
version: v1
@@ -162,6 +165,13 @@ spec:
162165
spec:
163166
clusterPermissions:
164167
- rules:
168+
- apiGroups:
169+
- ""
170+
resources:
171+
- configmaps
172+
- secrets
173+
verbs:
174+
- get
165175
- apiGroups:
166176
- ""
167177
resourceNames:
@@ -170,6 +180,14 @@ spec:
170180
- secrets
171181
verbs:
172182
- get
183+
- apiGroups:
184+
- apiextensions.k8s.io
185+
resources:
186+
- customresourcedefinitions
187+
verbs:
188+
- get
189+
- list
190+
- watch
173191
- apiGroups:
174192
- config.openshift.io
175193
resources:
@@ -190,6 +208,14 @@ spec:
190208
- patch
191209
- update
192210
- watch
211+
- apiGroups:
212+
- core.openstack.org
213+
resources:
214+
- openstackcontrolplanes
215+
verbs:
216+
- get
217+
- list
218+
- watch
193219
- apiGroups:
194220
- lightspeed.openstack.org
195221
resources:
@@ -305,6 +331,8 @@ spec:
305331
value: registry.redhat.io/openshift-lightspeed/lightspeed-console-plugin-rhel9:1.0.12
306332
- name: RELATED_IMAGE_CONSOLE_PF5_IMAGE_URL_DEFAULT
307333
value: registry.redhat.io/openshift-lightspeed/lightspeed-console-plugin-pf5-rhel9:1.0.12
334+
- name: RELATED_IMAGE_MCP_SERVER_IMAGE_URL_DEFAULT
335+
value: quay.io/openstack-lightspeed/lightspeed-mcps:latest
308336
image: quay.io/openstack-lightspeed/operator:latest
309337
livenessProbe:
310338
httpGet:
@@ -500,4 +528,6 @@ spec:
500528
name: console-image-url-default
501529
- image: registry.redhat.io/openshift-lightspeed/lightspeed-console-plugin-pf5-rhel9:1.0.12
502530
name: console-pf5-image-url-default
531+
- image: quay.io/openstack-lightspeed/lightspeed-mcps:latest
532+
name: mcp-server-image-url-default
503533
version: 0.0.1

cmd/main.go

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,17 @@ import (
2222
"fmt"
2323
"os"
2424
"strings"
25+
"sync/atomic"
2526

2627
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
2728
// to ensure that exec-entrypoint and run can make use of them.
2829
_ "k8s.io/client-go/plugin/pkg/client/auth"
2930

31+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
3032
"k8s.io/apimachinery/pkg/runtime"
33+
"k8s.io/apimachinery/pkg/runtime/schema"
3134
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
35+
"k8s.io/client-go/kubernetes"
3236
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
3337
ctrl "sigs.k8s.io/controller-runtime"
3438
"sigs.k8s.io/controller-runtime/pkg/cache"
@@ -62,6 +66,8 @@ func init() {
6266
utilruntime.Must(consolev1.AddToScheme(scheme))
6367

6468
utilruntime.Must(openshiftv1.AddToScheme(scheme))
69+
70+
utilruntime.Must(apiextensionsv1.AddToScheme(scheme))
6571
// +kubebuilder:scaffold:scheme
6672
}
6773

@@ -154,7 +160,15 @@ func main() {
154160
setupLog.Info(fmt.Sprintf("openstack-lightspeed operator watches %s namespace", namespace))
155161
}
156162

157-
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
163+
cfg := ctrl.GetConfigOrDie()
164+
165+
kclient, err := kubernetes.NewForConfig(cfg)
166+
if err != nil {
167+
setupLog.Error(err, "unable to create kubernetes client")
168+
os.Exit(1)
169+
}
170+
171+
mgr, err := ctrl.NewManager(cfg, ctrl.Options{
158172
Scheme: scheme,
159173
Metrics: metricsServerOptions,
160174
WebhookServer: webhookServer,
@@ -184,9 +198,14 @@ func main() {
184198
// Defaults for OpenStackLightspeed
185199
apiv1beta1.SetupDefaults()
186200

201+
dynamicWatchCRDs := getDynamicWatchCRDs()
202+
187203
if err = (&controller.OpenStackLightspeedReconciler{
188-
Client: mgr.GetClient(),
189-
Scheme: mgr.GetScheme(),
204+
Client: mgr.GetClient(),
205+
Kclient: kclient,
206+
Scheme: mgr.GetScheme(),
207+
Cache: mgr.GetCache(),
208+
DynamicWatchCRD: dynamicWatchCRDs,
190209
}).SetupWithManager(mgr); err != nil {
191210
setupLog.Error(err, "unable to create controller", "controller", "OpenStackLightspeed")
192211
os.Exit(1)
@@ -224,3 +243,21 @@ func getWatchNamespaces() ([]string, error) {
224243

225244
return strings.Split(ns, ","), nil
226245
}
246+
247+
// getDynamicWatchCRDs returns a map of GroupVersionKind to *atomic.Bool
248+
// representing resources that should be watched dynamically. The watch starts
249+
// once they appear in the cluster for the first time (not required at operator
250+
// start time).
251+
//
252+
// The OpenStackControlPlane GVK is hard-coded here to avoid pulling in the
253+
// openstack-operator/api dependency (which is pinned to an older k8s version).
254+
// The CRD is watched using unstructured types, so the Go type is not needed.
255+
func getDynamicWatchCRDs() map[schema.GroupVersionKind]*atomic.Bool {
256+
return map[schema.GroupVersionKind]*atomic.Bool{
257+
{
258+
Group: "core.openstack.org",
259+
Version: "v1beta1",
260+
Kind: "OpenStackControlPlane",
261+
}: new(atomic.Bool),
262+
}
263+
}

config/crd/bases/lightspeed.openstack.org_openstacklightspeeds.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,11 @@ spec:
234234
for this object.
235235
format: int64
236236
type: integer
237+
openStackReady:
238+
description: |-
239+
OpenStackReady indicates whether an OpenStackControlPlane was detected and
240+
is ready. When true, the OpenStack MCP tools are included in lightspeed-stack config.
241+
type: boolean
237242
type: object
238243
type: object
239244
served: true

config/manager/manager.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ spec:
8787
value: registry.redhat.io/openshift-lightspeed/lightspeed-console-plugin-rhel9:1.0.12
8888
- name: RELATED_IMAGE_CONSOLE_PF5_IMAGE_URL_DEFAULT
8989
value: registry.redhat.io/openshift-lightspeed/lightspeed-console-plugin-pf5-rhel9:1.0.12
90+
- name: RELATED_IMAGE_MCP_SERVER_IMAGE_URL_DEFAULT
91+
value: quay.io/openstack-lightspeed/lightspeed-mcps:latest
9092
securityContext:
9193
allowPrivilegeEscalation: false
9294
capabilities:

config/manifests/bases/openstack-lightspeed-operator.clusterserviceversion.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ spec:
8787
- kind: ConfigMap
8888
name: llama-stack-config
8989
version: v1
90+
- kind: ConfigMap
91+
name: mcp-config
92+
version: v1
9093
- kind: Secret
9194
name: metrics-reader-token
9295
version: v1

config/rbac/role.yaml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ kind: ClusterRole
44
metadata:
55
name: manager-role
66
rules:
7+
- apiGroups:
8+
- ""
9+
resources:
10+
- configmaps
11+
- secrets
12+
verbs:
13+
- get
714
- apiGroups:
815
- ""
916
resourceNames:
@@ -12,6 +19,14 @@ rules:
1219
- secrets
1320
verbs:
1421
- get
22+
- apiGroups:
23+
- apiextensions.k8s.io
24+
resources:
25+
- customresourcedefinitions
26+
verbs:
27+
- get
28+
- list
29+
- watch
1530
- apiGroups:
1631
- config.openshift.io
1732
resources:
@@ -32,6 +47,14 @@ rules:
3247
- patch
3348
- update
3449
- watch
50+
- apiGroups:
51+
- core.openstack.org
52+
resources:
53+
- openstackcontrolplanes
54+
verbs:
55+
- get
56+
- list
57+
- watch
3558
- apiGroups:
3659
- lightspeed.openstack.org
3760
resources:

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ require (
1313
github.com/openstack-k8s-operators/lib-common/modules/common v0.6.0
1414
github.com/operator-framework/api v0.37.0
1515
k8s.io/api v0.34.2
16+
k8s.io/apiextensions-apiserver v0.34.2
1617
k8s.io/apimachinery v0.34.3
1718
k8s.io/client-go v0.34.2
1819
sigs.k8s.io/controller-runtime v0.22.4
@@ -99,7 +100,6 @@ require (
99100
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
100101
gopkg.in/inf.v0 v0.9.1 // indirect
101102
gopkg.in/yaml.v3 v3.0.1 // indirect
102-
k8s.io/apiextensions-apiserver v0.34.2 // indirect
103103
k8s.io/apiserver v0.34.2 // indirect
104104
k8s.io/component-base v0.34.2 // indirect
105105
k8s.io/klog/v2 v2.130.1 // indirect

0 commit comments

Comments
 (0)