Skip to content
Merged
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
55 changes: 15 additions & 40 deletions mapping/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -142,49 +142,24 @@ if [ "$SKIP_MODEL_DOWNLOAD" != "1" ]; then
python3 /usr/local/bin/ondemand_model_loader.py
fi

# Execute the provided command or default to API service
exec /home/scenescape/SceneScape/api_service_cmd
# Check if development mode is enabled via environment variable
if [ "$DEV_MODE" = "true" ] || [ "$DEV_MODE" = "1" ] || [ "$DEVELOPMENT" = "true" ]; then
echo "Starting in DEVELOPMENT mode with ${MODEL_TYPE} model..."
exec python ${MODEL_TYPE}_service.py --dev-mode
else
echo "Starting in PRODUCTION mode with TLS and ${MODEL_TYPE} model..."
# Ensure certificate directory exists and has proper permissions
if [ -d "/run/secrets/certs" ]; then
echo "TLS certificates directory found"
else
echo "WARNING: TLS certificates directory not found at /run/secrets/certs"
echo "Make sure to mount certificates for production use"
fi
exec python ${MODEL_TYPE}_service.py
fi
EOF
RUN chmod +x /usr/local/bin/startup.sh

# Create API service script based on model type
ARG MODEL_TYPE
RUN cat <<EOF > $SCENESCAPE_HOME/api_service_cmd
#!/bin/bash

# Enable job control and set up signal handling
set -e
set -m

# Function to handle shutdown
shutdown() {
echo "Received shutdown signal, stopping API service..."
# Kill any background processes
jobs -p | xargs -r kill
exit 0
}

# Set up signal handlers
trap shutdown SIGINT SIGTERM

echo "Starting 3D Mapping ${MODEL_TYPE} API Service..."

# Change to workspace directory
cd $SCENESCAPE_HOME

# Set Python to unbuffered mode for immediate log output
export PYTHONUNBUFFERED=1

# Start Flask service with model-specific service file
echo "Starting Flask service with ${MODEL_TYPE} model on port 8000..."
echo "Press Ctrl+C to stop the service"

# Run Python in foreground with signal handling
exec python ${MODEL_TYPE}_service.py
EOF

RUN chmod +x $SCENESCAPE_HOME/api_service_cmd

# Set working directory
WORKDIR $SCENESCAPE_HOME

Expand Down
1 change: 1 addition & 0 deletions mapping/requirements_api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ flask-cors==4.0.0
gradio
open3d-cpu[headless]==0.19.0
requests
gunicorn==21.2.0
120 changes: 109 additions & 11 deletions mapping/src/api_service_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
Flask service with build-time model selection (no runtime model parameter needed).
"""

import argparse
import base64
import os
import signal
import subprocess
import sys
import tempfile
import time
Expand Down Expand Up @@ -257,9 +259,105 @@ def signalHandler(sig, frame):
log.info("Received SIGINT (Ctrl+C), shutting down gracefully...")
sys.exit(0)

def runDevelopmentServer():
"""Run Flask development server"""
log.info("Starting in DEVELOPMENT mode...")
log.info("Flask development server starting on http://0.0.0.0:8000")
log.info("Press Ctrl+C to stop the server")

try:
# Run Flask development server
app.run(
host="0.0.0.0",
port=8000,
debug=True,
threaded=True
)
except KeyboardInterrupt:
log.info("Server interrupted by user")
except Exception as e:
log.error(f"Server error: {e}")
finally:
log.info("Server shutdown complete")

def runProductionServer(cert_file=None, key_file=None):
"""Run Gunicorn production server with TLS"""
log.info("Starting in PRODUCTION mode with TLS...")

# Check if certificates exist
if not os.path.exists(cert_file):
log.error(f"TLS certificate file not found: {cert_file}")
sys.exit(1)

if not os.path.exists(key_file):
log.error(f"TLS key file not found: {key_file}")
sys.exit(1)

log.info(f"Using TLS certificate: {cert_file}")
log.info(f"Using TLS key: {key_file}")
log.info("Gunicorn HTTPS server starting on https://0.0.0.0:8000")

# Determine the service module based on model type
model_type = os.getenv("MODEL_TYPE", "mapanything")
service_module = f"{model_type}_service:app"

# Gunicorn command arguments
gunicorn_cmd = [
"gunicorn",
"--bind", "0.0.0.0:8000",
"--workers", "1",
"--worker-class", "sync",
"--timeout", "300",
"--keep-alive", "5",
"--max-requests", "1000",
"--max-requests-jitter", "100",
"--access-logfile", "-",
"--error-logfile", "-",
"--log-level", "info",
"--certfile", cert_file,
"--keyfile", key_file,
service_module
]

log.info(f"Starting Gunicorn with service module: {service_module}")

try:
# Run Gunicorn with TLS
subprocess.run(gunicorn_cmd, check=True)
except subprocess.CalledProcessError as e:
log.error(f"Gunicorn failed to start: {e}")
sys.exit(1)
except KeyboardInterrupt:
log.info("Server interrupted by user")
except Exception as e:
log.error(f"Server error: {e}")
sys.exit(1)

def startApp():
"""Start the application with model initialization"""
global device, loaded_model, model_name
"""Start the application with command line argument parsing"""
parser = argparse.ArgumentParser(description="3D Mapping Models API Server")
parser.add_argument(
"--dev-mode",
action="store_true",
help="Run in development mode with Flask development server (default: production mode with Gunicorn + TLS)"
)
Comment on lines +339 to +343
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please consider removing dev-mode, I don't think we need it at all.

parser.add_argument(
"--development",
action="store_true",
help="Alias for --dev-mode"
)
parser.add_argument(
"--cert-file",
default="/run/secrets/certs/scenescape-mapping.crt",
help="Path to TLS certificate file (default: /run/secrets/certs/scenescape-mapping.crt)"
)
parser.add_argument(
"--key-file",
default="/run/secrets/certs/scenescape-mapping.key",
help="Path to TLS private key file (default: /run/secrets/certs/scenescape-mapping.key)"
)

args = parser.parse_args()

# Set up signal handler for graceful shutdown
signal.signal(signal.SIGINT, signalHandler)
Expand All @@ -268,23 +366,23 @@ def startApp():
log.info("Starting 3D Mapping API server...")

# Initialize model before starting server
global device, loaded_model, model_name
device = "cpu"
log.info(f"Using device: {device}")

try:
# Only initialize model if not already loaded
loaded_model, model_name = initializeModel()
log.info("API Service startup completed successfully")

log.info("Flask server starting on http://0.0.0.0:8000")
log.info("Press Ctrl+C to stop the server")
# Determine which server to run
dev_mode = args.dev_mode or args.development or os.getenv("DEV_MODE", "").lower() in ("true", "1", "yes")

if dev_mode:
runDevelopmentServer()
else:
runProductionServer(cert_file=args.cert_file, key_file=args.key_file)

# Run Flask development server
app.run(
host="0.0.0.0",
port=8000,
debug=False,
threaded=True
)
except KeyboardInterrupt:
log.info("Server interrupted by user")
except Exception as e:
Expand Down
4 changes: 4 additions & 0 deletions mapping/src/vggt_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@
from scene_common import log

# Import the base API service
<<<<<<< HEAD
from api_service_base import start_app, app
=======
from api_service_base import startApp
>>>>>>> feature/mapping-service

def initializeModel():
"""Initialize VGGT model"""
Expand Down
16 changes: 14 additions & 2 deletions sample_data/docker-compose-dl-streamer-example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ secrets:
file: ${SECRETSDIR}/certs/scenescape-camcalibration.crt
camcalibration-key:
file: ${SECRETSDIR}/certs/scenescape-camcalibration.key
mapping-cert:
file: ${SECRETSDIR}/certs/scenescape-mapping.crt
mapping-key:
file: ${SECRETSDIR}/certs/scenescape-mapping.key

services:
ntpserv:
Expand Down Expand Up @@ -258,7 +262,8 @@ services:
retail-cams:
condition: service_started
healthcheck:
test: ["CMD", "curl", "-I", "-s", "http://localhost:8080/pipelines"]
test:
["CMD", "curl", "-k", "-I", "-s", "https://localhost:8080/pipelines"]
interval: 10s
timeout: 5s
retries: 5
Expand Down Expand Up @@ -422,8 +427,15 @@ services:
- NO_PROXY=${no_proxy},.scenescape.intel.com,influxdb2
- HTTP_PROXY=${http_proxy}
- HTTPS_PROXY=${https_proxy}
secrets:
- source: root-cert
target: certs/scenescape-ca.pem
- source: mapping-cert
target: certs/scenescape-mapping.crt
- source: mapping-key
target: certs/scenescape-mapping.key
healthcheck:
test: ["CMD", "curl", "-I", "-s", "http://localhost:8000/health"]
test: ["CMD", "curl", "-k", "-I", "-s", "https://localhost:8000/health"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you change also port number? 8000 is typically used for HTTP

interval: 10s
timeout: 60s
retries: 5
Expand Down
20 changes: 16 additions & 4 deletions tools/certificates/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ default: deploy-certificates

#.PHONY targets won't be skipped if files with these names happen to exist
.PHONY: default ca deploy-certificates deploy-csr
.PHONY: broker-cert web-cert vdms-c-cert vdms-s-cert camcalibration-cert
.PHONY: broker-csr web-csr vdms-c-csr vdms-s-csr camcalibration-csr
.PHONY: broker-cert web-cert vdms-c-cert vdms-s-cert camcalibration-cert mapping-cert
.PHONY: broker-csr web-csr vdms-c-csr vdms-s-csr camcalibration-csr mapping-csr

#.SECONDARY prevents make from deleting files created by intermediate targets
.SECONDARY:

deploy-certificates: ca broker-cert web-cert vdms-c-cert vdms-s-cert camcalibration-cert
deploy-certificates: ca broker-cert web-cert vdms-c-cert vdms-s-cert camcalibration-cert mapping-cert

deploy-csr: broker-csr web-csr vdms-c-csr vdms-s-csr camcalibration-csr
deploy-csr: broker-csr web-csr vdms-c-csr vdms-s-csr camcalibration-csr mapping-csr

broker-cert: HOST=broker
broker-cert: KEY_USAGE=serverAuth
Expand Down Expand Up @@ -52,6 +52,12 @@ camcalibration-cert: \
$(SECRETSDIR)/certs/scenescape-camcalibration.key \
$(SECRETSDIR)/certs/scenescape-camcalibration.crt \

mapping-cert: HOST=mapping
mapping-cert: KEY_USAGE=serverAuth
mapping-cert: \
$(SECRETSDIR)/certs/scenescape-mapping.key \
$(SECRETSDIR)/certs/scenescape-mapping.crt \

broker-csr: HOST=broker
broker-csr: KEY_USAGE=serverAuth
broker-csr: \
Expand Down Expand Up @@ -82,6 +88,12 @@ camcalibration-csr: \
$(SECRETSDIR)/certs/scenescape-camcalibration.key \
$(SECRETSDIR)/certs/scenescape-camcalibration.csr \

mapping-csr: HOST=mapping
mapping-csr: KEY_USAGE=serverAuth
mapping-csr: \
$(SECRETSDIR)/certs/scenescape-mapping.key \
$(SECRETSDIR)/certs/scenescape-mapping.csr \

ca: $(CASECRETSDIR)/certs/scenescape-ca.pem

$(CASECRETSDIR)/ca/scenescape-ca.key:
Expand Down