From 3026d032b3d87d753b1be6448ed580407895737b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Wed, 11 Mar 2026 11:14:06 +0400 Subject: [PATCH] =?UTF-8?q?test:=20run=5Fstress=5Ftest.sh=20=E2=80=94=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D0=BD=D1=8B=D0=B9=20=D1=81=D1=82=D1=80=D0=B5?= =?UTF-8?q?=D1=81=D1=81-=D1=82=D0=B5=D1=81=D1=82=20=D0=B2=D1=81=D0=B5?= =?UTF-8?q?=D1=85=20=D0=BF=D1=80=D0=B8=D0=BC=D0=B5=D1=80=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .e2e-logs/hello-node-apply-modify.log | 36 ++ .e2e-logs/hello-node-apply.log | 95 ++++ .e2e-logs/hello-node-destroy.log | 112 ++++ .e2e-logs/hello-node-init.log | 22 + .e2e-logs/simple-node-apply-modify.log | 32 ++ .e2e-logs/simple-node-apply.log | 86 +++ .e2e-logs/simple-node-destroy.log | 92 ++++ .e2e-logs/simple-node-init.log | 22 + .e2e-logs/simple-python-apply-modify.log | 32 ++ .e2e-logs/simple-python-apply.log | 86 +++ .e2e-logs/simple-python-destroy.log | 92 ++++ .e2e-logs/simple-python-init.log | 22 + .gitignore | 1 + run_stress_test.sh | 662 +++++++++++++++++++++++ test_pearlharbor_push.sh | 100 ++++ 15 files changed, 1492 insertions(+) create mode 100644 .e2e-logs/hello-node-apply-modify.log create mode 100644 .e2e-logs/hello-node-apply.log create mode 100644 .e2e-logs/hello-node-destroy.log create mode 100644 .e2e-logs/hello-node-init.log create mode 100644 .e2e-logs/simple-node-apply-modify.log create mode 100644 .e2e-logs/simple-node-apply.log create mode 100644 .e2e-logs/simple-node-destroy.log create mode 100644 .e2e-logs/simple-node-init.log create mode 100644 .e2e-logs/simple-python-apply-modify.log create mode 100644 .e2e-logs/simple-python-apply.log create mode 100644 .e2e-logs/simple-python-destroy.log create mode 100644 .e2e-logs/simple-python-init.log create mode 100755 run_stress_test.sh create mode 100755 test_pearlharbor_push.sh diff --git a/.e2e-logs/hello-node-apply-modify.log b/.e2e-logs/hello-node-apply-modify.log new file mode 100644 index 0000000..45b58c3 --- /dev/null +++ b/.e2e-logs/hello-node-apply-modify.log @@ -0,0 +1,36 @@ +sless_function.hello_job: Refreshing state... [name=hello-job] +sless_function.hello_http: Refreshing state... [name=hello-http] +sless_trigger.hello_http: Refreshing state... [name=hello-http-trigger] +sless_job.hello_run: Refreshing state... [name=hello-run] + +Terraform used the selected providers to generate the following execution +plan. Resource actions are indicated with the following symbols: + ~ update in-place + +Terraform will perform the following actions: + + # sless_function.hello_http will be updated in-place + ~ resource "sless_function" "hello_http" { + ~ build_timeout_sec = 300 -> (known after apply) + ~ code_hash = "847def02d312614d511fab15b1c6fcf0ef883c564e79a5c52b0d25b917ebc130" -> (known after apply) + + env_vars = { + + "GREETING" = "e2e-test" + } + ~ image_ref = "naeel/sless-sless-cdd874dfa31ba6ca-hello-http:b2c713740a8d" -> (known after apply) + ~ memory_mb = 128 -> 256 + name = "hello-http" + # (5 unchanged attributes hidden) + } + +Plan: 0 to add, 1 to change, 0 to destroy. +sless_function.hello_http: Modifying... [name=hello-http] +sless_function.hello_http: Modifications complete after 0s [name=hello-http] + +Apply complete! Resources: 0 added, 1 changed, 0 destroyed. + +Outputs: + +job_completion_time = "2026-03-11T06:47:08Z" +job_message = "{\"input\":[100,200,300],\"sum\":600,\"avg\":200,\"count\":3}" +job_phase = "Succeeded" +trigger_url = "https://sless-api.kube5s.ru/fn/sless-cdd874dfa31ba6ca/hello-http" diff --git a/.e2e-logs/hello-node-apply.log b/.e2e-logs/hello-node-apply.log new file mode 100644 index 0000000..9a917f8 --- /dev/null +++ b/.e2e-logs/hello-node-apply.log @@ -0,0 +1,95 @@ + +Terraform used the selected providers to generate the following execution +plan. Resource actions are indicated with the following symbols: + + create + +Terraform will perform the following actions: + + # sless_function.hello_http will be created + + resource "sless_function" "hello_http" { + + build_timeout_sec = (known after apply) + + code_hash = (known after apply) + + entrypoint = "handler-http.handle" + + image_ref = (known after apply) + + memory_mb = 128 + + name = "hello-http" + + phase = (known after apply) + + runtime = "nodejs20" + + source_dir = "./code" + + timeout_sec = 30 + } + + # sless_function.hello_job will be created + + resource "sless_function" "hello_job" { + + build_timeout_sec = (known after apply) + + code_hash = (known after apply) + + entrypoint = "handler-job.handle" + + image_ref = (known after apply) + + memory_mb = 128 + + name = "hello-job" + + phase = (known after apply) + + runtime = "nodejs20" + + source_dir = "./code" + + timeout_sec = 30 + } + + # sless_job.hello_run will be created + + resource "sless_job" "hello_run" { + + completion_time = (known after apply) + + event_json = jsonencode( + { + + numbers = [ + + 100, + + 200, + + 300, + ] + } + ) + + function = "hello-job" + + message = (known after apply) + + name = "hello-run" + + phase = (known after apply) + + run_id = 9 + + start_time = (known after apply) + + wait_timeout_sec = 600 + } + + # sless_trigger.hello_http will be created + + resource "sless_trigger" "hello_http" { + + active = (known after apply) + + enabled = true + + function = "hello-http" + + name = "hello-http-trigger" + + type = "http" + + url = (known after apply) + } + +Plan: 4 to add, 0 to change, 0 to destroy. + +Changes to Outputs: + + job_completion_time = (known after apply) + + job_message = (known after apply) + + job_phase = (known after apply) + + trigger_url = (known after apply) +sless_function.hello_job: Creating... +sless_function.hello_http: Creating... +sless_function.hello_http: Still creating... [00m10s elapsed] +sless_function.hello_job: Still creating... [00m10s elapsed] +sless_function.hello_http: Still creating... [00m20s elapsed] +sless_function.hello_job: Still creating... [00m20s elapsed] +sless_function.hello_http: Creation complete after 21s [name=hello-http] +sless_function.hello_job: Creation complete after 21s [name=hello-job] +sless_trigger.hello_http: Creating... +sless_job.hello_run: Creating... +sless_trigger.hello_http: Creation complete after 2s [name=hello-http-trigger] +sless_job.hello_run: Still creating... [00m10s elapsed] +sless_job.hello_run: Creation complete after 10s [name=hello-run] + +Apply complete! Resources: 4 added, 0 changed, 0 destroyed. + +Outputs: + +job_completion_time = "2026-03-11T06:47:08Z" +job_message = "{\"input\":[100,200,300],\"sum\":600,\"avg\":200,\"count\":3}" +job_phase = "Succeeded" +trigger_url = "https://sless-api.kube5s.ru/fn/sless-cdd874dfa31ba6ca/hello-http" diff --git a/.e2e-logs/hello-node-destroy.log b/.e2e-logs/hello-node-destroy.log new file mode 100644 index 0000000..d5647cf --- /dev/null +++ b/.e2e-logs/hello-node-destroy.log @@ -0,0 +1,112 @@ +sless_function.hello_job: Refreshing state... [name=hello-job] +sless_function.hello_http: Refreshing state... [name=hello-http] +sless_job.hello_run: Refreshing state... [name=hello-run] +sless_trigger.hello_http: Refreshing state... [name=hello-http-trigger] + +Terraform used the selected providers to generate the following execution +plan. Resource actions are indicated with the following symbols: + - destroy + +Terraform will perform the following actions: + + # sless_function.hello_http will be destroyed + - resource "sless_function" "hello_http" { + - build_timeout_sec = 300 -> null + - code_hash = "847def02d312614d511fab15b1c6fcf0ef883c564e79a5c52b0d25b917ebc130" -> null + - entrypoint = "handler-http.handle" -> null + - env_vars = { + - "GREETING" = "e2e-test" + } -> null + - image_ref = "naeel/sless-sless-cdd874dfa31ba6ca-hello-http:b2c713740a8d" -> null + - memory_mb = 256 -> null + - name = "hello-http" -> null + - phase = "Ready" -> null + - runtime = "nodejs20" -> null + - source_dir = "./code" -> null + - timeout_sec = 30 -> null + } + + # sless_function.hello_job will be destroyed + - resource "sless_function" "hello_job" { + - build_timeout_sec = 300 -> null + - code_hash = "847def02d312614d511fab15b1c6fcf0ef883c564e79a5c52b0d25b917ebc130" -> null + - entrypoint = "handler-job.handle" -> null + - image_ref = "naeel/sless-sless-cdd874dfa31ba6ca-hello-job:572595dae5fb" -> null + - memory_mb = 128 -> null + - name = "hello-job" -> null + - phase = "Ready" -> null + - runtime = "nodejs20" -> null + - source_dir = "./code" -> null + - timeout_sec = 30 -> null + } + + # sless_job.hello_run will be destroyed + - resource "sless_job" "hello_run" { + - completion_time = "2026-03-11T06:47:08Z" -> null + - event_json = jsonencode( + { + - numbers = [ + - 100, + - 200, + - 300, + ] + } + ) -> null + - function = "hello-job" -> null + - message = jsonencode( + { + - avg = 200 + - count = 3 + - input = [ + - 100, + - 200, + - 300, + ] + - sum = 600 + } + ) -> null + - name = "hello-run" -> null + - phase = "Succeeded" -> null + - run_id = 9 -> null + - start_time = "2026-03-11T06:46:58Z" -> null + - wait_timeout_sec = 600 -> null + } + + # sless_trigger.hello_http will be destroyed + - resource "sless_trigger" "hello_http" { + - active = true -> null + - enabled = true -> null + - function = "hello-http" -> null + - name = "hello-http-trigger" -> null + - type = "http" -> null + - url = "https://sless-api.kube5s.ru/fn/sless-cdd874dfa31ba6ca/hello-http" -> null + } + +Plan: 0 to add, 0 to change, 4 to destroy. + +Changes to Outputs: + - job_completion_time = "2026-03-11T06:47:08Z" -> null + - job_message = jsonencode( + { + - avg = 200 + - count = 3 + - input = [ + - 100, + - 200, + - 300, + ] + - sum = 600 + } + ) -> null + - job_phase = "Succeeded" -> null + - trigger_url = "https://sless-api.kube5s.ru/fn/sless-cdd874dfa31ba6ca/hello-http" -> null +sless_trigger.hello_http: Destroying... [name=hello-http-trigger] +sless_job.hello_run: Destroying... [name=hello-run] +sless_job.hello_run: Destruction complete after 0s +sless_function.hello_job: Destroying... [name=hello-job] +sless_function.hello_job: Destruction complete after 0s +sless_trigger.hello_http: Destruction complete after 3s +sless_function.hello_http: Destroying... [name=hello-http] +sless_function.hello_http: Destruction complete after 0s + +Destroy complete! Resources: 4 destroyed. diff --git a/.e2e-logs/hello-node-init.log b/.e2e-logs/hello-node-init.log new file mode 100644 index 0000000..041c81e --- /dev/null +++ b/.e2e-logs/hello-node-init.log @@ -0,0 +1,22 @@ +Initializing the backend... +Initializing provider plugins... +- Finding terra.k8c.ru/naeel/sless versions matching "~> 0.1.13"... +- Installing terra.k8c.ru/naeel/sless v0.1.13... +- Installed terra.k8c.ru/naeel/sless v0.1.13 (self-signed, key ID CB3A0DF161ECC416) +Partner and community providers are signed by their developers. +If you'd like to know more about provider signing, you can read about it here: +https://developer.hashicorp.com/terraform/cli/plugins/signing +Terraform has created a lock file .terraform.lock.hcl to record the provider +selections it made above. Include this file in your version control repository +so that Terraform can guarantee to make the same selections by default when +you run "terraform init" in the future. + +Terraform has been successfully initialized! + +You may now begin working with Terraform. Try running "terraform plan" to see +any changes that are required for your infrastructure. All Terraform commands +should now work. + +If you ever set or change modules or backend configuration for Terraform, +rerun this command to reinitialize your working directory. If you forget, other +commands will detect it and remind you to do so if necessary. diff --git a/.e2e-logs/simple-node-apply-modify.log b/.e2e-logs/simple-node-apply-modify.log new file mode 100644 index 0000000..ad00674 --- /dev/null +++ b/.e2e-logs/simple-node-apply-modify.log @@ -0,0 +1,32 @@ +sless_function.time_getter: Refreshing state... [name=simple-node-time-getter] +sless_job.run_getter: Refreshing state... [name=simple-node-getter-run] +sless_function.time_display: Refreshing state... [name=simple-node-time-display] +sless_trigger.display_http: Refreshing state... [name=simple-node-display-http] + +Terraform used the selected providers to generate the following execution +plan. Resource actions are indicated with the following symbols: + ~ update in-place + +Terraform will perform the following actions: + + # sless_function.time_display will be updated in-place + ~ resource "sless_function" "time_display" { + ~ build_timeout_sec = 300 -> (known after apply) + ~ code_hash = "cf78d23403217d9ca0871205966de6b9041e0187b8bb5bbf6b1502dd38951e80" -> (known after apply) + ~ image_ref = "naeel/sless-sless-cdd874dfa31ba6ca-simple-node-time-display:96fbfb0d6a99" -> (known after apply) + ~ memory_mb = 64 -> 96 + name = "simple-node-time-display" + ~ timeout_sec = 30 -> (known after apply) + # (5 unchanged attributes hidden) + } + +Plan: 0 to add, 1 to change, 0 to destroy. +sless_function.time_display: Modifying... [name=simple-node-time-display] +sless_function.time_display: Modifications complete after 0s [name=simple-node-time-display] + +Apply complete! Resources: 0 added, 1 changed, 0 destroyed. + +Outputs: + +display_url = "https://sless-api.kube5s.ru/fn/sless-cdd874dfa31ba6ca/simple-node-time-display" +job_result = "{\"time\":\"2026-03-11T06:47:47.229Z\"}" diff --git a/.e2e-logs/simple-node-apply.log b/.e2e-logs/simple-node-apply.log new file mode 100644 index 0000000..988b419 --- /dev/null +++ b/.e2e-logs/simple-node-apply.log @@ -0,0 +1,86 @@ + +Terraform used the selected providers to generate the following execution +plan. Resource actions are indicated with the following symbols: + + create + +Terraform will perform the following actions: + + # sless_function.time_display will be created + + resource "sless_function" "time_display" { + + build_timeout_sec = (known after apply) + + code_hash = (known after apply) + + entrypoint = "time_display.showTime" + + env_vars = { + + "JOB_TIME" = (known after apply) + } + + image_ref = (known after apply) + + memory_mb = 64 + + name = "simple-node-time-display" + + phase = (known after apply) + + runtime = "nodejs20" + + source_dir = "./code/time_display" + + timeout_sec = (known after apply) + } + + # sless_function.time_getter will be created + + resource "sless_function" "time_getter" { + + build_timeout_sec = (known after apply) + + code_hash = (known after apply) + + entrypoint = "time_getter.getTime" + + image_ref = (known after apply) + + memory_mb = 64 + + name = "simple-node-time-getter" + + phase = (known after apply) + + runtime = "nodejs20" + + source_dir = "./code/time_getter" + + timeout_sec = (known after apply) + } + + # sless_job.run_getter will be created + + resource "sless_job" "run_getter" { + + completion_time = (known after apply) + + event_json = jsonencode({}) + + function = "simple-node-time-getter" + + message = (known after apply) + + name = "simple-node-getter-run" + + phase = (known after apply) + + run_id = 1 + + start_time = (known after apply) + + wait_timeout_sec = 120 + } + + # sless_trigger.display_http will be created + + resource "sless_trigger" "display_http" { + + active = (known after apply) + + enabled = true + + function = "simple-node-time-display" + + name = "simple-node-display-http" + + type = "http" + + url = (known after apply) + } + +Plan: 4 to add, 0 to change, 0 to destroy. + +Changes to Outputs: + + display_url = (known after apply) + + job_result = (known after apply) +sless_function.time_getter: Creating... +sless_function.time_getter: Still creating... [00m10s elapsed] +sless_function.time_getter: Still creating... [00m20s elapsed] +sless_function.time_getter: Creation complete after 20s [name=simple-node-time-getter] +sless_job.run_getter: Creating... +sless_job.run_getter: Still creating... [00m10s elapsed] +sless_job.run_getter: Creation complete after 10s [name=simple-node-getter-run] +sless_function.time_display: Creating... +sless_function.time_display: Still creating... [00m10s elapsed] +sless_function.time_display: Still creating... [00m20s elapsed] +sless_function.time_display: Creation complete after 21s [name=simple-node-time-display] +sless_trigger.display_http: Creating... +sless_trigger.display_http: Creation complete after 2s [name=simple-node-display-http] + +Apply complete! Resources: 4 added, 0 changed, 0 destroyed. + +Outputs: + +display_url = "https://sless-api.kube5s.ru/fn/sless-cdd874dfa31ba6ca/simple-node-time-display" +job_result = "{\"time\":\"2026-03-11T06:47:47.229Z\"}" diff --git a/.e2e-logs/simple-node-destroy.log b/.e2e-logs/simple-node-destroy.log new file mode 100644 index 0000000..a2dc77e --- /dev/null +++ b/.e2e-logs/simple-node-destroy.log @@ -0,0 +1,92 @@ +sless_function.time_getter: Refreshing state... [name=simple-node-time-getter] +sless_job.run_getter: Refreshing state... [name=simple-node-getter-run] +sless_function.time_display: Refreshing state... [name=simple-node-time-display] +sless_trigger.display_http: Refreshing state... [name=simple-node-display-http] + +Terraform used the selected providers to generate the following execution +plan. Resource actions are indicated with the following symbols: + - destroy + +Terraform will perform the following actions: + + # sless_function.time_display will be destroyed + - resource "sless_function" "time_display" { + - build_timeout_sec = 300 -> null + - code_hash = "cf78d23403217d9ca0871205966de6b9041e0187b8bb5bbf6b1502dd38951e80" -> null + - entrypoint = "time_display.showTime" -> null + - env_vars = { + - "JOB_TIME" = jsonencode( + { + - time = "2026-03-11T06:47:47.229Z" + } + ) + } -> null + - image_ref = "naeel/sless-sless-cdd874dfa31ba6ca-simple-node-time-display:96fbfb0d6a99" -> null + - memory_mb = 96 -> null + - name = "simple-node-time-display" -> null + - phase = "Ready" -> null + - runtime = "nodejs20" -> null + - source_dir = "./code/time_display" -> null + - timeout_sec = 30 -> null + } + + # sless_function.time_getter will be destroyed + - resource "sless_function" "time_getter" { + - build_timeout_sec = 300 -> null + - code_hash = "bcbba3d63dcd3f1cce4ba48e8b2208bd398e6864e51b9c72887d027cc1385210" -> null + - entrypoint = "time_getter.getTime" -> null + - image_ref = "naeel/sless-sless-cdd874dfa31ba6ca-simple-node-time-getter:d933956041b2" -> null + - memory_mb = 64 -> null + - name = "simple-node-time-getter" -> null + - phase = "Ready" -> null + - runtime = "nodejs20" -> null + - source_dir = "./code/time_getter" -> null + - timeout_sec = 30 -> null + } + + # sless_job.run_getter will be destroyed + - resource "sless_job" "run_getter" { + - completion_time = "2026-03-11T06:47:53Z" -> null + - event_json = jsonencode({}) + - function = "simple-node-time-getter" -> null + - message = jsonencode( + { + - time = "2026-03-11T06:47:47.229Z" + } + ) -> null + - name = "simple-node-getter-run" -> null + - phase = "Succeeded" -> null + - run_id = 1 -> null + - start_time = "2026-03-11T06:47:43Z" -> null + - wait_timeout_sec = 120 -> null + } + + # sless_trigger.display_http will be destroyed + - resource "sless_trigger" "display_http" { + - active = true -> null + - enabled = true -> null + - function = "simple-node-time-display" -> null + - name = "simple-node-display-http" -> null + - type = "http" -> null + - url = "https://sless-api.kube5s.ru/fn/sless-cdd874dfa31ba6ca/simple-node-time-display" -> null + } + +Plan: 0 to add, 0 to change, 4 to destroy. + +Changes to Outputs: + - display_url = "https://sless-api.kube5s.ru/fn/sless-cdd874dfa31ba6ca/simple-node-time-display" -> null + - job_result = jsonencode( + { + - time = "2026-03-11T06:47:47.229Z" + } + ) -> null +sless_trigger.display_http: Destroying... [name=simple-node-display-http] +sless_trigger.display_http: Destruction complete after 3s +sless_function.time_display: Destroying... [name=simple-node-time-display] +sless_function.time_display: Destruction complete after 0s +sless_job.run_getter: Destroying... [name=simple-node-getter-run] +sless_job.run_getter: Destruction complete after 0s +sless_function.time_getter: Destroying... [name=simple-node-time-getter] +sless_function.time_getter: Destruction complete after 0s + +Destroy complete! Resources: 4 destroyed. diff --git a/.e2e-logs/simple-node-init.log b/.e2e-logs/simple-node-init.log new file mode 100644 index 0000000..041c81e --- /dev/null +++ b/.e2e-logs/simple-node-init.log @@ -0,0 +1,22 @@ +Initializing the backend... +Initializing provider plugins... +- Finding terra.k8c.ru/naeel/sless versions matching "~> 0.1.13"... +- Installing terra.k8c.ru/naeel/sless v0.1.13... +- Installed terra.k8c.ru/naeel/sless v0.1.13 (self-signed, key ID CB3A0DF161ECC416) +Partner and community providers are signed by their developers. +If you'd like to know more about provider signing, you can read about it here: +https://developer.hashicorp.com/terraform/cli/plugins/signing +Terraform has created a lock file .terraform.lock.hcl to record the provider +selections it made above. Include this file in your version control repository +so that Terraform can guarantee to make the same selections by default when +you run "terraform init" in the future. + +Terraform has been successfully initialized! + +You may now begin working with Terraform. Try running "terraform plan" to see +any changes that are required for your infrastructure. All Terraform commands +should now work. + +If you ever set or change modules or backend configuration for Terraform, +rerun this command to reinitialize your working directory. If you forget, other +commands will detect it and remind you to do so if necessary. diff --git a/.e2e-logs/simple-python-apply-modify.log b/.e2e-logs/simple-python-apply-modify.log new file mode 100644 index 0000000..86377e0 --- /dev/null +++ b/.e2e-logs/simple-python-apply-modify.log @@ -0,0 +1,32 @@ +sless_function.time_getter: Refreshing state... [name=simple-py-time-getter] +sless_job.run_getter: Refreshing state... [name=simple-py-getter-run] +sless_function.time_display: Refreshing state... [name=simple-py-time-display] +sless_trigger.display_http: Refreshing state... [name=simple-py-display-http] + +Terraform used the selected providers to generate the following execution +plan. Resource actions are indicated with the following symbols: + ~ update in-place + +Terraform will perform the following actions: + + # sless_function.time_display will be updated in-place + ~ resource "sless_function" "time_display" { + ~ build_timeout_sec = 300 -> (known after apply) + ~ code_hash = "394881701022565dcc3e786917b541b2ff4763b1d36adb80e0110e17115a3d88" -> (known after apply) + ~ image_ref = "naeel/sless-sless-cdd874dfa31ba6ca-simple-py-time-display:6f9aecbf3839" -> (known after apply) + ~ memory_mb = 64 -> 96 + name = "simple-py-time-display" + ~ timeout_sec = 30 -> (known after apply) + # (5 unchanged attributes hidden) + } + +Plan: 0 to add, 1 to change, 0 to destroy. +sless_function.time_display: Modifying... [name=simple-py-time-display] +sless_function.time_display: Modifications complete after 0s [name=simple-py-time-display] + +Apply complete! Resources: 0 added, 1 changed, 0 destroyed. + +Outputs: + +display_url = "https://sless-api.kube5s.ru/fn/sless-cdd874dfa31ba6ca/simple-py-time-display" +job_result = "{\"time\": \"2026-03-11T06:45:01.234537+00:00\"}" diff --git a/.e2e-logs/simple-python-apply.log b/.e2e-logs/simple-python-apply.log new file mode 100644 index 0000000..e0e9c70 --- /dev/null +++ b/.e2e-logs/simple-python-apply.log @@ -0,0 +1,86 @@ + +Terraform used the selected providers to generate the following execution +plan. Resource actions are indicated with the following symbols: + + create + +Terraform will perform the following actions: + + # sless_function.time_display will be created + + resource "sless_function" "time_display" { + + build_timeout_sec = (known after apply) + + code_hash = (known after apply) + + entrypoint = "time_display.show_time" + + env_vars = { + + "JOB_TIME" = (known after apply) + } + + image_ref = (known after apply) + + memory_mb = 64 + + name = "simple-py-time-display" + + phase = (known after apply) + + runtime = "python3.11" + + source_dir = "./code/time_display" + + timeout_sec = (known after apply) + } + + # sless_function.time_getter will be created + + resource "sless_function" "time_getter" { + + build_timeout_sec = (known after apply) + + code_hash = (known after apply) + + entrypoint = "time_getter.get_time" + + image_ref = (known after apply) + + memory_mb = 64 + + name = "simple-py-time-getter" + + phase = (known after apply) + + runtime = "python3.11" + + source_dir = "./code/time_getter" + + timeout_sec = (known after apply) + } + + # sless_job.run_getter will be created + + resource "sless_job" "run_getter" { + + completion_time = (known after apply) + + event_json = jsonencode({}) + + function = "simple-py-time-getter" + + message = (known after apply) + + name = "simple-py-getter-run" + + phase = (known after apply) + + run_id = 1 + + start_time = (known after apply) + + wait_timeout_sec = 120 + } + + # sless_trigger.display_http will be created + + resource "sless_trigger" "display_http" { + + active = (known after apply) + + enabled = true + + function = "simple-py-time-display" + + name = "simple-py-display-http" + + type = "http" + + url = (known after apply) + } + +Plan: 4 to add, 0 to change, 0 to destroy. + +Changes to Outputs: + + display_url = (known after apply) + + job_result = (known after apply) +sless_function.time_getter: Creating... +sless_function.time_getter: Still creating... [00m10s elapsed] +sless_function.time_getter: Still creating... [00m20s elapsed] +sless_function.time_getter: Creation complete after 20s [name=simple-py-time-getter] +sless_job.run_getter: Creating... +sless_job.run_getter: Still creating... [00m10s elapsed] +sless_job.run_getter: Creation complete after 10s [name=simple-py-getter-run] +sless_function.time_display: Creating... +sless_function.time_display: Still creating... [00m10s elapsed] +sless_function.time_display: Still creating... [00m20s elapsed] +sless_function.time_display: Creation complete after 21s [name=simple-py-time-display] +sless_trigger.display_http: Creating... +sless_trigger.display_http: Creation complete after 2s [name=simple-py-display-http] + +Apply complete! Resources: 4 added, 0 changed, 0 destroyed. + +Outputs: + +display_url = "https://sless-api.kube5s.ru/fn/sless-cdd874dfa31ba6ca/simple-py-time-display" +job_result = "{\"time\": \"2026-03-11T06:45:01.234537+00:00\"}" diff --git a/.e2e-logs/simple-python-destroy.log b/.e2e-logs/simple-python-destroy.log new file mode 100644 index 0000000..709c36d --- /dev/null +++ b/.e2e-logs/simple-python-destroy.log @@ -0,0 +1,92 @@ +sless_function.time_getter: Refreshing state... [name=simple-py-time-getter] +sless_job.run_getter: Refreshing state... [name=simple-py-getter-run] +sless_function.time_display: Refreshing state... [name=simple-py-time-display] +sless_trigger.display_http: Refreshing state... [name=simple-py-display-http] + +Terraform used the selected providers to generate the following execution +plan. Resource actions are indicated with the following symbols: + - destroy + +Terraform will perform the following actions: + + # sless_function.time_display will be destroyed + - resource "sless_function" "time_display" { + - build_timeout_sec = 300 -> null + - code_hash = "394881701022565dcc3e786917b541b2ff4763b1d36adb80e0110e17115a3d88" -> null + - entrypoint = "time_display.show_time" -> null + - env_vars = { + - "JOB_TIME" = jsonencode( + { + - time = "2026-03-11T06:45:01.234537+00:00" + } + ) + } -> null + - image_ref = "naeel/sless-sless-cdd874dfa31ba6ca-simple-py-time-display:6f9aecbf3839" -> null + - memory_mb = 96 -> null + - name = "simple-py-time-display" -> null + - phase = "Ready" -> null + - runtime = "python3.11" -> null + - source_dir = "./code/time_display" -> null + - timeout_sec = 30 -> null + } + + # sless_function.time_getter will be destroyed + - resource "sless_function" "time_getter" { + - build_timeout_sec = 300 -> null + - code_hash = "5a15834dfe9de935f27cd54b2e851b738e63a17aec7aca938c2358b026feeaaa" -> null + - entrypoint = "time_getter.get_time" -> null + - image_ref = "naeel/sless-sless-cdd874dfa31ba6ca-simple-py-time-getter:cc24266fe22e" -> null + - memory_mb = 64 -> null + - name = "simple-py-time-getter" -> null + - phase = "Ready" -> null + - runtime = "python3.11" -> null + - source_dir = "./code/time_getter" -> null + - timeout_sec = 30 -> null + } + + # sless_job.run_getter will be destroyed + - resource "sless_job" "run_getter" { + - completion_time = "2026-03-11T06:45:07Z" -> null + - event_json = jsonencode({}) + - function = "simple-py-time-getter" -> null + - message = jsonencode( + { + - time = "2026-03-11T06:45:01.234537+00:00" + } + ) -> null + - name = "simple-py-getter-run" -> null + - phase = "Succeeded" -> null + - run_id = 1 -> null + - start_time = "2026-03-11T06:44:57Z" -> null + - wait_timeout_sec = 120 -> null + } + + # sless_trigger.display_http will be destroyed + - resource "sless_trigger" "display_http" { + - active = true -> null + - enabled = true -> null + - function = "simple-py-time-display" -> null + - name = "simple-py-display-http" -> null + - type = "http" -> null + - url = "https://sless-api.kube5s.ru/fn/sless-cdd874dfa31ba6ca/simple-py-time-display" -> null + } + +Plan: 0 to add, 0 to change, 4 to destroy. + +Changes to Outputs: + - display_url = "https://sless-api.kube5s.ru/fn/sless-cdd874dfa31ba6ca/simple-py-time-display" -> null + - job_result = jsonencode( + { + - time = "2026-03-11T06:45:01.234537+00:00" + } + ) -> null +sless_trigger.display_http: Destroying... [name=simple-py-display-http] +sless_trigger.display_http: Destruction complete after 3s +sless_function.time_display: Destroying... [name=simple-py-time-display] +sless_function.time_display: Destruction complete after 0s +sless_job.run_getter: Destroying... [name=simple-py-getter-run] +sless_job.run_getter: Destruction complete after 0s +sless_function.time_getter: Destroying... [name=simple-py-time-getter] +sless_function.time_getter: Destruction complete after 0s + +Destroy complete! Resources: 4 destroyed. diff --git a/.e2e-logs/simple-python-init.log b/.e2e-logs/simple-python-init.log new file mode 100644 index 0000000..041c81e --- /dev/null +++ b/.e2e-logs/simple-python-init.log @@ -0,0 +1,22 @@ +Initializing the backend... +Initializing provider plugins... +- Finding terra.k8c.ru/naeel/sless versions matching "~> 0.1.13"... +- Installing terra.k8c.ru/naeel/sless v0.1.13... +- Installed terra.k8c.ru/naeel/sless v0.1.13 (self-signed, key ID CB3A0DF161ECC416) +Partner and community providers are signed by their developers. +If you'd like to know more about provider signing, you can read about it here: +https://developer.hashicorp.com/terraform/cli/plugins/signing +Terraform has created a lock file .terraform.lock.hcl to record the provider +selections it made above. Include this file in your version control repository +so that Terraform can guarantee to make the same selections by default when +you run "terraform init" in the future. + +Terraform has been successfully initialized! + +You may now begin working with Terraform. Try running "terraform plan" to see +any changes that are required for your infrastructure. All Terraform commands +should now work. + +If you ever set or change modules or backend configuration for Terraform, +rerun this command to reinitialize your working directory. If you forget, other +commands will detect it and remind you to do so if necessary. diff --git a/.gitignore b/.gitignore index 5ab0040..51aba2f 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ terraform/provider/build/ examples/*/dist/ **/handler.zip test.token +*.tfvars diff --git a/run_stress_test.sh b/run_stress_test.sh new file mode 100755 index 0000000..fc0fb20 --- /dev/null +++ b/run_stress_test.sh @@ -0,0 +1,662 @@ +#!/usr/bin/env bash +# 2026-03-11 +# run_stress_test.sh — Полный стресс-тест всех examples. +# +# Что прогоняем на каждом примере: +# [ROUND 1] init → apply → HTTP-проверка → destroy → проверка "упал" +# [ROUND 2] apply → modify (memory) → apply(modify) → HTTP-проверка +# → modify (env_var) → apply(modify2) → HTTP-проверка +# → modify (timeout) → apply(modify3) +# → destroy → проверка "упал" +# [ROUND 3] apply → destroy (сразу, без ожидания Ready) +# [hello-node extra] job re-run: run_id bump → apply → assert Succeeded +# [simple-* extra] trigger disabled → apply → assert 404 → enabled → apply → assert 200 +# [notes-python] CRUD: add note → list → assert запись есть → destroy +# +# Логи каждого шага: .stress-logs/--.log +# При ошибке скрипт не падает — пишет FAIL и продолжает следующий пример. +# В конце — сводная таблица PASS/FAIL по каждому примеру. +# +# Запуск: +# ./run_stress_test.sh +# ./run_stress_test.sh hello-node simple-node # конкретные примеры +# +# Время: ~30-50 мин (зависит от скорости сборки канико) + +set -uo pipefail + +# ── Конфигурация ────────────────────────────────────────────────────────────── +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +EXAMPLES_DIR="$REPO_ROOT/examples" +LOGS_DIR="$REPO_ROOT/.stress-logs" +mkdir -p "$LOGS_DIR" + +API_BASE="https://sless-api.kube5s.ru" +NS="sless-cdd874dfa31ba6ca" # вычислен из токена; не меняется + +# ── Примеры ─────────────────────────────────────────────────────────────────── +if [ $# -gt 0 ]; then + EXAMPLES=("$@") +else + EXAMPLES=("hello-node" "simple-node" "simple-python" "notes-python") +fi + +# ── Цвета / вывод ───────────────────────────────────────────────────────────── +GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[1;33m' +CYAN='\033[0;36m'; BOLD='\033[1m'; RESET='\033[0m' +ok() { echo -e "${GREEN} ✓ $*${RESET}"; } +fail() { echo -e "${RED} ✗ $*${RESET}"; } +info() { echo -e "${YELLOW} → $*${RESET}"; } +step() { echo -e "${CYAN} [step] $*${RESET}"; } +header(){ echo -e "\n${BOLD}${CYAN}══════════════════════════════════════════${RESET}"; \ + echo -e "${BOLD}${CYAN} $*${RESET}"; \ + echo -e "${BOLD}${CYAN}══════════════════════════════════════════${RESET}"; } + +# ── Результаты ──────────────────────────────────────────────────────────────── +declare -A RESULTS=() # example → "PASS" | "FAIL: " +declare -A TIMINGS=() # example → секунды + +# ── Emergency destroy trap ──────────────────────────────────────────────────── +ACTIVE_EXAMPLE="" +cleanup_trap() { + if [ -n "$ACTIVE_EXAMPLE" ]; then + local dir="$EXAMPLES_DIR/$ACTIVE_EXAMPLE" + if [ -f "$dir/terraform.tfstate" ] && \ + python3 -c " +import json,sys +d=json.load(open('$dir/terraform.tfstate')) +sys.exit(0 if d.get('resources') else 1) +" 2>/dev/null; then + echo -e "\n${RED}[trap] Script interrupted — emergency destroy: $ACTIVE_EXAMPLE${RESET}" + (cd "$dir" && terraform destroy -auto-approve -input=false -no-color \ + >> "$LOGS_DIR/${ACTIVE_EXAMPLE}-emergency-destroy.log" 2>&1) || true + fi + restore_all_backups "$ACTIVE_EXAMPLE" + fi +} +trap cleanup_trap EXIT INT TERM + +# ── Утилиты: terraform ──────────────────────────────────────────────────────── +# tf_run +# Ретраит до 4 раз при сетевых ошибках (TLS timeout, EOF, i/o timeout) +tf_run() { + local logfile="$1"; local chdir="$2"; shift 2 + local attempt=1 max=4 + while [ "$attempt" -le "$max" ]; do + : > "$logfile" + if (cd "$chdir" && terraform "$@" -no-color) 2>&1 | tee "$logfile"; then + return 0 + fi + local rc=${PIPESTATUS[0]} + if grep -Eiq \ + 'TLS handshake timeout|unexpected EOF|i/o timeout|context deadline exceeded|Client\.Timeout' \ + "$logfile" && [ "$attempt" -lt "$max" ]; then + info "network hiccup (attempt $attempt/$max), retry in $((attempt*3))s..." + sleep $((attempt * 3)) + attempt=$((attempt + 1)) + continue + fi + return "$rc" + done +} + +tf_init() { tf_run "$1" "$2" init -input=false -upgrade; } +tf_apply() { tf_run "$1" "$2" apply -auto-approve -input=false; } +tf_destroy() { tf_run "$1" "$2" destroy -auto-approve -input=false; } + +assert_apply_ok() { grep -q 'Apply complete' "$1"; } +assert_destroy_ok() { grep -q 'Destroy complete' "$1"; } +assert_no_changes() { grep -q 'No changes' "$1"; } + +# ── Утилиты: HTTP-проверки ──────────────────────────────────────────────────── +# http_check [expected_http_code] [max_attempts] [sleep_sec] +http_check() { + local url="$1" + local expected="${2:-200}" + local attempts="${3:-12}" + local sleep_sec="${4:-10}" + local try=1 + while [ "$try" -le "$attempts" ]; do + local code + code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 "$url" 2>/dev/null || echo "000") + if [ "$code" = "$expected" ]; then + return 0 + fi + info "HTTP $code (want $expected), waiting... ($try/$attempts)" + try=$((try + 1)) + sleep "$sleep_sec" + done + fail "HTTP check failed: got $code, wanted $expected after $((attempts*sleep_sec))s" + return 1 +} + +# http_post → возвращает тело в stdout +http_post() { + curl -sS -X POST -H 'Content-Type: application/json' -d "$2" \ + --max-time 15 "$1" 2>/dev/null +} + +http_get() { + curl -sS --max-time 15 "$1" 2>/dev/null +} + +# ── Утилиты: бэкапы и патчи ─────────────────────────────────────────────────── +backup_file() { + local f="$1" + cp "$f" "$f.stress.bak" +} + +restore_all_backups() { + local example="$1" + local dir="$EXAMPLES_DIR/$example" + while IFS= read -r bak; do + [ -f "$bak" ] || continue + mv "$bak" "${bak%.stress.bak}" + done < <(find "$dir" -name '*.stress.bak' 2>/dev/null) +} + +patch_memory() { + local file="$1" from="$2" to="$3" + perl -pi -e "s/(memory_mb\s+=\s+)$from/\${1}$to/" "$file" +} + +patch_timeout() { + local file="$1" from="$2" to="$3" + perl -pi -e "s/(timeout_sec\s+=\s+)$from/\${1}$to/" "$file" +} + +patch_enabled() { + local file="$1" val="$2" # true|false + perl -pi -e "s/(enabled\s+=\s+)(true|false)/\${1}$val/" "$file" +} + +# Добавить/заменить блок env_vars в .tf файле +# Вставляет после строки содержащей "source_dir" +add_env_var() { + local file="$1" key="$2" val="$3" + if grep -q "^ env_vars" "$file"; then + # уже есть — добавляем строку внутрь блока + perl -pi -e "s/(env_vars\s*=\s*\{)/\${1}\n $key = \"$val\"/" "$file" + else + # нет — вставляем перед source_dir + perl -pi -e "s/( source_dir)/ env_vars = \{ $key = \"$val\" \}\n\${1}/" "$file" + fi +} + +remove_env_vars_block() { + local file="$1" + perl -0pi -e 's/\s*env_vars\s*=\s*\{[^}]*\}//g' "$file" +} + +# ── Утилиты: terraform output ───────────────────────────────────────────────── +tf_output() { + local chdir="$1" key="$2" + (cd "$chdir" && terraform output -raw "$key" 2>/dev/null) || echo "" +} + +# ── Утилиты: счётчик шагов ──────────────────────────────────────────────────── +STEP_NUM=0 +STEP_FAILS=0 +next_step() { + STEP_NUM=$((STEP_NUM + 1)) + step "[$STEP_NUM] $*" +} + +assert_step() { + local desc="$1"; shift + if "$@"; then + ok "$desc" + return 0 + else + fail "$desc" + STEP_FAILS=$((STEP_FAILS + 1)) + return 1 + fi +} + +# ── Чистка артефактов ───────────────────────────────────────────────────────── +clean_artifacts() { + local dir="$1" + rm -rf "$dir/.terraform" "$dir/.terraform.lock.hcl" \ + "$dir/terraform.tfstate" "$dir/terraform.tfstate.backup" \ + "$dir"/terraform.tfstate.*.backup +} + +# ══════════════════════════════════════════════════════════════════════════════ +# ТЕСТ: hello-node +# ══════════════════════════════════════════════════════════════════════════════ +test_hello_node() { + local ex="hello-node" + local dir="$EXAMPLES_DIR/$ex" + local log="$LOGS_DIR/$ex" + ACTIVE_EXAMPLE="$ex" + STEP_NUM=0; STEP_FAILS=0 + local t0=$SECONDS + + header "hello-node (nodejs20 · HTTP trigger + Job)" + restore_all_backups "$ex" + clean_artifacts "$dir" + + # ── init ────────────────────────────────────────────────────────────────── + next_step "terraform init" + assert_step "init OK" tf_init "${log}-init.log" "$dir" || { RESULTS[$ex]="FAIL: init"; return 1; } + + # ── ROUND 1: чистый цикл ───────────────────────────────────────────────── + next_step "[R1] terraform apply" + assert_step "apply OK" tf_apply "${log}-r1-apply.log" "$dir" || { RESULTS[$ex]="FAIL: R1 apply"; return 1; } + assert_step "Apply complete in log" assert_apply_ok "${log}-r1-apply.log" + + local trigger_url + trigger_url=$(tf_output "$dir" "trigger_url") + next_step "[R1] HTTP check — trigger alive (url: $trigger_url)" + assert_step "HTTP 200" http_check "$trigger_url" 200 15 10 + + next_step "[R1] Job result check" + local job_phase + job_phase=$(tf_output "$dir" "job_phase") + assert_step "Job phase = Succeeded" [ "$job_phase" = "Succeeded" ] + local job_msg + job_msg=$(tf_output "$dir" "job_message") + assert_step "Job message contains sum" echo "$job_msg" | grep -q '"sum"' + info "Job output: $job_msg" + + next_step "[R1] terraform destroy" + assert_step "destroy OK" tf_destroy "${log}-r1-destroy.log" "$dir" || { RESULTS[$ex]="FAIL: R1 destroy"; return 1; } + assert_step "Destroy complete in log" assert_destroy_ok "${log}-r1-destroy.log" + + next_step "[R1] HTTP check — trigger gone (want 404/502+unreachable)" + # после destroy endpoint должен исчезнуть или вернуть 404 + local gone=0 + for i in $(seq 1 18); do + local code + code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 "$trigger_url" 2>/dev/null || echo "000") + if [ "$code" = "404" ] || [ "$code" = "000" ]; then + gone=1; break + fi + info " still up (HTTP $code), wait 5s... ($i/18)" + sleep 5 + done + assert_step "endpoint gone after destroy" [ "$gone" -eq 1 ] + + # ── ROUND 2: apply → 3 модификации → destroy ───────────────────────────── + next_step "[R2] terraform apply (fresh)" + assert_step "apply OK" tf_apply "${log}-r2-apply.log" "$dir" || { RESULTS[$ex]="FAIL: R2 apply"; return 1; } + + trigger_url=$(tf_output "$dir" "trigger_url") + + # Mod 1: memory 128 → 256 + next_step "[R2-mod1] memory_mb 128→256 в http.tf" + backup_file "$dir/http.tf" + patch_memory "$dir/http.tf" 128 256 + assert_step "apply mod1 OK" tf_apply "${log}-r2-mod1.log" "$dir" + assert_step "mod1: 1 changed" grep -q '1 changed' "${log}-r2-mod1.log" + assert_step "HTTP 200 after mod1" http_check "$trigger_url" 200 10 8 + + # Mod 2: добавить env_var + убрать + next_step "[R2-mod2] добавить env_var GREETING" + backup_file "$dir/job.tf" # на случай если патч затронет и его + add_env_var "$dir/http.tf" "GREETING" "stress-test-v1" + assert_step "apply mod2 OK" tf_apply "${log}-r2-mod2.log" "$dir" + assert_step "HTTP 200 after mod2" http_check "$trigger_url" 200 10 8 + info " mod2 changes: $(grep -E 'changed|updated' "${log}-r2-mod2.log" | head -2)" + + # Mod 3: timeout 30 → 45 + next_step "[R2-mod3] timeout_sec 30→45 в http.tf" + patch_timeout "$dir/http.tf" 30 45 + assert_step "apply mod3 OK" tf_apply "${log}-r2-mod3.log" "$dir" + assert_step "HTTP 200 after mod3" http_check "$trigger_url" 200 10 8 + + # Mod 4: trigger.enabled false → функция остаётся, триггер отключается + next_step "[R2-mod4] trigger enabled=false" + patch_enabled "$dir/http.tf" false + assert_step "apply mod4 OK" tf_apply "${log}-r2-mod4.log" "$dir" + # enabled=false → Deployment replicas=0 → /fn/ вернёт 503 или пустой ответ + info " trigger disabled — проверяем что НЕ 200" + local code_after_disable + code_after_disable=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 "$trigger_url" 2>/dev/null || echo "000") + ok " HTTP $code_after_disable (trigger disabled)" + + # Mod 5: trigger enabled=true обратно + next_step "[R2-mod5] trigger enabled=true (restore)" + patch_enabled "$dir/http.tf" true + assert_step "apply mod5 OK" tf_apply "${log}-r2-mod5.log" "$dir" + assert_step "HTTP 200 after re-enable" http_check "$trigger_url" 200 12 10 + + # Job re-run: bump run_id 9 → 10 + next_step "[R2-job-rerun] job re-run (run_id 9→10)" + backup_file "$dir/job.tf" + perl -pi -e 's/(run_id\s+=\s+)9/${1}10/' "$dir/job.tf" + assert_step "apply job-rerun OK" tf_apply "${log}-r2-job-rerun.log" "$dir" + local new_job_phase + new_job_phase=$(tf_output "$dir" "job_phase") + assert_step "re-run Succeeded" [ "$new_job_phase" = "Succeeded" ] + local new_job_msg + new_job_msg=$(tf_output "$dir" "job_message") + info " re-run output: $new_job_msg" + + # Восстанавливаем файлы до оригинала перед destroy + restore_all_backups "$ex" + + next_step "[R2] terraform destroy" + assert_step "destroy OK" tf_destroy "${log}-r2-destroy.log" "$dir" || { RESULTS[$ex]="FAIL: R2 destroy"; return 1; } + assert_step "Destroy complete" assert_destroy_ok "${log}-r2-destroy.log" + + # ── ROUND 3: apply → немедленный destroy (no-wait) ─────────────────────── + next_step "[R3] apply → immediate destroy" + assert_step "R3 apply OK" tf_apply "${log}-r3-apply.log" "$dir" || { RESULTS[$ex]="FAIL: R3 apply"; return 1; } + assert_step "R3 destroy OK" tf_destroy "${log}-r3-destroy.log" "$dir" || { RESULTS[$ex]="FAIL: R3 destroy"; return 1; } + + clean_artifacts "$dir" + TIMINGS[$ex]=$((SECONDS - t0)) + + if [ "$STEP_FAILS" -eq 0 ]; then + RESULTS[$ex]="PASS" + ok "hello-node все шаги PASS (${TIMINGS[$ex]}s)" + else + RESULTS[$ex]="FAIL: $STEP_FAILS step(s) failed" + fail "hello-node: $STEP_FAILS шагов с ошибкой" + fi + ACTIVE_EXAMPLE="" +} + +# ══════════════════════════════════════════════════════════════════════════════ +# ТЕСТ: simple-node / simple-python (одинаковая логика, разный runtime) +# ══════════════════════════════════════════════════════════════════════════════ +test_simple() { + local ex="$1" # simple-node | simple-python + local dir="$EXAMPLES_DIR/$ex" + local log="$LOGS_DIR/$ex" + ACTIVE_EXAMPLE="$ex" + STEP_NUM=0; STEP_FAILS=0 + local t0=$SECONDS + + header "$ex" + restore_all_backups "$ex" + clean_artifacts "$dir" + + # init + next_step "terraform init" + assert_step "init OK" tf_init "${log}-init.log" "$dir" || { RESULTS[$ex]="FAIL: init"; return 1; } + + # ── ROUND 1 ─────────────────────────────────────────────────────────────── + next_step "[R1] apply" + assert_step "apply OK" tf_apply "${log}-r1-apply.log" "$dir" || { RESULTS[$ex]="FAIL: R1 apply"; return 1; } + + local display_url + display_url=$(tf_output "$dir" "display_url") + next_step "[R1] HTTP check — display alive" + assert_step "HTTP 200" http_check "$display_url" 200 15 10 + + local job_result + job_result=$(tf_output "$dir" "job_result") + assert_step "job_result contains time" echo "$job_result" | grep -q '"time"' + info " job result: $job_result" + + next_step "[R1] destroy" + assert_step "destroy OK" tf_destroy "${log}-r1-destroy.log" "$dir" || { RESULTS[$ex]="FAIL: R1 destroy"; return 1; } + + next_step "[R1] endpoint gone check" + local gone=0 + for i in $(seq 1 18); do + local code + code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 "$display_url" 2>/dev/null || echo "000") + if [ "$code" = "404" ] || [ "$code" = "000" ]; then gone=1; break; fi + info " still $code, wait 5s... ($i/18)" + sleep 5 + done + assert_step "endpoint gone" [ "$gone" -eq 1 ] + + # ── ROUND 2: три модификации ────────────────────────────────────────────── + next_step "[R2] apply (fresh)" + assert_step "apply OK" tf_apply "${log}-r2-apply.log" "$dir" || { RESULTS[$ex]="FAIL: R2 apply"; return 1; } + display_url=$(tf_output "$dir" "display_url") + + # Mod 1: memory 64 → 96 в time-display.tf + next_step "[R2-mod1] memory 64→96 (time-display)" + backup_file "$dir/time-display.tf" + patch_memory "$dir/time-display.tf" 64 96 + assert_step "apply mod1 OK" tf_apply "${log}-r2-mod1.log" "$dir" + assert_step "1 changed" grep -q '1 changed' "${log}-r2-mod1.log" + assert_step "HTTP 200 after mod1" http_check "$display_url" 200 12 10 + + # Mod 2: memory 64 → 96 в time-getter.tf + next_step "[R2-mod2] memory 64→96 (time-getter)" + backup_file "$dir/time-getter.tf" + patch_memory "$dir/time-getter.tf" 64 96 + assert_step "apply mod2 OK" tf_apply "${log}-r2-mod2.log" "$dir" + assert_step "1 changed" grep -q '1 changed' "${log}-r2-mod2.log" + assert_step "HTTP 200 after mod2" http_check "$display_url" 200 10 8 + + # Mod 3: добавить env_var в time-display.tf + next_step "[R2-mod3] добавить env_var TEST_LABEL в time-display" + add_env_var "$dir/time-display.tf" "TEST_LABEL" "stress-round2" + assert_step "apply mod3 OK" tf_apply "${log}-r2-mod3.log" "$dir" + assert_step "HTTP 200 after mod3" http_check "$display_url" 200 10 8 + + # Mod 4: job re-run (run_id 1 → 2) + next_step "[R2-mod4] job re-run (run_id 1→2)" + backup_file "$dir/time-getter.tf" + perl -pi -e 's/(run_id\s+=\s+)1/${1}2/' "$dir/time-getter.tf" + assert_step "apply mod4 (job rerun) OK" tf_apply "${log}-r2-mod4.log" "$dir" + local new_job + new_job=$(tf_output "$dir" "job_result") + assert_step "re-run result has time" echo "$new_job" | grep -q '"time"' + info " re-run result: $new_job" + assert_step "HTTP 200 after job rerun" http_check "$display_url" 200 10 8 + + # Mod 5: timeout 30 → 60 в time-display.tf + next_step "[R2-mod5] timeout 30→60 (time-display)" + patch_timeout "$dir/time-display.tf" 30 60 + assert_step "apply mod5 OK" tf_apply "${log}-r2-mod5.log" "$dir" + + restore_all_backups "$ex" + + next_step "[R2] destroy" + assert_step "destroy OK" tf_destroy "${log}-r2-destroy.log" "$dir" || { RESULTS[$ex]="FAIL: R2 destroy"; return 1; } + + # ── ROUND 3: immediate destroy ──────────────────────────────────────────── + next_step "[R3] apply → immediate destroy" + assert_step "R3 apply OK" tf_apply "${log}-r3-apply.log" "$dir" || { RESULTS[$ex]="FAIL: R3 apply"; return 1; } + assert_step "R3 destroy OK" tf_destroy "${log}-r3-destroy.log" "$dir" || { RESULTS[$ex]="FAIL: R3 destroy"; return 1; } + + clean_artifacts "$dir" + TIMINGS[$ex]=$((SECONDS - t0)) + + if [ "$STEP_FAILS" -eq 0 ]; then + RESULTS[$ex]="PASS" + ok "$ex все шаги PASS (${TIMINGS[$ex]}s)" + else + RESULTS[$ex]="FAIL: $STEP_FAILS step(s) failed" + fail "$ex: $STEP_FAILS шагов с ошибкой" + fi + ACTIVE_EXAMPLE="" +} + +# ══════════════════════════════════════════════════════════════════════════════ +# ТЕСТ: notes-python (postgres + CRUD) +# ══════════════════════════════════════════════════════════════════════════════ +test_notes_python() { + local ex="notes-python" + local dir="$EXAMPLES_DIR/$ex" + local log="$LOGS_DIR/$ex" + ACTIVE_EXAMPLE="$ex" + STEP_NUM=0; STEP_FAILS=0 + local t0=$SECONDS + + header "notes-python (python3.11 · PostgreSQL CRUD)" + restore_all_backups "$ex" + clean_artifacts "$dir" + + next_step "terraform init" + assert_step "init OK" tf_init "${log}-init.log" "$dir" || { RESULTS[$ex]="FAIL: init"; return 1; } + + # ── ROUND 1 ─────────────────────────────────────────────────────────────── + next_step "[R1] apply (DB init + CRUD deploy)" + assert_step "apply OK" tf_apply "${log}-r1-apply.log" "$dir" || { RESULTS[$ex]="FAIL: R1 apply"; return 1; } + + # Проверяем DB init джобы + local tbl_phase idx_phase + tbl_phase=$(tf_output "$dir" "db_init_table_status" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('phase','?'))" 2>/dev/null || echo "?") + idx_phase=$(tf_output "$dir" "db_init_index_status" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('phase','?'))" 2>/dev/null || echo "?") + next_step "[R1] DB init jobs check" + assert_step "table init Succeeded" [ "$tbl_phase" = "Succeeded" ] + assert_step "index init Succeeded" [ "$idx_phase" = "Succeeded" ] + + local notes_url notes_list_url + notes_url=$(tf_output "$dir" "notes_url") + notes_list_url=$(tf_output "$dir" "notes_list_url") + next_step "[R1] HTTP check — notes alive" + assert_step "notes HTTP 200" http_check "$notes_url" 200 15 10 + assert_step "notes-list HTTP 200" http_check "$notes_list_url" 200 10 8 + + # CRUD: добавить несколько записей + next_step "[R1] CRUD: add notes" + local add1 add2 add3 + add1=$(http_post "${notes_url}/add?title=StressTest1&body=body1" '{}') + add2=$(http_post "${notes_url}/add?title=StressTest2&body=body2" '{}') + add3=$(http_post "${notes_url}/add?title=StressTest3&body=body3" '{}') + assert_step "add note1 OK" echo "$add1" | grep -qiE '"id"|"created|created_at' + assert_step "add note2 OK" echo "$add2" | grep -qiE '"id"|"created|created_at' + assert_step "add note3 OK" echo "$add3" | grep -qiE '"id"|"created|created_at' + info " add1: $add1" + info " add2: $add2" + + # List: проверить что записи есть + next_step "[R1] CRUD: list notes" + local listed + listed=$(http_get "$notes_list_url") + assert_step "list contains StressTest" echo "$listed" | grep -q 'StressTest' + info " list (первые 200 символов): ${listed:0:200}" + + # Update nota1 (если вернулся id) + local note_id + note_id=$(echo "$add1" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('id',''))" 2>/dev/null || echo "") + if [ -n "$note_id" ]; then + next_step "[R1] CRUD: update note id=$note_id" + local updated + updated=$(http_post "${notes_url}/update?id=${note_id}&title=StressUpdated&body=updated_body" '{}') + assert_step "update OK" echo "$updated" | grep -qiE '"updated|ok|success|StressUpdated' + info " update: $updated" + fi + + # ── ROUND 2: модификации ────────────────────────────────────────────────── + # Mod 1: memory 128→192 в notes.tf + next_step "[R2-mod1] memory 128→192 (notes.tf)" + backup_file "$dir/notes.tf" + patch_memory "$dir/notes.tf" 128 192 + assert_step "apply mod1 OK" tf_apply "${log}-r2-mod1.log" "$dir" + assert_step "1 changed" grep -q '1 changed' "${log}-r2-mod1.log" + assert_step "notes HTTP 200 after mod1" http_check "$notes_url" 200 12 10 + + # Mod 2: memory 128→192 в notes-list.tf + next_step "[R2-mod2] memory 128→192 (notes-list.tf)" + backup_file "$dir/notes-list.tf" + patch_memory "$dir/notes-list.tf" 128 192 + assert_step "apply mod2 OK" tf_apply "${log}-r2-mod2.log" "$dir" + assert_step "notes-list HTTP 200 after mod2" http_check "$notes_list_url" 200 12 10 + + # Mod 3: добавить env_var в notes.tf + next_step "[R2-mod3] env_var MAX_NOTES=100 в notes.tf" + add_env_var "$dir/notes.tf" "MAX_NOTES" "100" + assert_step "apply mod3 OK" tf_apply "${log}-r2-mod3.log" "$dir" + assert_step "HTTP 200 after mod3" http_check "$notes_url" 200 12 10 + + # Mod 4: timeout 30→60 в notes.tf + next_step "[R2-mod4] timeout 30→60 (notes.tf)" + patch_timeout "$dir/notes.tf" 30 60 + assert_step "apply mod4 OK" tf_apply "${log}-r2-mod4.log" "$dir" + + # CRUD после модификаций + next_step "[R2] CRUD: list после модификаций" + listed=$(http_get "$notes_list_url") + assert_step "list still works" echo "$listed" | grep -qiE '"id"|\[\]' + info " list: ${listed:0:200}" + + next_step "[R2] CRUD: add ещё запись" + local add4 + add4=$(http_post "${notes_url}/add?title=PostMod&body=after_modifications" '{}') + assert_step "add after mod OK" echo "$add4" | grep -qiE '"id"|"created' + + restore_all_backups "$ex" + + next_step "[R2] destroy" + assert_step "destroy OK" tf_destroy "${log}-r2-destroy.log" "$dir" || { RESULTS[$ex]="FAIL: R2 destroy"; return 1; } + + # ── ROUND 3: apply → immediate destroy ─────────────────────────────────── + next_step "[R3] apply → immediate destroy" + assert_step "R3 apply OK" tf_apply "${log}-r3-apply.log" "$dir" || { RESULTS[$ex]="FAIL: R3 apply"; return 1; } + assert_step "R3 destroy OK" tf_destroy "${log}-r3-destroy.log" "$dir" || { RESULTS[$ex]="FAIL: R3 destroy"; return 1; } + + clean_artifacts "$dir" + TIMINGS[$ex]=$((SECONDS - t0)) + + if [ "$STEP_FAILS" -eq 0 ]; then + RESULTS[$ex]="PASS" + ok "notes-python все шаги PASS (${TIMINGS[$ex]}s)" + else + RESULTS[$ex]="FAIL: $STEP_FAILS step(s) failed" + fail "notes-python: $STEP_FAILS шагов с ошибкой" + fi + ACTIVE_EXAMPLE="" +} + +# ══════════════════════════════════════════════════════════════════════════════ +# MAIN +# ══════════════════════════════════════════════════════════════════════════════ +main() { + local total_t0=$SECONDS + + echo -e "\n${BOLD}${CYAN}" + echo "╔══════════════════════════════════════════════╗" + echo "║ sless STRESS TEST $(date '+%Y-%m-%d %H:%M:%S') ║" + echo "╚══════════════════════════════════════════════╝" + echo -e "${RESET}" + echo "Примеры : ${EXAMPLES[*]}" + echo "Логи : $LOGS_DIR" + echo + echo "На каждый пример:" + echo " R1: apply → HTTP-check → destroy → endpoint-gone-check" + echo " R2: apply → 4-5 модификаций (memory, env_var, timeout, re-run) → destroy" + echo " R3: apply → immediate destroy" + echo + + for ex in "${EXAMPLES[@]}"; do + case "$ex" in + hello-node) test_hello_node || true ;; + simple-node) test_simple "$ex" || true ;; + simple-python) test_simple "$ex" || true ;; + notes-python) test_notes_python || true ;; + *) fail "Unknown example: $ex"; RESULTS[$ex]="SKIP: unknown" ;; + esac + done + + # ── Финальная сводка ────────────────────────────────────────────────────── + local total_elapsed=$((SECONDS - total_t0)) + echo + echo -e "${BOLD}${CYAN}════════════════ ИТОГ ════════════════${RESET}" + local all_pass=1 + for ex in "${EXAMPLES[@]}"; do + local result="${RESULTS[$ex]:-UNKNOWN}" + local timing="${TIMINGS[$ex]:-?}" + if [ "$result" = "PASS" ]; then + ok "$(printf '%-18s' "$ex") ${result} (${timing}s)" + else + fail "$(printf '%-18s' "$ex") ${result} (${timing}s)" + all_pass=0 + fi + done + echo -e "${BOLD}${CYAN}══════════════════════════════════════${RESET}" + echo "Общее время: ${total_elapsed}s" + echo "Логи: $LOGS_DIR" + echo + + if [ "$all_pass" -eq 1 ]; then + echo -e "${GREEN}${BOLD}✓ Все тесты прошли успешно.${RESET}" + exit 0 + else + echo -e "${RED}${BOLD}✗ Есть ошибки — см. логи выше и в $LOGS_DIR${RESET}" + exit 1 + fi +} + +main diff --git a/test_pearlharbor_push.sh b/test_pearlharbor_push.sh new file mode 100755 index 0000000..441fcfb --- /dev/null +++ b/test_pearlharbor_push.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +# 2026-03-11 12:30 +# Наборный тест: собирает образ, выполняет несколько пушей с разными тегами +# и выводит статистику (количество успешных/проваленных, время и попытки). + +set -euo pipefail + +SECRETS_FILE="secrets/pearlharbor_registry.txt" +if [ ! -f "$SECRETS_FILE" ]; then + echo "secrets file not found: $SECRETS_FILE" >&2 + exit 1 +fi + +NUM_PUSHES=${NUM_PUSHES:-5} +RETRIES_PER_PUSH=${RETRIES_PER_PUSH:-3} +BACKOFF=${BACKOFF:-2} +PROJECT=${PROJECT:-pearlharbor} +REGISTRY_USER=${REGISTRY_USER:-admin} +CREATE_PROJECT=${CREATE_PROJECT:-false} + +connection_url=$(grep -E '^connection_url=' "$SECRETS_FILE" | cut -d'=' -f2-) +admin_pass=$(grep -E '^admin_pass=' "$SECRETS_FILE" | cut -d'=' -f2-) +registry=$(echo "$connection_url" | sed -E 's~https?://~~' | sed -E 's~/$~~') + +if ! command -v docker >/dev/null 2>&1; then + echo "docker not found" >&2 + exit 2 +fi + +echo "Registry: $registry" +echo "Project: $PROJECT" +echo "Pushes: $NUM_PUSHES, retries per push: $RETRIES_PER_PUSH" + +echo "Building local image..." +docker build -t sless-sample:local -f examples/push-sample/Dockerfile examples/push-sample + +if [ "$CREATE_PROJECT" = "true" ]; then + echo "Ensuring project $PROJECT exists..." + create_code=$(curl -s -u "$REGISTRY_USER:$admin_pass" -o /dev/null -w "%{http_code}" -X POST "https://$registry/api/v2.0/projects" -H 'Content-Type: application/json' -d "{\"project_name\":\"$PROJECT\",\"metadata\":{\"public\":\"true\"}}") || create_code=0 + echo "Project create response: $create_code" +fi + +echo "Logging in to registry..." +if ! echo "$admin_pass" | docker login "$registry" -u "$REGISTRY_USER" --password-stdin >/dev/null 2>&1; then + echo "docker login failed" >&2 + exit 3 +fi + +results=() +start_all=$(date +%s.%N) +for i in $(seq 1 "$NUM_PUSHES"); do + tag="test-$(date +%Y%m%d%H%M%S)-$i" + remote="$registry/$PROJECT/sless-sample:$tag" + echo "\n=== Push $i/$NUM_PUSHES -> $remote ===" + docker tag sless-sample:local "$remote" + + attempt=0 + success=0 + t_start=$(date +%s.%N) + last_err="" + while [ $attempt -lt $RETRIES_PER_PUSH ]; do + attempt=$((attempt+1)) + echo "Attempt $attempt for $remote" + if docker push "$remote"; then + success=1 + break + else + last_err="push failed on attempt $attempt" + sleep_time=$((BACKOFF ** attempt)) + echo "push failed, sleeping $sleep_time s" + sleep $sleep_time + fi + done + t_end=$(date +%s.%N) + elapsed=$(printf "%.3f" "$(echo "$t_end - $t_start" | bc -l)") + results+=("$remote|$success|$attempt|$elapsed|$last_err") +done +end_all=$(date +%s.%N) +total_time=$(printf "%.3f" "$(echo "$end_all - $start_all" | bc -l)") + +echo "\n=== Summary ===" +echo "Total pushes: $NUM_PUSHES, total time: ${total_time}s" +printf "%s\n" "TAG | SUCCESS | ATTEMPTS | TIME_S | MESSAGE" +for r in "${results[@]}"; do + IFS='|' read -r tag ok attempts time msg <<< "$r" + if [ "$ok" -eq 1 ]; then ok_str="OK"; else ok_str="FAIL"; fi + printf "%s | %s | %s | %s | %s\n" "$tag" "$ok_str" "$attempts" "$time" "${msg:--}" +done + +fail_count=0 +for r in "${results[@]}"; do + IFS='|' read -r tag ok attempts time msg <<< "$r" + if [ "$ok" -ne 1 ]; then fail_count=$((fail_count+1)); fi +done + +echo "Failures: $fail_count / $NUM_PUSHES" +if [ "$fail_count" -ne 0 ]; then + exit 4 +fi +exit 0