Skip to content

improve local dev for server side development #1051

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
minikube-mount: minikube mount $LOCAL_DIR:/mnt/servicex
helm-install: sleep 5; cd $CHART_DIR && helm install -f $VALUES_FILE servicex . && tail -f /dev/null
port-forward-app: sleep 20 && cd $LOCAL_DIR && bash local/port-forward.sh app
port-forward-minio: sleep 20 && cd $LOCAL_DIR && bash local/port-forward.sh minio
port-forward-db: sleep 20 && cd $LOCAL_DIR && bash local/port-forward.sh db
4 changes: 4 additions & 0 deletions helm/servicex/templates/app/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ data:

CHART = '{{ .Chart.Name }}-{{ .Chart.Version }}'
APP_IMAGE_TAG = '{{ .Values.app.tag }}'

# Application environment and local development settings
APP_ENVIRONMENT = '{{ .Values.app.environment }}'
APP_MOUNT_LOCAL = {{- if .Values.app.mountLocal }}True{{- else }}False{{- end }}

#SERVER_NAME = '127.0.0.1:5000'
# this is the session secret, used to protect the Flask session. You should
Expand Down
19 changes: 19 additions & 0 deletions helm/servicex/templates/app/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ spec:
containers:
- name: {{ .Release.Name }}-servicex-app
image: {{ .Values.app.image }}:{{ .Values.app.tag }}
{{- if eq .Values.app.environment "dev" }}
{{- if .Values.app.reload }}
command: [ "./boot.sh", "--reload" ]
{{- end }}
{{- end }}
env:
- name: APP_CONFIG_FILE
value: "/opt/servicex/app.conf"
Expand Down Expand Up @@ -135,6 +140,12 @@ spec:
{{- end }}

volumeMounts:
{{- if eq .Values.app.environment "dev" }}
{{- if .Values.app.mount_local }}
- name: host-volume
mountPath: /home/servicex
{{- end }}
{{- end }}
- name: app-cfg
mountPath: /opt/servicex
- name: sqlite
Expand All @@ -149,6 +160,14 @@ spec:
- containerPort: 5000

volumes:
{{- if eq .Values.app.environment "dev" }}
{{- if .Values.app.mount_local }}
- name: host-volume
hostPath:
path: /mnt/servicex/servicex_app
type: DirectoryOrCreate
{{- end }}
{{- end }}
- name: app-cfg
configMap:
name: {{ .Release.Name }}-flask-config
Expand Down
2 changes: 2 additions & 0 deletions helm/servicex/values.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
app:
environment: production
mountLocal: false
adminEmail: [email protected]
auth: false
authExpires: 21600
Expand Down
209 changes: 209 additions & 0 deletions local/port-forward.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
#!/usr/bin/env bash

set -euo pipefail

# Constants
readonly NAMESPACE="${NAMESPACE:-default}"
readonly PING_INTERVAL=5
readonly MAX_RETRIES=10

# Parse service configuration
case "${1:-}" in
app)
SERVICE_NAME="servicex-servicex-app"
CONTAINER_PORT="8000"
LOCAL_PORT="5000"
;;
minio)
SERVICE_NAME="servicex-minio"
CONTAINER_PORT="9000"
LOCAL_PORT="9000"
;;
db)
SERVICE_NAME="servicex-postgresql"
CONTAINER_PORT="5432"
LOCAL_PORT="5432"
;;
*)
echo "Usage: $0 [app|minio|db]"
echo " app - Port forward to ServiceX app (5000 -> 8000)"
echo " minio - Port forward to Minio (9000 -> 9000)"
echo " db - Port forward to PostgreSQL (5432 -> 5432)"
exit 1
;;
esac

readonly SERVICE_TYPE="$1"

# Function to log with timestamp
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >&2
}

# Function to check if service exists
check_service_exists() {
kubectl get service "$SERVICE_NAME" --namespace="$NAMESPACE" >/dev/null 2>&1
}

# Function to wait for pod to be ready
wait_for_pod_ready() {
log "Waiting for pods of service $SERVICE_NAME to be ready..."
local retries=0

while [[ $retries -lt $MAX_RETRIES ]]; do
# Get pods for the service using selector
local selector=$(kubectl get service "$SERVICE_NAME" --namespace="$NAMESPACE" -o jsonpath='{.spec.selector}' 2>/dev/null || echo "")

if [[ -n "$selector" ]]; then
# Convert JSON selector to kubectl selector format
local kubectl_selector=$(echo "$selector" | sed 's/[{}"]//g' | sed 's/:/=/g' | sed 's/,/,/g')

# Check if any pods are ready
local ready_pods=$(kubectl get pods --namespace="$NAMESPACE" --selector="$kubectl_selector" --field-selector=status.phase=Running -o name 2>/dev/null | wc -l)

if [[ $ready_pods -gt 0 ]]; then
log "Found $ready_pods ready pod(s) for service $SERVICE_NAME"
return 0
fi
fi

((retries++))
log "No ready pods found, retrying... ($retries/$MAX_RETRIES)"
sleep 5
done

log "No ready pods found for service $SERVICE_NAME after $MAX_RETRIES attempts"
return 1
}

# Function to wait for service availability
wait_for_service() {
log "Waiting for service $SERVICE_NAME to be available..."
local retries=0

while ! check_service_exists && [[ $retries -lt $MAX_RETRIES ]]; do
((retries++))
log "Service not found, retrying... ($retries/$MAX_RETRIES)"
sleep 3
done

if [[ $retries -ge $MAX_RETRIES ]]; then
log "Service $SERVICE_NAME not found after $MAX_RETRIES attempts"
return 1
fi

log "Service $SERVICE_NAME is available"
return 0
}

# Function to start port forwarding using service
start_port_forward() {
log "Starting port forwarding: localhost:${LOCAL_PORT} -> ${SERVICE_NAME}:${CONTAINER_PORT}"

# Check if pods are still ready before attempting port forward
if ! wait_for_pod_ready; then
log "Pods not ready, cannot start port forwarding"
return 1
fi

kubectl port-forward --namespace="$NAMESPACE" "service/$SERVICE_NAME" "${LOCAL_PORT}:${CONTAINER_PORT}" &
PORT_FORWARD_PID=$!

# Give it a moment to establish connection
sleep 2

# Check if port forward process is still running
if ! kill -0 "$PORT_FORWARD_PID" 2>/dev/null; then
log "Port forwarding failed to start"
return 1
fi

log "Port forwarding established (PID: $PORT_FORWARD_PID)"
return 0
}

# Function to check if port forwarding is working
check_port_forward() {
# Simply check if the port forward process is still running
if ! kill -0 "$PORT_FORWARD_PID" 2>/dev/null; then
log "Port forward process is not running"
return 1
fi

# Additional check: verify port is actually listening
if command -v nc >/dev/null 2>&1; then
if ! nc -z localhost "$LOCAL_PORT" 2>/dev/null; then
log "Port $LOCAL_PORT is not accessible"
return 1
fi
fi

return 0
}

# Function to clean up resources
cleanup() {
log "Cleaning up..."
if [[ -n "${PORT_FORWARD_PID:-}" ]]; then
log "Terminating port forwarding process (PID: $PORT_FORWARD_PID)"
kill "$PORT_FORWARD_PID" 2>/dev/null || true
wait "$PORT_FORWARD_PID" 2>/dev/null || true
fi
log "Cleanup complete"
exit 0
}

# Set trap for cleanup
trap cleanup SIGINT SIGTERM EXIT

# Main execution
main() {
log "Starting port forwarding for $SERVICE_TYPE: $SERVICE_NAME"

# Wait for service to be available
if ! wait_for_service; then
log "Service $SERVICE_NAME is not available"
exit 1
fi

# Wait for pods to be ready
if ! wait_for_pod_ready; then
log "No ready pods found for service $SERVICE_NAME"
exit 1
fi

# Main monitoring loop
while true; do
# Start port forwarding if not already running
if [[ -z "${PORT_FORWARD_PID:-}" ]] || ! kill -0 "$PORT_FORWARD_PID" 2>/dev/null; then
log "Starting port forwarding..."
if ! start_port_forward; then
log "Failed to start port forwarding, retrying in $PING_INTERVAL seconds..."
# Reset PID if it exists but failed
if [[ -n "${PORT_FORWARD_PID:-}" ]]; then
unset PORT_FORWARD_PID
fi
sleep "$PING_INTERVAL"
continue
fi
fi

# Check if port forwarding is working
if ! check_port_forward; then
log "Port forwarding failed, restarting..."
if [[ -n "${PORT_FORWARD_PID:-}" ]]; then
kill "$PORT_FORWARD_PID" 2>/dev/null || true
wait "$PORT_FORWARD_PID" 2>/dev/null || true
unset PORT_FORWARD_PID
fi
sleep 2
continue
fi

# Wait before next check
sleep "$PING_INTERVAL"
done
}

# Run main function
main
15 changes: 14 additions & 1 deletion servicex_app/boot.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
#!/bin/sh

# Initialize reload flag
RELOAD=""

# Parse command line arguments
for arg in "$@"
do
if [ "$arg" = "--reload" ]; then
RELOAD="--reload"
break
fi
done

mkdir instance
# SQLite doesn't handle migrations, so rely on SQLAlchmy table creation
if grep "sqlite://" $APP_CONFIG_FILE; then
Expand All @@ -7,5 +20,5 @@ else
FLASK_APP=servicex_app/app.py flask db upgrade;
fi
[ -d "/default_users" ] && python3 servicex/cli/create_default_users.py
exec gunicorn -b [::]:5000 --workers=5 --threads=1 --timeout 120 --log-level=warning --access-logfile /tmp/gunicorn.log --error-logfile - "servicex_app:create_app()"
exec gunicorn -b [::]:5000 $RELOAD --workers=5 --threads=1 --timeout 120 --log-level=warning --access-logfile /tmp/gunicorn.log --error-logfile - "servicex_app:create_app()"
# to log requests to stdout --access-logfile -
Loading