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
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,14 @@ spec:
spec:
clusterPermissions:
- rules:
- apiGroups:
Comment thread
lpiwowar marked this conversation as resolved.
- ""
resourceNames:
- pull-secret
resources:
- secrets
verbs:
- get
- apiGroups:
- config.openshift.io
resources:
Expand Down
8 changes: 8 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ kind: ClusterRole
metadata:
name: manager-role
rules:
- apiGroups:
- ""
resourceNames:
- pull-secret
resources:
- secrets
verbs:
- get
- apiGroups:
- config.openshift.io
resources:
Expand Down
13 changes: 8 additions & 5 deletions internal/controller/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,14 @@ const (
ForceReloadAnnotationKey = "ols.openshift.io/force-reload"

// Data Exporter
ExporterConfigVolumeName = "exporter-config"
ExporterConfigMountPath = "/etc/config"
ExporterConfigFilename = "config.yaml"
RHOSOLightspeedOwnerIDLabel = "openstack.org/lightspeed-owner-id"
ServiceIDRHOSO = "rhos-lightspeed"
ExporterConfigVolumeName = "exporter-config"
ExporterConfigMountPath = "/etc/config"
ExporterConfigFilename = "config.yaml"
ExporterConfigCmName = "lightspeed-exporter-config"
DataverseExporterContainerName = "lightspeed-to-dataverse-exporter"
UserDataVolumeName = "ols-user-data"
RHOSOLightspeedOwnerIDLabel = "openstack.org/lightspeed-owner-id"
ServiceIDRHOSO = "rhos-lightspeed"

// Azure
AzureOpenAIType = "azure_openai"
Expand Down
1 change: 1 addition & 0 deletions internal/controller/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ var (
ErrGetTLSSecret = errors.New("failed to get TLS secret")
ErrCreateLlamaStackConfigMap = errors.New("failed to create Llama Stack configmap")
ErrGenerateLlamaStackConfigMap = errors.New("failed to generate Llama Stack configmap")
ErrCreateExporterConfigMap = errors.New("failed to create exporter configmap")

// Postgres Errors
ErrCreatePostgresDeployment = errors.New("failed to create Postgres deployment")
Expand Down
32 changes: 32 additions & 0 deletions internal/controller/lcore_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import (

common_helper "github.com/openstack-k8s-operators/lib-common/modules/common/helper"
apiv1beta1 "github.com/openstack-lightspeed/operator/api/v1beta1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
)

Expand Down Expand Up @@ -197,6 +199,36 @@ func buildLCoreConversationCacheConfig(h *common_helper.Helper, _ *apiv1beta1.Op
}
}

// isDataCollectionEnabled returns true if at least one of feedback or transcripts is enabled.
func isDataCollectionEnabled(instance *apiv1beta1.OpenStackLightspeed) bool {
return !instance.Spec.FeedbackDisabled || !instance.Spec.TranscriptsDisabled
}

// buildExporterConfigMap creates the ConfigMap for the dataverse exporter sidecar.
func buildExporterConfigMap(h *common_helper.Helper, _ *apiv1beta1.OpenStackLightspeed) *corev1.ConfigMap {
exporterConfig := fmt.Sprintf(`service_id: "%s"
ingress_server_url: "https://console.redhat.com/api/ingress/v1/upload"
allowed_subdirs:
- feedback
- transcripts
- config_status
collection_interval: 300
cleanup_after_send: true
ingress_connection_timeout: 30
`, ServiceIDRHOSO)

return &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: ExporterConfigCmName,
Namespace: h.GetBeforeObject().GetNamespace(),
Labels: generateAppServerSelectorLabels(),
},
Data: map[string]string{
ExporterConfigFilename: exporterConfig,
},
}
}

// buildLCoreConfigYAML assembles the complete Lightspeed Core Service configuration and converts to YAML.
// NOTE: MCP servers, quota handlers, and tools approval features are disabled for OpenStack Lightspeed.
func buildLCoreConfigYAML(h *common_helper.Helper, instance *apiv1beta1.OpenStackLightspeed) (string, error) {
Expand Down
74 changes: 74 additions & 0 deletions internal/controller/lcore_deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ package controller
import (
"context"
"fmt"
"path"

common_helper "github.com/openstack-k8s-operators/lib-common/modules/common/helper"
apiv1beta1 "github.com/openstack-lightspeed/operator/api/v1beta1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
Expand Down Expand Up @@ -86,13 +88,27 @@ func buildLCorePodTemplateSpec(h *common_helper.Helper, ctx context.Context, ins
ImagePullPolicy: corev1.PullIfNotPresent,
}

// Data collection volumes (shared folder + exporter config)
dataCollectionEnabled := isDataCollectionEnabled(instance)
if dataCollectionEnabled {
addDataCollectorVolumes(&volumes, VolumeDefaultMode)
}

// Lightspeed Stack container mounts: its config + shared + TLS (only API container needs TLS)
lightspeedStackMounts := []corev1.VolumeMount{lcoreMount}
lightspeedStackMounts = append(lightspeedStackMounts, sharedMounts...)
tlsMounts := []corev1.VolumeMount{}
addTLSVolumesAndMounts(&volumes, &tlsMounts, VolumeDefaultMode)
lightspeedStackMounts = append(lightspeedStackMounts, tlsMounts...)

// Mount shared data folder on lightspeed-service-api for feedback/transcripts
if dataCollectionEnabled {
lightspeedStackMounts = append(lightspeedStackMounts, corev1.VolumeMount{
Name: UserDataVolumeName,
MountPath: LCoreUserDataMountPath,
})
}

lightspeedStackContainer := corev1.Container{
Name: "lightspeed-service-api",
Image: apiv1beta1.OpenStackLightspeedDefaultValues.LCoreImageURL,
Expand All @@ -107,6 +123,42 @@ func buildLCorePodTemplateSpec(h *common_helper.Helper, ctx context.Context, ins

containers := []corev1.Container{llamaStackContainer, lightspeedStackContainer}

// Add dataverse exporter sidecar when data collection is enabled
if dataCollectionEnabled {
exporterContainer := corev1.Container{
Name: DataverseExporterContainerName,
Image: apiv1beta1.OpenStackLightspeedDefaultValues.ExporterImageURL,
ImagePullPolicy: corev1.PullAlways,
Comment thread
lpiwowar marked this conversation as resolved.
Args: []string{
"--mode", "openshift",
"--config", path.Join(ExporterConfigMountPath, ExporterConfigFilename),
"--log-level", "INFO",
Comment thread
lpiwowar marked this conversation as resolved.
"--data-dir", LCoreUserDataMountPath,
},
VolumeMounts: []corev1.VolumeMount{
{
Name: UserDataVolumeName,
MountPath: LCoreUserDataMountPath,
},
{
Name: ExporterConfigVolumeName,
MountPath: ExporterConfigMountPath,
ReadOnly: true,
},
},
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("50m"),
corev1.ResourceMemory: resource.MustParse("64Mi"),
},
Limits: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("200Mi"),
},
},
}
containers = append(containers, exporterContainer)
}

// Build configmap resource version annotations for change detection
annotations, err := buildConfigMapAnnotations(h, ctx)
if err != nil {
Expand Down Expand Up @@ -248,6 +300,28 @@ func addLlamaCacheVolumesAndMounts(volumes *[]corev1.Volume, mounts *[]corev1.Vo
})
}

// addDataCollectorVolumes adds the shared data EmptyDir and exporter config volumes.
func addDataCollectorVolumes(volumes *[]corev1.Volume, volumeDefaultMode int32) {
*volumes = append(*volumes, corev1.Volume{
Name: UserDataVolumeName,
VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{},
},
})

*volumes = append(*volumes, corev1.Volume{
Name: ExporterConfigVolumeName,
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: ExporterConfigCmName,
},
DefaultMode: toPtr(volumeDefaultMode),
},
},
})
}

// addUserCAVolumesAndMounts adds user-provided additional CA certificate volume and mount
// if instance.Spec.TLSCACertBundle is set.
func addUserCAVolumesAndMounts(volumes *[]corev1.Volume, mounts *[]corev1.VolumeMount, instance *apiv1beta1.OpenStackLightspeed, volumeDefaultMode int32) {
Expand Down
49 changes: 49 additions & 0 deletions internal/controller/lcore_reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func ReconcileLCoreResources(h *common_helper.Helper, ctx context.Context, insta
{Name: "SARRoleBinding", Task: reconcileSARRoleBinding},
{Name: "LlamaStackConfigMap", Task: reconcileLlamaStackConfigMap},
{Name: "LcoreConfigMap", Task: reconcileLcoreConfigMap},
{Name: "ExporterConfigMap", Task: reconcileExporterConfigMap},
{Name: "OpenStackLightspeedAdditionalCAConfigMap", Task: reconcileOpenStackLightspeedAdditionalCAConfigMap},
{Name: "ProxyCAConfigMap", Task: reconcileProxyCAConfigMap},
{Name: "NetworkPolicy", Task: reconcileNetworkPolicy},
Expand Down Expand Up @@ -115,6 +116,17 @@ func reconcileSARRole(h *common_helper.Helper, ctx context.Context, instance *ap
Resources: []string{"tokenreviews"},
Verbs: []string{"create"},
},
{
APIGroups: []string{"config.openshift.io"},
Resources: []string{"clusterversions"},
Verbs: []string{"get"},
},
{
APIGroups: []string{""},
Resources: []string{"secrets"},
ResourceNames: []string{"pull-secret"},
Verbs: []string{"get"},
},
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
// Note: ClusterRole is cluster-scoped, no owner reference needed
return nil
Expand Down Expand Up @@ -233,6 +245,43 @@ func reconcileLcoreConfigMap(h *common_helper.Helper, ctx context.Context, insta
return nil
}

// reconcileExporterConfigMap ensures the dataverse exporter ConfigMap exists when data
// collection is enabled, and deletes it when disabled.
func reconcileExporterConfigMap(h *common_helper.Helper, ctx context.Context, instance *apiv1beta1.OpenStackLightspeed) error {
logger := h.GetLogger()

if !isDataCollectionEnabled(instance) {
cm := &corev1.ConfigMap{}
cm.Name = ExporterConfigCmName
cm.Namespace = h.GetBeforeObject().GetNamespace()
if err := h.GetClient().Delete(ctx, cm); err != nil && !errors.IsNotFound(err) {
return fmt.Errorf("failed to delete exporter configmap: %w", err)
}
return nil
}
Comment thread
lpiwowar marked this conversation as resolved.

cm := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: ExporterConfigCmName,
Namespace: h.GetBeforeObject().GetNamespace(),
},
}

result, err := controllerutil.CreateOrPatch(ctx, h.GetClient(), cm, func() error {
desiredCm := buildExporterConfigMap(h, instance)
cm.Data = desiredCm.Data
cm.Labels = desiredCm.Labels
return controllerutil.SetControllerReference(h.GetBeforeObject(), cm, h.GetScheme())
})

if err != nil {
return fmt.Errorf("%w: %v", ErrCreateExporterConfigMap, err)
}

logger.Info("Exporter ConfigMap reconciled", "name", cm.Name, "result", result)
return nil
}

// reconcileOpenStackLightspeedAdditionalCAConfigMap verifies that the additional CA config map
// exists if one is specified in the configuration.
func reconcileOpenStackLightspeedAdditionalCAConfigMap(h *common_helper.Helper, ctx context.Context, instance *apiv1beta1.OpenStackLightspeed) error {
Expand Down
1 change: 1 addition & 0 deletions internal/controller/openstacklightspeed_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ func (r *OpenStackLightspeedReconciler) GetLogger(ctx context.Context) logr.Logg
// +kubebuilder:rbac:groups=operators.coreos.com,resources=clusterserviceversions,verbs=get;list;watch
// +kubebuilder:rbac:groups=operators.coreos.com,resources=clusterserviceversions,namespace=openstack-lightspeed,verbs=update;patch;delete
// +kubebuilder:rbac:groups=config.openshift.io,resources=clusterversions,verbs=get;list;watch
// +kubebuilder:rbac:groups="",resources=secrets,resourceNames=pull-secret,verbs=get
// +kubebuilder:rbac:groups=networking.k8s.io,resources=networkpolicies,namespace=openstack-lightspeed,verbs=get;list;watch;create;patch;update
// +kubebuilder:rbac:groups=apps,resources=deployments,namespace=openstack-lightspeed,verbs=get;list;watch;create;update;patch
// +kubebuilder:rbac:groups="",resources=configmaps,namespace=openstack-lightspeed,verbs=get;list;watch;create;patch;update;delete
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
Comment thread
lpiwowar marked this conversation as resolved.
apiVersion: v1
kind: ConfigMap
metadata:
name: lightspeed-exporter-config
namespace: openstack-lightspeed
data:
config.yaml: |
service_id: "rhos-lightspeed"
ingress_server_url: "https://console.redhat.com/api/ingress/v1/upload"
allowed_subdirs:
- feedback
- transcripts
- config_status
collection_interval: 300
cleanup_after_send: true
ingress_connection_timeout: 30
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,20 @@ rules:
- tokenreviews
verbs:
- create
- apiGroups:
- config.openshift.io
resources:
- clusterversions
verbs:
- get
- apiGroups:
- ""
resources:
- secrets
resourceNames:
- pull-secret
verbs:
- get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
Expand Down Expand Up @@ -100,6 +114,12 @@ metadata:
namespace: openstack-lightspeed
---
apiVersion: v1
kind: ConfigMap
metadata:
name: lightspeed-exporter-config
namespace: openstack-lightspeed
---
apiVersion: v1
kind: Service
metadata:
name: lightspeed-app-server
Expand All @@ -110,6 +130,13 @@ kind: Deployment
metadata:
name: lightspeed-stack-deployment
namespace: openstack-lightspeed
spec:
template:
spec:
containers:
- name: llama-stack
- name: lightspeed-service-api
- name: lightspeed-to-dataverse-exporter
status:
replicas: 1
readyReplicas: 1
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
apiVersion: v1
kind: ConfigMap
metadata:
name: lightspeed-exporter-config
namespace: openstack-lightspeed
data:
config.yaml: |
service_id: "rhos-lightspeed"
ingress_server_url: "https://console.redhat.com/api/ingress/v1/upload"
allowed_subdirs:
- feedback
- transcripts
- config_status
collection_interval: 300
cleanup_after_send: true
ingress_connection_timeout: 30
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
apiVersion: v1
kind: ConfigMap
metadata:
name: lightspeed-exporter-config
namespace: openstack-lightspeed
data:
config.yaml: |
service_id: "rhos-lightspeed"
ingress_server_url: "https://console.redhat.com/api/ingress/v1/upload"
allowed_subdirs:
- feedback
- transcripts
- config_status
collection_interval: 300
cleanup_after_send: true
ingress_connection_timeout: 30
Loading
Loading