From 3c4e87b5a3f7a7b1e8bbd87727a1d42ac1d69ff0 Mon Sep 17 00:00:00 2001 From: Katharina Przybill <30441792+kathap@users.noreply.github.com> Date: Fri, 18 Jul 2025 18:43:39 +0200 Subject: [PATCH 1/5] Add cc uploader configurable drain timeout - Add `capi.cc_uploader.cc_uploader_drain_timeout_in_minutes` (type: time, default: 15m) to job spec - Inject `--shutdownTimeoutInMinutes=<%= p("capi.cc_uploader.cc_uploader_drain_timeout_in_minutes") %>` into `bpm.yml.erb` - Update `drain.sh.erb` to read the same property, apply the timeout when waiting for PID exit - Retain 15-minute fallback in the script for robustness against missing property values --- jobs/cc_uploader/spec | 6 +++++ jobs/cc_uploader/templates/bpm.yml.erb | 1 + jobs/cc_uploader/templates/drain.sh.erb | 33 +++++++++++++++++++++++++ 3 files changed, 40 insertions(+) create mode 100755 jobs/cc_uploader/templates/drain.sh.erb diff --git a/jobs/cc_uploader/spec b/jobs/cc_uploader/spec index 0114fdf097..561066da1f 100644 --- a/jobs/cc_uploader/spec +++ b/jobs/cc_uploader/spec @@ -14,6 +14,7 @@ templates: cc_uploader_server.crt.erb: config/certs/cc_uploader/server.crt cc_uploader_server.key.erb: config/certs/cc_uploader/server.key pre-start.erb: bin/pre-start + drain.sh.erb: bin/drain packages: - capi_utils @@ -75,3 +76,8 @@ properties: description: "PEM-encoded certificate for secure, mutually authenticated TLS communication" capi.cc_uploader.mutual_tls.server_key: description: "PEM-encoded key for secure, mutually authenticated TLS communication" + + capi.cc_uploader.cc_uploader_drain_timeout_in_minutes: + description: Maximum time to wait for in‐flight uploads to finish before forcing shutdown + type: time + default: 15m \ No newline at end of file diff --git a/jobs/cc_uploader/templates/bpm.yml.erb b/jobs/cc_uploader/templates/bpm.yml.erb index c13035e68f..3ebf076e4c 100644 --- a/jobs/cc_uploader/templates/bpm.yml.erb +++ b/jobs/cc_uploader/templates/bpm.yml.erb @@ -4,5 +4,6 @@ processes: executable: /var/vcap/packages/cc_uploader/bin/cc-uploader args: - --configPath=/var/vcap/jobs/cc_uploader/config/cc_uploader_config.json + - --shutdownTimeoutInMinutes=<%= p("capi.cc_uploader.cc_uploader_drain_timeout_in_minutes") %> limits: open_files: 100000 diff --git a/jobs/cc_uploader/templates/drain.sh.erb b/jobs/cc_uploader/templates/drain.sh.erb new file mode 100755 index 0000000000..ed7c6aab97 --- /dev/null +++ b/jobs/cc_uploader/templates/drain.sh.erb @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +LOG_FILE="/var/vcap/sys/log/cc_uploader/drain.log" +PID_FILE="/var/vcap/sys/run/bpm/cc_uploader/cc_uploader.pid" + +TIMEOUT_MINUTES="<%= p("capi.cc_uploader.cc_uploader_drain_timeout_in_minutes") %>" + +TIMEOUT_MINUTES="${TIMEOUT_MINUTES%m}" +TIMEOUT_MINUTES="${TIMEOUT_MINUTES:-15}" + +DRAIN_TIMEOUT=$(( TIMEOUT_MINUTES * 60 )) +START_TS=$(date +%s) + +echo "$(date): cc_uploader drain starting (timeout ${DRAIN_TIMEOUT}s)" >> "$LOG_FILE" + +if [ -f "$PID_FILE" ]; then + kill -TERM "$(cat "$PID_FILE")" + while kill -0 "$(cat "$PID_FILE")" >/dev/null 2>&1; do + NOW_TS=$(date +%s) + ELAPSED=$(( NOW_TS - START_TS )) + if [ "$ELAPSED" -ge "$DRAIN_TIMEOUT" ]; then + echo "$(date): drain timeout reached after ${DRAIN_TIMEOUT}s, forcing exit" >> "$LOG_FILE" + break + fi + echo "$(date): waiting for cc_uploader (elapsed ${ELAPSED}s)" >> "$LOG_FILE" + sleep 1 + done +fi + +echo "$(date): drain complete, returning 0 to BOSH" >> "$LOG_FILE" +echo 0 +exit 0 + From ae3cd5539c3882a0686bbebd803738e9fb9276fd Mon Sep 17 00:00:00 2001 From: Katharina Przybill <30441792+kathap@users.noreply.github.com> Date: Tue, 22 Jul 2025 15:39:36 +0200 Subject: [PATCH 2/5] Add test for cc uploader drain script rendering --- spec/cc_uploader/cc_uploader_drain_spec.rb | 60 ++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 spec/cc_uploader/cc_uploader_drain_spec.rb diff --git a/spec/cc_uploader/cc_uploader_drain_spec.rb b/spec/cc_uploader/cc_uploader_drain_spec.rb new file mode 100644 index 0000000000..a2efb233e1 --- /dev/null +++ b/spec/cc_uploader/cc_uploader_drain_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'rspec' +require 'bosh/template/test' +require 'yaml' +require 'json' + +module Bosh + module Template + module Test + describe 'cc_uploader drain script rendering' do + let(:release_path) { File.join(File.dirname(__FILE__), '../..') } + let(:release) { ReleaseDir.new(release_path) } + let(:job) { release.job('cc_uploader') } + let(:template) { job.template('bin/drain') } + + context 'when capi.cc_uploader.cc_uploader_drain_timeout_in_minutes is provided' do + let(:properties) do + { + 'capi' => { + 'cc_uploader' => { + 'cc_uploader_drain_timeout_in_minutes' => '10m' + } + } + } + end + + it 'renders the drain script with correct timeout' do + rendered = template.render(properties) + expect(rendered).to include('TIMEOUT_MINUTES="10m"') + expect(rendered).to include('DRAIN_TIMEOUT=$(( TIMEOUT_MINUTES * 60 ))') + end + end + + context 'when capi.cc_uploader.cc_uploader_drain_timeout_in_minutes is not provided' do + it 'defaults to 15 minutes' do + rendered = template.render({}) + expect(rendered).to include('TIMEOUT_MINUTES="15m"') + end + end + + it 'writes logs to the expected log file' do + rendered = template.render({}) + expect(rendered).to include('LOG_FILE="/var/vcap/sys/log/cc_uploader/drain.log"') + end + + it 'uses the correct PID file location' do + rendered = template.render({}) + expect(rendered).to include('PID_FILE="/var/vcap/sys/run/bpm/cc_uploader/cc_uploader.pid"') + end + + it 'safely attempts to terminate the cc_uploader process' do + rendered = template.render({}) + expect(rendered).to include('kill -TERM "$(cat "$PID_FILE")"') + expect(rendered).to include('kill -0 "$(cat "$PID_FILE")"') + end + end + end + end +end From 821b3d51229f7fd85ceb576427280426801117a2 Mon Sep 17 00:00:00 2001 From: Katharina Przybill <30441792+kathap@users.noreply.github.com> Date: Tue, 29 Jul 2025 10:31:00 +0200 Subject: [PATCH 3/5] Rename timeout, remove double default assignement --- jobs/cc_uploader/templates/drain.sh.erb | 9 ++++----- spec/cc_uploader/cc_uploader_drain_spec.rb | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/jobs/cc_uploader/templates/drain.sh.erb b/jobs/cc_uploader/templates/drain.sh.erb index ed7c6aab97..336d314950 100755 --- a/jobs/cc_uploader/templates/drain.sh.erb +++ b/jobs/cc_uploader/templates/drain.sh.erb @@ -6,20 +6,19 @@ PID_FILE="/var/vcap/sys/run/bpm/cc_uploader/cc_uploader.pid" TIMEOUT_MINUTES="<%= p("capi.cc_uploader.cc_uploader_drain_timeout_in_minutes") %>" TIMEOUT_MINUTES="${TIMEOUT_MINUTES%m}" -TIMEOUT_MINUTES="${TIMEOUT_MINUTES:-15}" -DRAIN_TIMEOUT=$(( TIMEOUT_MINUTES * 60 )) +DRAIN_TIMEOUT_IN_SECONDS=$(( TIMEOUT_MINUTES * 60 )) START_TS=$(date +%s) -echo "$(date): cc_uploader drain starting (timeout ${DRAIN_TIMEOUT}s)" >> "$LOG_FILE" +echo "$(date): cc_uploader drain starting (timeout ${DRAIN_TIMEOUT_IN_SECONDS}s)" >> "$LOG_FILE" if [ -f "$PID_FILE" ]; then kill -TERM "$(cat "$PID_FILE")" while kill -0 "$(cat "$PID_FILE")" >/dev/null 2>&1; do NOW_TS=$(date +%s) ELAPSED=$(( NOW_TS - START_TS )) - if [ "$ELAPSED" -ge "$DRAIN_TIMEOUT" ]; then - echo "$(date): drain timeout reached after ${DRAIN_TIMEOUT}s, forcing exit" >> "$LOG_FILE" + if [ "$ELAPSED" -ge "DRAIN_TIMEOUT_IN_SECONDS" ]; then + echo "$(date): drain timeout reached after ${DRAIN_TIMEOUT_IN_SECONDS}s" >> "$LOG_FILE" break fi echo "$(date): waiting for cc_uploader (elapsed ${ELAPSED}s)" >> "$LOG_FILE" diff --git a/spec/cc_uploader/cc_uploader_drain_spec.rb b/spec/cc_uploader/cc_uploader_drain_spec.rb index a2efb233e1..065bdd3ef4 100644 --- a/spec/cc_uploader/cc_uploader_drain_spec.rb +++ b/spec/cc_uploader/cc_uploader_drain_spec.rb @@ -28,7 +28,7 @@ module Test it 'renders the drain script with correct timeout' do rendered = template.render(properties) expect(rendered).to include('TIMEOUT_MINUTES="10m"') - expect(rendered).to include('DRAIN_TIMEOUT=$(( TIMEOUT_MINUTES * 60 ))') + expect(rendered).to include('DRAIN_TIMEOUT_IN_SECONDS=$(( TIMEOUT_MINUTES * 60 ))') end end From a2555de3024c28b479ae7283aba3e6c87499068d Mon Sep 17 00:00:00 2001 From: Katharina Przybill <30441792+kathap@users.noreply.github.com> Date: Thu, 14 Aug 2025 16:49:01 +0200 Subject: [PATCH 4/5] fix time comparison --- jobs/cc_uploader/templates/drain.sh.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jobs/cc_uploader/templates/drain.sh.erb b/jobs/cc_uploader/templates/drain.sh.erb index 336d314950..c4570c5111 100755 --- a/jobs/cc_uploader/templates/drain.sh.erb +++ b/jobs/cc_uploader/templates/drain.sh.erb @@ -17,7 +17,7 @@ if [ -f "$PID_FILE" ]; then while kill -0 "$(cat "$PID_FILE")" >/dev/null 2>&1; do NOW_TS=$(date +%s) ELAPSED=$(( NOW_TS - START_TS )) - if [ "$ELAPSED" -ge "DRAIN_TIMEOUT_IN_SECONDS" ]; then + if [ "$ELAPSED" -ge "$DRAIN_TIMEOUT_IN_SECONDS" ]; then echo "$(date): drain timeout reached after ${DRAIN_TIMEOUT_IN_SECONDS}s" >> "$LOG_FILE" break fi From 030c9d61ed0c3d9aec29cd10fa364e3f1d5c1703 Mon Sep 17 00:00:00 2001 From: Katharina Przybill <30441792+kathap@users.noreply.github.com> Date: Mon, 1 Sep 2025 11:38:12 +0200 Subject: [PATCH 5/5] pass minutes as integer instead of go duration format --- jobs/cc_uploader/spec | 4 ++-- jobs/cc_uploader/templates/drain.sh.erb | 2 -- spec/cc_uploader/cc_uploader_drain_spec.rb | 6 +++--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/jobs/cc_uploader/spec b/jobs/cc_uploader/spec index 561066da1f..395ca49ca4 100644 --- a/jobs/cc_uploader/spec +++ b/jobs/cc_uploader/spec @@ -78,6 +78,6 @@ properties: description: "PEM-encoded key for secure, mutually authenticated TLS communication" capi.cc_uploader.cc_uploader_drain_timeout_in_minutes: - description: Maximum time to wait for in‐flight uploads to finish before forcing shutdown + description: Maximum time in minutes to wait for in‐flight uploads to finish before forcing shutdown type: time - default: 15m \ No newline at end of file + default: 15 \ No newline at end of file diff --git a/jobs/cc_uploader/templates/drain.sh.erb b/jobs/cc_uploader/templates/drain.sh.erb index c4570c5111..f9a619cc80 100755 --- a/jobs/cc_uploader/templates/drain.sh.erb +++ b/jobs/cc_uploader/templates/drain.sh.erb @@ -5,8 +5,6 @@ PID_FILE="/var/vcap/sys/run/bpm/cc_uploader/cc_uploader.pid" TIMEOUT_MINUTES="<%= p("capi.cc_uploader.cc_uploader_drain_timeout_in_minutes") %>" -TIMEOUT_MINUTES="${TIMEOUT_MINUTES%m}" - DRAIN_TIMEOUT_IN_SECONDS=$(( TIMEOUT_MINUTES * 60 )) START_TS=$(date +%s) diff --git a/spec/cc_uploader/cc_uploader_drain_spec.rb b/spec/cc_uploader/cc_uploader_drain_spec.rb index 065bdd3ef4..fda932b89c 100644 --- a/spec/cc_uploader/cc_uploader_drain_spec.rb +++ b/spec/cc_uploader/cc_uploader_drain_spec.rb @@ -19,7 +19,7 @@ module Test { 'capi' => { 'cc_uploader' => { - 'cc_uploader_drain_timeout_in_minutes' => '10m' + 'cc_uploader_drain_timeout_in_minutes' => '10' } } } @@ -27,7 +27,7 @@ module Test it 'renders the drain script with correct timeout' do rendered = template.render(properties) - expect(rendered).to include('TIMEOUT_MINUTES="10m"') + expect(rendered).to include('TIMEOUT_MINUTES="10"') expect(rendered).to include('DRAIN_TIMEOUT_IN_SECONDS=$(( TIMEOUT_MINUTES * 60 ))') end end @@ -35,7 +35,7 @@ module Test context 'when capi.cc_uploader.cc_uploader_drain_timeout_in_minutes is not provided' do it 'defaults to 15 minutes' do rendered = template.render({}) - expect(rendered).to include('TIMEOUT_MINUTES="15m"') + expect(rendered).to include('TIMEOUT_MINUTES="15"') end end