#!/bin/bash # Zero-downtime deployment script for Foxhunt HFT Trading System # Implements canary deployment with performance validation set -euo pipefail # Configuration SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DEPLOYMENT_LOG="/home/jgrusewski/Work/foxhunt/logs/deployment-$(date +%s).log" FOXHUNT_HOME="/opt/foxhunt" BACKUP_DIR="/opt/foxhunt/backups" RELEASES_DIR="/opt/foxhunt/releases" CURRENT_LINK="/opt/foxhunt/current" DEPLOYMENT_MARKER="/tmp/deployment-in-progress" # Services in dependency order SERVICES=("foxhunt-core" "foxhunt-data" "foxhunt-risk" "foxhunt-ml" "foxhunt-tli") # Performance thresholds MAX_LATENCY_US=30 MIN_THROUGHPUT_OPS=1000 # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$DEPLOYMENT_LOG" } error() { echo -e "${RED}ERROR: $1${NC}" | tee -a "$DEPLOYMENT_LOG" } success() { echo -e "${GREEN}SUCCESS: $1${NC}" | tee -a "$DEPLOYMENT_LOG" } warning() { echo -e "${YELLOW}WARNING: $1${NC}" | tee -a "$DEPLOYMENT_LOG" } cleanup() { log "Cleaning up deployment artifacts..." rm -f "$DEPLOYMENT_MARKER" exit "${1:-1}" } trap cleanup EXIT INT TERM usage() { echo "Usage: $0 [options]" echo "Options:" echo " --strategy Deployment strategy (default: canary)" echo " --validate-only Only validate deployment, don't deploy" echo " --skip-performance Skip performance validation" echo " --rollback Rollback to previous version" exit 1 } validate_performance() { local service_name="$1" local endpoint="$2" log "Validating performance for $service_name..." # Check latency local latency_us latency_us=$(curl -s "$endpoint/metrics" | grep -o 'foxhunt_order_latency_microseconds [0-9]*' | awk '{print $2}' || echo "999") if [ "$latency_us" -gt "$MAX_LATENCY_US" ]; then error "Latency validation failed: ${latency_us}μs > ${MAX_LATENCY_US}μs threshold" return 1 fi # Check throughput local throughput throughput=$(curl -s "$endpoint/metrics" | grep -o 'foxhunt_orders_processed_total [0-9]*' | awk '{print $2}' || echo "0") if [ "$throughput" -lt "$MIN_THROUGHPUT_OPS" ]; then error "Throughput validation failed: ${throughput} ops/min < ${MIN_THROUGHPUT_OPS} threshold" return 1 fi success "Performance validation passed - Latency: ${latency_us}μs, Throughput: ${throughput} ops/min" return 0 } health_check() { local service_name="$1" local health_url="$2" local max_attempts=30 local attempt=0 log "Health checking $service_name at $health_url..." while [ $attempt -lt $max_attempts ]; do if curl -f -s "$health_url" > /dev/null 2>&1; then success "$service_name is healthy" return 0 fi attempt=$((attempt + 1)) log "Health check attempt $attempt/$max_attempts failed, retrying in 5s..." sleep 5 done error "$service_name failed health check after $max_attempts attempts" return 1 } backup_current_version() { log "Backing up current version..." if [ -L "$CURRENT_LINK" ]; then local current_version current_version=$(readlink "$CURRENT_LINK" | xargs basename) echo "$current_version" > "$FOXHUNT_HOME/.last-good-version" log "Backed up current version: $current_version" else warning "No current version found to backup" fi } canary_deployment() { local new_version="$1" local release_dir="$RELEASES_DIR/$new_version" log "Starting canary deployment for version $new_version..." # Step 1: Deploy to canary instance (if available) if systemctl is-active foxhunt-core-canary > /dev/null 2>&1; then log "Deploying to canary instance..." # Update canary with new version systemctl stop foxhunt-core-canary rsync -a "$release_dir/" /opt/foxhunt/canary/ systemctl start foxhunt-core-canary # Validate canary performance sleep 10 if ! health_check "foxhunt-core-canary" "http://localhost:8085/health"; then error "Canary deployment failed health check" return 1 fi if ! validate_performance "foxhunt-core-canary" "http://localhost:8085"; then error "Canary deployment failed performance validation" return 1 fi log "Canary validation successful, proceeding with production deployment..." fi # Step 2: Deploy to production with rolling update for service in "${SERVICES[@]}"; do log "Deploying $service..." # Stop service systemctl stop "$service" # Update symlink to new version ln -sfn "$release_dir" "$CURRENT_LINK" # Start service systemctl start "$service" # Validate service health case $service in foxhunt-core) health_check "$service" "http://localhost:8080/health" ;; foxhunt-tli) health_check "$service" "http://localhost:8081/health" ;; foxhunt-ml) health_check "$service" "http://localhost:8082/health" ;; foxhunt-risk) health_check "$service" "http://localhost:8083/health" ;; foxhunt-data) health_check "$service" "http://localhost:8084/health" ;; esac if [ $? -ne 0 ]; then error "Service $service failed to deploy" return 1 fi # Brief pause between services sleep 5 done # Step 3: Final system validation log "Performing final system validation..." # Core system performance check if ! validate_performance "foxhunt-core" "http://localhost:8080"; then error "Final performance validation failed" return 1 fi # gRPC connectivity check if ! grpcurl -plaintext localhost:50051 list > /dev/null 2>&1; then error "gRPC connectivity check failed" return 1 fi success "Canary deployment completed successfully" return 0 } rollback_deployment() { log "Starting emergency rollback..." local last_good_version if [ -f "$FOXHUNT_HOME/.last-good-version" ]; then last_good_version=$(cat "$FOXHUNT_HOME/.last-good-version") else error "No previous version found for rollback" return 1 fi local rollback_dir="$RELEASES_DIR/$last_good_version" if [ ! -d "$rollback_dir" ]; then error "Rollback version directory not found: $rollback_dir" return 1 fi log "Rolling back to version: $last_good_version" # Quick rollback - stop all, update, start all log "Stopping all services..." for service in "${SERVICES[@]}"; do systemctl stop "$service" || true done # Update to previous version ln -sfn "$rollback_dir" "$CURRENT_LINK" # Start services in dependency order log "Starting services..." for service in "${SERVICES[@]}"; do systemctl start "$service" sleep 2 done # Quick health validation sleep 10 if health_check "foxhunt-core" "http://localhost:8080/health" && \ health_check "foxhunt-tli" "http://localhost:8081/health"; then success "Rollback completed successfully" return 0 else error "Rollback validation failed" return 1 fi } main() { local version="" local strategy="canary" local validate_only=false local skip_performance=false local do_rollback=false # Parse arguments while [[ $# -gt 0 ]]; do case $1 in --strategy) strategy="$2" shift 2 ;; --validate-only) validate_only=true shift ;; --skip-performance) skip_performance=true shift ;; --rollback) do_rollback=true shift ;; -h|--help) usage ;; *) if [ -z "$version" ]; then version="$1" else error "Unknown option: $1" usage fi shift ;; esac done # Handle rollback if [ "$do_rollback" = true ]; then touch "$DEPLOYMENT_MARKER" rollback_deployment return $? fi # Validate version provided if [ -z "$version" ] && [ "$validate_only" = false ]; then error "Version is required for deployment" usage fi # Create deployment marker touch "$DEPLOYMENT_MARKER" log "Starting zero-downtime deployment..." log "Version: $version" log "Strategy: $strategy" log "Validate only: $validate_only" log "Skip performance: $skip_performance" # Validate release exists local release_dir="$RELEASES_DIR/$version" if [ ! -d "$release_dir" ] && [ "$validate_only" = false ]; then error "Release directory not found: $release_dir" return 1 fi # Backup current version backup_current_version # Execute deployment strategy case $strategy in canary) if [ "$validate_only" = true ]; then log "Validation-only mode: would deploy version $version using canary strategy" return 0 else canary_deployment "$version" fi ;; blue-green) error "Blue-green deployment not yet implemented" return 1 ;; *) error "Unknown deployment strategy: $strategy" return 1 ;; esac if [ $? -eq 0 ]; then success "Deployment completed successfully" rm -f "$DEPLOYMENT_MARKER" return 0 else error "Deployment failed" return 1 fi } # Execute main function main "$@"