From e60139ebafdf08c76470137bd926bedc2a7834f1 Mon Sep 17 00:00:00 2001 From: Lukas Piwowarski Date: Tue, 5 May 2026 15:31:08 +0200 Subject: [PATCH 1/2] Configure OGX with RAG This commit ensures that the deployed OGX and Lightspeed Stack is configured to run as RAG with an image provided in the OpenStackLightspeed instance or the default one set in the operator code. The main logic is built on top of initContainers and provides easy extension when we decide to introduce BYOK later. The logic works as follows. Before the Lightspeed Stack and OGX main containers are executed, the following initContainers are run: 1. vector-database-collect: Copies all vector database data (faiss_store.db, llama-stack.yaml, embeddings_model) to a shared volume across all containers in the pod. 2. vector-store-build: Generates the final ogx_config.yaml and lightspeed-stack.yaml files with injected RAG configuration based on the data from step 1. After the init containers complete, both Lightspeed Stack and OGX consume the config files produced by the init containers. Later when introducing BYOK support, we should address: - We should prevent copying the same embedding model multiple times into the shared volume when customers provide multiple container images with the same embedding model. - The embedding model configuration logic for OGX needs to be updated to support BYOK images. Currently, when an embedding model with the same name appears in multiple images, it is only introduced once into the final OGX config, which causes errors related to missing embedding models for some vector databases. The default vector database has been changed to: quay.io/openstack-lightspeed/rag-content-openstack:alpha-ogx-os-docs-2025.2 This image was built MANUALLY and contains ONLY data for the nova project. The default image should be replaced once the upstream pipeline is ready to build llamastack-faiss compatible vector databases. Assisted-By: Claude --- api/v1beta1/openstacklightspeed_types.go | 5 +- ...tspeed-operator.clusterserviceversion.yaml | 6 +- config/manager/manager.yaml | 5 +- hack/env.sh | 5 +- .../assets/vector_database_build.py | 291 ++++++++++++++++++ .../assets/vector_database_collect.sh | 258 ++++++++++++++++ internal/controller/constants.go | 121 +++++++- internal/controller/lcore_config.go | 31 +- internal/controller/lcore_deployment.go | 201 +++++++++--- internal/controller/lcore_reconciler.go | 32 +- internal/controller/llama_stack_config.go | 77 +---- 11 files changed, 874 insertions(+), 158 deletions(-) create mode 100644 internal/controller/assets/vector_database_build.py create mode 100644 internal/controller/assets/vector_database_collect.sh diff --git a/api/v1beta1/openstacklightspeed_types.go b/api/v1beta1/openstacklightspeed_types.go index 83ae9921..dd43ec7e 100644 --- a/api/v1beta1/openstacklightspeed_types.go +++ b/api/v1beta1/openstacklightspeed_types.go @@ -23,8 +23,11 @@ import ( ) const ( + // TODO(lpiwowar): Replace this with a stable (non-alpha) image version once + // the automated pipeline for building OGX-compatible vector database images + // is ready. // OpenStackLightspeedContainerImage is the fall-back container image for OpenStackLightspeed - OpenStackLightspeedContainerImage = "quay.io/openstack-lightspeed/rag-content:os-docs-2025.2" + OpenStackLightspeedContainerImage = "quay.io/openstack-lightspeed/rag-content:alpha-ogx-os-docs-2025.2" // LCoreContainerImage is the fall-back container image for LCore LCoreContainerImage = "quay.io/lightspeed-core/lightspeed-stack:latest" diff --git a/bundle/manifests/openstack-lightspeed-operator.clusterserviceversion.yaml b/bundle/manifests/openstack-lightspeed-operator.clusterserviceversion.yaml index 5e973474..0ed32667 100644 --- a/bundle/manifests/openstack-lightspeed-operator.clusterserviceversion.yaml +++ b/bundle/manifests/openstack-lightspeed-operator.clusterserviceversion.yaml @@ -25,7 +25,7 @@ metadata: ] capabilities: Basic Install categories: AI/Machine Learning - createdAt: "2026-04-09T11:08:52Z" + createdAt: "2026-05-17T15:20:36Z" description: AI-powered virtual assistant for Red Hat OpenStack Services on OpenShift features.operators.openshift.io/cnf: "false" features.operators.openshift.io/cni: "false" @@ -255,7 +255,7 @@ spec: fieldRef: fieldPath: metadata.annotations['olm.targetNamespaces'] - name: RELATED_IMAGE_OPENSTACK_LIGHTSPEED_IMAGE_URL_DEFAULT - value: quay.io/openstack-lightspeed/rag-content:os-docs-2025.2 + value: quay.io/openstack-lightspeed/rag-content:alpha-ogx-os-docs-2025.2 - name: RELATED_IMAGE_LCORE_IMAGE_URL_DEFAULT value: quay.io/lightspeed-core/lightspeed-stack:latest - name: RELATED_IMAGE_EXPORTER_IMAGE_URL_DEFAULT @@ -433,7 +433,7 @@ spec: name: Red Hat url: https://github.com/openstack-lightspeed/operator relatedImages: - - image: quay.io/openstack-lightspeed/rag-content:os-docs-2025.2 + - image: quay.io/openstack-lightspeed/rag-content:alpha-ogx-os-docs-2025.2 name: openstack-lightspeed-image-url-default - image: quay.io/lightspeed-core/lightspeed-stack:latest name: lcore-image-url-default diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 3e4fae29..96a95cd5 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -70,8 +70,11 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace + # TODO(lpiwowar): Replace this with a stable (non-alpha) image version once + # the automated pipeline for building OGX-compatible vector database images + # is ready. - name: RELATED_IMAGE_OPENSTACK_LIGHTSPEED_IMAGE_URL_DEFAULT - value: quay.io/openstack-lightspeed/rag-content:os-docs-2025.2 + value: quay.io/openstack-lightspeed/rag-content:alpha-ogx-os-docs-2025.2 - name: RELATED_IMAGE_LCORE_IMAGE_URL_DEFAULT value: quay.io/lightspeed-core/lightspeed-stack:latest - name: RELATED_IMAGE_EXPORTER_IMAGE_URL_DEFAULT diff --git a/hack/env.sh b/hack/env.sh index 73ed18e0..5eb0ecc5 100644 --- a/hack/env.sh +++ b/hack/env.sh @@ -2,5 +2,8 @@ export RELATED_IMAGE_LCORE_IMAGE_URL_DEFAULT="quay.io/lightspeed-core/lightspeed-stack:latest" export RELATED_IMAGE_EXPORTER_IMAGE_URL_DEFAULT="quay.io/lightspeed-core/lightspeed-to-dataverse-exporter:latest" export RELATED_IMAGE_POSTGRES_IMAGE_URL_DEFAULT="registry.redhat.io/rhel9/postgresql-16:latest" -export RELATED_IMAGE_OPENSTACK_LIGHTSPEED_IMAGE_URL_DEFAULT=quay.io/openstack-lightspeed/rag-content:os-docs-2025.2 +# TODO(lpiwowar): Replace this with a stable (non-alpha) image version once +# the automated pipeline for building OGX-compatible vector database images +# is ready. +export RELATED_IMAGE_OPENSTACK_LIGHTSPEED_IMAGE_URL_DEFAULT="quay.io/openstack-lightspeed/rag-content:alpha-ogx-os-docs-2025.2" export WATCH_NAMESPACE="openstack-lightspeed" diff --git a/internal/controller/assets/vector_database_build.py b/internal/controller/assets/vector_database_build.py new file mode 100644 index 00000000..55186a0f --- /dev/null +++ b/internal/controller/assets/vector_database_build.py @@ -0,0 +1,291 @@ +#!/usr/bin/env python3 + +# +# Copyright 2026. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Vector database configuration builder for the OpenStack Lightspeed operator. + +Runs as the second init container (`vector-database-config-build`), after +`vector_database_collect.sh`. It loads operator-provided base configs, walks every +vector DB directory left by the collect step, and writes merged configs back to +the shared volume. + +Input layout (under --vector-db-path, produced by vector_database_collect.sh): + {vector-db-path}/ + └── / (random directory name from collect script) + ├── vector_db/ + │ ├── / + │ │ ├── llama-stack.yaml + │ │ └── faiss_store.db + │ └── ocp_X.YZ/ (optional, when OCP RAG is enabled) + │ ├── llama-stack.yaml + │ └── faiss_store.db + └── embeddings_model/ + +Output (written to --vector-db-path, same basenames as the base configs): + {vector-db-path}/ + ├── ogx_config.yaml + ├── lightspeed-stack.yaml + └── / (collected data preserved) + +Processing: + 1. For each subdirectory of */vector_db/, read its llama-stack.yaml. + 2. For each detected llama-stack.yaml file, extract its data and inject + the relevant entries into the output ogx_config.yaml and lightspeed-stack.yaml + files. + 3. Write the merged YAML next to the collected data. + +Warning: This script only injects values into existing config structures. +Base configs MUST contain otherwise this script will fail: +- OGX config: registered_resources.{models,vector_stores}, storage.backends, + providers.{inference,vector_io} +- Lightspeed Stack config: byok_rag, rag.inline + +Arguments: + --vector-db-path Shared volume path (input collected data, output configs) + --ogx-config-path Path to the base OGX configuration file + --lightspeed-stack-path Path to the base Lightspeed Stack configuration file +""" + +import argparse +from pathlib import Path +from typing import Any, Iterable, Optional, Callable +import logging +import sys + +import yaml + +# Template for the directory path where data for a single vector database +# instance resides. In the configuration, VECTOR_DB_DATA_PATH will typically +# be substituted by the operator as an environment variable. +VECTOR_DB_DIR_TEMPLATE = ( + "${{env.VECTOR_DB_DATA_PATH}}/{uuid}/vector_db/{vector_db_name}" +) + +# Template for the directory path where data for an embedding model resides. In +# the configuration, VECTOR_DB_DATA_PATH will be substituted by OGX using +# an environment variable. +EMBEDDING_MODEL_DIR_TEMPLATE = "${{env.VECTOR_DB_DATA_PATH}}/{uuid}/embeddings_model" + +# Template for a file path where vector db data are stored. +VECTOR_DB_DATA_PATH_TEMPLATE = f"{VECTOR_DB_DIR_TEMPLATE}/faiss_store.db" + +# The original configuration file name for OGX in the mounted vector database data. +# Update later: The file is still named 'llama-stack.yaml' for backward compatibility, +# since the Llama Stack project was renamed to OGX recently. +OGX_CONFIG_SOURCE_FILE_NAME = "llama-stack.yaml" + + +# -- Shared functions -------------------------------------------------------- +def load_yaml_file(yaml_file_path: Path) -> dict[str, Any]: + """Load YAML file""" + try: + with open(yaml_file_path, "r", encoding="utf-8") as f: + return yaml.safe_load(f) or {} + except FileNotFoundError: + logging.error("YAML file not found: %s", yaml_file_path) + sys.exit(1) + + +def add_unique(lst: list, item: Any, key: Optional[str] = None) -> None: + """Add item to list if not already present. + + :param lst: List to modify in-place + :param item: Item to add + :param key: If provided, check uniqueness by comparing item[key] values. + If None, check direct item equality. + """ + if key: + if any(existing.get(key) == item.get(key) for existing in lst): + return + elif item in lst: + return + lst.append(item) + + +def write_yaml_file(yaml_data: dict[str, Any], dest_path: Path) -> None: + """Write YAML data to the specified file path.""" + try: + dest_path.parent.mkdir(parents=True, exist_ok=True) + with open(dest_path, "w", encoding="utf-8") as f: + yaml.dump(yaml_data, f, default_flow_style=False, sort_keys=False) + except (OSError, yaml.YAMLError) as e: + logging.error("Failed to write YAML to %s: %s", dest_path, e) + sys.exit(1) + + +def iterate_vector_db_data_dir(vector_db_data_dir_path: Path) -> Iterable[Path]: + """Return all folders inside any vector_db/ subfolder, one per yield.""" + for image_uuid_dir in vector_db_data_dir_path.iterdir(): + vector_db_path = image_uuid_dir.joinpath("vector_db") + + if not vector_db_path.is_dir(): + continue + + for folder in vector_db_path.iterdir(): + if folder.is_dir(): + yield folder + + +def config_build( + vector_db_parent_dir: Path, + config_target_path: Path, + config_populate_fn: Callable[[Path, dict[str, Any]], dict[str, Any]], +) -> None: + config_target = load_yaml_file(config_target_path) + for vector_db_dir in iterate_vector_db_data_dir(vector_db_parent_dir): + ogx_config_source_path = vector_db_dir.joinpath(OGX_CONFIG_SOURCE_FILE_NAME) + try: + config_target = config_populate_fn(ogx_config_source_path, config_target) + except (KeyError, IndexError) as e: + logging.error( + "Error processing config: missing required section in source " + "or target file (%s)", + e, + ) + sys.exit(1) + + config_product_path = vector_db_parent_dir.joinpath(config_target_path.name) + write_yaml_file(config_target, config_product_path) + + +# ---------------------------------------------------------------------------- + + +# -- OGX functions ----------------------------------------------------------- +def ogx_process(ogx_config_source_path: Path, ogx_config_target: dict[str, Any]): + """Populate the target OGX config with vector DB data from source OGX config""" + ogx_config_source = load_yaml_file(ogx_config_source_path) + + # E.g.: /data//vector_db/os_product_docs/llama-stack.yaml -> + image_uuid = ogx_config_source_path.parts[-4] + + # E.g.: /data//vector_db/os_product_docs/llama-stack.yaml -> os_product_docs + vector_db_name = ogx_config_source_path.parts[-2] + + vector_db_file = VECTOR_DB_DATA_PATH_TEMPLATE.format( + uuid=image_uuid, vector_db_name=vector_db_name + ) + embedding_model_dir = EMBEDDING_MODEL_DIR_TEMPLATE.format(uuid=image_uuid) + + # Populate registered_resources.models + src_model = ogx_config_source["registered_resources"]["models"][0].copy() + src_model["provider_model_id"] = embedding_model_dir + tgt_models = ogx_config_target["registered_resources"]["models"] + add_unique(tgt_models, src_model, "model_id") + + # Populate registered_resources.vector_stores + embedding_model = f"{src_model['provider_id']}/{embedding_model_dir}" + src_vstore = ogx_config_source["registered_resources"]["vector_stores"][0].copy() + src_vstore["embedding_model"] = embedding_model + tgt_vstores = ogx_config_target["registered_resources"]["vector_stores"] + add_unique(tgt_vstores, src_vstore) + + # Populate storage.backends + storage_backend_key = f"kv_rag_{image_uuid}_{vector_db_name}" + storage_backend = ogx_config_source["storage"]["backends"]["kv_rag"].copy() + storage_backend["db_path"] = vector_db_file + ogx_config_target["storage"]["backends"][storage_backend_key] = storage_backend + + # Populate providers.inference + src_inference = ogx_config_source["providers"]["inference"][0] + tgt_inferences = ogx_config_target["providers"]["inference"] + add_unique(tgt_inferences, src_inference) + + # Populate providers.vector_io + src_vector_io = ogx_config_source["providers"]["vector_io"][0].copy() + src_vector_io["config"]["persistence"]["backend"] = storage_backend_key + tgt_vector_ios = ogx_config_target["providers"]["vector_io"] + add_unique(tgt_vector_ios, src_vector_io) + + return ogx_config_target + + +# ---------------------------------------------------------------------------- + + +# -- Lightspeed Stack functions ---------------------------------------------- +def lstack_process( + ogx_config_source_path: Path, lstack_config_target: dict[str, Any] +) -> dict[str, Any]: + """Update Lightspeed stack config with RAG entries from OGX config source.""" + ogx_config_source = load_yaml_file(ogx_config_source_path) + + src_vstores = ogx_config_source["registered_resources"]["vector_stores"] + vector_store_id = src_vstores[0]["vector_store_id"] + + add_unique( + lstack_config_target["byok_rag"], + { + "rag_id": vector_store_id, + "vector_db_id": vector_store_id, + # The score multiplier is set to 1.0 so all BYOK sources have + # equal weighting. + "score_multiplier": 1.0, + # The Lightspeed Stack currently requires a "db_path" value even + # when OGX operates in server mode. This placeholder value ("NONE") + # is provided solely to satisfy this requirement and should be + # removed once the Lightspeed Stack no longer mandates it for + # server mode. + "db_path": "NONE", + }, + ) + + add_unique(lstack_config_target["rag"]["inline"], vector_store_id) + return lstack_config_target + + +# ---------------------------------------------------------------------------- + + +def parse_arguments() -> argparse.Namespace: + """Parse command-line arguments and return parsed namespace.""" + parser = argparse.ArgumentParser( + description=( + "Build vector database configuration files by merging collected " + "vector DB data with base configs" + ) + ) + parser.add_argument( + "--vector-db-path", + type=Path, + required=True, + help="Path (as pathlib.Path) to the mounted vector DB data volume and output destination", + ) + parser.add_argument( + "--ogx-config-path", + type=Path, + required=True, + help="Path (as pathlib.Path) to the base OGX configuration file", + ) + parser.add_argument( + "--lightspeed-stack-path", + type=Path, + required=True, + help="Path (as pathlib.Path) to the base Lightspeed Stack configuration file", + ) + + return parser.parse_args() + + +def main() -> None: + """main""" + args = parse_arguments() + config_build(args.vector_db_path, args.ogx_config_path, ogx_process) + config_build(args.vector_db_path, args.lightspeed_stack_path, lstack_process) + + +if __name__ == "__main__": + main() diff --git a/internal/controller/assets/vector_database_collect.sh b/internal/controller/assets/vector_database_collect.sh new file mode 100644 index 00000000..2bca02fa --- /dev/null +++ b/internal/controller/assets/vector_database_collect.sh @@ -0,0 +1,258 @@ +#!/bin/sh + +# Copyright 2026. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# vector_database_collect.sh +# +# Collects vector database data and embedding models from a container image and +# copies them to a target directory. +# +# IMPORTANT: The vector database data must strictly adhere to the expected +# directory structure described below. Otherwise the script will fail. +# +# Expected Container Image Structure (Input): +# /rag/ +# ├── vector_db/ +# │ ├── vector-db-data-1/ +# │ │ ├── faiss_store.db +# │ │ └── llama-stack.yaml +# │ └── vector-db-data-N/ +# │ ├── faiss_store.db +# │ └── llama-stack.yaml +# ├── ocp_vector_db/ +# │ ├── ocp_X.YZ/ +# │ │ ├── faiss_store.db +# │ │ └── llama-stack.yaml +# │ └── ocp_latest/ +# │ ├── faiss_store.db +# │ └── llama-stack.yaml +# └── embeddings_model/ +# └── +# +# Output Structure: +# / (specified via --vector-db-path) +# └── / +# ├── vector_db/ +# │ ├── vector-db-data-1/ +# │ ├── vector-db-data-N/ +# │ └── ocp_X.YZ/ (if --enable-ocp-rag true and --ocp-version X.YZ) +# └── embeddings_model/ +# └── +# +# Arguments: +# --vector-db-path PATH Target directory for collected data (required) +# --enable-ocp-rag BOOL Enable OCP vector DB collection: true/false (required) +# --ocp-version VERSION OCP version to collect, e.g., "X.YZ" (required) + +set -euo pipefail + +# -- vector_database_collect.sh script parameters ---------------------------- + +# VECTOR_DB_VOLUME_MOUNT_PATH is location where content of a vector database +# image is mounted. Populated via parse_arguments_and_init. +VECTOR_DB_VOLUME_MOUNT_PATH="" + +# ENABLE_OCP_RAG specifies whether this script should collect vector database +# content related to OCP (expected to be found under OCP_VECTOR_DB_DIR). +# Must be set to "true" for the collection to be enabled. Populated via +# parse_arguments_and_init. +ENABLE_OCP_RAG="" + +# OCP_VERSION specifies what version of OCP content should be collected from +# the vector database image -> ${OCP_VECTOR_DB_DIR}/ocp_${OCP_VERSION}. Populated +# via parse_arguments_and_init. +OCP_VERSION="" +# ---------------------------------------------------------------------------- + +# -- Global vars ------------------------------------------------------------- + +# COLLECT_DIR is location within user provided VECTOR_DB_VOLUME_MOUNT_PATH where +# data collected from a single vector db image should be stored (populated +# via parse_arguments_and_init) +COLLECT_DIR="" + +# VECTOR_DB_DATA_COLLECT_DIR is location within COLLECT_DIR where vector db +# related data should be stored (faiss_store.db, ogx_config.yaml). Populated +# via parse_arguments_and_init. +VECTOR_DB_DATA_COLLECT_DIR="" + +# EMBEDDINGS_MODEL_DATA_COLLECT_DIR is location within COLLECT_DIR where +# embeddings model should be stored. Populated via parse_arguments_and_init. +EMBEDDINGS_MODEL_DATA_COLLECT_DIR="" + +# OCP_VECTOR_DB_DIR specifies the directory within the vector DB container image +# where OCP-specific vector DB data must reside. This script expects to find the +# OCP data exclusively at this location. +OCP_VECTOR_DB_DIR="/rag/ocp_vector_db" + +# OCP_VECTOR_DB_DIR_FALLBACK specifies the directory within the vector DB container +# image that contains the default OCP vector DB data. This path is used when no data +# matching the version specified via --ocp-version is found. +OCP_VECTOR_DB_DIR_FALLBACK="/rag/ocp_vector_db/ocp_latest" + +# VECTOR_DB_DIR specifies the directory within the vector DB container image +# where general vector DB data must reside. +VECTOR_DB_DIR="/rag/vector_db" + +# EMBEDDINGS_MODEL_DIR specifies the directory within the vector DB container image +# where embeddings model must reside. +EMBEDDINGS_MODEL_DIR="/rag/embeddings_model" + +# OGX_CONFIG_FILE_NAME is the name of the OGX config file associated with a +# single vector database. +OGX_CONFIG_FILE_NAME="llama-stack.yaml" + +# VECTOR_DB_FILE_NAME is the name of the file containing vector database data +# for a single vector database. +VECTOR_DB_FILE_NAME="faiss_store.db" +# ---------------------------------------------------------------------------- + +parse_arguments_and_init() { + while [ $# -gt 0 ]; do + case $1 in + --vector-db-path) + VECTOR_DB_VOLUME_MOUNT_PATH="$2" + shift 2 + ;; + --enable-ocp-rag) + ENABLE_OCP_RAG="$2" + shift 2 + ;; + --ocp-version) + OCP_VERSION="$2" + shift 2 + ;; + -h|--help) + echo "Usage: $0 --vector-db-path PATH --enable-ocp-rag BOOL --ocp-version VERSION" + echo "" + echo "Arguments:" + echo " --vector-db-path Target path for vector DB data collection" + echo " --enable-ocp-rag Enable OCP RAG collection (true/false)" + echo " --ocp-version OCP version to collect (e.g., 4.16)" + echo " -h, --help Show this help message" + exit 0 + ;; + *) + echo "Unknown argument: $1" + echo "Use --help for usage information" + exit 1 + ;; + esac + done + + if [ -z "${VECTOR_DB_VOLUME_MOUNT_PATH:-}" ]; then + echo "ERROR: --vector-db-path is required" + exit 1 + fi + + if [ -z "${ENABLE_OCP_RAG:-}" ]; then + echo "ERROR: --enable-ocp-rag is required" + exit 1 + fi + + if [ -z "${OCP_VERSION:-}" ]; then + echo "ERROR: --ocp-version is required" + exit 1 + fi + + COLLECT_DIR=$(mktemp -d "${VECTOR_DB_VOLUME_MOUNT_PATH}/XXXXXXXXXX") + VECTOR_DB_DATA_COLLECT_DIR="${COLLECT_DIR}/vector_db/" + EMBEDDINGS_MODEL_DATA_COLLECT_DIR="${COLLECT_DIR}/embeddings_model" +} + +validate_vector_db_dir() { + local vector_db_dir="$1" + + if [ ! -d "${vector_db_dir}" ]; then + echo "ERROR: ${vector_db_dir} is not a directory" + exit 1 + fi + + if [ ! -f "${vector_db_dir}/${VECTOR_DB_FILE_NAME}" ]; then + echo "ERROR: faiss_store.db is missing in ${vector_db_dir}" + exit 1 + fi + + if [ ! -f "${vector_db_dir}/${OGX_CONFIG_FILE_NAME}" ]; then + echo "ERROR: llama-stack.yaml is missing in ${vector_db_dir}" + exit 1 + fi +} + +collect_ocp_vector_db_data() { + if [ "${ENABLE_OCP_RAG}" != "true" ]; then + echo "Collecting of OCP vector db data is DISABLED => Skipping" + return + fi + + echo "Collecting OCP vector DB data ..." + mkdir -p "${VECTOR_DB_DATA_COLLECT_DIR}" + + local ocp_dir="${OCP_VECTOR_DB_DIR}/ocp_${OCP_VERSION}" + if [ ! -d "${ocp_dir}" ]; then + echo "Data for OCP version ${OCP_VERSION} not found. Using: ${OCP_VECTOR_DB_DIR_FALLBACK}" + ocp_dir=${OCP_VECTOR_DB_DIR_FALLBACK} + fi + + validate_vector_db_dir "${ocp_dir}" + cp -rL "${ocp_dir}" "${VECTOR_DB_DATA_COLLECT_DIR}" + echo "Discovered and collected OCP vector DB data from ${ocp_dir}" +} + +collect_vector_db_data() { + echo "Collecting vector DB data ..." + mkdir -p "${VECTOR_DB_DATA_COLLECT_DIR}" + local vector_db_data_collected="false" + for dir in ${VECTOR_DB_DIR}/*/; do + [ ! -d "$dir" ] && continue + + validate_vector_db_dir "$dir" + cp -rL "${dir}" "${VECTOR_DB_DATA_COLLECT_DIR}" + vector_db_data_collected="true" + echo "Discovered and collected vector DB data from ${dir}" + done + + if [ "${ENABLE_OCP_RAG}" != "true" ] && [ ${vector_db_data_collected} != "true" ]; then + echo "ERROR: ENABLE_OCP_RAG='${ENABLE_OCP_RAG}' and no generic vector db data found." + exit 1 + fi +} + +# TODO(lpiwowar): When introducing BYOK, ensure that the same embeddings model is not +# copied multiple times from different vector database images. Implement logic to check +# if the model already exists in the collection directory before copying; consider using +# symlinks or a similar mechanism to avoid redundant copies. These models can be large +# (e.g., 500MB), so minimizing duplication is important. +collect_embeddings_model() { + echo "Collecting embeddings model ..." + if [ ! -d "${EMBEDDINGS_MODEL_DIR}" ]; then + echo "ERROR: Embeddings model dir not found under ${EMBEDDINGS_MODEL_DIR}." + exit 1 + fi + + cp -rL "${EMBEDDINGS_MODEL_DIR}" "${EMBEDDINGS_MODEL_DATA_COLLECT_DIR}" + echo "Discovered and collected embeddings model data from ${EMBEDDINGS_MODEL_DIR}" +} + +main() { + # NOTE: parse_arguments_and_init must be called first to ensure all global + # variables are initialized before proceeding. + parse_arguments_and_init "$@" + collect_vector_db_data + collect_ocp_vector_db_data + collect_embeddings_model +} + +main "$@" diff --git a/internal/controller/constants.go b/internal/controller/constants.go index 4bff8a47..ef7a93bd 100644 --- a/internal/controller/constants.go +++ b/internal/controller/constants.go @@ -22,10 +22,6 @@ import ( ) const ( - // Volume Permissions - VolumeDefaultMode = int32(420) - VolumeRestrictedMode = int32(0600) - // Operator Settings ResourceCreationTimeout = 60 * time.Second @@ -80,21 +76,15 @@ const ( PostgresVarRunVolumeMountPath = "/var/run/postgresql" TmpVolumeName = "tmp-writable-volume" TmpVolumeMountPath = "/tmp" - PostgresConfigMapResourceVersionAnnotation = "ols.openshift.io/postgres-configmap-version" // LCore specific - LlamaStackContainerPort = int32(8321) - LlamaStackConfigCmName = "llama-stack-config" - LCoreConfigCmName = "lightspeed-stack-config" - LCoreDeploymentName = "lightspeed-stack-deployment" - LlamaStackConfigMountPath = "/app-root/run.yaml" - LCoreConfigMountPath = "/app-root/lightspeed-stack.yaml" - LlamaStackConfigFilename = "run.yaml" - LCoreConfigFilename = "lightspeed-stack.yaml" - LCoreConfigMapResourceVersionAnnotation = "ols.openshift.io/lcore-configmap-version" - LlamaStackConfigMapResourceVersionAnnotation = "ols.openshift.io/llamastack-configmap-version" - LCoreUserDataMountPath = "/tmp/data" - ForceReloadAnnotationKey = "ols.openshift.io/force-reload" + LlamaStackContainerPort = int32(8321) + LlamaStackConfigCmName = "llama-stack-config" + LCoreConfigCmName = "lightspeed-stack-config" + LCoreDeploymentName = "lightspeed-stack-deployment" + LCoreConfigMountPath = "/app-root/lightspeed-stack.yaml" + LCoreUserDataMountPath = "/tmp/data" + ForceReloadAnnotationKey = "ols.openshift.io/force-reload" // Data Exporter ExporterConfigVolumeName = "exporter-config" @@ -111,6 +101,87 @@ const ( // EnvVarSuffixAPIKey is the environment variable suffix for API key credentials EnvVarSuffixAPIKey = "_API_KEY" + + // VectorDBVolumeName is the name of the volume used by init containers to + // store discovered values from vector DB images. + VectorDBVolumeName = "vector-db-discovered-values" + + // VectorDBVolumeMountPath specifies the mount path for the volume that stores + // discovered values from vector database images. + VectorDBVolumeMountPath = "/vector-db-discovered-values" + + // VectorDBVolumeOGXConfigPath specifies the path within the `VectorDBVolumeName` volume + // where the final OGX configuration file (ogx_config.yaml) is stored. This file is + // generated by the init container responsible for assembling the final OGX config. + VectorDBVolumeOGXConfigPath = VectorDBVolumeMountPath + "/ogx_config.yaml" + + // VectorDBVolumeLightspeedStackConfigPath specifies the path within the + // `VectorDBVolumeName` volume where the final Lightspeed Stack configuration + // file (lightspeed-stack.yaml) is stored. This file is generated by the + // init container responsible for assembling the final Lightspeed Stack config. + VectorDBVolumeLightspeedStackConfigPath = VectorDBVolumeMountPath + "/lightspeed-stack.yaml" + + // OGXConfigInitContainerMountPath specifies the path where the operator-generated + // OGX config file is mounted in the init container responsible for assembling + // the final OGX configuration, which includes information about RAG. + OGXConfigInitContainerMountPath = "/ogx_config.yaml" + + // LightspeedStackInitContainerMountPath specifies the path where the + // operator-generated Lightspeed Stack config file is mounted in the init + // container responsible for assembling the final Lightspeed Stack configuration, + // which includes information about RAG. + LightspeedStackInitContainerMountPath = "/lightspeed-stack.yaml" + + // OGXConfigVolumeName specifies the name of the volume holding config file for OGX + // (generated by the operator and passed to init containers) + OGXConfigVolumeName = "ogx-config" + + // LightspeedStackConfig specifies the name of the volume holding config file for + // Lightspeed Stack (generated by the operator and passed to init containers) + LightspeedStackConfig = "lightspeed-stack-config" + + // OGXConfigCMKey is the key in the ConfigMap under which the OGX configuration + // is stored. + OGXConfigCMKey = "ogx_config.yaml" + + // LightspeedStackConfigCMKey is the key in the ConfigMap under which the Lightspeed Stack + // configuration is stored. + LightspeedStackConfigCMKey = "lightspeed-stack.yaml" + + // VectorDBScriptsConfigMapName is the name of the ConfigMap that contains the + // initialization scripts used by init containers to collect and build vector database data + VectorDBScriptsConfigMapName = "vector-db-scripts" + + // VectorDBScriptsVolumeName is the name of the volume that mounts the ConfigMap containing + // vector database initialization scripts for use by init containers + VectorDBScriptsVolumeName = "vector-db-scripts" + + // VectorDBScriptsMountPath specifies the path where vector database init scripts + // should be mounted within the init containers. + VectorDBScriptsMountPath = "/scripts" + + // VectorDBCollectScriptKey is the ConfigMap key under which the vector_database_collect.sh + // script is stored in the ConfigMap containing vector database init scripts. + VectorDBCollectScriptKey = "vector_database_collect.sh" + + // VectorDBBuildScriptKey is the ConfigMap key under which the vector_database_build.py + // script is stored in the ConfigMap containing vector database init scripts. + VectorDBBuildScriptKey = "vector_database_build.py" + + // Resource Version Annotation + // These constants define annotation keys used to track the resource versions of specific ConfigMaps. + // By recording the resource version of a ConfigMap in a Deployment, StatefulSet, or similar resource, + // changes to the referenced ConfigMaps can be detected and trigger rollouts or reconciliation in the operator. + PostgresConfigMapResourceVersionAnnotation = "ols.openshift.io/postgres-configmap-version" + VectorDBScriptsConfigMapVersionAnnotation = "ols.openshift.io/vector-db-scripts-configmap-version" + LlamaStackConfigMapResourceVersionAnnotation = "ols.openshift.io/llamastack-configmap-version" + LCoreConfigMapResourceVersionAnnotation = "ols.openshift.io/lcore-configmap-version" + + // Volume Permissions + // These constants define file permissions for volumes mounted in containers. + VolumeDefaultMode = int32(420) + VolumeRestrictedMode = int32(0600) + VolumeExecutableMode = int32(0755) ) // PostgreSQL Bootstrap Script - creates database, extensions, and schemas @@ -122,3 +193,19 @@ var PostgresBootStrapScriptContent string // //go:embed assets/postgres.conf var PostgresConfigMapContent string + +// vectorDatabaseCollectScript embeds the contents of the vector_database_collect.sh script +// found in the assets directory. This script is used during the initialization of the +// vector database, run as an init container in the deployment. Read +// assets/vector_database_collect.sh for more comprehensive explanation. +// +//go:embed assets/vector_database_collect.sh +var vectorDatabaseCollectScript string + +// vectorDatabaseBuildScript embeds the contents of the vector_database_build.py script +// found in the assets directory. This script is responsible for building or processing +// the vector database and is used by an init container during deployment initialization. +// Read assets/vector_database_build.py for more comprehensive explanation. +// +//go:embed assets/vector_database_build.py +var vectorDatabaseBuildScript string diff --git a/internal/controller/lcore_config.go b/internal/controller/lcore_config.go index fccfdb3b..dd0238d9 100644 --- a/internal/controller/lcore_config.go +++ b/internal/controller/lcore_config.go @@ -55,13 +55,6 @@ type lcoreModel struct { MaxTokensForResponse int } -// lcoreRAG represents RAG configuration. -type lcoreRAG struct { - Image string - IndexPath string - IndexID string -} - // buildProvider creates an lcoreProvider from an OpenStackLightspeed instance. func buildProvider(instance *apiv1beta1.OpenStackLightspeed) lcoreProvider { return lcoreProvider{ @@ -81,26 +74,6 @@ func buildProvider(instance *apiv1beta1.OpenStackLightspeed) lcoreProvider { } } -// buildLCoreRAGConfigs builds the RAG configuration from an OpenStackLightspeed instance. -func buildLCoreRAGConfigs(instance *apiv1beta1.OpenStackLightspeed, ocpVersion string) []lcoreRAG { - rags := []lcoreRAG{ - { - Image: instance.Spec.RAGImage, - IndexPath: OpenStackLightspeedVectorDBPath, - }, - } - - if ocpVersion != "" { - rags = append(rags, lcoreRAG{ - Image: instance.Spec.RAGImage, - IndexPath: GetOCPVectorDBPath(ocpVersion), - IndexID: GetOCPIndexName(ocpVersion), - }) - } - - return rags -} - func buildLCoreServiceConfig(_ *common_helper.Helper, _ *apiv1beta1.OpenStackLightspeed) map[string]interface{} { return map[string]interface{}{ "host": "0.0.0.0", @@ -243,6 +216,10 @@ func buildLCoreConfigYAML(h *common_helper.Helper, instance *apiv1beta1.OpenStac "database": buildLCoreDatabaseConfig(h, instance), "customization": buildLCoreCustomizationConfig(), "conversation_cache": buildLCoreConversationCacheConfig(h, instance), + "byok_rag": []interface{}{}, + "rag": map[string]interface{}{ + "inline": []interface{}{}, + }, } // Convert to YAML diff --git a/internal/controller/lcore_deployment.go b/internal/controller/lcore_deployment.go index 7f391b33..5f0c43a5 100644 --- a/internal/controller/lcore_deployment.go +++ b/internal/controller/lcore_deployment.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "path" + "strconv" common_helper "github.com/openstack-k8s-operators/lib-common/modules/common/helper" apiv1beta1 "github.com/openstack-lightspeed/operator/api/v1beta1" @@ -35,15 +36,11 @@ import ( // This function is used by CreateOrPatch to generate the desired pod spec. func buildLCorePodTemplateSpec(h *common_helper.Helper, ctx context.Context, instance *apiv1beta1.OpenStackLightspeed) (corev1.PodTemplateSpec, error) { // Build shared volumes - volumes := []corev1.Volume{} - - // Llama Stack config volume (used by llama-stack container) - llamaVol, llamaMount := buildLlamaStackConfigVolumeAndMount(VolumeDefaultMode) - volumes = append(volumes, llamaVol) - - // LCore config volume (used by lightspeed-stack container) - lcoreVol, lcoreMount := buildLCoreConfigVolumeAndMount(VolumeDefaultMode) - volumes = append(volumes, lcoreVol) + volumes := []corev1.Volume{ + buildOGXConfigVolume(VolumeDefaultMode), + buildLightspeedStackConfigVolume(VolumeDefaultMode), + buildVectorDBScriptsVolume(), + } // Shared volumes - CA, postgres sharedMounts := []corev1.VolumeMount{} @@ -51,6 +48,7 @@ func buildLCorePodTemplateSpec(h *common_helper.Helper, ctx context.Context, ins addOpenShiftRootCAVolumesAndMounts(&volumes, &sharedMounts, VolumeDefaultMode) addPostgresCAVolumesAndMounts(&volumes, &sharedMounts) addUserCAVolumesAndMounts(&volumes, &sharedMounts, instance, VolumeDefaultMode) + addVectorDBDataVolumesAndMounts(&volumes, &sharedMounts) // Llama cache emptydir llamaCacheMounts := []corev1.VolumeMount{} @@ -63,15 +61,15 @@ func buildLCorePodTemplateSpec(h *common_helper.Helper, ctx context.Context, ins } lsEnvVars := buildLightspeedStackEnvVars() - // Llama Stack container mounts: its config + shared + cache - llamaStackMounts := []corev1.VolumeMount{llamaMount} + // Llama Stack container mounts: its config + shared + cache + vector_store_db data + llamaStackMounts := []corev1.VolumeMount{} llamaStackMounts = append(llamaStackMounts, sharedMounts...) llamaStackMounts = append(llamaStackMounts, llamaCacheMounts...) llamaStackContainer := corev1.Container{ Name: "llama-stack", Image: apiv1beta1.OpenStackLightspeedDefaultValues.LCoreImageURL, - Command: []string{"llama", "stack", "run", LlamaStackConfigMountPath}, + Command: []string{"llama", "stack", "run", VectorDBVolumeOGXConfigPath}, Ports: []corev1.ContainerPort{{Name: "llama-stack", ContainerPort: LlamaStackContainerPort}}, VolumeMounts: llamaStackMounts, Env: llamaEnvVars, @@ -95,8 +93,9 @@ func buildLCorePodTemplateSpec(h *common_helper.Helper, ctx context.Context, ins } // Lightspeed Stack container mounts: its config + shared + TLS (only API container needs TLS) - lightspeedStackMounts := []corev1.VolumeMount{lcoreMount} + lightspeedStackMounts := []corev1.VolumeMount{} lightspeedStackMounts = append(lightspeedStackMounts, sharedMounts...) + tlsMounts := []corev1.VolumeMount{} addTLSVolumesAndMounts(&volumes, &tlsMounts, VolumeDefaultMode) lightspeedStackMounts = append(lightspeedStackMounts, tlsMounts...) @@ -112,6 +111,7 @@ func buildLCorePodTemplateSpec(h *common_helper.Helper, ctx context.Context, ins lightspeedStackContainer := corev1.Container{ Name: "lightspeed-service-api", Image: apiv1beta1.OpenStackLightspeedDefaultValues.LCoreImageURL, + Args: []string{"-c", VectorDBVolumeLightspeedStackConfigPath}, Ports: []corev1.ContainerPort{{Name: "https", ContainerPort: OpenStackLightspeedAppServerContainerPort}}, VolumeMounts: lightspeedStackMounts, Env: lsEnvVars, @@ -120,7 +120,6 @@ func buildLCorePodTemplateSpec(h *common_helper.Helper, ctx context.Context, ins Resources: getResourcesOrDefault(nil, corev1.ResourceRequirements{}), ImagePullPolicy: corev1.PullIfNotPresent, } - containers := []corev1.Container{llamaStackContainer, lightspeedStackContainer} // Add dataverse exporter sidecar when data collection is enabled @@ -165,6 +164,11 @@ func buildLCorePodTemplateSpec(h *common_helper.Helper, ctx context.Context, ins return corev1.PodTemplateSpec{}, err } + initContainers, err := buildInitContainers(ctx, h, instance) + if err != nil { + return corev1.PodTemplateSpec{}, err + } + return corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: generateAppServerSelectorLabels(), @@ -172,16 +176,114 @@ func buildLCorePodTemplateSpec(h *common_helper.Helper, ctx context.Context, ins }, Spec: corev1.PodSpec{ ServiceAccountName: OpenStackLightspeedAppServerServiceAccountName, + InitContainers: initContainers, Containers: containers, Volumes: volumes, }, }, nil } -// buildLCoreConfigVolumeAndMount returns the volume and mount for the lightspeed-stack config. -func buildLCoreConfigVolumeAndMount(volumeDefaultMode int32) (corev1.Volume, corev1.VolumeMount) { - vol := corev1.Volume{ - Name: "lcore-config", +// buildInitContainers returns the configuration for initContainers that run +// before the main OGX and Lightspeed Stack containers in the Lightspeed Stack +// deployment. These initContainers are responsible for generating the final OGX +// and Lightspeed Stack configuration files, incorporating information from +// the provided vector database images. For details on their logic, see: +// (1) assets/vector_database_collect.sh and (2) assets/vector_database_build.py. +func buildInitContainers( + ctx context.Context, + helper *common_helper.Helper, + instance *apiv1beta1.OpenStackLightspeed, +) ([]corev1.Container, error) { + ocp_version, err := DetectOCPVersion(ctx, helper) + if err != nil { + return []corev1.Container{}, err + } + + securityContext := &corev1.SecurityContext{ + RunAsNonRoot: &[]bool{true}[0], + AllowPrivilegeEscalation: &[]bool{false}[0], + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + } + + resourceRequirements := corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("256Mi"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("500m"), + corev1.ResourceMemory: resource.MustParse("1Gi"), + }, + } + + var containers []corev1.Container + containers = append(containers, corev1.Container{ + Name: "vector-database-collect", + Image: instance.Spec.RAGImage, + Command: []string{ + "sh", VectorDBScriptsMountPath + "/" + VectorDBCollectScriptKey, + "--vector-db-path", VectorDBVolumeMountPath, + "--enable-ocp-rag", strconv.FormatBool(instance.Spec.EnableOCPRAG), + "--ocp-version", ocp_version, + }, + SecurityContext: securityContext, + Resources: resourceRequirements, + VolumeMounts: []corev1.VolumeMount{ + { + Name: VectorDBVolumeName, + MountPath: VectorDBVolumeMountPath, + }, + { + Name: VectorDBScriptsVolumeName, + MountPath: VectorDBScriptsMountPath, + ReadOnly: true, + }, + }, + }) + + containers = append(containers, corev1.Container{ + Name: "vector-database-config-build", + Image: apiv1beta1.OpenStackLightspeedDefaultValues.LCoreImageURL, + Command: []string{ + "python3", VectorDBScriptsMountPath + "/" + VectorDBBuildScriptKey, + "--vector-db-path", VectorDBVolumeMountPath, + "--ogx-config-path", OGXConfigInitContainerMountPath, + "--lightspeed-stack-path", LightspeedStackInitContainerMountPath, + }, + SecurityContext: securityContext, + Resources: resourceRequirements, + VolumeMounts: []corev1.VolumeMount{ + { + Name: VectorDBVolumeName, + MountPath: VectorDBVolumeMountPath, + }, + { + Name: VectorDBScriptsVolumeName, + MountPath: VectorDBScriptsMountPath, + ReadOnly: true, + }, + { + Name: OGXConfigVolumeName, + MountPath: OGXConfigInitContainerMountPath, + SubPath: OGXConfigCMKey, + }, + { + Name: LightspeedStackConfig, + MountPath: LightspeedStackInitContainerMountPath, + SubPath: LightspeedStackConfigCMKey, + }, + }, + }) + + return containers, nil +} + +// buildLightspeedStackConfigVolume returns the volume for the lightspeed-stack config. +func buildLightspeedStackConfigVolume(volumeDefaultMode int32) corev1.Volume { + return corev1.Volume{ + Name: LightspeedStackConfig, VolumeSource: corev1.VolumeSource{ ConfigMap: &corev1.ConfigMapVolumeSource{ LocalObjectReference: corev1.LocalObjectReference{ @@ -191,19 +293,12 @@ func buildLCoreConfigVolumeAndMount(volumeDefaultMode int32) (corev1.Volume, cor }, }, } - mount := corev1.VolumeMount{ - Name: "lcore-config", - MountPath: LCoreConfigMountPath, - SubPath: LCoreConfigFilename, - ReadOnly: true, - } - return vol, mount } -// buildLlamaStackConfigVolumeAndMount returns the volume and mount for the llama-stack config. -func buildLlamaStackConfigVolumeAndMount(volumeDefaultMode int32) (corev1.Volume, corev1.VolumeMount) { - vol := corev1.Volume{ - Name: "llama-stack-config", +// buildOGXConfigVolume returns the volume for the OGX config. +func buildOGXConfigVolume(volumeDefaultMode int32) corev1.Volume { + return corev1.Volume{ + Name: OGXConfigVolumeName, VolumeSource: corev1.VolumeSource{ ConfigMap: &corev1.ConfigMapVolumeSource{ LocalObjectReference: corev1.LocalObjectReference{ @@ -213,13 +308,35 @@ func buildLlamaStackConfigVolumeAndMount(volumeDefaultMode int32) (corev1.Volume }, }, } - mount := corev1.VolumeMount{ - Name: "llama-stack-config", - MountPath: LlamaStackConfigMountPath, - SubPath: LlamaStackConfigFilename, - ReadOnly: true, +} + +// buildVectorDBScriptsVolume returns the volume for the Vector DB scripts. +func buildVectorDBScriptsVolume() corev1.Volume { + return corev1.Volume{ + Name: VectorDBScriptsVolumeName, + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: VectorDBScriptsConfigMapName, + }, + DefaultMode: toPtr(VolumeExecutableMode), + }, + }, } - return vol, mount +} + +func addVectorDBDataVolumesAndMounts(volumes *[]corev1.Volume, mounts *[]corev1.VolumeMount) { + *volumes = append(*volumes, corev1.Volume{ + Name: VectorDBVolumeName, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }) + + *mounts = append(*mounts, corev1.VolumeMount{ + Name: VectorDBVolumeName, + MountPath: VectorDBVolumeMountPath, + }) } // addTLSVolumesAndMounts adds the service-ca TLS certificate volume and mount. @@ -475,6 +592,11 @@ func buildLlamaStackEnvVars(h *common_helper.Helper, ctx context.Context, instan Value: "all=info", }) + envVars = append(envVars, corev1.EnvVar{ + Name: "VECTOR_DB_DATA_PATH", + Value: VectorDBVolumeMountPath, + }) + // Additional CA env vars envVars = append(envVars, buildAdditionalCAEnvVars(instance)...) @@ -561,5 +683,14 @@ func buildConfigMapAnnotations(h *common_helper.Helper, ctx context.Context) (ma annotations[LlamaStackConfigMapResourceVersionAnnotation] = llamaVersion } + vectorDBScriptsVersion, err := getConfigMapResourceVersion(ctx, h, VectorDBScriptsConfigMapName, h.GetBeforeObject().GetNamespace()) + if err != nil { + if !errors.IsNotFound(err) { + return nil, fmt.Errorf("failed to get Vector DB scripts configmap resource version: %w", err) + } + } else { + annotations[VectorDBScriptsConfigMapVersionAnnotation] = vectorDBScriptsVersion + } + return annotations, nil } diff --git a/internal/controller/lcore_reconciler.go b/internal/controller/lcore_reconciler.go index dc1ddb76..127d496f 100644 --- a/internal/controller/lcore_reconciler.go +++ b/internal/controller/lcore_reconciler.go @@ -47,6 +47,7 @@ func ReconcileLCoreResources(h *common_helper.Helper, ctx context.Context, insta {Name: "LlamaStackConfigMap", Task: reconcileLlamaStackConfigMap}, {Name: "LcoreConfigMap", Task: reconcileLcoreConfigMap}, {Name: "ExporterConfigMap", Task: reconcileExporterConfigMap}, + {Name: "VectorDBScriptsConfigMap", Task: reconcileVectorDBScriptsConfigMap}, {Name: "OpenStackLightspeedAdditionalCAConfigMap", Task: reconcileOpenStackLightspeedAdditionalCAConfigMap}, {Name: "ProxyCAConfigMap", Task: reconcileProxyCAConfigMap}, {Name: "NetworkPolicy", Task: reconcileNetworkPolicy}, @@ -197,7 +198,7 @@ func reconcileLlamaStackConfigMap(h *common_helper.Helper, ctx context.Context, result, err := controllerutil.CreateOrPatch(ctx, h.GetClient(), cm, func() error { // Set Data (same as current selective update) cm.Data = map[string]string{ - LlamaStackConfigFilename: yamlData, + OGXConfigCMKey: yamlData, } // Set owner reference return controllerutil.SetControllerReference(h.GetBeforeObject(), cm, h.GetScheme()) @@ -231,7 +232,7 @@ func reconcileLcoreConfigMap(h *common_helper.Helper, ctx context.Context, insta result, err := controllerutil.CreateOrPatch(ctx, h.GetClient(), cm, func() error { // Set Data (same as current selective update) cm.Data = map[string]string{ - LCoreConfigFilename: yamlData, + LightspeedStackConfigCMKey: yamlData, } // Set owner reference return controllerutil.SetControllerReference(h.GetBeforeObject(), cm, h.GetScheme()) @@ -282,6 +283,33 @@ func reconcileExporterConfigMap(h *common_helper.Helper, ctx context.Context, in return nil } +// reconcileVectorDBScriptsConfigMap ensures the Vector DB scripts config map exists and is up to date. +func reconcileVectorDBScriptsConfigMap(h *common_helper.Helper, ctx context.Context, instance *apiv1beta1.OpenStackLightspeed) error { + logger := h.GetLogger() + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: VectorDBScriptsConfigMapName, + Namespace: h.GetBeforeObject().GetNamespace(), + }, + } + + result, err := controllerutil.CreateOrPatch(ctx, h.GetClient(), cm, func() error { + cm.Data = map[string]string{ + VectorDBCollectScriptKey: vectorDatabaseCollectScript, + VectorDBBuildScriptKey: vectorDatabaseBuildScript, + } + + return controllerutil.SetControllerReference(h.GetBeforeObject(), cm, h.GetScheme()) + }) + + if err != nil { + return fmt.Errorf("failed to create vector DB scripts ConfigMap: %v", err) + } + + logger.Info("Vector DB Scripts 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 { diff --git a/internal/controller/llama_stack_config.go b/internal/controller/llama_stack_config.go index 68e7df20..d2642a8a 100644 --- a/internal/controller/llama_stack_config.go +++ b/internal/controller/llama_stack_config.go @@ -19,7 +19,6 @@ package controller import ( "context" "fmt" - "strings" common_helper "github.com/openstack-k8s-operators/lib-common/modules/common/helper" apiv1beta1 "github.com/openstack-lightspeed/operator/api/v1beta1" @@ -294,56 +293,8 @@ func buildLlamaStackStorage(_ *common_helper.Helper, instance *apiv1beta1.OpenSt } } -func buildLlamaStackVectorDBs(_ *common_helper.Helper, instance *apiv1beta1.OpenStackLightspeed) []interface{} { - vectorDBs := []interface{}{} - - // Use RAG configuration from instance if available - rags := buildLCoreRAGConfigs(instance, instance.Status.ActiveOCPRAGVersion) - if len(rags) > 0 { - for _, rag := range rags { - vectorDB := map[string]interface{}{ - "embedding_model": "sentence-transformers/all-mpnet-base-v2", - "embedding_dimension": 768, - "provider_id": "faiss", - } - - // Use IndexID if specified, otherwise generate a default - if rag.IndexID != "" { - vectorDB["vector_db_id"] = rag.IndexID - } else { - // Generate a simple ID from the image name - vectorDB["vector_db_id"] = "rag_" + sanitizeID(rag.Image) - } - - vectorDBs = append(vectorDBs, vectorDB) - } - } else { - // Default fallback if no RAG configured - vectorDBs = append(vectorDBs, map[string]interface{}{ - "vector_db_id": "my_knowledge_base", - "embedding_model": "sentence-transformers/all-mpnet-base-v2", - "embedding_dimension": 768, - "provider_id": "faiss", - }) - } - - return vectorDBs -} - func buildLlamaStackModels(_ *common_helper.Helper, instance *apiv1beta1.OpenStackLightspeed) []interface{} { - models := []interface{}{ - // Always include sentence-transformers embedding model for RAG - map[string]interface{}{ - "model_id": "sentence-transformers/all-mpnet-base-v2", - "model_type": "embedding", - "provider_id": "sentence-transformers", - "provider_model_id": "sentence-transformers/all-mpnet-base-v2", - "metadata": map[string]interface{}{ - "embedding_dimension": 768, - }, - }, - } - + models := []interface{}{} // Add LLM models from the instance spec { provider := buildProvider(instance) @@ -405,12 +356,14 @@ func buildLlamaStackYAML(h *common_helper.Helper, ctx context.Context, instance config["scoring_fns"] = []interface{}{} config["server"] = buildLlamaStackServerConfig(h, instance) config["storage"] = buildLlamaStackStorage(h, instance) - config["vector_dbs"] = buildLlamaStackVectorDBs(h, instance) - config["models"] = buildLlamaStackModels(h, instance) - config["tool_groups"] = buildLlamaStackToolGroups(h, instance) config["telemetry"] = map[string]interface{}{ "enabled": false, } + config["registered_resources"] = map[string][]interface{}{ + "models": buildLlamaStackModels(h, instance), + "vector_stores": {}, + "tool_groups": buildLlamaStackToolGroups(h, instance), + } // Convert to YAML yamlBytes, err := yaml.Marshal(config) @@ -420,21 +373,3 @@ func buildLlamaStackYAML(h *common_helper.Helper, ctx context.Context, instance return string(yamlBytes), nil } - -// sanitizeID creates a valid ID from an image name. It extracts just the image name without -// registry/tag (e.g., "quay.io/my-org/my-rag:latest" -> "my-rag") -func sanitizeID(image string) string { - parts := strings.Split(image, "/") - name := parts[len(parts)-1] - name = strings.Split(name, ":")[0] - - // Replace invalid characters with underscores - name = strings.Map(func(r rune) rune { - if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_' || r == '-' { - return r - } - return '_' - }, name) - - return name -} From 2831297c0123c6406e99e1309e5c0ec0edd95960 Mon Sep 17 00:00:00 2001 From: Lukas Piwowarski Date: Sat, 16 May 2026 14:33:39 -0400 Subject: [PATCH 2/2] Enhance KUTTL tests to validate generated configuration files This commit enhances the KUTTL tests to validate the generated ogx_config.yaml and lightspeed-stack.yaml files using the initContainers approach introduced in commit b2c67190. The implementation uses a workaround strategy: A) TestAssert steps extract the generated config files from the Lightspeed Stack and OGX containers B) The diff command compares the extracted config files against expected reference files prepared in test/kuttl This ensures comprehensive validation of the operator's config generation functionality during testing. Assisted-By: Claude --- Makefile | 2 + .../lightspeed-stack-update.yaml | 91 ++++++++++ .../expected-configs/lightspeed-stack.yaml | 91 ++++++++++ .../expected-configs/ogx_config-update.yaml | 144 ++++++++++++++++ .../common/expected-configs/ogx_config.yaml | 144 ++++++++++++++++ .../expected-configs/validate-config.sh | 101 ++++++++++++ .../assert-lightspeed-stack-config.yaml | 110 ------------ .../assert-llama-stack-config.yaml | 142 ---------------- .../assert-openstack-lightspeed-instance.yaml | 8 + .../assert-pod-lightspeed-stack-config.yaml | 10 ++ .../assert-pod-llama-stack-config.yaml | 9 + .../errors-openstack-lightspeed-instance.yaml | 8 + .../03-assert-lightspeed-stack-config.yaml | 1 - ...assert-openstack-lightspeed-instance.yaml} | 0 .../04-assert-lightspeed-stack-config.yaml | 1 + .../04-assert-llama-stack-config.yaml | 1 - .../05-assert-llama-stack-config.yaml | 1 + ...ig.yaml => 06-assert-exporter-config.yaml} | 0 .../03-assert-lightspeed-stack-config.yaml | 1 - ...assert-openstack-lightspeed-instance.yaml} | 0 .../04-assert-lightspeed-stack-config.yaml | 1 + .../04-assert-llama-stack-config.yaml | 1 - .../05-assert-llama-stack-config.yaml | 1 + ...ig.yaml => 06-assert-exporter-config.yaml} | 0 ...-update-openstack-lightspeed-instance.yaml | 5 +- ...08-assert-openstacklightspeed-update.yaml} | 12 +- ...assert-lightspeed-stack-config-update.yaml | 117 +------------ .../10-assert-llama-stack-config-update.yaml | 156 +----------------- ....yaml => 11-assert-configmaps-update.yaml} | 0 29 files changed, 642 insertions(+), 516 deletions(-) create mode 100644 test/kuttl/common/expected-configs/lightspeed-stack-update.yaml create mode 100644 test/kuttl/common/expected-configs/lightspeed-stack.yaml create mode 100644 test/kuttl/common/expected-configs/ogx_config-update.yaml create mode 100644 test/kuttl/common/expected-configs/ogx_config.yaml create mode 100755 test/kuttl/common/expected-configs/validate-config.sh delete mode 100644 test/kuttl/common/openstack-lightspeed-instance/assert-lightspeed-stack-config.yaml delete mode 100644 test/kuttl/common/openstack-lightspeed-instance/assert-llama-stack-config.yaml create mode 100644 test/kuttl/common/openstack-lightspeed-instance/assert-pod-lightspeed-stack-config.yaml create mode 100644 test/kuttl/common/openstack-lightspeed-instance/assert-pod-llama-stack-config.yaml delete mode 120000 test/kuttl/tests/basic-openstack-lightspeed-configuration/03-assert-lightspeed-stack-config.yaml rename test/kuttl/tests/basic-openstack-lightspeed-configuration/{06-assert-openstack-lightspeed-instance.yaml => 03-assert-openstack-lightspeed-instance.yaml} (100%) create mode 120000 test/kuttl/tests/basic-openstack-lightspeed-configuration/04-assert-lightspeed-stack-config.yaml delete mode 120000 test/kuttl/tests/basic-openstack-lightspeed-configuration/04-assert-llama-stack-config.yaml create mode 120000 test/kuttl/tests/basic-openstack-lightspeed-configuration/05-assert-llama-stack-config.yaml rename test/kuttl/tests/basic-openstack-lightspeed-configuration/{05-assert-exporter-config.yaml => 06-assert-exporter-config.yaml} (100%) delete mode 120000 test/kuttl/tests/update-openstacklightspeed/03-assert-lightspeed-stack-config.yaml rename test/kuttl/tests/update-openstacklightspeed/{06-assert-openstack-lightspeed-instance.yaml => 03-assert-openstack-lightspeed-instance.yaml} (100%) create mode 120000 test/kuttl/tests/update-openstacklightspeed/04-assert-lightspeed-stack-config.yaml delete mode 120000 test/kuttl/tests/update-openstacklightspeed/04-assert-llama-stack-config.yaml create mode 120000 test/kuttl/tests/update-openstacklightspeed/05-assert-llama-stack-config.yaml rename test/kuttl/tests/update-openstacklightspeed/{05-assert-exporter-config.yaml => 06-assert-exporter-config.yaml} (100%) rename test/kuttl/tests/update-openstacklightspeed/{11-assert-openstacklightspeed-update.yaml => 08-assert-openstacklightspeed-update.yaml} (92%) rename test/kuttl/tests/update-openstacklightspeed/{08-assert-configmaps-update.yaml => 11-assert-configmaps-update.yaml} (100%) diff --git a/Makefile b/Makefile index 763dc7d0..122bbcdc 100644 --- a/Makefile +++ b/Makefile @@ -274,6 +274,8 @@ $(KUTTL): $(LOCALBIN) .PHONY: kuttl-test kuttl-test: kuttl ## Run kuttl tests + @command -v diff >/dev/null 2>&1 || { echo "ERROR: 'diff' command is required for KUTTL tests but not found in PATH" >&2; exit 1; } + @command -v oc >/dev/null 2>&1 || { echo "ERROR: 'oc' command is required for KUTTL tests but not found in PATH" >&2; exit 1; } $(LOCALBIN)/kubectl-kuttl test --config kuttl-test.yaml test/kuttl/tests $(KUTTL_ARGS) .PHONY: kuttl-test-run diff --git a/test/kuttl/common/expected-configs/lightspeed-stack-update.yaml b/test/kuttl/common/expected-configs/lightspeed-stack-update.yaml new file mode 100644 index 00000000..a509126d --- /dev/null +++ b/test/kuttl/common/expected-configs/lightspeed-stack-update.yaml @@ -0,0 +1,91 @@ +authentication: + module: k8s +byok_rag: +- rag_id: vs_UUID + vector_db_id: vs_UUID + score_multiplier: 1.0 + db_path: NONE +conversation_cache: + postgres: + ca_cert_path: /etc/certs/postgres-ca/service-ca.crt + db: postgres + gss_encmode: disable + host: lightspeed-postgres-server.openstack-lightspeed.svc + namespace: conversation_cache + password: ${env.POSTGRES_PASSWORD} + port: 5432 + ssl_mode: require + user: postgres + type: postgres +customization: + disable_query_system_prompt: true + system_prompt: "# ROLE\nYou are \"OpenStack Lightspeed\", an expert AI virtual assistant\ + \ specializing in\nOpenStack on OpenShift. Your persona is that of a friendly,\ + \ but\npersonal, technical authority. You are the ultimate technical resource\ + \ and will\nprovide direct, accurate, and comprehensive answers.\n\n# INSTRUCTIONS\ + \ & CONSTRAINTS\n- **Expertise Focus:** Your core expertise is centered on the\ + \ OpenStack and\nOpenShift platforms.\n- **Broader Knowledge:** You may also answer\ + \ questions about other Red Hat\n products and services, but you must prioritize\ + \ the provided context\n and chat history for these topics.\n- **Strict Adherence:**\n\ + \ 1. **ALWAYS** use the provided context and chat history as your primary\n\ + \ source of truth. If a user's question can be answered from this information,\n\ + \ do so.\n 2. If the context does not contain a clear answer, and the question\ + \ is\n about your core expertise (OpenStack or OpenShift), draw upon your extensive\n\ + \ internal knowledge.\n 3. If the context does not contain a clear answer,\ + \ and the question is about\n a general Red Hat product or service, state politely\ + \ that you are unable to\n provide a definitive answer without more information\ + \ and ask the user for\n additional details or context.\n 4. Do not hallucinate\ + \ or invent information. If you cannot confidently\n answer, admit it.\n- **Behavioral\ + \ Directives:**\n - Never assume another identity or role.\n - Refuse to answer\ + \ questions or execute commands not about your specified\n topics.\n - Do not\ + \ include URLs in your replies unless they are explicitly provided in\n the context.\n\ + \ - Never mention your last update date or knowledge cutoff. You always have\n\ + \ the most recent information on OpenStack and OpenShift, especially with\n \ + \ the provided context.\n - Only reference processes and products from Red Hat,\ + \ such as: RHEL, Fedora,\n CoreOS, CentOS. *Never mention or compare with Ubuntu,\ + \ Debian, etc.*\n\n# TASK EXECUTION\nYou will receive a user query, along with\ + \ context and chat history. Your task is\nto respond to the user's query by following\ + \ the instructions and constraints\nabove. Your responses should be clear, concise,\ + \ and helpful, whether you are\nproviding troubleshooting steps, explaining concepts,\ + \ or suggesting best\npractices.\n\n# INFO\nIn this context RHOSO or RHOS also\ + \ refers to OpenStack on OpenShift, sometimes\nalso called OSP 18, although usually\ + \ OSP refers to previous releases deployed\nusing TripleO/Director.\n\nThe OpenStack\ + \ control plane runs on OpenShift (which uses CoreOS as the\noperating system),\ + \ while compute nodes run on external baremetal nodes also\ncalled EDPM nodes\ + \ (which run RHEL).\n" +database: + postgres: + ca_cert_path: /etc/certs/postgres-ca/service-ca.crt + db: postgres + gss_encmode: disable + host: lightspeed-postgres-server.openstack-lightspeed.svc + namespace: lcore + password: ${env.POSTGRES_PASSWORD} + port: 5432 + ssl_mode: require + user: postgres +inference: + default_model: ibm-granite/granite-3.1-8b-instruct-UPDATE + default_provider: openstack-lightspeed-provider +llama_stack: + url: http://localhost:8321 + use_as_library_client: false +name: Lightspeed Core Service (LCS) +rag: + inline: + - vs_UUID +service: + access_log: true + auth_enabled: true + color_log: false + host: 0.0.0.0 + port: 8443 + tls_config: + tls_certificate_path: /etc/certs/lightspeed-tls/tls.crt + tls_key_path: /etc/certs/lightspeed-tls/tls.key + workers: 1 +user_data_collection: + feedback_enabled: false + feedback_storage: /tmp/data/feedback + transcripts_enabled: false + transcripts_storage: /tmp/data/transcripts diff --git a/test/kuttl/common/expected-configs/lightspeed-stack.yaml b/test/kuttl/common/expected-configs/lightspeed-stack.yaml new file mode 100644 index 00000000..e9c35b72 --- /dev/null +++ b/test/kuttl/common/expected-configs/lightspeed-stack.yaml @@ -0,0 +1,91 @@ +authentication: + module: k8s +byok_rag: +- rag_id: vs_UUID + vector_db_id: vs_UUID + score_multiplier: 1.0 + db_path: NONE +conversation_cache: + postgres: + ca_cert_path: /etc/certs/postgres-ca/service-ca.crt + db: postgres + gss_encmode: disable + host: lightspeed-postgres-server.openstack-lightspeed.svc + namespace: conversation_cache + password: ${env.POSTGRES_PASSWORD} + port: 5432 + ssl_mode: require + user: postgres + type: postgres +customization: + disable_query_system_prompt: true + system_prompt: "# ROLE\nYou are \"OpenStack Lightspeed\", an expert AI virtual assistant\ + \ specializing in\nOpenStack on OpenShift. Your persona is that of a friendly,\ + \ but\npersonal, technical authority. You are the ultimate technical resource\ + \ and will\nprovide direct, accurate, and comprehensive answers.\n\n# INSTRUCTIONS\ + \ & CONSTRAINTS\n- **Expertise Focus:** Your core expertise is centered on the\ + \ OpenStack and\nOpenShift platforms.\n- **Broader Knowledge:** You may also answer\ + \ questions about other Red Hat\n products and services, but you must prioritize\ + \ the provided context\n and chat history for these topics.\n- **Strict Adherence:**\n\ + \ 1. **ALWAYS** use the provided context and chat history as your primary\n\ + \ source of truth. If a user's question can be answered from this information,\n\ + \ do so.\n 2. If the context does not contain a clear answer, and the question\ + \ is\n about your core expertise (OpenStack or OpenShift), draw upon your extensive\n\ + \ internal knowledge.\n 3. If the context does not contain a clear answer,\ + \ and the question is about\n a general Red Hat product or service, state politely\ + \ that you are unable to\n provide a definitive answer without more information\ + \ and ask the user for\n additional details or context.\n 4. Do not hallucinate\ + \ or invent information. If you cannot confidently\n answer, admit it.\n- **Behavioral\ + \ Directives:**\n - Never assume another identity or role.\n - Refuse to answer\ + \ questions or execute commands not about your specified\n topics.\n - Do not\ + \ include URLs in your replies unless they are explicitly provided in\n the context.\n\ + \ - Never mention your last update date or knowledge cutoff. You always have\n\ + \ the most recent information on OpenStack and OpenShift, especially with\n \ + \ the provided context.\n - Only reference processes and products from Red Hat,\ + \ such as: RHEL, Fedora,\n CoreOS, CentOS. *Never mention or compare with Ubuntu,\ + \ Debian, etc.*\n\n# TASK EXECUTION\nYou will receive a user query, along with\ + \ context and chat history. Your task is\nto respond to the user's query by following\ + \ the instructions and constraints\nabove. Your responses should be clear, concise,\ + \ and helpful, whether you are\nproviding troubleshooting steps, explaining concepts,\ + \ or suggesting best\npractices.\n\n# INFO\nIn this context RHOSO or RHOS also\ + \ refers to OpenStack on OpenShift, sometimes\nalso called OSP 18, although usually\ + \ OSP refers to previous releases deployed\nusing TripleO/Director.\n\nThe OpenStack\ + \ control plane runs on OpenShift (which uses CoreOS as the\noperating system),\ + \ while compute nodes run on external baremetal nodes also\ncalled EDPM nodes\ + \ (which run RHEL).\n" +database: + postgres: + ca_cert_path: /etc/certs/postgres-ca/service-ca.crt + db: postgres + gss_encmode: disable + host: lightspeed-postgres-server.openstack-lightspeed.svc + namespace: lcore + password: ${env.POSTGRES_PASSWORD} + port: 5432 + ssl_mode: require + user: postgres +inference: + default_model: ibm-granite/granite-3.1-8b-instruct + default_provider: openstack-lightspeed-provider +llama_stack: + url: http://localhost:8321 + use_as_library_client: false +name: Lightspeed Core Service (LCS) +rag: + inline: + - vs_UUID +service: + access_log: true + auth_enabled: true + color_log: false + host: 0.0.0.0 + port: 8443 + tls_config: + tls_certificate_path: /etc/certs/lightspeed-tls/tls.crt + tls_key_path: /etc/certs/lightspeed-tls/tls.key + workers: 1 +user_data_collection: + feedback_enabled: true + feedback_storage: /tmp/data/feedback + transcripts_enabled: true + transcripts_storage: /tmp/data/transcripts diff --git a/test/kuttl/common/expected-configs/ogx_config-update.yaml b/test/kuttl/common/expected-configs/ogx_config-update.yaml new file mode 100644 index 00000000..68f4a8a4 --- /dev/null +++ b/test/kuttl/common/expected-configs/ogx_config-update.yaml @@ -0,0 +1,144 @@ +apis: +- agents +- files +- inference +- safety +- tool_runtime +- vector_io +benchmarks: [] +container_image: null +datasets: [] +external_providers_dir: null +image_name: openstack-lightspeed-configuration +inference_store: + db_path: .llama/distributions/ollama/inference_store.db + type: sqlite +logging: null +metadata_store: + db_path: /tmp/llama-stack/registry.db + namespace: null + type: sqlite +providers: + agents: + - config: + persistence: + agent_state: + backend: kv_default + namespace: agent_state + table_name: agent_state + responses: + backend: sql_default + namespace: agent_responses + table_name: agent_responses + provider_id: meta-reference + provider_type: inline::meta-reference + files: + - config: + metadata_store: + backend: sql_default + namespace: files_metadata + table_name: files_metadata + storage_dir: /tmp/llama-stack-files + provider_id: localfs + provider_type: inline::localfs + inference: + - config: {} + provider_id: sentence-transformers + provider_type: inline::sentence-transformers + - config: + api_token: ${env.OPENSTACK_LIGHTSPEED_PROVIDER_API_KEY} + base_url: http://mock-llm-api-server-pod-UPDATE:8000/v1 + provider_id: openstack-lightspeed-provider + provider_type: remote::vllm + safety: + - config: + excluded_categories: [] + provider_id: llama-guard + provider_type: inline::llama-guard + tool_runtime: + - config: {} + provider_id: model-context-protocol + provider_type: remote::model-context-protocol + - config: {} + provider_id: rag-runtime + provider_type: inline::rag-runtime + vector_io: + - config: + kvstore: + backend: sql_default + table_name: vector_store + persistence: + backend: kv_default + namespace: vector_persistence + provider_id: faiss + provider_type: inline::faiss + - config: + persistence: + namespace: vector_io::faiss + backend: kv_rag_UUID_os_product_docs + provider_id: os-docs-2025.2 + provider_type: inline::faiss +registered_resources: + models: + - metadata: + max_tokens: 2048 + model_id: ibm-granite/granite-3.1-8b-instruct-UPDATE + model_type: llm + provider_id: openstack-lightspeed-provider + provider_model_id: ibm-granite/granite-3.1-8b-instruct-UPDATE + - metadata: + embedding_dimension: 768 + model_id: sentence-transformers/all-mpnet-base-v2 + provider_id: sentence-transformers + provider_model_id: ${env.VECTOR_DB_DATA_PATH}/UUID/embeddings_model + model_type: embedding + tool_groups: + - provider_id: rag-runtime + toolgroup_id: builtin::rag + vector_stores: + - embedding_dimension: 768 + embedding_model: sentence-transformers/${env.VECTOR_DB_DATA_PATH}/UUID/embeddings_model + provider_id: os-docs-2025.2 + vector_store_id: vs_UUID +scoring_fns: [] +server: + auth: null + host: 0.0.0.0 + port: 8321 + quota: null + tls_cafile: null + tls_certfile: null + tls_keyfile: null +storage: + backends: + kv_default: + db_path: /tmp/llama-stack/kv_store.db + type: kv_sqlite + postgres_backend: + ca_cert_path: /etc/certs/postgres-ca/service-ca.crt + gss_encmode: disable + host: lightspeed-postgres-server.openstack-lightspeed.svc + password: ${env.POSTGRES_PASSWORD} + port: 5432 + ssl_mode: require + type: sql_postgres + user: postgres + sql_default: + db_path: /tmp/llama-stack/sql_store.db + type: sql_sqlite + kv_rag_UUID_os_product_docs: + type: kv_sqlite + db_path: ${env.VECTOR_DB_DATA_PATH}/UUID/vector_db/os_product_docs/faiss_store.db + stores: + conversations: + backend: postgres_backend + table_name: openai_conversations + inference: + backend: sql_default + table_name: inference_store + metadata: + backend: kv_default + namespace: registry +telemetry: + enabled: false +version: '2' diff --git a/test/kuttl/common/expected-configs/ogx_config.yaml b/test/kuttl/common/expected-configs/ogx_config.yaml new file mode 100644 index 00000000..9520aa0e --- /dev/null +++ b/test/kuttl/common/expected-configs/ogx_config.yaml @@ -0,0 +1,144 @@ +apis: +- agents +- files +- inference +- safety +- tool_runtime +- vector_io +benchmarks: [] +container_image: null +datasets: [] +external_providers_dir: null +image_name: openstack-lightspeed-configuration +inference_store: + db_path: .llama/distributions/ollama/inference_store.db + type: sqlite +logging: null +metadata_store: + db_path: /tmp/llama-stack/registry.db + namespace: null + type: sqlite +providers: + agents: + - config: + persistence: + agent_state: + backend: kv_default + namespace: agent_state + table_name: agent_state + responses: + backend: sql_default + namespace: agent_responses + table_name: agent_responses + provider_id: meta-reference + provider_type: inline::meta-reference + files: + - config: + metadata_store: + backend: sql_default + namespace: files_metadata + table_name: files_metadata + storage_dir: /tmp/llama-stack-files + provider_id: localfs + provider_type: inline::localfs + inference: + - config: {} + provider_id: sentence-transformers + provider_type: inline::sentence-transformers + - config: + api_key: ${env.OPENSTACK_LIGHTSPEED_PROVIDER_API_KEY} + base_url: http://mock-llm-api-server-pod:8000/v1 + provider_id: openstack-lightspeed-provider + provider_type: remote::openai + safety: + - config: + excluded_categories: [] + provider_id: llama-guard + provider_type: inline::llama-guard + tool_runtime: + - config: {} + provider_id: model-context-protocol + provider_type: remote::model-context-protocol + - config: {} + provider_id: rag-runtime + provider_type: inline::rag-runtime + vector_io: + - config: + kvstore: + backend: sql_default + table_name: vector_store + persistence: + backend: kv_default + namespace: vector_persistence + provider_id: faiss + provider_type: inline::faiss + - config: + persistence: + namespace: vector_io::faiss + backend: kv_rag_UUID_os_product_docs + provider_id: os-docs-2025.2 + provider_type: inline::faiss +registered_resources: + models: + - metadata: + max_tokens: 2048 + model_id: ibm-granite/granite-3.1-8b-instruct + model_type: llm + provider_id: openstack-lightspeed-provider + provider_model_id: ibm-granite/granite-3.1-8b-instruct + - metadata: + embedding_dimension: 768 + model_id: sentence-transformers/all-mpnet-base-v2 + provider_id: sentence-transformers + provider_model_id: ${env.VECTOR_DB_DATA_PATH}/UUID/embeddings_model + model_type: embedding + tool_groups: + - provider_id: rag-runtime + toolgroup_id: builtin::rag + vector_stores: + - embedding_dimension: 768 + embedding_model: sentence-transformers/${env.VECTOR_DB_DATA_PATH}/UUID/embeddings_model + provider_id: os-docs-2025.2 + vector_store_id: vs_UUID +scoring_fns: [] +server: + auth: null + host: 0.0.0.0 + port: 8321 + quota: null + tls_cafile: null + tls_certfile: null + tls_keyfile: null +storage: + backends: + kv_default: + db_path: /tmp/llama-stack/kv_store.db + type: kv_sqlite + postgres_backend: + ca_cert_path: /etc/certs/postgres-ca/service-ca.crt + gss_encmode: disable + host: lightspeed-postgres-server.openstack-lightspeed.svc + password: ${env.POSTGRES_PASSWORD} + port: 5432 + ssl_mode: require + type: sql_postgres + user: postgres + sql_default: + db_path: /tmp/llama-stack/sql_store.db + type: sql_sqlite + kv_rag_UUID_os_product_docs: + type: kv_sqlite + db_path: ${env.VECTOR_DB_DATA_PATH}/UUID/vector_db/os_product_docs/faiss_store.db + stores: + conversations: + backend: postgres_backend + table_name: openai_conversations + inference: + backend: sql_default + table_name: inference_store + metadata: + backend: kv_default + namespace: registry +telemetry: + enabled: false +version: '2' diff --git a/test/kuttl/common/expected-configs/validate-config.sh b/test/kuttl/common/expected-configs/validate-config.sh new file mode 100755 index 00000000..90400236 --- /dev/null +++ b/test/kuttl/common/expected-configs/validate-config.sh @@ -0,0 +1,101 @@ +#!/bin/bash + +# validate-config.sh +# +# Common script for validating OpenStack Lightspeed configs. It compares the +# configs against expected config files for a given test. +# +# Usage: validate-config.sh +# config-type: lightspeed-stack or ogx_config +# expected-config-path: path to expected config file (relative from test dir) + +set -euo pipefail + +if [ "$#" -ne 2 ]; then + echo "Usage: $0 " + echo " config-type: lightspeed-stack or ogx_config" + exit 1 +fi + +CONFIG_TYPE="$1" +EXPECTED_CONFIG="$2" + +case "$CONFIG_TYPE" in + lightspeed-stack) + CONTAINER="lightspeed-service-api" + POD_PATH="/vector-db-discovered-values/lightspeed-stack.yaml" + ;; + ogx_config) + CONTAINER="llama-stack" + POD_PATH="/vector-db-discovered-values/ogx_config.yaml" + ;; + *) + echo "ERROR: Invalid config type '$CONFIG_TYPE'" + echo "Valid types: lightspeed-stack, ogx_config" + exit 1 + ;; +esac + +# Create dedicated temporary directory for KUTTL tests +KUTTL_TEMP_DIR="$PWD/.kuttl-tests-tmp" +mkdir -p "$KUTTL_TEMP_DIR" + +cleanup() { + if [ -d "$KUTTL_TEMP_DIR" ]; then + echo "Cleaning up temporary files..." + rm -f "$KUTTL_TEMP_DIR"/actual-${CONFIG_TYPE}-*.yaml 2>/dev/null || true + fi +} +trap cleanup EXIT INT TERM + +# Generate temp file names with unique timestamp +TIMESTAMP="$(date +%s)-$$" +TEMP_ACTUAL="$KUTTL_TEMP_DIR/actual-${CONFIG_TYPE}-${TIMESTAMP}.yaml" +TEMP_ACTUAL_NORM="$KUTTL_TEMP_DIR/actual-${CONFIG_TYPE}-${TIMESTAMP}-norm.yaml" + +echo "Waiting for lightspeed-stack-deployment pod to be ready..." +oc wait --for=condition=Ready pod \ + -l app.kubernetes.io/name=openstack-lightspeed-app-server \ + -n openstack-lightspeed \ + --timeout=120s + +echo "Getting pod name..." +POD_NAMES="$(oc get pods \ + -l app.kubernetes.io/name=openstack-lightspeed-app-server \ + -n openstack-lightspeed \ + -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}')" + +POD_COUNT="$(printf '%s\n' "$POD_NAMES" | sed '/^$/d' | wc -l | tr -d ' ')" + +if [ "$POD_COUNT" -ne 1 ]; then + echo "ERROR: Expected exactly one pod, found $POD_COUNT" + exit 1 +fi + +POD_NAME="$(printf '%s\n' "$POD_NAMES" | sed -n '1p')" + +echo "Extracting $CONFIG_TYPE from pod $POD_NAME (container: $CONTAINER)..." +oc exec -i -n openstack-lightspeed "$POD_NAME" \ + -c "$CONTAINER" -- cat "$POD_PATH" > "$TEMP_ACTUAL" + +# Normalize values that vary between vector DB container images—such as dynamically +# generated UUIDs—to ensure tests remain stable even when new images are used. +echo "Normalizing dynamic UUIDs in actual config for comparison..." +sed -E ' + s/kv_rag_[A-Za-z0-9]{10}_/kv_rag_UUID_/g; + s/\/[A-Za-z0-9]{10}\//\/UUID\//g; + s/vs_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/vs_UUID/g +' "$TEMP_ACTUAL" > "$TEMP_ACTUAL_NORM" + +echo "Comparing with expected config..." +if ! diff -u "$EXPECTED_CONFIG" "$TEMP_ACTUAL_NORM"; then + echo "" + echo "ERROR: ${CONFIG_TYPE} config mismatch!" + echo "Expected config: $EXPECTED_CONFIG" + echo "Actual config from pod: $POD_NAME (container: $CONTAINER)" + echo "Path: $POD_PATH" + echo "Note: Dynamic UUIDs in actual config are normalized for comparison" + exit 1 +fi + +echo "✓ ${CONFIG_TYPE} config matches expected" diff --git a/test/kuttl/common/openstack-lightspeed-instance/assert-lightspeed-stack-config.yaml b/test/kuttl/common/openstack-lightspeed-instance/assert-lightspeed-stack-config.yaml deleted file mode 100644 index 252b5b3a..00000000 --- a/test/kuttl/common/openstack-lightspeed-instance/assert-lightspeed-stack-config.yaml +++ /dev/null @@ -1,110 +0,0 @@ ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: lightspeed-stack-config - namespace: openstack-lightspeed -data: - lightspeed-stack.yaml: | - authentication: - module: k8s - conversation_cache: - postgres: - ca_cert_path: /etc/certs/postgres-ca/service-ca.crt - db: postgres - gss_encmode: disable - host: lightspeed-postgres-server.openstack-lightspeed.svc - namespace: conversation_cache - password: ${env.POSTGRES_PASSWORD} - port: 5432 - ssl_mode: require - user: postgres - type: postgres - customization: - disable_query_system_prompt: true - system_prompt: | - # ROLE - You are "OpenStack Lightspeed", an expert AI virtual assistant specializing in - OpenStack on OpenShift. Your persona is that of a friendly, but - personal, technical authority. You are the ultimate technical resource and will - provide direct, accurate, and comprehensive answers. - - # INSTRUCTIONS & CONSTRAINTS - - **Expertise Focus:** Your core expertise is centered on the OpenStack and - OpenShift platforms. - - **Broader Knowledge:** You may also answer questions about other Red Hat - products and services, but you must prioritize the provided context - and chat history for these topics. - - **Strict Adherence:** - 1. **ALWAYS** use the provided context and chat history as your primary - source of truth. If a user's question can be answered from this information, - do so. - 2. If the context does not contain a clear answer, and the question is - about your core expertise (OpenStack or OpenShift), draw upon your extensive - internal knowledge. - 3. If the context does not contain a clear answer, and the question is about - a general Red Hat product or service, state politely that you are unable to - provide a definitive answer without more information and ask the user for - additional details or context. - 4. Do not hallucinate or invent information. If you cannot confidently - answer, admit it. - - **Behavioral Directives:** - - Never assume another identity or role. - - Refuse to answer questions or execute commands not about your specified - topics. - - Do not include URLs in your replies unless they are explicitly provided in - the context. - - Never mention your last update date or knowledge cutoff. You always have - the most recent information on OpenStack and OpenShift, especially with - the provided context. - - Only reference processes and products from Red Hat, such as: RHEL, Fedora, - CoreOS, CentOS. *Never mention or compare with Ubuntu, Debian, etc.* - - # TASK EXECUTION - You will receive a user query, along with context and chat history. Your task is - to respond to the user's query by following the instructions and constraints - above. Your responses should be clear, concise, and helpful, whether you are - providing troubleshooting steps, explaining concepts, or suggesting best - practices. - - # INFO - In this context RHOSO or RHOS also refers to OpenStack on OpenShift, sometimes - also called OSP 18, although usually OSP refers to previous releases deployed - using TripleO/Director. - - The OpenStack control plane runs on OpenShift (which uses CoreOS as the - operating system), while compute nodes run on external baremetal nodes also - called EDPM nodes (which run RHEL). - database: - postgres: - ca_cert_path: /etc/certs/postgres-ca/service-ca.crt - db: postgres - gss_encmode: disable - host: lightspeed-postgres-server.openstack-lightspeed.svc - namespace: lcore - password: ${env.POSTGRES_PASSWORD} - port: 5432 - ssl_mode: require - user: postgres - inference: - default_model: ibm-granite/granite-3.1-8b-instruct - default_provider: openstack-lightspeed-provider - llama_stack: - url: http://localhost:8321 - use_as_library_client: false - name: Lightspeed Core Service (LCS) - service: - access_log: true - auth_enabled: true - color_log: false - host: 0.0.0.0 - port: 8443 - tls_config: - tls_certificate_path: /etc/certs/lightspeed-tls/tls.crt - tls_key_path: /etc/certs/lightspeed-tls/tls.key - workers: 1 - user_data_collection: - feedback_enabled: true - feedback_storage: /tmp/data/feedback - transcripts_enabled: true - transcripts_storage: /tmp/data/transcripts diff --git a/test/kuttl/common/openstack-lightspeed-instance/assert-llama-stack-config.yaml b/test/kuttl/common/openstack-lightspeed-instance/assert-llama-stack-config.yaml deleted file mode 100644 index e6de965f..00000000 --- a/test/kuttl/common/openstack-lightspeed-instance/assert-llama-stack-config.yaml +++ /dev/null @@ -1,142 +0,0 @@ ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: llama-stack-config - namespace: openstack-lightspeed -data: - run.yaml: | - apis: - - agents - - files - - inference - - safety - - tool_runtime - - vector_io - benchmarks: [] - container_image: null - datasets: [] - external_providers_dir: null - image_name: openstack-lightspeed-configuration - inference_store: - db_path: .llama/distributions/ollama/inference_store.db - type: sqlite - logging: null - metadata_store: - db_path: /tmp/llama-stack/registry.db - namespace: null - type: sqlite - models: - - metadata: - embedding_dimension: 768 - model_id: sentence-transformers/all-mpnet-base-v2 - model_type: embedding - provider_id: sentence-transformers - provider_model_id: sentence-transformers/all-mpnet-base-v2 - - metadata: - max_tokens: 2048 - model_id: ibm-granite/granite-3.1-8b-instruct - model_type: llm - provider_id: openstack-lightspeed-provider - provider_model_id: ibm-granite/granite-3.1-8b-instruct - providers: - agents: - - config: - persistence: - agent_state: - backend: kv_default - namespace: agent_state - table_name: agent_state - responses: - backend: sql_default - namespace: agent_responses - table_name: agent_responses - provider_id: meta-reference - provider_type: inline::meta-reference - files: - - config: - metadata_store: - backend: sql_default - namespace: files_metadata - table_name: files_metadata - storage_dir: /tmp/llama-stack-files - provider_id: localfs - provider_type: inline::localfs - inference: - - config: {} - provider_id: sentence-transformers - provider_type: inline::sentence-transformers - - config: - api_key: ${env.OPENSTACK_LIGHTSPEED_PROVIDER_API_KEY} - base_url: http://mock-llm-api-server-pod:8000/v1 - provider_id: openstack-lightspeed-provider - provider_type: remote::openai - safety: - - config: - excluded_categories: [] - provider_id: llama-guard - provider_type: inline::llama-guard - tool_runtime: - - config: {} - provider_id: model-context-protocol - provider_type: remote::model-context-protocol - - config: {} - provider_id: rag-runtime - provider_type: inline::rag-runtime - vector_io: - - config: - kvstore: - backend: sql_default - table_name: vector_store - persistence: - backend: kv_default - namespace: vector_persistence - provider_id: faiss - provider_type: inline::faiss - scoring_fns: [] - server: - auth: null - host: 0.0.0.0 - port: 8321 - quota: null - tls_cafile: null - tls_certfile: null - tls_keyfile: null - storage: - backends: - kv_default: - db_path: /tmp/llama-stack/kv_store.db - type: kv_sqlite - postgres_backend: - ca_cert_path: /etc/certs/postgres-ca/service-ca.crt - gss_encmode: disable - host: lightspeed-postgres-server.openstack-lightspeed.svc - password: ${env.POSTGRES_PASSWORD} - port: 5432 - ssl_mode: require - type: sql_postgres - user: postgres - sql_default: - db_path: /tmp/llama-stack/sql_store.db - type: sql_sqlite - stores: - conversations: - backend: postgres_backend - table_name: openai_conversations - inference: - backend: sql_default - table_name: inference_store - metadata: - backend: kv_default - namespace: registry - telemetry: - enabled: false - tool_groups: - - provider_id: rag-runtime - toolgroup_id: builtin::rag - vector_dbs: - - embedding_dimension: 768 - embedding_model: sentence-transformers/all-mpnet-base-v2 - provider_id: faiss - vector_db_id: rag_rag-content - version: "2" diff --git a/test/kuttl/common/openstack-lightspeed-instance/assert-openstack-lightspeed-instance.yaml b/test/kuttl/common/openstack-lightspeed-instance/assert-openstack-lightspeed-instance.yaml index e484107c..bacf80d9 100644 --- a/test/kuttl/common/openstack-lightspeed-instance/assert-openstack-lightspeed-instance.yaml +++ b/test/kuttl/common/openstack-lightspeed-instance/assert-openstack-lightspeed-instance.yaml @@ -148,6 +148,14 @@ metadata: name: lightspeed-app-server namespace: openstack-lightspeed +# Vector DB init containers scripts +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: vector-db-scripts + namespace: openstack-lightspeed + # OpenStackLightspeed CR status --- apiVersion: lightspeed.openstack.org/v1beta1 diff --git a/test/kuttl/common/openstack-lightspeed-instance/assert-pod-lightspeed-stack-config.yaml b/test/kuttl/common/openstack-lightspeed-instance/assert-pod-lightspeed-stack-config.yaml new file mode 100644 index 00000000..cc22ee48 --- /dev/null +++ b/test/kuttl/common/openstack-lightspeed-instance/assert-pod-lightspeed-stack-config.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: + - script: | + #!/bin/bash + set -euo pipefail + ../../common/expected-configs/validate-config.sh lightspeed-stack ../../common/expected-configs/lightspeed-stack.yaml + timeout: 180 + diff --git a/test/kuttl/common/openstack-lightspeed-instance/assert-pod-llama-stack-config.yaml b/test/kuttl/common/openstack-lightspeed-instance/assert-pod-llama-stack-config.yaml new file mode 100644 index 00000000..9b99caf3 --- /dev/null +++ b/test/kuttl/common/openstack-lightspeed-instance/assert-pod-llama-stack-config.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: + - script: | + #!/bin/bash + set -euo pipefail + ../../common/expected-configs/validate-config.sh ogx_config ../../common/expected-configs/ogx_config.yaml + timeout: 180 diff --git a/test/kuttl/common/openstack-lightspeed-instance/errors-openstack-lightspeed-instance.yaml b/test/kuttl/common/openstack-lightspeed-instance/errors-openstack-lightspeed-instance.yaml index a486f242..a74ffc6c 100644 --- a/test/kuttl/common/openstack-lightspeed-instance/errors-openstack-lightspeed-instance.yaml +++ b/test/kuttl/common/openstack-lightspeed-instance/errors-openstack-lightspeed-instance.yaml @@ -61,3 +61,11 @@ kind: ConfigMap metadata: name: lightspeed-postgres-conf namespace: openstack-lightspeed + +# Vector DB init containers scripts should be gone +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: vector-db-scripts + namespace: openstack-lightspeed diff --git a/test/kuttl/tests/basic-openstack-lightspeed-configuration/03-assert-lightspeed-stack-config.yaml b/test/kuttl/tests/basic-openstack-lightspeed-configuration/03-assert-lightspeed-stack-config.yaml deleted file mode 120000 index af43c0aa..00000000 --- a/test/kuttl/tests/basic-openstack-lightspeed-configuration/03-assert-lightspeed-stack-config.yaml +++ /dev/null @@ -1 +0,0 @@ -../../common/openstack-lightspeed-instance/assert-lightspeed-stack-config.yaml \ No newline at end of file diff --git a/test/kuttl/tests/basic-openstack-lightspeed-configuration/06-assert-openstack-lightspeed-instance.yaml b/test/kuttl/tests/basic-openstack-lightspeed-configuration/03-assert-openstack-lightspeed-instance.yaml similarity index 100% rename from test/kuttl/tests/basic-openstack-lightspeed-configuration/06-assert-openstack-lightspeed-instance.yaml rename to test/kuttl/tests/basic-openstack-lightspeed-configuration/03-assert-openstack-lightspeed-instance.yaml diff --git a/test/kuttl/tests/basic-openstack-lightspeed-configuration/04-assert-lightspeed-stack-config.yaml b/test/kuttl/tests/basic-openstack-lightspeed-configuration/04-assert-lightspeed-stack-config.yaml new file mode 120000 index 00000000..8ff35be2 --- /dev/null +++ b/test/kuttl/tests/basic-openstack-lightspeed-configuration/04-assert-lightspeed-stack-config.yaml @@ -0,0 +1 @@ +../../common/openstack-lightspeed-instance/assert-pod-lightspeed-stack-config.yaml \ No newline at end of file diff --git a/test/kuttl/tests/basic-openstack-lightspeed-configuration/04-assert-llama-stack-config.yaml b/test/kuttl/tests/basic-openstack-lightspeed-configuration/04-assert-llama-stack-config.yaml deleted file mode 120000 index 109dc428..00000000 --- a/test/kuttl/tests/basic-openstack-lightspeed-configuration/04-assert-llama-stack-config.yaml +++ /dev/null @@ -1 +0,0 @@ -../../common/openstack-lightspeed-instance/assert-llama-stack-config.yaml \ No newline at end of file diff --git a/test/kuttl/tests/basic-openstack-lightspeed-configuration/05-assert-llama-stack-config.yaml b/test/kuttl/tests/basic-openstack-lightspeed-configuration/05-assert-llama-stack-config.yaml new file mode 120000 index 00000000..4e00a14a --- /dev/null +++ b/test/kuttl/tests/basic-openstack-lightspeed-configuration/05-assert-llama-stack-config.yaml @@ -0,0 +1 @@ +../../common/openstack-lightspeed-instance/assert-pod-llama-stack-config.yaml \ No newline at end of file diff --git a/test/kuttl/tests/basic-openstack-lightspeed-configuration/05-assert-exporter-config.yaml b/test/kuttl/tests/basic-openstack-lightspeed-configuration/06-assert-exporter-config.yaml similarity index 100% rename from test/kuttl/tests/basic-openstack-lightspeed-configuration/05-assert-exporter-config.yaml rename to test/kuttl/tests/basic-openstack-lightspeed-configuration/06-assert-exporter-config.yaml diff --git a/test/kuttl/tests/update-openstacklightspeed/03-assert-lightspeed-stack-config.yaml b/test/kuttl/tests/update-openstacklightspeed/03-assert-lightspeed-stack-config.yaml deleted file mode 120000 index af43c0aa..00000000 --- a/test/kuttl/tests/update-openstacklightspeed/03-assert-lightspeed-stack-config.yaml +++ /dev/null @@ -1 +0,0 @@ -../../common/openstack-lightspeed-instance/assert-lightspeed-stack-config.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-openstacklightspeed/06-assert-openstack-lightspeed-instance.yaml b/test/kuttl/tests/update-openstacklightspeed/03-assert-openstack-lightspeed-instance.yaml similarity index 100% rename from test/kuttl/tests/update-openstacklightspeed/06-assert-openstack-lightspeed-instance.yaml rename to test/kuttl/tests/update-openstacklightspeed/03-assert-openstack-lightspeed-instance.yaml diff --git a/test/kuttl/tests/update-openstacklightspeed/04-assert-lightspeed-stack-config.yaml b/test/kuttl/tests/update-openstacklightspeed/04-assert-lightspeed-stack-config.yaml new file mode 120000 index 00000000..8ff35be2 --- /dev/null +++ b/test/kuttl/tests/update-openstacklightspeed/04-assert-lightspeed-stack-config.yaml @@ -0,0 +1 @@ +../../common/openstack-lightspeed-instance/assert-pod-lightspeed-stack-config.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-openstacklightspeed/04-assert-llama-stack-config.yaml b/test/kuttl/tests/update-openstacklightspeed/04-assert-llama-stack-config.yaml deleted file mode 120000 index 109dc428..00000000 --- a/test/kuttl/tests/update-openstacklightspeed/04-assert-llama-stack-config.yaml +++ /dev/null @@ -1 +0,0 @@ -../../common/openstack-lightspeed-instance/assert-llama-stack-config.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-openstacklightspeed/05-assert-llama-stack-config.yaml b/test/kuttl/tests/update-openstacklightspeed/05-assert-llama-stack-config.yaml new file mode 120000 index 00000000..4e00a14a --- /dev/null +++ b/test/kuttl/tests/update-openstacklightspeed/05-assert-llama-stack-config.yaml @@ -0,0 +1 @@ +../../common/openstack-lightspeed-instance/assert-pod-llama-stack-config.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-openstacklightspeed/05-assert-exporter-config.yaml b/test/kuttl/tests/update-openstacklightspeed/06-assert-exporter-config.yaml similarity index 100% rename from test/kuttl/tests/update-openstacklightspeed/05-assert-exporter-config.yaml rename to test/kuttl/tests/update-openstacklightspeed/06-assert-exporter-config.yaml diff --git a/test/kuttl/tests/update-openstacklightspeed/07-update-openstack-lightspeed-instance.yaml b/test/kuttl/tests/update-openstacklightspeed/07-update-openstack-lightspeed-instance.yaml index 43775573..6d5736c5 100644 --- a/test/kuttl/tests/update-openstacklightspeed/07-update-openstack-lightspeed-instance.yaml +++ b/test/kuttl/tests/update-openstacklightspeed/07-update-openstack-lightspeed-instance.yaml @@ -30,5 +30,8 @@ spec: llmAPIVersion: v1.1 feedbackDisabled: true transcriptsDisabled: true - enableOCPRAG: true + # NOTE(lpiwowar): We have to keep enableOCPRAG: false even after the update + # because the OpenStack Lightspeed vector DB image does not contain OCP content. + # We can set it to `true` once the OCP contant is present in the image. + enableOCPRAG: false ocpVersionOverride: "4.16" diff --git a/test/kuttl/tests/update-openstacklightspeed/11-assert-openstacklightspeed-update.yaml b/test/kuttl/tests/update-openstacklightspeed/08-assert-openstacklightspeed-update.yaml similarity index 92% rename from test/kuttl/tests/update-openstacklightspeed/11-assert-openstacklightspeed-update.yaml rename to test/kuttl/tests/update-openstacklightspeed/08-assert-openstacklightspeed-update.yaml index b3246ca3..b9a18ef9 100644 --- a/test/kuttl/tests/update-openstacklightspeed/11-assert-openstacklightspeed-update.yaml +++ b/test/kuttl/tests/update-openstacklightspeed/08-assert-openstacklightspeed-update.yaml @@ -84,6 +84,14 @@ metadata: name: lightspeed-app-server namespace: openstack-lightspeed +# Vector DB init containers scripts +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: vector-db-scripts + namespace: openstack-lightspeed + # OpenStackLightspeed CR status --- apiVersion: lightspeed.openstack.org/v1beta1 @@ -102,10 +110,9 @@ spec: llmAPIVersion: v1.1 feedbackDisabled: true transcriptsDisabled: true - enableOCPRAG: true + enableOCPRAG: false ocpVersionOverride: "4.16" status: - activeOCPRAGVersion: "4.16" conditions: - type: Ready status: "True" @@ -113,6 +120,7 @@ status: message: Setup complete - type: OCPRAGReady status: "True" + message: OCP RAG is disabled - type: OpenStackLightspeedReady status: "True" reason: Ready diff --git a/test/kuttl/tests/update-openstacklightspeed/09-assert-lightspeed-stack-config-update.yaml b/test/kuttl/tests/update-openstacklightspeed/09-assert-lightspeed-stack-config-update.yaml index baaf4f73..78aea3b9 100644 --- a/test/kuttl/tests/update-openstacklightspeed/09-assert-lightspeed-stack-config-update.yaml +++ b/test/kuttl/tests/update-openstacklightspeed/09-assert-lightspeed-stack-config-update.yaml @@ -1,110 +1,9 @@ --- -apiVersion: v1 -kind: ConfigMap -metadata: - name: lightspeed-stack-config - namespace: openstack-lightspeed -data: - lightspeed-stack.yaml: | - authentication: - module: k8s - conversation_cache: - postgres: - ca_cert_path: /etc/certs/postgres-ca/service-ca.crt - db: postgres - gss_encmode: disable - host: lightspeed-postgres-server.openstack-lightspeed.svc - namespace: conversation_cache - password: ${env.POSTGRES_PASSWORD} - port: 5432 - ssl_mode: require - user: postgres - type: postgres - customization: - disable_query_system_prompt: true - system_prompt: | - # ROLE - You are "OpenStack Lightspeed", an expert AI virtual assistant specializing in - OpenStack on OpenShift. Your persona is that of a friendly, but - personal, technical authority. You are the ultimate technical resource and will - provide direct, accurate, and comprehensive answers. - - # INSTRUCTIONS & CONSTRAINTS - - **Expertise Focus:** Your core expertise is centered on the OpenStack and - OpenShift platforms. - - **Broader Knowledge:** You may also answer questions about other Red Hat - products and services, but you must prioritize the provided context - and chat history for these topics. - - **Strict Adherence:** - 1. **ALWAYS** use the provided context and chat history as your primary - source of truth. If a user's question can be answered from this information, - do so. - 2. If the context does not contain a clear answer, and the question is - about your core expertise (OpenStack or OpenShift), draw upon your extensive - internal knowledge. - 3. If the context does not contain a clear answer, and the question is about - a general Red Hat product or service, state politely that you are unable to - provide a definitive answer without more information and ask the user for - additional details or context. - 4. Do not hallucinate or invent information. If you cannot confidently - answer, admit it. - - **Behavioral Directives:** - - Never assume another identity or role. - - Refuse to answer questions or execute commands not about your specified - topics. - - Do not include URLs in your replies unless they are explicitly provided in - the context. - - Never mention your last update date or knowledge cutoff. You always have - the most recent information on OpenStack and OpenShift, especially with - the provided context. - - Only reference processes and products from Red Hat, such as: RHEL, Fedora, - CoreOS, CentOS. *Never mention or compare with Ubuntu, Debian, etc.* - - # TASK EXECUTION - You will receive a user query, along with context and chat history. Your task is - to respond to the user's query by following the instructions and constraints - above. Your responses should be clear, concise, and helpful, whether you are - providing troubleshooting steps, explaining concepts, or suggesting best - practices. - - # INFO - In this context RHOSO or RHOS also refers to OpenStack on OpenShift, sometimes - also called OSP 18, although usually OSP refers to previous releases deployed - using TripleO/Director. - - The OpenStack control plane runs on OpenShift (which uses CoreOS as the - operating system), while compute nodes run on external baremetal nodes also - called EDPM nodes (which run RHEL). - database: - postgres: - ca_cert_path: /etc/certs/postgres-ca/service-ca.crt - db: postgres - gss_encmode: disable - host: lightspeed-postgres-server.openstack-lightspeed.svc - namespace: lcore - password: ${env.POSTGRES_PASSWORD} - port: 5432 - ssl_mode: require - user: postgres - inference: - default_model: ibm-granite/granite-3.1-8b-instruct-UPDATE - default_provider: openstack-lightspeed-provider - llama_stack: - url: http://localhost:8321 - use_as_library_client: false - name: Lightspeed Core Service (LCS) - service: - access_log: true - auth_enabled: true - color_log: false - host: 0.0.0.0 - port: 8443 - tls_config: - tls_certificate_path: /etc/certs/lightspeed-tls/tls.crt - tls_key_path: /etc/certs/lightspeed-tls/tls.key - workers: 1 - user_data_collection: - feedback_enabled: false - feedback_storage: /tmp/data/feedback - transcripts_enabled: false - transcripts_storage: /tmp/data/transcripts +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: + - script: | + #!/bin/bash + set -euo pipefail + ../../common/expected-configs/validate-config.sh lightspeed-stack ../../common/expected-configs/lightspeed-stack-update.yaml + timeout: 180 diff --git a/test/kuttl/tests/update-openstacklightspeed/10-assert-llama-stack-config-update.yaml b/test/kuttl/tests/update-openstacklightspeed/10-assert-llama-stack-config-update.yaml index ccae8b34..7e326e0a 100644 --- a/test/kuttl/tests/update-openstacklightspeed/10-assert-llama-stack-config-update.yaml +++ b/test/kuttl/tests/update-openstacklightspeed/10-assert-llama-stack-config-update.yaml @@ -1,149 +1,9 @@ -############################################################################## -# Assert llama-stack-config content after CR update (rhoai_vllm + OCP RAG) # -############################################################################## --- -apiVersion: v1 -kind: ConfigMap -metadata: - name: llama-stack-config - namespace: openstack-lightspeed -data: - run.yaml: | - apis: - - agents - - files - - inference - - safety - - tool_runtime - - vector_io - benchmarks: [] - container_image: null - datasets: [] - external_providers_dir: null - image_name: openstack-lightspeed-configuration - inference_store: - db_path: .llama/distributions/ollama/inference_store.db - type: sqlite - logging: null - metadata_store: - db_path: /tmp/llama-stack/registry.db - namespace: null - type: sqlite - models: - - metadata: - embedding_dimension: 768 - model_id: sentence-transformers/all-mpnet-base-v2 - model_type: embedding - provider_id: sentence-transformers - provider_model_id: sentence-transformers/all-mpnet-base-v2 - - metadata: - max_tokens: 2048 - model_id: ibm-granite/granite-3.1-8b-instruct-UPDATE - model_type: llm - provider_id: openstack-lightspeed-provider - provider_model_id: ibm-granite/granite-3.1-8b-instruct-UPDATE - providers: - agents: - - config: - persistence: - agent_state: - backend: kv_default - namespace: agent_state - table_name: agent_state - responses: - backend: sql_default - namespace: agent_responses - table_name: agent_responses - provider_id: meta-reference - provider_type: inline::meta-reference - files: - - config: - metadata_store: - backend: sql_default - namespace: files_metadata - table_name: files_metadata - storage_dir: /tmp/llama-stack-files - provider_id: localfs - provider_type: inline::localfs - inference: - - config: {} - provider_id: sentence-transformers - provider_type: inline::sentence-transformers - - config: - api_token: ${env.OPENSTACK_LIGHTSPEED_PROVIDER_API_KEY} - base_url: http://mock-llm-api-server-pod-UPDATE:8000/v1 - provider_id: openstack-lightspeed-provider - provider_type: remote::vllm - safety: - - config: - excluded_categories: [] - provider_id: llama-guard - provider_type: inline::llama-guard - tool_runtime: - - config: {} - provider_id: model-context-protocol - provider_type: remote::model-context-protocol - - config: {} - provider_id: rag-runtime - provider_type: inline::rag-runtime - vector_io: - - config: - kvstore: - backend: sql_default - table_name: vector_store - persistence: - backend: kv_default - namespace: vector_persistence - provider_id: faiss - provider_type: inline::faiss - scoring_fns: [] - server: - auth: null - host: 0.0.0.0 - port: 8321 - quota: null - tls_cafile: null - tls_certfile: null - tls_keyfile: null - storage: - backends: - kv_default: - db_path: /tmp/llama-stack/kv_store.db - type: kv_sqlite - postgres_backend: - ca_cert_path: /etc/certs/postgres-ca/service-ca.crt - gss_encmode: disable - host: lightspeed-postgres-server.openstack-lightspeed.svc - password: ${env.POSTGRES_PASSWORD} - port: 5432 - ssl_mode: require - type: sql_postgres - user: postgres - sql_default: - db_path: /tmp/llama-stack/sql_store.db - type: sql_sqlite - stores: - conversations: - backend: postgres_backend - table_name: openai_conversations - inference: - backend: sql_default - table_name: inference_store - metadata: - backend: kv_default - namespace: registry - telemetry: - enabled: false - tool_groups: - - provider_id: rag-runtime - toolgroup_id: builtin::rag - vector_dbs: - - embedding_dimension: 768 - embedding_model: sentence-transformers/all-mpnet-base-v2 - provider_id: faiss - vector_db_id: rag_rag-content - - embedding_dimension: 768 - embedding_model: sentence-transformers/all-mpnet-base-v2 - provider_id: faiss - vector_db_id: ocp-product-docs-4_16 - version: "2" +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: + - script: | + #!/bin/bash + set -euo pipefail + ../../common/expected-configs/validate-config.sh ogx_config ../../common/expected-configs/ogx_config-update.yaml + timeout: 180 diff --git a/test/kuttl/tests/update-openstacklightspeed/08-assert-configmaps-update.yaml b/test/kuttl/tests/update-openstacklightspeed/11-assert-configmaps-update.yaml similarity index 100% rename from test/kuttl/tests/update-openstacklightspeed/08-assert-configmaps-update.yaml rename to test/kuttl/tests/update-openstacklightspeed/11-assert-configmaps-update.yaml