mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-07 08:26:10 +00:00
* feat(helm): compose DATABASE_URL_READ_REPLICA from a reader host secret key Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(helm): cover reader host composition and readReplicaUrlKey precedence Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(helm): suppress unused reader host env when readReplicaUrlKey is set Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(helm): emit reader host only when readReplicaUrl composition is active Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: milan <milan@berri.ai> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Yassin Kortam <yassin@berri.ai>
494 lines
16 KiB
YAML
494 lines
16 KiB
YAML
suite: test deployment
|
|
templates:
|
|
- deployment.yaml
|
|
- configmap-litellm.yaml
|
|
tests:
|
|
- it: should work
|
|
template: deployment.yaml
|
|
set:
|
|
image.tag: test
|
|
asserts:
|
|
- isKind:
|
|
of: Deployment
|
|
- matchRegex:
|
|
path: metadata.name
|
|
pattern: -litellm$
|
|
- equal:
|
|
path: spec.template.spec.containers[0].image
|
|
value: ghcr.io/berriai/litellm:test
|
|
- it: should work with tolerations
|
|
template: deployment.yaml
|
|
set:
|
|
tolerations:
|
|
- key: node-role.kubernetes.io/master
|
|
operator: Exists
|
|
effect: NoSchedule
|
|
asserts:
|
|
- equal:
|
|
path: spec.template.spec.tolerations[0].key
|
|
value: node-role.kubernetes.io/master
|
|
- equal:
|
|
path: spec.template.spec.tolerations[0].operator
|
|
value: Exists
|
|
- it: should work with affinity
|
|
template: deployment.yaml
|
|
set:
|
|
affinity:
|
|
nodeAffinity:
|
|
requiredDuringSchedulingIgnoredDuringExecution:
|
|
nodeSelectorTerms:
|
|
- matchExpressions:
|
|
- key: topology.kubernetes.io/zone
|
|
operator: In
|
|
values:
|
|
- antarctica-east1
|
|
asserts:
|
|
- equal:
|
|
path: spec.template.spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].key
|
|
value: topology.kubernetes.io/zone
|
|
- equal:
|
|
path: spec.template.spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].operator
|
|
value: In
|
|
- equal:
|
|
path: spec.template.spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].values[0]
|
|
value: antarctica-east1
|
|
- it: should work without masterkeySecretName or masterkeySecretKey
|
|
template: deployment.yaml
|
|
set:
|
|
masterkeySecretName: ""
|
|
masterkeySecretKey: ""
|
|
asserts:
|
|
- contains:
|
|
path: spec.template.spec.containers[0].env
|
|
content:
|
|
name: PROXY_MASTER_KEY
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: RELEASE-NAME-litellm-masterkey
|
|
key: masterkey
|
|
- it: should work with masterkeySecretName and masterkeySecretKey
|
|
template: deployment.yaml
|
|
set:
|
|
masterkeySecretName: my-secret
|
|
masterkeySecretKey: my-key
|
|
asserts:
|
|
- contains:
|
|
path: spec.template.spec.containers[0].env
|
|
content:
|
|
name: PROXY_MASTER_KEY
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: my-secret
|
|
key: my-key
|
|
- it: should inject DATABASE_READER_HOST from readReplicaEndpointKey before DATABASE_URL_READ_REPLICA
|
|
template: deployment.yaml
|
|
set:
|
|
db:
|
|
deployStandalone: false
|
|
useExisting: true
|
|
secret:
|
|
name: postgres
|
|
usernameKey: username
|
|
passwordKey: password
|
|
readReplicaEndpointKey: reader-host
|
|
readReplicaUrl: postgresql://$(DATABASE_USERNAME):$(DATABASE_PASSWORD)@$(DATABASE_READER_HOST):5432/$(DATABASE_NAME)?sslmode=require
|
|
asserts:
|
|
- contains:
|
|
path: spec.template.spec.containers[0].env
|
|
content:
|
|
name: DATABASE_READER_HOST
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: postgres
|
|
key: reader-host
|
|
- contains:
|
|
path: spec.template.spec.containers[0].env
|
|
content:
|
|
name: DATABASE_URL_READ_REPLICA
|
|
value: postgresql://$(DATABASE_USERNAME):$(DATABASE_PASSWORD)@$(DATABASE_READER_HOST):5432/$(DATABASE_NAME)?sslmode=require
|
|
# $(VAR) interpolation only resolves vars defined EARLIER in the env
|
|
# array, so the reader host must precede the composed URL
|
|
- equal:
|
|
path: spec.template.spec.containers[0].env[7].name
|
|
value: DATABASE_READER_HOST
|
|
- equal:
|
|
path: spec.template.spec.containers[0].env[8].name
|
|
value: DATABASE_URL_READ_REPLICA
|
|
- it: should omit reader host when readReplicaUrl is unset
|
|
template: deployment.yaml
|
|
set:
|
|
db:
|
|
deployStandalone: false
|
|
useExisting: true
|
|
secret:
|
|
name: postgres
|
|
usernameKey: username
|
|
passwordKey: password
|
|
readReplicaEndpointKey: reader-host
|
|
asserts:
|
|
- notContains:
|
|
path: spec.template.spec.containers[0].env
|
|
content:
|
|
name: DATABASE_READER_HOST
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: postgres
|
|
key: reader-host
|
|
- it: should prefer readReplicaUrlKey over readReplicaEndpointKey composition
|
|
template: deployment.yaml
|
|
set:
|
|
db:
|
|
useExisting: true
|
|
secret:
|
|
name: postgres
|
|
usernameKey: username
|
|
passwordKey: password
|
|
readReplicaUrlKey: reader-url
|
|
readReplicaEndpointKey: reader-host
|
|
readReplicaUrl: postgresql://ignored
|
|
asserts:
|
|
- contains:
|
|
path: spec.template.spec.containers[0].env
|
|
content:
|
|
name: DATABASE_URL_READ_REPLICA
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: postgres
|
|
key: reader-url
|
|
- notContains:
|
|
path: spec.template.spec.containers[0].env
|
|
content:
|
|
name: DATABASE_URL_READ_REPLICA
|
|
value: postgresql://ignored
|
|
# the unused reader-host secret ref must be suppressed so a missing
|
|
# key can't fail pod creation
|
|
- notContains:
|
|
path: spec.template.spec.containers[0].env
|
|
content:
|
|
name: DATABASE_READER_HOST
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: postgres
|
|
key: reader-host
|
|
- it: should work with extraEnvVars
|
|
template: deployment.yaml
|
|
set:
|
|
extraEnvVars:
|
|
- name: EXTRA_ENV_VAR
|
|
valueFrom:
|
|
fieldRef:
|
|
fieldPath: metadata.labels['env']
|
|
asserts:
|
|
- contains:
|
|
path: spec.template.spec.containers[0].env
|
|
content:
|
|
name: EXTRA_ENV_VAR
|
|
valueFrom:
|
|
fieldRef:
|
|
fieldPath: metadata.labels['env']
|
|
- it: should work with both extraEnvVars and envVars
|
|
template: deployment.yaml
|
|
set:
|
|
envVars:
|
|
ENV_VAR: ENV_VAR_VALUE
|
|
extraEnvVars:
|
|
- name: EXTRA_ENV_VAR
|
|
value: EXTRA_ENV_VAR_VALUE
|
|
asserts:
|
|
- contains:
|
|
path: spec.template.spec.containers[0].env
|
|
content:
|
|
name: ENV_VAR
|
|
value: ENV_VAR_VALUE
|
|
- contains:
|
|
path: spec.template.spec.containers[0].env
|
|
content:
|
|
name: EXTRA_ENV_VAR
|
|
value: EXTRA_ENV_VAR_VALUE
|
|
- it: should mount existing configmap when create=false
|
|
template: deployment.yaml
|
|
set:
|
|
proxyConfigMap:
|
|
create: false
|
|
name: my-litellm-config
|
|
key: custom.yaml
|
|
asserts:
|
|
- contains:
|
|
path: spec.template.spec.volumes
|
|
content:
|
|
name: litellm-config
|
|
configMap:
|
|
name: my-litellm-config
|
|
items:
|
|
- key: custom.yaml
|
|
path: config.yaml
|
|
- contains:
|
|
path: spec.template.spec.containers[0].volumeMounts
|
|
content:
|
|
name: litellm-config
|
|
mountPath: /etc/litellm/config.yaml
|
|
subPath: config.yaml
|
|
- it: should work with lifecycle hooks
|
|
template: deployment.yaml
|
|
set:
|
|
lifecycle:
|
|
preStop:
|
|
exec:
|
|
command:
|
|
- /bin/sh
|
|
- -c
|
|
- echo "Container stopping"
|
|
asserts:
|
|
- exists:
|
|
path: spec.template.spec.containers[0].lifecycle
|
|
- equal:
|
|
path: spec.template.spec.containers[0].lifecycle.preStop.exec.command[0]
|
|
value: /bin/sh
|
|
- equal:
|
|
path: spec.template.spec.containers[0].lifecycle.preStop.exec.command[1]
|
|
value: -c
|
|
- equal:
|
|
path: spec.template.spec.containers[0].lifecycle.preStop.exec.command[2]
|
|
value: echo "Container stopping"
|
|
- it: should render background health check settings from proxy_config.general_settings
|
|
template: configmap-litellm.yaml
|
|
set:
|
|
proxy_config.general_settings.background_health_checks: true
|
|
proxy_config.general_settings.health_check_interval: 240
|
|
proxy_config.general_settings.health_check_concurrency: 16
|
|
proxy_config.general_settings.health_check_details: false
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["config.yaml"]
|
|
pattern: '(?m)^\s*background_health_checks:\s*true$'
|
|
- matchRegex:
|
|
path: data["config.yaml"]
|
|
pattern: '(?m)^\s*health_check_interval:\s*240$'
|
|
- matchRegex:
|
|
path: data["config.yaml"]
|
|
pattern: '(?m)^\s*health_check_concurrency:\s*16$'
|
|
- matchRegex:
|
|
path: data["config.yaml"]
|
|
pattern: '(?m)^\s*health_check_details:\s*false$'
|
|
- it: should allow overriding liveness, readiness, and startup probes
|
|
template: deployment.yaml
|
|
set:
|
|
livenessProbe:
|
|
path: /custom/livez
|
|
initialDelaySeconds: 5
|
|
periodSeconds: 15
|
|
timeoutSeconds: 5
|
|
successThreshold: 1
|
|
failureThreshold: 5
|
|
readinessProbe:
|
|
path: /custom/readyz
|
|
initialDelaySeconds: 10
|
|
periodSeconds: 20
|
|
timeoutSeconds: 6
|
|
successThreshold: 1
|
|
failureThreshold: 6
|
|
startupProbe:
|
|
path: /custom/startupz
|
|
initialDelaySeconds: 15
|
|
periodSeconds: 25
|
|
timeoutSeconds: 7
|
|
successThreshold: 1
|
|
failureThreshold: 40
|
|
asserts:
|
|
- equal:
|
|
path: spec.template.spec.containers[0].livenessProbe.httpGet.path
|
|
value: /custom/livez
|
|
- equal:
|
|
path: spec.template.spec.containers[0].livenessProbe.timeoutSeconds
|
|
value: 5
|
|
- equal:
|
|
path: spec.template.spec.containers[0].readinessProbe.httpGet.path
|
|
value: /custom/readyz
|
|
- equal:
|
|
path: spec.template.spec.containers[0].readinessProbe.timeoutSeconds
|
|
value: 6
|
|
- equal:
|
|
path: spec.template.spec.containers[0].startupProbe.httpGet.path
|
|
value: /custom/startupz
|
|
- equal:
|
|
path: spec.template.spec.containers[0].startupProbe.failureThreshold
|
|
value: 40
|
|
- it: should render container resources from values
|
|
template: deployment.yaml
|
|
set:
|
|
resources:
|
|
limits:
|
|
cpu: 500m
|
|
memory: 2Gi
|
|
requests:
|
|
cpu: 250m
|
|
memory: 1Gi
|
|
asserts:
|
|
- equal:
|
|
path: spec.template.spec.containers[0].resources.limits.cpu
|
|
value: 500m
|
|
- equal:
|
|
path: spec.template.spec.containers[0].resources.limits.memory
|
|
value: 2Gi
|
|
- equal:
|
|
path: spec.template.spec.containers[0].resources.requests.cpu
|
|
value: 250m
|
|
- equal:
|
|
path: spec.template.spec.containers[0].resources.requests.memory
|
|
value: 1Gi
|
|
- it: should keep default probes and empty resources unchanged
|
|
template: deployment.yaml
|
|
asserts:
|
|
- equal:
|
|
path: spec.template.spec.containers[0].livenessProbe.httpGet.path
|
|
value: /health/liveliness
|
|
- equal:
|
|
path: spec.template.spec.containers[0].livenessProbe.initialDelaySeconds
|
|
value: 0
|
|
- equal:
|
|
path: spec.template.spec.containers[0].livenessProbe.periodSeconds
|
|
value: 15
|
|
- equal:
|
|
path: spec.template.spec.containers[0].livenessProbe.timeoutSeconds
|
|
value: 5
|
|
- equal:
|
|
path: spec.template.spec.containers[0].livenessProbe.successThreshold
|
|
value: 1
|
|
- equal:
|
|
path: spec.template.spec.containers[0].livenessProbe.failureThreshold
|
|
value: 5
|
|
- equal:
|
|
path: spec.template.spec.containers[0].readinessProbe.httpGet.path
|
|
value: /health/readiness
|
|
- equal:
|
|
path: spec.template.spec.containers[0].readinessProbe.initialDelaySeconds
|
|
value: 0
|
|
- equal:
|
|
path: spec.template.spec.containers[0].readinessProbe.periodSeconds
|
|
value: 10
|
|
- equal:
|
|
path: spec.template.spec.containers[0].readinessProbe.timeoutSeconds
|
|
value: 5
|
|
- equal:
|
|
path: spec.template.spec.containers[0].readinessProbe.successThreshold
|
|
value: 1
|
|
- equal:
|
|
path: spec.template.spec.containers[0].readinessProbe.failureThreshold
|
|
value: 3
|
|
- equal:
|
|
path: spec.template.spec.containers[0].startupProbe.httpGet.path
|
|
value: /health/readiness
|
|
- equal:
|
|
path: spec.template.spec.containers[0].startupProbe.initialDelaySeconds
|
|
value: 0
|
|
- equal:
|
|
path: spec.template.spec.containers[0].startupProbe.periodSeconds
|
|
value: 10
|
|
- equal:
|
|
path: spec.template.spec.containers[0].startupProbe.timeoutSeconds
|
|
value: 5
|
|
- equal:
|
|
path: spec.template.spec.containers[0].startupProbe.successThreshold
|
|
value: 1
|
|
- equal:
|
|
path: spec.template.spec.containers[0].startupProbe.failureThreshold
|
|
value: 30
|
|
- equal:
|
|
path: spec.template.spec.containers[0].resources
|
|
value: {}
|
|
- it: should be able to set minReadySeconds
|
|
template: deployment.yaml
|
|
set:
|
|
deploymentMinReadySeconds: 5
|
|
asserts:
|
|
- equal:
|
|
path: spec.minReadySeconds
|
|
value: 5
|
|
- it: should have minReadySeconds absent when deploymentMinReadySeconds is not set
|
|
template: deployment.yaml
|
|
asserts:
|
|
- notExists:
|
|
path: spec.minReadySeconds
|
|
- it: should work with extraInitContainers
|
|
template: deployment.yaml
|
|
set:
|
|
extraInitContainers:
|
|
- name: init-test
|
|
image: busybox:latest
|
|
command: ["echo", "hello"]
|
|
asserts:
|
|
- contains:
|
|
path: spec.template.spec.initContainers
|
|
content:
|
|
name: init-test
|
|
image: busybox:latest
|
|
command: ["echo", "hello"]
|
|
- it: should support tpl in extraInitContainers
|
|
template: deployment.yaml
|
|
set:
|
|
image:
|
|
repository: ghcr.io/berriai/litellm
|
|
tag: test
|
|
extraInitContainers:
|
|
- name: init-tpl
|
|
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
|
command: ["echo", "hello"]
|
|
asserts:
|
|
- contains:
|
|
path: spec.template.spec.initContainers
|
|
content:
|
|
name: init-tpl
|
|
image: "ghcr.io/berriai/litellm:test"
|
|
command: ["echo", "hello"]
|
|
- it: should work with extraContainers
|
|
template: deployment.yaml
|
|
set:
|
|
extraContainers:
|
|
- name: sidecar
|
|
image: busybox:latest
|
|
asserts:
|
|
- contains:
|
|
path: spec.template.spec.containers
|
|
content:
|
|
name: sidecar
|
|
image: busybox:latest
|
|
- it: should support tpl in extraContainers
|
|
template: deployment.yaml
|
|
set:
|
|
image:
|
|
repository: ghcr.io/berriai/litellm
|
|
tag: test
|
|
extraContainers:
|
|
- name: sidecar-tpl
|
|
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
|
asserts:
|
|
- contains:
|
|
path: spec.template.spec.containers
|
|
content:
|
|
name: sidecar-tpl
|
|
image: "ghcr.io/berriai/litellm:test"
|
|
- it: should support tpl in podAnnotations
|
|
template: deployment.yaml
|
|
set:
|
|
image:
|
|
repository: ghcr.io/berriai/litellm
|
|
tag: test
|
|
# Mirrors the real-world scenario this feature unblocks:
|
|
# user disables the built-in ConfigMap (and its built-in checksum/config
|
|
# annotation) and re-implements checksum/config themselves via tpl.
|
|
proxyConfigMap:
|
|
create: false
|
|
podAnnotations:
|
|
checksum/config: "{{ .Values.image.tag }}"
|
|
example.com/some-key: "{{ .Values.image.repository }}"
|
|
example.com/literal: "plain-string-value"
|
|
asserts:
|
|
- equal:
|
|
path: spec.template.metadata.annotations["checksum/config"]
|
|
value: "test"
|
|
- equal:
|
|
path: spec.template.metadata.annotations["example.com/some-key"]
|
|
value: "ghcr.io/berriai/litellm"
|
|
- equal:
|
|
path: spec.template.metadata.annotations["example.com/literal"]
|
|
value: "plain-string-value"
|