Initial commit: stateful OpenStack API laboratory simulator.

Ship Keystone auth, multi-service handlers (Yoga→Dalmatian), Compose/Helm
packaging, API contract packs, and pytest/Pulumi coverage labs.
This commit is contained in:
Sergey Antropoff
2026-07-18 04:26:48 +03:00
commit d096ad4d10
509 changed files with 464404 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
.DS_Store
.git
.gitignore
*.md
*.tgz
charts/*.tgz
values-ingress-example.yaml
+16
View File
@@ -0,0 +1,16 @@
apiVersion: v2
name: openstack-api-simulator
description: Stateful OpenStack API simulator (PostgreSQL + multi-port nginx gateway) for labs and CI
type: application
version: 0.1.0
appVersion: "0.1.0"
home: https://github.com/inecs/openstack-api-simulator
keywords:
- openstack
- cloud
- api
- simulator
maintainers:
- name: inecs
# Bundled PostgreSQL uses the official postgres image (see templates/postgresql-*.yaml).
# No external chart dependency is required — run helm install directly.
+93
View File
@@ -0,0 +1,93 @@
**Language / Язык:** [English](README.md) | [Русский](README.ru.md)
# Helm chart: openstack-api-simulator
Deploys the published runtime image
[`inecs/openstack-api-simulator`](https://hub.docker.com/r/inecs/openstack-api-simulator)
with bundled PostgreSQL, migrations, optional seed Job, multi-port nginx
**api-gateway**, Ingress, and cert-manager Let's Encrypt `ClusterIssuer` resources.
Full guide: [docs/kubernetes.md](../../docs/kubernetes.md).
## Quick install
Prerequisites: Kubernetes, Helm 3, ingress-nginx (or compatible), cert-manager
(for TLS example).
```bash
helm upgrade --install os-sim ./helm/openstack-api-simulator \
-n openstack-sim --create-namespace \
-f ./helm/openstack-api-simulator/values-ingress-example.yaml \
--set certManager.email=you@example.com \
--set ingress.hosts[0].host=os-sim.example.com \
--set ingress.tls[0].hosts[0]=os-sim.example.com \
--set secret.ticketSigningKey="$(openssl rand -hex 32)" \
--set postgresql.auth.password="$(openssl rand -hex 16)"
```
Point DNS at the Ingress controller, wait for Certificate Ready, then open
`https://os-sim.example.com/`.
Minimal ClusterIP + port-forward:
```bash
helm upgrade --install os-sim ./helm/openstack-api-simulator \
-n openstack-sim --create-namespace \
--set secret.ticketSigningKey="$(openssl rand -hex 32)" \
--set seed.enabled=true --set seed.profile=minimal
kubectl -n openstack-sim port-forward \
svc/os-sim-openstack-api-simulator-gateway \
5000:5000 8774:8774 9696:9696 9292:9292 8776:8776
```
## Values overview
| Key | Default | Meaning |
|---|---|---|
| `image.repository` | `inecs/openstack-api-simulator` | Hub image |
| `image.tag` | chart `appVersion` | Image tag |
| `gateway.enabled` | `true` | Multi-port nginx OpenStack gateway |
| `gateway.service.ports` | 5000, 8774, 9696, … | Published API ports |
| `postgresql.enabled` | `true` | Bundle official PostgreSQL StatefulSet |
| `migrate.enabled` | `true` | Schema migrate initContainer |
| `seed.enabled` | `false` | Post-install seed Job |
| `seed.profile` | `minimal` | `minimal` or `demo` |
| `ingress.enabled` | `false` | Expose Keystone/UI via Ingress |
| `certManager.enabled` | `false` | Annotate Ingress + optional ClusterIssuers |
See [`values.yaml`](values.yaml) and [`values-ingress-example.yaml`](values-ingress-example.yaml).
## Chart layout
```text
helm/openstack-api-simulator/
Chart.yaml
values.yaml
values-ingress-example.yaml
templates/
deployment.yaml # simulator (FastAPI :8080)
gateway-deployment.yaml # nginx multi-port gateway
gateway-service.yaml
gateway-configmap.yaml
postgresql-statefulset.yaml
migrate-job.yaml / initContainer
seed-job.yaml
ingress.yaml
clusterissuer.yaml
secret.yaml
NOTES.txt
```
Validate locally:
```bash
helm lint ./helm/openstack-api-simulator
helm template os-sim ./helm/openstack-api-simulator --set secret.ticketSigningKey=test
```
## Integration suites
Hypervisor-lab / API coverage tests (`pulumi-tests/`) run via **Docker Compose**, not this chart.
Deploy the simulator with Helm, then point host-side or CI runners at the gateway
Service (port-forward or LoadBalancer). See [docs/hypervisor-lab.md](../../docs/hypervisor-lab.md).
+93
View File
@@ -0,0 +1,93 @@
**Language / Язык:** [English](README.md) | [Русский](README.ru.md)
# Helm chart: openstack-api-simulator
Разворачивает опубликованный runtime-образ
[`inecs/openstack-api-simulator`](https://hub.docker.com/r/inecs/openstack-api-simulator)
со встроенным PostgreSQL, миграциями, опциональным seed Job, multi-port nginx
**api-gateway**, Ingress и ресурсами cert-manager Let's Encrypt `ClusterIssuer`.
Полное руководство: [docs/ru/kubernetes.md](../../docs/ru/kubernetes.md).
## Быстрая установка
Требования: Kubernetes, Helm 3, ingress-nginx (или совместимый), cert-manager
(для TLS-примера).
```bash
helm upgrade --install os-sim ./helm/openstack-api-simulator \
-n openstack-sim --create-namespace \
-f ./helm/openstack-api-simulator/values-ingress-example.yaml \
--set certManager.email=you@example.com \
--set ingress.hosts[0].host=os-sim.example.com \
--set ingress.tls[0].hosts[0]=os-sim.example.com \
--set secret.ticketSigningKey="$(openssl rand -hex 32)" \
--set postgresql.auth.password="$(openssl rand -hex 16)"
```
Укажите DNS на Ingress controller, дождитесь Certificate Ready, затем откройте
`https://os-sim.example.com/`.
Минимальный ClusterIP + port-forward:
```bash
helm upgrade --install os-sim ./helm/openstack-api-simulator \
-n openstack-sim --create-namespace \
--set secret.ticketSigningKey="$(openssl rand -hex 32)" \
--set seed.enabled=true --set seed.profile=minimal
kubectl -n openstack-sim port-forward \
svc/os-sim-openstack-api-simulator-gateway \
5000:5000 8774:8774 9696:9696 9292:9292 8776:8776
```
## Обзор values
| Ключ | По умолчанию | Смысл |
|---|---|---|
| `image.repository` | `inecs/openstack-api-simulator` | Образ на Hub |
| `image.tag` | chart `appVersion` | Тег образа |
| `gateway.enabled` | `true` | Multi-port nginx шлюз OpenStack |
| `gateway.service.ports` | 5000, 8774, 9696, … | Публикуемые порты API |
| `postgresql.enabled` | `true` | Встроенный PostgreSQL StatefulSet |
| `migrate.enabled` | `true` | initContainer миграций схемы |
| `seed.enabled` | `false` | Post-install seed Job |
| `seed.profile` | `minimal` | `minimal` или `demo` |
| `ingress.enabled` | `false` | Keystone/UI через Ingress |
| `certManager.enabled` | `false` | Аннотации Ingress + опциональные ClusterIssuers |
См. [`values.yaml`](values.yaml) и [`values-ingress-example.yaml`](values-ingress-example.yaml).
## Структура чарта
```text
helm/openstack-api-simulator/
Chart.yaml
values.yaml
values-ingress-example.yaml
templates/
deployment.yaml # simulator (FastAPI :8080)
gateway-deployment.yaml # nginx multi-port gateway
gateway-service.yaml
gateway-configmap.yaml
postgresql-statefulset.yaml
migrate-job.yaml / initContainer
seed-job.yaml
ingress.yaml
clusterissuer.yaml
secret.yaml
NOTES.txt
```
Локальная проверка:
```bash
helm lint ./helm/openstack-api-simulator
helm template os-sim ./helm/openstack-api-simulator --set secret.ticketSigningKey=test
```
## Интеграционные сьюты
Тесты покрытия API (`pulumi-tests/`) запускаются через **Docker Compose**, не через этот чарт.
Разверните симулятор Helm'ом, затем направьте host/CI runners на gateway
Service (port-forward или LoadBalancer). См. [docs/ru/hypervisor-lab.md](../../docs/ru/hypervisor-lab.md).
@@ -0,0 +1,45 @@
openstack-api-simulator {{ .Chart.AppVersion }} installed as release "{{ .Release.Name }}".
Image: {{ include "openstack-api-simulator.image" . }}
1. Check readiness:
kubectl -n {{ .Release.Namespace }} get pods -l "app.kubernetes.io/instance={{ .Release.Name }}"
2. Access Keystone / Web UI:
{{- if .Values.ingress.enabled }}
https://{{ (index .Values.ingress.hosts 0).host }}/
{{- if .Values.certManager.enabled }}
(TLS via cert-manager issuer {{ include "openstack-api-simulator.clusterIssuer" . }})
{{- end }}
{{- else if .Values.gateway.enabled }}
kubectl -n {{ .Release.Namespace }} port-forward \
svc/{{ include "openstack-api-simulator.fullname" . }}-gateway 5000:5000 8774:8774 9696:9696
Then:
http://127.0.0.1:5000/ # Keystone + console
http://127.0.0.1:8774/v2.1/ # Nova
http://127.0.0.1:9696/v2.0/ # Neutron
{{- else }}
kubectl -n {{ .Release.Namespace }} port-forward \
svc/{{ include "openstack-api-simulator.fullname" . }} {{ .Values.service.port }}:{{ .Values.service.port }}
Then open http://127.0.0.1:{{ .Values.service.port }}/
(path-based routing only — prefer gateway.enabled=true for real OpenStack clients)
{{- end }}
3. Seed OpenStack data (if seed job was disabled):
kubectl -n {{ .Release.Namespace }} exec deploy/{{ include "openstack-api-simulator.fullname" . }} -- \
python -m app.openstack.seed_cli --profile minimal
Demo cloud (~1000 servers):
python -m app.openstack.seed_cli --profile demo
Or: helm upgrade … --set seed.enabled=true --set seed.profile=demo
Default Keystone login after seeding: admin / secret (project demo or admin).
Demo cloud also enables ops/developer/auditor — password secret.
Change secret.ticketSigningKey before exposing the cluster publicly.
Docs: docs/kubernetes.md
@@ -0,0 +1,121 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "openstack-api-simulator.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
*/}}
{{- define "openstack-api-simulator.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "openstack-api-simulator.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "openstack-api-simulator.labels" -}}
helm.sh/chart: {{ include "openstack-api-simulator.chart" . }}
{{ include "openstack-api-simulator.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "openstack-api-simulator.selectorLabels" -}}
app.kubernetes.io/name: {{ include "openstack-api-simulator.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Service account name
*/}}
{{- define "openstack-api-simulator.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "openstack-api-simulator.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
{{/*
Image reference
*/}}
{{- define "openstack-api-simulator.image" -}}
{{- $tag := .Values.image.tag | default .Chart.AppVersion }}
{{- printf "%s:%s" .Values.image.repository $tag }}
{{- end }}
{{/*
Secret name holding DATABASE_URL and TICKET_SIGNING_KEY
*/}}
{{- define "openstack-api-simulator.secretName" -}}
{{- if .Values.secret.existingSecret }}
{{- .Values.secret.existingSecret }}
{{- else }}
{{- include "openstack-api-simulator.fullname" . }}
{{- end }}
{{- end }}
{{/*
PostgreSQL hostname when bundled subchart is enabled
*/}}
{{- define "openstack-api-simulator.postgresqlHost" -}}
{{- printf "%s-postgresql" .Release.Name }}
{{- end }}
{{/*
Build DATABASE_URL when not supplied explicitly (bundled or external discrete fields).
*/}}
{{- define "openstack-api-simulator.databaseUrl" -}}
{{- if .Values.secret.databaseUrl }}
{{- .Values.secret.databaseUrl }}
{{- else if .Values.postgresql.enabled }}
{{- $user := .Values.postgresql.auth.username }}
{{- $pass := .Values.postgresql.auth.password }}
{{- $db := .Values.postgresql.auth.database }}
{{- $host := include "openstack-api-simulator.postgresqlHost" . }}
{{- printf "postgresql://%s:%s@%s:5432/%s" $user $pass $host $db }}
{{- else if .Values.externalDatabase.host }}
{{- $user := .Values.externalDatabase.user }}
{{- $pass := .Values.externalDatabase.password }}
{{- $db := .Values.externalDatabase.database }}
{{- $host := .Values.externalDatabase.host }}
{{- $port := int .Values.externalDatabase.port }}
{{- printf "postgresql://%s:%s@%s:%d/%s" $user $pass $host $port $db }}
{{- else }}
{{- fail "Set postgresql.enabled=true, or secret.databaseUrl / secret.existingSecret, or externalDatabase.host" }}
{{- end }}
{{- end }}
{{/*
cert-manager ClusterIssuer name used by Ingress
*/}}
{{- define "openstack-api-simulator.clusterIssuer" -}}
{{- if .Values.certManager.useStaging }}
{{- .Values.certManager.stagingIssuerName }}
{{- else }}
{{- .Values.certManager.issuerName }}
{{- end }}
{{- end }}
@@ -0,0 +1,42 @@
{{- if and .Values.certManager.enabled .Values.certManager.createClusterIssuer }}
{{- $solverClass := .Values.certManager.solverIngressClassName | default .Values.ingress.className }}
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: {{ .Values.certManager.issuerName }}
labels:
{{- include "openstack-api-simulator.labels" . | nindent 4 }}
spec:
acme:
email: {{ required "certManager.email is required when createClusterIssuer=true" .Values.certManager.email | quote }}
server: {{ .Values.certManager.server | quote }}
privateKeySecretRef:
name: {{ printf "%s-account-key" .Values.certManager.issuerName }}
solvers:
- http01:
ingress:
{{- if $solverClass }}
ingressClassName: {{ $solverClass }}
{{- end }}
---
{{- if .Values.certManager.createStagingIssuer }}
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: {{ .Values.certManager.stagingIssuerName }}
labels:
{{- include "openstack-api-simulator.labels" . | nindent 4 }}
spec:
acme:
email: {{ .Values.certManager.email | quote }}
server: {{ .Values.certManager.stagingServer | quote }}
privateKeySecretRef:
name: {{ printf "%s-account-key" .Values.certManager.stagingIssuerName }}
solvers:
- http01:
ingress:
{{- if $solverClass }}
ingressClassName: {{ $solverClass }}
{{- end }}
{{- end }}
{{- end }}
@@ -0,0 +1,142 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "openstack-api-simulator.fullname" . }}
labels:
{{- include "openstack-api-simulator.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "openstack-api-simulator.selectorLabels" . | nindent 6 }}
app.kubernetes.io/component: simulator
template:
metadata:
annotations:
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "openstack-api-simulator.labels" . | nindent 8 }}
app.kubernetes.io/component: simulator
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "openstack-api-simulator.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
{{- if and .Values.migrate.enabled (not .Values.migrate.asJob) }}
initContainers:
- name: migrate
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: {{ include "openstack-api-simulator.image" . }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ include "openstack-api-simulator.secretName" . }}
key: DATABASE_URL
- name: LOG_LEVEL
value: {{ .Values.config.logLevel | quote }}
command:
- python
- -c
- |
import asyncio
import os
import sys
import time
import asyncpg
dsn = os.environ["DATABASE_URL"]
deadline = time.time() + 300
while True:
try:
async def ping() -> None:
conn = await asyncpg.connect(dsn=dsn, timeout=5)
await conn.close()
asyncio.run(ping())
break
except Exception as exc: # noqa: BLE001 - wait until Postgres accepts connections
if time.time() >= deadline:
print(f"database not ready: {exc}", file=sys.stderr)
raise
print(f"waiting for database: {exc}")
time.sleep(3)
from app.db.migrate_cli import run
asyncio.run(run())
resources:
{{- toYaml .Values.migrate.resources | nindent 12 }}
volumeMounts:
- name: tmp
mountPath: /tmp
{{- end }}
containers:
- name: simulator
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: {{ include "openstack-api-simulator.image" . }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 8080
protocol: TCP
env:
- name: APP_HOST
value: "0.0.0.0"
- name: APP_PORT
value: "8080"
- name: LOG_LEVEL
value: {{ .Values.config.logLevel | quote }}
- name: REQUEST_ID_HEADER
value: {{ .Values.config.requestIdHeader | quote }}
{{- if .Values.config.openstackSeries }}
- name: OPENSTACK_SERIES
value: {{ .Values.config.openstackSeries | quote }}
{{- end }}
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ include "openstack-api-simulator.secretName" . }}
key: DATABASE_URL
- name: TICKET_SIGNING_KEY
valueFrom:
secretKeyRef:
name: {{ include "openstack-api-simulator.secretName" . }}
key: TICKET_SIGNING_KEY
livenessProbe:
{{- toYaml .Values.livenessProbe | nindent 12 }}
readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
@@ -0,0 +1,75 @@
{{- if .Values.gateway.enabled }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "openstack-api-simulator.fullname" . }}-gateway
labels:
{{- include "openstack-api-simulator.labels" . | nindent 4 }}
app.kubernetes.io/component: gateway
data:
# Replaces nginx's stock default.conf (avoids conflicting listen 80).
default.conf: |
# OpenStack multi-port gateway (Helm). Mirrors docker/gateway/openstack-ports.conf.
upstream openstack_simulator {
server {{ include "openstack-api-simulator.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.port }};
}
map $server_port $openstack_service {
default "simulator";
5000 "keystone";
8774 "nova";
9696 "neutron";
9292 "glance";
8776 "cinder";
8003 "placement";
8004 "heat";
8000 "heat-cfn";
8080 "swift";
6385 "ironic";
9876 "octavia";
9311 "barbican";
8786 "manila";
9001 "designate";
9511 "magnum";
9517 "zun";
8779 "trove";
8989 "mistral";
8042 "aodh";
8889 "cloudkitty";
9090 "freezer";
1234 "blazar";
8999 "vitrage";
15868 "masakari";
9890 "tacker";
5050 "adjutant";
9322 "watcher";
8888 "zaqar";
80 "horizon";
}
server {
{{- range .Values.gateway.service.ports }}
listen {{ .port }}; # {{ .name }}
{{- end }}
server_name _;
location / {
proxy_pass http://openstack_simulator;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-OpenStack-Service $openstack_service;
proxy_set_header X-OpenStack-Route-Service $http_x_openstack_route_service;
proxy_set_header X-Request-ID $request_id;
proxy_set_header OpenStack-API-Version $http_openstack_api_version;
proxy_set_header X-OpenStack-Nova-API-Version $http_x_openstack_nova_api_version;
add_header X-OpenStack-Service $openstack_service always;
add_header Access-Control-Expose-Headers "X-Subject-Token,x-subject-token" always;
add_header X-Forwarded-Port $server_port always;
}
}
{{- end }}
@@ -0,0 +1,70 @@
{{- if .Values.gateway.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "openstack-api-simulator.fullname" . }}-gateway
labels:
{{- include "openstack-api-simulator.labels" . | nindent 4 }}
app.kubernetes.io/component: gateway
spec:
replicas: {{ .Values.gateway.replicaCount }}
selector:
matchLabels:
{{- include "openstack-api-simulator.selectorLabels" . | nindent 6 }}
app.kubernetes.io/component: gateway
template:
metadata:
annotations:
checksum/gateway-config: {{ include (print $.Template.BasePath "/gateway-configmap.yaml") . | sha256sum }}
labels:
{{- include "openstack-api-simulator.labels" . | nindent 8 }}
app.kubernetes.io/component: gateway
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "openstack-api-simulator.serviceAccountName" . }}
containers:
- name: gateway
image: "{{ .Values.gateway.image.repository }}:{{ .Values.gateway.image.tag }}"
imagePullPolicy: {{ .Values.gateway.image.pullPolicy }}
ports:
{{- range .Values.gateway.service.ports }}
- name: {{ .name | trunc 15 | trimSuffix "-" }}
containerPort: {{ .port }}
protocol: TCP
{{- end }}
volumeMounts:
- name: nginx-conf
mountPath: /etc/nginx/conf.d
readOnly: true
readinessProbe:
tcpSocket:
port: 5000
initialDelaySeconds: 3
periodSeconds: 5
livenessProbe:
tcpSocket:
port: 5000
initialDelaySeconds: 10
periodSeconds: 10
resources:
{{- toYaml .Values.gateway.resources | nindent 12 }}
volumes:
- name: nginx-conf
configMap:
name: {{ include "openstack-api-simulator.fullname" . }}-gateway
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
@@ -0,0 +1,21 @@
{{- if .Values.gateway.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "openstack-api-simulator.fullname" . }}-gateway
labels:
{{- include "openstack-api-simulator.labels" . | nindent 4 }}
app.kubernetes.io/component: gateway
spec:
type: {{ .Values.gateway.service.type }}
ports:
{{- range .Values.gateway.service.ports }}
- name: {{ .name | trunc 15 | trimSuffix "-" }}
port: {{ .port }}
targetPort: {{ .port }}
protocol: TCP
{{- end }}
selector:
{{- include "openstack-api-simulator.selectorLabels" . | nindent 4 }}
app.kubernetes.io/component: gateway
{{- end }}
@@ -0,0 +1,48 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "openstack-api-simulator.fullname" . -}}
{{- $gatewayEnabled := .Values.gateway.enabled -}}
{{- $svcName := ternary (printf "%s-gateway" $fullName) $fullName $gatewayEnabled -}}
{{- $svcPort := ternary 5000 .Values.service.port $gatewayEnabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
{{- include "openstack-api-simulator.labels" . | nindent 4 }}
annotations:
{{- if .Values.certManager.enabled }}
cert-manager.io/cluster-issuer: {{ include "openstack-api-simulator.clusterIssuer" . | quote }}
{{- end }}
{{- with .Values.ingress.annotations }}
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- with .Values.ingress.className }}
ingressClassName: {{ . }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ $svcName }}
port:
number: {{ $svcPort }}
{{- end }}
{{- end }}
{{- end }}
@@ -0,0 +1,50 @@
{{- /* Kept for optional standalone migrate Job when migrate.asJob=true */ -}}
{{- if and .Values.migrate.enabled .Values.migrate.asJob }}
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "openstack-api-simulator.fullname" . }}-migrate
labels:
{{- include "openstack-api-simulator.labels" . | nindent 4 }}
app.kubernetes.io/component: migrate
spec:
backoffLimit: {{ .Values.migrate.backoffLimit }}
activeDeadlineSeconds: {{ .Values.migrate.activeDeadlineSeconds }}
template:
metadata:
labels:
{{- include "openstack-api-simulator.selectorLabels" . | nindent 8 }}
app.kubernetes.io/component: migrate
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "openstack-api-simulator.serviceAccountName" . }}
restartPolicy: OnFailure
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: migrate
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: {{ include "openstack-api-simulator.image" . }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ include "openstack-api-simulator.secretName" . }}
key: DATABASE_URL
- name: LOG_LEVEL
value: {{ .Values.config.logLevel | quote }}
command: ["python", "-m", "app.db.migrate_cli"]
resources:
{{- toYaml .Values.migrate.resources | nindent 12 }}
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
{{- end }}
@@ -0,0 +1,19 @@
{{- if .Values.postgresql.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "openstack-api-simulator.postgresqlHost" . }}
labels:
{{- include "openstack-api-simulator.labels" . | nindent 4 }}
app.kubernetes.io/component: postgresql
spec:
type: ClusterIP
ports:
- port: 5432
targetPort: postgresql
protocol: TCP
name: postgresql
selector:
app.kubernetes.io/name: {{ include "openstack-api-simulator.name" . }}-postgresql
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
@@ -0,0 +1,71 @@
{{- if .Values.postgresql.enabled }}
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: {{ include "openstack-api-simulator.postgresqlHost" . }}
labels:
{{- include "openstack-api-simulator.labels" . | nindent 4 }}
app.kubernetes.io/component: postgresql
spec:
serviceName: {{ include "openstack-api-simulator.postgresqlHost" . }}
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: {{ include "openstack-api-simulator.name" . }}-postgresql
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "openstack-api-simulator.name" . }}-postgresql
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: postgresql
spec:
containers:
- name: postgresql
image: {{ printf "%s:%s" .Values.postgresql.image.repository .Values.postgresql.image.tag | quote }}
imagePullPolicy: {{ .Values.postgresql.image.pullPolicy }}
ports:
- name: postgresql
containerPort: 5432
env:
- name: POSTGRES_DB
value: {{ .Values.postgresql.auth.database | quote }}
- name: POSTGRES_USER
value: {{ .Values.postgresql.auth.username | quote }}
- name: POSTGRES_PASSWORD
value: {{ .Values.postgresql.auth.password | quote }}
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
livenessProbe:
exec:
command: ["pg_isready", "-U", {{ .Values.postgresql.auth.username | quote }}]
initialDelaySeconds: 20
periodSeconds: 10
readinessProbe:
exec:
command: ["pg_isready", "-U", {{ .Values.postgresql.auth.username | quote }}]
initialDelaySeconds: 5
periodSeconds: 5
resources:
{{- toYaml .Values.postgresql.resources | nindent 12 }}
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
{{- if .Values.postgresql.persistence.enabled }}
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
{{- with .Values.postgresql.persistence.storageClass }}
storageClassName: {{ . | quote }}
{{- end }}
resources:
requests:
storage: {{ .Values.postgresql.persistence.size }}
{{- else }}
volumes:
- name: data
emptyDir: {}
{{- end }}
{{- end }}
@@ -0,0 +1,12 @@
{{- if and .Values.secret.create (not .Values.secret.existingSecret) }}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "openstack-api-simulator.fullname" . }}
labels:
{{- include "openstack-api-simulator.labels" . | nindent 4 }}
type: Opaque
stringData:
DATABASE_URL: {{ include "openstack-api-simulator.databaseUrl" . | quote }}
TICKET_SIGNING_KEY: {{ required "secret.ticketSigningKey is required when secret.create=true" .Values.secret.ticketSigningKey | quote }}
{{- end }}
@@ -0,0 +1,59 @@
{{- if .Values.seed.enabled }}
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "openstack-api-simulator.fullname" . }}-seed
labels:
{{- include "openstack-api-simulator.labels" . | nindent 4 }}
app.kubernetes.io/component: seed
annotations:
"helm.sh/hook": post-install,post-upgrade
"helm.sh/hook-weight": "5"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
backoffLimit: {{ .Values.seed.backoffLimit }}
template:
metadata:
labels:
{{- include "openstack-api-simulator.selectorLabels" . | nindent 8 }}
app.kubernetes.io/component: seed
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "openstack-api-simulator.serviceAccountName" . }}
restartPolicy: OnFailure
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: seed
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: {{ include "openstack-api-simulator.image" . }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ include "openstack-api-simulator.secretName" . }}
key: DATABASE_URL
- name: SEED_PROFILE
value: {{ .Values.seed.profile | quote }}
- name: LOG_LEVEL
value: {{ .Values.config.logLevel | quote }}
command:
- python
- -m
- app.openstack.seed_cli
- --profile
- {{ .Values.seed.profile | quote }}
resources:
{{- toYaml .Values.seed.resources | nindent 12 }}
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
{{- end }}
@@ -0,0 +1,16 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "openstack-api-simulator.fullname" . }}
labels:
{{- include "openstack-api-simulator.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "openstack-api-simulator.selectorLabels" . | nindent 4 }}
app.kubernetes.io/component: simulator
@@ -0,0 +1,13 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "openstack-api-simulator.serviceAccountName" . }}
labels:
{{- include "openstack-api-simulator.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
automountServiceAccountToken: false
{{- end }}
@@ -0,0 +1,57 @@
# Example: public Ingress + Let's Encrypt (cert-manager) + Hub image + demo seed.
#
# helm upgrade --install os-sim ./helm/openstack-api-simulator \
# -n openstack-sim --create-namespace \
# -f helm/openstack-api-simulator/values-ingress-example.yaml \
# --set certManager.email=you@example.com \
# --set ingress.hosts[0].host=os-sim.example.com \
# --set ingress.tls[0].hosts[0]=os-sim.example.com \
# --set secret.ticketSigningKey="$(openssl rand -hex 32)" \
# --set postgresql.auth.password="$(openssl rand -hex 16)"
image:
repository: inecs/openstack-api-simulator
tag: "0.1.0"
pullPolicy: IfNotPresent
secret:
create: true
ticketSigningKey: "replace-me"
gateway:
enabled: true
postgresql:
enabled: true
auth:
username: openstack
password: "replace-me-db-password"
database: openstack_simulator
seed:
enabled: true
profile: demo
ingress:
enabled: true
className: nginx
hosts:
- host: os-sim.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: openstack-api-simulator-tls
hosts:
- os-sim.example.com
certManager:
enabled: true
createClusterIssuer: true
createStagingIssuer: true
email: you@example.com
issuerName: letsencrypt-prod
stagingIssuerName: letsencrypt-staging
# Set true first to validate HTTP-01 against Let's Encrypt staging.
useStaging: false
solverIngressClassName: nginx
+245
View File
@@ -0,0 +1,245 @@
## Default values for openstack-api-simulator.
## Image: https://hub.docker.com/r/inecs/openstack-api-simulator
replicaCount: 1
image:
repository: inecs/openstack-api-simulator
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: ""
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
serviceAccount:
create: true
annotations: {}
name: ""
podAnnotations: {}
podLabels: {}
podSecurityContext:
fsGroup: 10001
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
readOnlyRootFilesystem: true
## Internal simulator Service (FastAPI on 8080). Clients should use gateway.
service:
type: ClusterIP
port: 8080
## Nginx api-gateway — OpenStack default ports (same as docker-compose).
## Sets X-OpenStack-Service / X-Forwarded-Port so path roots do not collide.
gateway:
enabled: true
image:
repository: nginx
tag: "1.28.0-alpine"
pullPolicy: IfNotPresent
replicaCount: 1
service:
type: ClusterIP
# Published OpenStack API ports (name → port). Keep in sync with docs/ports.md.
ports:
- name: keystone
port: 5000
- name: nova
port: 8774
- name: neutron
port: 9696
- name: glance
port: 9292
- name: cinder
port: 8776
- name: placement
port: 8003
- name: heat
port: 8004
- name: heat-cfn
port: 8000
- name: swift
port: 8080
- name: ironic
port: 6385
- name: octavia
port: 9876
- name: barbican
port: 9311
- name: manila
port: 8786
- name: designate
port: 9001
- name: magnum
port: 9511
- name: zun
port: 9517
- name: trove
port: 8779
- name: mistral
port: 8989
- name: aodh
port: 8042
- name: cloudkitty
port: 8889
- name: freezer
port: 9090
- name: blazar
port: 1234
- name: vitrage
port: 8999
- name: masakari
port: 15868
- name: tacker
port: 9890
- name: adjutant
port: 5050
- name: watcher
port: 9322
- name: zaqar
port: 8888
- name: http
port: 80
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
memory: 256Mi
## Application environment (non-secret).
config:
logLevel: INFO
# OpenStack contract packs live under contracts/openstack/<series>/.
# Optional override for active series at cold start (yoga|antelope|caracal|dalmatian).
openstackSeries: dalmatian
requestIdHeader: X-Request-ID
## Secrets. Prefer existingSecret in production.
secret:
# Create a Secret from the values below when existingSecret is empty.
create: true
existingSecret: ""
# Keys expected in an existing secret (when existingSecret is set):
# DATABASE_URL, TICKET_SIGNING_KEY
ticketSigningKey: "change-me-to-a-long-random-secret"
# Used only when postgresql.enabled=true and databaseUrl is empty.
databaseUrl: ""
## Bundled PostgreSQL (official image — same major as docker-compose.release.yml).
postgresql:
enabled: true
image:
repository: postgres
tag: "17.5-bookworm"
pullPolicy: IfNotPresent
auth:
username: openstack
password: openstack
database: openstack_simulator
persistence:
enabled: true
size: 8Gi
storageClass: ""
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
memory: 512Mi
## External database when postgresql.enabled=false.
externalDatabase:
host: ""
port: 5432
user: openstack
password: ""
database: openstack_simulator
existingSecret: ""
existingSecretPasswordKey: database-password
## Database migrations.
## Default: idempotent initContainer on the Deployment (recommended).
migrate:
enabled: true
asJob: false
backoffLimit: 20
activeDeadlineSeconds: 600
resources: {}
## Optional post-install seed Job (lab data).
## Profiles: minimal | demo (demo ≈ 1000 servers + full topology)
seed:
enabled: false
profile: minimal
backoffLimit: 3
resources: {}
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: "1"
memory: 1Gi
livenessProbe:
httpGet:
path: /health/live
port: http
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
readinessProbe:
httpGet:
path: /health/ready
port: http
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 8
nodeSelector: {}
tolerations: []
affinity: {}
## Ingress + TLS via cert-manager (Let's Encrypt).
## Backend is the api-gateway Keystone/UI port (5000) when gateway.enabled.
ingress:
enabled: false
className: nginx
annotations: {}
hosts:
- host: os-sim.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: openstack-api-simulator-tls
hosts:
- os-sim.example.com
## cert-manager ClusterIssuers for Let's Encrypt.
## Requires cert-manager already installed in the cluster.
certManager:
enabled: false
createClusterIssuer: true
email: admin@example.com
issuerName: letsencrypt-prod
server: https://acme-v02.api.letsencrypt.org/directory
createStagingIssuer: true
stagingIssuerName: letsencrypt-staging
stagingServer: https://acme-staging-v02.api.letsencrypt.org/directory
useStaging: false
solverIngressClassName: ""