diff --git a/jobs/cc_uploader/spec b/jobs/cc_uploader/spec index 0114fdf097..395ca49ca4 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 in minutes to wait for in‐flight uploads to finish before forcing shutdown + type: time + default: 15 \ 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..f9a619cc80 --- /dev/null +++ b/jobs/cc_uploader/templates/drain.sh.erb @@ -0,0 +1,30 @@ +#!/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") %>" + +DRAIN_TIMEOUT_IN_SECONDS=$(( TIMEOUT_MINUTES * 60 )) +START_TS=$(date +%s) + +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_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" + sleep 1 + done +fi + +echo "$(date): drain complete, returning 0 to BOSH" >> "$LOG_FILE" +echo 0 +exit 0 + 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..fda932b89c --- /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' => '10' + } + } + } + end + + it 'renders the drain script with correct timeout' do + rendered = template.render(properties) + expect(rendered).to include('TIMEOUT_MINUTES="10"') + expect(rendered).to include('DRAIN_TIMEOUT_IN_SECONDS=$(( 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="15"') + 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