Skip to content

Commit 344b53c

Browse files
Akrogclaude
andcommitted
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 own rhos-mcps repository [1]. On installation the `openshift-cli` tool is enabled when OpenStack Lightspeed is configured, 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 not using TLS is not a real security risk. [1]: https://github.com/openstack-lightspeed/rhos-mcps Jira: OSPRH-27075 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3e72e18 commit 344b53c

51 files changed

Lines changed: 1615 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/rhos-mcps:latest"
51+
4952
// MaxTokensForResponseDefault is the default maximum number of tokens that should be used for response
5053
MaxTokensForResponseDefault = 2048
5154
)
@@ -202,6 +205,11 @@ type OpenStackLightspeedStatus struct {
202205

203206
// ObservedGeneration - the most recent generation observed for this object.
204207
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
208+
209+
// +optional
210+
// OpenStackReady indicates whether an OpenStackControlPlane was detected and
211+
// is ready. When true, the OpenStack MCP tools are included in lightspeed-stack config.
212+
OpenStackReady bool `json:"openStackReady,omitempty"`
205213
}
206214

207215
// +kubebuilder:object:root=true
@@ -226,6 +234,7 @@ type OpenStackLightspeedStatus struct {
226234
// +operator-sdk:csv:customresourcedefinitions:resources={{PersistentVolumeClaim,v1,openstack-lightspeed-database}}
227235
// +operator-sdk:csv:customresourcedefinitions:resources={{ClusterRole,v1,lightspeed-app-server-sar-role}}
228236
// +operator-sdk:csv:customresourcedefinitions:resources={{ClusterRoleBinding,v1,lightspeed-app-server-sar-role-binding}}
237+
// +operator-sdk:csv:customresourcedefinitions:resources={{ConfigMap,v1,mcp-config}}
229238
// +operator-sdk:csv:customresourcedefinitions:resources={{Subscription,v1alpha1}}
230239
// +operator-sdk:csv:customresourcedefinitions:resources={{ClusterServiceVersion,v1alpha1}}
231240
// +operator-sdk:csv:customresourcedefinitions:resources={{InstallPlan,v1alpha1}}
@@ -265,6 +274,7 @@ type OpenStackLightspeedDefaults struct {
265274
ConsoleImageURL string
266275
ConsoleImagePF5URL string
267276
OKPImageURL string
277+
MCPServerImageURL string
268278
MaxTokensForResponse int
269279
}
270280

@@ -288,6 +298,8 @@ func SetupDefaults() {
288298
"RELATED_IMAGE_CONSOLE_PF5_IMAGE_URL_DEFAULT", ConsoleContainerImagePF5),
289299
OKPImageURL: util.GetEnvVar(
290300
"RELATED_IMAGE_OKP_IMAGE_URL_DEFAULT", OKPContainerImage),
301+
MCPServerImageURL: util.GetEnvVar(
302+
"RELATED_IMAGE_MCP_SERVER_IMAGE_URL_DEFAULT", MCPServerContainerImage),
291303
MaxTokensForResponse: MaxTokensForResponseDefault,
292304
}
293305

bundle/manifests/lightspeed.openstack.org_openstacklightspeeds.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,11 @@ spec:
231231
for this object.
232232
format: int64
233233
type: integer
234+
openStackReady:
235+
description: |-
236+
OpenStackReady indicates whether an OpenStackControlPlane was detected and
237+
is ready. When true, the OpenStack MCP tools are included in lightspeed-stack config.
238+
type: boolean
234239
type: object
235240
type: object
236241
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/rhos-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/rhos-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

@@ -152,7 +158,15 @@ func main() {
152158
setupLog.Info(fmt.Sprintf("openstack-lightspeed operator watches %s namespace", namespace))
153159
}
154160

155-
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
161+
cfg := ctrl.GetConfigOrDie()
162+
163+
kclient, err := kubernetes.NewForConfig(cfg)
164+
if err != nil {
165+
setupLog.Error(err, "unable to create kubernetes client")
166+
os.Exit(1)
167+
}
168+
169+
mgr, err := ctrl.NewManager(cfg, ctrl.Options{
156170
Scheme: scheme,
157171
Metrics: metricsServerOptions,
158172
WebhookServer: webhookServer,
@@ -182,9 +196,14 @@ func main() {
182196
// Defaults for OpenStackLightspeed
183197
apiv1beta1.SetupDefaults()
184198

199+
dynamicWatchCRDs := getDynamicWatchCRDs()
200+
185201
if err = (&controller.OpenStackLightspeedReconciler{
186-
Client: mgr.GetClient(),
187-
Scheme: mgr.GetScheme(),
202+
Client: mgr.GetClient(),
203+
Kclient: kclient,
204+
Scheme: mgr.GetScheme(),
205+
Cache: mgr.GetCache(),
206+
DynamicWatchCRD: dynamicWatchCRDs,
188207
}).SetupWithManager(mgr); err != nil {
189208
setupLog.Error(err, "unable to create controller", "controller", "OpenStackLightspeed")
190209
os.Exit(1)
@@ -222,3 +241,21 @@ func getWatchNamespaces() ([]string, error) {
222241

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

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,11 @@ spec:
231231
for this object.
232232
format: int64
233233
type: integer
234+
openStackReady:
235+
description: |-
236+
OpenStackReady indicates whether an OpenStackControlPlane was detected and
237+
is ready. When true, the OpenStack MCP tools are included in lightspeed-stack config.
238+
type: boolean
234239
type: object
235240
type: object
236241
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/rhos-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)