From bdd5d8f909c50d90aa3f098759fb56fbe71a9545 Mon Sep 17 00:00:00 2001 From: yerkennz Date: Wed, 13 May 2026 14:19:39 +0500 Subject: [PATCH 1/5] feat: [CPCAP-9445] add integration atp test --- operator/api/apps/v1/postgresservice_types.go | 34 +++-- operator/api/apps/v1/zz_generated.deepcopy.go | 40 ++++++ operator/api/patroni/v1/patronicore_types.go | 34 +++-- .../api/patroni/v1/zz_generated.deepcopy.go | 40 ++++++ .../crds/netcracker.com_patronicores.yaml | 22 +++ .../charts/patroni-core/templates/cr.yaml | 25 ++++ .../templates/secrets/atp-storage-secret.yaml | 12 ++ operator/charts/patroni-core/values.yaml | 12 ++ .../crds/netcracker.com_patroniservices.yaml | 22 +++ .../charts/patroni-services/templates/cr.yaml | 26 ++++ .../templates/secrets/atp-storage-secret.yaml | 12 ++ operator/charts/patroni-services/values.yaml | 12 ++ operator/pkg/deployment/tests.go | 136 +++++++++++++++++- services/upgrade/docker/start.sh | 18 +-- tests/Dockerfile | 9 +- tests/docker/requirements.txt | 1 + tests/docker/robot-entrypoint.sh | 4 + 17 files changed, 428 insertions(+), 31 deletions(-) create mode 100644 operator/charts/patroni-core/templates/secrets/atp-storage-secret.yaml create mode 100644 operator/charts/patroni-services/templates/secrets/atp-storage-secret.yaml create mode 100644 tests/docker/robot-entrypoint.sh diff --git a/operator/api/apps/v1/postgresservice_types.go b/operator/api/apps/v1/postgresservice_types.go index 58882b51..d961aecf 100644 --- a/operator/api/apps/v1/postgresservice_types.go +++ b/operator/api/apps/v1/postgresservice_types.go @@ -201,15 +201,31 @@ type CustomQueries struct { } type IntegrationTests struct { - Resources *v1.ResourceRequirements `json:"resources,omitempty"` - DockerImage string `json:"image,omitempty"` - RunTestScenarios string `json:"runTestScenarios,omitempty"` - TestList []string `json:"testList,omitempty"` - Replicas int `json:"replicas,omitempty"` - PgNodeQty int `json:"pgNodeQty,omitempty"` - PodLabels map[string]string `json:"podLabels,omitempty"` - Affinity v1.Affinity `json:"affinity,omitempty"` - MonitoredImages string `json:"monitoredImages,omitempty"` + Resources *v1.ResourceRequirements `json:"resources,omitempty"` + DockerImage string `json:"image,omitempty"` + RunTestScenarios string `json:"runTestScenarios,omitempty"` + TestList []string `json:"testList,omitempty"` + Replicas int `json:"replicas,omitempty"` + PgNodeQty int `json:"pgNodeQty,omitempty"` + PodLabels map[string]string `json:"podLabels,omitempty"` + Affinity v1.Affinity `json:"affinity,omitempty"` + MonitoredImages string `json:"monitoredImages,omitempty"` + AtpStorage *AtpStorage `json:"atpStorage,omitempty"` + AtpReport *AtpReport `json:"atpReport,omitempty"` + AtpReportViewUiUrl string `json:"atpReportViewUiUrl,omitempty"` + EnvironmentName string `json:"environmentName,omitempty"` +} + +type AtpStorage struct { + Provider string `json:"provider,omitempty"` + ServerUrl string `json:"serverUrl,omitempty"` + ServerUiUrl string `json:"serverUiUrl,omitempty"` + Bucket string `json:"bucket,omitempty"` + Region string `json:"region,omitempty"` +} + +type AtpReport struct { + Enabled bool `json:"enabled,omitempty"` } // ExternalDataBase defines the desired state of ExternalDataBase diff --git a/operator/api/apps/v1/zz_generated.deepcopy.go b/operator/api/apps/v1/zz_generated.deepcopy.go index 68b58c34..4d48aaad 100644 --- a/operator/api/apps/v1/zz_generated.deepcopy.go +++ b/operator/api/apps/v1/zz_generated.deepcopy.go @@ -10,6 +10,36 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AtpReport) DeepCopyInto(out *AtpReport) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AtpReport. +func (in *AtpReport) DeepCopy() *AtpReport { + if in == nil { + return nil + } + out := new(AtpReport) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AtpStorage) DeepCopyInto(out *AtpStorage) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AtpStorage. +func (in *AtpStorage) DeepCopy() *AtpStorage { + if in == nil { + return nil + } + out := new(AtpStorage) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BackupDaemon) DeepCopyInto(out *BackupDaemon) { *out = *in @@ -146,6 +176,16 @@ func (in *IntegrationTests) DeepCopyInto(out *IntegrationTests) { } } in.Affinity.DeepCopyInto(&out.Affinity) + if in.AtpStorage != nil { + in, out := &in.AtpStorage, &out.AtpStorage + *out = new(AtpStorage) + **out = **in + } + if in.AtpReport != nil { + in, out := &in.AtpReport, &out.AtpReport + *out = new(AtpReport) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IntegrationTests. diff --git a/operator/api/patroni/v1/patronicore_types.go b/operator/api/patroni/v1/patronicore_types.go index 09a3eba3..2ba17eb5 100644 --- a/operator/api/patroni/v1/patronicore_types.go +++ b/operator/api/patroni/v1/patronicore_types.go @@ -180,15 +180,31 @@ type Dcs struct { } type IntegrationTests struct { - Resources *v1.ResourceRequirements `json:"resources,omitempty"` - DockerImage string `json:"image,omitempty"` - RunTestScenarios string `json:"runTestScenarios,omitempty"` - TestList []string `json:"testList,omitempty"` - Replicas int `json:"replicas,omitempty"` - PgNodeQty int `json:"pgNodeQty,omitempty"` - PodLabels map[string]string `json:"podLabels,omitempty"` - Affinity v1.Affinity `json:"affinity,omitempty"` - MonitoredImages string `json:"monitoredImages,omitempty"` + Resources *v1.ResourceRequirements `json:"resources,omitempty"` + DockerImage string `json:"image,omitempty"` + RunTestScenarios string `json:"runTestScenarios,omitempty"` + TestList []string `json:"testList,omitempty"` + Replicas int `json:"replicas,omitempty"` + PgNodeQty int `json:"pgNodeQty,omitempty"` + PodLabels map[string]string `json:"podLabels,omitempty"` + Affinity v1.Affinity `json:"affinity,omitempty"` + MonitoredImages string `json:"monitoredImages,omitempty"` + AtpStorage *AtpStorage `json:"atpStorage,omitempty"` + AtpReport *AtpReport `json:"atpReport,omitempty"` + AtpReportViewUiUrl string `json:"atpReportViewUiUrl,omitempty"` + EnvironmentName string `json:"environmentName,omitempty"` +} + +type AtpStorage struct { + Provider string `json:"provider,omitempty"` + ServerUrl string `json:"serverUrl,omitempty"` + ServerUiUrl string `json:"serverUiUrl,omitempty"` + Bucket string `json:"bucket,omitempty"` + Region string `json:"region,omitempty"` +} + +type AtpReport struct { + Enabled bool `json:"enabled,omitempty"` } type Policies struct { diff --git a/operator/api/patroni/v1/zz_generated.deepcopy.go b/operator/api/patroni/v1/zz_generated.deepcopy.go index a9ed7bd0..d17f01b4 100644 --- a/operator/api/patroni/v1/zz_generated.deepcopy.go +++ b/operator/api/patroni/v1/zz_generated.deepcopy.go @@ -10,6 +10,36 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AtpReport) DeepCopyInto(out *AtpReport) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AtpReport. +func (in *AtpReport) DeepCopy() *AtpReport { + if in == nil { + return nil + } + out := new(AtpReport) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AtpStorage) DeepCopyInto(out *AtpStorage) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AtpStorage. +func (in *AtpStorage) DeepCopy() *AtpStorage { + if in == nil { + return nil + } + out := new(AtpStorage) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ConsulRegistration) DeepCopyInto(out *ConsulRegistration) { *out = *in @@ -110,6 +140,16 @@ func (in *IntegrationTests) DeepCopyInto(out *IntegrationTests) { } } in.Affinity.DeepCopyInto(&out.Affinity) + if in.AtpStorage != nil { + in, out := &in.AtpStorage, &out.AtpStorage + *out = new(AtpStorage) + **out = **in + } + if in.AtpReport != nil { + in, out := &in.AtpReport, &out.AtpReport + *out = new(AtpReport) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IntegrationTests. diff --git a/operator/charts/patroni-core/crds/netcracker.com_patronicores.yaml b/operator/charts/patroni-core/crds/netcracker.com_patronicores.yaml index 5017993a..491ebb0b 100644 --- a/operator/charts/patroni-core/crds/netcracker.com_patronicores.yaml +++ b/operator/charts/patroni-core/crds/netcracker.com_patronicores.yaml @@ -1017,6 +1017,28 @@ spec: x-kubernetes-list-type: atomic type: object type: object + atpReport: + properties: + enabled: + type: boolean + type: object + atpReportViewUiUrl: + type: string + atpStorage: + properties: + bucket: + type: string + provider: + type: string + region: + type: string + serverUiUrl: + type: string + serverUrl: + type: string + type: object + environmentName: + type: string image: type: string monitoredImages: diff --git a/operator/charts/patroni-core/templates/cr.yaml b/operator/charts/patroni-core/templates/cr.yaml index 1c1fd98d..025f2107 100644 --- a/operator/charts/patroni-core/templates/cr.yaml +++ b/operator/charts/patroni-core/templates/cr.yaml @@ -253,6 +253,31 @@ spec: {{- end }} {{- end }} pgNodeQty: {{ ( include "postgres.replicasCount" . ) }} + +{{- if or .Values.tests.environmentName .Values.tests.atpReport.enabled .Values.tests.atpReportViewUiUrl }} +{{- if .Values.tests.environmentName }} + environmentName: {{ .Values.tests.environmentName | quote }} +{{- end }} + atpReport: + enabled: {{ .Values.tests.atpReport.enabled | default false }} +{{- if .Values.tests.atpReport.enabled }} + atpStorage: + provider: {{ .Values.tests.atpReport.atpStorage.provider | quote }} + region: {{ .Values.tests.atpReport.atpStorage.region | quote }} +{{- if .Values.tests.atpReport.atpStorage.serverUrl }} + serverUrl: {{ .Values.tests.atpReport.atpStorage.serverUrl | quote }} +{{- end }} +{{- if .Values.tests.atpReport.atpStorage.serverUiUrl }} + serverUiUrl: {{ .Values.tests.atpReport.atpStorage.serverUiUrl | quote }} +{{- end }} +{{- if .Values.tests.atpReport.atpStorage.bucket }} + bucket: {{ .Values.tests.atpReport.atpStorage.bucket | quote }} +{{- end }} +{{- end }} +{{- if .Values.tests.atpReportViewUiUrl }} + atpReportViewUiUrl: {{ .Values.tests.atpReportViewUiUrl | quote }} +{{- end }} +{{- end }} {{ end }} {{ if .Values.runTestsOnly }} runTestsTime: {{ now | unixEpoch | quote }} diff --git a/operator/charts/patroni-core/templates/secrets/atp-storage-secret.yaml b/operator/charts/patroni-core/templates/secrets/atp-storage-secret.yaml new file mode 100644 index 00000000..12260ff2 --- /dev/null +++ b/operator/charts/patroni-core/templates/secrets/atp-storage-secret.yaml @@ -0,0 +1,12 @@ +{{- if and .Values.tests.install .Values.tests.atpReport.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: patroni-core-atp-storage-secret + labels: + {{- include "kubernetes.labels" . | nindent 4 }} +type: Opaque +stringData: + atp-storage-username: {{ .Values.tests.atpReport.atpStorage.username | quote }} + atp-storage-password: {{ .Values.tests.atpReport.atpStorage.password | quote }} +{{- end }} \ No newline at end of file diff --git a/operator/charts/patroni-core/values.yaml b/operator/charts/patroni-core/values.yaml index 95cc749e..568d852b 100644 --- a/operator/charts/patroni-core/values.yaml +++ b/operator/charts/patroni-core/values.yaml @@ -288,6 +288,18 @@ tests: - check_scale_down_master - check_delete_master - check_manual_switchover + atpReport: + enabled: false + atpStorage: + provider: "aws" + serverUrl: "https://s3.amazonaws.com" + serverUiUrl: "" + bucket: "" + region: "us-east-1" + username: "" + password: "" + atpReportViewUiUrl: "" + environmentName: "pgskipper-operator" runTestsOnly: false diff --git a/operator/charts/patroni-services/crds/netcracker.com_patroniservices.yaml b/operator/charts/patroni-services/crds/netcracker.com_patroniservices.yaml index c9fb5086..e2143f0a 100644 --- a/operator/charts/patroni-services/crds/netcracker.com_patroniservices.yaml +++ b/operator/charts/patroni-services/crds/netcracker.com_patroniservices.yaml @@ -3553,6 +3553,28 @@ spec: x-kubernetes-list-type: atomic type: object type: object + atpReport: + properties: + enabled: + type: boolean + type: object + atpReportViewUiUrl: + type: string + atpStorage: + properties: + bucket: + type: string + provider: + type: string + region: + type: string + serverUiUrl: + type: string + serverUrl: + type: string + type: object + environmentName: + type: string image: type: string monitoredImages: diff --git a/operator/charts/patroni-services/templates/cr.yaml b/operator/charts/patroni-services/templates/cr.yaml index 522aaf63..89f9e5d7 100644 --- a/operator/charts/patroni-services/templates/cr.yaml +++ b/operator/charts/patroni-services/templates/cr.yaml @@ -433,6 +433,32 @@ spec: {{- end }} {{- end }} pgNodeQty: {{ default "1" .Values.patroni.replicas }} +{{- if or .Values.tests.environmentName .Values.tests.atpReport.enabled .Values.tests.atpReportViewUiUrl }} + {{- if .Values.tests.environmentName }} + environmentName: {{ .Values.tests.environmentName | quote }} + {{- end }} + atpReport: + enabled: {{ .Values.tests.atpReport.enabled | default false }} + {{- if .Values.tests.atpReport.enabled }} + atpStorage: + provider: {{ .Values.tests.atpReport.atpStorage.provider | quote }} + {{- if .Values.tests.atpReport.atpStorage.serverUrl }} + serverUrl: {{ .Values.tests.atpReport.atpStorage.serverUrl | quote }} + {{- end }} + {{- if .Values.tests.atpReport.atpStorage.serverUiUrl }} + serverUiUrl: {{ .Values.tests.atpReport.atpStorage.serverUiUrl | quote }} + {{- end }} + {{- if .Values.tests.atpReport.atpStorage.bucket }} + bucket: {{ .Values.tests.atpReport.atpStorage.bucket | quote }} + {{- end }} + {{- if .Values.tests.atpReport.atpStorage.region }} + region: {{ .Values.tests.atpReport.atpStorage.region | quote }} + {{- end }} + {{- end }} + {{- if .Values.tests.atpReportViewUiUrl }} + atpReportViewUiUrl: {{ .Values.tests.atpReportViewUiUrl | quote }} + {{- end }} +{{- end }} {{ end }} {{ if .Values.runTestsOnly }} runTestsTime: {{ now | unixEpoch | quote }} diff --git a/operator/charts/patroni-services/templates/secrets/atp-storage-secret.yaml b/operator/charts/patroni-services/templates/secrets/atp-storage-secret.yaml new file mode 100644 index 00000000..cc37bae1 --- /dev/null +++ b/operator/charts/patroni-services/templates/secrets/atp-storage-secret.yaml @@ -0,0 +1,12 @@ +{{- if and .Values.tests.install .Values.tests.atpReport.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "helm-chart.fullname" . }}-tests-atp-storage-secret + labels: + {{- include "kubernetes.labels" . | nindent 4 }} +type: Opaque +stringData: + atp-storage-username: {{ .Values.tests.atpReport.atpStorage.username }} + atp-storage-password: {{ .Values.tests.atpReport.atpStorage.password }} +{{- end }} \ No newline at end of file diff --git a/operator/charts/patroni-services/values.yaml b/operator/charts/patroni-services/values.yaml index b6eca4e7..a36fc636 100644 --- a/operator/charts/patroni-services/values.yaml +++ b/operator/charts/patroni-services/values.yaml @@ -455,6 +455,18 @@ tests: - check_evict_api - check_granular_api - check_wal_archiving + atpReport: + enabled: false + atpStorage: + provider: "aws" + serverUrl: "https://s3.amazonaws.com" + serverUiUrl: "" + bucket: "" + region: "us-east-1" + username: "" + password: "" + atpReportViewUiUrl: "" + environmentName: "pgskipper-operator" runTestsOnly: false diff --git a/operator/pkg/deployment/tests.go b/operator/pkg/deployment/tests.go index fe097341..43d1816b 100644 --- a/operator/pkg/deployment/tests.go +++ b/operator/pkg/deployment/tests.go @@ -72,7 +72,6 @@ func NewIntegrationTestsPod(cr *v1.PatroniServices, cluster *patroniv1.PatroniCl Image: dockerImage, ImagePullPolicy: cr.Spec.ImagePullPolicy, SecurityContext: util.GetDefaultSecurityContext(), - Args: []string{"robot", "-i", testsTags, "/test_runs/"}, Env: []corev1.EnvVar{ { Name: "POSTGRES_USER", @@ -144,6 +143,11 @@ func NewIntegrationTestsPod(cr *v1.PatroniServices, cluster *patroniv1.PatroniCl pod.Spec.ImagePullSecrets = append(pod.Spec.ImagePullSecrets, corev1.LocalObjectReference{Name: name}) } } + pod.Spec.Containers[0].Env = appendAtpEnvVarsServices( + pod.Spec.Containers[0].Env, + testsSpec, + fmt.Sprintf("%s-atp-storage-secret", cr.Name), + ) return pod } @@ -191,7 +195,6 @@ func NewCoreIntegrationTests(cr *patroniv1.PatroniCore, cluster *patroniv1.Patro Image: dockerImage, ImagePullPolicy: cr.Spec.ImagePullPolicy, SecurityContext: util.GetDefaultSecurityContext(), - Args: []string{"robot", "-i", testsTags, "/test_runs/"}, Env: []corev1.EnvVar{ { Name: "POSTGRES_USER", @@ -267,6 +270,135 @@ func NewCoreIntegrationTests(cr *patroniv1.PatroniCore, cluster *patroniv1.Patro pod.Spec.ImagePullSecrets = append(pod.Spec.ImagePullSecrets, corev1.LocalObjectReference{Name: name}) } } + pod.Spec.Containers[0].Env = appendAtpEnvVarsCore( + pod.Spec.Containers[0].Env, + testsSpec, + fmt.Sprintf("%s-atp-storage-secret", cr.Name), + ) return pod } + +func appendAtpEnvVarsServices(env []corev1.EnvVar, tests *v1.IntegrationTests, secretName string) []corev1.EnvVar { + if tests == nil { + return env + } + + if tests.EnvironmentName != "" { + env = append(env, corev1.EnvVar{Name: "ENVIRONMENT_NAME", Value: tests.EnvironmentName}) + } + + if tests.AtpStorage != nil && tests.AtpStorage.Provider != "" { + env = append(env, corev1.EnvVar{Name: "ATP_STORAGE_PROVIDER", Value: tests.AtpStorage.Provider}) + + if tests.AtpStorage.Region != "" { + env = append(env, corev1.EnvVar{Name: "ATP_STORAGE_REGION", Value: tests.AtpStorage.Region}) + } + if tests.AtpStorage.ServerUrl != "" { + env = append(env, corev1.EnvVar{Name: "ATP_STORAGE_SERVER_URL", Value: tests.AtpStorage.ServerUrl}) + } + if tests.AtpStorage.ServerUiUrl != "" { + env = append(env, corev1.EnvVar{Name: "ATP_STORAGE_SERVER_UI_URL", Value: tests.AtpStorage.ServerUiUrl}) + } + if tests.AtpStorage.Bucket != "" { + env = append(env, corev1.EnvVar{Name: "ATP_STORAGE_BUCKET", Value: tests.AtpStorage.Bucket}) + } + } + + atpReportEnabled := false + if tests.AtpReport != nil { + atpReportEnabled = tests.AtpReport.Enabled + } + env = append(env, corev1.EnvVar{Name: "ATP_REPORT_ENABLED", Value: strconv.FormatBool(atpReportEnabled)}) + + if atpReportEnabled { + env = append(env, + corev1.EnvVar{ + Name: "ATP_STORAGE_USERNAME", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: secretName}, + Key: "atp-storage-username", + }, + }, + }, + corev1.EnvVar{ + Name: "ATP_STORAGE_PASSWORD", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: secretName}, + Key: "atp-storage-password", + }, + }, + }, + ) + } + + if tests.AtpReportViewUiUrl != "" { + env = append(env, corev1.EnvVar{Name: "ATP_REPORT_VIEW_UI_URL", Value: tests.AtpReportViewUiUrl}) + } + + return env +} + +func appendAtpEnvVarsCore(env []corev1.EnvVar, tests *patroniv1.IntegrationTests, secretName string) []corev1.EnvVar { + if tests == nil { + return env + } + + if tests.EnvironmentName != "" { + env = append(env, corev1.EnvVar{Name: "ENVIRONMENT_NAME", Value: tests.EnvironmentName}) + } + + if tests.AtpStorage != nil && tests.AtpStorage.Provider != "" { + env = append(env, corev1.EnvVar{Name: "ATP_STORAGE_PROVIDER", Value: tests.AtpStorage.Provider}) + + if tests.AtpStorage.Region != "" { + env = append(env, corev1.EnvVar{Name: "ATP_STORAGE_REGION", Value: tests.AtpStorage.Region}) + } + if tests.AtpStorage.ServerUrl != "" { + env = append(env, corev1.EnvVar{Name: "ATP_STORAGE_SERVER_URL", Value: tests.AtpStorage.ServerUrl}) + } + if tests.AtpStorage.ServerUiUrl != "" { + env = append(env, corev1.EnvVar{Name: "ATP_STORAGE_SERVER_UI_URL", Value: tests.AtpStorage.ServerUiUrl}) + } + if tests.AtpStorage.Bucket != "" { + env = append(env, corev1.EnvVar{Name: "ATP_STORAGE_BUCKET", Value: tests.AtpStorage.Bucket}) + } + } + + atpReportEnabled := false + if tests.AtpReport != nil { + atpReportEnabled = tests.AtpReport.Enabled + } + env = append(env, corev1.EnvVar{Name: "ATP_REPORT_ENABLED", Value: strconv.FormatBool(atpReportEnabled)}) + + if atpReportEnabled { + env = append(env, + corev1.EnvVar{ + Name: "ATP_STORAGE_USERNAME", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: secretName}, + Key: "atp-storage-username", + }, + }, + }, + corev1.EnvVar{ + Name: "ATP_STORAGE_PASSWORD", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: secretName}, + Key: "atp-storage-password", + }, + }, + }, + ) + } + + if tests.AtpReportViewUiUrl != "" { + env = append(env, corev1.EnvVar{Name: "ATP_REPORT_VIEW_UI_URL", Value: tests.AtpReportViewUiUrl}) + } + + return env +} diff --git a/services/upgrade/docker/start.sh b/services/upgrade/docker/start.sh index 7ee00ed9..2b3b651e 100755 --- a/services/upgrade/docker/start.sh +++ b/services/upgrade/docker/start.sh @@ -21,11 +21,11 @@ SLEEP_BETWEEN_ITERATIONS=5 function handle_master_upgrade() { cd /var/lib/pgsql/data/ - echo "[$(date +%Y-%m-%dT%H:%M:%S)] cur path: `pwd`" + echo "[$(date +%Y-%m-%dT%H:%M:%S)] cur path: $(pwd)" - DB_SIZE_GB_FLOAT=$(du -sk /var/lib/pgsql/data/${DATA_DIR} | awk '{ print $1 / 1024 / 1024 }') - DB_SIZE_GB=`printf "%.0f\n" ${DB_SIZE_GB_FLOAT}` + DB_SIZE_GB_FLOAT=$(du -sk "/var/lib/pgsql/data/${DATA_DIR}" | awk '{ print $1 / 1024 / 1024 }') + DB_SIZE_GB=$(printf "%.0f\n" "${DB_SIZE_GB_FLOAT}") PV_SIZE_GB=$(echo "${PV_SIZE}" | tr -dc '0-9') echo @@ -53,7 +53,7 @@ function handle_master_upgrade() { fi else echo "[$(date +%Y-%m-%dT%H:%M:%S)] Migration PV is NOT used, check if there is enough space for migration in master PV" - DOUBLE_DB_SIZE="$((${DB_SIZE_GB} * 2))" + DOUBLE_DB_SIZE="$((DB_SIZE_GB * 2))" if [[ ${DOUBLE_DB_SIZE} -gt ${PV_SIZE_GB} ]]; then echo "[$(date +%Y-%m-%dT%H:%M:%S)] DB size is more than PV size, exiting ..." exit 1 @@ -69,7 +69,7 @@ function handle_master_upgrade() { echo "[$(date +%Y-%m-%dT%H:%M:%S)] initialize complete, copying configs" mkdir "/tmp/configs/" - cp /var/lib/pgsql/data/${DATA_DIR}/*.conf "/tmp/configs/" + cp /var/lib/pgsql/data/"${DATA_DIR}"/*.conf "/tmp/configs/" echo "turning off wal archiving" sed -e '/archive_command/ s/^#*/#/' -i "$MIGRATION_PATH/tmp/pg/postgresql.conf" @@ -87,18 +87,18 @@ function handle_master_upgrade() { echo "[$(date +%Y-%m-%dT%H:%M:%S)] making chmod 750 to datadir" - chmod 750 $MIGRATION_PATH/${DATA_DIR} + chmod 750 "${MIGRATION_PATH}/${DATA_DIR}" - SHARED_PRELOAD_LIBRARIES=$(grep "shared_preload_libraries" /var/lib/pgsql/data/${DATA_DIR}/postgresql.conf) + SHARED_PRELOAD_LIBRARIES=$(grep "shared_preload_libraries" "/var/lib/pgsql/data/${DATA_DIR}/postgresql.conf") if [[ -z ${SHARED_PRELOAD_LIBRARIES} ]]; then echo "shared_preload_libraries is not found in PostgreSQL config, please check PostgreSQL params, exiting..." exit 1 fi - echo ${SHARED_PRELOAD_LIBRARIES} >> $MIGRATION_PATH/tmp/pg/postgresql.conf + echo "${SHARED_PRELOAD_LIBRARIES}" >> "${MIGRATION_PATH}/tmp/pg/postgresql.conf" - ls -la $MIGRATION_PATH + ls -la "${MIGRATION_PATH}" echo "[$(date +%Y-%m-%dT%H:%M:%S)] Check cluster before upgrade" /usr/lib/postgresql/"${PG_VERSION_TARGET}"/bin/pg_upgrade \ diff --git a/tests/Dockerfile b/tests/Dockerfile index 541a84a4..37bb9fbf 100644 --- a/tests/Dockerfile +++ b/tests/Dockerfile @@ -3,6 +3,8 @@ FROM ghcr.io/netcracker/qubership-docker-integration-tests:0.4.3 ENV LC_ALL=en_US.UTF-8 \ LANG=en_US.UTF-8 +USER root + COPY docker/pip.conf /root/.pip/pip.conf COPY docker/requirements.txt /root/requirements.txt @@ -17,11 +19,14 @@ RUN chmod -R g=u /etc/passwd && \ COPY ./app/* /app/ COPY robot /test_runs/ +COPY robot ${ROBOT_HOME}/tests COPY docker/uid_entrypoint /opt/uid_entrypoint +COPY docker/robot-entrypoint.sh /opt/robot-entrypoint.sh RUN chgrp -R 0 /app && chmod g+w /app && \ chgrp -R 0 /test_runs && chmod -R g+w /test_runs && \ - chmod +x /opt/uid_entrypoint + chmod +x /opt/uid_entrypoint /opt/robot-entrypoint.sh && \ + chown -R 1000:0 "${ROBOT_HOME}" && chmod -R 775 "${ROBOT_HOME}" # Volumes are defined to support read-only root file system VOLUME /etc @@ -33,4 +38,4 @@ USER 1001 WORKDIR /app ENTRYPOINT [ "/opt/uid_entrypoint" ] -CMD ["robot -i ${TESTS_TAGS} /test_runs/"] +CMD ["/opt/robot-entrypoint.sh", "run-robot"] diff --git a/tests/docker/requirements.txt b/tests/docker/requirements.txt index a8b86eb1..cc74d962 100644 --- a/tests/docker/requirements.txt +++ b/tests/docker/requirements.txt @@ -1,3 +1,4 @@ +allure-robotframework==2.15.0 aniso8601==10.0.1 cachetools==4.2.4 certifi==2026.4.22 diff --git a/tests/docker/robot-entrypoint.sh b/tests/docker/robot-entrypoint.sh new file mode 100644 index 00000000..0d0a107d --- /dev/null +++ b/tests/docker/robot-entrypoint.sh @@ -0,0 +1,4 @@ +#!/bin/bash +export TAGS="${TESTS_TAGS:-$TAGS}" +cd "${ROBOT_HOME:-/opt/robot}" || exit 1 +exec /docker-entrypoint.sh "$@" \ No newline at end of file From 358febad7fc9ba52b190e76fb7a259419cbad130 Mon Sep 17 00:00:00 2001 From: yerkennz Date: Wed, 13 May 2026 17:43:08 +0500 Subject: [PATCH 2/5] fix: secret naming --- .../patroni-services/templates/secrets/atp-storage-secret.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operator/charts/patroni-services/templates/secrets/atp-storage-secret.yaml b/operator/charts/patroni-services/templates/secrets/atp-storage-secret.yaml index cc37bae1..fbcda56e 100644 --- a/operator/charts/patroni-services/templates/secrets/atp-storage-secret.yaml +++ b/operator/charts/patroni-services/templates/secrets/atp-storage-secret.yaml @@ -2,7 +2,7 @@ apiVersion: v1 kind: Secret metadata: - name: {{ include "helm-chart.fullname" . }}-tests-atp-storage-secret + name: {{ include "helm-chart.fullname" . }}-atp-storage-secret labels: {{- include "kubernetes.labels" . | nindent 4 }} type: Opaque From 54e24c195877638f06922888c56e837c6ecb69fe Mon Sep 17 00:00:00 2001 From: yerkennz Date: Wed, 13 May 2026 19:20:07 +0500 Subject: [PATCH 3/5] fix: atp storage services naming --- .../templates/secrets/atp-storage-secret.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/operator/charts/patroni-services/templates/secrets/atp-storage-secret.yaml b/operator/charts/patroni-services/templates/secrets/atp-storage-secret.yaml index fbcda56e..e0151f69 100644 --- a/operator/charts/patroni-services/templates/secrets/atp-storage-secret.yaml +++ b/operator/charts/patroni-services/templates/secrets/atp-storage-secret.yaml @@ -2,11 +2,11 @@ apiVersion: v1 kind: Secret metadata: - name: {{ include "helm-chart.fullname" . }}-atp-storage-secret + name: patroni-services-atp-storage-secret labels: {{- include "kubernetes.labels" . | nindent 4 }} type: Opaque stringData: - atp-storage-username: {{ .Values.tests.atpReport.atpStorage.username }} - atp-storage-password: {{ .Values.tests.atpReport.atpStorage.password }} + atp-storage-username: {{ .Values.tests.atpReport.atpStorage.username | quote }} + atp-storage-password: {{ .Values.tests.atpReport.atpStorage.password | quote }} {{- end }} \ No newline at end of file From 36ea7c4169f7b2fdf6b357f0c0901314ce5dd020 Mon Sep 17 00:00:00 2001 From: yerkennz Date: Wed, 13 May 2026 19:47:01 +0500 Subject: [PATCH 4/5] fix: run robot without ttyd --- .../patroni-services/templates/secrets/atp-storage-secret.yaml | 2 +- tests/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/operator/charts/patroni-services/templates/secrets/atp-storage-secret.yaml b/operator/charts/patroni-services/templates/secrets/atp-storage-secret.yaml index e0151f69..21931fa5 100644 --- a/operator/charts/patroni-services/templates/secrets/atp-storage-secret.yaml +++ b/operator/charts/patroni-services/templates/secrets/atp-storage-secret.yaml @@ -2,7 +2,7 @@ apiVersion: v1 kind: Secret metadata: - name: patroni-services-atp-storage-secret + name: {{ include "helm-chart.fullname" . }}-atp-storage-secret labels: {{- include "kubernetes.labels" . | nindent 4 }} type: Opaque diff --git a/tests/Dockerfile b/tests/Dockerfile index 37bb9fbf..42abdb2a 100644 --- a/tests/Dockerfile +++ b/tests/Dockerfile @@ -38,4 +38,4 @@ USER 1001 WORKDIR /app ENTRYPOINT [ "/opt/uid_entrypoint" ] -CMD ["/opt/robot-entrypoint.sh", "run-robot"] +CMD ["/opt/robot-entrypoint.sh", "run-robot-without-ttyd"] From 87d72002f74b991934366c630c35f3328c39071b Mon Sep 17 00:00:00 2001 From: yerkennz Date: Thu, 14 May 2026 14:46:08 +0500 Subject: [PATCH 5/5] feat: add integration tests docs --- docs/public/installation.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/public/installation.md b/docs/public/installation.md index 95ad0f6e..db1343b5 100644 --- a/docs/public/installation.md +++ b/docs/public/installation.md @@ -525,6 +525,26 @@ This sections describes all possible deploy parameters to run Integration Tests | tests.runTestScenarios | string | no | basic | Specifies tests level runs. One of 'full', 'basic' or one of testScenarios. | | tests.testScenarios | map[string][]string | no | n/a | Specifies list of test tags to run. | +## Integration tests and ATP storage + +Integration test settings live under `tests` in the Helm values for **patroni-core** and **patroni-services** (see [`operator/charts/patroni-core/values.yaml`](operator/charts/patroni-core/values.yaml) and [`operator/charts/patroni-services/values.yaml`](operator/charts/patroni-services/values.yaml)). The test image is based on [qubership-docker-integration-tests](https://github.com/Netcracker/qubership-docker-integration-tests). + +ATP-related Helm values are `atpReport` (with nested `atpReport.atpStorage`), `atpReportViewUiUrl`, and `environmentName`. The chart maps them into the Custom Resource and the operator sets the usual `ATP_*` and `ENVIRONMENT_NAME` environment variables on the integration test pod. + +| Value (Helm) | Description | +|------------------------------------|-------------| +| `atpReport.enabled` | Opt-in for ATP report upload; when `false`, S3-related env vars are not applied as in other product charts. | +| `atpReport.atpStorage.provider` | S3 provider (for example `aws`, `minio`, `s3`). | +| `atpReport.atpStorage.serverUrl` | S3 API endpoint URL. | +| `atpReport.atpStorage.serverUiUrl` | Optional storage UI URL. | +| `atpReport.atpStorage.bucket` | Bucket name; empty usually means no S3 upload in the base image flow. | +| `atpReport.atpStorage.region` | Region (for example for AWS). | +| `atpReport.atpStorage.username` | Access key (sensitive; stored in Kubernetes Secret when `atpReport.enabled=true`). | +| `atpReport.atpStorage.password` | Secret key (same as username; stored in Kubernetes Secret when `atpReport.enabled=true`). | +| `atpReportViewUiUrl` | Optional Allure report UI base URL. | +| `environmentName` | Optional logical name for paths or labels. | + + ## consulRegistration Postgres Operator allows register of PostgreSQL connection properties in Consul. By default, registration disabled.