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/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 -} 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